# Plan de implementación — Lote 7

**Fecha**: 2026-06-12  
**Items**: DT-001, DT-002, DT-003, DT-004, DT-006, DT-010, DT-013, DT-022, DT-025, DT-026  
**Criterio de selección**: ROI máximo del inventario restante (CRÍTICO + ALTA × coste BAJO/MEDIO prioritario)

---

## Orden de implementación (por ROI)

| # | ID | Impacto | Coste | Descripción |
|---|---|---|---|---|
| 1 | DT-013 | MEDIA | MEDIO | GrupoVoter |
| 2 | DT-025 | BAJA | BAJO | Convención de rutas documentada |
| 3 | DT-003 | CRÍTICO | ALTO | SincronizarColeccionMessage → async |
| 4 | DT-002 | CRÍTICO | ALTO | AuditService → Messenger async |
| 5 | DT-001 | CRÍTICO | ALTO | Evento.creadoPor nullable + SET NULL |
| 6 | DT-026 | ALTA | MEDIO | JS extraído de plantillas Twig |
| 7 | DT-006 | ALTA | ALTO | InvitacionService extraído de GrupoService |
| 8 | DT-004 | CRÍTICO | ALTO | Tests unitarios GrupoPermisoService |
| 9 | DT-010 | ALTA | ALTO | Tests de seguridad funcionales |
| 10 | DT-022 | BAJA | ALTO | Convenciones de nomenclatura documentadas |

---

## Implementaciones

### DT-013 — GrupoVoter

`src/Security/Voter/GrupoVoter.php` (nuevo):
- Extiende `Voter<string, Grupo>`
- `supports()`: subject es `Grupo` y attribute es `PermisoGrupo::tryFrom()`
- `voteOnAttribute()`: delega a `GrupoPermisoService->can()`
- Permite `isGranted(PermisoGrupo::CrearEventos->value, $grupo)` estándar

### DT-025 — Convenciones de rutas

`cerebro/doc/convenciones-rutas.md` (nuevo): documenta el patrón `app_<dominio>_<accion>` por controlador.

### DT-003 — SincronizarColeccionMessage async

`config/packages/messenger.yaml`: `App\Message\SincronizarColeccionMessage: sync` → `async`.  
El `usleep()` en `BggApiService` queda en el worker, fuera del hilo web.

### DT-002 — AuditService async

- `src/Message/AuditLogMessage.php` (nuevo): DTO readonly con todos los campos del log
- `src/MessageHandler/AuditLogMessageHandler.php` (nuevo): `#[AsMessageHandler]` que persiste `AuditLog` y hace flush
- `src/Service/AuditService.php` (reescrito): constructor acepta solo `MessageBusInterface`; `log()` despacha `AuditLogMessage`
- `config/packages/messenger.yaml`: `App\Message\AuditLogMessage: async`

### DT-001 — Evento.creadoPor nullable

- `src/Entity/Evento.php`: `nullable: false` → `nullable: true, onDelete: 'SET NULL'`; tipo `Usuario` → `?Usuario = null`
- `migrations/Version20260612184040.php`: altera columna `creado_por_id` para permitir NULL con SET NULL
- `src/Controller/Api/EventoController.php`: null-check en `getCreadoPor()`

### DT-026 — JS extraído de Twig

- `public/js/coleccion.js` (nuevo): filtros, sorting, votación AJAX y popup ranking
- `public/js/evento-show.js` (nuevo): modal colección miembro y pre-selección estrellas
- `templates/coleccion/index.html.twig`: `<div id="js-coleccion-data" hidden data-num-miembros="...">` + `<script src defer>`
- `templates/evento/show.html.twig`: `<div id="js-evento-data" hidden data-coleccion-url="...">` + `<script src defer>`

### DT-006 — InvitacionService

- `src/Service/InvitacionService.php` (nuevo): `invitarPorTag`, `aceptarInvitacion`, `rechazarInvitacion`, `assertInvitacionPendiente` extraídos de `GrupoService`
- `src/Service/GrupoService.php`: eliminados los 4 métodos y 4 dependencias (`InvitacionRepository`, `UsuarioRepository`, `NotificacionService`, entidad `Invitacion`)
- `src/Controller/GrupoController.php`: inyecta `InvitacionService` en `invitar`, `aceptarInvitacion`, `rechazarInvitacion`

### DT-004 — Tests GrupoPermisoService

`tests/Unit/Service/GrupoPermisoServiceTest.php` (nuevo):
- 8 tests cubriendo: no miembro → false, admin → true (todos permisos), miembro con permiso → true, miembro sin permiso → false, `memberCan(null)` → false, `assertCan` lanza excepción, `assertCan` pasa sin excepción, `getMembership` delega al repo
- `GrupoServiceTest` actualizado: eliminados imports/mocks de `InvitacionRepository`, `UsuarioRepository`, `NotificacionService` y los 3 tests de `invitarPorTag`

### DT-010 — Tests de seguridad

`tests/Functional/SecurityControllerTest.php` ampliado:
- `testUnauthenticatedAccessRedirectsToLogin`: data provider con 6 rutas protegidas (`/`, `/grupos`, `/coleccion`, `/perfil`, `/notificaciones`, `/admin`)
- `testPublicRoutesAreAccessible`: verifica login, register y recuperar-password accesibles sin autenticación
- `phpunit.dist.xml`: añadido `<server name="KERNEL_CLASS" value="App\Kernel" />`

### DT-022 — Convenciones de nomenclatura

`cerebro/doc/doc-naming-conventions.md` (nuevo): cubre PHP, Doctrine, Servicios, Rutas, Twig y JavaScript.

---

## Verificación

```
php bin/console lint:container  → OK
php bin/phpunit tests/Unit/     → 14 tests, 24 assertions, OK
```
