# Plan maestro — Laravel Roles / Sistema modular

> Fecha de creación: 2026-04-23
> Estado general: En corrección y reestructuración del núcleo

---

## Stack actual

| Dependencia | Versión actual | Notas |
|---|---|---|
| PHP | ^8.2 | Actualizar a 8.4 cuando sea posible |
| Laravel | ^12.0 | Estable, mantener en 12.x |
| Livewire | ^3.6 | Estable |
| Livewire Volt | ^1.7 | Mantener pero usar con criterio |
| MaryUI | ^2.2 | Mantener — solo como capa de presentación |
| Spatie Permission | ^6.16 | Estable |
| Tailwind CSS | ^4.1.3 | Ya en v4, mantener actualizaciones menores |
| DaisyUI | ^5.0.19 | Estable |
| Vite | ^6.0 | Estable |
| Pest | ^3.7 | Estable |

---

## Diagnóstico — bugs activos y deuda técnica

### Bugs críticos (rompen seguridad o funcionalidad)

| ID | Archivo | Problema | Impacto |
|---|---|---|---|
| B-01 | `app/Livewire/Modules/SFM/BucketIndex.php:48` | `queryBuckets()` no filtra por `user_id` | Cualquier usuario ve datos de todos |
| B-02 | `app/Livewire/Modules/SFM/MovementCrud.php:74` | `Wallet::all()` sin filtro `user_id` | Wallets de todos los usuarios visibles |
| B-03 | `app/Livewire/Modules/SFM/MovementCrud.php:125` | `Wallet::create()` sin `user_id` | Wallets creadas sin dueño |
| B-04 | `Modules/Core/Policies/GenericPolicy.php:14` | `hasPermissionTo($model_name, '.index')` — coma en lugar de concatenación | Verificación de permiso `.index` siempre falla para no-admins |
| B-05 | `Modules/SFM/pages.json` | `"permission": "bucket.index"` pero sidebar agrega `.index` → verifica `bucket.index.index` | Menú SFM no aparece para usuarios no-admin |
| B-06 | `app/Livewire/Modules/SFM/BucketIndex.php:78` | `addBucket()` sin `$this->validate()` | Inserción sin validación de datos |

### Deuda técnica (no bloquean hoy, rompen en el futuro)

| ID | Archivo | Problema |
|---|---|---|
| D-01 | `app/Providers/ModulesServiceProvider.php:64` | `->namespace()` deprecado en Laravel 10+, se eliminará en versión futura |
| D-02 | `Modules/SFM/Models/Transaction.php` | `$guarded = []` — sin protección de mass assignment |
| D-03 | `app/Livewire/Modules/SFM/MovementCrud.php:104` | `loadTotals()` tiene `return;` al inicio — totales nunca se calculan |
| D-04 | `app/Livewire/Modules/SFM/MovementCrud.php:151` | `addDestination()` vacío |
| D-05 | `Modules/SFM/Http/Controllers/*` | Mayoría de métodos en controllers están vacíos |
| D-06 | Livewire components | Viven en `app/Livewire/Modules/` y vistas en `resources/views/livewire/` — fuera de los módulos |
| D-07 | Nomenclatura Livewire | `$sort_by` (BucketIndex) vs `$sortBy` (MovementCrud) — inconsistencia de convención |
| D-08 | `Modules/*/module.json` | Sin campos `enabled`, `requires`, `core` — no hay mecanismo de activar/desactivar |
| D-09 | Migraciones SFM | Sin índices compuestos para queries frecuentes de dashboard |
| D-10 | Toda la app | Sin mecanismo de instalación inicial (wizard de setup) |

---

## Arquitectura de módulos — decisiones tomadas

### module.json — estructura objetivo

```json
{
    "id": "SimpleFinancialManagement",
    "priority": 2,
    "title": "Gestión financiera simple",
    "description": "Para gestionar finanzas personales de manera sencilla.",
    "version": "1.0.0",
    "enabled": true,
    "core": false,
    "requires": [],
    "permissions": ["wallet", "bucket", "transaction", "credit_card", "tag", "card_cycle"]
}
```

- `enabled`: controlado por el Wizard de módulos (BD + archivo)
- `core: true` en Core — nunca se puede deshabilitar
- `requires`: lista de IDs de módulos que deben estar activos
- `permissions`: para auto-registro de permisos al instalar

### Estructura de Livewire por módulo (objetivo)

```
Modules/
  SimpleFinancialManagement/
    Http/
      Livewire/                          ← Livewire components aquí
        BucketIndex.php
        MovementCrud.php
        WalletIndex.php
    Resources/
      views/
        livewire/                        ← Vistas Livewire aquí
          bucket-index.blade.php
          movement-crud.blade.php
          wallet-index.blade.php
        Bucket/
          BucketIndex.blade.php          ← Vistas de controller (si aplica)
```

### pages.json — estándar unificado

El campo `permission` debe ser solo el nombre del recurso (sin acción). El sidebar agrega `.index` al verificar. Esto aplica a todos los módulos:

```json
{ "permission": "bucket" }    ← CORRECTO — sidebar verifica "bucket.index"
{ "permission": "bucket.index" }   ← INCORRECTO — sidebar verificaría "bucket.index.index"
```

### Regla de nomenclatura Livewire

Todas las propiedades públicas en componentes Livewire usan `camelCase`:
- `$sortBy`, `$perPage`, `$searchQuery`
- NO `$sort_by`, `$per_page`, `$search_query`

---

## Fase 0 — Correcciones bloqueantes

> Estas se hacen primero, antes de cualquier otra cosa.

- [x] **B-04** Corregir `GenericPolicy::GenericIndex` (coma → concatenación)
- [x] **B-05** Corregir `pages.json` de SFM (quitar `.index` del campo `permission`)
- [x] **B-01** Agregar `->where('user_id', Auth::id())` en `queryBuckets()`
- [x] **B-02** Agregar filtro `user_id` en `Wallet::all()` de `MovementCrud`
- [x] **B-03** Agregar `user_id` al `Wallet::create()` en `addWallet()` + campos faltantes (`type`, `currency`, `balance`)
- [x] **B-06** Agregar `$this->validate()` en `addBucket()` con reglas mínimas
- [x] **extra** `queryMovements()` sin filtro `user_id` — corregido
- [x] **extra** `deleteBucket()` sin verificación de propietario — corregido con `where('user_id', Auth::id())`
- [x] **extra** `user_id` agregado a `$fillable` de `Wallet`

---

## Fase 1 — Reestructuración del núcleo

> Objetivo: núcleo robusto, modular, sin deprecated y con aislamiento correcto.

### 1.1 ModulesServiceProvider — eliminar deprecated y hacerlo resiliente

- [x] Eliminar `->namespace()` deprecated — reescritura completa del provider
- [x] Filtro `enabled` + `core` desde `module.json`
- [x] Validación de `requires` con `Log::warning` si dependencia falta
- [x] `try/catch` por módulo — un módulo corrupto no rompe la app
- [x] `Livewire::componentNamespace()` por alias reemplaza registro manual

### 1.2 Actualizar module.json de todos los módulos

- [x] `Core/module.json`: alias, enabled, core, requires, permissions
- [x] `SimpleFinancialManagement/module.json`: alias, enabled, core, requires, permissions
- [x] `TaskManager/module.json`: creado con enabled: false

### 1.3 Mover Livewire components a dentro de cada módulo

- [x] Creado `Modules/SFM/Http/Livewire/` con BucketIndex, MovementCrud, Test
- [x] Creado `Modules/SFM/Resources/views/livewire/` con las 3 vistas
- [x] Eliminados archivos de `app/Livewire/Modules/SFM/` y `resources/views/livewire/modules/sfm/`
- [x] `@livewire()` actualizado a tag con alias: `sfm.bucket-index`

### 1.4 Agregar Global Scope de usuario en modelos SFM

- [x] `Wallet`, `Bucket`, `Transaction`, `CreditCard`, `Tag`
- [x] Relaciones usan `withoutGlobalScope('owner')` para evitar interferencia en JOINs

### 1.5 Agregar índices de rendimiento en migraciones SFM

- [x] Nueva migración `2026_04_23_000001_add_performance_indexes_to_sfm_tables.php`
- [x] 8 índices: transactions (4), wallets, buckets, credit_cards, tags

### 1.6 Estandarizar nomenclatura Livewire

- [x] Todo renombrado a camelCase en BucketIndex y MovementCrud (reescritos)

### 1.7 Proteger mass assignment en modelos SFM

- [x] `Transaction`: `$guarded = []` → `$fillable` explícito + `$casts`
- [x] `CreditCard`, `Tag`: user_id agregado a fillable

### 1.8 Flush de caché de permisos al actualizar en Wizard

- [x] `WizardController::resetPermissions()` llama `forgetCachedPermissions()`

### Extra — Migración de alias de vistas

- [x] 6 controllers del Core: `module::` → `core::`
- [x] 5 controllers del SFM: `module::` → `sfm::`

---

## Fase 2 — Los 3 wizards del sistema

### Wizard 1 — Instalación inicial ✅

> Accesible solo cuando la app no está configurada (sin `.env` con BD válida o sin tablas).
> Protegido: si ya está instalado, redirige al dashboard. No debe ser accesible una vez configurado.
> **Implementado:** `Modules/Core/Http/Livewire/InstallWizard.php` + vista `core::livewire.install-wizard` + ruta `/install`

Pasos del wizard:
1. **Bienvenida** — verificar que los requisitos del servidor estén OK (PHP version, extensiones, permisos de escritura)
2. **Conexión a BD** — formulario para ingresar host, puerto, nombre de BD, usuario, contraseña. Botón "probar conexión" antes de guardar
3. **Generar `.env`** — escribir las variables de conexión al `.env` de forma segura (sin exponer credenciales en pantalla después de guardar)
4. **Ejecutar migraciones** — `php artisan migrate` vía `Artisan::call()` con output visible
5. **Crear usuario administrador** — nombre, email, contraseña con confirmación
6. **Configuración inicial** — nombre de la instancia, idioma por defecto
7. **Activar módulos** — selección inicial de módulos a habilitar (redirige al Wizard 3)
8. **Finalizar** — marcar instalación como completa (guardar flag en BD o archivo `storage/installed`)

Seguridad del wizard de instalación:
- Solo accesible si no existe el flag `storage/installed`
- No expone credenciales en logs ni en vistas
- Valida la conexión antes de escribir al `.env`
- Usa `Storage::disk('local')` para escribir archivos, nunca `exec()` directo

### Wizard 2 — Migración de base de datos ✅

> Accesible desde Configuración → Administración (solo admin).
> Para cambiar de BD, hacer una instalación limpia manteniendo datos, o migrar a otra instancia.
> **Implementado:** `Modules/Core/Http/Livewire/DbMigrationWizard.php` + vista `core::livewire.db-migration-wizard`

Pasos:
1. **Tipo de migración** — seleccionar: cambiar conexión BD / instalación limpia / exportar estructura para otra instancia
2. **Nueva conexión** — igual que Wizard 1 paso 2, con prueba de conexión
3. **Verificar BD destino** — mostrar si la BD destino ya tiene tablas (advertencia si hay datos)
4. **Opciones de migración** — migrar solo estructura / estructura + datos / solo conexión
5. **Ejecutar** — `php artisan migrate` en nueva conexión con output visible
6. **Verificar** — listado de tablas creadas, errores si los hay
7. **Confirmar cambio** — actualizar `.env` con la nueva conexión

### Wizard 3 — Gestión de módulos ✅

> Accesible desde la navegación (menú Admin) para usuarios con permiso `module_manager`.
> NO requiere acceso al código ni al servidor. Los cambios se aplican en caliente.
> **Implementado:** `Modules/Core/Http/Livewire/ModuleManager.php` + vista `core::livewire.module-manager` + `ModuleState` model + migración `module_states`

Funcionalidad:
- Listar todos los módulos detectados (los que tienen `module.json` en `Modules/`)
- Para cada módulo mostrar: nombre, versión, descripción, estado (activo/inactivo), dependencias
- Toggle activar/desactivar con validación de dependencias (no puedes desactivar un módulo del que otros dependen)
- Al activar: verificar que las migraciones del módulo estén ejecutadas, ejecutar si no
- Al desactivar: el módulo deja de cargar rutas y de aparecer en el menú — sin borrar datos
- Core siempre aparece como "Sistema base — siempre activo" sin toggle
- Guardar estado en tabla `module_states(module_id, enabled, updated_at)` en BD

Tabla de BD para el wizard 3:
```sql
module_states:
  id
  module_id (string, único)
  enabled (boolean)
  updated_at
```

---

## Fase 3 — Actualización de dependencias

> Hacer antes de completar el módulo de finanzas para evitar romper trabajo nuevo.

### PHP
- [ ] Evaluar actualización a PHP 8.3 (LTS) — Laravel 12 lo soporta
- [ ] PHP 8.4 es estable desde nov 2024 — evaluar compatibilidad con paquetes

### Laravel y paquetes PHP
- [ ] `composer update` con revisión de breaking changes en changelog
- [ ] Verificar compatibilidad de MaryUI, Spatie, Livewire con versiones actualizadas
- [ ] Actualizar `laravel/pint` y correr formatter en todo el proyecto
- [ ] Ejecutar `php artisan about` después de actualizar para verificar

### Frontend
- [ ] `npm update` — revisar Tailwind, DaisyUI, Vite
- [ ] Tailwind 4.x: ya estás en 4.1.3 — revisar si hay 4.2.x disponible
- [ ] DaisyUI 5.x: ya estás en 5.0.19 — revisar actualizaciones menores
- [ ] Vite 6.x: ya estás en 6.0 — revisar si hay 6.x más reciente
- [ ] FontAwesome: ya en ^6.7.2 — verificar si hay 6.x más reciente

### Proceso seguro de actualización
1. Hacer commit del estado actual antes de actualizar
2. Actualizar de a una dependencia major a la vez
3. Correr `php artisan test` después de cada actualización
4. Verificar visualmente las páginas principales

---

## Fase 4 — Seguridad del núcleo

- [ ] Forzar HTTPS en producción (`AppServiceProvider` o middleware)
- [ ] Agregar rate limiting en rutas de autenticación (ya disponible en Laravel, solo activar)
- [ ] Revisar que todos los formularios Livewire tengan `$this->validate()` antes de persistir
- [ ] Agregar `confirmed` rule en formularios de contraseña
- [ ] Agregar `unique` validation con exclusión del registro actual en updates
- [ ] Revisar que el wizard de instalación no sea accesible una vez instalado
- [ ] Headers de seguridad HTTP (CSP, X-Frame-Options, etc.) — middleware o servidor

---

## Fase 5 — Módulo SimpleFinancialManagement — completar MVP

### 5.1 Modelos — relaciones y scopes

- [ ] `Wallet`: relación `hasMany(Transaction)`, global scope `user_id`
- [ ] `Bucket`: relación `hasMany(Transaction)`, global scope `user_id`
- [ ] `Transaction`: relaciones `belongsTo(Wallet)`, `belongsTo(Bucket)`, `belongsTo(CreditCard)`, global scope `user_id`
- [ ] `CreditCard`: relación `hasMany(Transaction)`, `hasMany(CardCycle)`, global scope `user_id`
- [ ] `Tag`: relación `belongsToMany(Transaction)`, global scope `user_id`

### 5.2 Lógica de balance en Wallet

Al crear/actualizar/eliminar una Transaction, el balance de la Wallet debe actualizarse automáticamente usando un Observer:

```php
// TransactionObserver
public function created(Transaction $transaction): void
{
    DB::transaction(function () use ($transaction) {
        $wallet = Wallet::lockForUpdate()->find($transaction->wallet_id);
        // Actualizar balance según tipo
    });
}
```

- [ ] Crear `TransactionObserver`
- [ ] Registrar observer en `SFMServiceProvider` o en el modelo
- [ ] Manejar los casos: income sube balance, expense lo baja, transferencia afecta dos wallets

### 5.3 CRUDs pendientes

- [ ] **Wallet**: CRUD completo con validación (nombre, tipo, moneda, balance inicial)
- [ ] **Bucket**: CRUD completo con validación + límite mensual (para alertas de presupuesto)
- [ ] **Transaction**: CRUD completo — crear, editar, eliminar con actualización de balance
- [ ] **CreditCard**: CRUD completo (nombre, banco, límite, día de corte, día de pago)
- [ ] **Tag**: CRUD completo (simple)
- [ ] **CardCycle**: CRUD completo

### 5.4 Dashboard mínimo

Indicadores en la pantalla principal del módulo:

- [ ] Saldo total (suma de todas las wallets del usuario)
- [ ] Deuda actual en tarjetas de crédito
- [ ] Disponible real (saldo - deuda TC)
- [ ] Gasto del mes actual vs mes anterior
- [ ] Top 5 categorías (Buckets) del mes con % vs presupuesto
- [ ] Alerta si algún Bucket supera el 80% de su límite mensual
- [ ] Alerta si alguna TC supera el 80% de su límite de crédito

### 5.5 Control de tarjeta de crédito

- [ ] Cálculo de saldo usado en TC por ciclo activo
- [ ] Mostrar: límite total, usado, disponible, fecha de corte próxima
- [ ] Transacciones de TC: registrar compra, registrar pago (reduce deuda)

### 5.6 Exportación

- [ ] Exportar transacciones del mes a CSV (con filtros: fecha, wallet, bucket, tipo)
- [ ] Formato configurable (para compatibilidad con otros sistemas)

---

## Fase 6 — Módulo TaskManager — diseño e implementación

### Modelo de datos

```
tasks
  id
  user_id
  external_id (varchar 100) — ID del sistema externo donde se asigna la tarea
  parent_task_id (nullable FK a tasks) — para historial/relación con tarea anterior
  title
  description (text, nullable)
  week (año-semana, ej: "2026-17")
  day_of_week (1-7)
  work_date (date)
  status (enum: pending, in_progress, done, returned)
  notes (text, nullable)
  timestamps

task_files
  id
  task_id
  file_path (varchar)
  relevant_lines (varchar, nullable) — ej: "45-67, 120"
  note (text, nullable)
  timestamps

task_history
  id
  original_task_id — la tarea original
  related_task_id — la tarea nueva que la referencia (devolución o ajuste)
  reason (text, nullable) — por qué se vinculó
  timestamps
```

### Funcionalidades

- [ ] CRUD de tareas con agrupación por semana y día
- [ ] Agregar archivos modificados a cada tarea
- [ ] Vincular tarea devuelta a su tarea original (por external_id o ID registro)
- [ ] Vista semanal con agrupación por día
- [ ] Exportación a CSV con formato configurable (para la bitácora del sistema externo)
- [ ] Búsqueda por `external_id`, semana, estado

---

## Fase 7 — Tests

### Progresión recomendada para alguien que empieza con Pest

**Nivel 1 — Feature tests de seguridad (máximo valor, mínima complejidad):**
```php
// Verificar aislamiento de datos entre usuarios
test('user cannot see another users wallets', function () { ... });
test('user cannot see another users transactions', function () { ... });
test('unauthenticated user cannot access SFM routes', function () { ... });
```

**Nivel 2 — Feature tests de CRUDs:**
```php
test('user can create a wallet', function () { ... });
test('wallet balance updates when transaction is created', function () { ... });
```

**Nivel 3 — Unit tests de lógica de negocio:**
```php
test('available balance calculation is correct', function () { ... });
test('credit card usage percentage is calculated correctly', function () { ... });
```

- [ ] Configurar base de datos de testing (MySQL en memoria o SQLite solo para tests)
- [ ] Crear `UserFactory`, `WalletFactory`, `TransactionFactory`
- [ ] Tests de seguridad (aislamiento por usuario) — todos los modelos SFM
- [ ] Tests de CRUD básico para Wallet y Transaction
- [ ] Tests del observer de balance
- [ ] Tests del wizard de instalación

---

## Fase 8 — CI/CD

### Pipeline básico con GitHub Actions

```yaml
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.3'
      - run: composer install --no-dev --optimize-autoloader
      - run: npm ci && npm run build
      - run: cp .env.ci .env && php artisan key:generate
      - run: php artisan migrate --force
      - run: ./vendor/bin/pest
```

### Consideraciones de seguridad CI/CD

- Las credenciales de BD de producción NUNCA van al repositorio
- El `.env` de producción se configura con el Wizard 1 directamente en el servidor
- El pipeline solo valida código — no tiene acceso a instancias de clientes
- Los módulos activos se configuran con el Wizard 3 desde la UI, no desde código
- Las migraciones de módulos se ejecutan al activar el módulo desde el Wizard 3

- [ ] Crear `.env.ci` (con BD de testing)
- [ ] Crear `.github/workflows/ci.yml`
- [ ] Agregar badge de CI al README
- [ ] Documentar proceso de deploy manual (hasta tener CD)

---

## Fase 9 — Documentación

- [ ] Documentar en `README.md`: requisitos, instalación con el Wizard, estructura de módulos
- [ ] Documentar cómo crear un módulo nuevo (template con los archivos mínimos)
- [ ] Documentar el formato de `module.json` y `pages.json`
- [ ] Agregar comentarios en `ModulesServiceProvider` explicando el orden de carga
- [ ] Documentar el sistema de permisos: convención `recurso.accion`

---

## Consideraciones de arquitectura para módulos comerciales futuros

- Cada módulo tiene su propio prefijo de tabla (ej: `sfm_`, `tm_`, `pos_`, `hotel_`)
- Cada módulo registra sus permisos en su `module.json` — el Wizard los crea automáticamente
- Los módulos pueden sobrescribir vistas del Core usando el sistema de prioridades
- Un módulo puede declarar `"requires": ["Core"]` para garantizar que el Core está activo
- Los módulos no deben tener dependencias entre sí excepto por declaración explícita en `requires`
- MaryUI se usa solo en capa de presentación — la lógica de negocio nunca depende de Mary
- Los Controllers de módulos extienden el Controller base del Core para heredar `checkPermission`, `getSetting`, `Alerts`

### Módulos planificados a futuro

| Módulo | Prefijo tabla | Dependencias |
|---|---|---|
| TaskManager | `tm_` | Core |
| SimpleFinancialManagement | `sfm_` | Core |
| PointOfSale | `pos_` | Core |
| HotelOrders | `hotel_` | Core, PointOfSale (opcional) |

---

## Orden de ejecución recomendado

```
Fase 0  →  Fase 1  →  Fase 2 (actualizaciones)  →  Fase 3 (wizards)
    →  Fase 4 (seguridad)  →  Fase 5 (finanzas MVP)
    →  Fase 6 (tareas)  →  Fase 7 (tests)  →  Fase 8 (CI/CD)
    →  Fase 9 (documentación)
```

Las fases 7, 8 y 9 pueden ejecutarse en paralelo con las fases 5 y 6 — los tests se escriben a medida que se completan los CRUDs.

---

## Checklist de "núcleo 100% listo" antes de continuar módulos

- [ ] Fase 0 completada (todos los bugs críticos corregidos)
- [ ] Fase 1 completada (Livewire en módulos, module.json actualizado, nomenclatura estandarizada)
- [ ] Fase 2 completada (dependencias actualizadas)
- [x] Wizard 1 funcional (instalación inicial)
- [x] Wizard 3 funcional (gestión de módulos)
- [ ] Global scope de `user_id` en todos los modelos SFM
- [ ] `->namespace()` deprecated eliminado
- [ ] Índices de BD agregados
- [x] Al menos los tests de aislamiento por usuario pasando
