Los menús de un panel suelen llegar del servidor: cada rol ve entradas distintas y añadir páginas no exige publicar el frontend. El trabajo no es dibujar lo que llega, sino someter menú, rutas y acciones al mismo conjunto de reglas.

El contrato del menú

El menú llega como grupos y elementos. Cada elemento incluye ruta, texto o clave de texto, icono, grupo, orden, marca de oculto, códigos de permiso e hijos. Los grupos fijan el orden de la barra lateral y la jerarquía se limita a cuatro niveles para que una carga anómala no desborde la navegación.

Los elementos externos declaran su dirección y modo de apertura: ventana nueva, misma ventana o incrustado. Incrustar solo funciona si el sitio lo permite: uno que envía X-Frame-Options: DENY se verá en blanco.

La tolerancia también corresponde al frontend: rutas extrañas, iconos desconocidos e hijos demasiado profundos se normalizan y dejan una lista de avisos legible. Al conectar una API real conviene revisar esa lista en lugar de esperar el informe de un usuario.

Las rutas solo conocen páginas registradas

Las rutas dinámicas toman sus componentes de un registro local: la API puede referenciar una página registrada, pero no introducir código nuevo. Una sesión o un permiso ausente termina en la pantalla de acceso o en un 403, nunca en una página vacía.

Tres capas, un conjunto de reglas

  • Menú: los elementos se filtran por código de permiso, así que las entradas no disponibles no se dibujan.
  • Rutas: escribir la URL directamente activa el mismo control y termina en 403.
  • Acciones: <Can permission="customer:create"> cubre operaciones como crear o exportar y muestra un estado deshabilitado en lugar de fallar en silencio.

Los códigos de permiso admiten * para todo, module:* para un módulo completo y coincidencias exactas, de modo que las diferencias de rol caben en una lista. La documentación recoge el contrato completo y la demostración permite comparar dos cuentas: en la misma dirección el administrador ve el registro de auditoría y la cuenta de prueba recibe un 403.