# Roadmap & Análisis del Proyecto Laravel Roles

> Última actualización: 2026-05-12 (rev. 9)

---

## Stack Tecnológico

- **Backend**: Laravel 13 + Livewire 4 + Volt
- **Frontend**: Tailwind CSS 4 + DaisyUI 5 + MaryUI + Vite 6
- **Permisos**: spatie/laravel-permission
- **PDF**: barryvdh/laravel-dompdf
- **Exportación**: openspout/openspout
- **Base de datos**: SQLite (desarrollo) / MySQL (producción)

---

## Módulos Activos

| Módulo | Prioridad | Estado |
|--------|-----------|--------|
| Core | 1 | Activo (siempre) |
| SimpleFinancialManagement | 2 | Activo |
| Bitácora | 3 | Activo |
| MediaMinistry | 4 | Activo |
| HotelManagement | 5 | Activo |

---

## Observaciones Generales del Proyecto

### Decisiones Tomadas

- **WhatsApp API**: No se implementará por ahora — sin capacidad de pagar la API de Twilio/Meta.

### Funcionalidades Transversales Implementadas ✅

1. **Sistema de importación CSV con mapeo de campos** ✅
2. **Dashboard personalizable por usuario/rol** ✅
3. **Imagen configurable para PDFs desde el panel de Configuración** ✅

---

## EN PROGRESO

*(Sin elementos activos — todo el roadmap completado)*

---

## CORRECCIONES (Bugs / Incompletos)

### Core

- [x] **Wizard de instalación desprotegido**: Protección en `InstallWizard::mount()`. Nota: el Closure middleware en la ruta fue removido (2026-05-10) porque impedía que Laravel serializara el middleware y cancelaba la carga de TODAS las rutas del Core — el guard en `mount()` es suficiente.
- [x] **Auditoría inconsistente**: `WithAuditLog` añadido a los modelos que faltaban: `MediaMinistry\Event`, `MediaMinistry\Task`, `MediaMinistry\MediaFile`, `SFM\CardCycle`, `SFM\SavingGoal`. Los modelos clave de HotelManagement y Bitácora ya lo tenían.
- [x] **Invalidación de cache de permisos**: Middleware `CheckPermissionVersion` añadido al grupo `web`. Compara la versión global (`PermissionVersion::get()`) contra la almacenada en sesión del usuario; si difieren, llama a `forgetCachedPermissions()` y actualiza la versión en sesión. Funciona en la siguiente request sin re-login.

### SimpleFinancialManagement

- [x] ~~**Migración de Subcategory eliminada**~~: La migración `2026_05_01_100003` no existe en el filesystem — era un phantom. La única migración de DROP (`2026_04_28_200003`) elimina las tablas de SharedFunds, que nunca se usaron en producción y tienen comentario explícito en el `down()`. No hay riesgo real.
- [x] **TransactionObserver en operaciones masivas**: No existen operaciones bulk sobre `Transaction` en el codebase actual. Añadido comentario en `TransactionObserver` advirtiendo la limitación para futuros desarrolladores. Mientras no haya bulk ops, el observer cubre el 100% de los casos.
- [x] **CardCycle — transacciones en ciclos cerrados**: `createCycle()` ahora valida solapamiento de períodos para la misma tarjeta antes de crear. Si existe un ciclo que se superpone al período solicitado, retorna error de validación en `newPeriodStart`.

### MediaMinistry

- [x] ~~**Bug silencioso en GoogleCalendarService**~~: Auditado — `TaskObserver` ya tiene `if ($googleId)` como guard. Si el servicio retorna string vacío, no guarda nada y no hace nada. El comportamiento es silencioso pero correcto mientras la integración no esté implementada.
- [x] **BibleImporter — duplicados en reimportación**: `ImportBibleFromXml::handle()` ahora hace `Verse::where('version_id')->delete()` antes de empezar el insert. La reimportación del mismo `version_id` es idempotente: borra los versículos anteriores y los vuelve a insertar limpio. La validación `unique:mm_bible_versions,code` en el form ya previene crear dos versiones con el mismo código.
- [x] **Imágenes rotas (logos/favicons/Apariencia)**: Todos los `Storage::disk('public')->url()` en blade reemplazados por el helper `public_storage_url($path)`. Nueva ruta `GET /pf/{path}` (`storage.public`) en Core sirve archivos del directorio `app/` del disco público sin depender del symlink — idéntico al patrón `mm.file` de MediaLibrary. Funciona en dev (Windows + `php artisan serve`) y en producción.

#### MediaLibrary — Correcciones críticas (auditadas 2026-05-09)

- [x] **Sin validación de MIME type ni extensión** *(seguridad)* — **CORREGIDO** en `MediaLibrary.php`. Ahora usa `mimes:` por tipo: imágenes (jpg,jpeg,png,webp,gif,svg), videos (mp4,mov,avi,webm), audio (mp3,wav,ogg,m4a), pdf.
- [x] **Archivos físicos huérfanos al eliminar** *(integridad de datos)* — **CORREGIDO** en `MediaLibrary.php::deleteFile()`. Ahora llama a `Storage::disk($file->disk)->delete($file->file_path)` antes de eliminar el registro de BD.
- [x] **Límites de tamaño insuficientes** — **CORREGIDO** en `MediaLibrary.php`. Nuevos límites: imágenes 100MB, videos 500MB, audio 50MB, PDFs 50MB. **Pendiente en servidor**: ajustar `upload_max_filesize` y `post_max_size` en la configuración PHP de producción.
- [x] **Miniaturas ausentes en galería** — `loading="lazy"` + `decoding="async"` **APLICADO** + thumbnails reales via `intervention/image` v3 **IMPLEMENTADO**. Al subir, genera JPEG 400×300 en `mm-media/thumbnails/`. La galería usa el thumbnail; el archivo original sólo se descarga al hacer clic en Descargar.

### HotelManagement

- [x] ~~**Doble booking posible**~~: Auditado — `ReservationIndex.php` ya valida solapamiento de fechas por habitación y capacidad del hotel (líneas 113-143). No es bug real.
- [x] **Checkout sin validación de balance**: **CORREGIDO** en `FrontDeskIndex.php` y `CheckinIndex.php`. Calcula el balance proyectado con `now()` antes de cerrar y bloquea el checkout si hay saldo pendiente.
- [x] ~~**Conflicto Reservation + Checkin al cancelar**~~: Auditado — `ReservationObserver` ya lanza `RuntimeException` si se intenta cancelar una reserva con checkin activo. No es bug real.
- [x] **Botones de acción en tablas apilados verticalmente**: **CORREGIDO** en las 6 tablas del módulo (`client-index`, `checkin-index`, `reservation-index`, `room-index`, `hotel-index`, `payment-index`). Botones envueltos en `<div class="flex gap-1">` para alineación horizontal, igual que SFM.

---

## MEJORAS POR MÓDULO

### Core

| # | Mejora | Descripción |
|---|--------|-------------|
| 1 | Dashboard de administrador ✅ | Componente `AdminDashboard`, ruta `GET /AdminDashboard` (`admin_dashboard.index`). KPIs: total usuarios / admins / módulos habilitados / errores 7d. Tabla de actividad reciente (10 entradas), alertas de sistema (errores/warnings recientes), lista de módulos con estado on/off. Permiso: `admin_dashboard.index`. |
| 2 | Notificaciones in-app ✅ | Tabla `notifications` (Laravel database channel). Componente `NotificationBell` con badge rojo + dropdown últimas 6 + marcar leída/todas leídas. `NotificationCenter` (`/Notificaciones`) con filtro todo/sin leer/leídas, marcar leída/no leída, eliminar individual o todas las leídas. Campana en los 3 layouts de navegación. Primer trigger: `LowWalletBalance` — se dispara cuando un gasto/transferencia cruza el umbral de saldo configurado por wallet (`low_balance_threshold` en `sfm_wallets`). |
| 3 | Auditoría completa ✅ | `WithAuditLog` aplicado a todos los modelos relevantes: SFM (Subcategory), MediaMinistry (CopyTemplate, Delivery, DeliveryVersion, EventType, TaskType, Theme), Bitácora (TaskFile, TaskNote). Los demás ya lo tenían. Omitidos intencionalmente: Verse/Book (datos de importación masiva) y tablas pivot. |
| 4 | Identidad del sitio ✅ | Ver especificación completa más abajo en "Apariencia — Identidad del sitio". |

### SimpleFinancialManagement

| # | Mejora | Descripción |
|---|--------|-------------|
| 1 | Dashboard con gráficas reales ✅ | Chart.js v4 vía npm. Gráfica de barras "Ingresos vs Gastos últimos 6 meses" + donut "Gastos por categoría este mes". Componentes Alpine (`sfmMonthlyChart`, `sfmDonutChart`) registrados en `app.js`. `wire:ignore` en el contenedor. Colores adaptados a tema claro/oscuro. |
| 2 | Presupuesto mensual ✅ | Sección "Presupuesto del mes" en `SfmDashboard`. Barras de progreso por bucket con `monthly_target`, coloreadas (verde <80% / amarillo 80-99% / rojo ≥100%). Selector de mes para ver histórico. La meta se configura en el campo `monthly_target` de cada bucket. |
| 3 | Metas de ahorro completas ✅ | Tabla `sfm_saving_goals`, modelo `SavingGoal`, componente `SavingGoalsIndex`. Tarjetas con barra de progreso, deadline con días restantes, acción "Agregar aporte" (suma/resta), marca como lograda automáticamente, color acento configurable, toggle "mostrar logradas". Ruta `GET /SavingGoals` (`saving_goals.index`). |
| 4 | PDF de transacciones mejorado ✅ | `PdfExportController@transactions` — ruta `GET /sfm/transacciones-pdf` (`sfm.transaction.pdf`). Aplica los filtros activos (dateFrom, dateTo, wallet). Secciones: encabezado con logo + nombre app, listado de movimientos con tipo coloreado, resumen por categoría/bucket con ingresos/gastos/neto, totales generales. Botón "PDF" en `MovementCrud` junto a XLSX y CSV. |
| 5 | Importar transacciones (CSV) ✅ | `Transaction implements Importable` — campos: tipo, monto, fecha, descripción, wallet (por nombre), bucket (por nombre). Hook `csvImportTransform` resuelve nombres a IDs. Botón importar junto a XLSX/CSV en `MovementCrud`. |
| 6 | UI de transferencias mejorada ✅ | Formulario: cuando tipo=transferencia muestra bloque visual Origen→Destino con borde info, hints "los fondos salen/llegan aquí" y oculta el campo Bucket. Tabla: columna Wallet muestra "Origen → Destino" para transferencias; columna Gasto muestra ⇄ icono + monto en info; bucket column muestra "—" para transferencias. |
| 7 | Alerta de saldo bajo ✅ | Campo `low_balance_threshold` (nullable) en `sfm_wallets`. Observer detecta cruce del umbral (de arriba hacia abajo) en `created` y dispara `LowWalletBalance` al usuario. Configurable desde el formulario de edición de cada wallet. |
| 8 | Estadísticas anuales ✅ | Componente `AnnualReport`, ruta `GET /SfmAnnual` (`sfm_annual.index`). Selector año (← →). 4 KPIs (ingresos, gastos, neto, tasa de ahorro) con diff % vs año anterior. Gráfica de barras 12 meses (reutiliza `sfmMonthlyChart`). Tabla mes a mes con columna ∆ Gastos vs año anterior. Sidebar: promedios mensuales + resumen año anterior + top 8 buckets con barra de porcentaje relativo. |

### MediaMinistry

| # | Mejora | Descripción |
|---|--------|-------------|
| 1 | Galería de medios ✅ | Thumbnails reales via `intervention/image` v3. Al subir una imagen se genera JPEG 400×300 en `mm-media/thumbnails/`. Columna `thumbnail_path` en `mm_media_files`. La galería muestra el thumbnail con fallback al original. `loading="lazy"` + `decoding="async"` aplicados. |
| 2 | Calendario visual de eventos ✅ | Vista toggle grid/calendario en `EventsIndex`. Grid mensual Blade + Tailwind puro (sin dependencias JS). Navegación prev/next mes, day cells con eventos como píldoras color-coded, overflow "+N más", día actual resaltado. Respeta filtros activos de tipo y búsqueda. |
| 3 | Dashboard con KPIs reales ✅ | Stats: eventos del mes, tareas pendientes/vencidas, medios subidos. Lista de próximos 14 días de eventos y tareas con vencimiento esta semana. |
| 4 | Sub-módulo Entregas ✅ | `DeliveriesIndex`, `DeliveryDetail`, versionado completo, descarga por versión. Ver especificación completa más abajo. |

### HotelManagement

| # | Mejora | Descripción |
|---|--------|-------------|
| 1 | Mapa visual de habitaciones ✅ | Componente `RoomMap`, ruta `GET /RoomMap` (`room_map.index`). Grid agrupado por hotel, tarjetas color-coded (verde/rojo/amarillo), ocupante + noches acumuladas en rooms activas, toggle mantenimiento↔disponible, checkout directo con validación de balance, auto-refresh `wire:poll.30s`. Acceso desde menú y botón "Mapa" en FrontDesk. |
| 2 | PDF de recibo al checkout ✅ | `PdfExportController@receipt`, template `pdf/receipt.blade.php`, ruta `GET /hm/recibo/{checkin}`. Abre automáticamente en nueva pestaña al hacer checkout (FrontDesk y CheckinIndex). Botón "Recibo PDF" en historial de check-ins cerrados. |
| 3 | Historial del cliente ✅ | Componente `ClientHistory`, ruta `GET /HmClients/{client}` (`hm_client.history`). Stats (estadías, noches, total pagado), timeline de checkins con pagos y botón de recibo PDF, y lista de reservas. Botón reloj en columna de acciones de `ClientIndex`. |
| 4 | Reporte de ocupación ✅ | Componente `OccupancyReport`, ruta `GET /OccupancyReport` (`hm.occupancy`). Stats: % ocupación actual, noches vendidas, ingresos totales, estancia promedio. Gráfica de barras "Ocupación semanal %" últimas 12 semanas + gráfica de ingresos mensuales últimos 12 meses (Chart.js). Tabla por habitación con noches vendidas, ingresos y barra de utilización relativa. Filtro por hotel. |
| 5 | Estado de limpieza ✅ | Campo `housekeeping_status` (`clean`/`dirty`/`in_progress`) en `hm_rooms`. Toggle desde Room Map (botón ↻ por tarjeta) y filtro + columna en Room Index. Al hacer checkout (desde FrontDesk, CheckinIndex o RoomMap) se marca automáticamente como `dirty`. |

### Bitácora

| # | Mejora | Descripción |
|---|--------|-------------|
| 1 | CRUD de tareas completo ✅ | Título, fecha, horario con auto-cálculo de duración, cliente, ticket, módulo, categoría, bug, actividad, pin/unpin, clone, notas estructuradas, archivos con ciclo de entornos pending→dev→qa→prod. |
| 2 | Exportación CSV ✅ | Exportar con BOM UTF-8. CRUD de clientes incluido. |
| 3 | Reactivar módulo ✅ | `enabled: true` en `module.json`. PermissionSeeder creado (`bitacora.*`, `bk_client.*`). |

---

## FUNCIONALIDADES TRANSVERSALES

### Sistema de Importación CSV ✅ IMPLEMENTADO

**Concepto**: Cualquier módulo que tenga una vista de tabla puede ofrecer un botón "Importar CSV". El usuario con permisos carga un archivo CSV y puede mapear las columnas del CSV a los campos reales del sistema antes de ejecutar la importación.

**Flujo**:
1. Usuario hace clic en "Importar" en la vista de tabla (junto al botón de exportar)
2. Se abre un wizard 3 pasos: upload → mapeo de columnas → resultado
3. Al cargar el CSV, el sistema lee la primera fila (cabeceras) y muestra una tabla de mapeo
4. Previsualización de las primeras N filas antes de confirmar
5. Importación con skip/overwrite de duplicados como opción
6. Al finalizar: resumen de filas importadas y filas con error por fila

**Activo en**:
- `SimpleFinancialManagement`: `Transaction implements Importable` — campos: tipo, monto, fecha, descripción, wallet, bucket
- `HotelManagement`: `Client implements Importable`

**Para agregar más modelos**: implementar `Importable` con método `importRules()` + embed `@livewire('core.csv-importer', ...)` en la vista. Ver `ideas.md` para candidatos pendientes.

**Consideraciones técnicas**:
- `openspout/openspout` para lectura de CSV
- Soporte para encodings UTF-8 y Latin-1
- Los errores de filas individuales no cancelan toda la importación
- Permiso `[modulo].import` para controlar acceso

---

### Dashboard Personalizable por Usuario/Rol ✅ IMPLEMENTADO

**Concepto**: Cada usuario (o grupo de usuarios) puede tener asignado un dashboard específico según su rol o configuración individual.

**Jerarquía de resolución**: usuario individual → rol → global → módulo mayor prioridad → bienvenida genérica.

**Implementación**:
- Config en `/Dashboards` (componente `DashboardSettings`)
- Campo `default_dashboard` en tabla `users`
- Dropdown en formulario de usuario
- Marcar `is_dashboard: true` en `pages.json` de cada módulo para que aparezca como opción

**Estructura en `pages.json`**:
```json
{
  "name": "Front Desk",
  "route": "/FrontDesk",
  "is_dashboard": true,
  "permission": "hotel_frontdesk"
}
```

---

### Sub-módulo Entregas (MediaMinistry) ✅ IMPLEMENTADO

**Concepto**: Área centralizada para subir y organizar archivos finales de diseño (imágenes, videos, PDFs) de alta calidad, con versionado completo.

**Decisiones de diseño**:
- Sin flujo de aprobación formal — solo subir, organizar y descargar
- Vinculación opcional a un Evento o Tarea existente (no obligatoria)
- Versionado completo — todas las versiones se conservan (v1, v2, v3...), ninguna se elimina al subir una nueva

**Estructura de datos**:

```
Delivery (tabla: mm_deliveries)
├── id
├── name              — nombre del proyecto/entrega
├── description       — descripción opcional
├── event_id          — nullable, FK a mm_events
├── task_id           — nullable, FK a mm_tasks
├── created_by        — FK a users
├── timestamps

DeliveryVersion (tabla: mm_delivery_versions)
├── id
├── delivery_id       — FK a mm_deliveries
├── version_number    — entero autoincremental por entrega (1, 2, 3...)
├── file_path         — path en storage
├── disk              — 'public' o 's3'
├── original_filename — nombre original del archivo
├── file_size         — en bytes
├── mime_type         — tipo MIME real del archivo
├── notes             — comentario opcional de la versión
├── uploaded_by       — FK a users
├── timestamps
```

**Permisos**:
- `mm_delivery.index` — ver lista de entregas
- `mm_delivery.create` — crear entrega y subir versiones
- `mm_delivery.delete` — eliminar entrega completa (elimina también archivos físicos)

**Límites de archivo**: imágenes 100MB, videos 500MB, PDFs 50MB, audio 50MB.

---

### Apariencia — Identidad del sitio ✅ IMPLEMENTADO

**Campos configurables desde `/Apariencia`**:

| Campo | Tipo | Descripción |
|-------|------|-------------|
| `site_logo_path` | Imagen (PNG/JPG, max 1MB) | Logo del sitio — reemplaza el de Laravel en sidebar y navbar |
| `site_name` | Texto (max 60 chars) | Nombre del sitio — reemplaza "Laravel" |
| `site_favicon_path` | Imagen (ICO/PNG, max 100KB) | Favicon — ícono de la pestaña del navegador |

**Valores en `app_settings`**: `general.site_logo_path`, `general.site_name`, `general.site_favicon_path`.

Aplicado en los 3 layouts de navegación + título de pestaña. Archivos servidos via ruta `GET /pf/{path}` sin depender del symlink de storage.

---

### Imagen Configurable para PDFs ✅ IMPLEMENTADO

**Clave**: `general.pdf_logo_path` en `app_settings`. Logo base64 inyectado en los templates DomPDF.

**Nota técnica**: DomPDF requiere la imagen como path absoluto del servidor, no como URL pública. Al leer el path desde `app_settings`, convertir con `storage_path()` o `base_path()` según corresponda.

Aplicado en: los 4 PDFs de MediaMinistry. Pendiente aplicar en SFM y HotelManagement al mejorar esos PDFs.

---

## HISTORIAL DE PRIORIDADES COMPLETADAS

### Urgente — Correcciones pre-producción
1. ✅ MediaLibrary: sin validación de MIME type — **CORREGIDO**
2. ✅ MediaLibrary: archivos físicos no se borran al eliminar — **CORREGIDO**
3. ✅ GoogleCalendarService bug silencioso — Auditado, no era bug real
4. ✅ Doble booking en HotelManagement — Auditado, ya estaba implementado
5. ✅ Proteger ruta `/install` — **CORREGIDO** (guard en `mount()`)
6. ✅ Checkout sin validación de balance en HotelManagement — **CORREGIDO**

### Corto plazo — Completado
1. ✅ MediaLibrary: límites de tamaño (100MB imágenes, 500MB videos)
2. ✅ Sub-módulo Entregas en MediaMinistry
3. ✅ Imagen configurable para PDFs desde Configuración
4. ✅ Identidad del sitio (logo, nombre, favicon)
5. ✅ PDF de recibo al checkout en HotelManagement
6. ✅ Sistema de importación CSV
7. ✅ Dashboard personalizable por usuario/rol

### Mediano plazo — Completado
1. ✅ Dashboard con gráficas en SFM
2. ✅ Mapa visual de habitaciones en HotelManagement
3. ✅ Historial del cliente en HotelManagement
4. ✅ Calendario visual de eventos en MediaMinistry
5. ✅ Presupuesto mensual en SFM
6. ✅ Reactivar módulo Bitácora
7. ✅ Miniaturas en MediaLibrary

---

*Ideas y funcionalidades futuras descartadas del roadmap activo en [ideas.md](ideas.md)*

*Este documento es un artefacto vivo — actualizar al completar cada ítem.*
