Ir al contenido
Portal de documentación del EGD
Menú de la sección

Ventana de anexos («Cargar anexos» y «Anexos del documento»)

Especificación de AttachmentsModal (app/src/app/dashboard/documentos/administrativos/nuevo/anexos/attachments-modal.tsx). La abren el formulario de emisión, la lista de referencias y las bandejas. Se apoya en ReferenceTreePanel, DialogContent redimensionable (ui/dialog-resizable.tsx), el divisor SidebarResizeHandle con ui/use-split-panel.ts y ConfirmDialog.

Estructura y tamaño

  • Con árbol de referencias: dos paneles, árbol a la izquierda y detalle con la lista de anexos a la derecha. La ventana abre grande (como la de Seguimiento). El árbol ocupa el 40 % del ancho y su borde derecho es un divisor arrastrable (ratón, táctil y teclado: ← →, Inicio, Fin, doble clic o Enter para restablecer; clic o Espacio abre los controles de ancho). Límites: 25 % a 60 % del ancho y no menos de 256 px. La proporción se recuerda (sgd_anexos_arbol_ratio) y se conserva al ampliar. Con menos de 48 rem de ancho los paneles se apilan y no hay divisor.
  • Sin árbol: una sola columna.
  • Tabla o lista: según el ancho de la propia lista (consulta de contenedor), desde 38 rem es una tabla con columnas fijas selección 2 rem | archivo (el resto) | Requiere firma 4,5 rem | Firmar / visar 10 rem | Opciones 5 rem; más angosta, cada anexo ocupa dos líneas. Las columnas fijas miden lo justo para que el nombre del archivo tenga el resto del ancho. El encabezado dice «Requiere firma» completo, en dos líneas. Sin firma disponible (documento ajeno o solo lectura) las columnas de selección y de firma miden 0.

Acciones de cada anexo

AcciónPresentaciónNotas
VerIcono sin borde con tooltip «Ver» y el icono del tipo de archivo (ver «Iconos por tipo de archivo»)Criterio común: toda acción que abre un PDF lleva picture_as_pdf
Eliminar anexoPapelera roja con tooltipSolo documento propio en «En proyecto»; pide confirmación
Traer anexoIcono con tooltip, en los anexos de una referenciaCopia el anexo al documento propio
Requiere firmaCasilla (solo PDF); al guardar, el indicador de carga se dibuja encima de la casilla y no ocupa sitio, así que nada se mueveLa marca es la solicitud de firma al firmante: no se quita al firmar quien elabora, solo con la firma o el visto bueno del firmante en «Para despacho» (se comprueba su firma en el PDF) o con «No requiere mi firma». Se puede volver a marcar aunque el anexo ya esté firmado
Firmar / VisarBotones que aparecen con la casilla marcadaReemplazan el archivo por el PDF firmado
Marcar todos los PDFCasilla de tres estados en la barra de la lista, desde dos PDF, con «N de M requieren firma»Marca los que faltan o, si ya están todos, los desmarca; cada PDF se guarda por su cuenta y una falla no deshace los demás

Iconos por tipo de archivo

El icono del botón «Ver» se elige por el tipo del archivo con fileTypeOf (app/src/lib/file-type.ts), que usan la ventana de anexos, Reportes y Mesa de Partes. Se mira la última extensión del nombre, sin distinguir mayúsculas («informe.final.DOCX» es un documento de texto); si el nombre no sirve, se usa el tipo MIME; sin ninguno de los dos, el icono genérico. Sin color: el tipo se distingue por la forma. Son iconos de Material Symbols, no los logos de Word o Excel.

TipoExtensionesIcono
PDFpdfpicture_as_pdf
Documento de textodoc, docx, odt, rtf, txtdescription
Hoja de cálculoxls, xlsx, ods, csvtable_chart
Imagenjpg, jpeg, png, gif, webp, bmp, tif, tiffimage
Presentaciónppt, pptx, odpslideshow
Archivo comprimidozip, rar, 7zfolder_zip
Otro tipo, o sin extensióncualquier otradraft

Casos: un nombre sin extensión con tipo MIME de imagen recibe el icono de imagen; una doble extensión («datos.final.xlsx») usa la última; una extensión desconocida («.desconocido») usa el genérico. El catálogo con prueba de nombres está en /design-system/componentes («Iconos por tipo de archivo»). Un tipo nuevo se agrega en la tabla de file-type.ts y, si su icono no está en la fuente alojada, con npm run icons:build.

Selección y eliminación de varios anexos

Disponible solo en el documento propio y en «En proyecto» (la misma condición que la papelera de cada fila) y desde dos anexos: con uno solo no hay casillas ni barra de selección; basta la papelera de la fila. Se oculta en solo lectura y en los anexos de una referencia.

  1. Cada fila lleva una casilla («Seleccionar nombre-del-archivo») y el encabezado una casilla de tres estados («Seleccionar todos los anexos»). Con la lista angosta, que no tiene encabezado, «Seleccionar todos» está en la barra.
  2. Al marcar la primera casilla, la barra de «Marcar todos los PDF» se reemplaza por la barra de selección: «N seleccionado(s)», «Seleccionar todos» (mientras falte alguno), «Cancelar» (botón de texto, con nombre accesible «Cancelar selección») y «Eliminar» (botón con borde rojo y papelera, nombre accesible «Eliminar seleccionados»). Con un solo anexo restante la barra desaparece y se limpia la selección. El número se anuncia a los lectores de pantalla (aria-live).
  3. Mayús + clic en una casilla marca o desmarca el rango desde la última casilla tocada.
  4. «Eliminar» abre la confirmación: título «Eliminar N anexos», texto «Se eliminarán N anexos del documento y no se podrán recuperar.», lista de hasta cuatro nombres y «y N más», y botón rojo «Eliminar N anexos». Con un solo anexo se conserva el aviso de siempre («Eliminar anexo» / «¿Estás seguro de que deseas eliminar este anexo?» / «Eliminar»). ConfirmDialog admite contenido bajo la descripción (children), que aquí lleva la lista.
  5. Se eliminan de a cinco a la vez, con el botón de la confirmación en «Procesando…». Cada anexo se elimina por su cuenta con la misma operación que la papelera: no hay cambios en el servidor.

Casuísticas

SituaciónComportamiento
Documento ajeno (anexos de una referencia) o solo lecturaSin casillas, sin barra de selección y sin papelera
Sin anexosEstado vacío; sin barra ni casillas
Un solo anexoSin casillas ni barra de selección; solo la papelera de la fila; «Marcar todos los PDF» no aparece (desde dos PDF)
Se elimina hasta quedar unoLa barra de selección desaparece, la casilla de la fila también y la selección se limpia
Selección parcialBarra de selección con el conteo; casilla del encabezado en estado intermedio
Todos seleccionadosCasilla del encabezado marcada; no se ofrece «Seleccionar todos»; esa casilla desmarca todos
Conservar uno de varios (por ejemplo 19 de 20)«Seleccionar todos», quitar la casilla del que se conserva y «Eliminar seleccionados»
RangoMayús + clic aplica a todas las filas entre la última casilla tocada y la actual, con el estado de la casilla pulsada
TecladoTab llega a cada casilla; Espacio la cambia; los botones de la barra son accesibles con Tab
Cancelar, X o Escape en la confirmaciónNo se elimina nada y la selección se conserva
Eliminación correctaLos anexos salen de la lista y de la selección; aviso «Anexos eliminados · Se eliminaron N anexos.» y anuncio a lectores de pantalla
Falla en algunosLos eliminados salen; los que fallaron siguen en la lista y seleccionados; aviso de error «No se pudo eliminar K de N anexos» con el motivo del primero; se puede reintentar
Fallan todosNada cambia; aviso «No se pudo eliminar K de N anexos»
Falla al eliminar uno (papelera)Aviso «No se pudo eliminar el anexo»; el anexo sigue
Papelera de una fila con una selección activaSolo se elimina esa fila; sale de la selección y el resto sigue seleccionado
Cambiar de documento en el árbol o cerrar la ventanaLa selección se limpia
Subir archivos con una selección activaLos nuevos no quedan seleccionados; «Seleccionar todos» sí los incluye
Selección activa y «Marcar todos los PDF»La barra de selección reemplaza a la de firma; al cancelar o terminar, esta reaparece
Lista angostaSin encabezado de columnas: «Seleccionar todos» en la barra y la casilla de cada fila a la izquierda del nombre
Lista largaLa lista se desplaza dentro de su recuadro; la barra y el encabezado quedan fijos arriba
Nombres repetidosSe distinguen por tamaño y fecha; la etiqueta accesible repite el nombre del archivo
Anexo ya firmado o visadoSe elimina igual y no se conserva copia. El sistema no guarda que un anexo esté firmado, por lo que el aviso no distingue ese caso (ver «Pendientes»)
Documento que dejó de estar «En proyecto»El servidor rechaza la eliminación (como al subir) y se muestra el error
DeshacerNo existe: la eliminación es definitiva

Cantidades y plurales

Los contadores usan countLabel (app/src/lib/plural.ts): «1 documento», «2 documentos», con separador de miles de Perú. Aplica a «N documentos» del árbol de referencias, «N archivos · N requieren firma», «N seleccionados» y los totales de las bandejas. Las frases con verbo concuerdan («1 requiere firma», «1 coincide», «Se exportó 1 documento»).

Accesibilidad

  • Cada casilla tiene nombre accesible propio («Seleccionar …», «Requiere firma: …», «Seleccionar todos los anexos»). Los títulos de columna son decorativos para lectores de pantalla (aria-hidden); la casilla del encabezado sí es accesible.
  • El conteo de la selección y los resultados de eliminar se anuncian (aria-live y el aviso general).
  • El divisor es un separator vertical con foco y valores accesibles.
  • Contraste: la fila seleccionada usa el color primario al 5 % y la barra de selección al 10 %, con texto del color de primer plano.

Pendientes

  • Reemplazar archivo: el SGD antiguo permitía reemplazar el archivo de un anexo sin eliminarlo. Aplazado por decisión expresa. Propuesta: botón con el icono de dos flechas («Reemplazar archivo») entre «Ver» y «Eliminar», flujo «elegir y luego confirmar», endpoint PUT /documents/:id/attachments/:attachmentId/file con el permiso de anexos, la marca de firma se quita si el archivo nuevo no es PDF.
  • Trazabilidad de firma en el servidor (quién, cuándo y motivo; bloquear la emisión con un anexo con firma pendiente; conservar el original): aplazada. Resolvería que «Marcar todos los PDF» y la eliminación distingan los anexos ya firmados.
  • Descripción editable por anexo del SGD antiguo: sin definir.

Verificación (24-oct-2026)

Chromium con la ventana real y /api/backend simulado: 20 anexos, «Seleccionar todos» y quitar uno da «19 seleccionados»; la confirmación lista cuatro nombres y «y 15 más»; se emiten 19 eliminaciones con un máximo de 5 simultáneas y queda el anexo no seleccionado; con una falla simulada, los demás se eliminan y el que falló sigue seleccionado; el rango con Mayús marca seis filas; la papelera individual conserva el aviso original; al reabrir la ventana no hay selección; la barra sigue visible al desplazar la lista; con 600 px aparece «Seleccionar todos» y no el encabezado; en solo lectura no hay casillas ni papeleras. No verificado: lector de pantalla real, táctil, modo oscuro y los seis temas.

Catálogo de iconos por tipo (25-oct-2026)

En /design-system/componentes («Iconos por tipo de archivo») los formatos se muestran como etiquetas sin punto (PDF, DOCX, XLSX…) y la prueba no pide escribir nada: el formato se elige de una lista de formatos comunes y el catálogo muestra un archivo de ejemplo (Informe técnico 012-2026.docx), su tipo y su icono. Quien usa el catálogo no necesita conocer las extensiones. Es solo la forma de explicarlo: el sistema sigue detectando el tipo por la última extensión del nombre real del archivo y, de respaldo, por el tipo MIME (fileTypeOf).

Firma del jefe sobre los anexos (25-oct-2026)

  • signerMode: el firmante del documento en «Para despacho» abre la ventana con signerMode (el documento sigue en solo lectura). La lista muestra por anexo PDF «Por firmar» o «Sin pendiente» y, si está pendiente, Firmar, Visar y No requiere mi firma (ConfirmDialog). Columnas TABLE_COLS_SIGNER: 0 | archivo | estado 5 rem | acciones 13 rem | opciones 5 rem. No hay selección ni eliminación. Arriba, un aviso (role="status") cuenta los pendientes.

  • Insignia «Firmado» / «Visado» (SignedBadge, icono verified, tooltip con los firmantes): sale en todo anexo PDF con signed verdadero, también en documentos ajenos o referenciados. «Visado» solo si todas las firmas son visto bueno.

  • Servidor: la detección (pdfsig.ListSignatures) se guarda normalizada: una fila por firma en SGD_DOCUMENT.TRS_ATTACHMENT_SIGNATURE (orden, nombre, DNI y si es visto bueno) y TRS_ATTACHMENT.SIGNATURES_CHECKED_AT, que distingue «sin analizar» de «analizado, sin firmas»; la cantidad de firmas es el número de filas. Se guarda al subir, traer o firmar el anexo, y los anteriores se analizan una vez al consultarlos. ToggleAttachmentSignature: en proyecto marca o quita (solo PDF); en «Para despacho» solo el firmante y solo para quitar. SignAttachment: quien elabora no apaga la marca; el firmante debe subir un PDF con firma o visto bueno nuevo y suyo (DNI) para apagarla. Emit y EmitWithoutSignature rechazan con AttachmentsPendingError (HTTP 422) si queda alguno.

  • Botón de la tarjeta: ViewAttachmentsAction recibe pendingSignCount y Action pendingLabel: botón ámbar con la insignia «N por firmar».

  • ZIP del árbol (30/10/2026): «Zip de todo el árbol» se prepara en segundo plano y su panel flota abajo a la izquierda, por encima de la capa oscura de la ventana (ver «Capa flotante sobre un diálogo» en dialogs.md). El botón de la ventana refleja el mismo estado: «Preparando ZIP…» (apagado, con indicador), «Descargar ZIP» cuando está listo para este documento y «Zip de todo el árbol» en reposo o tras un error. Pulsar «Descargar ZIP» (en el botón o en el panel) no cierra la ventana. «Zip de este documento» es una descarga directa y no usa el panel.