uecko-control-tiempos/docs/arquitectura/backend.md

4.7 KiB

Arquitectura backend

Objetivo

Definir responsabilidades, contratos y límites del backend. La dirección general de dependencias está en visión general.

Capas y composición

Domain no debe conocer Application ni Infrastructure. Application debe depender de Domain, contratos y Core; no debe conocer Express, Sequelize ni modelos ORM. Infrastructure puede depender de Application, Domain y Core.

Cada módulo debe construir explícitamente sus dependencias en su composition root. No se permite Service Locator, DI implícita o mágica ni importar Infrastructure interna de otro módulo.

Entrada, DTOs y contratos HTTP

Zod se establece como fuente única de verdad de los contratos HTTP. Los tipos deben inferirse mediante z.infer<typeof Schema> y no se mantienen interfaces manuales equivalentes. Cada endpoint debe disponer de DTOs de request y response; los decimales exactos no viajan como number.

Los schemas deben expresar semánticamente UUID, fechas, timestamps, porcentajes, importes y cantidades, y distinguir campo omitido, null, cadena vacía y colección vacía. No se mezclan validación de transporte y lógica de negocio; .default() y .transform() solo se usan en DTOs de negocio cuando estén justificados. Los subschemas solo se reutilizan cuando sean realmente estables y compartidos.

El flujo habitual es:

RequestDTO → UseCase({ dto, context }) → InputMapper → Value Objects / Props

RequestMapper → Command es opcional y solo procede ante una adaptación HTTP real, como combinar body, params y query. No se crea solo para renombrar campos que Application volverá a transformar.

Application y Domain

Los casos de uso deben orquestar, gestionar transacciones cuando corresponde, delegar en servicios de aplicación, devolver Result y construir respuestas mediante builders o mappers de salida. No contienen lógica HTTP, ORM, modelos Sequelize ni responsabilidades de controller, repositorio o Infrastructure; tampoco construyen agregados directamente desde DTOs HTTP.

Los servicios de aplicación deben ser pequeños, explícitos y de responsabilidad concreta. Se evitan nombres genéricos como Manager, GenericService, CommandService, BaseRepository o Helper sin semántica.

Se establecen los patrones del proyecto: Result para operaciones que pueden fallar, Maybe para ausencia semántica y Collection cuando el dominio requiere comportamiento propio para un conjunto homogéneo. Los fallos de negocio esperables no se representan mediante excepciones, null, arrays ni tipos ad hoc cuando corresponda uno de esos patrones.

create() representa creación funcional: valida invariantes, puede fallar y devuelve Result. rehydrate() reconstruye persistencia válida: no valida creación, no genera eventos y no devuelve Result. Infrastructure debe usar rehydrate(); Application y Domain deben usar create() para instancias nuevas. update() no usa ninguna de las dos, y los Value Objects se reconstruyen con create().

Repositorios, mappers y respuestas

Cada repositorio debe gestionar un agregado o responsabilidad concreta, no compartirse entre módulos ni exponerse como API pública. No se crean repositorios base genéricos sin necesidad demostrada. Los detalles de ORM deben permanecer en Infrastructure. Los mappers de persistencia e invariantes de almacenamiento se definen en datos y persistencia.

Los snapshot builders deben materializar el DTO de salida exacto y tiparse con el tipo inferido del schema, de modo que un cambio incompatible del schema produzca un error de compilación. Son deterministas, sin lógica de negocio ni acceso a repositorios o Infrastructure; pueden transformar Maybe en null, Value Objects en primitivas y enums internos en valores del contrato. No se crean interfaces I*Snapshot que dupliquen DTOs.

Errores, seguridad y API

Los errores esperables deben ser tipados y predecibles. Domain y Application se mapean a HTTP en Infrastructure sin exponer SQL, stack traces, detalles internos ni información sensible. Los errores deben conservar contexto suficiente para diagnosticar el fallo. No se ocultan fallos, no se convierten en éxito para continuar y el logging no sustituye su tratamiento. Las APIs y servicios deben mantener observabilidad suficiente para investigar fallos relevantes sin exponer información sensible.

Toda entrada externa debe validarse. La autorización se verifica además de la autenticación; no se confía en permisos, compañía o identificadores enviados por el cliente sin contrastarlos con el contexto autenticado. Las consultas y mutaciones deben preservar aislamiento multiempresa. La idempotencia se aplica a operaciones repetibles según su semántica.