diff --git a/docs/evaluacion-de-alternativas-a-sequelize.md b/docs/evaluacion-de-alternativas-a-sequelize.md new file mode 100644 index 0000000..f84261b --- /dev/null +++ b/docs/evaluacion-de-alternativas-a-sequelize.md @@ -0,0 +1,323 @@ +# Evaluación de alternativas a Sequelize en Metis Desk + +Fecha: 10 de octubre de 2026. Estado: estudio técnico; no se ha cambiado el ORM, instalado +ninguna alternativa ni modificado la base de datos. Las conclusiones de encaje y esfuerzo +son una valoración del código actual, no resultados de una migración o benchmark. + +## 1. Recomendación + +No recomiendo una sustitución completa inmediata. Recomiendo una prueba comparativa +acotada de **TypeORM y MikroORM**, manteniendo MariaDB, las tablas, el dominio y los +contratos HTTP. Si la prioridad es controlar SQL y reforzar su tipado, incluir **Kysely** +en la prueba, reconociendo que es un constructor de consultas y no un ORM completo. + +- **TypeORM:** camino más próximo al funcionamiento actual, con dialecto MariaDB explícito. +- **MikroORM:** candidato interesante para persistencia de relaciones y seguimiento de + cambios; su beneficio debe compensar el contexto por petición y los modelos separados. +- **Kysely:** alternativa muy alineada con los repositorios que ya utilizan SQL explícito. +- **Prisma 7:** buen candidato para ergonomía de CRUD, condicionado por la transición de + versiones y el uso intensivo de SQL/bloqueos del proyecto. Prisma 8 no entra en la prueba + con MariaDB mientras su documentación indique ausencia de MySQL. +- **Drizzle:** atractivo para TypeScript y esquema relacional, pero no lo elegiría aquí sin + validar primero compatibilidad MariaDB en consultas y migraciones. +- **Sequelize 6:** mantenerlo y mejorar aspectos concretos sigue siendo una opción válida. + +La decisión debe salir de medir menos código de persistencia, mejores tipos y mantenimiento, +sin perder garantías de concurrencia; no de elegir el ORM con ejemplos de CRUD más breves. + +## 2. Situación real del proyecto + +La inspección del repositorio muestra: + +| Aspecto | Estado observado | Consecuencia | +| --- | --- | --- | +| ORM y motor | Sequelize 6.37.8; driver mariadb 3.5.4; contenedor MariaDB 11.8 | Comparar contra esta combinación, no contra PostgreSQL ni un ejemplo SQLite. | +| Acoplamiento | 90 ficheros de servidor importan o mencionan Sequelize; 24 implementaciones de repositorio tienen nombre `sequelize-*.repository.ts` | El cambio afecta a bastantes piezas aunque la frontera arquitectónica esté bien definida. Los números son de código fuente, no de tablas. | +| Capas | Puertos en Application; modelos ORM y mappers en Infrastructure; dominio DDD propio | La mayoría de entidades, Value Objects y casos de uso deberían conservarse. | +| Persistencia | Combinación de Model, consultas SQL parametrizadas, helpers relacionales y transacciones | Una sustitución no consiste solo en regenerar entidades. | +| Concurrencia | `FOR UPDATE`, bloqueos del catálogo y orden fijo de adquisición | Deben reproducirse dentro de la misma transacción/conexión. | +| Históricos | Revisiones de activos y asignaciones, entre otros | Deben mantenerse identidad, orden e inmutabilidad del histórico. | +| Sesiones | `connect-session-sequelize` 8.0.6 sobre `http_sessions` | Eliminar Sequelize completamente requiere sustituir o adaptar el store de Express. | +| Normalización | Activos tienen persistencia relacional; todavía aparecen JSON/JSON_EXTRACT en otros ámbitos | Cambiar ORM no completa la normalización. Son proyectos distintos. | + +Fuentes locales principales: `apps/server/src/infrastructure/http/identity-runtime.ts`, +`modules/assets/src/api/infrastructure/persistence/sequelize-asset.repository.ts`, +`sequelize-brand.repository.ts`, `asset-inventory-relational.ts`, +`modules/companies/src/api/infrastructure/persistence/sequelize-dashboard.repository.ts`, +`modules/resources/src/api/infrastructure/persistence/resource-catalog.model.ts` y +`docs/arquitectura/capas-del-servidor.md`. + +El dominio sigue utilizando `UniqueID`, `Result`, `Maybe` y mappers. Ningún ORM debería +sustituir esos contratos ni generar nuevos UUID con otra estrategia por defecto. + +## 3. Comparación resumida + +Las valoraciones de esfuerzo son relativas a Metis Desk y abarcan reescritura y validación, +no solo conexión inicial. No son métricas objetivas de calidad universal. + +| Alternativa | MariaDB | Ventaja principal aquí | Coste de adaptación | Valoración | +| --- | --- | --- | --- | --- | +| Sequelize 6 | Dialecto actual ya operativo | Continuidad y menor riesgo | Bajo si se mantiene | Referencia contra la que medir el beneficio. | +| TypeORM | Soporte explícito | Modelo/repository/query builder reconocibles | Medio | Primera prueba si se busca migrar con menor distancia conceptual. | +| MikroORM | Driver explícito | Unit of Work, relaciones y seguimiento de cambios | Medio-alto | Primera prueba si se busca mejorar mantenibilidad de persistencia compleja. | +| Prisma 7 | Conector MySQL admite MariaDB | Cliente generado y CRUD tipado | Medio-alto | Condicionado por versiones y consultas específicas. | +| Prisma 8 | Documentación consultada: sin MySQL | No aplicable al motor actual | No evaluable aquí | Excluido de la propuesta actual. | +| Drizzle | Integración documentada MySQL/mysql2; soporte específico MariaDB pendiente de verificar | Esquemas TypeScript y consultas próximas a SQL | Medio; riesgo adicional de dialecto | No primera elección con MariaDB. | +| Kysely | Dialecto MySQL/mysql2; comprobar diferencias MariaDB | Consultas SQL compuestas y tipadas | Medio | Buena alternativa cuando importa más SQL que gestión automática de objetos. | + +## 4. Sequelize: mantener o actualizar + +**Ventajas:** funciona con el motor y las sesiones actuales; los errores y transacciones ya +están integrados; dispone de inferencia de atributos TypeScript, que el proyecto utiliza. +Su documentación reconoce que determinadas declaraciones de modelo siguen requiriendo +trabajo manual. [TypeScript en Sequelize 6](https://sequelize.org/docs/v6/other-topics/typescript/). + +**Inconvenientes en este proyecto:** se repiten definiciones entre modelos y tipos; +`database.query` declara un tipo de resultado, pero no demuestra que el SQL devuelva +esas columnas; las migraciones y helpers necesitan disciplina propia. + +**Mejoras sin migración:** separar el ejecutor de SQL de consultas concretas, reutilizar +mappers reales y revisar transacciones/lecturas excesivas. No crear un repositorio genérico +que oculte reglas de negocio o bloqueos. No atribuir a Sequelize costes de consultas que +cargan todo un contexto de inventario: cambiar el ORM no evita esa decisión de diseño. + +**Actualización a v7:** es un camino distinto de cambiar de ORM. La documentación consultada +sigue etiquetándola como alpha; no la tomaría como actualización automática del proyecto. +[Estado de Sequelize 7](https://sequelize.org/docs/v7/). + +## 5. TypeORM + +### Capacidades verificadas + +Tiene soporte explícito MySQL/MariaDB, configuración `type: "mariadb"` y driver mysql2. +Permite definir entidades mediante `EntitySchema`, sin imponer decoradores. Incluye +migraciones, query builder y transacciones; las operaciones transaccionales deben usar el +manager proporcionado por la transacción. +[MariaDB](https://typeorm.io/docs/drivers/mysql/), +[EntitySchema](https://typeorm.io/docs/entity/separating-entity-definition/), +[transacciones](https://typeorm.io/docs/transactions/), +[migraciones](https://typeorm.io/docs/migrations/setup/). + +### Ventajas para Metis Desk + +- Sustitución relativamente directa de modelos y repositorios de infraestructura. +- Permite conservar SQL particular donde aporte claridad. +- Es posible mantener entidades de persistencia separadas del dominio DDD. +- Su dialecto MariaDB reduce incertidumbre respecto a una integración solo MySQL. + +### Inconvenientes y adaptación + +Seguirán existiendo modelos de persistencia y mappers; no desaparece toda la duplicación. +Los resultados de consultas SQL crudas tampoco quedan automáticamente validados. +Las cascadas, relaciones y métodos de guardado deben configurarse expresamente para no +modificar históricos o borrar registros relacionados por accidente. + +Reescribiríamos modelos, repositorios, composición de módulos, conexión y migraciones; +revisaríamos traducción de errores de FK/unique y semántica de upsert. Evitaríamos Active +Record dentro del dominio y `synchronize` como sustituto de migraciones. La documentación +actual anuncia TypeORM 1.0: fijaríamos una versión concreta y sus instrucciones, sin mezclar +ejemplos antiguos de 0.2/0.3 con la API elegida. + +**Valoración:** el reemplazo convencional más cercano al código actual. La ganancia principal +sería ergonomía y consistencia, no una transformación radical de la arquitectura. + +## 6. MikroORM + +### Capacidades verificadas + +Dispone de driver MariaDB, Unit of Work e Identity Map. Puede calcular los cambios de +entidades gestionadas al hacer `flush`; ofrece migraciones y generación de entidades desde +el esquema. La definición `defineEntity` permite inferir tipos sin decoradores. +[Arquitectura y drivers](https://mikro-orm.io/docs/architecture), +[Unit of Work](https://mikro-orm.io/docs/unit-of-work), +[defineEntity](https://mikro-orm.io/docs/define-entity), +[generador](https://mikro-orm.io/docs/entity-generator), +[migraciones](https://mikro-orm.io/docs/migrations). + +### Ventajas para Metis Desk + +- Interesante para persistir conjuntos relacionados sin repetir tantas operaciones de guardado. +- El seguimiento de cambios puede ayudar a actualizar únicamente entidades modificadas. +- Herramientas para definir, generar y evolucionar el esquema. +- Buen candidato para relaciones complejas si se mantiene clara la separación entre dominio + y representación persistida. + +### Inconvenientes y adaptación + +Requiere comprender el ciclo de vida de las entidades gestionadas y sus colecciones. +La Identity Map debe aislarse por petición, mediante fork/contexto; un EntityManager global +compartido sin aislamiento no es aceptable. +[Contexto por petición](https://mikro-orm.io/docs/identity-map). + +Las entidades DDD actuales no se convertirían directamente en entidades gestionadas: +se mantendrían mappers. Si un mapper crea una representación persistida completamente +nueva en cada guardado, parte de las ventajas del seguimiento de cambios se reduce. +La prueba debe resolver este punto, no suponer que Unit of Work elimina todos los mappers. + +Reescribiríamos modelos/repositorios y añadiríamos contexto por petición, respetando límites +transaccionales. Las revisiones continuarían como registros explícitos; una cascada de +borrado no sustituye las reglas del histórico. Los bloqueos existentes seguirían siendo +necesarios. + +**Valoración:** candidato con ventajas potenciales de persistencia más profundas, a cambio de +mayor aprendizaje e integración que TypeORM. No introducir sus abstracciones en Domain. + +## 7. Prisma: distinguir versiones + +### Capacidades y situación verificadas + +Prisma 7 documenta MariaDB mediante el conector MySQL y el adaptador mariadb. Ofrece +introspección, cliente generado, transacciones interactivas y baseline para adoptar una BD +existente sin recrearla. +[Conector v7](https://www.prisma.io/docs/orm/v7/core-concepts/supported-databases/mysql), +[transacciones v7](https://www.prisma.io/docs/orm/v7/prisma-client/queries/transactions), +[baseline v7](https://www.prisma.io/docs/orm/v7/prisma-migrate/workflows/baselining). + +La documentación principal ya presenta Prisma 8 y el repositorio publica versiones RC. +La página actual de transacciones indica que Prisma 8 no tiene soporte MySQL. No se debe +suponer que instalar `latest` conserva compatibilidad MariaDB ni trasladar ejemplos v7 a v8. +[Releases](https://github.com/prisma/orm/releases), +[documentación actual de transacciones](https://www.prisma.io/docs/orm/fundamentals/transactions). + +### Ventajas de evaluar v7 + +- Buen autocompletado y selección tipada de campos en el cliente generado. +- CRUD y relaciones sencillas pueden expresarse de forma compacta. +- Introspección útil para comenzar a partir de nuestras tablas. +- Esquema declarativo e historial de migraciones formalizado. + +### Inconvenientes y adaptación + +Añade un esquema propio y un paso de generación al monorepo. Los modelos generados no +reemplazan entidades DDD ni DTOs Zod. Las consultas particulares del dashboard y el orden +de bloqueos del inventario requerirían revisión y, donde proceda, SQL parametrizado dentro +de la misma transacción; no basta con sustituirlas por escrituras anidadas de CRUD. + +La introspección no genera reglas de negocio ni reconstruye las migraciones históricas de +Sequelize. Hay que revisar tipos físicos, restricciones e índices y preservar aquellos +elementos no representados por el esquema. Existe además incertidumbre de evolución de +versiones que debe resolverse antes de elegirlo para un motor MariaDB a largo plazo. + +**Valoración:** atractivo para aplicaciones CRUD, pero no el candidato principal ahora. +Prisma 7 podría evaluarse fijando versión; Prisma 8 queda fuera por el motor actual. + +## 8. Drizzle + +### Capacidades verificadas + +Declara el esquema en TypeScript y ofrece consultas cercanas a SQL. La integración oficial +consultada es MySQL con mysql2; Drizzle Kit puede introspectar una base existente. +[MySQL](https://orm.drizzle.team/docs/mysql/get-started-mysql), +[introspección](https://orm.drizzle.team/docs/get-started/mysql-existing). + +Hay una petición abierta para comportamiento específico MariaDB y reportes de incompatibilidad +con consultas relacionales basadas en LATERAL. Es evidencia de un área que requiere +validación, no una prueba de que cualquier consulta Drizzle falle en MariaDB ni de que el +problema afecte a todas las versiones/APIs. +[Issue del proyecto](https://github.com/drizzle-team/drizzle-orm/issues/2007). + +### Ventajas e inconvenientes para Metis Desk + +Su enfoque resulta afín a una BD normalizada y a infraestructura que ya usa SQL. Evita +imponer entidades gestionadas y decoradores. No elimina mappers ni reglas de guardado de +agregados y no aporta Unit of Work al estilo MikroORM. + +La dificultad extra aquí es verificar diferencias MariaDB/MySQL en generación de SQL, +introspección, tipos y migraciones. Conservaría SQL explícito donde fuese necesario y no +usaría una API relacional sin comprobar su SQL en MariaDB 11.8. La documentación consultada +contiene ejemplos con paquetes RC: fijaríamos una versión y separaríamos APIs estables y RC. + +**Valoración:** buena propuesta técnica si la prueba de compatibilidad es satisfactoria; +menor prioridad que los candidatos con soporte MariaDB explícito. + +## 9. Kysely, Knex y Objection.js + +**Kysely no es un ORM de entidades.** Es un constructor SQL tipado; su dialecto MySQL usa +mysql2. Sus tipos actúan en compilación y el driver determina los valores reales devueltos. +[Introducción](https://kysely.dev/docs/intro), +[drivers y tipos](https://kysely.dev/docs/getting-started). + +Para Metis Desk, permite mantener el control sobre joins, proyecciones y escrituras de +históricos, mejorando el tipado frente a SQL en strings. Su coste es seguir implementando +mapeo, persistencia de relaciones y cambios de esquema; los tipos de tabla deben mantenerse +alineados con la BD. Al emplear el dialecto MySQL, también hay que probar funciones y SQL +específico de MariaDB. El problema de consultas relacionales automáticas de Drizzle no se +traslada por sí solo a Kysely: son implementaciones distintas. + +**Valoración:** potencialmente la opción más sencilla si lo que queremos sustituir es el +uso de Sequelize como ejecutor SQL. Menos apropiada si buscamos guardado automático de +objetos relacionados. + +**Knex** cubre consultas/migraciones; **Objection.js** añade un ORM sobre Knex y deja las +migraciones del esquema a esa capa. Podrían sustituir partes del proyecto, pero no los +priorizaría sin una necesidad concreta frente a la prueba de Kysely/TypeORM/MikroORM. +[Diseño de Objection.js y migraciones](https://github.com/Vincit/objection.js). +Esta es una priorización por encaje, no una afirmación de falta de mantenimiento. + +## 10. Riesgos comunes de la migración + +1. **Transacciones:** no mezclar operaciones Sequelize y el nuevo cliente dentro de una + operación que necesita atomicidad. Dos pools no comparten automáticamente una transacción. +2. **Bloqueos:** preservar el orden catálogo → compañía → registros y comprobar la semántica + de `FOR UPDATE`, especialmente al eliminar marcas mientras se crean activos. +3. **Migrations:** durante coexistencia, mantener una única autoridad para cada tabla. No + ejecutar dos generadores sobre las mismas tablas ni emplear sync/push para recrearlas. +4. **Tipos físicos:** revisar UUID actuales, booleanos TINYINT, fechas sin hora, precisión de + timestamps/decimales, nulabilidad y collation. No cambiar su representación por comodidad. +5. **Errores:** conservar conflictos de unicidad/FK, códigos de aplicación y datos seguros. +6. **Históricos:** preservar UUID, orden, fechas, usuario actor y referencias aunque un valor + de catálogo se desactive. +7. **Sesiones:** inicialmente puede seguir el store Sequelize. Para retirar la dependencia + completamente habría que reemplazarlo preservando expiración, cookies y validación de + sesiones, o asumir un cierre de sesiones controlado. No usar MemoryStore como reemplazo. +8. **Normalización:** continuar la eliminación de JSON como trabajo separado, autorizado y + con migraciones propias. Ninguna herramienta garantiza una BD normalizada por usarla. +9. **Rendimiento:** medir número de consultas, tiempo y filas leídas con datos equivalentes; + no afirmar que un ORM es más rápido solo por su API. + +## 11. Prueba propuesta antes de decidir + +No ejecutada en este informe. Usaría una copia de prueba de la BD, sin tocar desarrollo. + +### Casos representativos + +- **Marcas:** listar, crear, actualizar y rechazar borrado referenciado. Incluye bloqueos + y restricciones, por lo que da más información que un CRUD de ejemplo. +- **Un activo:** características normalizadas, plantilla con campos opcionales e histórico. +- **Lectura de dashboard:** consultas con agregaciones y parámetros de compañía. +- **Concurrencia:** dos solicitudes sobre el mismo catálogo/activo y fallo a mitad del guardado. + +Los candidatos implementarían los mismos puertos. Primero reutilizaríamos los tests de +contrato, dominio e integración existentes; después añadiríamos casos de concurrencia que +falten. No sustituir MariaDB por SQLite para concluir compatibilidad. + +### Criterios de aceptación + +- Mismos datos, contratos HTTP, historial y reglas de borrado. +- Sin cambios accidentales en DDL al adoptar el ORM. +- Rollback completo y bloqueos equivalentes. +- Menos declaraciones duplicadas o guardados manuales en un caso real. +- Mejor detección de errores de columnas/resultados en TypeScript. +- Consultas y tiempos comparables bajo el mismo volumen. +- Inicialización, cierre de conexiones, migraciones y pruebas reproducibles con Node 24/PNPM. + +Como estimación de planificación, dedicaría unas pocas jornadas a una prueba acotada y +reservaría varias semanas para una migración completa y validación de los 24 repositorios +nombrados y sus helpers. No es un presupuesto: el inventario detallado de tablas y el piloto +son necesarios para convertirlo en una estimación fiable. + +## 12. Ruta de adopción si la prueba resulta favorable + +1. Fijar candidato/versión y actualizar las decisiones de arquitectura que hoy prescriben + Sequelize; no empezar mezclando estilos sin una decisión explícita. +2. Inventariar esquema y migraciones; crear baseline verificado con DDL y datos de prueba. +3. Migrar un módulo o un conjunto transaccional completo, manteniendo los puertos y mappers. +4. Repetir integración sobre MariaDB y comparar concurrencia/consultas. +5. Ampliar por módulos; conservar Sequelize temporalmente para partes no migradas. +6. Sustituir el store de sesiones y retirar la conexión/dependencia cuando no queden usos. +7. Retirar la antigua ejecución de migraciones sin perder su historial ni trazabilidad. + +Conclusión: **TypeORM** ofrece el recorrido más cercano; **MikroORM** debe demostrar mayor +ahorro en persistencia compleja; **Kysely** puede ser la elección más coherente si preferimos +SQL explícito. Mantener Sequelize sigue siendo preferible a una migración que solo cambie +la sintaxis y no reduzca la complejidad real.