Saltar a contenido

Pantallas del sistema

El frontend de OrpycaMCP (frontend/, SvelteKit 2 + Svelte 4 SSR, E22) es la única capa de presentación oficial del sistema. Ninguna pantalla accede a datos directamente: cada vista tiene su propia subcarpeta api/ que actúa como Backend-for-Frontend (BFF) — recibe la acción del navegador, adjunta el JWT (guardado en cookie httpOnly, nunca visible al JS del cliente) y reenvía la petición al api-gateway. Ver el detalle de esta arquitectura en Arquitectura, sección "Flujo de autenticación".

La navegación y las acciones visibles en cada pantalla reflejan el RBAC real del usuario (PERM_RADI, USUA_PERM_EXPEDIENTE, USUA_PERM_ADMIN, PERM_RADI_SALIDA, PERM_FIRMA, PERM_DLQ_ADMIN, …), pero esa restricción es solo defensa en profundidad en la UI — el backend siempre revalida el permiso y el nivel de clearance (RF-SEG-08) por request.

Pantallas públicas ((public)/)

Ruta Propósito
(public)/ Landing pública del producto: qué es OrpycaMCP, marco normativo (Ley 594/2000, Acuerdo AGN 060/2001), capacidades, comparativa con Orfeo y licencia AGPL v3. Si locals.user está poblado (sesión activa), su +page.server.js redirige 302 a /dashboard — un usuario autenticado nunca la ve

Autenticación

Ruta Propósito
(public)/login Inicia el flujo OAuth2 Authorization Code + PKCE contra Keycloak. Composición split: en móvil solo la tarjeta de ingreso; desde 960px aparece el panel de marca. Si Keycloak devuelve ?error=, muestra un mensaje mapeado desde una tabla cerrada en español (nunca el string crudo del IdP) con enlace «Reintentar» — evita el bucle de redirección
(public)/login/callback Recibe el código de autorización, lo intercambia por tokens (server-side) y establece la sesión
(public)/logout Cierra sesión local y en Keycloak
/403 Página de acceso denegado cuando el usuario no tiene el permiso requerido

Pantallas operativas ((app)/)

Ruta Pantalla Propósito Rol típico Endpoints principales (vía gateway)
/dashboard Panel personal Cuatro módulos personales visibles sin scroll: 01 Radicados pendientes, 02 Actividad reciente, 03 Alertas (radicados abiertos por due_date, RF-RAD-04), 04 Favoritos. Las métricas agregadas del tenant quedan en una sección secundaria colapsable, cerrada por defecto y gateada por SGD_PERM_ESTADISTICA Todos los usuarios autenticados (métricas: SGD_PERM_ESTADISTICA) GET /api/v1/documents, GET /api/v1/reports/radicados (solo la sección de métricas)
/bandeja Bandeja de entrada/trámites Ver, tramitar, devolver, anular o responder radicados asignados; solicitar/otorgar vistos buenos (individual o en cadena); ajustar nivel de seguridad; ver/fijar la disposición final (TRD) del radicado individual, con confirmación explícita para la acción de eliminación; el drawer de detalle incluye una sección "Respuestas" con las Salidas vinculadas Operador de correspondencia, funcionario de dependencia GET/PATCH /api/v1/documents, /api/v1/workflows, /api/v1/documents/{id}/anulacion, /api/v1/documents/{id}/respuesta, GET /api/v1/documents/{id}/respuestas, GET/PATCH /api/v1/documents/{id}/disposition
/radicar Radicación de documentos Registrar un radicado de entrada, salida o interno con sus anexos; botón "Sugerir con antecedentes" (RAG con citas, F6) para el cuerpo de Salida/Interno y chips de tipo documental sugerido (nunca autoaplicados) junto al selector de clasificación Ventanilla única, operador de correspondencia (PERM_RADI, PERM_RADI_SALIDA) POST /api/v1/documents, POST /api/v1/storage/upload, POST /api/v1/knowledge/rag, POST /api/v1/knowledge/antecedentes
/borradores Borradores Preparar un radicado antes de asignarle número; aprobarlo o radicarlo definitivamente; botón "Sugerir con antecedentes" (RAG con citas, F6) en el editor del cuerpo Funcionario redactor GET/POST /api/v1/documents/borradores, acción "radicar", POST /api/v1/knowledge/rag
/busqueda Búsqueda de documentos Tres pestañas deep-linkeables (?tab=): Exacta (full-text FTS PostgreSQL con filtros), Semántica (documentos parecidos por significado, similitud vectorial) y Antecedentes (trámites relacionados, por texto o por un radicado pivote verificado) — F6. Los resultados de Semántica/Antecedentes se presentan siempre como "parecidos", nunca como coincidencia exacta; el recorte por permisos de acceso es invisible por diseño Todos los usuarios con permiso de consulta GET /api/v1/search, POST /api/v1/knowledge/search, POST /api/v1/knowledge/antecedentes, GET /api/v1/documents/by-tracking/{tracking}
/expedientes Listado de expedientes Ver expedientes abiertos/cerrados/transferidos, crear uno nuevo Archivista, funcionario con USUA_PERM_EXPEDIENTE GET/POST /api/v1/expedientes
/expedientes/[id] Detalle de expediente Agregar/quitar radicados, ver anexos, cerrar el expediente, descargar el índice electrónico XML firmado, exportar ZIP, iniciar transferencia; ver (solo lectura) la disposición final materializada al cierre o, si sigue abierto, una vista previa de la regla de la serie TRD asignada Archivista GET/PATCH /api/v1/expedientes/{id}, GET /api/v1/expedientes/{id}/indice, GET /api/v1/expedientes/{id}/export, GET /api/v1/trd/{serie_id}
/archivo-fisico Archivo físico Gestionar ubicaciones (direcciones recursivas), unidades de conservación, préstamos y su FUID Custodio de archivo central GET/POST /api/v1/ubicaciones, /api/v1/unidades, /api/v1/prestamos
/transferencias Transferencias documentales Enviar/recibir/rechazar transferencias entre archivo de gestión y central, generar FUID Archivista, jefe de dependencia POST /api/v1/transferencias, acciones enviar/recibir/rechazar
/transferencias/[id] Detalle de transferencia (acta-certificado) Ver el acta de entrega en apariencia de certificado (qué/entre quién/cuándo), el sello institucional XAdES, y firmar personalmente como elaborador/remitente/receptor — el rol de cada persona se deriva de su identidad, nunca se elige; verificar sello y firmas on-demand Archivista, jefe de dependencia con USUA_PERM_EXPEDIENTE (firma personal: además PERM_FIRMA de facto, gate del backend) GET /api/v1/transferencias/{id}, .../acta, .../acta/verificacion, POST .../acta/firmar-personal, POST .../acta/firmar
/firmas Firmas electrónicas Firmar (individual o en lote) radicados/expedientes pendientes, rechazar firma Firmante autorizado (PERM_FIRMA) GET/POST /api/v1/signature, /api/v1/signature/cadena
/envios Envíos/correspondencia postal Gestionar remesas hacia el operador postal externo, su confirmación de entrega, y "Actualizar rastreo" (consulta PULL contra el operador, F5) que declara explícitamente cuando no hay integración en vivo (operador_conectado=false) en vez de aparentar una consulta real Operador de correspondencia POST /api/v1/documents/{id}/envios, GET /api/v1/envios, GET /api/v1/envios/{id}/tracking, webhook entrante del operador
/anulaciones Resolver anulaciones Cola de trabajo del aprobador de anulaciones (rol distinto del solicitante, que pide desde /bandeja con PERM_SOL_ANULAR): buscar por número de radicado o asunto, ver el estado de la solicitud en el drawer (causal, motivo, fechas, observación) y aprobar o rechazar con motivo obligatorio. Aprobar es el acto destructivo e irreversible (el radicado pasa a anulado) y por eso va en variant="danger" con la advertencia "Esta acción no se puede deshacer", mientras que rechazar es conservador. El motivo es obligatorio aunque el esquema del backend lo tenga como opcional —es un acto con efectos jurídicos y traza de auditoría (Ley 594/2000 art. 28)— y el proxy rechaza con 422 en vez de truncar silenciosamente a 2000 caracteres. Limitación declarada en pantalla (banner permanente, no disimulada): el backend no expone ninguna cola de anulaciones pendientes —solo el estado por radicado— así que la pantalla obliga a buscar radicado por radicado; sin paginación (20 resultados por búsqueda) y sin acciones en lote. Honestidad de interfaz: "no se pudo consultar el servidor de búsqueda" es un estado distinto de "ningún radicado coincide", y el vacío legítimo advierte además que "esto no significa que no haya anulaciones pendientes"; cada fila resuelve su estado por separado, con chips propios para Sin solicitud / No se pudo consultar y botón de reintento por fila Aprobador de anulaciones (PERM_PANU_CODI, gate real del backend en POST /anulacion/{aprobar,rechazar}). Desajuste UI↔backend: GET /documents/{id}/anulacion y GET /search, que la pantalla también consume, no tienen require_permission en el backend hoy (solo aislamiento por tenant y no-read-up por clearance) — la restricción de esos dos es únicamente la del gate de la pantalla GET /api/v1/search, GET /api/v1/documents/{id}/anulacion, POST /api/v1/documents/{id}/anulacion/aprobar, POST /api/v1/documents/{id}/anulacion/rechazar
/perfil Mi perfil Cuatro secciones en orden deliberado: identidad (usuario, correo, tenant, roles y permisos efectivos, solo lectura, sin llamada extra); segundo factor TOTP (Stepper generar secreto → verificar código → activo, con reemplazo y revocación tras step-up con el código vigente); credencial de firma electrónica PKI (solicitar CSR, ver vigencia, reemplazar o revocar), y dependencia activa (conmutador de dependencia). El secreto TOTP y el CSR se muestran una sola vez y la pantalla lo dice: si se pierden, la única salida es generar otros. La sección de firma está bloqueada hasta tener TOTP activo, con enlace en página a la sección requerida, en vez de dejar que el usuario choque contra el error del backend. Reemplazar una credencial activa la revoca de inmediato y la deja inservible hasta una re-emisión fuera de línea, así que exige código TOTP y escribir una frase exacta de confirmación —una barrera deliberadamente más alta que la del reemplazo de TOTP. Limitaciones declaradas: no hay criptografía en el navegador (la clave privada se genera y custodia en auth-service), no hay QR para el TOTP, la emisión del certificado es fuera de banda, y no hay cambio de contraseña, edición de identidad ni gestión de sesiones. Nota legal explícita: custodia en servidor ⇒ no hay control exclusivo del firmante, equivalencia funcional de firma electrónica (Ley 527/1999 art. 7), no firma digital acreditada (art. 28). Las dos facetas remotas se cargan con Promise.allSettled y cada una lleva su propio aviso de error Cualquier usuario autenticado (identidad propia: los routers de /auth/me/totp, /auth/me/signing-key y /auth/context autorizan por identidad, sin permiso). Desajuste UI↔backend: POST /auth/me/signing-key/enroll sí exige PERM_FIRMA en el backend y la UI no lo refleja — un usuario con TOTP activo pero sin PERM_FIRMA ve el botón habilitado y recibe un 403 presentado como fallo genérico; el comentario de navItems.js que afirma que todos los routers consumidos "solo exigen get_caller" es falso para ese endpoint. Asimetría adicional: la auto-revocación (DELETE /auth/me/signing-key) no exige PERM_FIRMA, solo step-up TOTP GET /api/v1/auth/me/totp, POST /api/v1/auth/me/totp/{enroll,activate}, DELETE /api/v1/auth/me/totp, GET /api/v1/auth/me/signing-key, POST /api/v1/auth/me/signing-key/enroll, DELETE /api/v1/auth/me/signing-key, GET /api/v1/auth/context, POST /api/v1/auth/context/switch
/reportes Reportes Generar y exportar reportes (CSV) de gestión documental; pestaña "Índice de clasificados" (Ley 1712 art. 20) — registro content-free de radicados reservados/clasificados con su fundamento jurídico, sin exponer asunto/contenido Jefe de dependencia, administrador (índice de clasificados: PERM_RECLASIFICAR) GET /api/v1/reports/radicados, GET /api/v1/reports/indice-reservado[.csv]
AssistantDock (no es una ruta) Asistente conversacional Chat en lenguaje natural que consulta el sistema (RAG con citas) traduciendo la pregunta a llamadas MCP de solo lectura. Es un panel flotante montado en AppLayout.svelte, disponible desde cualquier pantalla: no existe una página /asistente, solo el proxy BFF (app)/asistente/api/message Cualquier usuario autenticado POST /api/v1/assistant/message (vía mcp-server)
/admin Índice de administración Mapa de tarjetas del panel de administración (18 áreas hoy). Cada tarjeta declara el require_permission real del router que consume su subruta, no un USUA_PERM_ADMIN genérico — por eso TRD / CCD, TVD y Metadatos exigen USUA_PERM_TRD, Colas de errores exige PERM_DLQ_ADMIN, Preservación exige USUA_PERM_EXPEDIENTE e Interoperabilidad abre con USUA_PERM_EXPEDIENTE o PERM_RADI. Una tarjeta cuya área aún no tiene subruta se muestra deshabilitada con badge de texto «Próximamente» + candado (no solo por color) en vez de omitirse: el mapa de administración se comunica completo sin mentir sobre lo que funciona; hoy las 18 están implementadas. La pantalla no llama al gateway: ADMIN_AREAS es un catálogo estático de la UI filtrado con la misma filterVisibleNavItems que usan SidebarNav y el /dashboard. Sin ningún permiso de administración se muestra un EmptyState, no una página vacía. Corregido en el lote 1 de la revisión UX 2026-08-02, que cruzó las tarjetas contra el gate real (dos declaraban mal su permiso) y enlazó las dos pantallas que existían sin figurar en ningún sitio Administrador (USUA_PERM_ADMIN para el panel; cada tarjeta por su permiso propio)
/admin/preservacion Administración de preservación digital Consultar y actualizar el plan de preservación (versionado), ver su historial; visibilidad operacional de índices electrónicos pendientes de sellado XAdES (con reintento manual) y de artefactos WORM (índice/acta/AIP) pendientes de renovación de retención de Conservación Total. No ofrece empaquetado manual de AIP ni protección WORM manual de un artefacto arbitrario: ambos requieren datos (documentos[]/file_id) que solo archive-service conoce en el contexto de un expediente, y la renovación de retención es, por diseño, disparo de sistema únicamente. No estaba enlazada —solo se alcanzaba escribiendo la URL— hasta el lote 1 de la revisión UX 2026-08-02, que la añadió al índice de tarjetas de /admin Archivista, administrador (USUA_PERM_EXPEDIENTE; reintento de sellado además PERM_FIRMA) GET/PUT /api/v1/preservacion/plan, GET /api/v1/preservacion/plan/versions, GET /api/v1/expedientes/indices/pendientes-firma, GET /api/v1/expedientes/indices/pendientes-renovacion, POST /api/v1/expedientes/{id}/indice/firmar
/admin/plantillas Administración de plantillas CRUD de plantillas de cuerpo de documentos de salida, con editor de texto enriquecido. No es un constructor de formularios: las plantillas de metadatos (JSON Schema por serie TRD/tipo documental) se administran en /admin/metadatos Administrador (USUA_PERM_ADMIN) GET/POST /api/v1/plantillas
/admin/trd Administración de TRD / CCD Dos pestañas: series TRD/CCD en árbol indentado (jerarquía real vía parent_id, nunca lista plana) con alta/edición (código inmutable, append-only fuera de borrador); tipos documentales (tercer nivel), con alta/edición/borrado y filtro por serie. Circuito de convalidación (aprobar → convalidar → registrar RUSD → derogar, más devolver) vía el componente compartido InstrumentLifecycleActions (ADR-026) — cada acto irreversible (convalidar, derogar) advierte dentro del cuadro de confirmación, antes de confirmar Administrador de TRD (USUA_PERM_TRD, gate real del backend — más específico que USUA_PERM_ADMIN, que solo controla la visibilidad del panel /admin) GET/POST /api/v1/trd, PATCH /api/v1/trd/{id}, GET/POST /api/v1/trd/{code}/versiones, POST /api/v1/trd/{code}/versiones/{version}/{aprobar,devolver,convalidar,registrar-rusd,derogar}, GET/POST/PATCH/DELETE /api/v1/tipos-documentales
/admin/tvd Administración de TVD (fondo acumulado) Espejo estructural de /admin/trd (ADR-026): árbol de agrupaciones de valoración con columnas propias -- fondo/productora, fechas extremas, estado del instrumento -- y sin columna de archivo de gestión (no existe para TVD: un fondo acumulado ya está en central/histórico). El formulario de creación exige fechas extremas y trata la justificación de la valoración como el campo principal, no secundario. Mismo circuito de convalidación que TRD, mismo componente compartido Administrador de TRD (USUA_PERM_TRD -- ADR-026 reutiliza a propósito el permiso de TRD: misma potestad archivística, mismo Comité) GET/POST /api/v1/tvd, PATCH /api/v1/tvd/{id}, GET/POST /api/v1/tvd/{code}/versiones, POST /api/v1/tvd/{code}/versiones/{version}/{aprobar,devolver,convalidar,registrar-rusd,derogar}
/admin/metadatos Administración de metadatos Tres pestañas: plantillas de metadatos de expediente (por serie TRD, archive-service) y de documento (por tipo documental, document-service) — ambas de solo alta (inmutables, JSON Schema editado como texto con validación de sintaxis antes de enviar; nueva versión = nuevo registro); elementos de metadato reutilizables (CRUD completo). Reparto con /admin/trd: TRD administra la clasificación, esta pantalla administra los campos de esos esquemas Administrador (USUA_PERM_ADMIN del lado del cliente; los routers de plantilla/elemento de document-service no gatean por permiso en el backend hoy — hallazgo de dominio documentado en el código, no corregido en el front) GET/POST /api/v1/expediente-metadata/templates, GET/POST /api/v1/metadata/templates, GET/POST/PATCH/DELETE /api/v1/metadata/elements
/admin/catalogos Administración de catálogos Maestro-detalle: lista de catálogos de referencia del tenant a la izquierda, items del seleccionado a la derecha (CRUD completo); en móvil degrada a dos niveles navegables con botón «Volver», nunca a columnas apretadas Administrador (USUA_PERM_ADMIN del lado del cliente; catalogos.py tampoco gatea por permiso en el backend hoy) GET /api/v1/catalogos, GET/POST /api/v1/catalogos/{catalogo}, PATCH/DELETE /api/v1/catalogos/{catalogo}/{id}
/admin/parametros Administración de parámetros Tres secciones: parámetros del tenant (clave→valor JSON libre), calendario de festivos (alta puntual + precarga Ley 51/1983 sin llamada externa), y calculadora de días hábiles que muestra el desglose de fines de semana/festivos descontados en el rango (el backend solo devuelve la fecha resultante; el desglose se reconstruye en el cliente a partir del mismo catálogo de festivos ya cargado) Administrador (USUA_PERM_ADMIN del lado del cliente; config.py documenta explícitamente que el gate de permiso está pendiente) GET/PUT /api/v1/config/params[/{key}], GET/POST/DELETE /api/v1/config/holidays, POST /api/v1/config/holidays/seed, GET /api/v1/business-days/calculate
/admin/usuarios Administración de usuarios Listado paginado de usuarios del tenant (usuario, correo, estado, chip ROOT) con alta y un drawer de detalle que muestra los permisos efectivos con su nivel CRUD y la URD del usuario (dependencia + rol/grupo + marca de principal), con alta y baja de asignaciones URD. No hay editar, desactivar ni borrar un usuario: rbac.py solo expone alta + lectura + gestión de URD, y la pantalla lo declara como ausencia del backend, no como omisión de la UI. La casilla «Otorgar ROOT» solo se muestra a quien ya es ROOT (ADR-024: el backend responde 403 en caso contrario) y falla cerrada si no se pudo determinar la condición ROOT del solicitante —un control que solo puede fallar no es una opción— y el valor se recalcula igualmente al enviar, porque ocultar la casilla es UI, no control. El borrado de una URD se confirma en línea dentro del drawer, nunca apilando un Modal sobre él. Honestidad de interfaz: si falla el catálogo de dependencias o de grupos, el listado de usuarios no se cae; solo los selectores del formulario URD quedan vacíos, con su propio aviso «No se pudo cargar el catálogo de dependencias» Administrador (USUA_PERM_ADMIN, gate a nivel de router en rbac.py y urd.py de auth-service). Desajuste UI↔backend: GET /api/v1/dependencias, que la pantalla consume como catálogo, no tiene gate de permiso en tenant-service (sus escrituras sí exigen USUA_PERM_ADMIN). Además es la única de las cuatro pantallas nuevas de /admin sin EmptyState de bloqueo propio: quien entre sin el permiso ve la cáscara de la página con un error de carga en vez de un mensaje de "sin permiso" GET/POST /api/v1/auth/users, GET /api/v1/auth/users/{id}/permissions, GET/POST /api/v1/auth/users/{id}/urd, DELETE /api/v1/auth/users/{id}/urd/{urd_id}, GET /api/v1/auth/groups, GET /api/v1/dependencias?activo=true
/admin/grupos Administración de grupos Listar/crear grupos; drawer de detalle con miembros, permisos por grupo y clearance (RF-SEG-08). No hay edición ni borrado de grupo (el backend solo tiene alta+lectura). El backend tampoco expone lectura de membresías/permisos/clearance ya asignados a un grupo (solo altas/bajas), advertido explícitamente en el drawer — las acciones se aplican de inmediato sin poder mostrar "el estado actual" Administrador (USUA_PERM_ADMIN) GET/POST /api/v1/auth/groups, GET /api/v1/auth/permissions, GET /api/v1/auth/security-levels, POST/DELETE /api/v1/auth/groups/{id}/members/{user_id}, PUT /api/v1/auth/groups/{id}/permissions/{permission_id}, PUT /api/v1/auth/groups/{id}/clearance
/admin/dependencias Administración de dependencias Vista árbol del organigrama institucional (GET /dependencias/tree); crear, editar y activar/desactivar. Renombrar o desactivar dispara un modal de confirmación explícito (no un tooltip): las dependencias se correlacionan por nombre, no por id/código, en workflow_rules.assign_to_dept y en los flow_steps en vuelo — ambos pueden quedar huérfanos. No ofrece borrado duro (el backend lo tiene, bloqueado si hay hijos, pero el alcance de esta pantalla es crear/editar/desactivar) Administrador (USUA_PERM_ADMIN) GET /api/v1/dependencias, GET /api/v1/dependencias/tree, POST /api/v1/dependencias, PATCH /api/v1/dependencias/{id}
/admin/flujos/reglas Reglas de enrutamiento CRUD de las reglas que asignan automáticamente un radicado a una dependencia (E05): tabla con prioridad ordenable, estado, destino y número de condiciones, y un Modal de alta/edición con condiciones dinámicas (campo/operador/valor) y match_mode (todas Y / alguna O). Tres decisiones de honestidad: (1) una condición incompleta o fuera del catálogo bloquea el guardado y señala cuál es, en vez del comportamiento anterior que las descartaba en silencio —un administrador podía creer que guardó tres condiciones cuando se guardaron dos, o ninguna y por tanto una regla atrapa-todo—; (2) guardar una regla sin condiciones exige una confirmación aparte que advierte que se aplicará a cualquier radicado que entre; (3) la prioridad fuera de 1..999 devuelve 422 explícito en vez de recortarse en silencio a un valor distinto del que se escribió. PATCH es edición parcial real (solo se reenvían las claves presentes, porque en el backend "presente" significa "reemplazar"). Limitaciones declaradas: las cuatro columnas heredadas (doc_type, doc_class, dest_dept, subject_contains) no se exponen a propósito —conditions[] es estrictamente más expresivo y tiene prioridad en el motor—; y como el backend no devuelve total en el listado, la pantalla pide una página amplia y filtra en cliente en vez de fingir paginación de servidor Administrador (USUA_PERM_ADMIN, gate por endpoint en los siete de workflow-service/app/routers/rules.py). Sin desajustes GET/POST /api/v1/workflow/rules, GET/PATCH/DELETE /api/v1/workflow/rules/{id}
/admin/flujos/reasignacion Reasignación en cascada Traslado en bloque de todos los pasos de flujo pendientes de un usuario o de una dependencia hacia otro usuario, en cuatro secciones numeradas: origen (por usuario o por dependencia, con selector sobre el catálogo real —antes era texto libre sobre un nombre de columna del backend, donde un error de escritura reasignaba en silencio cero o los pasos equivocados—), destino, vista previa e confirmación escribiendo REASIGNAR, exigida de nuevo del lado del servidor en el proxy (422) porque el cliente es potencialmente hostil. Banner permanente de advertencia: es una acción masiva e irreversible, sin «deshacer» automático. La vista previa se declara como estimación y se explica por qué al usuario, no solo en el código: workflow-service no tiene endpoint de simulación, así que el conteo se toma de GET /workflows/pending —el mismo predicado que la reasignación, pero con el filtro de clearance del solicitante (RF-SEG-08), que la reasignación no aplica por ser un acto de custodia—, de modo que el número solo puede quedarse corto, nunca pasarse: los radicados clasificados por encima del nivel de acceso no se cuentan pero sí se reasignan. Cambiar el origen tras estimar invalida la vista previa; tras ejecutar se informa el número exacto de pasos y se limpia el formulario para impedir un doble envío Administrador (USUA_PERM_ADMIN, gate real de POST /workflows/reassign/cascade). Desajuste UI↔backend en las tres lecturas: GET /workflows/pending no tiene require_permission (solo aislamiento por tenant y filtro de clearance), GET /auth/users/pickable está gateado por PERM_RADI —no por USUA_PERM_ADMIN— y GET /api/v1/dependencias no tiene gate. Solo el acto de escritura está realmente reservado al administrador GET /api/v1/auth/users/pickable, GET /api/v1/dependencias?activo=true, GET /api/v1/workflows/pending, POST /api/v1/workflows/reassign/cascade
/admin/auditoria Consulta del registro de auditoría Lectura del audit_log inmutable con filtros de igualdad exacta (acción, tipo de objeto, referencia, actor) resueltos en el servidor, drawer con el asiento completo (incluidos hash y payload crudo) y panel de integridad que verifica la cadena de hashes contra PostgreSQL real, sin caché, tanto al cargar como con el botón «Reverificar cadena». Es la pantalla que define la honestidad de interfaz del inventario: el veredicto tiene cuatro estados, nunca doscomprobando / íntegra / comprometida / no se pudo comprobar—; el cuarto dice literalmente que «esto NO significa que la cadena esté íntegra, significa que todavía no se sabe», y hay una guarda específica para que un 200 con cuerpo ilegible no se resuelva como «comprometida», porque afirmar una ruptura que nadie comprobó es tan falso como afirmar la integridad y además dispara una alarma de incidente sin incidente. Solo lectura, sin mutaciones. Dos limitaciones declaradas: (1) AuditQueryRepository no aplica filtro de clearance sobre object_ref, así que un administrador con clearance bajo puede ver en el propio log la existencia y los metadatos de acciones sobre documentos clasificados — la pantalla se niega a añadir un filtro en cliente («sería una falsa sensación de seguridad: el dato ya viajó al navegador») y lo reporta como hallazgo de backend abierto; (2) el api-gateway no reenvía la cabecera X-Total-Count (no está en FORWARDED_RESPONSE_HEADERS), así que el total colapsa al tamaño de la página y la paginación más allá de la primera página no es alcanzable hoy — los filtros sí funcionan Administrador (USUA_PERM_ADMIN, gate a nivel de router en auth-service/app/routers/audit.py, cubre listado y verificación). Sin desajustes GET /api/v1/audit, GET /api/v1/audit/verify
/admin/notificaciones Notificaciones y webhooks Dos facetas independientes cargadas con Promise.allSettled —cada una con su propio estado de carga, error y vacío, para que un fallo nunca oculte lo que sí funcionó—: historial de envíos (fecha, destinatario, asunto, estado, evento, intentos, con botón de actualizar) y webhooks salientes (alta con URL, tipos de evento y secreto HMAC opcional; baja con confirmación). El historial no tiene paginación real y la pantalla lo dice: el backend solo acepta limit, sin total ni cursor, así que la nota explícita advierte que la lista «NO es todas las notificaciones, es una ventana reciente» (últimos 50). Encuadre proporcional al riesgo: los webhooks se presentan como un canal de salida de datos hacia un tercero, y la baja advierte la irreversibilidad nombrando la URL — el destino deja de recibir eventos y volver atrás exige crear una suscripción nueva con un secreto nuevo Administrador (USUA_PERM_ADMIN: GET /notifications/history gateado por endpoint y el router de webhooks gateado a nivel de router en notification-service). Sin desajustes ni endpoints sin gate GET /api/v1/notifications/history, GET/POST /api/v1/webhooks, DELETE /api/v1/webhooks/{id}
/admin/colas Colas de errores (dead-letter) Consola de los eventos en dead-letter de los tres servicios que la tienen (ADR-021), en tres pestañas: Flujos (workflow), Notificaciones y Firma. Filtro por estado y por origin_stream, tabla con chip de estado y drawer de detalle con el failure y el sobre del evento completo. Dos acciones, con confirmación proporcional al daño: «Reintentar» es una confirmación simple que advierte que la reejecución dispara el efecto real (puede incluir un envío de correo o una distribución de verdad) y que un segundo fallo lo deja igual; «Descartar» es danger, exige una justificación de 10 caracteres mínimo que queda en el audit_log inmutable, y deletrea exactamente lo que se pierde: el mensaje original nunca se reprocesará, ese efecto no ocurrirá jamás. El mínimo de 10 caracteres se aplica en el proxy a los tres servicios (dos de ellos solo exigen 1) para que el requisito no cambie en silencio según la pestaña. Honestidad de interfaz: el 404 del backend es ambiguo por diseño (no-read-up, RF-SEG-08) y el proxy se niega a desambiguarlo — la UI muestra la unión honesta, «no se encontró el evento, o tu clearance no permite verlo»; el error de listado tiene precedencia sobre el vacío legítimo. Limitación real: la pantalla muestra un banner de aviso cuando el backend trunca los candidatos (X-Truncated), pero el api-gateway no reenvía esa cabecera ni X-Total-Count, así que hoy ese banner es código muerto y el total colapsa al tamaño de la página PERM_DLQ_ADMIN (gate a nivel de router en los tres admin_deadletter.py de workflow, notification y signature), no USUA_PERM_ADMIN. Sin desajustes. No está en el menú lateral: solo se llega por la tarjeta de /admin GET /api/v1/{workflow,notification,signature}/admin/deadletter/, GET .../{event_id}, POST .../{event_id}/replay, POST .../{event_id}/discard
/admin/pinar Listado de planes PINAR MVP (Fase 7): listar planes con filtro por estado, crear una versión nueva (siempre en borrador; reformular = crear otra versión, el histórico se conserva). No hay edición ni borrado — el plan solo cambia a través de su ciclo de vida en el workspace Administrador (USUA_PERM_ADMIN) GET/POST /api/v1/pinar/planes
/admin/pinar/[id] Workspace de un plan PINAR Stepper informativo del ciclo de vida (borrador→aprobado→en_ejecucion→cerrado, no navegable por clic) + pestañas Resumen y Tablero (avance por objetivo/por eje). Acciones aprobar/pasar a ejecución/cerrar con Modal de confirmación que advierte la irreversibilidad ANTES de ejecutar — aprobar congela el contenido estructural del plan de forma permanente (asiento en audit_log inmutable); cerrar es terminal. Fuera de alcance del MVP (sin UI, endpoints existen): aspectos críticos, priorización, objetivos, proyectos, seguimiento, instrumentos, mapa de ruta Administrador (USUA_PERM_ADMIN; el detalle/tablero es solo-lectura para cualquier autenticado) GET /api/v1/pinar/planes/{id}, POST .../aprobar, POST .../ejecutar, POST .../cerrar, GET .../tablero
/admin/seguridad/claves Revocación administrativa de credenciales de firma Permite a un administrador revocar la credencial de firma personal PKI (E17/F4) de otro usuario, exigiendo escribir una frase de confirmación exacta. Es una revocación a ciegas y la pantalla lo dice: ningún endpoint lista el estado de firma por usuario (solo GET /auth/me/signing-key, el propio), así que los usuarios del tenant se cargan en una página amplia y se filtran en cliente, y el backend responde 404 no_signing_key cuando el destinatario no tiene ninguna. Faltaba en el índice de tarjetas de /admin —que se presenta como el mapa completo de administración— hasta el lote 1 de la revisión UX 2026-08-02 Administrador (USUA_PERM_ADMIN, gate a nivel de router en admin_router de auth-service/app/routers/signing_key.py) GET /api/v1/auth/users, POST /api/v1/auth/admin/users/{user_id}/signing-key/revoke
/admin/interoperabilidad Interoperabilidad y carga masiva Cuatro pestañas (Fase 8, última del cierre del desfase API↔UI): Exportar (paquete ZIP interoperable, descarga en cliente, con contadores de incluidos/excluidos por nivel de seguridad); Importar (Stepper subir→validación→confirmar→resultado; la validación de fixity/esquema es atómica en el backend, un 422 muestra el archivo/checksum EXACTO que falló, nunca un mensaje genérico); Carga masiva de documentos (hasta 1000) y Carga masiva de expedientes (hasta 100), ambas con modal de confirmación con el número exacto de ítems y sondeo de progreso cada 2s que distingue "no se pudo consultar" de "aún sin resultados". Las cuatro pestañas operan de punta a punta; la carga masiva no reportaba resultado (revisión UX 2026-08-02: el sondeo se había quedado con la forma de URL anterior al fix de ruteo del gateway y el panel de progreso no era reactivo), corregido en el lote 1 junto con un test de contrato que fija la forma de la URL Administrador con USUA_PERM_EXPEDIENTE o radicador con PERM_RADI: el gate es heterogéneo por pestaña —la carga masiva de documentos exige PERM_RADI— así que la pantalla se abre con cualquiera de los dos y cada pestaña filtra con su permiso propio, en vez de ofrecer lo que negará u ocultarse a quien sí puede usarla POST /api/v1/export, POST /api/v1/import, POST/GET /api/v1/batch/documents[/{job_id}/status], POST/GET /api/v1/batch/expedientes[/{job_id}/status]
/_kit Kit de componentes UI Showcase interno de componentes del sistema de diseño Orpyca (solo entorno de desarrollo, no producción) Desarrolladores

Presentes en todas las pantallas (app)/, provistas por AppLayout y SidebarNav:

  • Búsqueda global (role="search" en la barra superior): navega a /busqueda?q=…, la misma vista de búsqueda full-text con sus filtros avanzados. Atajo de teclado /, inhibido cuando el foco ya está en un campo editable. En pantallas estrechas se despliega desde un icono. Como el término viaja en la URL, toda búsqueda es enlazable y compartible.
  • Favoritos: cada entrada del menú lateral tiene un control fijar/desfijar (aria-pressed, nunca señalizado solo por color). Los ítems fijados encabezan el menú y alimentan el módulo 04 del /dashboard. Se persisten en localStorage namespaced por tenant + usuario (lib/stores/favorites.js, con guardia SSR). El store no filtra por sí mismo: recibe la lista ya filtrada por permisos desde lib/utils/navItems.js, de modo que un favorito cuyo permiso se retira desaparece solo. El catálogo NAV_ITEMS y su filtro RBAC son la fuente única compartida por SidebarNav y el /dashboard.

Notas de diseño

  • Sistema de diseño Orpyca v1.0: todos los componentes reutilizables (DataTable, Drawer, Modal, StatusChip, FormField, Button, Loader, Stepper, Card, EmptyState, Toast, Stepper, UserPicker) viven en frontend/src/lib/components/ui/ y usan tokens de diseño --op-* (colores/espaciados/tipografía/radios/sombras/movimiento), nunca valores hex sueltos (ADR-020). La fuente única es src/styles/_tokens.scss.
  • Contraste y uso del verde de marca: el primario v1.0 (--op-primary, #2A8C3A) mide 4.28:1 sobre blanco, por debajo del 4.5:1 que WCAG AA exige a texto normal. Por eso el token está partido por rol: --op-primary solo para elementos no textuales (bordes, iconos, anillos de foco, umbral ≥3:1) y --op-primary-dark (#1C6B33, 6.56:1) para todo texto —incluidos $primary/$link de Bulma— o los pares --op-*-fg verificados. Los semánticos crudos (--op-warning, --op-error, …) son colores de borde/icono; su versión de texto es --op-*-fg. --op-accent (#8DBF3C, 2.18:1) nunca se usa como texto sobre fondo claro.
  • Tipografía: Space Grotesk (display, títulos, cifras) + Public Sans (UI y cuerpo), self-hosted vía @fontsource — sin CDN externo, por soberanía y por no filtrar la navegación de los usuarios a un tercero.
  • Movimiento: _tokens.scss declara una regla global prefers-reduced-motion que neutraliza toda transición y animación del sitio; ninguna pantalla necesita repetirla.
  • DataTable es la tabla del sistema: expone ocho capacidades opt-in por prop (filtros por columna, ordenamiento con aria-sort, exportación CSV, columnas configurables, vistas guardadas, selección múltiple, virtualización a nivel de render y edición en celda) con el comportamiento simple como valor por defecto. La virtualización usa content-visibility en vez de retirar filas del DOM: es menos agresiva, pero no rompe la semántica nativa de <table> para lectores de pantalla. La usan /busqueda, /expedientes, /firmas, /envios y los cuatro paneles de /reportes; /bandeja y /archivo-fisico siguen con tabla propia (ver Roadmap).
  • Edición de texto enriquecido (respuestas, observaciones): usa TipTap 3, con el HTML saneado server-side antes de guardar y antes de renderizar — nunca se interpola HTML crudo del usuario.
  • Exportación CSV desde el navegador neutraliza inyección de fórmulas (antepone ' a celdas que empiezan con = + - @).
  • No existen capturas de pantalla en esta documentación — las tablas anteriores describen la funcionalidad real implementada; para verla en ejecución, levantar la plataforma (ver Primeros pasos) y navegar a http://localhost:19300.

Ver también

  • Arquitectura — cómo cada pantalla se conecta con los microservicios a través del gateway.
  • Referencia de API — contrato completo de cada endpoint listado arriba.