# Plan de Permisos — Nucleo 3

Análisis y propuesta de implementación para el sistema de roles y permisos del ERP Inmobiliario.

---

## Estado actual — Problemas identificados

| # | Problema | Severidad |
|---|---|---|
| 1 | Sin enforcement en API: cualquier usuario autenticado puede llamar cualquier tarea (createRol, deleteSystem, etc.) | Crítico |
| 2 | IDs de rol hardcodeados en render.js y class.modules.php (`rolId == 1`, `rolId == 2`) | Alto |
| 3 | Config admin sin gating: menú completo visible para cualquiera que abra el modal | Alto |
| 4 | Bits de permiso no se usan en runtime: se guardan pero no se consultan antes de renderizar botones | Medio |
| 5 | `rolId` se extrae del token sin re-validar contra DB en cada request | Crítico |
| 6 | `rol_json` siempre NULL, sin esquema definido | Bajo |
| 7 | `roles_systems` y `roles_categorys` sin bits de permiso (acceso binario) | Medio |
| 8 | Sin audit trail: createRol, deleteRol, createUser no escriben en tabla `logs` | Alto |
| 9 | REMS doc_types sin gate de rol: cualquier autenticado puede crear/editar tipos de documento | Alto |

---

## Modelo de datos actual

### Tabla `roles`
```
rol_id, rol_name, rol_description, rol_parent_id,
rol_redirection_url, rol_json, rol_state
```

### Tabla `roles_modules` (permisos por módulo)
```
rol_mod_rol_id, rol_mod_mod_id, rol_mod_ent_id,
rol_mod_order, rol_mod_permits VARCHAR(20)
-- rol_mod_permits ejemplo: "1,0,1,1,0"
```

### String de 5 bits actual
| Pos | Clave | Significado |
|---|---|---|
| 0 | Ver | Leer registros |
| 1 | Publicar | Publicar (concepto CMS) |
| 2 | Agregar | Crear nuevos registros |
| 3 | Editar | Modificar registros existentes |
| 4 | Eliminar | Soft-delete |

---

## Propuesta — String de 7 bits para ERP

Reemplaza "Publicar" por bits más relevantes para inmobiliaria:

| Pos | Clave | Caso de uso ERP |
|---|---|---|
| 0 | Ver | Ver registros, dashboards, reportes |
| 1 | Agregar | Crear propiedad, contrato, pago |
| 2 | Editar | Modificar registros existentes |
| 3 | Eliminar | Soft-delete |
| 4 | Aprobar | Aprobar contrato, autorizar pago, firmar documento |
| 5 | Exportar | Descargar Excel/PDF, exportar paquetes |
| 6 | Configurar | Acceder a configuración del módulo (tipos doc, tablas de precio, etapas) |

**Backward compatible:** posiciones 0-3 sin cambio. "Publicar" (pos 1) pasa a "Agregar". Datos de 5 bits existentes siguen válidos con los nuevos 2 bits en 0 por defecto.

---

## Plan de implementación — 9 pasos

### Paso 1 — Hardener validación de token
**Archivos:** `models/class/class.users.php`
**Estado:** Pendiente

`validateUserToken()` actualmente extrae `rolId` desde el payload del token (base64 decodificado, posición 2). Si el token es manipulado, el sistema usará un rolId falso durante toda la sesión.

**Cambio:** `validateUserToken()` retorna solo `userId` desde el token. El `rolId` se obtiene siempre con un JOIN fresco contra `users_roles`. Cero cambios en interfaces externas.

---

### Paso 2 — Migración DB: columnas en `roles`
**Archivo SQL:** `models/querys/migrations/001_roles_new_columns.sql`
**Estado:** SQL generado — pendiente ejecución

```sql
ALTER TABLE roles
  ADD COLUMN rol_is_system TINYINT NOT NULL DEFAULT 0
    COMMENT '1=built-in, no puede ser eliminado',
  ADD COLUMN rol_config_sections INT NOT NULL DEFAULT 0
    COMMENT 'Bitmask secciones del config admin accesibles';
```

Bitmask `rol_config_sections`:

| Sección | Bit | Valor |
|---|---|---|
| Configuración | 0 | 1 |
| Sistemas | 1 | 2 |
| Sitios | 2 | 4 |
| Usuarios | 3 | 8 |
| Roles | 4 | 16 |
| Grupos | 5 | 32 |
| Tipos de Documentos | 6 | 64 |

Ejemplo: Super Admin = 127 (todos), Administrador = 1+8+32 = 41.

---

### Paso 3 — Exponer permisos en el session payload
**Archivos:** `models/class/class.users.php` (`userDataAuth`), `core/dashboard/controllers/apis/v1/dashboard.php`
**Estado:** Pendiente

`userDataAuth()` debe retornar:
- `rolConfigSections` (int bitmask desde `roles.rol_config_sections`)
- `permits` (array `modId => permitsString` desde `roles_modules`)

El dashboard los guarda en `window._SESSION_PERMITS` al init. Reemplaza los `datosUsuario.rol_id == 1` dispersos.

---

### Paso 4 — Helper `hasPermit()` en frontend
**Archivos:** `components/functions.js`, `components/renders/renderTables.js`
**Estado:** Pendiente

```javascript
// En functions.js
export const hasPermit = (modId, bit) => {
    const p = (window._SESSION_PERMITS || {})[modId];
    if (!p) return false;
    return (p.toString().split(',').map(Number)[bit] ?? 0) === 1;
};
```

`renderColActions()` recibe un parámetro `permits` y oculta botones Edit/Delete/Approve/Export según los bits.

---

### Paso 5 — Gating sidebar del config admin
**Archivos:** `core/dashboard/views/configWindow.html`, `core/dashboard/components/configurations.js`
**Estado:** Parcialmente implementado (menuItemRestricted para Tipos de Documentos)

Agregar `data-sections-bit="N"` a cada `<li>`:

```html
<li data-sections-bit="1">  <!-- Configuración -->
<li data-sections-bit="2">  <!-- Sistemas -->
<li data-sections-bit="4">  <!-- Sitios -->
<li data-sections-bit="8">  <!-- Usuarios -->
<li data-sections-bit="16"> <!-- Roles -->
<li data-sections-bit="32"> <!-- Grupos -->
<li data-sections-bit="64"> <!-- Tipos de Documentos -->
```

En `loadConfigAdmin()`, después de insertar el HTML, filtrar `<li>` cuyo bit no esté en `rolConfigSections`. Guardar `getPageConfig()` con un check antes del `eval()`.

---

### Paso 6 — API-level permission gating
**Archivos:** `models/class/class.users.php` (nuevo método), todos los `controllers/apis/v1/*.php`
**Estado:** Pendiente

Nuevo método `checkTaskPermission($rolId, $task, $entitieId)`:

```php
// Mapeo task → [mod_id, bit_requerido]
const TASK_PERMISSION_MAP = [
    // Config admin (no requieren módulo, requieren config_sections)
    'createSystem'           => ['config', 2],   // bit sistemas
    'updateSystem'           => ['config', 2],
    'createSite'             => ['config', 4],   // bit sitios
    'createRol'              => ['config', 16],  // bit roles
    'updateRol'              => ['config', 16],
    'deleteRol'              => ['config', 16],
    'createUser'             => ['config', 8],   // bit usuarios
    'updateUser'             => ['config', 8],
    // REMS doc types (mod_id a definir)
    'createDocumentType'     => [3906, 6],  // bit Configurar
    'updateDocumentType'     => [3906, 6],
    'deleteDocumentType'     => [3906, 6],
    'changeStateDocumentType'=> [3906, 6],
];
```

Se llama justo después de `validateUserToken()` en cada controlador. Retorna 403 JSON si el rol no tiene el bit.

---

### Paso 7 — Expandir a 7 bits para módulos ERP
**Archivos:** `models/class/class.users.php` (`normalizePermits()`), `core/config/components/roles.js` (`renderPermits()`)
**Archivo SQL:** `models/querys/migrations/002_roles_modules_schema.sql`
**Estado:** SQL generado — pendiente ejecución + code changes

- `normalizePermits()` pasa de 5 a 7 posiciones.
- `renderPermits()` en roles.js renderiza 7 botones toggle con etiquetas según `permitsSchema` ('cms' vs 'erp').
- Nueva columna `rol_mod_permits_schema ENUM('cms','erp') DEFAULT 'cms'` en `roles_modules`.
- Datos existentes de 5 bits siguen válidos (posiciones 5-6 quedan en 0).

---

### Paso 8 — Audit logging en mutaciones
**Archivos:** `models/class/class.users.php` (createRol, updateRol, deleteRol, createUser, updateUser)
**Estado:** Pendiente

Después de cada mutación exitosa, llamar:
```php
$this->fmt->logs->logAction([
    'userId'     => $vars['user']['userId'],
    'entitieId'  => $vars['entitieId'],
    'action'     => 'createRol',
    'description'=> "Rol '{$name}' creado",
]);
```

La tabla `logs` ya existe con la estructura correcta.

---

### Paso 9 — Registrar módulo REMS Config y gating
**Archivos:** `models/querys/nucleo_base.sql`, componentes REMS JS
**Archivo SQL:** `models/querys/migrations/003_rems_config_module.sql`
**Estado:** SQL generado — pendiente ejecución + code changes

Registrar módulo "Configuración REMS" en `modules` y `systems_modules`. Asignar `rol_mod_permits='0,0,0,0,0,0,1'` (solo bit 6 = Configurar) para roles permitidos. Agregar `hasPermit(modId, 6)` en el componente JS de tipos de documentos.

---

## Matriz de prioridades

| Paso | Riesgo | Esfuerzo | Valor | Depende de |
|---|---|---|---|---|
| 1 — Hardener token | Bajo | Pequeño | Crítico (seguridad) | — |
| 4 — `hasPermit()` utility | Bajo | Pequeño | Alto (foundation) | — |
| 5 — Config admin gating | Bajo | Medio | Alto (UX inmediata) | Paso 2+3 para full, o solo rolName para parcial |
| 6 — API permission gating | Medio | Medio | Crítico (seguridad) | Paso 1 |
| 2 — DB migración roles | Bajo | Pequeño | Habilitador | — |
| 3 — Session payload | Medio | Medio | Habilitador | Paso 2 |
| 8 — Audit logging | Bajo | Pequeño | Alto (compliance) | — |
| 7 — 7 bits ERP | Medio | Medio | Medio (ERP feature) | Paso 2+3 |
| 9 — REMS doc types gating | Bajo | Pequeño | Medio | Paso 2+3+4 |

**Secuencia recomendada:** 2 → 1 → 3 → 4 → 5 → 6 → 8 → 7 → 9

---

## Archivos clave

| Archivo | Rol |
|---|---|
| `models/class/class.users.php` | validateUserToken, normalizePermits, userDataAuth |
| `core/config/components/roles.js` | renderPermits, saveRole, loadRoleForm |
| `core/dashboard/components/configurations.js` | loadConfigAdmin, getPageConfig |
| `core/dashboard/views/configWindow.html` | Sidebar del config admin |
| `components/functions.js` | hasPermit() (a agregar) |
| `components/renders/renderTables.js` | renderColActions() |
| `models/querys/migrations/` | Migraciones SQL |
