@ronaldjdevfs/forge 1.3.0-beta → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +11 -4
  2. package/package.json +1 -1
  3. package/skills/forge/SKILL.md +57 -128
  4. package/skills/forge/command/forge.md +59 -14
  5. package/skills/forge/reference/adr.md +242 -0
  6. package/skills/forge/reference/anti-corruption-layer.md +340 -0
  7. package/skills/forge/reference/api-design.md +7 -0
  8. package/skills/forge/reference/api-versioning.md +354 -0
  9. package/skills/forge/reference/architectural-depth-checklist.md +311 -0
  10. package/skills/forge/reference/architecture-template.md +41 -0
  11. package/skills/forge/reference/assay.md +6 -0
  12. package/skills/forge/reference/bounded-contexts.md +311 -0
  13. package/skills/forge/reference/chain.md +6 -0
  14. package/skills/forge/reference/cohesion-checklist.md +256 -0
  15. package/skills/forge/reference/cqrs.md +286 -0
  16. package/skills/forge/reference/data-patterns.md +6 -0
  17. package/skills/forge/reference/di-strategies.md +6 -0
  18. package/skills/forge/reference/errors.md +5 -0
  19. package/skills/forge/reference/events.md +8 -0
  20. package/skills/forge/reference/evolutionary-architecture.md +300 -0
  21. package/skills/forge/reference/forge.md +7 -0
  22. package/skills/forge/reference/hooks.md +6 -0
  23. package/skills/forge/reference/idempotency.md +283 -0
  24. package/skills/forge/reference/inscribe.md +5 -0
  25. package/skills/forge/reference/inspect.md +6 -0
  26. package/skills/forge/reference/modular-monolith.md +252 -0
  27. package/skills/forge/reference/observability.md +5 -0
  28. package/skills/forge/reference/quench.md +5 -0
  29. package/skills/forge/reference/relocate.md +6 -0
  30. package/skills/forge/reference/sagas.md +359 -0
  31. package/skills/forge/reference/security-patterns.md +6 -0
  32. package/skills/forge/reference/smelt.md +6 -0
  33. package/skills/forge/reference/temper.md +6 -0
  34. package/skills/forge/reference/testing-patterns.md +6 -0
  35. package/skills/forge/reference/transactional-outbox.md +311 -0
  36. package/skills/forge/scripts/architecture.mjs +10 -5
  37. package/skills/forge/scripts/assay.mjs +2 -2
  38. package/skills/forge/scripts/chain.mjs +31 -5
  39. package/skills/forge/scripts/context.mjs +24 -4
  40. package/skills/forge/scripts/detect.mjs +57 -36
  41. package/skills/forge/scripts/forge-boot.mjs +108 -0
  42. package/skills/forge/scripts/forge-config.mjs +182 -3
  43. package/skills/forge/scripts/forge-state.mjs +1 -1
  44. package/skills/forge/scripts/forgeSentinel-lib.mjs +2 -2
  45. package/skills/forge/scripts/forgeSentinel.mjs +2 -2
  46. package/skills/forge/scripts/forgeSmith.mjs +2 -2
  47. package/skills/forge/scripts/graph.mjs +65 -9
  48. package/skills/forge/scripts/hook.mjs +2 -2
  49. package/skills/forge/scripts/inspect.mjs +56 -48
  50. package/skills/forge/scripts/parse-imports.mjs +0 -2
  51. package/skills/forge/scripts/posttool.mjs +211 -17
  52. package/skills/forge/scripts/recommendation-engine.mjs +125 -0
  53. package/skills/forge/scripts/rename.mjs +62 -14
  54. package/skills/forge/scripts/rollback.mjs +5 -3
  55. package/skills/forge/templates/feature/acl-gateway.ts.md +52 -0
  56. package/skills/forge/templates/feature/acl-repository.ts.md +36 -0
  57. package/skills/forge/templates/feature/acl-translator.ts.md +24 -0
  58. package/skills/forge/templates/feature/cqrs-query.ts.md +40 -0
  59. package/skills/forge/templates/feature/entity.ts.md +1 -1
  60. package/skills/forge/templates/feature/mapper.ts.md +1 -1
  61. package/skills/forge/templates/feature/outbox-repository.ts.md +36 -0
  62. package/skills/forge/templates/feature/repository-impl.ts.md +2 -2
  63. package/skills/forge/templates/feature/repository-interface.ts.md +2 -2
  64. package/skills/forge/templates/feature/saga-orchestrator.ts.md +72 -0
  65. package/skills/forge/templates/feature/schema.ts.md +1 -1
  66. package/skills/forge/templates/feature/use-case.ts.md +2 -2
  67. package/skills/forge/templates/platform/outbox-relayer.ts.md +80 -0
  68. package/skills/forge/tests/core.test.mjs +288 -4
  69. package/src/cli.js +26 -13
@@ -0,0 +1,242 @@
1
+ # Architecture Decision Records (ADR)
2
+
3
+ Las decisiones arquitectónicas son el activo más valioso de un proyecto a largo plazo. Sin registro, cada nueva incorporación al equipo redescubre lo mismo. Los ADRs capturan el qué, el por qué y las consecuencias de cada decisión.
4
+
5
+ ---
6
+
7
+ ## Formato ADR Estándar
8
+
9
+ Cada ADR es un archivo en `docs/adr/` con el formato `NNNN-title-with-dashes.md`.
10
+
11
+ ```md
12
+ # NNNN — Título corto pero descriptivo
13
+
14
+ **Estado:** [Proposed | Accepted | Deprecated | Superseded | Amended]
15
+ **Fecha:** YYYY-MM-DD
16
+ **Decisores:** [lista de personas que tomaron la decisión]
17
+
18
+ ## Contexto
19
+
20
+ Describe el problema o situación que motiva la decisión. Incluye:
21
+ - Restricciones técnicas o de negocio
22
+ - Alternativas consideradas brevemente
23
+ - Por qué el status quo no es aceptable
24
+ - Enlaces a ADRs relacionados
25
+
26
+ ## Decisión
27
+
28
+ La decisión que se tomó. Debe ser específica, no genérica.
29
+
30
+ > Adoptamos Prisma como ORM para la capa de infra, con esquemas separados
31
+ > por schema de base de datos Postgres, mapeando cada feature a un schema.
32
+
33
+ ## Consecuencias
34
+
35
+ Lo que cambia a partir de esta decisión:
36
+
37
+ - **Positivas:**
38
+ - Type-safe queries sin runtime validation
39
+ - Migraciones automáticas con `prisma migrate`
40
+ - Schemas por feature como primer paso hacia microservicios
41
+ - **Negativas:**
42
+ - Vendor lock-in con Prisma (cambiar de ORM requiere reescribir repos)
43
+ - Migraciones lentas en bases de datos con millones de registros
44
+ - Dependencia de `prisma generate` como paso de build
45
+ - **Neutrales:**
46
+ - El equipo necesita aprender Prisma (curva de 1-2 semanas)
47
+ ```
48
+
49
+ ---
50
+
51
+ ## Variantes
52
+
53
+ ### ADR Completo (recomendado para decisiones fundacionales)
54
+
55
+ Incluye Contexto + Decisión + Consecuencias + Alternativas evaluadas.
56
+
57
+ ```md
58
+ ## Alternativas Consideradas
59
+
60
+ | Alternativa | Pros | Contras | Veredicto |
61
+ |---|---|---|---|
62
+ | TypeORM | Maduro, Decorators | Performance pobre en joins complejos | ❌ |
63
+ | Drizzle | SQL-like, sin decorators | Ecosistema más pequeño | ❌ |
64
+ | Prisma | Type-safe, Migraciones, Schemas | Vendor lock-in, generate step | ✅ |
65
+ ```
66
+
67
+ ### ADR Ligero (para decisiones tácticas)
68
+
69
+ ```md
70
+ # 0012 — Usar tRPC para endpoints internos
71
+
72
+ **Estado:** Accepted
73
+ **Fecha:** 2026-05-10
74
+
75
+ **Contexto:** Los endpoints entre features dentro del monolith
76
+ necesitan type-safety sin overhead de serialización REST.
77
+
78
+ **Decisión:** Los endpoints internos en platform/http usarán tRPC.
79
+ Los endpoints públicos siguen siendo REST con OpenAPI.
80
+
81
+ **Consecuencias:** Type-safety extremo entre features pero
82
+ acoplamiento a tRPC en la capa de plataforma.
83
+ ```
84
+
85
+ ### ADR de Excepción (para decisiones que violan una regla de Forge)
86
+
87
+ ```md
88
+ # 0023 — Ignorar R1 temporalmente en ReportEngine
89
+
90
+ **Estado:** Accepted
91
+ **Fecha:** 2026-06-15
92
+ **Expira:** 2026-09-15
93
+
94
+ **Contexto:** ReportEngine necesita acceso directo a datos de infra
95
+ para generar reportes en tiempo real. Extraer a servicio separado
96
+ requiere 3 sprints.
97
+
98
+ **Decisión:** Se permite `feature/reports → infra/prisma` como
99
+ excepción temporal, documentada en ADR y con `forge-ignore: R1`
100
+ en los imports afectados.
101
+
102
+ **Consecuencias:** Degradación arquitectónica controlada.
103
+ Se revertirá antes de la expiración.
104
+ ```
105
+
106
+ ---
107
+
108
+ ## Estados de un ADR
109
+
110
+ ```mermaid
111
+ stateDiagram-v2
112
+ [*] --> Proposed
113
+ Proposed --> Accepted
114
+ Proposed --> Rejected
115
+ Accepted --> Amended
116
+ Accepted --> Superseded
117
+ Accepted --> Deprecated
118
+ Deprecated --> [*]
119
+ Superseded --> [*]
120
+ Amended --> Accepted
121
+ ```
122
+
123
+ | Estado | Significado |
124
+ |---|---|
125
+ | **Proposed** | Propuesto, en discusión |
126
+ | **Accepted** | Aprobado e implementado |
127
+ | **Rejected** | Descartado, se preserva para no repetir |
128
+ | **Deprecated** | Ya no se aplica, pero sigue vigente para sistemas existentes |
129
+ | **Superseded** | Reemplazado por otro ADR |
130
+ | **Amended** | Modificado parcialmente, el ADR original + amendment |
131
+
132
+ ---
133
+
134
+ ## Integración con Forge
135
+
136
+ ### ADRs y ARCHITECTURE.md
137
+
138
+ `forge inscribe` debe incluir en ARCHITECTURE.md:
139
+
140
+ ```md
141
+ ## Architecture Decision Records
142
+
143
+ | ADR | Título | Estado | Fecha |
144
+ |---|---|---|---|
145
+ | 0001 | Adoptar Prisma como ORM | Accepted | 2025-12-01 |
146
+ | 0002 | Schemas separados por feature | Accepted | 2025-12-10 |
147
+ | 0003 | Event Bus asíncrono con RabbitMQ | Proposed | 2026-01-15 |
148
+ | 0004 | Feature Splits de Catalog a Search | Superseded por 0007 | 2026-02-01 |
149
+ ```
150
+
151
+ ### ADRs y `forge assay`
152
+
153
+ El ensayo multi-persona (`assay`) debe considerar ADRs como fuente de información:
154
+
155
+ - **Bezos**: evalúa si la decisión es reversible o irreversible
156
+ - **Fowler**: evalúa la evolución de la decisión en el tiempo
157
+ - **Arquitecta Senior**: evalúa consistencia con el modelo arquitectónico
158
+
159
+ ### ADRs y reglas inline ignore
160
+
161
+ Cuando se usa `forge-ignore` para excepcionar una regla, debe referenciar el ADR que la autoriza:
162
+
163
+ ```ts
164
+ // forge-ignore: R1 — ver ADR-0023
165
+ import { PrismaClient } from "../../infra/prisma/client";
166
+ ```
167
+
168
+ ### ADRs y `forge quench`
169
+
170
+ `forge quench` debe verificar que:
171
+ - Los ADRs aceptados tienen su decisión implementada
172
+ - Los ADRs con expiración no han vencido sin renovación
173
+ - No hay `forge-ignore` sin ADR asociado
174
+
175
+ ---
176
+
177
+ ## Cuándo escribir un ADR
178
+
179
+ | Situación | Ejemplo | ADR necesario |
180
+ |---|---|---|
181
+ | Decisión fundacional | Framework, ORM, BD, message broker | ✅ Obligatorio |
182
+ | Patrón arquitectónico | CQRS, Event Sourcing, Sagas | ✅ Recomendado |
183
+ | Cambio de regla de Forge | Ignorar R8 entre dos features | ✅ Obligatorio |
184
+ | Tecnología nueva | Adoptar Redis, Elasticsearch | ✅ Recomendado |
185
+ | Estándar de equipo | Formato de commits, naming conventions | ✅ Ligero |
186
+ | Excepción temporal | Ignorar R1 por 3 sprints | ✅ Obligatorio |
187
+ | Cambio de provider | Migrar de AWS a GCP | ✅ Obligatorio |
188
+ | Refactor mayor | Extraer Catalog como microservicio | ✅ Obligatorio |
189
+ | Dependencia externa | Adoptar librería X | ⚠️ Si tiene impacto arquitectónico |
190
+ | Bugfix complejo | Cambio en algoritmo de pricing | ❌ |
191
+
192
+ ---
193
+
194
+ ## Estructura de directorios
195
+
196
+ ```
197
+ docs/
198
+ adr/
199
+ 0001-adopt-prisma-orm.md
200
+ 0002-separate-schemas-per-feature.md
201
+ 0003-event-bus-rabbitmq.md
202
+ README.md ← index de ADRs activos
203
+ ```
204
+
205
+ El `README.md` se genera automáticamente listando los ADRs activos:
206
+
207
+ ```bash
208
+ # scripts/forge-adr.mjs (futuro)
209
+ node scripts/forge-adr.mjs list # lista todos los ADRs
210
+ node scripts/forge-adr.mjs new # crea nuevo ADR desde template
211
+ node scripts/forge-adr.mjs status # cambia estado de un ADR
212
+ ```
213
+
214
+ ---
215
+
216
+ ## Anti-patrones
217
+
218
+ | Anti-patrón | Problema | Solución |
219
+ |---|---|---|
220
+ | **ADR sin contexto** | "Usamos Prisma". Sin por qué ni alternativas. | Incluir motivación y alternativas consideradas siempre. |
221
+ | **ADR sin consecuencia** | "Adoptamos Kafka" sin decir el costo operativo. | Documentar consecuencias positivas, negativas y neutrales. |
222
+ | **ADRs que nadie lee** | Se escriben y se archivan. Nadie los consulta. | Integrar en `forge inscribe`. Mencionar en code review cuando aplica. |
223
+ | **ADR sobre tecnología obvia** | "Usamos TypeScript" como ADR. | No todo es ADR. Si no hay trade-off significativo, no es ADR. |
224
+ | **ADR sin fecha** | No se sabe cuándo se tomó ni si sigue vigente. | Fecha obligatoria. Estados para ciclo de vida. |
225
+ | **Demasiados ADRs** | Cada PR tiene un ADR. Se deja de leer. | Solo decisiones con impacto arquitectónico. No para implementación cotidiana. |
226
+
227
+ ---
228
+
229
+ ## Conexión con Forge
230
+
231
+ | Comando | Acción |
232
+ |---|---|
233
+ | `forge inscribe` | Incluye ADRs activos en ARCHITECTURE.md |
234
+ | `forge assay` | Usa ADRs como insumo para el ensayo multi-persona |
235
+ | `forge quench` | Verifica que ADRs aceptados están implementados |
236
+ | `forge inspect` | Reporta ADRs vencidos o sin implementar |
237
+ | `forge reforge` | Sugiere crear ADR cuando se detecta un cambio arquitectónico |
238
+
239
+ ## Ver también
240
+
241
+ - `reference/evolutionary-architecture.md` — fitness functions que los ADRs registran
242
+ - `reference/principles.md` — principios que los ADRs documentan como decisiones
@@ -0,0 +1,340 @@
1
+ # Anti-Corruption Layer (ACL)
2
+
3
+ Cuando un bounded context debe integrarse con otro que tiene un modelo distinto (especialmente legacy o externo), la ACL es la capa que traduce sin contaminar.
4
+
5
+ ---
6
+
7
+ ## Definición y Propósito
8
+
9
+ Una **Anti-Corruption Layer** es un mecanismo de traducción entre dos bounded contexts. Su función es evitar que el modelo de un sistema externo (o legacy) "corrompa" el modelo de dominio del sistema nuevo.
10
+
11
+ ```mermaid
12
+ flowchart LR
13
+ subgraph "Sistema Legacy"
14
+ LegacyModel[(BD Legacy)]
15
+ Customer[CustomerLegacy]
16
+ end
17
+ subgraph "ACL"
18
+ Translator[Traductor]
19
+ Gateway[Gateway]
20
+ end
21
+ subgraph "Sistema Nuevo (Forge)"
22
+ DomainModel[(BD Nueva)]
23
+ Service[Orders Service]
24
+ end
25
+ Customer --> Gateway
26
+ Gateway --> Translator
27
+ Translator --> Service
28
+ Service --> DomainModel
29
+ ```
30
+
31
+ **Cuándo usar ACL:**
32
+ - Integración con un sistema legacy que no puede modificarse (SAP, Salesforce, mainframe)
33
+ - Integración con un servicio externo cuyo modelo no controlas (API de terceros)
34
+ - Migración incremental donde ambos sistemas coexisten (Strangler Fig)
35
+ - Dos bounded contexts que evolucionan con ritmos distintos pero necesitan comunicarse
36
+
37
+ **Cuándo NO usar ACL:**
38
+ - El sistema externo expone un Published Language estable (usa el contrato directamente)
39
+ - El modelo externo es idéntico o trivialmente mapeable (un mapper simple basta)
40
+ - La integración es temporal y el sistema externo será reemplazado pronto
41
+
42
+ ---
43
+
44
+ ## Estructura de una ACL
45
+
46
+ La ACL vive en `adapter/out/` de la feature que necesita protegerse:
47
+
48
+ ```
49
+ src/features/orders/
50
+ domain/
51
+ entities/
52
+ OrderEntity.ts
53
+ CustomerEntity.ts ← modelo limpio
54
+ repositories/
55
+ ICustomerRepository.ts ← puerto que la ACL implementa
56
+ adapters/
57
+ out/
58
+ legacy-crm/
59
+ CustomerGateway.ts ← llama al sistema legacy
60
+ CustomerTranslator.ts ← traduce del modelo legacy al dominio
61
+ CustomerACL.ts ← facade que combina gateway + translator
62
+ persistence/
63
+ PostgresCustomerRepository.ts ← implementación normal
64
+ ```
65
+
66
+ ### Componentes de la ACL
67
+
68
+ | Componente | Responsabilidad |
69
+ |---|---|
70
+ | **Gateway** | Comunicación con el sistema externo (HTTP, SOAP, archivos, BD) |
71
+ | **Translator** | Mapeo del modelo externo al modelo de dominio y viceversa |
72
+ | **Facade** | Orquesta gateway + translator. Implementa la interfaz del puerto. |
73
+ | **Contract** | DTOs del modelo externo (viven en la ACL, no contaminan el dominio) |
74
+
75
+ ---
76
+
77
+ ## Implementación
78
+
79
+ ### 1. Definir el puerto (dominio)
80
+
81
+ ```ts
82
+ // src/features/orders/domain/repositories/ICustomerRepository.ts
83
+ export interface ICustomerRepository {
84
+ findById(id: string): Promise<CustomerEntity | null>;
85
+ findByEmail(email: string): Promise<CustomerEntity | null>;
86
+ }
87
+ ```
88
+
89
+ ### 2. Gateway (comunicación con externo)
90
+
91
+ ```ts
92
+ // src/features/orders/adapters/out/legacy-crm/CustomerGateway.ts
93
+ // Este archivo es infra y puede depender de HTTP clients, SOAP, etc.
94
+
95
+ import { LegacyCustomerDTO } from "./LegacyCustomerDTO";
96
+
97
+ export class LegacyCRMGateway {
98
+ private readonly baseUrl: string;
99
+
100
+ constructor(baseUrl: string) {
101
+ this.baseUrl = baseUrl;
102
+ }
103
+
104
+ async fetchCustomer(crmId: string): Promise<LegacyCustomerDTO> {
105
+ const response = await fetch(`${this.baseUrl}/api/customers/${crmId}`, {
106
+ headers: { Authorization: `Bearer ${this.apiKey}` },
107
+ });
108
+ if (!response.ok) throw new Error(`CRM error: ${response.statusText}`);
109
+ return response.json();
110
+ }
111
+ }
112
+ ```
113
+
114
+ ### 3. Translator (traducción)
115
+
116
+ ```ts
117
+ // src/features/orders/adapters/out/legacy-crm/CustomerTranslator.ts
118
+ // Este archivo es PURO. No depende de infraestructura. Solo transforma datos.
119
+
120
+ import { LegacyCustomerDTO } from "./LegacyCustomerDTO";
121
+ import { CustomerEntity } from "../../../domain/entities/CustomerEntity";
122
+
123
+ export class CustomerTranslator {
124
+ toDomain(dto: LegacyCustomerDTO): CustomerEntity {
125
+ return new CustomerEntity({
126
+ id: dto.customerId,
127
+ name: `${dto.firstName} ${dto.lastName}`,
128
+ email: dto.emailAddress,
129
+ status: this.mapStatus(dto.active),
130
+ createdAt: new Date(dto.registrationDate),
131
+ });
132
+ }
133
+
134
+ private mapStatus(legacyActive: boolean): "active" | "inactive" {
135
+ return legacyActive ? "active" : "inactive";
136
+ }
137
+ }
138
+ ```
139
+
140
+ ### 4. Facade ACL (implementación del puerto)
141
+
142
+ ```ts
143
+ // src/features/orders/adapters/out/legacy-crm/CustomerACL.ts
144
+ // Implementa ICustomerRepository traduciendo desde el CRM legacy.
145
+
146
+ import { ICustomerRepository } from "../../../domain/repositories/ICustomerRepository";
147
+ import { CustomerEntity } from "../../../domain/entities/CustomerEntity";
148
+ import { LegacyCRMGateway } from "./CustomerGateway";
149
+ import { CustomerTranslator } from "./CustomerTranslator";
150
+
151
+ export class LegacyCRMRepository implements ICustomerRepository {
152
+ constructor(
153
+ private readonly gateway: LegacyCRMGateway,
154
+ private readonly translator: CustomerTranslator,
155
+ ) {}
156
+
157
+ async findById(id: string): Promise<CustomerEntity | null> {
158
+ try {
159
+ const dto = await this.gateway.fetchCustomer(id);
160
+ return this.translator.toDomain(dto);
161
+ } catch (error) {
162
+ // El gateway lanza errores técnicos. La ACL traduce a errores de dominio.
163
+ if (error.message.includes("404")) return null;
164
+ throw new Error("CustomerRepository.fetchFailed");
165
+ }
166
+ }
167
+
168
+ async findByEmail(email: string): Promise<CustomerEntity | null> {
169
+ // Si el CRM legacy no soporta búsqueda por email, la ACL puede:
170
+ // 1. Obtener todos los clientes y filtrar (ineficiente)
171
+ // 2. Cachear localmente y buscar en caché
172
+ // 3. Fallar con error explícito
173
+ throw new Error("CustomerRepository.methodNotSupported");
174
+ }
175
+ }
176
+ ```
177
+
178
+ ### 5. Registro en DI
179
+
180
+ ```ts
181
+ // En la composición root (platform/di/container.ts)
182
+ container.registerSingleton<ICustomerRepository>("ICustomerRepository", {
183
+ useClass: process.env.USE_LEGACY_CRM === "true"
184
+ ? LegacyCRMRepository // ACL
185
+ : PostgresCustomerRepository, // implementación nativa
186
+ });
187
+ ```
188
+
189
+ La DI permite switchear entre legacy y nativo sin cambiar el caso de uso.
190
+
191
+ ---
192
+
193
+ ## Strangler Fig Pattern
194
+
195
+ El Strangler Fig permite migrar de un sistema legacy a uno nuevo incrementalmente, usando ACL como fachada.
196
+
197
+ ### Fase 1: ACL intercepta todo
198
+
199
+ ```
200
+ [API Gateway] → [ACL] → [Legacy CRM]
201
+ ```
202
+
203
+ Todas las llamadas pasan por la ACL que habla con el legacy.
204
+
205
+ ### Fase 2: Rutas individuales migradas
206
+
207
+ ```ts
208
+ // La ACL decide por ruta/característica si va al legacy o al nuevo
209
+ export class CustomerRouterACL implements ICustomerRepository {
210
+ constructor(
211
+ private readonly legacy: LegacyCRMRepository,
212
+ private readonly newRepo: PostgresCustomerRepository,
213
+ private readonly migrationFlag: FeatureFlagService,
214
+ ) {}
215
+
216
+ async findById(id: string): Promise<CustomerEntity | null> {
217
+ if (this.migrationFlag.isEnabled("customer-read")) {
218
+ return await this.newRepo.findById(id);
219
+ }
220
+ return await this.legacy.findById(id);
221
+ }
222
+ }
223
+ ```
224
+
225
+ ### Fase 3: Legacy retirado
226
+
227
+ ```
228
+ [API Gateway] → [PostgresCustomerRepository]
229
+ ```
230
+
231
+ La ACL ya no es necesaria. Se elimina el gateway, el translator y la facade.
232
+
233
+ ### Reglas para Strangler Fig exitoso
234
+
235
+ 1. **Un feature completo se migra a la vez**, no funciones sueltas
236
+ 2. **La ACL nunca modifica el legacy**. Solo lee y traduce
237
+ 3. **Feature flags** para activar/desactivar rutas migradas
238
+ 4. **Métricas de comparación**: response time, error rate, data consistency entre legacy y nuevo
239
+ 5. **Rollback plan**: desactivar el feature flag vuelve al legacy instantáneamente
240
+
241
+ ---
242
+
243
+ ## Conexión con el Modelo de Forge
244
+
245
+ ### Reglas de dependencia y ACL
246
+
247
+ | Regla | Relación con ACL |
248
+ |---|---|
249
+ | **R1** (feature → infra) | La ACL está en `adapter/out/` y accede a infra (el gateway). Está permitido porque el adapter es el puerto de salida. El dominio nunca ve infra. |
250
+ | **R7** (infra → feature) | El gateway externo llama a la ACL (infra → adapter). No viola R7 porque el adapter recibe la llamada y la traduce al dominio. |
251
+ | **R8** (cross-feature) | Si la ACL se comunica con otra feature de Forge, debe hacerlo mediante eventos o contratos en shared/, no con imports directos. |
252
+
253
+ ### La ACL en la arquitectura de 4 capas
254
+
255
+ ```
256
+ ┌─────────────────────────────────────────────────┐
257
+ │ features/orders/ │
258
+ │ domain/ ← modelo protegido │
259
+ │ CustomerEntity │
260
+ │ ICustomerRepository ← puerto │
261
+ │ adapters/out/ │
262
+ │ legacy-crm/ ← ACL completa │
263
+ │ CustomerGateway ← infra (llama externo) │
264
+ │ CustomerTranslator ← pure translation │
265
+ │ CustomerACL ← facade │
266
+ │ persistence/ │
267
+ │ PostgresCustomerRepository │
268
+ └─────────────────────────────────────────────────┘
269
+
270
+ ▼ (gateway)
271
+ ┌──────────────────────┐
272
+ │ CRM Legacy (externo) │
273
+ │ /api/customers/:id │
274
+ └──────────────────────┘
275
+ ```
276
+
277
+ ### Dónde NO poner la ACL
278
+
279
+ | Ubicación incorrecta | Problema |
280
+ |---|---|
281
+ | `src/shared/acl/` | La ACL es específica de un feature, no compartida. Cada feature tiene su propia ACL. |
282
+ | `src/infra/` | La ACL no es infraestructura genérica. Pertenece al adapter del feature. |
283
+ | `src/platform/` | Platform no debe saber qué sistemas externos existen. |
284
+
285
+ ---
286
+
287
+ ## Anti-patrones
288
+
289
+ | Anti-patrón | Problema | Solución |
290
+ |---|---|---|
291
+ | **Leaky Abstraction** | La ACL pasa DTOs del sistema externo en vez de traducir. El dominio termina dependiendo del modelo legacy. | El Translator debe mapear TODOS los campos. Si falta uno, el dominio no sabe que existe. |
292
+ | **Pasamanos (passthrough)** | La ACL no traduce nada. Solo delega. Es una capa que añade complejidad sin valor. | Si no hay traducción que hacer, no uses ACL. Usa el contrato directamente. |
293
+ | **ACL que escribe en el legacy** | La ACL muta el sistema externo para "facilitar" la integración. | La ACL solo traduce. Si necesitas escribir, el legacy debe exponer API de escritura. |
294
+ | **ACL compartida** | Una sola ACL para todos los features. | Cada feature tiene su propia ACL adaptada a su modelo de dominio. |
295
+ | **Gateway sin timeout** | Una llamada al legacy lenta bloquea todo el feature. | Timeouts, circuit breakers y fallbacks obligatorios en el gateway. |
296
+ | **ACL sin test** | No se puede verificar que la traducción es correcta. | Tests unitarios del Translator + tests de integración del Gateway. |
297
+
298
+ ### Testing de ACL
299
+
300
+ ```ts
301
+ // Test unitario del Translator (puro, sin infraestructura)
302
+ describe("CustomerTranslator", () => {
303
+ it("traduce LegacyCustomerDTO a CustomerEntity", () => {
304
+ const dto: LegacyCustomerDTO = {
305
+ customerId: "123",
306
+ firstName: "John",
307
+ lastName: "Doe",
308
+ emailAddress: "john@example.com",
309
+ active: true,
310
+ registrationDate: "2024-01-15T10:00:00Z",
311
+ };
312
+ const result = new CustomerTranslator().toDomain(dto);
313
+ expect(result.id).toBe("123");
314
+ expect(result.status).toBe("active");
315
+ });
316
+
317
+ it("mapea active=false a status=inactive", () => {
318
+ const dto = { ...baseDTO, active: false };
319
+ const result = new CustomerTranslator().toDomain(dto);
320
+ expect(result.status).toBe("inactive");
321
+ });
322
+ });
323
+ ```
324
+
325
+ ---
326
+
327
+ ## Conexión con Forge
328
+
329
+ | Comando | Acción |
330
+ |---|---|
331
+ | `forge cast` | Durante el shape de la feature, detectar si necesita ACL |
332
+ | `forge inspect` | Detecta patrones que indican necesidad de ACL (imports directos a infra externa) |
333
+ | `forge relocate` | Puede generar ACL automática al migrar un feature desde legacy |
334
+ | `forge reforge` | Sugiere introducir ACL cuando detecta leaky abstractions |
335
+
336
+ ## Ver también
337
+
338
+ - `reference/bounded-contexts.md` — contexts que la ACL aísla
339
+ - `reference/modular-monolith.md` — migración con ACL entre módulos
340
+ - `reference/relocate.md` — uso de ACL durante migración legacy
@@ -60,3 +60,10 @@ export interface IPaginatedResponse<T> {
60
60
  - Errores normalizados: `{ error: { code, message, details? } }`
61
61
  - Idempotencia en POST con `Idempotency-Key` header
62
62
  - HATEOAS opcional, solo para APIs hipermedia
63
+
64
+ ## Ver también
65
+
66
+ - `reference/api-versioning.md` — versionado de APIs y deprecación
67
+ - `reference/idempotency.md` — idempotency keys en POST
68
+ - `reference/security-patterns.md` — AuthN/AuthZ, rate limiting, validación
69
+ - `reference/testing-patterns.md` — tests de controllers y endpoints