Saltar a contenido

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_menu del usuario y cfg_menu.is_visible = true y cfg_menu.is_activo = true. Un root ve 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_id y orden (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_accion para acciones finas (crear/editar/eliminar) está definido en el ADR 0015; las acciones se validan con los permisos del token en cada servicio (ADR 0014).