# Autorización del panel legado

## Permisos efectivos

`LegacyAuthorization::allows` combina la relación `users.role_id → roles` y los roles adicionales `user_roles`, comprobando `permission_role → permissions.key`. El rol principal cuenta aunque la pivot de roles adicionales esté vacía. Un usuario inactivo siempre queda denegado. No existe un bypass basado en contener «Admin» en el nombre.

El login usa email/password/status, sesiones Laravel con regeneración de ID y CSRF. El proveedor `LegacyUserProvider` conserva los hashes bcrypt y no modifica remember_token al entrar o salir. No se ofrece remember-me. La sesión vuelve a validar usuario activo y `browse_admin` en cada petición protegida.

## Permisos por recurso

| Recurso | Permiso |
|---|---|
| Entrada a `/admin` | `browse_admin` |
| Clientes, servidores, demos, paquetes | `browse_` + nombre real de tabla |
| Usuarios, créditos, duraciones, configuración | `browse_users`, `browse_credits`, `browse_durations`, `browse_settings` |
| Movimientos y aviso de sesiones de una plataforma | Permiso `browse_` de clientes de esa plataforma |
| Solicitudes | `browse_admin`, con alcance por autor/plataforma |

El dump no contiene permisos específicos de movimientos ni content_requests. El mapeo anterior es una decisión explícita de FASE 1, no una afirmación sobre el PHP desaparecido. No se insertan permisos nuevos. La navegación y las rutas directas usan la misma comprobación; ocultar el menú no es el control de seguridad.

## Alcance de filas

- Roles globales: lista explícita `LEGACY_GLOBAL_ROLE_IDS`. Por defecto vacío. En esta copia se habilitaron IDs `1` (admin) y `4` (general_admin) como administradores globales de lectura. Esto solo amplía filas y plataformas, no salta permisos del recurso.
- Sin rol global: el usuario ve sus propios clientes y demos. Si tiene `browse_users`, se incluye el árbol descendiente `parent_user_id` hasta 32 niveles, con detección de repetidos. No se sigue el padre ausente ni se concede acceso global por un huérfano.
- `users.service` se decodifica de forma acotada; solo se aceptan `plex`, `jellyfin`, `emby`. Vacío o inválido significa ninguna plataforma para no globales. No es una lista de permisos.
- Servidores y paquetes: los no globales requieren la asignación de servidor mediante las pivots existentes de su árbol visible. No se heredan asignaciones de ancestros de forma implícita. `admin.server_for_alls` no se activa automáticamente.
- Clientes: se limita por propietario/árbol; su servidor puede mostrarse como relación de ese cliente aunque no exista una pivot de asignación. Eso permite consultar clientes legítimos del legado sin habilitar administración del servidor.
- Movimientos: `parent_user_id` limita el histórico para no globales. Los textos `user/customer/server` no se convierten en relaciones ni permisos. Un administrador global puede ver huérfanos.
- Solicitudes: autor dentro del alcance y, para no globales, plataforma habilitada. Las solicitudes de autores eliminados solo aparecen con alcance global.
- Dashboard: usa las mismas consultas y permisos; no expone totales globales a vendedores. Si falta permiso de créditos muestra únicamente el saldo propio; si falta permiso de usuarios no muestra su total.

Las relaciones cargadas para renderizar seleccionan nombre/id, sin tokens ni contraseñas. El contador de clientes por servidor también se limita al alcance permitido. Configuración enumera claves/etiquetas con valores siempre enmascarados; no entrega `settings.value` al navegador.

## Límites conscientes

La semántica completa de roles/subrevendedores no puede recuperarse solo del SQL. La configuración es conservadora fuera de los dos roles globales elegidos. Cualquier ampliación debe incluir una prueba de aislamiento de datos. No se implementan acciones de escritura aunque el rol tenga `add_*`, `edit_*` o `delete_*`.
