# Arquitectura — FASES 1 y 2

Monolito Laravel 12 con Blade, Bootstrap 5 servido localmente y JavaScript mínimo para el menú. Sin compilación frontend, cron ni microservicios. FASE 2 agrega APIs remotas de solo lectura, deshabilitadas hasta autorizar sus destinos.

## Flujo

1. Login Laravel → proveedor de usuarios legacy, sin rehash ni remember-me.
2. Middleware `auth` + `legacy.active` → usuario activo y `browse_admin`.
3. Controlador → `LegacyReadService` + `LegacyAuthorization`.
4. Eloquent → tablas originales mediante `$table` explícito y cuenta SQL `SELECT`.
5. Filas seguras → Blade con escape automático; no se entrega el modelo completo a JavaScript.

## Componentes

- `app/Models`: 49 modelos del inventario, pivots incluidas. `LegacyModel` y `ReadOnlyLegacy` bloquean guardado/borrado de instancias. Las tablas sin PK o con PK compuesta se consultan sin suponer un `id` inexistente.
- `app/Support/Platform.php`: mapa fijo de las tres plataformas, modelos y claves. Nunca se obtiene una clase PHP desde datos BREAD ni desde entrada del usuario.
- `app/Support/LegacyServiceParser.php`: lector de listas doblemente codificadas, profundidad limitada, sin excepciones por contenido inválido.
- `LegacySettingService`: lectura backend de valores opacos por clave; no ejecuta PHP, plantillas ni integraciones.
- `LegacyReadService`: proyecciones de columnas seguras, relaciones, filtros enlazados, paginación y presentación de estados.
- `LegacyAuthorization`: permisos heredados, plataformas habilitadas y alcance de filas.
- `Navigation`: usa esas mismas reglas para mostrar enlaces.
- `DashboardController`: agregados locales separados de `LiveDashboard`, que presenta estados runtime y sesiones con caché corta.
- `LegacyListController`: listados locales GET. `MediaServerController` resuelve información, bibliotecas, usuarios y sesiones mediante servicios.
- `AuthController`: login/logout, validación y límite de intentos. La única escritura de aplicación es a sesiones/caché en archivos, no al legado.

## Contratos especiales

`sessions` pertenece a Plex; `notifications` es un aviso propio. Sesión web y caché usan archivos. Los modelos de tablas técnicas no tienen CRUD público. `PasswordReset` es un modelo de lectura sin PK; el broker está configurado para el nombre antiguo, sin rutas de restablecimiento habilitadas.

Clientes Plex conservan estados de texto; Jellyfin/Emby conservan enteros 0/1/2. El dashboard considera activos los de estado activo con `date_to >= hoy`; el filtro de estado de listado consulta el valor almacenado y el filtro de vencimiento consulta la fecha. La presentación puede indicar «Vencido» por fecha aunque el estado remoto/local siga activo.

`date_to` de demos Emby/Jellyfin es datetime, no date. Paquetes no interpretan `libraries` porque no hay muestras suficientes; su listado informa esa limitación. Los JSON sensibles permanecen ocultos; no hay cifrado ni conversión masiva. FASE 2 lee `json_data.Id` de Jellyfin/Emby para comparar identidades de forma exacta dentro de un servidor, sin persistirlo ni modificar el JSON.

Tickets solo resuelven autores `App\Models\User` mediante una relación explícita; otros tipos quedan sin autor resuelto. `personal_access_tokens` no resuelve tipos arbitrarios del legado. Las extensiones de esos modelos se posponen hasta verificar sus contratos.

## Pruebas y datos

## Integración multimedia de FASE 2

`MediaServerInterface` define las operaciones de lectura. `PlexService`, `JellyfinService` y `EmbyService` normalizan respuestas en `MediaResult`; `MediaTransport` concentra Laravel Http, límites, validación y manejo de errores. `LegacyConnection` interpreta los formatos de conexión comprobados, incluido el conjunto PMS dentro del JSON Plex. `DestinationPolicy` aplica SSRF y pin DNS; `SensitiveDataRedactor` retira secretos antes de cachear o presentar.

`MediaAccess` aplica permisos y alcance por servidor y filtra identidades remotas por propietario después de consultar caché. `MediaReader` cachea por plataforma/servidor/operación y configuración. `LiveDashboard` mantiene separadas las lecturas runtime de los agregados SQL. No se escribe disponibilidad remota en `status` o `is_dead`.

Las conexiones están deshabilitadas por defecto y requieren una lista explícita de IDs autorizados. El comando manual `media:test-connections` usa exclusivamente esa lista. No existe scheduler ni operación remota de escritura. Rutas, tiempos, configuración, seguridad y límites se detallan en [MEDIA-SERVICES.md](MEDIA-SERVICES.md).

## Verificación de datos

`tests/Fixtures/legacy-schema.sql` es DDL sin INSERT para referencia MySQL. `legacy-sqlite.sql` es una adaptación estructural para pruebas aisladas, sin pretender validar todos los comportamientos de MySQL. `required-defaults.json` contiene valores sintéticos, no datos del backup.

Cada prueba de base de datos comprueba antes de crear tablas que la conexión sea SQLite `:memory:` y `APP_ENV=testing`. No se usan `RefreshDatabase`, migraciones estándar ni factories con datos reales. Las pruebas impiden llamadas HTTP externas.

La restauración MySQL se verifica aparte mediante `legacy:verify`: lee los 49 modelos, relaciones y recuentos sin cambiar registros. El diseño usa Laravel 12 por compatibilidad PHP 8.2; la [política oficial de soporte](https://laravel.com/docs/12.x/releases) obliga a planificar la siguiente actualización.
