# Sistema de Permisos Específicos a Nivel de Usuario

## Introducción

El SLM Framework ahora soporta permisos específicos a nivel de usuario, permitiendo personalizar los permisos de usuarios individuales sin necesidad de crear o modificar perfiles completos. Esta funcionalidad es ideal para casos especiales donde un usuario necesita permisos diferentes a su perfil asignado.

## Características Principales

- **ACL a nivel de usuario**: Campo `acl` opcional en documentos de usuario
- **Fusión automática**: Los permisos del usuario se combinan con los del perfil
- **Override por módulo**: Permisos de usuario reemplazan completamente los del perfil por módulo
- **Interfaz gráfica**: Gestión visual desde el módulo de usuarios
- **Indicadores visuales**: Badges de estado muestran diferencias con el perfil
- **Backward compatible**: Usuarios sin ACL personalizado funcionan como antes

## Estrategia de Fusión de Permisos

### Reglas de Fusión

1. **Base**: El ACL del perfil se usa como fundamento
2. **Override**: Si el usuario define permisos para un módulo, reemplazan COMPLETAMENTE los del perfil para ese módulo específico
3. **Revocación**: Un array vacío `[]` en usuario revoca el acceso a ese módulo
4. **Adicionales**: El usuario puede tener permisos sobre módulos que no están en su perfil
5. **Sin override**: Módulos no definidos en usuario heredan permisos del perfil sin cambios

### Ejemplos de Fusión

#### Ejemplo 1: Override Simple

```json
// Perfil ACL
{
  "GROUP_1": {
    "config.users": ["read", "create", "update", "delete"],
    "config.profile": ["read", "create", "update", "delete"]
  }
}

// Usuario ACL
{
  "GROUP_1": {
    "config.users": ["read"]
  }
}

// Resultado Final
{
  "GROUP_1": {
    "config.users": ["read"],           // ← Override: solo lectura
    "config.profile": ["read", "create", "update", "delete"]  // ← Heredado
  }
}
```

#### Ejemplo 2: Revocación de Acceso

```json
// Perfil ACL
{
  "GROUP_1": {
    "proyectos.admin": ["read", "create", "update"],
    "proyectos.catalogo": ["read"]
  }
}

// Usuario ACL
{
  "GROUP_1": {
    "proyectos.admin": []  // Array vacío = revocado
  }
}

// Resultado Final
{
  "GROUP_1": {
    "proyectos.admin": [],                    // ← Acceso revocado
    "proyectos.catalogo": ["read"]            // ← Heredado
  }
}
```

#### Ejemplo 3: Permisos Adicionales

```json
// Perfil ACL (Viewer)
{
  "GROUP_1": {
    "home.main": ["read"],
    "proyectos.catalogo": ["read"]
  }
}

// Usuario ACL
{
  "GROUP_1": {
    "reportes.avanzados": ["read", "create"]  // Módulo no en perfil
  }
}

// Resultado Final
{
  "GROUP_1": {
    "home.main": ["read"],                          // ← Heredado
    "proyectos.catalogo": ["read"],                 // ← Heredado
    "reportes.avanzados": ["read", "create"]        // ← Adicional
  }
}
```

## Uso de la Interfaz

### Acceder a Permisos Específicos

1. Ir a **Configuración → Usuarios**
2. Crear nuevo usuario o editar uno existente
3. Ir a la pestaña **"Permisos Específicos"**
4. Marcar checkbox **"Este usuario tiene permisos personalizados"**
5. Configurar permisos en el acordeón de grupos/módulos
6. Guardar cambios

### Indicadores de Estado

La columna "Estado" muestra un badge con el estado de cada módulo:

| Badge | Significado | Descripción |
|-------|-------------|-------------|
| **Heredado** (verde) | Sin override | Usuario hereda permisos del perfil |
| **Sin cambios** (azul) | Redundante | Usuario tiene los mismos permisos que el perfil |
| **Modificado** (amarillo) | Override | Usuario tiene permisos diferentes al perfil |
| **Revocado** (rojo) | Sin acceso | Usuario no tiene acceso a este módulo |
| **Adicional** (azul primario) | Nuevo | Usuario tiene acceso a módulo no en perfil |

### Mejores Prácticas

#### ✅ Casos de Uso Recomendados

1. **Usuario temporal con acceso reducido**
   - Asignar perfil "Admin"
   - Revocar acceso a módulos sensibles
   - Ejemplo: Consultor externo

2. **Usuario con responsabilidad adicional**
   - Asignar perfil "Editor"
   - Agregar permisos de lectura en reportes
   - Ejemplo: Editor que necesita ver estadísticas

3. **Caso especial de emergencia**
   - Asignar perfil estándar
   - Agregar temporalmente permisos específicos
   - Ejemplo: Soporte técnico temporal

#### ❌ Anti-patrones (Evitar)

1. **Sobrescribir todos los módulos**
   - Si necesitas cambiar más del 50% de permisos, crea un nuevo perfil

2. **Permisos redundantes**
   - No configures permisos idénticos al perfil (badge "Sin cambios")
   - Solo override lo que realmente necesitas cambiar

3. **Gestión masiva**
   - No uses permisos específicos para grupos de usuarios
   - Para múltiples usuarios con mismos permisos, crea un perfil

## Implementación Técnica

### Estructura de Datos en Elasticsearch

```json
{
  "_id": "USER_ID_123",
  "_source": {
    "name": "Juan",
    "lastname": "Pérez",
    "email": "juan@example.com",
    "profile": "PROFILE_ID_456",
    "acl": {                              // ← Campo opcional
      "GROUP_1": {
        "config.users": ["read"],
        "reportes.avanzados": ["read", "create"]
      }
    },
    "slm": {
      "type": "user",
      "timestamp": "2025-01-22T10:30:00Z"
    }
  }
}
```

### Flujo de Autenticación

1. Usuario inicia sesión
2. Sistema carga sesión (`_SLM::loadSession()`)
3. Se obtiene perfil del usuario y su ACL
4. Si usuario tiene campo `acl`, se carga en sesión como `user_acl`
5. Al solicitar permisos (`_SLM::getUserACL()`):
   - Se obtiene ACL del perfil
   - Se obtiene ACL del usuario
   - Se fusionan usando `_SLM::mergeACLs()`
   - Se retorna ACL fusionado

### Archivos Modificados

| Archivo | Cambios | Líneas |
|---------|---------|--------|
| `_SLM.slm` | Core: loadSession(), getUserACL(), mergeACLs() | 169-320 |
| `ajax.users.slm` | CRUD: getUser(), createUser(), updateUser() | 71-294 |
| `view.users.slm` | UI: agregar tab de permisos | 73-170 |
| `load.users.slm` | Data: cargar grupos de menú | 1-4 |
| `end.users.slm` | JS: lógica de interfaz de permisos | 496-660 |

## API de Backend

### Obtener Usuario con ACL

```php
// Endpoint: config.users
// Acción: getUser

POST ./?a=config.users
{
  "acc": "getUser",
  "id": "USER_ID"
}

// Response
{
  "error": false,
  "message": "",
  "data": {
    "user": {
      "id": "USER_ID",
      "name": "Juan",
      "profile": "PROFILE_ID",
      "acl": { ... }  // ACL personalizado o null
    },
    "profiles": [ ... ],
    "profile_acl": { ... }  // ACL del perfil para comparación
  }
}
```

### Crear Usuario con ACL

```php
POST ./?a=config.users
{
  "acc": "createUser",
  "name": "Juan",
  "email": "juan@example.com",
  "profile": "PROFILE_ID",
  "hasCustomACL": "1",
  "acl[GROUP_1][config.users][]": ["read"]
}
```

### Actualizar Usuario con ACL

```php
POST ./?a=config.users
{
  "acc": "updateUser",
  "user_id": "USER_ID",
  "hasCustomACL": "1",
  "acl[GROUP_1][config.users][]": ["read", "create"]
}
```

## JavaScript API

### Cargar Checkboxes de ACL

```javascript
loadACLCheckboxes({
  "GROUP_1": {
    "config.users": ["read", "create"],
    "home.main": ["read"]
  }
});
```

### Comparar con Perfil

```javascript
compareWithProfilePermissions(userACL, profileACL);
// Actualiza badges de estado en la interfaz
```

### Eventos

```javascript
// Al cambiar checkbox de permisos
document.addEventListener('change', function(e) {
  if (e.target.matches('input[name^="acl["]')) {
    // Se actualiza automáticamente el badge de estado
  }
});
```

## Casos de Uso Detallados

### Caso 1: Consultor Temporal

**Escenario**: Contratar un consultor externo por 3 meses con acceso limitado.

**Solución**:
1. Crear usuario con perfil "Admin"
2. Ir a "Permisos Específicos"
3. Revocar acceso a módulos sensibles:
   - `config.users` → `[]` (revocado)
   - `config.profile` → `[]` (revocado)
   - `config.modules` → `[]` (revocado)
4. Mantener acceso a:
   - `proyectos.admin` → heredado
   - `proyectos.catalogo` → heredado

**Resultado**: Usuario con permisos de admin en proyectos, pero sin acceso a configuración.

### Caso 2: Editor con Permisos de Lectura en Reportes

**Escenario**: Editor que necesita ver reportes pero no modificarlos.

**Solución**:
1. Crear usuario con perfil "Editor"
2. Agregar permiso adicional:
   - `reportes.avanzados` → `["read"]`

**Resultado**: Usuario con permisos de editor + lectura de reportes.

### Caso 3: Soporte Técnico Temporal

**Escenario**: Dar acceso temporal a soporte sin crear nuevo perfil.

**Solución**:
1. Crear usuario con perfil "Viewer"
2. Agregar permisos temporales:
   - `config.users` → `["read", "update"]`
   - `system.logs` → `["read"]`

**Resultado**: Usuario puede ver y actualizar usuarios, y ver logs del sistema.

## Preguntas Frecuentes

### ¿Qué pasa si borro el ACL personalizado?

Si desmarcas "Este usuario tiene permisos personalizados" y guardas, el campo `acl` del usuario se establece en `null`, y el usuario heredará completamente los permisos de su perfil.

### ¿Puedo revocar TODOS los permisos de un módulo?

Sí, simplemente desmarca todos los checkboxes (Leer, Crear, Actualizar, Eliminar) de ese módulo. El array vacío revocará el acceso.

### ¿Los cambios afectan sesiones activas?

No inmediatamente. Los cambios en permisos se aplican en el próximo inicio de sesión del usuario. Para aplicar cambios inmediatamente, el usuario debe cerrar sesión y volver a iniciar.

### ¿Puedo ver qué usuarios tienen permisos personalizados?

Actualmente no hay filtro específico, pero puedes identificarlos buscando usuarios con el campo `acl` no nulo en Elasticsearch:

```json
GET /slm/_search
{
  "query": {
    "bool": {
      "must": [
        { "term": { "slm.type": "user" } },
        { "exists": { "field": "acl" } }
      ]
    }
  }
}
```

### ¿Afecta el rendimiento?

No. La fusión de ACLs se realiza una sola vez al cargar la sesión, sin queries adicionales ni procesamiento complejo.

## Compatibilidad

- **Backward compatible**: Usuarios existentes sin campo `acl` funcionan sin cambios
- **Versión mínima**: SLM Framework 2.0+
- **Elasticsearch**: Compatible con todas las versiones soportadas

## Troubleshooting

### Problema: Los permisos no se aplican

**Solución**: Verificar que:
1. El checkbox "Este usuario tiene permisos personalizados" esté marcado
2. Se haya guardado el formulario correctamente
3. El usuario haya cerrado sesión y vuelto a iniciar

### Problema: Badge muestra "Sin cambios"

**Solución**: Esto significa que los permisos configurados son idénticos al perfil. Es redundante y puedes eliminar ese override.

### Problema: No aparece la pestaña "Permisos Específicos"

**Solución**: Verificar que:
1. Exista el archivo `load.users.slm`
2. La función `fetchGroupMenus()` esté disponible
3. No haya errores JavaScript en la consola

## Changelog

### Versión 1.0.0 (2026-01-22)

- Implementación inicial del sistema de permisos a nivel usuario
- Interfaz gráfica con tabs
- Indicadores visuales de estado
- Fusión automática de ACLs
- Documentación completa

## Contribuir

Para reportar bugs o sugerir mejoras al sistema de permisos específicos:

1. Crear issue en el repositorio
2. Describir el caso de uso o problema
3. Incluir ejemplos de ACLs si aplica

## Licencia

Copyright © 2026 SLM Framework. Todos los derechos reservados.
