Saltar a contenido

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:

  1. renderizar el menú/sidebar a partir del catálogo que vive en la BD del backend, y navegar con React Router usando ruta_frontend;
  2. mostrar el registro de movimientos ('qué hizo quién') de forma legible, consumiendo audit_log ví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 / panelGET /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ú/sidebarGET /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 endpointmenu_id/accion_id/modulo_id de forma automática (ADR 0009). El frontend solo usa ruta_frontend para 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 de audit_log (con empresa_id del token — el frontend nunca manda empresa_id libre, 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_log guarda en el momento actor_username/actor_nombre/ actor_email, menu_codigo/menu_nombre y accion_codigo/accion_nombre — ADR 0014), más el archivos como 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 en audit_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)

  1. 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 campos modulo_codigo, modulo_nombre del snapshot de auditoría están incluidos en el schema OpenAPI.
  2. 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).
  3. 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).
  4. Solo se muestra lo autorizado: la API de auditoría filtra por empresa_id y por los permisos del usuario; el frontend no tiene acceso a movimientos de otras empresas (RLS, ADR 0002).
  5. 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_frontend en 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_id del 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/panel y GET /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).