mirror of
https://origin.cursor.com/mrdevmx/panels.git
synced 2026-10-09 12:43:18 +00:00
<!-- CURSOR_AGENT_PR_BODY_BEGIN --> ## Problema En el expediente del padrón, al ver un documento el navegador lo trataba como descarga (cabeceras `application/octet-stream` + `attachment` y blob sin MIME). No había forma de eliminar archivos cargados y al subir otro del mismo tipo algunos entornos fallaban o no refrescaban bien la lista vigente. ## Cambios - **API:** los GET de documentos (trabajador, proyecto, empresa) envían el `Content-Type` real del archivo y `Content-Disposition: inline` por defecto (`?disposition=attachment` para forzar descarga). - **UI:** `DocumentPreviewDialog` pide `disposition=inline` y reconstruye el blob con MIME correcto para que PDF/imagen se vean en el diálogo. - **Eliminar:** RPC `core.fn_worker_document_delete`, `DELETE /v1/workers/:id/documents/:docId`, permiso `documents.delete` (incluido en `manage_documents`), botón de papelera en vigentes e histórico. - **Reemplazar:** `fn_worker_document_store` marca como no vigentes **todas** las filas del mismo tipo antes del INSERT; se limpia el `FileUpload` tras subir; filtro `is_current` tolera booleano o `1`. ## Migraciones - Core: `040-fn-worker-document-delete.sql`, `041-fn-worker-document-store-replace.sql` - IAM: `006-documents-delete-permission.sql` ## Verificación Tras aplicar Liquibase, en un trabajador del padrón: abrir ojo en PDF/JPG (debe verse en modal), eliminar un doc de prueba, subir dos veces el mismo tipo (debe quedar una vigente y la anterior en histórico). <!-- CURSOR_AGENT_PR_BODY_END --> <div><a href="https://cursor.com/agents/bc-d8a5d34a-ead9-4912-8296-322cff799783?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a> <a href="https://cursor.com/background-agent?bcId=bc-d8a5d34a-ead9-4912-8296-322cff799783&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a> </div>
24 lines
1 KiB
TypeScript
24 lines
1 KiB
TypeScript
/** Cabeceras HTTP para bytes de documento cifrado ya descifrados. */
|
|
export function documentServeHeaders(
|
|
doc: { original_name: string; mime?: string },
|
|
opts: { inline?: boolean } = {},
|
|
): Record<string, string> {
|
|
const inline = opts.inline !== false;
|
|
const mime = (doc.mime || "").trim() || "application/octet-stream";
|
|
const name = doc.original_name || "documento";
|
|
const disposition = inline ? "inline" : "attachment";
|
|
return {
|
|
"Content-Type": mime,
|
|
"X-Content-Type-Options": "nosniff",
|
|
"Content-Disposition": `${disposition}; filename="${encodeURIComponent(name)}"`,
|
|
"Cache-Control": "private, max-age=60",
|
|
};
|
|
}
|
|
|
|
export function wantsInlineDocumentDisposition(url: URL | string): boolean {
|
|
const u = typeof url === "string" ? new URL(url, "http://local") : url;
|
|
const d = (u.searchParams.get("disposition") || u.searchParams.get("inline") || "").toLowerCase();
|
|
if (d === "attachment" || d === "download" || d === "0" || d === "false") return false;
|
|
if (d === "inline" || d === "1" || d === "true") return true;
|
|
return true;
|
|
}
|