# Gestión de Permisos

## Estructura general

Los permisos siguen el formato `recurso.accion`. Cada página/módulo define su propio recurso, y siempre se usan las mismas 5 acciones estándar:

```
recurso.index
recurso.create
recurso.update
recurso.delete
recurso.export
```

---

## Recursos registrados

| Módulo | Recurso | Página |
|--------|---------|--------|
| Core | `user` | Gestión de usuarios, roles |
| Core | `wizard` | Asignación de permisos, permisos extra |
| Core | `module_manager` | Gestor de módulos |
| Core | `db_migration` | Asistente de migración de BD |
| Core | `setting` | Configuración del sistema |
| Core | `admin` | Vista demo |
| SFM | `wallet` | Billeteras |
| SFM | `bucket` | Categorías (buckets) |
| SFM | `credit_card` | Tarjetas de crédito |
| SFM | `tag` | Etiquetas |
| SFM | `card_cycle` | Ciclos de tarjeta |
| SFM | `transaction` | Movimientos/transacciones |

---

## Agregar permisos a un módulo nuevo

### 1. Declarar el recurso en `pages.json`

```json
{
  "pages": [
    {
      "title": "Mi Página",
      "permission": "mi_recurso",
      "icon": "o-document",
      "route": "mi_ruta.index"
    }
  ]
}
```

### 2. Registrar el recurso en `module.json` (opcional, para prune)

```json
{
  "permissions": ["mi_recurso"]
}
```

### 3. Crear los permisos en BD

Ir a **Gestión de Usuarios → Permisos → Sincronizar** y pulsar el botón de sincronización del recurso correspondiente, o usar "Sync All".

---

## Proteger un Controller

```php
public function index()
{
    $this->checkPermission('mi_recurso', 'index');
    // ...
}

public function store(Request $request)
{
    $this->checkPermission('mi_recurso', 'create');
    // ...
}
```

`checkPermission()` llama `abort(403)` si el usuario no tiene el permiso. Los admins pasan automáticamente.

---

## Proteger un componente Livewire

### 1. Agregar el trait

```php
use App\Traits\WithPermissionCheck;

class MiComponente extends Component
{
    use WithPermissionCheck;
```

### 2. Llamar `requirePermission()` al inicio de cada método de escritura

```php
public function crear(): void
{
    $this->requirePermission('mi_recurso.create');
    // ...
}

public function actualizar(): void
{
    $this->requirePermission('mi_recurso.update');
    // ...
}

public function eliminar(int $id): void
{
    $this->requirePermission('mi_recurso.delete');
    // ...
}
```

Igual que `checkPermission()`, lanza `abort(403)` si no tiene permiso.

---

## Proteger exports (WithTableExport)

Sobreescribir `exportableResource()` en el componente que usa el trait:

```php
protected function exportableResource(): string
{
    return 'mi_recurso';
}
```

El trait llama `gateExport()` automáticamente en `exportXlsx()`, `exportCsv()` y `exportPdf()`.

---

## Ocultar elementos en las vistas (Blade)

Usar `@can` / `@endcan` para envolver botones, formularios y modales:

```blade
{{-- Botón de crear --}}
@can('mi_recurso.create')
    <x-mary-button label="{{ __('Nuevo') }}" wire:click="..." />
@endcan

{{-- Botones de fila --}}
@scope('actions', $item)
    @can('mi_recurso.update')
        <x-mary-button icon="o-pencil" wire:click="openEdit({{ $item->id }})" />
    @endcan
    @can('mi_recurso.delete')
        <x-mary-button icon="o-trash" wire:click="eliminar({{ $item->id }})" />
    @endcan
@endscope

{{-- Modal de creación --}}
@can('mi_recurso.create')
<x-mary-modal wire:model="createModal">
    ...
</x-mary-modal>
@endcan
```

> **Regla:** cada elemento que dispara una acción de escritura necesita su `@can`. El check en el backend es la barrera real; el `@can` en la vista es UX.

---

## Admins

`Gate::before()` en `AppServiceProvider` retorna `true` para cualquier usuario con `is_admin = true`. Esto significa que:

- `@can('cualquier.permiso')` siempre pasa para admins.
- `requirePermission()` retorna inmediatamente para admins.
- `checkPermission()` retorna inmediatamente para admins.

No es necesario agregar lógica especial para admins en ningún lugar nuevo.

---

## Invalidación de caché de navegación

Cada vez que cambian roles, permisos o asignaciones, se debe llamar `PermissionVersion::bump()`. Esto ocurre automáticamente via observers en la mayoría de los casos:

| Evento | Observer | Resultado |
|--------|----------|-----------|
| Permiso creado/eliminado | `PermissionObserver` | `bump()` |
| Rol actualizado/eliminado | `RoleObserver` | `bump()` |
| `is_admin` del usuario cambia | `UserObserver` | `bump()` |
| Usuario eliminado | `UserObserver` | limpia su caché |

**Excepción — tablas pivot:** Spatie no dispara eventos de modelo al usar `givePermissionTo()`, `revokePermissionTo()` o `syncRoles()`. En esos casos llamar `bump()` manualmente:

```php
$role->givePermissionTo($permission);
app(PermissionRegistrar::class)->forgetCachedPermissions();
PermissionVersion::bump(); // obligatorio
```

Los componentes que hacen esto actualmente: `MenuNavegacion`, `UserForm`, `WizardController`, `ModuleManager`.

---

## Vista de errores 403

Existe `resources/views/errors/403.blade.php`. Se muestra automáticamente cuando se llama `abort(403)` desde cualquier controller o componente Livewire.
