0012. Menú del módulo y permisos de navegación¶
Estado: Propuesto Fecha: 2026-09-08 Autor: Duval Alcivar Módulo(s) afectado(s): svc-identidad, frontend (React 18)
Contexto¶
El ADR 0006 define las tablas de navegación (cat_aplicaciones,
cfg_modulos, cfg_menu, cfg_menu_acciones) y el ADR 0010 define el panel
de aplicaciones. El ADR 0007 define los permisos individuales y la asignación
directa usuario → página del menú (rel_usuario_menu). Falta definir
cómo se decide, en ejecución, qué módulos y páginas ve cada usuario: el
menú lateral (sidebar) de cada módulo se construye con esos permisos.
Regla de negocio: el usuario solo ve los módulos y páginas a los que tiene
permiso; el menú lateral se construye con esos permisos. Un root ve todo.
Decisión¶
Los permisos de navegación se resuelven uniendo permisos individuales del
usuario con el menú: cada usuario tiene filas propias en rel_usuario_menu
(ADR 0007), y un módulo es visible si el usuario tiene al menos una página
visible dentro de él.
1. Cómo se resuelve el menú de un usuario¶
tipo del usuario (mae_usuarios.tipo_usuario_id → cat_tipos_usuario)
│
├── "root" → todas las páginas del menú, sin más consultas
│
▼ "usuario"
páginas permitidas (rel_usuario_menu) ──▶ filtradas por empresa (RLS)
│
▼
árbol del menú del módulo (cfg_menu, solo páginas permitidas y visibles)
Reglas:
- Página visible = la página está en
rel_usuario_menudel usuario ycfg_menu.is_visible = trueycfg_menu.is_activo = true. Unrootve todas. - Módulo visible en el panel = el módulo tiene al menos una página visible para el usuario (y el módulo está activo y visible). Si no tiene ninguna, el módulo no aparece en el panel (ADR 0010).
- Menú del módulo (sidebar) = el árbol de páginas visibles, respetando
padre_idyorden(máximo 3 niveles, ADR 0006). - Si una página de nivel 2 es visible pero su padre de nivel 1 no está en
rel_usuario_menu, el padre se muestra igual como contenedor (sin página propia) para no romper el árbol.
2. Endpoints¶
| Endpoint | Método | Qué devuelve |
|---|---|---|
/api/v1/navegacion/panel |
GET | aplicaciones + módulos visibles del usuario (alimenta el panel, ADR 0010) |
/api/v1/navegacion/menu?modulo_id=... |
GET | árbol de páginas del módulo, filtrado por permisos (alimenta el sidebar) |
Ambos resuelven en svc-identidad con el empresa_id del token (RLS). El
frontend los consume una vez por sesión o por módulo y los cachea (TanStack
Query, ADR transversal 0007). (Bajo /api/v1, ADR transversal 0016.)
3. Defensa en dos capas¶
El menú oculta lo que el usuario no puede usar, pero el permiso real lo
valida el backend de cada servicio en cada petición (ADR transversal 0007:
"la UI puede mentir, la API no"). rel_usuario_menu controla visibilidad
de navegación; la autorización de la operación (qué puede ejecutar) la
valida cada servicio con el tipo_usuario y los permisos del token y sus
propias reglas.
Alternativas descartadas¶
- Perfiles/roles dinámicos (agrupar páginas por rol) — descartado por regla de negocio: no existen perfiles; la asignación es individual por usuario (ADR 0007).
- Filtrar el menú solo en el frontend — inseguro: cualquiera con la URL directa accedería; el filtro nace en el backend.
- Menú estático por aplicación (sin permisos) — todos verían todo lo contratado por la empresa; rompe la regla de negocio.
Consecuencias¶
- El panel y el sidebar siempre reflejan los permisos reales: lo que no se puede usar, no se ve.
- Asignar una página nueva a un usuario es solo insertar una fila en
rel_usuario_menu, sin tocar código. - Quitar acceso a una página es inmediato: se borra la fila y en el próximo login (o recarga del menú) desaparece.
- Resuelto:
rel_usuario_menu_accionpara acciones finas (crear/editar/eliminar) está definido en el ADR 0015; las acciones se validan con lospermisosdel token en cada servicio (ADR 0014).