# Enrutamiento y módulos Cómo una URL termina ejecutando una pantalla concreta, y qué hace falta para agregar un módulo nuevo. ## El recorrido de una petición ``` GET /erp.php?m=turnero&v=historial │ ├── erp.php define APP_ROOT y llama App::run() │ ├── App::boot() carga config, abre sesión, fija zona horaria │ ├── Router decide módulo y vista │ ├── 1º intenta la ruta limpia: /turnero/historial │ └── 2º cae a los parámetros: ?m=turnero&v=historial │ ├── Router::resolveFile() │ └── modules/turnero/views/historial.php ¿existe? si no → 404 │ ├── Rbac::hasModule('turnero') ¿tiene acceso? si no → 403 │ └── include del archivo de la vista ``` El punto clave: **la ruta es literalmente la ubicación del archivo**. `?m=turnero&v=historial` carga `modules/turnero/views/historial.php`. No hay tabla de rutas ni configuración intermedia. ## Validación de la URL `Router` acepta como módulo y vista solo `[a-zA-Z0-9_]`, máximo 64 caracteres. Cualquier cosa fuera de ese patrón se descarta silenciosamente y se reemplaza por el valor por defecto (`dashboard` / `index`). Eso cierra la puerta a recorrer directorios con `../`. ## Rutas públicas Casi todo exige sesión. Las excepciones están fijas en `core/Router.php`: | Ruta | Por qué es pública | |---|---| | `turnero/display` | Pantalla de TV en sala de espera; no hay quién inicie sesión | | `turnero/kiosko` | El paciente saca su turno solo | Cualquier otra combinación pasa por el control de acceso. > Ojo: `isPublic()` solo omite la verificación **de módulo**. La sesión se maneja aparte, dentro de cada vista. ## Registrar un módulo nuevo Hacen falta tres cosas. Si falta alguna, el módulo no aparece o da 403. **1. La carpeta y al menos una vista** ``` modules/mimodulo/ module.php views/index.php api/ (opcional) ``` **2. El descriptor `module.php`** — devuelve un arreglo: ```php 'mimodulo', 'name' => 'Mi Módulo', 'icon' => 'fas fa-cube', 'category' => 'lab', 'route' => '/erp.php?m=mimodulo&v=index', 'is_active' => true, 'sort_order' => 50, 'description' => 'Para qué sirve', 'links' => [ ['name' => 'Inicio', 'icon' => 'fas fa-home', 'route' => '/erp.php?m=mimodulo&v=index'], ], ]; ``` `links` son las entradas que salen en el menú lateral. El descriptor se ejecuta como PHP, así que puede armar los enlaces según el rol de quien mira — el turnero lo hace: muestra escritorios distintos a recepcionistas y bacteriólogos. **3. El registro en `SYSTEM_MODULES`** (`config/config.php`) ```php define('SYSTEM_MODULES', [ ... 'mimodulo' => 'Mi Módulo', ]); ``` Estar acá es lo que **activa la verificación de permisos**. Un módulo ausente de esta lista no se valida y queda accesible para cualquier sesión. **4. Dar acceso a los roles** — sin esto, todos reciben 403: ```sql INSERT INTO role_modules (role_id, module_slug, permission, can_view) SELECT id, 'mimodulo', 'write', 1 FROM roles WHERE slug IN ('admin','superadmin'); ``` > Los módulos de la sesión se cargan **al iniciar sesión**, desde `role_id`. Después de tocar `role_modules`, el usuario afectado tiene que volver a entrar para que el cambio surta efecto. ## Módulos actuales {{modulos}} ## Vistas y layout Una vista se escribe así: ```php require_once APP_ROOT . '/config/config.php'; if (!isUserLoggedIn()) { header('Location: ' . BASE_URL . 'login.php'); exit; } Layout::open('Título de la pantalla', 'fas fa-icono'); // HTML, CSS y JS de la pantalla Layout::close(); ``` `Layout::open()` emite el ``, la barra superior y el menú lateral — que construye leyendo los `module.php` de los módulos a los que el usuario tiene acceso. `Layout::close()` cierra el documento. ## Endpoints Cada módulo puede tener su carpeta `api/`. Son archivos PHP sueltos que devuelven JSON y se consumen por `fetch` desde las vistas. No pasan por `Router`: se invocan por su ruta real (`modules/turnero/api/get_historial.php`). Por convención, `api/_helpers.php` de cada módulo concentra lo común — conexión, lectura del cuerpo JSON, respuestas `jsonOk()` / `jsonError()` y la verificación de acceso. Los archivos que empiezan con guión bajo son de uso interno y no se llaman directamente desde el navegador.