0010. Catálogo de menú y auditoría en el frontend (React)¶
Fecha: 2026-09-01 Estado: Propuesto Módulo(s) afectado(s): frontend (ERP y CRM) — transversal
Contexto¶
Los ADR 0013 (catálogo de navegación), 0008 y 0009 (captura de auditoría)
definieron el catálogo (cat_aplicaciones, cfg_modulos, cfg_menu,
cfg_menu_acciones) y el registro de movimientos (middleware automático +
AuditService, ADR 0009); svc-identidad adoptó el catálogo de navegación
en su ADR 0006. El frontend (React 18, Vite, MUI — ADR 0007) necesita dos cosas:
- renderizar el menú/sidebar a partir del catálogo que vive en la BD
del backend, y navegar con React Router usando
ruta_frontend; - mostrar el registro de movimientos ('qué hizo quién') de forma
legible, consumiendo
audit_logvía una API del backend.
Aquí se define cómo se consume y se muestra, sin duplicar el catálogo.
Decisión¶
El catálogo y la auditoría viven solo en el backend (ADRs 0008/0009/0013;
en svc-identidad, ADR 0006). El frontend los consume por API; no tiene copia
propia ni lógica de persistencia.
1. Navegación: aplicaciones → módulos → menú (React Router)¶
El backend de svc-identidad expone los endpoints de navegación (ADR 0012)
que el frontend consume en orden:
- Login / panel —
GET /api/v1/navegacion/panel→ aplicaciones de la empresa (rel_empresa_aplicacion, ADR 0006 de svc-identidad) + módulos visibles para el usuario. El usuario elige ERP o CRM. - Menú/sidebar —
GET /api/v1/navegacion/menu?modulo_id=...→ árbol de páginas del módulo (cfg_menu, 3 niveles con submenús), filtrado por los permisos del usuario.
Cada resultado se guarda en TanStack Query (estado de servidor, ADR 0007).
El árbol de páginas pinta el sidebar; ruta_frontend es el path de React Router:
React Router:
<Route path="/inventario/ingreso" element={<IngresoPage/>}>
<Route path="/inventario/ingreso/crear" element={<IngresoForm/>}>
- El componente de menú recorre el árbol (nivel 1 → 2 → 3) y genera los
<ListItemButton onClick={() => navigate(item.ruta_frontend)}>. - El router de la SPA y las rutas del API no se casan automáticamente
(ADR 0008): el backend (middleware + catálogo cacheado) es quien resuelve
endpoint↔menu_id/accion_id/modulo_idde forma automática (ADR 0009). El frontend solo usaruta_frontendpara navegar; la auditoría se registra completamente en el backend sin intervención del cliente.
2. Registro de movimientos (vista de auditoría)¶
- El backend expone, p. ej.:
GET /api/v1/audit?filtros=...→ lista paginada deaudit_log(conempresa_iddel token — el frontend nunca mandaempresa_idlibre, ADR 0002/0006). - La vista "Historial / Auditoría" usa TanStack Query para listar y
paginar. El backend ya entrega los nombres resueltos en el propio
registro (
audit_logguarda en el momentoactor_username/actor_nombre/actor_email,menu_codigo/menu_nombreyaccion_codigo/accion_nombre— ADR 0014), más elarchivoscomo array para mostrar iconos/link de archivos (ruta o URL, ADR 0008). - El componente de detalle muestra: quién, cuándo, qué acción, en qué
módulo/página, método HTTP y endpoint, qué cambió (detalle +
datos_antes/datos_despues), y los archivos asociados. - El frontend puede filtrar por módulo (
modulo_codigo) directamente del snapshot enaudit_log, sin necesidad de JOINs ni llamadas adicionales. - Si la ruta del archivo es local hoy y mañana es una URL de AWS S3, el render no cambia: el frontend solo muestra el link/icono del elemento.
3. Reglas de consumo (frontend)¶
- Tipos generados desde OpenAPI (ADR 0007): las respuestas de menú y
auditoría entran a
src/types/desde el OpenAPI del backend; nunca a mano. Los camposmodulo_codigo,modulo_nombredel snapshot de auditoría están incluidos en el schema OpenAPI. - El catálogo se cachea con TanStack Query e invalidar únicamente cuando el backend lo indique (TTL/versión); no se replica en el store local salvo para interacción (Zustand para colapsar niveles, ADR 0007).
- Permisos en la UI son espejo (ADR 0007): el menú puede ocultar opciones por permisos, pero el backend sigue validando (middleware de auditoría + permisos).
- Solo se muestra lo autorizado: la API de auditoría filtra por
empresa_idy por los permisos del usuario; el frontend no tiene acceso a movimientos de otras empresas (RLS, ADR 0002). - Sin lógica discrecional de auditoría en el frontend: el cliente no decide qué se registra; solo invoca lo que el backend audita.
Estructura (por app, siguiendo ADR 0007)¶
app/src/
├── features/
│ ├── aplicaciones/ # selector de aplicación en el login
│ ├── modulos/ # grilla de módulos (tipo Odoo)
│ ├── menu/ # hooks y componentes del sidebar (árbol de páginas)
│ └── auditoria/ # hooks y componentes de "Historial / Movimientos"
├── app/ # layout: sidebar recibe el árbol de páginas
└── types/ # generados desde OpenAPI
Alternativas descartadas¶
- Guardar un duplicado del menú en el frontend (JSON/estado) — descartado: dos fuentes de verdad se desincronizan; el catálogo vive en el backend y se sirve por API.
- Hardcodear
ruta_frontenden React — descartado: el catálogo del backend es la fuente; la SPA lo navega por API (y se ajusta desde la BD). - El frontend construye los textos de acción/auditoría — descartado:
pierde el
accion_iddel catálogo y rompe la consistencia con ADR 0008.
Consecuencias¶
- Una sola fuente del menú y de la auditoría (el backend); el frontend solo consume y renderiza.
- Cambiar el árbol del menú (orden, nombres, niveles, links) no requiere build de la SPA: se edita el catálogo en la BD y el sidebar se refresca con TanStack Query.
- Migrar de ruta local a URL S3 no toca el frontend: solo cambia el dato
en
archivos. - Resuelto: los endpoints exactos del catálogo de menú son
GET /api/v1/navegacion/panelyGET /api/v1/navegacion/menu(ADR 0012 de svc-identidad); la vista de auditoría (paginación/filtros) queda para definirse en el servicio dueño respetando el contrato OpenAPI (ADR 0003 transversal).