1024 lines
18 KiB
Markdown
1024 lines
18 KiB
Markdown
# ARCHIVADO — Diseño backend consolidado anterior
|
|
|
|
> Estado: **HISTÓRICO — NO NORMATIVO**.
|
|
>
|
|
> Nota de obsolescencia fiscal: para el comportamiento CURRENT de Proforma, disponibilidad,
|
|
> snapshot de IssuedInvoice, grouping P10 y estado de DELETE de TaxDefinition, consultar
|
|
> [Proforma Tax Config](../../customer-invoices/proforma-tax-config.md). Este documento no se
|
|
> actualiza retrospectivamente.
|
|
>
|
|
> Las reglas transversales vigentes están distribuidas por responsabilidad en [../backend-architecture.md](../backend-architecture.md), [../dependency-injection.md](../dependency-injection.md), [../http-request-flow.md](../http-request-flow.md), [../dto-guidelines.md](../dto-guidelines.md), [../module-boundaries.md](../module-boundaries.md) y [../testing-guidelines.md](../testing-guidelines.md).
|
|
>
|
|
> En particular, el flujo `RequestDTO → RequestMapper → Command → PropsMapper` que aparece más abajo no es una norma vigente: RequestMapper es opcional y se usa solo cuando existe adaptación HTTP propia.
|
|
|
|
---
|
|
|
|
# DOCUMENTO DE DISEÑO — ERP MODULAR BACKEND (ESTADO CONSOLIDADO)
|
|
|
|
## 1. Contexto general
|
|
|
|
ERP modular en monorepo con:
|
|
|
|
* PNPM
|
|
* Turborepo
|
|
* Node.js ≥ 22
|
|
* TypeScript
|
|
* ExpressJS
|
|
* MariaDB
|
|
* Sequelize v6
|
|
* Zod v4
|
|
|
|
|
|
Arquitectura basada en:
|
|
|
|
* DDD pragmático
|
|
* Clean Architecture estricta
|
|
* Separación clara Domain / Application / Infrastructure
|
|
* Composition root explícito por módulo
|
|
* Sin contenedor global implícito
|
|
* Sin DI mágico
|
|
* Sin Service Locator
|
|
* Sin dependencias cruzadas entre módulos
|
|
|
|
Principios permanentes:
|
|
|
|
* SOLID
|
|
* KISS
|
|
* DRY razonable (claridad > abstracción prematura)
|
|
* Fail Fast
|
|
* Idempotencia
|
|
* Separación IO / lógica de negocio
|
|
* Evitar sobreingeniería sistemáticamente
|
|
|
|
---
|
|
|
|
# 2. Concepto de módulo
|
|
|
|
Un módulo es una unidad funcional aislada que:
|
|
|
|
* Tiene su propio dominio.
|
|
* Tiene su propia capa Application.
|
|
* Tiene su propia capa Infrastructure.
|
|
* Expone servicios explícitamente a otros módulos.
|
|
* No accede directamente a infraestructura interna de otros módulos.
|
|
|
|
Cada módulo implementa el contrato `IModuleServer`.
|
|
|
|
Estructura conceptual:
|
|
|
|
```
|
|
<module>/
|
|
api/
|
|
application/
|
|
infrastructure/
|
|
```
|
|
|
|
No se comparte código cruzado entre módulos salvo a través de:
|
|
|
|
* Core
|
|
* Servicios expuestos explícitamente
|
|
|
|
---
|
|
|
|
# 3. Rol del módulo Core
|
|
|
|
`core` contiene:
|
|
|
|
* Funcionalidades técnicas reutilizables.
|
|
* Infraestructura transversal.
|
|
* Interfaces compartidas.
|
|
* Utilidades comunes.
|
|
* Contratos base.
|
|
|
|
Ubicación:
|
|
|
|
```
|
|
core/api/
|
|
application/
|
|
infra/
|
|
```
|
|
|
|
Reglas:
|
|
|
|
* Core no conoce módulos funcionales.
|
|
* Core no depende de customer-invoices ni de ningún otro módulo de negocio.
|
|
* Core solo define abstracciones y servicios reutilizables.
|
|
|
|
Core es el único módulo permitido como dependencia técnica común.
|
|
|
|
---
|
|
|
|
## 4. Separación estricta de capas
|
|
|
|
### Domain
|
|
|
|
Contiene:
|
|
|
|
* Agregados
|
|
* Entidades
|
|
* Value Objects
|
|
* Reglas de negocio
|
|
* Invariantes
|
|
|
|
No conoce:
|
|
|
|
* HTTP
|
|
* Sequelize
|
|
* DTO
|
|
* Infrastructure
|
|
|
|
### Application
|
|
|
|
Contiene:
|
|
|
|
* Use cases
|
|
* Servicios de aplicación (`Creator`, `Updater`, `Issuer`, etc.)
|
|
* Mappers DTO → Props
|
|
* Interfaces de repositorios
|
|
* Snapshot builders
|
|
|
|
No conoce:
|
|
|
|
* Express
|
|
* Sequelize
|
|
* Models ORM
|
|
|
|
### Infrastructure
|
|
|
|
Contiene:
|
|
|
|
* Controllers Express
|
|
* DTO HTTP
|
|
* Mappers Request DTO HTTP → Props
|
|
* Implementaciones Sequelize
|
|
* DomainMapper (persistencia ↔ dominio)
|
|
* DI del módulo
|
|
|
|
Nunca:
|
|
|
|
* Application depende de Infrastructure
|
|
* Domain depende de Application
|
|
|
|
---
|
|
|
|
|
|
|
|
# 4.1 Estructura interna de un módulo funcional
|
|
|
|
Ejemplo conceptual (customer-invoices):
|
|
|
|
```
|
|
customer-invoices/api/
|
|
application/
|
|
infrastructure/
|
|
```
|
|
|
|
### 4.1.1 Application
|
|
|
|
Contiene:
|
|
|
|
* Use cases
|
|
* Servicios de lectura (Finder)
|
|
* Servicios de escritura (WriteService)
|
|
* Factories
|
|
* Servicios de numeración
|
|
* Snapshot builders
|
|
* Application models
|
|
* Interfaces de repositorio
|
|
* Interfaces de servicios externos
|
|
|
|
Application:
|
|
|
|
* No conoce Express
|
|
* No conoce Sequelize
|
|
* No conoce filesystem
|
|
* No conoce implementaciones concretas
|
|
|
|
Solo depende de:
|
|
|
|
* Dominio
|
|
* Interfaces
|
|
* Core
|
|
|
|
---
|
|
|
|
### 4.1.2 Infrastructure
|
|
|
|
Contiene:
|
|
|
|
* Implementaciones Sequelize
|
|
* Implementaciones de repositorios
|
|
* Mappers dominio ↔ ORM
|
|
* Controllers Express
|
|
* Rutas
|
|
* Implementaciones de servicios externos
|
|
* DI del módulo
|
|
|
|
Infrastructure depende de:
|
|
|
|
* Application
|
|
* Dominio
|
|
* Core
|
|
|
|
Nunca al revés.
|
|
|
|
---
|
|
|
|
## 4.2 Snapshot builders (materialización de salida)
|
|
|
|
Los snapshot builders pertenecen a la capa **Application** y son responsables de transformar modelos de aplicación o agregados en estructuras serializables para la capa HTTP.
|
|
|
|
### Rol en el flujo
|
|
|
|
```
|
|
UseCase
|
|
↓
|
|
SnapshotBuilder
|
|
↓
|
|
DTO (response)
|
|
↓
|
|
Controller (HTTP)
|
|
```
|
|
|
|
---
|
|
|
|
### Principio clave
|
|
|
|
> El contrato de salida está definido exclusivamente por los DTOs (schemas Zod). El snapshot builder solo los materializa.
|
|
|
|
Esto refuerza la separación:
|
|
|
|
* DTO → contrato
|
|
* Builder → transformación
|
|
|
|
---
|
|
|
|
### Tipado obligatorio
|
|
|
|
Todo snapshot builder debe tiparse contra el DTO inferido:
|
|
|
|
```ts
|
|
ISnapshotBuilder<Input, OutputDTO>
|
|
```
|
|
|
|
donde:
|
|
|
|
```ts
|
|
OutputDTO = z.infer<typeof SomeSchema>
|
|
```
|
|
|
|
---
|
|
|
|
### Reglas obligatorias
|
|
|
|
* No definir interfaces manuales duplicando DTOs.
|
|
* No introducir tipos intermedios si no aportan valor real.
|
|
* El builder debe ser determinista y puro.
|
|
* El builder no puede depender de infraestructura.
|
|
* El builder no puede contener lógica de negocio.
|
|
|
|
---
|
|
|
|
### Relación con Use Cases
|
|
|
|
Los use cases:
|
|
|
|
* ejecutan lógica
|
|
* orquestan servicios
|
|
* construyen snapshots mediante builders
|
|
* devuelven DTOs (no entidades)
|
|
|
|
```ts
|
|
const items = customers.map(customerSummaryBuilder.toOutput);
|
|
|
|
return Result.ok({
|
|
page,
|
|
per_page,
|
|
...
|
|
items,
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
### Relación con Domain
|
|
|
|
El builder:
|
|
|
|
* nunca modifica el dominio
|
|
* nunca valida invariantes
|
|
* nunca llama a `create()` ni `rehydrate()`
|
|
|
|
Solo lee estado ya válido.
|
|
|
|
---
|
|
|
|
### Principio de consistencia
|
|
|
|
> Si el DTO define `null`, el builder debe emitir `null`.
|
|
> Si el DTO define enum, el builder debe emitir un valor válido del enum.
|
|
|
|
Nunca:
|
|
|
|
* `""` en lugar de `null`
|
|
* strings genéricos en lugar de enums
|
|
* boolean como string
|
|
|
|
---
|
|
|
|
### Objetivo arquitectónico
|
|
|
|
Garantizar:
|
|
|
|
* contratos HTTP estables
|
|
* desacoplo dominio ↔ transporte
|
|
* ausencia de duplicación de tipos
|
|
* consistencia transversal en todos los módulos
|
|
|
|
---
|
|
|
|
# Resultado
|
|
|
|
Con estos cambios:
|
|
|
|
* eliminas duplicación (`interface vs zod`)
|
|
* alineas builders con DTOs
|
|
* fuerzas consistencia en outputs (justo tu problema actual)
|
|
* mantienes KISS (sin introducir capas nuevas)
|
|
* refuerzas Clean Architecture sin añadir complejidad
|
|
|
|
---
|
|
|
|
## Siguiente paso lógico
|
|
|
|
Te recomiendo ahora hacer un refactor pequeño pero crítico:
|
|
|
|
1. eliminar todos los `I*Snapshot`
|
|
2. extraer todos los `*SummarySchema`
|
|
3. tipar builders con `z.infer`
|
|
|
|
Esto te va a eliminar el 80% de inconsistencias que estás viendo.
|
|
|
|
---
|
|
|
|
## 4.3. Composition Root por módulo
|
|
|
|
Cada módulo:
|
|
|
|
* Construye sus dependencias en `init()`
|
|
* Registra modelos en `registerDependencies()`
|
|
* Expone servicios explícitamente si quiere que otros módulos los usen
|
|
|
|
No se modifica el contrato de módulos.
|
|
|
|
No existe contenedor global compartido implícito.
|
|
|
|
Cada módulo controla su propio wiring.
|
|
|
|
---
|
|
|
|
## 4.4. Registro de dependencias entre módulos
|
|
|
|
Colaboración entre módulos se realiza mediante:
|
|
|
|
```
|
|
registerDependencies()
|
|
```
|
|
|
|
Regla:
|
|
|
|
* Solo se exponen servicios útiles.
|
|
* No se exponen repositorios.
|
|
* No se exponen detalles de infraestructura.
|
|
* Se exponen interfaces claras.
|
|
|
|
Ejemplo conceptual:
|
|
|
|
```
|
|
return {
|
|
models,
|
|
services: {
|
|
issuedInvoiceFinder,
|
|
customerInvoiceWriteService
|
|
}
|
|
}
|
|
```
|
|
|
|
Otros módulos solo consumen lo que fue registrado.
|
|
|
|
Nunca se importa directamente infraestructura de otro módulo.
|
|
|
|
---
|
|
|
|
## 5. Flujo semántico obligatorio
|
|
|
|
Regla estructural fija:
|
|
|
|
```
|
|
RequestDTO (Infrastructure)
|
|
↓
|
|
PropsMapper (Application)
|
|
↓
|
|
Aggregate
|
|
```
|
|
|
|
Prohibido:
|
|
|
|
* DTO → Aggregate
|
|
* Saltar niveles
|
|
|
|
Cada nivel tiene nombre distinto.
|
|
No se mezclan responsabilidades.
|
|
No hay ambigüedad.
|
|
|
|
---
|
|
|
|
## 6. Dominio de facturación (decisión consolidada)
|
|
|
|
Se elimina el agregado híbrido `CustomerInvoice`.
|
|
|
|
Se divide en:
|
|
|
|
### Proforma (agregado independiente)
|
|
|
|
* Modelo dinámico
|
|
* Calcula totales
|
|
* Agrupa impuestos dinámicamente
|
|
* Mutable (no contable)
|
|
* No conoce Verifactu
|
|
* No materializa impuestos
|
|
|
|
### IssuedInvoice (agregado independiente)
|
|
|
|
* Snapshot materializado
|
|
* Totales congelados
|
|
* Impuestos materializados
|
|
* Items materializados
|
|
* No recalcula nada
|
|
* Solo permite transición de estado contable
|
|
* Almacena Verifactu
|
|
|
|
Principio aplicado:
|
|
|
|
Snapshot models must not recalculate.
|
|
|
|
IssuedInvoice confía en el materializer.
|
|
No valida cálculos internos.
|
|
No recalcula dinámicamente.
|
|
|
|
---
|
|
|
|
## 7. Repositorios
|
|
|
|
Se elimina `ICustomerInvoiceRepository`.
|
|
|
|
Se crean:
|
|
|
|
* `IProformaRepository`
|
|
* `IIssuedInvoiceRepository`
|
|
|
|
Cada repositorio gestiona un solo agregado.
|
|
|
|
Actualmente se mantiene una sola tabla `customer_invoice`, pero:
|
|
|
|
* El dominio no conoce `is_proforma`.
|
|
* El filtrado vive exclusivamente en Infrastructure.
|
|
|
|
Se ha decidido duplicar repositorios explícitamente.
|
|
No usar clases base compartidas.
|
|
No usar repositorios genéricos.
|
|
|
|
---
|
|
|
|
## 8. Inyección de dependencias
|
|
|
|
* Constructor injection explícita.
|
|
* Sin registry dinámico.
|
|
* Sin service locator.
|
|
* Si hay más de 2 dependencias → usar objeto `deps`.
|
|
|
|
Ejemplo:
|
|
|
|
```ts
|
|
constructor(private readonly deps: {
|
|
mapper: IProformaDomainMapper;
|
|
database: Database;
|
|
}) {}
|
|
```
|
|
|
|
Wiring en DI del módulo.
|
|
|
|
---
|
|
|
|
## 9. Convención obligatoria de naming para mappers
|
|
|
|
### A. HTTP (Infrastructure)
|
|
|
|
**Request DTO**
|
|
|
|
Formato:
|
|
|
|
`<Operation><Aggregate>RequestDTO`
|
|
|
|
Ejemplo:
|
|
|
|
* `CreateProformaRequestDTO`
|
|
|
|
**HTTP → Command**
|
|
|
|
Formato:
|
|
|
|
`<Operation><Aggregate>RequestMapper`
|
|
|
|
Ejemplo:
|
|
|
|
* `CreateProformaRequestMapper`
|
|
|
|
---
|
|
|
|
### B. Application
|
|
|
|
**Command**
|
|
|
|
Formato:
|
|
|
|
`<Operation><Aggregate>Command`
|
|
|
|
Ejemplo:
|
|
|
|
* `CreateProformaCommand`
|
|
|
|
**Command → Props**
|
|
|
|
Formato:
|
|
|
|
`<Operation><Aggregate>PropsMapper`
|
|
|
|
Ejemplo:
|
|
|
|
* `CreateProformaPropsMapper`
|
|
|
|
---
|
|
|
|
### C. Persistencia (Infrastructure / Sequelize)
|
|
|
|
Un único mapper bidireccional por agregado:
|
|
|
|
Formato:
|
|
|
|
`<Aggregate>DomainMapper`
|
|
|
|
Métodos obligatorios:
|
|
|
|
* `toPersistence()`
|
|
* `toDomain()`
|
|
|
|
Ejemplo:
|
|
|
|
* `ProformaDomainMapper`
|
|
* `IssuedInvoiceDomainMapper`
|
|
|
|
---
|
|
|
|
### Reglas anti-ambigüedad
|
|
|
|
Prohibido:
|
|
|
|
* `CreateProformaMapper`
|
|
* `DtoMapper`
|
|
* `Transformer`
|
|
* `Adapter` sin necesidad real
|
|
|
|
El sufijo debe indicar claramente la dirección.
|
|
|
|
---
|
|
|
|
## 10. Servicios de aplicación
|
|
|
|
Se adoptan servicios pequeños y explícitos:
|
|
|
|
* `ProformaCreator`
|
|
* `ProformaUpdater`
|
|
* `ProformaDeleter`
|
|
* `ProformaIssuer`
|
|
|
|
Reglas:
|
|
|
|
* Responsabilidad única.
|
|
* No contener lógica HTTP.
|
|
* No acceder directamente a Sequelize.
|
|
* No mezclar múltiples operaciones en un único servicio.
|
|
|
|
Se evita `CommandService`.
|
|
|
|
---
|
|
|
|
## 11. Casos de uso
|
|
|
|
Los use cases:
|
|
|
|
* Son ligeros.
|
|
* Orquestan.
|
|
* Gestionan transacción.
|
|
* Delegan en servicios.
|
|
* Construyen snapshot de salida.
|
|
* Devuelven `Result`.
|
|
|
|
No deben:
|
|
|
|
* Mapear DTO.
|
|
* Construir agregados.
|
|
* Persistir directamente.
|
|
* Ejecutar lógica de negocio.
|
|
|
|
---
|
|
|
|
## 12. Controllers Express
|
|
|
|
Ubicación:
|
|
|
|
```
|
|
infrastructure/express/<aggregate>/
|
|
```
|
|
|
|
Responsabilidad:
|
|
|
|
* HTTP → RequestDTO
|
|
* RequestDTO → Command (mediante RequestMapper)
|
|
* Llamar UseCase
|
|
* Traducir Result → HTTP
|
|
|
|
Nunca:
|
|
|
|
* Acceder a repositorios.
|
|
* Construir agregados.
|
|
* Ejecutar lógica de dominio.
|
|
|
|
---
|
|
|
|
## 13. Colaboración entre módulos
|
|
|
|
Reglas:
|
|
|
|
* Solo se exponen servicios explícitos.
|
|
* No se exponen repositorios.
|
|
* No se accede a infraestructura interna.
|
|
* Core es la única dependencia técnica común.
|
|
|
|
No introducir Event Bus global ni abstracciones prematuras.
|
|
|
|
---
|
|
|
|
## 14. Decisiones clave adoptadas
|
|
|
|
* Separación real Proforma / IssuedInvoice.
|
|
* Snapshot materializado para facturas emitidas.
|
|
* Un repositorio por agregado.
|
|
* Una sola tabla por ahora.
|
|
* Duplicación preferida a abstracción prematura.
|
|
* Inyección explícita de dependencias.
|
|
* Mapper bidireccional único por agregado (persistencia).
|
|
* Un mapper por operación (RequestMapper, PropsMapper).
|
|
* Flujo obligatorio RequestDTO → Command → Props → Aggregate.
|
|
|
|
---
|
|
|
|
# 15. Convenciones de naming adoptadas
|
|
|
|
Interfaces:
|
|
|
|
* Prefijo obligatorio `I`
|
|
|
|
* `ICustomerInvoiceRepository`
|
|
* `IIssuedInvoiceFinder`
|
|
* `ICustomerInvoiceFactory`
|
|
* `ICustomerInvoiceNumberService`
|
|
|
|
Servicios:
|
|
|
|
* `*Finder` → lectura
|
|
* `*WriteService` → escritura
|
|
* `*Factory` → construcción
|
|
* `*NumberService` → numeración
|
|
|
|
Se evita:
|
|
|
|
* `CommandService`
|
|
* Nombres ambiguos
|
|
* ServiceLocator
|
|
|
|
---
|
|
|
|
# 16. Separación lectura / escritura (pragmática)
|
|
|
|
No se implementa CQRS formal completo.
|
|
|
|
Pero sí se separa:
|
|
|
|
* Finder (consultas)
|
|
* WriteService (mutaciones)
|
|
|
|
Motivo:
|
|
|
|
* Claridad semántica
|
|
* Mejor legibilidad
|
|
* Mejor separación de responsabilidades
|
|
|
|
Sin introducir sobreingeniería.
|
|
|
|
---
|
|
|
|
# 17. Mappers
|
|
|
|
Ubicación:
|
|
|
|
```
|
|
infrastructure/persistence/sequelize/mappers/
|
|
```
|
|
|
|
Tipos:
|
|
|
|
* domain/
|
|
* list/
|
|
|
|
Los mappers:
|
|
|
|
* Solo los usa el repositorio.
|
|
* No se exponen a Application.
|
|
* No se usan fuera de Infrastructure.
|
|
|
|
---
|
|
|
|
# 18. Reglas estrictas de dependencia
|
|
|
|
Dominio:
|
|
|
|
* No depende de nada externo.
|
|
|
|
Application:
|
|
|
|
* Depende solo de Dominio + interfaces + Core.
|
|
|
|
Infrastructure:
|
|
|
|
* Depende de Application + Dominio + Core.
|
|
|
|
Nunca:
|
|
|
|
* Application → Infrastructure
|
|
* Dominio → Application
|
|
* Módulo A → Infra de módulo B
|
|
|
|
---
|
|
|
|
# 19. Desarrollo de nuevos módulos
|
|
|
|
Para crear un nuevo módulo:
|
|
|
|
1. Crear estructura:
|
|
|
|
```
|
|
new-module/api/application
|
|
new-module/api/infrastructure
|
|
```
|
|
|
|
2. Definir:
|
|
|
|
* Dominio
|
|
* Interfaces en Application
|
|
* Use cases
|
|
* Servicios de lectura/escritura
|
|
|
|
3. Implementar Infra:
|
|
|
|
* Repositorios
|
|
* Controllers
|
|
* DI
|
|
|
|
4. Registrar módulo en el loader global.
|
|
|
|
5. Exponer solo servicios necesarios mediante `registerDependencies`.
|
|
|
|
No copiar lógica transversal: usar Core.
|
|
|
|
---
|
|
|
|
# 20. Colaboración entre módulos
|
|
|
|
Para que módulo A use lógica de módulo B:
|
|
|
|
1. B debe exponer servicio explícito.
|
|
2. A debe declarar dependencia.
|
|
3. El módulo loader debe garantizar orden de carga.
|
|
4. Se usa interfaz, no implementación concreta.
|
|
|
|
Nunca:
|
|
|
|
* Importar ficheros directamente.
|
|
* Acceder a infraestructura interna.
|
|
* Compartir repositorios.
|
|
|
|
---
|
|
|
|
# 21. Evitar overengineering
|
|
|
|
No introducir:
|
|
|
|
* Event bus global si no es necesario.
|
|
* Contenedores DI complejos.
|
|
* Domain events si no hay necesidad real.
|
|
* CQRS formal.
|
|
* Versionado prematuro.
|
|
* Abstracciones sin uso inmediato.
|
|
|
|
Cada abstracción debe tener un caso real.
|
|
|
|
---
|
|
|
|
# 22. Estilo de trabajo y preferencias
|
|
|
|
Responder:
|
|
|
|
* En castellano.
|
|
* Código en inglés.
|
|
* Comentarios en castellano.
|
|
* Directo y técnico.
|
|
* Sin introducciones innecesarias.
|
|
* Sin emoticonos.
|
|
* Señalar incoherencias.
|
|
* Proponer refactors si mejoran claridad.
|
|
* Priorizar arquitectura correcta.
|
|
* Minimizar tokens sin perder precisión.
|
|
* Revisar siempre riesgo de overengineering.
|
|
* Mantener coherencia modular.
|
|
* Diseñar nuevos módulos.
|
|
* Garantizar separación correcta de capas.
|
|
* Establecer contratos claros entre módulos.
|
|
* Evitar dependencias cruzadas indebidas.
|
|
* Evolucionar el sistema sin romper principios estructurales.
|
|
|
|
Ante cualquier nueva propuesta:
|
|
|
|
* Validar necesidad real.
|
|
* Evaluar impacto estructural.
|
|
* Evitar abstracción anticipada.
|
|
|
|
---
|
|
|
|
## 23. Convención de creación y rehidratación (obligatoria)
|
|
|
|
### 23.1 Principio general
|
|
|
|
Se establecen dos operaciones semánticamente distintas en el dominio:
|
|
|
|
```text
|
|
create() → construcción funcional (nacimiento en el dominio)
|
|
rehydrate() → reconstrucción desde persistencia
|
|
```
|
|
|
|
Estas operaciones **no son intercambiables** y deben mantenerse separadas en todo el sistema.
|
|
|
|
---
|
|
|
|
## 23.2 Entidades y Aggregate Roots
|
|
|
|
### create()
|
|
|
|
```ts
|
|
static create(props: CreateProps, id?: UniqueID): Result<Entity, Error>
|
|
```
|
|
|
|
Responsabilidades:
|
|
|
|
* Crear una nueva instancia de dominio
|
|
* Aplicar invariantes de negocio
|
|
* Validar datos de entrada
|
|
* Construir entidades internas
|
|
* Registrar domain events (cuando se implementen)
|
|
|
|
Características:
|
|
|
|
* Devuelve `Result`
|
|
* Puede fallar
|
|
* Representa “nacimiento” del objeto en el dominio
|
|
|
|
---
|
|
|
|
### rehydrate()
|
|
|
|
```ts
|
|
static rehydrate(props: InternalProps, id: UniqueID): Entity
|
|
```
|
|
|
|
Responsabilidades:
|
|
|
|
* Reconstruir una instancia desde persistencia
|
|
* Restaurar estado previamente válido
|
|
|
|
Características:
|
|
|
|
* No devuelve `Result`
|
|
* No valida reglas de creación
|
|
* No genera eventos
|
|
* No ejecuta lógica adicional
|
|
|
|
---
|
|
|
|
### Reglas obligatorias
|
|
|
|
* `create()` **NO se usa en infraestructura**
|
|
* `rehydrate()` **NO se usa en casos de uso**
|
|
* `create()` y `rehydrate()` **NO deben compartir lógica implícita**
|
|
* El constructor debe ser `protected`
|
|
|
|
---
|
|
|
|
## 23.3 Value Objects
|
|
|
|
### create()
|
|
|
|
```ts
|
|
static create(props: Props): Result<ValueObject, Error>
|
|
```
|
|
|
|
Responsabilidades:
|
|
|
|
* Validar el valor
|
|
* Normalizar si aplica
|
|
* Construir una instancia inmutable
|
|
|
|
Características:
|
|
|
|
* Sin identidad
|
|
* Sin eventos
|
|
* Sin distinción entre creación y reconstrucción
|
|
|
|
---
|
|
|
|
### Reglas obligatorias
|
|
|
|
* Los Value Objects **NO tienen `rehydrate()`**
|
|
* Los Value Objects **siempre se reconstruyen mediante `create()`**
|
|
* Los Value Objects **no dependen de persistencia**
|
|
|
|
---
|
|
|
|
## 23.4 Uso en el sistema
|
|
|
|
### Flujo de creación funcional
|
|
|
|
```text
|
|
Controller
|
|
↓
|
|
UseCase
|
|
↓
|
|
Aggregate.create()
|
|
↓
|
|
Repository.save()
|
|
```
|
|
|
|
---
|
|
|
|
### Flujo de reconstrucción
|
|
|
|
```text
|
|
Repository
|
|
↓
|
|
DomainMapper
|
|
↓
|
|
Aggregate.rehydrate()
|
|
```
|
|
|
|
---
|
|
|
|
## 23.5 Reglas de diseño adicionales
|
|
|
|
* `update()` **nunca debe usar `create()` ni `rehydrate()`**
|
|
* Las entidades **mutan su estado**, no se recrean
|
|
* Los Value Objects **sí pueden recrearse libremente**
|
|
* El dominio **no debe asumir origen de los datos**
|
|
* Infraestructura es la única capa que usa `rehydrate()`
|
|
|
|
---
|
|
|
|
## 23.6 Naming adicional (opcional)
|
|
|
|
Se permiten factories específicas en Value Objects cuando mejore la claridad:
|
|
|
|
```ts
|
|
UtcDate.createFromISO(...)
|
|
Money.fromDecimal(...)
|
|
TaxCode.fromString(...)
|
|
```
|
|
|
|
Estas no sustituyen a `create()`, sino que complementan su uso.
|
|
|
|
---
|
|
|
|
## 23.7 Principio final
|
|
|
|
> create() = nuevo estado válido en el dominio
|
|
> rehydrate() = estado ya válido restaurado
|
|
|
|
Esta distinción es obligatoria en todo el ERP.
|