# Roles y permisos Quién puede ver y hacer qué. Es el punto donde más seguido se cometen errores, así que conviene entenderlo completo. ## Las dos columnas de un usuario En `admin_users` conviven dos campos que parecen redundantes y **no lo son**: | Columna | Para qué se usa | |---|---| | `role` | Texto del rol (`admin`, `bacteriologo`, …). Lo consultan las vistas para decidir qué mostrar | | `role_id` | Apunta a `roles.id`. Es de donde se **cargan los módulos** al iniciar sesión | > **Hay que mantener las dos sincronizadas.** Cambiar solo `role` deja al usuario con los permisos viejos, porque el acceso real sale de `role_id`. Este error ya ocurrió: un usuario cambió de rol, la interfaz mostraba el rol nuevo y los módulos seguían siendo los anteriores. Al cambiar el rol de alguien, actualice las dos a la vez: ```sql UPDATE admin_users SET role = 'lab_recepcion', role_id = (SELECT id FROM roles WHERE slug = 'lab_recepcion') WHERE id = 12; ``` ## Cómo se arma el acceso al iniciar sesión `authenticateUser()` (`config/config.php`) valida la contraseña y arma la sesión: ``` admin_users.role_id └── role_modules → lista de module_slug + permission └── $_SESSION['admin_user']['modules'] (qué módulos ve) $_SESSION['admin_user']['module_permissions'] (read o write en cada uno) ``` **Esto ocurre una sola vez, al entrar.** Cualquier cambio en `role_modules` no afecta a las sesiones abiertas: el usuario tiene que cerrar sesión y volver a entrar. ## Las dos preguntas del control de acceso ```php hasModule('lab_domicilios') // ¿puede entrar al módulo? hasModuleWrite('lab_domicilios') // ¿puede modificar, o solo mirar? ``` - `hasModule()` mira si el slug está en la lista de módulos de la sesión. - `hasModuleWrite()` mira `module_permissions[slug] === 'write'`. Los administradores siempre pueden escribir. Una vista típica lo usa así: ```php $puedeEscribir = hasModuleWrite('lab_domicilios'); ... ``` ## La columna que manda es `permission` `role_modules` tiene dos formas de expresar lo mismo, y solo una se usa: | Columnas | ¿Se usan? | |---|---| | `permission` (`read` / `write`) | **Sí.** Es lo que lee `hasModuleWrite()` | | `can_view`, `can_create`, `can_edit`, `can_delete`, `can_export` | No las lee el control de acceso | > Poner `can_edit = 0` **no impide editar**. Para dejar un módulo en solo lectura hay que fijar `permission = 'read'`. Las columnas `can_*` quedaron de un diseño anterior; conviene mantenerlas coherentes por prolijidad, pero no protegen nada. Solo lectura de verdad: ```sql UPDATE role_modules SET permission = 'read' WHERE role_id = 1030 AND module_slug IN ('lab_domicilios', 'lab_ordenes'); ``` ## Roles actuales {{roles}} ## Sesiones sin `role_id` Hay dos casos heredados que siguen contemplados en el código: - **`modules` nulo y rol `admin`** → acceso total. Cubre usuarios anteriores al sistema de roles. - **Rol `enfermero` sin `role_id`** → recibe `enfermero_portal` y `lab_formularios` de forma fija. ## Verificaciones adicionales El control por módulo no siempre alcanza. Varias pantallas agregan sus propias reglas: | Dónde | Regla | |---|---| | `enfermero_portal.php` | Solo `admin`, `superadmin` y `enfermero` | | `api/lab/save_domicilio.php` | Un enfermero solo edita domicilios que creó **o** que tiene asignados | | `api/lab/firmar_profesional.php` | Un enfermero solo firma envíos propios | | `modules/turnero/api/_helpers.php` | `requireTurnero()` en todos los endpoints del turnero | | `modules/turnero/module.php` | El menú cambia según rol y según la IP del equipo | Al agregar un endpoint que modifica datos, **no alcanza con confiar en que la vista ocultó el botón**: el endpoint tiene que verificar por su cuenta. ## Diagnóstico rápido Alguien reporta que no ve un módulo o que puede editar lo que no debería: ```sql -- Qué rol tiene realmente y si las dos columnas coinciden SELECT u.id, u.username, u.role, u.role_id, r.slug AS rol_real FROM admin_users u LEFT JOIN roles r ON r.id = u.role_id WHERE u.username = 'usuario'; -- Qué módulos le da ese rol SELECT module_slug, permission FROM role_modules WHERE role_id = ; ``` Si los datos se ven bien y el usuario sigue sin acceso: **no ha vuelto a iniciar sesión**.