Informe de Deuda Técnica

Proyecto Wari · Symfony 7.3 / PHP 8.2

Última actualización01 junio 2026
Revisiones3 discoveries completados
ScopeEntidades, controladores, servicios, repositorios, Twig, tests, config
Estado41 ítems detectados · 0 resueltos

Historial de revisiones

Crítica 4 Requiere atención urgente
Alta 12 Deuda técnica significativa
Media 19 Abordar próximamente
Baja 6 Mejoras técnicas menores

Matriz gravedad × coste correctivo

Coste ALTO
Coste MEDIO
Coste BAJO
CRÍTICA
3DT-001, DT-002, DT-003
0
1DT-004
ALTA
3DT-006, DT-010
4DT-005, DT-007, DT-026·bis, DT-028
5DT-008, DT-009, DT-011, DT-029, DT-030
MEDIA
0
8DT-013–016, DT-019, DT-027, DT-033, DT-026
11DT-012, DT-017–018, DT-020–021, DT-031, DT-032, DT-034–039
BAJA
1DT-022
0
5DT-023–025, DT-040–041

Crítica

4 ítems
DT-001
Cascades destructivos en la entidad Usuario
Relaciones OneToMany con cascade: ['persist', 'remove']. Eliminar un usuario borra en cascada todos sus eventos e invitaciones sin posibilidad de recuperación.
src/Entity/Usuario.php ~L63, 72, 75, 78, 81
Coste ALTOD2
DT-002
AuditService hace flush() por cada registro
Cada llamada ejecuta un flush() individual. En operaciones que generan múltiples logs se ejecutan N queries extra fuera de la transacción principal.
src/Service/AuditService.php ~L37
Coste ALTOD2
DT-003
BggApiService bloquea el hilo con usleep()
Rate limiting manual con sleep en el hilo principal. La sincronización con BoardGameGeek congela la aplicación. Ya existe SincronizarColeccionHandler asíncrono que debería absorber toda esta lógica.
src/Service/BggApiService.php ~L61-72
Coste ALTOD2
DT-004
Cobertura de tests inferior al 5%
~6 archivos de test para 17 entidades, 20 servicios y 8 controladores. Sin tests de seguridad ni de integración reales. Riesgo alto de regresiones silenciosas en cualquier refactor.
tests/
Coste ALTOD2

Alta

12 ítems
DT-005
Lógica de negocio en EventoController
Búsqueda y validación de juegos, mapeo de arrays con array_map, serialización JSON directamente en el controlador.
src/Controller/EventoController.php ~L95-123
Coste MEDIOD2
DT-006
GrupoService y EventoService violan SRP
GrupoService inyecta 10 dependencias y gestiona creación, membresía, invitaciones, expulsiones, Telegram, auditoría y notificaciones. EventoService inyecta 7. Servicios "dios".
src/Service/GrupoService.php · src/Service/EventoService.php
Coste ALTOD2
DT-007
GrupoController accede a repositorios directamente
TelegramBotRepository inyectado y usado en el controlador sin pasar por ningún servicio. Rompe la separación de capas.
src/Controller/GrupoController.php ~L318, 334, 353
Coste MEDIOD2
DT-008
N+1 queries en GrupoRepository
innerJoin sobre miembros sin addSelect(). Al iterar resultados y acceder a la colección de miembros, Doctrine ejecuta una query adicional por cada grupo.
src/Repository/GrupoRepository.php ~L28-37
Coste BAJOD2
DT-009
Enumeración de usuarios por mensaje de error
El mensaje "No existe ningún usuario con el tag «X»" confirma que un username no existe, permitiendo enumeración de usuarios registrados.
src/Service/GrupoService.php ~L111
Coste BAJOD2
DT-010
Sin tests de seguridad
No existe ningún test que verifique que un usuario no puede acceder a grupos ajenos, votar en eventos de otros grupos, o escalar privilegios.
tests/
Coste ALTOD2
DT-011
Validaciones #[Assert] ausentes en entidades críticas
Campos nombre, email y usuarioBgg en Usuario sin constraints. Las validaciones solo existen en formularios. Código que persiste vía API o servicio directo no valida nada.
src/Entity/Usuario.php · src/Entity/Evento.php · src/Entity/Notificacion.php
Coste BAJOD2
DT-026
JavaScript masivo embebido en plantillas Twig
Más de 300 líneas de JS con lógica de filtrado, votación AJAX y modales incrustado directamente en las plantillas. Imposible testear, difícil mantener.
templates/coleccion/index.html.twig ~L90-358 · templates/evento/show.html.twig ~L134-229
Coste MEDIOD3
DT-027
Lógica de permisos fragmentada en tres lugares
La misma lógica existe en la entidad (hasPermission()), en el servicio (can()) y como chequeos ad-hoc en el controlador. Tres fuentes de verdad distintas para el mismo concepto.
src/Entity/GrupoMiembro.php ~L101 · src/Service/GrupoPermisoService.php · src/Controller/GrupoController.php ~L325, 401, 544
Coste MEDIOD3
DT-028
Notificaciones Telegram duplicadas en múltiples servicios
Seis bloques casi idénticos de "si telegram activo → enviar notificación" dispersos entre EventoService y GrupoService.
src/Service/EventoService.php ~L66, 119, 172, 189 · src/Service/GrupoService.php ~L88, 170
Coste MEDIOD3
DT-029
URL hardcodeada con dominio antiguo en la entidad Evento
getPermalink() construye una URL con el dominio antiguo bfbg.birdez.ginernet.net hardcodeado. Lógica de infraestructura dentro de una entidad de dominio.
src/Entity/Evento.php ~L169
Coste BAJOD3
DT-030
Email hardcodeado en BggApiService
Email 'bfbg@asociacion.local' hardcodeado en la llamada a la API de BoardGameGeek. Nombre del proyecto antiguo, valor no configurable.
src/Service/BggApiService.php ~L286
Coste BAJOD3

Media

19 ítems
DT-012
Fetch strategy no explicitada en relaciones ManyToOne
ManyToOne sin fetch: 'LAZY' explícito en varias entidades. Ausencia de declaración explícita genera confusión y bugs potenciales con proxies de Doctrine.
src/Entity/Evento.php ~L24, 34
Coste BAJOD2
DT-013
Autorización manual sin Symfony Voter
Múltiples llamadas a grupoPermisoService->can() dispersas en el controlador. Sin Voter, la lógica de permisos es difícil de centralizar, testear y extender.
src/Controller/GrupoController.php ~L97-100, 325-330
Coste MEDIOD2
DT-014
Búsqueda de roles con LIKE sobre campo JSON
u.roles LIKE '%ROLE_ADMIN%' es frágil si el formato del JSON cambia. Debería usar JSON_CONTAINS de MySQL.
src/Repository/UsuarioRepository.php ~L104, 128
Coste MEDIOD2
DT-015
Password reset sin rate limiting
La ruta de reset tiene token válido 3600 s pero sin limitación de intentos fallidos. El rate limiter del login no cubre este endpoint.
src/Service/PasswordResetService.php ~L28-46
Coste MEDIOD2
DT-016
2FA sin backup codes de recuperación
TOTP implementado sin códigos de recuperación. Un usuario que pierde su autenticador queda bloqueado permanentemente. scheb/2fa soporta backup codes de forma nativa.
src/Controller/PerfilController.php ~L124-181
Coste MEDIOD2
DT-017
NotificacionService no hace flush (inconsistencia)
Persiste entidades sin llamar a flush(), dejando esa responsabilidad al código llamador. Inconsistente con AuditService. El patrón no está documentado.
src/Service/NotificacionService.php ~L28, 39
Coste BAJOD2
DT-018
ColeccionService con doble flush en la misma operación
Dos llamadas a flush() dentro del mismo flujo. Debería ser una sola transacción atómica.
src/Service/ColeccionService.php ~L55, 75
Coste BAJOD2
DT-019
Generación de código de grupo con hasta 30 reintentos
Algoritmo de generación de código de invitación sin garantía formal de unicidad. En sistemas con muchos grupos, la tasa de colisión aumenta.
src/Service/GrupoService.php ~L245-261
Coste MEDIOD2
DT-020
Queries complejas sin índices de base de datos
GROUP BY, HAVING y ORDER BY sobre campos que probablemente carecen de índices en la BD.
src/Repository/GrupoRepository.php · src/Repository/PuntuacionJuegoRepository.php
Coste BAJOD2
DT-021
Lógica de negocio dentro de la entidad FechaPropuesta
El método countDisponibles() itera la colección en memoria e implementa lógica de negocio, generando un N+1 potencial en plantillas.
src/Entity/FechaPropuesta.php ~L72-75
Coste MEDIOD2
DT-031
Patrón try/catch → flash → redirect duplicado cuatro veces
El mismo bloque try/catch → addFlash() + redirectToRoute() se repite en cuatro acciones distintas del controlador.
src/Controller/EventoController.php ~L106, 217, 241, 265
Coste BAJOD3
DT-032
Verificación de membresía repetida en tres acciones de EventoController
$grupoMiembroRepository->isMember() comprobado manualmente en tres métodos distintos del mismo controlador.
src/Controller/EventoController.php ~L143, 171, 284
Coste BAJOD3
DT-033
Lógica de conexión Telegram en GrupoController
Validación, parseo del JSON request y persistencia de TelegramGrupo gestionados directamente en el controlador.
src/Controller/GrupoController.php ~L334-378
Coste MEDIOD3
DT-034
Patrón retry con usleep() triplicado en BggApiService
Tres bloques foreach (array_chunk(...)) idénticos con el mismo patrón de sleep y retry.
src/Service/BggApiService.php ~L52, 72, 85
Coste BAJOD3
DT-035
usernameTagExists() y emailExists() con patrón idéntico
Ambos métodos repiten findOneBy([...]) !== null. Candidatos a un método privado existsBy().
src/Repository/UsuarioRepository.php ~L45-52
Coste BAJOD3
DT-036
Validación de negocio en setter GrupoMiembro::setPermisos()
El setter valida contra PermisoGrupo::cases(). Validación en setters de entidades es frágil y difícil de testear. El setter debe ser dumb.
src/Entity/GrupoMiembro.php ~L93-98
Coste BAJOD3
DT-037
Transformación de datos en ColeccionController
Loop que transforma puntuaciones a array indexado directamente en el controlador.
src/Controller/ColeccionController.php ~L40-42
Coste BAJOD3
DT-038
Sanitización htmlspecialchars() para Telegram en el controlador
Sanitización de strings para Telegram hecha manualmente en GrupoController. Debería estar en TelegramService.
src/Controller/GrupoController.php ~L567-568
Coste BAJOD3
DT-039
Interfaz inconsistente en GrupoPermisoService
El servicio expone can() (devuelve bool) pero también lanza AccessDeniedHttpException en otros flujos. La interfaz es confusa.
src/Service/GrupoPermisoService.php ~L31-35
Coste BAJOD3

Baja

6 ítems
DT-022
Nomenclatura inconsistente en columnas de base de datos
Mezcla de convenciones: bannedAt, creadoEn, fechaCreacion entre distintas entidades.
src/Entity/ (varias entidades)
Coste ALTOD2
DT-023
find() usado como findOneBy() en GrupoMiembroRepository
find(['grupo' => $grupo, 'usuario' => $usuario]) debería ser findOneBy(). Funciona, pero es semánticamente incorrecto.
src/Repository/GrupoMiembroRepository.php ~L26
Coste BAJOD2
DT-024
Sin validación de dimensiones en uploads de imagen
El formulario valida el tamaño (2MB) pero no las dimensiones. Una imagen de 10000×10000 px pasaría la validación.
src/Form/GrupoFormType.php ~L34-41
Coste BAJOD2
DT-025
Rutas inconsistentes entre controladores
Sin convención clara: invitaciones en GrupoController, votación en EventoController, sin principio explícito.
src/Controller/GrupoController.php · src/Controller/EventoController.php
Coste BAJOD2
DT-040
strlen() en lugar de mb_strlen() para contraseñas
strlen() puede dar longitud incorrecta con caracteres multibyte en contraseñas.
src/Controller/PerfilController.php ~L76
Coste BAJOD3
DT-041
'true' como string en query de NotificacionRepository
->set('n.leida', 'true') usa string en lugar de booleano PHP. Funciona en MySQL pero es semánticamente incorrecto.
src/Repository/NotificacionRepository.php ~L58
Coste BAJOD3

Orden de intervención recomendado

Máximo impacto · mínimo coste · sin romper funcionalidad
1Mensaje de error no informativo sobre existencia de usuarios — un cambio de string, impacto inmediatoDT-009
2URL hardcodeada con dominio antiguo en Evento::getPermalink()DT-029
3Email hardcodeado en BggApiService — mover a .envDT-030
4Añadir #[Assert] en entidades críticas (Usuario, Evento)DT-011
5Fix N+1 en GrupoRepository — añadir addSelect()DT-008
6Unificar patrón de flush en NotificacionService y ColeccionServiceDT-017, DT-018
7Extraer patrón try/catch → flash → redirect en EventoControllerDT-031
8Centralizar lógica de permisos — eliminar hasPermission() de la entidadDT-027
9Extraer JS de las plantillas Twig a assets compiladosDT-026
10Extraer notificaciones Telegram a un dispatcher centralizadoDT-028
11Añadir rate limiting al endpoint de password resetDT-015
12Implementar backup codes para 2FADT-016
13Refactorizar AuditService a batch flush o asyncDT-002
14Revisar y corregir cascades destructivos en UsuarioDT-001
15Mover sincronización BGG completamente al Handler asíncronoDT-003
16Extraer lógica de EventoController a EventoServiceDT-005
17Implementar Voter de Symfony para autorización de gruposDT-013
18Dividir GrupoService y EventoService en servicios de responsabilidad únicaDT-006
19Plan sistemático de tests — servicios críticos y rutas con control de accesoDT-004, DT-010