# 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');
...
<?php if ($puedeEscribir): ?><button>Nuevo domicilio</button><?php endif; ?>
```

## 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 = <role_id>;
```

Si los datos se ven bien y el usuario sigue sin acceso: **no ha vuelto a iniciar sesión**.
