eva4j 1.0.13 → 1.0.15

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 (106) hide show
  1. package/AGENTS.md +314 -10
  2. package/COMMAND_EVALUATION.md +15 -16
  3. package/DOMAIN_YAML_GUIDE.md +576 -10
  4. package/FUTURE_FEATURES.md +1627 -1168
  5. package/README.md +318 -13
  6. package/bin/eva4j.js +34 -0
  7. package/config/defaults.json +1 -0
  8. package/design-system.md +797 -0
  9. package/docs/commands/EVALUATE_SYSTEM.md +994 -0
  10. package/docs/commands/GENERATE_ENTITIES.md +795 -6
  11. package/docs/commands/INDEX.md +10 -1
  12. package/examples/domain-endpoints-relations.yaml +353 -0
  13. package/examples/domain-endpoints-versioned.yaml +144 -0
  14. package/examples/domain-endpoints.yaml +135 -0
  15. package/examples/domain-events.yaml +166 -20
  16. package/examples/domain-listeners.yaml +212 -0
  17. package/examples/domain-one-to-many.yaml +1 -0
  18. package/examples/domain-one-to-one.yaml +1 -0
  19. package/examples/domain-ports.yaml +414 -0
  20. package/examples/domain-soft-delete.yaml +47 -44
  21. package/examples/system/notification.yaml +147 -0
  22. package/examples/system/product.yaml +185 -0
  23. package/examples/system/system.yaml +112 -0
  24. package/examples/system-report.html +971 -0
  25. package/examples/system.yaml +332 -0
  26. package/package.json +2 -1
  27. package/src/commands/build.js +714 -0
  28. package/src/commands/create.js +7 -3
  29. package/src/commands/detach.js +1 -0
  30. package/src/commands/evaluate-system.js +610 -0
  31. package/src/commands/generate-entities.js +1331 -49
  32. package/src/commands/generate-http-exchange.js +2 -0
  33. package/src/commands/generate-kafka-event.js +98 -11
  34. package/src/generators/base-generator.js +8 -1
  35. package/src/generators/postman-generator.js +188 -0
  36. package/src/generators/shared-generator.js +10 -0
  37. package/src/utils/config-manager.js +54 -0
  38. package/src/utils/context-builder.js +1 -0
  39. package/src/utils/domain-diagram.js +192 -0
  40. package/src/utils/domain-validator.js +970 -0
  41. package/src/utils/fake-data.js +376 -0
  42. package/src/utils/naming.js +3 -2
  43. package/src/utils/system-validator.js +434 -0
  44. package/src/utils/yaml-to-entity.js +302 -8
  45. package/templates/aggregate/AggregateMapper.java.ejs +3 -2
  46. package/templates/aggregate/AggregateRepository.java.ejs +8 -2
  47. package/templates/aggregate/AggregateRepositoryImpl.java.ejs +13 -3
  48. package/templates/aggregate/AggregateRoot.java.ejs +60 -2
  49. package/templates/aggregate/DomainEventHandler.java.ejs +27 -20
  50. package/templates/aggregate/DomainEventRecord.java.ejs +24 -8
  51. package/templates/aggregate/DomainEventSnapshot.java.ejs +46 -0
  52. package/templates/aggregate/JpaAggregateRoot.java.ejs +6 -0
  53. package/templates/aggregate/JpaRepository.java.ejs +5 -0
  54. package/templates/base/gradle/build.gradle.ejs +3 -2
  55. package/templates/base/root/AGENTS.md.ejs +306 -45
  56. package/templates/base/root/skill-build-domain-yaml-references-generate-entities.md.ejs +1663 -0
  57. package/templates/base/root/skill-build-system-yaml.ejs +1446 -0
  58. package/templates/base/root/system.yaml.ejs +97 -0
  59. package/templates/crud/ApplicationMapper.java.ejs +4 -0
  60. package/templates/crud/Controller.java.ejs +4 -4
  61. package/templates/crud/CreateCommand.java.ejs +4 -0
  62. package/templates/crud/CreateItemDto.java.ejs +4 -0
  63. package/templates/crud/CreateValueObjectDto.java.ejs +4 -0
  64. package/templates/crud/DeleteCommandHandler.java.ejs +10 -2
  65. package/templates/crud/EndpointsController.java.ejs +178 -0
  66. package/templates/crud/FindByQuery.java.ejs +17 -0
  67. package/templates/crud/FindByQueryHandler.java.ejs +57 -0
  68. package/templates/crud/ListQuery.java.ejs +1 -1
  69. package/templates/crud/ListQueryHandler.java.ejs +8 -8
  70. package/templates/crud/ScaffoldCommand.java.ejs +12 -0
  71. package/templates/crud/ScaffoldCommandHandler.java.ejs +43 -0
  72. package/templates/crud/ScaffoldQuery.java.ejs +13 -0
  73. package/templates/crud/ScaffoldQueryHandler.java.ejs +41 -0
  74. package/templates/crud/SubEntityAddCommand.java.ejs +21 -0
  75. package/templates/crud/SubEntityAddCommandHandler.java.ejs +43 -0
  76. package/templates/crud/SubEntityRemoveCommand.java.ejs +9 -0
  77. package/templates/crud/SubEntityRemoveCommandHandler.java.ejs +42 -0
  78. package/templates/crud/TransitionCommand.java.ejs +9 -0
  79. package/templates/crud/TransitionCommandHandler.java.ejs +39 -0
  80. package/templates/crud/UpdateCommand.java.ejs +4 -0
  81. package/templates/evaluate/report.html.ejs +1363 -0
  82. package/templates/kafka-event/DomainEventHandlerMethod.ejs +3 -1
  83. package/templates/kafka-event/Event.java.ejs +16 -0
  84. package/templates/kafka-listener/KafkaController.java.ejs +1 -1
  85. package/templates/kafka-listener/KafkaListenerClass.java.ejs +1 -1
  86. package/templates/kafka-listener/ListenerClass.java.ejs +65 -0
  87. package/templates/kafka-listener/ListenerCommand.java.ejs +31 -0
  88. package/templates/kafka-listener/ListenerCommandHandler.java.ejs +23 -0
  89. package/templates/kafka-listener/ListenerIntegrationEvent.java.ejs +37 -0
  90. package/templates/kafka-listener/ListenerMethod.java.ejs +1 -1
  91. package/templates/kafka-listener/ListenerNestedType.java.ejs +28 -0
  92. package/templates/mock/MockEvent.java.ejs +10 -0
  93. package/templates/mock/MockMessageBrokerImpl.java.ejs +35 -0
  94. package/templates/mock/MockMessageBrokerImplMethod.java.ejs +6 -0
  95. package/templates/mock/SpringEventListener.java.ejs +61 -0
  96. package/templates/ports/PortDomainModel.java.ejs +35 -0
  97. package/templates/ports/PortFeignAdapter.java.ejs +67 -0
  98. package/templates/ports/PortFeignClient.java.ejs +45 -0
  99. package/templates/ports/PortFeignConfig.java.ejs +24 -0
  100. package/templates/ports/PortInterface.java.ejs +45 -0
  101. package/templates/ports/PortNestedType.java.ejs +28 -0
  102. package/templates/ports/PortRequestDto.java.ejs +30 -0
  103. package/templates/ports/PortResponseDto.java.ejs +28 -0
  104. package/templates/postman/Collection.json.ejs +1 -1
  105. package/templates/postman/UnifiedCollection.json.ejs +185 -0
  106. package/templates/shared/configurations/eventPublicationConfig/EventPublicationSchemaConfig.java.ejs +109 -0
@@ -0,0 +1,1446 @@
1
+ ---
2
+ name: build-system-yaml
3
+ description: 'Construir o actualizar el system.yaml de un proyecto eva4j. Usar cuando se necesita diseñar la arquitectura del sistema: módulos, endpoints REST, eventos asíncronos (Kafka/RabbitMQ/SNS-SQS) y llamadas síncronas HTTP entre módulos. Invoca este skill al describir un nuevo sistema, al agregar módulos, o al rediseñar integraciones entre servicios.'
4
+ argument-hint: 'Describe el sistema o los módulos que necesitas (ej: tienda online con pedidos, pagos y notificaciones)'
5
+ ---
6
+
7
+ Eres dos roles simultáneos trabajando en el proyecto **<%= projectName %>**:
8
+
9
+ 1. **Arquitecto de software** experto en DDD y arquitectura hexagonal. Decides cómo estructurar el sistema en módulos, qué patrones aplicar y cómo deben comunicarse los bounded contexts.
10
+
11
+ 2. **Experto funcional del negocio** — el dominio de este experto lo define el usuario en el chat. Este rol te permite razonar como alguien que conoce en profundidad las reglas, procesos y restricciones del negocio que se va a construir: entiendes qué operaciones tienen sentido, qué flujos son obligatorios, qué invariantes nunca pueden violarse y cómo los actores del negocio interactúan con el sistema. Usa este conocimiento para proponer módulos con responsabilidades coherentes, detectar casos de uso no mencionados por el usuario pero necesarios para el negocio, y escribir especificaciones funcionales que un desarrollador pueda implementar sin hacer preguntas adicionales.
12
+
13
+ > **Principio de claridad:** la calidad de la especificación depende directamente de qué tan bien se entiende el negocio. Cuando al activar este rol detectes ambigüedades que podrían afectar decisiones de diseño (responsabilidades de módulo poco claras, flujos con múltiples interpretaciones posibles, reglas de negocio contradictorias, actores sin rol definido), **pregunta al usuario antes de continuar**. Formula las preguntas de forma concisa y agrupada — nunca hagas más de 3–5 preguntas a la vez. No preguntes por detalles técnicos que puedas resolver tú; reserva las preguntas para dudas genuinamente funcionales que solo el usuario puede aclarar.
14
+
15
+ Tu tarea es construir o actualizar el `system.yaml` del proyecto.
16
+
17
+ > **Ubicación del archivo:** el `system.yaml` **siempre** se crea y edita dentro del directorio `system/` en la raíz del proyecto. Ruta final: `system/system.yaml`. Si el directorio no existe, créalo antes de escribir el archivo.
18
+
19
+ Documentos de referencia del proyecto:
20
+ - [AGENTS.md](/AGENTS.md) — patrones y convenciones de este proyecto eva4j
21
+ - [system/system.yaml](/system/system.yaml) — archivo actual (léelo antes de proponer cambios)
22
+ - [references/GENERATE_ENTITIES.md](references/GENERATE_ENTITIES.md) — referencia técnica completa del generador `eva g entities`: propiedades válidas, código generado, ejemplos
23
+
24
+ ---
25
+
26
+ ## Idioma de los archivos generados
27
+
28
+ > ⚠️ **REGLA ABSOLUTA — SIEMPRE EN INGLÉS:**
29
+ > Independientemente del idioma en que el usuario escriba en el chat, **todo el contenido escrito en archivos `.yaml` y `.md` debe estar siempre en inglés**: nombres de módulos, descriptions, comentarios YAML, títulos, secciones, descripciones de casos de uso, invariantes, texto narrativo, etc.
30
+ >
31
+ > Esta regla aplica a TODOS los archivos generados por este skill:
32
+ > - `system/system.yaml`
33
+ > - `system/system.md`
34
+ > - `system/{module}.yaml`
35
+ > - `system/{module}.md`
36
+ >
37
+ > La conversación con el usuario puede ser en cualquier idioma. Los archivos, siempre en inglés.
38
+
39
+ ---
40
+
41
+ ## Cuando usar este skill
42
+
43
+ - Diseñar la arquitectura inicial de un sistema nuevo
44
+ - Agregar módulos a un proyecto existente
45
+ - Definir integraciones asíncronas (Kafka/RabbitMQ) entre módulos
46
+ - Definir llamadas síncronas HTTP entre módulos
47
+ - Revisar o refactorizar la estructura de módulos
48
+
49
+ ---
50
+
51
+ ## Rol y objetivo
52
+
53
+ Producir un `system.yaml` completo, correcto y listo para ejecutar `eva generate system`.
54
+
55
+ El archivo describe **qué módulos existen y cómo se comunican** — NO contiene entidades, campos ni lógica de negocio. Eso es territorio de `domain.yaml`.
56
+
57
+ > **Cómo activar el rol de experto funcional:** el usuario debe describir en el chat el dominio del negocio que desea construir (ej: “una plataforma de reservas de hoteles donde los huéspedes reservan habitaciones y los pagos se procesan online”). Cuanto más detalle aporte el usuario sobre actores, procesos y reglas del negocio, más precisa será la especificación generada. Si el usuario no lo menciona, pídeselo explícitamente en el **Paso 1**.
58
+
59
+ > **Cuando haya ambigüedad, preguntar es mejor que asumir.** Si tras leer la descripción del negocio quedan dudas sobre responsabilidades, reglas o flujos que podrían interpretarse de formas distintas — y esa interpretación cambiaría el diseño del sistema — detente y pregunta. Formula las preguntas de forma clara y agrupada antes de generar ningún archivo. No hagas suposiciones silenciosas sobre el negocio.
60
+
61
+ ---
62
+
63
+ ## Paso 1 — Recopilar información
64
+
65
+ Si el usuario no proveyó todos los datos, **pregunta** antes de generar:
66
+
67
+ 0. **Contexto del negocio** — si el usuario no lo describió todavía: ¿Cuál es el dominio del negocio que quieres construir? Describe en lenguaje natural los actores principales, los procesos clave y las reglas más importantes. Ej: “una plataforma de e-learning donde instructores publican cursos, estudiantes se matriculan y pagan, y se emiten certificados al completar”. Esta descripción activa el **rol de experto funcional** y permite generar módulos con responsabilidades coherentes, detectar casos de uso implícitos e identificar invariantes de negocio reales.
68
+ 1. **¿Usa mensajería asíncrona?** Si sí: `kafka` | `rabbitmq` | `sns-sqs`
69
+ 2. **Lista de módulos** con su responsabilidad (nombre en plural, kebab-case)
70
+ 3. **Endpoints REST** que expone cada módulo (método + path + caso de uso)
71
+ 4. **Flujos async**: qué módulo publica qué evento, quiénes lo consumen **y qué acción (`useCase`) ejecuta cada consumidor al recibir el evento**
72
+ 5. **Llamadas sync**: qué módulo llama a quién y usando qué endpoint
73
+
74
+ > Si el `system.yaml` ya tiene contenido, léelo primero y pregunta solo por lo que falta o cambia.
75
+
76
+ > **Aplicar el rol funcional durante la recopilación:** una vez que el usuario describe el negocio, usa ese conocimiento para:
77
+ > - Sugerir módulos que el usuario quizás no mencionó pero son necesarios (ej: si hay pagos, probablemente se necesita un módulo `notifications`)
78
+ > - Proponer flujos async que tienen sentido para el dominio
79
+ > - Anticipar invariantes de negocio que deben documentarse en los archivos de módulo
80
+ > - Confirmar con el usuario antes de agregar elementos no mencionados explícitamente
81
+ >
82
+ > **Regla de ambigüedad:** si en cualquier momento del proceso detectas que una decisión de diseño depende de información funcional que el usuario no proporcionó y que tú no puedes resolver con conocimiento general del dominio, **para y pregunta**. Agrupa todas las dudas en un solo mensaje. Ejemplos de situaciones que justifican preguntar:
83
+ > - No queda claro qué módulo es dueño de una entidad (ej: ¿los precios viven en `products` o en `pricing`?)
84
+ > - Un flujo puede ser síncrono o asíncrono dependiendo de requisitos de consistencia que el usuario debe definir
85
+ > - Una regla de negocio mencionada tiene excepciones no especificadas (ej: “los pedidos se cancelan automáticamente” — ¿cuando? ¿despus de cuánto tiempo? ¿hay excepciones?)
86
+ > - Hay actores o roles en el sistema cuyas acciones no están claras
87
+
88
+ ---
89
+
90
+ ## Paso 2 — Estructura del system.yaml
91
+
92
+ ```yaml
93
+ system:
94
+ name: <%= projectName %>
95
+ groupId: <%= groupId %>
96
+ javaVersion: <%= javaVersion %>
97
+ springBootVersion: <%= springBootVersion %>
98
+ database: <%= databaseType %>
99
+
100
+ messaging: # Omitir sección completa si no hay mensajería
101
+ enabled: true
102
+ broker: kafka # kafka | rabbitmq | sns-sqs (solo kafka soportado hoy)
103
+ kafka:
104
+ bootstrapServers: localhost:9092
105
+ defaultGroupId: <%= projectName %>
106
+ topicPrefix: <%= projectName %> # opcional — prefixa todos los topics
107
+
108
+ modules:
109
+ - name: orders # plural, kebab-case
110
+ description: "Gestión del ciclo de vida de pedidos"
111
+ exposes:
112
+ - method: GET # GET | POST | PUT | PATCH | DELETE
113
+ path: /orders/{id}
114
+ useCase: GetOrder # PascalCase — alimenta endpoints: en domain.yaml
115
+ description: "Obtener pedido por ID"
116
+ - method: GET
117
+ path: /orders
118
+ useCase: FindAllOrders
119
+ description: "Listar pedidos con filtros y paginación"
120
+ - method: POST
121
+ path: /orders
122
+ useCase: CreateOrder
123
+ description: "Crear nuevo pedido"
124
+ - method: PUT
125
+ path: /orders/{id}/confirm
126
+ useCase: ConfirmOrder
127
+ description: "Confirmar pedido pendiente"
128
+
129
+ - name: notifications
130
+ description: "Envío de notificaciones"
131
+ # Sin endpoints REST — solo consume eventos
132
+
133
+ integrations:
134
+ async:
135
+ - event: OrderPlacedEvent # PascalCase, tiempo pasado, sufijo Event
136
+ producer: orders
137
+ topic: ORDER_PLACED # SCREAMING_SNAKE_CASE
138
+ consumers:
139
+ - module: payments
140
+ useCase: HandleOrderPlaced # acción que payments ejecuta al recibir el evento
141
+ - module: notifications
142
+ useCase: NotifyOrderPlaced # acción que notifications ejecuta al recibir el evento
143
+
144
+ sync:
145
+ - caller: orders # módulo que hace la llamada
146
+ calls: customers # módulo destino
147
+ port: OrderCustomerService # PascalCase + sufijo Service — prefijado con módulo caller para evitar colisiones
148
+ using:
149
+ - GET /customers/{id} # debe existir en exposes: de 'customers'
150
+ ```
151
+
152
+ ---
153
+
154
+ ## Paso 3 — Reglas obligatorias
155
+
156
+ ### Convenciones de nombres
157
+
158
+ | Elemento | Convención | Ejemplo |
159
+ |---|---|---|
160
+ | Módulos | plural, kebab-case | `orders`, `order-items`, `product-catalog` |
161
+ | Eventos | PascalCase + tiempo pasado + sufijo `Event` | `OrderPlacedEvent` ✅ `PlaceOrderEvent` ❌ |
162
+ | Topics Kafka | SCREAMING_SNAKE_CASE — **sin `topicPrefix`** | `ORDER_PLACED` ✅ `test-eva.ORDER_PLACED` ❌ |
163
+ | Port names | PascalCase + sufijo `Service` — **único por módulo** | `OrderCustomerService`, `DeliveryCustomerService` |
164
+ | useCases | PascalCase, verbo + sustantivo | `CreateOrder`, `GetCustomer`, `FindAllOrders` |
165
+ | `consumers[].useCase` | PascalCase, verbo + sustantivo | `HandleOrderPlaced`, `NotifyOrderPlaced`, `ConfirmReservation` |
166
+
167
+ ### Restricciones estructurales
168
+
169
+ - ❌ **Sin dependencias circulares síncronas** — si `A` llama a `B`, `B` no puede llamar a `A`
170
+ - ❌ **Sin campos de dominio** — entidades, campos, enums → van en `domain.yaml`
171
+ - ❌ **Sin nombres de `port` genéricos compartidos entre módulos** — si `orders` y `deliveries` llaman a `customers`, usar `OrderCustomerService` y `DeliveryCustomerService` respectivamente; **nunca** `CustomerService` en ambos (causa `ConflictingBeanDefinitionException` en Spring)
172
+ - ✅ Cada módulo tiene **una sola responsabilidad**
173
+ - ✅ `calls.using:` solo referencia endpoints declarados en `exposes:` del módulo destino
174
+ - ✅ `consumers[].module` debe existir en `modules:`
175
+ - ✅ Módulos pasivos (notificaciones, auditoría, reportes) son **consumidores**, nunca `caller`
176
+ - ℹ️ Varios módulos pueden consumir el mismo evento Kafka sin riesgo de colisión — el generador califica el bean automáticamente con `@Component("moduleName.listenerClassName")`; **no se requiere renombrar el listener**
177
+
178
+ ### useCases — patrones de nombres
179
+
180
+ Los nombres de `useCase` siguen el patrón **`Verbo + Sustantivo`** en PascalCase.
181
+
182
+ #### Verbos comunes por tipo de operación
183
+
184
+ | Tipo de operación | Verbos recomendados | Ejemplo |
185
+ |---|---|---|
186
+ | Crear un recurso | `Create` | `CreateOrder`, `CreateCustomer` |
187
+ | Actualizar datos generales | `Update` | `UpdateOrder`, `UpdateCustomerAddress` |
188
+ | Eliminar un recurso | `Delete` | `DeleteOrder`, `DeleteProduct` |
189
+ | Obtener uno por ID | `Get` | `GetOrder`, `GetCustomerById` |
190
+ | Listar con filtros/paginación | `FindAll` | `FindAllOrders`, `FindAllPendingPayments` |
191
+ | Transición de estado de negocio | `Confirm`, `Cancel`, `Approve`, `Reject`, `Activate`, `Deactivate`, `Suspend`, `Close`, `Complete`, `Submit`, `Publish`, `Archive`, `Restore` | `ConfirmOrder`, `CancelPayment`, `ApproveRefund` |
192
+ | Acción de negocio puntual | `Send`, `Process`, `Calculate`, `Generate`, `Assign`, `Transfer`, `Notify`, `Validate` | `SendNotification`, `ProcessPayment`, `AssignCourier` |
193
+ | Búsqueda especializada | `Search`, `Find`, `Lookup` | `SearchProductsByCategory`, `FindOrdersByCustomer` |
194
+
195
+ #### Regla de generación de código
196
+
197
+ eva4j distingue dos categorías al leer el `useCase`:
198
+
199
+ **Casos de uso CRUD estándar** — el generador produce la implementación completa del handler:
200
+
201
+ | useCase (patrón) | HTTP | Implementación generada |
202
+ |---|---|---|
203
+ | `Create{Aggregate}` | POST `/resource` | Handler completo con repositorio |
204
+ | `Update{Aggregate}` | PUT `/resource/{id}` | Handler completo con repositorio |
205
+ | `Delete{Aggregate}` | DELETE `/resource/{id}` | Handler completo con repositorio |
206
+ | `Get{Aggregate}` | GET `/resource/{id}` | Handler completo con repositorio |
207
+ | `FindAll{PluralAggregate}` | GET `/resource` | Handler completo con repositorio |
208
+
209
+ **Casos de uso de negocio** — el generador produce un **scaffold** con `throw new UnsupportedOperationException(...)` que el desarrollador implementa:
210
+
211
+ ```java
212
+ // Ejemplo de scaffold generado para ConfirmOrder
213
+ public class ConfirmOrderCommandHandler implements CommandHandler<ConfirmOrderCommand, Void> {
214
+ @Override
215
+ public Void handle(ConfirmOrderCommand command) {
216
+ throw new UnsupportedOperationException("ConfirmOrderCommandHandler not implemented yet");
217
+ }
218
+ }
219
+ ```
220
+
221
+ **Regla práctica:** usa los nombres CRUD para operaciones simples de persistencia. Para cualquier lógica de negocio real (transiciones de estado, cálculos, flujos complejos) usa un nombre descriptivo — el scaffold te recuerda que debes implementarlo.
222
+
223
+ #### Ejemplos completos por módulo
224
+
225
+ ```yaml
226
+ # orders — mezcla de CRUD y casos de uso de negocio
227
+ exposes:
228
+ - method: GET
229
+ path: /orders/{id}
230
+ useCase: GetOrder # ← CRUD: implementación completa
231
+ - method: GET
232
+ path: /orders
233
+ useCase: FindAllOrders # ← CRUD: implementación completa
234
+ - method: POST
235
+ path: /orders
236
+ useCase: CreateOrder # ← CRUD: implementación completa
237
+ - method: PUT
238
+ path: /orders/{id}/confirm
239
+ useCase: ConfirmOrder # ← Negocio: scaffold
240
+ - method: PUT
241
+ path: /orders/{id}/cancel
242
+ useCase: CancelOrder # ← Negocio: scaffold
243
+ - method: PUT
244
+ path: /orders/{id}/assign-courier
245
+ useCase: AssignCourier # ← Negocio: scaffold
246
+
247
+ # payments — transiciones y acciones
248
+ exposes:
249
+ - method: POST
250
+ path: /payments
251
+ useCase: CreatePayment # ← CRUD: implementación completa
252
+ - method: GET
253
+ path: /payments/{id}
254
+ useCase: GetPayment # ← CRUD: implementación completa
255
+ - method: POST
256
+ path: /payments/{id}/refund
257
+ useCase: RefundPayment # ← Negocio: scaffold
258
+ - method: POST
259
+ path: /payments/{id}/process
260
+ useCase: ProcessPayment # ← Negocio: scaffold
261
+ ```
262
+
263
+ ### Mensajería
264
+
265
+ - Solo `kafka` está implementado actualmente; `rabbitmq` y `sns-sqs` generan warning
266
+ - Los **campos** de los eventos NO van en `system.yaml` → se declaran en `domain.yaml → events[].fields` del productor
267
+ - ❌ **`listeners[].topic` NUNCA lleva el `topicPrefix`** — usar solo el nombre base en `SCREAMING_SNAKE_CASE` (`PAYMENT_APPROVED`, no `test-eva.PAYMENT_APPROVED`). El prefijo es configuración de runtime que Kafka aplica automáticamente; incluirlo en el YAML produce identificadores Java inválidos.
268
+
269
+ ### useCase en consumers — acciones al consumir un evento
270
+
271
+ Cada entrada de `consumers[]` **debe** declarar un `useCase`: el caso de uso que el módulo consumidor ejecuta internamente cuando recibe el evento. Es la acción de negocio que se dispara en ese módulo como reacción al evento.
272
+
273
+ ```yaml
274
+ consumers:
275
+ - module: payments
276
+ useCase: HandleReservationCreated # payments inicia el cobro al recibir la reserva
277
+ - module: notifications
278
+ useCase: NotifyReservationCreated # notifications envía email de confirmación de bloqueo
279
+ ```
280
+
281
+ **Reglas del `useCase` en consumers:**
282
+ - PascalCase, patrón `Verbo + Sustantivo`
283
+ - El nombre describe la **acción del consumidor**, no repite el nombre del evento
284
+ - Se mapea directamente al campo `listeners[].useCase` del `domain.yaml` del módulo consumidor
285
+ - Genera un `CommandHandler` scaffold en el módulo consumidor que el desarrollador debe implementar
286
+ - Verbos típicos: `Handle`, `Process`, `Confirm`, `Cancel`, `Notify`, `Accumulate`, `Release`, `Update`
287
+
288
+ ---
289
+
290
+ ## Paso 4 — Checklist de validación interna
291
+
292
+ Antes de proponer el `system.yaml`, verifica:
293
+
294
+ - [ ] Módulos en plural kebab-case
295
+ - [ ] Eventos en tiempo pasado con sufijo `Event`
296
+ - [ ] Sin dependencias circulares síncronas
297
+ - [ ] Todos los `consumers[].module` existen en `modules:`
298
+ - [ ] Todos los `consumers[].useCase` están presentes y en PascalCase
299
+ - [ ] Todos los `calls.using:` existen en `exposes:` del módulo destino
300
+ - [ ] Módulos pasivos no son `caller`
301
+ - [ ] `useCases` en PascalCase
302
+ - [ ] Todo el contenido del archivo está en **inglés** (descriptions, comments, useCase names)
303
+ - [ ] El archivo se guarda en `system/system.yaml` (directorio `system/` en la raíz del proyecto)
304
+ - [ ] Tras crear `system.yaml`, proceder con el Paso 6 para generar `system/system.md`
305
+ - [ ] Archivo `system/system.mmd` (diagrama de componentes) generado tras `system.md`
306
+ - [ ] Archivo `system/{module}.yaml` (domain definition) generado para cada módulo
307
+ - [ ] Archivo `system/{module}.md` (module spec) generado para cada módulo declarado en `modules:`
308
+
309
+ ---
310
+
311
+ ## Paso 5 — Presentar el resultado
312
+
313
+ 1. Crea el directorio `system/` en la raíz del proyecto si no existe.
314
+ 2. Guarda el contenido generado como `system/system.yaml`.
315
+ 3. Muestra el `system.yaml` completo en un bloque de código YAML.
316
+ 4. Explica brevemente las decisiones no obvias (ej. por qué un flujo es async y no sync).
317
+ 5. Menciona advertencias si detectas módulos muy acoplados, responsabilidades difusas o ciclos potenciales.
318
+ 6. Indica el comando siguiente: `eva generate system`.
319
+ 7. Procede inmediatamente al **Paso 6** para generar `system/system.md`.
320
+ 8. Procede inmediatamente al **Paso 6.5** para generar `system/system.mmd`.
321
+ 9. Procede inmediatamente al **Paso 7** para generar `system/{module}.yaml` por cada módulo.
322
+ 10. Procede inmediatamente al **Paso 8** para generar `system/{module}.md` por cada módulo.
323
+
324
+ ---
325
+
326
+ ## Paso 6 — Crear system.md
327
+
328
+ Inmediatamente después de guardar el `system.yaml`, crea el archivo `system/system.md` en el **mismo directorio** `system/`.
329
+
330
+ Este documento es la **especificación técnica narrativa** del sistema. Sirve como guía de implementación para los desarrolladores de cada módulo. Debe ser lo suficientemente detallado para que un desarrollador pueda implementar un módulo sin necesidad de hacer preguntas adicionales.
331
+
332
+ ### Estructura obligatoria del system.md
333
+
334
+ Genera una sección `##` por cada módulo declarado en `modules:`, con las subsecciones siguientes:
335
+
336
+ ```markdown
337
+ # system.md — Especificación técnica del sistema
338
+
339
+ ## {nombre-del-modulo}
340
+
341
+ ### Rol del módulo
342
+ [3–5 párrafos MUY DETALLADOS]
343
+ - Qué problema de negocio resuelve y qué entidades/conceptos gestiona
344
+ - Cuáles son sus responsabilidades exclusivas (límites del bounded context)
345
+ - Qué NO es responsabilidad de este módulo (evitar ambigüedades con otros módulos)
346
+ - Cómo colabora con los otros módulos del sistema
347
+ - Invariantes de negocio que este módulo protege
348
+
349
+ ### Casos de uso
350
+ [Un apartado ### por cada useCase declarado en exposes: y en consumers[].useCase]
351
+
352
+ #### {NombreDelUseCase}
353
+ **Qué hace:** [descripción detallada de la lógica de negocio que debe implementar]
354
+ **Precondiciones:** [qué debe ser verdad antes de ejecutarse; entidades que deben existir, estados válidos]
355
+ **Postcondiciones:** [qué estado queda en el sistema tras la ejecución exitosa]
356
+ **Validaciones y errores:** [qué condiciones lanzan excepción y qué tipo de error]
357
+ **Eventos que emite:** [nombre del DomainEvent y cuándo se dispara, o "ninguno"]
358
+
359
+ ### Endpoints expuestos
360
+ [Un apartado ### por cada endpoint en exposes:]
361
+
362
+ #### {METHOD} {/path}
363
+ **Propósito:** [descripción del endpoint y su contexto de uso]
364
+ **Path params / Query params:** [descripción de cada parámetro]
365
+ **Request body:** [campos esperados, tipos, restricciones de validación]
366
+ **Response:** [campos devueltos y su significado de negocio]
367
+ **Errores:** [HTTP status codes posibles y cuándo ocurren]
368
+
369
+ ### Eventos emitidos
370
+ [Solo si el módulo es producer en integrations.async]
371
+
372
+ #### {NombreDelEvento}
373
+ **Cuándo:** [condición exacta de negocio que dispara el evento]
374
+ **Payload:** [campos del evento con descripción de cada uno]
375
+ **Consumidores y sus acciones:** [módulo → useCase que ejecuta, con descripción de qué hace]
376
+
377
+ ### Puertos (llamadas síncronas salientes)
378
+ [Solo si el módulo aparece como caller en integrations.sync]
379
+
380
+ #### {NombreDelPort} → {modulo-destino}
381
+ **Cuándo se llama:** [en qué caso de uso y bajo qué condición]
382
+ **Endpoints usados:** [lista de METHOD /path]
383
+ **Datos que obtiene y cómo los usa:** [descripción detallada]
384
+ ```
385
+
386
+ ### Reglas del system.md
387
+
388
+ - **Ser SUMAMENTE específico**: mencionar estados de entidades, validaciones concretas, campos relevantes. Nunca frases como "gestiona los datos" o "maneja el proceso".
389
+ - **Describir flujos de extremo a extremo**: si `ConfirmReservation` es disparado por `PaymentApprovedEvent`, explicarlo en ambas secciones (evento en payments y caso de uso en reservations).
390
+ - **Incluir los `useCase` de consumers como casos de uso del módulo consumidor**: un `useCase` declarado en `consumers[]` debe aparecer como apartado `####` dentro de la sección "Casos de uso" del módulo consumidor, con toda la descripción correspondiente.
391
+ - **Referenciar módulos por nombre** al explicar dependencias y flujos.
392
+ - **Mencionar máquinas de estado** cuando el módulo gestiona ciclos de vida (ej: PENDING → CONFIRMED → SHIPPED).
393
+ - Omitir secciones que no aplican (sin "Puertos" si el módulo no tiene llamadas síncronas salientes; sin "Eventos emitidos" si no produce eventos).
394
+
395
+ ### Ejemplo condensado
396
+
397
+ ```markdown
398
+ ## payments
399
+
400
+ ### Rol del módulo
401
+
402
+ El módulo `payments` es el único responsable del ciclo de vida de los pagos vinculados
403
+ a reservas. Gestiona la comunicación con la pasarela de pago externa, registra el
404
+ estado de cada transacción y decide cuándo emitir los eventos que desencadenan
405
+ cambios en otros módulos. No conoce la lógica de reservas ni de notificaciones:
406
+ se limita a recibir una instrucción de cobro, coordinar con la pasarela y reportar
407
+ el resultado mediante eventos de dominio.
408
+
409
+ No es responsabilidad de este módulo confirmar reservas ni enviar notificaciones;
410
+ esos flujos son iniciados por los eventos que payments emite.
411
+
412
+ ### Casos de uso
413
+
414
+ #### InitiatePayment
415
+ **Qué hace:** Crea un registro de pago en estado PENDING asociado a una reserva específica.
416
+ Consulta el monto total de la reserva a través del puerto ReservationService y delega
417
+ el cobro a la pasarela externa.
418
+ **Precondiciones:** La reserva debe existir y estar en estado PENDING_PAYMENT. No debe
419
+ existir ya un pago activo para esa reserva.
420
+ **Postcondiciones:** Pago creado en estado PENDING con referencia a la pasarela.
421
+ **Validaciones y errores:** 404 si la reserva no existe; 409 si ya hay un pago activo.
422
+ **Eventos que emite:** ninguno directamente; la pasarela notifica asíncronamente.
423
+
424
+ #### HandleReservationCreated
425
+ **Qué hace:** Al recibir el evento ReservationCreatedEvent, extrae el reservationId
426
+ y el totalAmount del payload e invoca internamente InitiatePayment para iniciar
427
+ el cobro de forma automática sin intervención del usuario.
428
+ **Precondiciones:** El payload debe contener reservationId válido y totalAmount > 0.
429
+ **Postcondiciones:** Se crea un pago PENDING y la pasarela queda en espera de confirmación.
430
+ **Validaciones y errores:** Si InitiatePayment falla, el error se registra en el log
431
+ y el evento se reencola para reintento.
432
+ **Eventos que emite:** ninguno adicional.
433
+
434
+ ### Endpoints expuestos
435
+
436
+ #### POST /payments
437
+ **Propósito:** Punto de entrada REST para iniciar un pago manualmente desde integraciones externas.
438
+ **Request body:** reservationId (String, obligatorio), paymentMethod (CARD | CASH, obligatorio).
439
+ **Response:** paymentId, status (PENDING), amount, currency, createdAt.
440
+ **Errores:** 404 si la reserva no existe; 409 si la reserva ya tiene un pago activo.
441
+
442
+ ### Eventos emitidos
443
+
444
+ #### PaymentApprovedEvent
445
+ **Cuándo:** La pasarela confirma la aprobación del cobro y se ejecuta ApprovePayment.
446
+ **Payload:** paymentId, reservationId, amount, approvedAt.
447
+ **Consumidores y sus acciones:**
448
+ - reservations → ConfirmReservation: confirma la reserva y la mueve a estado CONFIRMED
449
+ - notifications → NotifyPaymentApproved: envía email y SMS al cliente con el resumen
450
+
451
+ ### Puertos (llamadas síncronas salientes)
452
+
453
+ #### ReservationService → reservations
454
+ **Cuándo se llama:** Durante InitiatePayment para obtener el monto total de la reserva.
455
+ **Endpoints usados:** GET /reservations/{id}
456
+ **Datos que obtiene y cómo los usa:** Obtiene totalAmount y customerId para registrar
457
+ el pago con el monto correcto y asociarlo al cliente.
458
+ ```
459
+
460
+ ---
461
+
462
+ ---
463
+
464
+ ## Paso 6.5 — Crear diagrama de componentes (`system/system.mmd`)
465
+
466
+ Inmediatamente después de guardar `system/system.md`, crea el archivo `system/system.mmd` en el **mismo directorio** `system/`.
467
+
468
+ Este archivo es el **diagrama de componentes Mermaid** del sistema. Es una representación visual directa de la arquitectura descrita en `system.md`: muestra cada módulo como un componente, los actores externos que los consumen, los flujos asíncronos vía broker y las llamadas síncronas HTTP entre módulos.
469
+
470
+ ### Estructura del diagrama
471
+
472
+ Usa el tipo `C4Component` si el sistema tiene más de 4 módulos o llamadas externas relevantes; usa `graph LR` para sistemas más simples. La convención preferida es `graph LR` con subgraphs y estilos explícitos:
473
+
474
+ ```mermaid
475
+ graph LR
476
+ %% External actors
477
+ Client(["👤 Client"])
478
+
479
+ %% Modules — one subgraph per module
480
+ subgraph orders["orders"]
481
+ ORD_API["REST API"]
482
+ ORD_UC["Use Cases"]
483
+ end
484
+
485
+ subgraph payments["payments"]
486
+ PAY_API["REST API"]
487
+ PAY_UC["Use Cases"]
488
+ end
489
+
490
+ subgraph notifications["notifications"]
491
+ NOT_UC["Use Cases"]
492
+ end
493
+
494
+ %% Message broker (solo si hay mensajería async)
495
+ BROKER[["📨 Kafka"]]
496
+
497
+ %% External services (solo si hay ports)
498
+ EXT_GW[/"💳 PaymentGateway"/]
499
+
500
+ %% HTTP connections — sync
501
+ Client -->|"REST"| ORD_API
502
+ Client -->|"REST"| PAY_API
503
+ ORD_UC -->|"CustomerService\nGET /customers/{id}"| CUST_API
504
+
505
+ %% Async event flows — producer → broker → consumer(s)
506
+ ORD_UC -->|"OrderPlacedEvent"| BROKER
507
+ BROKER -->|"OrderPlacedEvent"| PAY_UC
508
+ BROKER -->|"OrderPlacedEvent"| NOT_UC
509
+ PAY_UC -->|"PaymentApprovedEvent"| BROKER
510
+ BROKER -->|"PaymentApprovedEvent"| ORD_UC
511
+
512
+ %% External port calls
513
+ PAY_UC -->|"processPayment"| EXT_GW
514
+
515
+ %% Styles
516
+ classDef module fill:#dbeafe,stroke:#3b82f6,color:#1e3a5f
517
+ classDef broker fill:#fef9c3,stroke:#ca8a04,color:#713f12
518
+ classDef actor fill:#f0fdf4,stroke:#16a34a,color:#14532d
519
+ classDef external fill:#fce7f3,stroke:#db2777,color:#831843
520
+ class orders,payments,notifications module
521
+ class BROKER broker
522
+ class Client actor
523
+ class EXT_GW external
524
+ ```
525
+
526
+ ### Reglas del diagrama de componentes
527
+
528
+ - **Un subgraph por módulo** — el nombre del subgraph coincide exactamente con el nombre del módulo en `system.yaml`
529
+ - **Actores externos** (`Client`, `Admin`, etc.) como nodos `(["..."])` fuera de los subgraphs — solo los que interactúan directamente con la API
530
+ - **Broker** como nodo `[["..."`]] central — solo si `messaging.enabled: true`; omitir si no hay async
531
+ - **Servicios externos** vía `ports:` como nodos `[/"..."/]` — uno por `service:` único en `integrations.sync`
532
+ - **Flechas sync HTTP** (`-->|"PortName\nMETHOD /path"|`) entre módulo caller y módulo/servicio callee
533
+ - **Flechas async** (`-->|"EventName"|`) siempre pasan por el broker: `producer → BROKER → consumer`; nunca directo
534
+ - **Estilos obligatorios**: usar `classDef` para diferenciar visualmente módulos, broker, actores y servicios externos
535
+ - **Todo en inglés**: labels de flechas, nombres de nodos, comments
536
+ - **El archivo no contiene nada más que el bloque Mermaid** — sin frontmatter, sin texto markdown, solo el diagrama
537
+
538
+ ### Correspondencia con system.md
539
+
540
+ El diagrama es una proyección visual 1:1 de lo descrito en `system.md`:
541
+
542
+ | Elemento en system.md | Elemento en system.mmd |
543
+ |---|---|
544
+ | Módulo con endpoints REST | Subgraph con nodo `REST API` interno |
545
+ | Módulo sin endpoints | Subgraph sin nodo REST |
546
+ | `integrations.async[].producer` → broker | Flecha `producer → BROKER` |
547
+ | `integrations.async[].consumers[]` | Flechas `BROKER → consumer` (una por consumidor) |
548
+ | `integrations.sync[].caller` → `calls` | Flecha directa `caller → target` con label del port |
549
+ | `integrations.sync[].calls` (externo) | Nodo `[/"ExtSvc"/]` fuera de subgraphs |
550
+ | Actor que llama endpoints | Nodo `Client` con flecha REST al módulo |
551
+
552
+ ### Checklist del system.mmd
553
+
554
+ Antes de guardar, verifica:
555
+
556
+ - [ ] Un subgraph por cada módulo en `modules:`
557
+ - [ ] Flechas async pasan siempre por el nodo BROKER
558
+ - [ ] Flechas sync son directas entre módulos (no pasan por broker)
559
+ - [ ] Servicios externos tienen nodo propio fuera de subgraphs
560
+ - [ ] `classDef` aplicado a todos los nodos relevantes
561
+ - [ ] Todo el contenido en inglés
562
+ - [ ] El archivo contiene **solo** el bloque Mermaid (sin markdown alrededor)
563
+
564
+ ---
565
+
566
+ ---
567
+
568
+ ## Paso 7 — Crear archivos de dominio por módulo (`system/{module}.yaml`)
569
+
570
+ Inmediatamente después de crear `system/system.mmd`, genera **un archivo `domain.yaml` por cada módulo** declarado en `modules:`. La ruta es `system/{nombre-del-modulo}.yaml` (mismo directorio `system/`).
571
+
572
+ Este archivo es el **modelo de dominio del módulo**: define las entidades, value objects, enums, relaciones, eventos y configuraciones de infraestructura. Es el input directo para el comando `eva g entities <module>`.
573
+
574
+ > **Referencia técnica completa:** antes de construir cada archivo, lee `references/GENERATE_ENTITIES.md` para verificar qué propiedades son válidas y qué código produce cada configuración.
575
+
576
+ ### Rol de experto de dominio por módulo
577
+
578
+ Al construir el `system/{module}.yaml` de cada módulo, **activa el rol de experto en el dominio específico de ese módulo**. Esto significa razonar como alguien que conoce en profundidad el negocio de ese bounded context en particular:
579
+
580
+ - **`orders`** → piensa como un experto en gestión de pedidos: ciclos de vida, estados válidos, invariantes de negocio (no se puede confirmar un pedido ya cancelado), relaciones con items, totales calculados
581
+ - **`payments`** → piensa como un experto en pagos: métodos de pago, reintentos, estados terminales, reconciliación, prevención de doble cobro
582
+ - **`inventory`** → piensa como un experto en inventario: stock disponible vs. reservado, movimientos, alertas de reposición
583
+ - **`notifications`** → piensa como un experto en comunicaciones: canales (email, SMS, push), plantillas, idempotencia, reintentos
584
+
585
+ Este conocimiento aplicado te permite:
586
+ - Proponer **campos que el usuario no mencionó** pero son necesarios para el dominio (ej: `cancelledReason` en un módulo de pedidos)
587
+ - Sugerir **Value Objects** que mejoran la expresividad (ej: `Money` para importes, `Address` para direcciones)
588
+ - Reconocer **invariantes implícitas** del dominio (ej: el stock no puede ser negativo)
589
+ - Modelar **transiciones de estado** completas y realistas para el ciclo de vida de la entidad
590
+ - Identificar qué campos deben ser `readOnly` (calculados), `hidden` (sensibles) o tener `defaultValue`
591
+
592
+ > **Cuándo preguntar:** si el conocimiento general del dominio no es suficiente para tomar una decisión de modelado — por ejemplo, reglas de negocio específicas de la empresa (¿se permiten pedidos con cantidad 0? ¿cuántos días para cancelar?) — pregunta en un solo mensaje antes de continuar con ese módulo.
593
+
594
+ ### Restricciones absolutas al construir el domain.yaml
595
+
596
+ Antes de escribir cualquier YAML, verifica que NO estás cometiendo ninguno de estos errores:
597
+
598
+ 1. ❌ **No agregar `@ManyToOne` / `@OneToMany` entre agregados distintos** — las referencias cross-aggregate son IDs simples con `reference:`
599
+ 2. ❌ **No incluir campos de auditoría en `fields:`** (`createdAt`, `updatedAt`, `createdBy`, `updatedBy`) — los genera la infraestructura automáticamente con `audit.enabled: true`
600
+ 3. ❌ **No usar `defaultValue` en campos que no son `readOnly: true`**
601
+ 4. ❌ **No declarar `transitions` sin `initialValue` en el enum** — si hay lifecycle, declarar ambos
602
+ 5. ❌ **No inventar módulos en `reference.module`** — solo los declarados en `system/system.yaml → modules:`
603
+ 6. ❌ **No duplicar en `endpoints:`** lo que ya está en `system/system.yaml → exposes:`
604
+ 12. ❌ **`endpoints:` NUNCA es una lista plana** — usar siempre la estructura `{ basePath, versions: [{ version, operations: [...] }] }`. Una lista plana hace que el generador no produzca ningún Command, Handler ni Controller.
605
+ 7. ❌ **No inventar eventos en `events:`** — deben coincidir con `integrations.async[]` donde `producer` es este módulo. ✅ **Declarar siempre `topic:` en cada evento** con el valor exacto de `integrations.async[].topic` — aunque el generador puede derivarlo automáticamente, la declaración explícita garantiza consistencia con los `listeners[]` de los módulos consumidores
606
+ 8. ❌ **No inventar listeners** — deben coincidir con `integrations.async[].consumers[]` donde `module` es este módulo
607
+ 9. ❌ **No inventar ports** — deben coincidir con `integrations.sync[]` donde `caller` es este módulo
608
+ 10. ❌ **Todo el contenido del archivo debe estar en inglés** — field names, comments, descriptions, values
609
+ 11. ❌ **No dejar transiciones sin evidencia de activación** — toda transición que se ejecuta como consecuencia de una respuesta de `ports:` (éxito o falla) o de un proceso interno/scheduler **debe** tener un domain event con `triggers` — es el único mecanismo que permite al validador C2-001 reconocerlas como alcanzables
610
+ 13. ❌ **No reutilizar el mismo nombre de `service:` en `ports[]` de módulos distintos** — si dos módulos llaman al mismo servicio externo, cada uno debe usar un nombre propio de su bounded context (ej: `OrderCustomerService` en `orders`, `DeliveryCustomerService` en `deliveries`); el mismo nombre produce `ConflictingBeanDefinitionException` en Spring
611
+
612
+ ### Inferencia desde system.yaml
613
+
614
+ Para cada módulo, extrae del `system/system.yaml`:
615
+
616
+ | Fuente en system.yaml | Destino en domain.yaml |
617
+ |---|---|
618
+ | `modules[x].exposes[]` | `endpoints:` — objeto con `basePath` + `versions[].operations[]`; **NUNCA** lista plana |
619
+ | `integrations.async[]` donde `producer = módulo` | `events:` (nombres de eventos a producir) |
620
+ | `integrations.async[].consumers[]` donde `module = módulo` | `listeners:` (con `useCase`) |
621
+ | `integrations.sync[]` donde `caller = módulo` | `ports:` (con `service`, `http`, `using`) |
622
+
623
+ > ❌ **Formato ERRADO** — `endpoints:` como lista plana de objetos `{method, path, useCase}` — el generador lo ignora y no produce ningún Command/Handler:
624
+ > ```yaml
625
+ > # ❌ NUNCA así
626
+ > endpoints:
627
+ > - method: POST
628
+ > path: /orders
629
+ > useCase: CreateOrder
630
+ > ```
631
+ > ✅ **Formato CORRECTO** — objeto con `basePath` y `versions[].operations[]`:
632
+ > ```yaml
633
+ > # ✅ SIEMPRE así
634
+ > endpoints:
635
+ > basePath: /orders
636
+ > versions:
637
+ > - version: v1
638
+ > operations:
639
+ > - useCase: CreateOrder
640
+ > method: POST
641
+ > path: /
642
+ > ```
643
+
644
+ ### Estructura completa del `system/{module}.yaml`
645
+
646
+ ```yaml
647
+ aggregates:
648
+ - name: Order # PascalCase — nombre del agregado
649
+ entities:
650
+ - name: order # camelCase — nombre de la entidad raíz
651
+ isRoot: true
652
+ tableName: orders # snake_case — nombre de tabla SQL
653
+ hasSoftDelete: false # true para borrado lógico (deletedAt)
654
+ audit:
655
+ enabled: true # adds createdAt, updatedAt
656
+ trackUser: false # adds createdBy, updatedBy
657
+ fields:
658
+ - name: id
659
+ type: String
660
+ - name: orderNumber
661
+ type: String
662
+ validations:
663
+ - type: NotBlank
664
+ message: "Order number is required"
665
+ - name: totalAmount
666
+ type: BigDecimal
667
+ readOnly: true # calculated — excluded from CreateDto and business constructor
668
+ defaultValue: "0.00" # initial value in creation constructor
669
+ - name: status
670
+ type: OrderStatus
671
+ readOnly: true # managed by transitions
672
+ - name: processingToken
673
+ type: String
674
+ hidden: true # sensitive — excluded from ResponseDto
675
+ - name: customerId
676
+ type: String
677
+ reference:
678
+ aggregate: Customer # PascalCase
679
+ module: customers # module where the aggregate lives
680
+ relationships:
681
+ - type: OneToMany
682
+ target: OrderItem
683
+ mappedBy: order
684
+ cascade: [PERSIST, MERGE, REMOVE]
685
+ fetch: LAZY
686
+
687
+ - name: orderItem # secondary entity — isRoot omitted (false by default)
688
+ tableName: order_items
689
+ fields:
690
+ - name: id
691
+ type: String
692
+ - name: productId
693
+ type: String
694
+ - name: quantity
695
+ type: Integer
696
+ validations:
697
+ - type: Min
698
+ value: 1
699
+ - name: unitPrice
700
+ type: BigDecimal
701
+ # Do NOT declare ManyToOne toward Order — the generator infers it automatically
702
+ # from the OneToMany declared in the root entity
703
+
704
+ valueObjects:
705
+ - name: ShippingAddress # PascalCase — immutable, no ID
706
+ fields:
707
+ - name: street
708
+ type: String
709
+ - name: city
710
+ type: String
711
+ - name: zipCode
712
+ type: String
713
+ methods: # optional — business logic of the VO
714
+ - name: format
715
+ returnType: String
716
+ parameters: []
717
+ body: "return street + \", \" + city + \" \" + zipCode;"
718
+
719
+ enums:
720
+ - name: OrderStatus
721
+ initialValue: PENDING # initial state — excluded from CreateDto
722
+ transitions:
723
+ - from: PENDING
724
+ to: CONFIRMED
725
+ method: confirm
726
+ - from: CONFIRMED
727
+ to: SHIPPED
728
+ method: ship
729
+ - from: [PENDING, CONFIRMED] # multiple origins allowed
730
+ to: CANCELLED
731
+ method: cancel
732
+ guard: "this.status == OrderStatus.DELIVERED" # throws BusinessException if true
733
+ values: [PENDING, CONFIRMED, SHIPPED, DELIVERED, CANCELLED]
734
+
735
+ events:
736
+ - name: OrderPlacedEvent # PascalCase, past tense, Event suffix
737
+ topic: ORDER_PLACED # preferred: explicit topic — must match listeners[].topic in consumers
738
+ triggers:
739
+ - confirm # transition method name that fires this event
740
+ fields:
741
+ # Declare {entityName}Id when the event crosses module boundaries via Kafka
742
+ # — the generator maps it to event.getAggregateId() in the handler
743
+ - name: customerId
744
+ type: String
745
+ - name: confirmedAt # ends in 'At' + LocalDateTime → resolves to LocalDateTime.now()
746
+ type: LocalDateTime
747
+ - name: OrderCancelledEvent
748
+ topic: ORDER_CANCELLED # preferred: explicit topic
749
+ triggers:
750
+ - cancel
751
+ fields:
752
+ - name: reason # unresolved → null /* TODO: provide reason */
753
+ type: String
754
+
755
+ # Declarative REST endpoints — inferred from system/system.yaml → exposes:
756
+ endpoints:
757
+ basePath: /orders
758
+ versions:
759
+ - version: v1
760
+ operations:
761
+ - useCase: GetOrder
762
+ method: GET
763
+ path: /{id} # relative to basePath
764
+ - useCase: FindAllOrders
765
+ method: GET
766
+ path: /
767
+ - useCase: CreateOrder
768
+ method: POST
769
+ path: /
770
+ - useCase: ConfirmOrder # business name → generates scaffold
771
+ method: PUT
772
+ path: /{id}/confirm
773
+
774
+ # External events this module CONSUMES
775
+ # Inferred from system/system.yaml → integrations.async[].consumers[] where module = this module
776
+ listeners:
777
+ - event: PaymentApprovedEvent
778
+ producer: payments
779
+ topic: PAYMENT_APPROVED
780
+ useCase: ConfirmOrder # must match consumers[].useCase in system.yaml
781
+ fields:
782
+ - name: orderId
783
+ type: String
784
+ - name: approvedAt
785
+ type: LocalDateTime
786
+ - name: paymentDetails # object type → MUST declare in nestedTypes:
787
+ type: PaymentDetails
788
+ nestedTypes:
789
+ - name: paymentDetails # camelCase → generates PaymentDetails record
790
+ fields:
791
+ - name: paymentId
792
+ type: String
793
+ - name: amount
794
+ type: BigDecimal
795
+
796
+ # Synchronous outbound HTTP services this module CALLS
797
+ # Inferred from system/system.yaml → integrations.sync[] where caller = this module
798
+ ports:
799
+ - name: findCustomerById
800
+ service: CustomerService # must match integrations.sync[].port
801
+ target: customers
802
+ baseUrl: http://localhost:8080 # only on the first entry per service
803
+ http: GET /customers/{id}
804
+ fields:
805
+ - name: id
806
+ type: String
807
+ - name: email
808
+ type: String
809
+ - name: processPayment
810
+ service: PaymentGateway
811
+ target: payments
812
+ baseUrl: https://api.payments.example.com
813
+ http: POST /payments
814
+ body:
815
+ - name: reservationId
816
+ type: String
817
+ - name: paymentMethod
818
+ type: PaymentMethodInput # object in body → declare in nestedTypes:
819
+ nestedTypes:
820
+ - name: paymentMethodInput
821
+ fields:
822
+ - name: type
823
+ type: String
824
+ - name: cardToken
825
+ type: String
826
+ fields:
827
+ - name: paymentId
828
+ type: String
829
+ - name: status
830
+ type: String
831
+ ```
832
+
833
+ ### Tabla de visibilidad de campos
834
+
835
+ | Configuración | Business constructor | CreateDto | ResponseDto |
836
+ |---|---|---|---|
837
+ | Normal field | ✅ | ✅ | ✅ |
838
+ | `readOnly: true` | ❌ | ❌ | ✅ |
839
+ | `readOnly: true` + `defaultValue` | ⚡ assigned with default | ❌ | ✅ |
840
+ | `hidden: true` | ✅ | ✅ | ❌ |
841
+ | Both flags | ❌ | ❌ | ❌ |
842
+
843
+ ### Tipos de datos soportados
844
+
845
+ | YAML type | Java type |
846
+ |---|---|
847
+ | `String` | String |
848
+ | `Integer` | Integer |
849
+ | `Long` | Long |
850
+ | `BigDecimal` | BigDecimal |
851
+ | `Boolean` | Boolean |
852
+ | `LocalDate` | LocalDate |
853
+ | `LocalDateTime` | LocalDateTime |
854
+ | `UUID` | UUID |
855
+
856
+ ### Reglas de relaciones
857
+
858
+ - ✅ `OneToMany` / `OneToOne` entre entidades del **mismo agregado** → declarar solo en la entidad raíz
859
+ - ✅ El generador infiere automáticamente el lado inverso (`ManyToOne`) — **no declararlo en la secundaria**
860
+ - ✅ Referencia a entidad de **otro agregado** → usar `reference:` en el campo ID, nunca `relationships:`
861
+ - El `mappedBy` debe coincidir con el nombre del campo en la entidad secundaria que apunta de vuelta a la raíz
862
+
863
+ ### Proyecciones locales (read models sincronizados por eventos)
864
+
865
+ Cuando un módulo consume eventos de otro módulo para mantener una **copia local desnormalizada** (proyección / read model), ese agregado tiene características distintas al dominio principal.
866
+
867
+ **Señales de que un agregado es una proyección local:**
868
+ - Sus campos coinciden con los `fields[]` de uno o más listeners
869
+ - Su useCase principal es `Register*InLocalCatalog`, `Sync*` o `Update*FromEvent`
870
+ - No tiene endpoints HTTP de escritura — solo se actualiza por listeners Kafka
871
+ - Existe para evitar llamadas síncronas al módulo origen en tiempo real
872
+
873
+ **Reglas de auditoría para proyecciones:**
874
+
875
+ | Tipo de entidad | `audit.enabled` | `audit.trackUser` | Justificación |
876
+ |---|---|---|---|
877
+ | Entidad de dominio principal | ✅ true | según necesidad | Trazabilidad completa requerida |
878
+ | Proyección local (read model) | ✅ true | ❌ false | `createdAt`/`updatedAt` permite depurar desincronizaciones; `trackUser: false` porque los cambios vienen de eventos, no de usuarios |
879
+
880
+ > **Error común:** omitir `audit.enabled` en proyecciones porque "no son datos propios". En módulos críticos (reservations, payments, orders) las proyecciones afectan directamente al negocio — saber cuándo cambió `isAvailable` o `accountStatus` es esencial para depurar problemas de sincronización en producción.
881
+
882
+ **Patrón canónico de proyección local:**
883
+
884
+ ```yaml
885
+ - name: BikeCatalog # nombre del agregado proyección
886
+ entities:
887
+ - name: bikeCatalogEntry
888
+ isRoot: true
889
+ tableName: bike_catalog_entries
890
+ audit:
891
+ enabled: true # ✅ siempre true en módulos críticos
892
+ trackUser: false # ✅ cambios vienen de eventos, no de usuarios
893
+ fields:
894
+ - name: id
895
+ type: String
896
+ - name: isAvailable
897
+ type: Boolean
898
+ readOnly: true
899
+ defaultValue: true # estado inicial al registrarse
900
+
901
+ # Los listeners que mantienen esta proyección:
902
+ listeners:
903
+ - event: BikeCreatedEvent
904
+ producer: fleet
905
+ topic: BIKE_CREATED
906
+ useCase: RegisterBikeInLocalCatalog # crea la entrada en la proyección
907
+ fields:
908
+ - name: code
909
+ type: String
910
+ - name: stationId
911
+ type: String
912
+
913
+ - event: BikeMaintenanceStartedEvent
914
+ producer: fleet
915
+ topic: BIKE_MAINTENANCE_STARTED
916
+ useCase: MarkBikeUnavailable # actualiza isAvailable=false
917
+ fields:
918
+ - name: stationId
919
+ type: String
920
+ ```
921
+
922
+ ### Clasificación de campos `readOnly` por origen
923
+
924
+ Antes de declarar los `events:`, clasifica cada campo `readOnly` de la entidad raíz según **cuándo y cómo se asigna su valor**. Esta clasificación determina en qué evento debe aparecer:
925
+
926
+ | Categoría | Cuándo se asigna | Debe aparecer en... |
927
+ |---|---|---|
928
+ | **Constante del sistema** | Constructor con `defaultValue` | Ningún evento — es configuración |
929
+ | **Estado de máquina** | Cada transición (enum con `transitions`) | Ningún campo explícito — el nombre del evento comunica el estado |
930
+ | **Timestamp de transición** | Una transición específica (`pickup()`, `complete()`…) | El evento de **esa** transición via `triggers` |
931
+ | **Dato calculado de transición** | Se calcula al ejecutar una transición | El evento de esa transición via `triggers` |
932
+ | **Acumulador** | Se actualiza en múltiples operaciones | En el evento de cada operación que lo modifica |
933
+
934
+ **Patrón más propenso a omisiones — timestamp de transición:**
935
+
936
+ Campos como `actualPickupAt`, `completedAt`, `processedAt`, `sentAt` se asignan en el momento exacto de una transición. Son la **evidencia temporal del hecho ocurrido** y deben incluirse en el evento de esa transición:
937
+
938
+ ```yaml
939
+ # ❌ MAL — actualPickupAt se asigna en pickup() pero no hay evento para esa transición
940
+ fields:
941
+ - name: actualPickupAt
942
+ type: LocalDateTime
943
+ readOnly: true
944
+ enums:
945
+ - name: ReservationStatus
946
+ transitions:
947
+ - from: CONFIRMED
948
+ to: IN_PROGRESS
949
+ method: pickup # asigna actualPickupAt pero no tiene evento asociado
950
+ # events: no incluye ningún evento con triggers: [pickup]
951
+ ```
952
+
953
+ ```yaml
954
+ # ✅ BIEN — la transición pickup tiene su evento y el timestamp está en los fields
955
+ fields:
956
+ - name: actualPickupAt
957
+ type: LocalDateTime
958
+ readOnly: true
959
+ enums:
960
+ - name: ReservationStatus
961
+ transitions:
962
+ - from: CONFIRMED
963
+ to: IN_PROGRESS
964
+ method: pickup
965
+ events:
966
+ - name: ReservationPickedUpEvent
967
+ triggers:
968
+ - pickup
969
+ fields:
970
+ - name: userId
971
+ type: String
972
+ - name: bikeId
973
+ type: String
974
+ - name: actualPickupAt # timestamp asignado en esta transición
975
+ type: LocalDateTime
976
+ ```
977
+
978
+ **Protocolo para cada campo `readOnly` sin `defaultValue` y cuyo tipo no es un enum con `transitions`:**
979
+
980
+ ```
981
+ 1. ¿En qué transición o caso de uso se asigna este campo?
982
+ 2. ¿Esa transición ya tiene un evento con triggers?
983
+ → Sí: agregar el campo al fields[] de ese evento
984
+ → No: crear el evento + triggers ANTES de continuar el diseño
985
+ 3. Si se asigna en múltiples transiciones: incluirlo en el evento de la transición principal
986
+ ```
987
+
988
+ > **Señal de alerta:** una transición que asigna campos `readOnly` (especialmente `*At`) pero no tiene evento asociado es una **brecha de diseño**. Nunca dejes una transición sin evento cuando modifica datos que otros módulos o el propio módulo necesitan trazar.
989
+
990
+ ### Análisis de activación de transiciones
991
+
992
+ Antes de escribir los `events:`, recorre **cada método en `transitions[]`** del módulo y clasifícalo según quién lo activa:
993
+
994
+ | ¿Quién activa la transición? | Mecanismo de diseño correcto | C2-001 silenciado por... |
995
+ |---|---|---|
996
+ | Un endpoint HTTP (PUT/POST/PATCH) | `endpoints:` con `useCase` que hace match semántico | Overlap de palabras entre método y useCase |
997
+ | Un listener Kafka | `listeners:` con `useCase` que hace match semántico | Overlap de palabras entre método y useCase |
998
+ | La **respuesta exitosa** de un `ports:` call | Domain event + `triggers: [método]` | Presencia del trigger en `triggeredMethods` |
999
+ | La **respuesta de error/timeout** de un `ports:` call | Domain event + `triggers: [método]` | Presencia del trigger en `triggeredMethods` |
1000
+ | Un scheduler / process interno / batch | Domain event + `triggers: [método]` | Presencia del trigger en `triggeredMethods` |
1001
+
1002
+ **Patrón canonical — respuesta de port con dos ramas (éxito/falla):**
1003
+
1004
+ Cuando el módulo llama a un servicio externo vía `ports:` y el resultado puede ser éxito o falla, las transiciones de esas dos ramas deben modelarse con eventos separados:
1005
+
1006
+ ```yaml
1007
+ events:
1008
+ - name: NotificationSentEvent # rama éxito del ports: call
1009
+ triggers:
1010
+ - markAsSent # transition method activado por 2xx
1011
+ fields:
1012
+ - name: sentAt
1013
+ type: LocalDateTime
1014
+
1015
+ - name: NotificationFailedEvent # rama error del ports: call
1016
+ triggers:
1017
+ - markAsFailed # transition method activado por error/timeout
1018
+ fields: [] # puede no necesitar campos extra
1019
+ ```
1020
+
1021
+ > **Señal de alerta:** si el módulo tiene `ports:` con respuesta y el enum de estado tiene transiciones de `SUCCESS`/`FAILED` (o equivalentes), y NO hay domain events con `triggers` para ellas → el validador C2-001 las marcará como inalcanzables. La solución siempre es agregar los events, no modificar el validador.
1022
+
1023
+ ### Análisis de trazabilidad de enums `*Type`
1024
+
1025
+ Todo enum cuyo nombre termina en `Type` (ej: `IncidentType`, `PaymentType`, `NotificationType`) representa una **clasificación** cuyos valores solo pueden nacer de un mecanismo externo o interno. El validador C2-003 verifica que **cada valor sea semánticamente trazable** a al menos uno de estos orígenes:
1026
+
1027
+ | Origen del valor | Traza semántica | Ejemplo |
1028
+ |---|---|---|
1029
+ | Listener Kafka (nombre del evento) | Tokens del nombre del evento | `TripCompletedEvent` → `trip`, `completed` |
1030
+ | Listener Kafka (nombre de campo) | Tokens del nombre del campo | `wasLateReturn` → `late`, `return` → traza `LATE_RETURN` |
1031
+ | Endpoint HTTP (useCase) | Tokens del nombre del useCase | `RegisterIncident` → `register`, `incident` |
1032
+ | Domain event producido (nombre) | Tokens del nombre del evento | `IncidentRegisteredEvent` → `incident`, `registered` |
1033
+
1034
+ **Protocolo obligatorio — para cada valor de un enum `*Type`:**
1035
+
1036
+ ```
1037
+ Para cada valor en {Enum}Type:
1038
+ 1. ¿Lo crea un usuario/operador vía HTTP? → declarar endpoint con useCase que contenga
1039
+ palabras del valor (RegisterIncident,
1040
+ ReportDamage, CreatePaymentByCard, etc.)
1041
+ 2. ¿Lo origina un evento Kafka entrante? → el nombre del evento o alguno de sus campos
1042
+ debe contener palabras del valor
1043
+ 3. ¿Lo origina un domain event producido? → el nombre del evento ya aporta tokens
1044
+ 4. Ninguno aplica → valor HUÉRFANO — falta un endpoint o listener;
1045
+ revisar si falta diseño o si el valor sobra
1046
+ ```
1047
+
1048
+ **Ejemplo canónico — `IncidentType` con `[LATE_RETURN, DAMAGE_REPORT]`:**
1049
+
1050
+ | Valor | ¿Quién lo origina? | Traza correcta |
1051
+ |---|---|---|
1052
+ | `LATE_RETURN` | Automático: `TripCompletedEvent.wasLateReturn == true` | Campo `wasLateReturn` en `listeners[].fields` → tokens `late`, `return` |
1053
+ | `DAMAGE_REPORT` | Manual: staff reporta daño vía HTTP | Endpoint `POST /{id}/incidents` con `useCase: RegisterIncident` → tokens `register`, `incident` |
1054
+
1055
+ Si `DAMAGE_REPORT` no tiene un endpoint declarado → C2-003 lo marcará huérfano. La solución es **agregar el endpoint**, no ignorar el warning.
1056
+
1057
+ **Patrón `subEntityAdd` para entidades secundarias con `*Type`:**
1058
+
1059
+ Cuando el módulo tiene `OneToMany` hacia una entidad secundaria con un campo `type: FooType` que se crea por HTTP, declarar siempre el endpoint correspondiente:
1060
+
1061
+ ```yaml
1062
+ endpoints:
1063
+ basePath: /accounts
1064
+ versions:
1065
+ - version: v1
1066
+ operations:
1067
+ - useCase: RegisterIncident # tokens: register + incident
1068
+ method: POST # subEntityAdd pattern — AddIncidentCommand generado
1069
+ path: /{id}/incidents # relativo al basePath
1070
+ ```
1071
+
1072
+ El nombre del useCase debe contener palabras que tracen semánticamente con **todos** los valores del `*Type` que ese endpoint puede crear. Si distintos valores tienen orígenes distintos (algunos por HTTP, otros por Kafka), trazar cada uno por su mecanismo correspondiente.
1073
+
1074
+ > **Regla de oro:** para cada valor de `FooType`, al menos uno de los listeners, endpoints o domain events del módulo debe contener una palabra semántica equivalente. Si ninguno la contiene → falta diseño.
1075
+
1076
+ ### Checklist del domain.yaml por módulo
1077
+
1078
+ Antes de guardar `system/{module}.yaml`, verifica:
1079
+
1080
+ - [ ] Campo `id` presente en todas las entidades
1081
+ - [ ] Solo una entidad con `isRoot: true` por agregado
1082
+ - [ ] Relaciones intra-agregado declaradas solo en la entidad raíz; lado inverso NO declarado en la secundaria
1083
+ - [ ] Campos de auditoría NO en `fields:` — los genera el framework
1084
+ - [ ] Campos `readOnly` con `defaultValue` si tienen valor inicial conocido
1085
+ - [ ] Enums con ciclo de vida tienen `initialValue` y `transitions`
1086
+ - [ ] Value Objects sin campo `id`
1087
+ - [ ] Referencias cross-aggregate usando `reference:` en el campo ID, no `relationships:`
1088
+ - [ ] `events[].name` consistente con `integrations.async[]` donde `producer` es este módulo
1089
+ - [ ] `events[].topic` declarado en cada evento — debe coincidir **exactamente** con `integrations.async[].topic`; si se omite, el generador lo deriva quitando el sufijo `Event` (`OrderPlacedEvent` → `ORDER_PLACED`), pero declararlo explícitamente evita mismatch con consumidores
1090
+ - [ ] `events[].triggers[]` referencia métodos que existen en `enums[].transitions[].method`
1091
+ - [ ] `{entityName}Id` declarado en `events[].fields` cuando el evento **cruza módulos via Kafka** (el generador lo mapea a `event.getAggregateId()` automáticamente)
1092
+ - [ ] `endpoints[].operations[].useCase` consistente con `modules[].exposes[].useCase`
1093
+ - [ ] `endpoints:` tiene la estructura `{ basePath, versions: [{ version, operations }] }` — **no** es una lista plana de operaciones
1094
+ - [ ] `endpoints[].path` son **relativos** al basePath
1095
+ - [ ] `listeners[]` presentes para todos los eventos donde este módulo es consumidor
1096
+ - [ ] `listeners[].useCase` coincide con `consumers[].useCase` en `system/system.yaml`
1097
+ - [ ] `listeners[].topic` coincide **exactamente** con `integrations.async[].topic` en `system/system.yaml` — valor bare `SCREAMING_SNAKE_CASE`, **nunca** con `topicPrefix` prepended (ej: `PAYMENT_APPROVED`, no `test-eva.PAYMENT_APPROVED`)
1098
+ - [ ] `ports[]` presentes para todas las entradas `integrations.sync[]` donde `caller = este módulo`
1099
+ - [ ] `ports[].baseUrl` solo en la primera entrada de cada `service:`
1100
+ - [ ] `listeners[].nestedTypes[]` declarado para cada campo de tipo objeto en `fields[]`
1101
+ - [ ] Cada transición activada por respuesta de `ports:` (éxito o error) tiene un domain event con `triggers` correspondiente
1102
+ - [ ] Cada transición activada por scheduler o proceso interno tiene un domain event con `triggers` correspondiente
1103
+ - [ ] Para cada enum `*Type`: cada valor tiene trazabilidad semántica en un listener (nombre de evento o campo) o en un endpoint (useCase) del módulo
1104
+ - [ ] Para entidades secundarias en `OneToMany` con campo `type: *Type` creado por HTTP: existe endpoint `RegisterX` / `AddX` con useCase que traza semánticamente con los valores del enum
1105
+ - [ ] Cada campo `readOnly` sin `defaultValue` y sin tipo de estado (enum con `transitions`): la transición que lo asigna tiene un evento con `triggers` que lo incluye en `fields[]`
1106
+ - [ ] Proyecciones locales (agregados sincronizados por listeners): `audit.enabled: true` y `trackUser: false`
1107
+ - [ ] Todo el contenido está en **inglés**
1108
+
1109
+
1110
+ ## Paso 8 — Crear archivos individuales por módulo (`system/{module}.md`)
1111
+
1112
+ Inmediatamente después de crear los archivos `system/{module}.yaml`, genera **un archivo `.md` separado por cada módulo** declarado en `modules:`. La ruta es `system/{nombre-del-modulo}.md` (mismo directorio `system/`).
1113
+
1114
+ El objetivo es tener una **especificación técnica desagregada** que un desarrollador pueda leer de forma independiente para implementar su módulo sin necesidad de conocer el sistema completo.
1115
+
1116
+ ### Estructura obligatoria de cada `system/{module}.md`
1117
+
1118
+ ```markdown
1119
+ # {nombre-del-modulo} — Especificación técnica
1120
+
1121
+ ## Rol del módulo
1122
+ [3–5 párrafos MUY DETALLADOS]
1123
+ - Qué problema de negocio resuelve y qué entidades/conceptos gestiona
1124
+ - Cuáles son sus responsabilidades exclusivas (límites del bounded context)
1125
+ - Qué NO es responsabilidad de este módulo
1126
+ - Cómo colabora con los otros módulos del sistema
1127
+
1128
+ ## Invariantes
1129
+
1130
+ > Las invariantes son condiciones que deben ser **siempre verdaderas** dentro de este bounded context,
1131
+ > independientemente de qué operación se ejecute. Violar una invariante es un **error de negocio crítico**
1132
+ > que debe lanzar excepción y nunca persistirse.
1133
+
1134
+ | ID | Invariante | Consecuencia de violación |
1135
+ |----|-----------|---------------------------|
1136
+ | INV-01 | [condición que siempre debe cumplirse] | [qué excepción lanzar / qué impide] |
1137
+ | INV-02 | ... | ... |
1138
+
1139
+ > **Regla de implementación:** cada caso de uso debe verificar las invariantes relevantes **antes** de persistir cualquier cambio. Preferir verificación en el método de negocio de la entidad de dominio.
1140
+
1141
+ ## Máquina de estados
1142
+ [SOLO si el módulo gestiona un ciclo de vida — omitir la sección si no aplica]
1143
+
1144
+ ```mermaid
1145
+ stateDiagram-v2
1146
+ [*] --> ESTADO_INICIAL
1147
+ ESTADO_INICIAL --> ESTADO_SIGUIENTE : evento / acción
1148
+ ESTADO_SIGUIENTE --> ESTADO_FINAL : evento / acción
1149
+ ESTADO_FINAL --> [*]
1150
+ ```
1151
+
1152
+ > Restricciones de transición: [explicar qué transiciones están prohibidas y por qué — estas son invariantes implícitas de la máquina de estados]
1153
+
1154
+ ## Diagrama de interacciones
1155
+
1156
+ > Muestra el flujo completo: qué llega al módulo (endpoint HTTP o evento asíncrono), qué caso de uso se ejecuta y qué evento se emite como resultado.
1157
+
1158
+ ```mermaid
1159
+ flowchart TD
1160
+ subgraph HTTP["Endpoints REST"]
1161
+ EP1["METHOD /path"]
1162
+ end
1163
+
1164
+ subgraph ASYNC_IN["Eventos entrantes"]
1165
+ EI1["📥 NombreDelEventoEvent"]
1166
+ end
1167
+
1168
+ subgraph USE_CASES["Casos de uso"]
1169
+ UC1["NombreDelUseCase"]
1170
+ end
1171
+
1172
+ subgraph EVENTS_OUT["Eventos emitidos"]
1173
+ EO1["📤 NombreDelEventoEmitidoEvent"]
1174
+ end
1175
+
1176
+ EP1 --> UC1
1177
+ EI1 --> UC2["OtroUseCase"]
1178
+ UC1 --> EO1
1179
+ UC2 -->|"sin evento"| FIN([fin])
1180
+ ```
1181
+
1182
+ > **Convención de colores / nodos:**
1183
+ > - Endpoints HTTP → nodos rectangulares con el método y path
1184
+ > - Eventos entrantes → nodos con prefijo `📥`
1185
+ > - Casos de uso → nodos rectangulares con el nombre del handler
1186
+ > - Eventos emitidos → nodos con prefijo `📤`
1187
+ > - Flujos sin evento de salida → conectar al nodo `FIN([fin])`
1188
+ > - Omitir subgraph `ASYNC_IN` si el módulo no consume eventos
1189
+ > - Omitir subgraph `EVENTS_OUT` si el módulo no emite eventos
1190
+
1191
+ ## Diagrama de secuencia
1192
+ [Muestra las interacciones cronológicas entre actores y componentes para los flujos principales. Generar un diagrama por cada caso de uso complejo o flujo con bifurcaciones significativas (happy path + rama de error/compensación).]
1193
+
1194
+ > **Rol de experto en diagramas de sistemas:** modela cada actor involucrado — usuarios externos, sistemas externos, módulos del dominio, broker de mensajes, base de datos — y sus interacciones en orden temporal. Priorizar claridad: mostrar qué mensaje se envía, quién responde y en qué orden. Las respuestas asíncronas (eventos Kafka) deben modelarse con líneas punteadas.
1195
+
1196
+ ```mermaid
1197
+ sequenceDiagram
1198
+ actor Client as Client
1199
+ participant API as REST API
1200
+ participant Handler as CommandHandler
1201
+ participant Domain as Domain Entity
1202
+ participant Repo as Repository
1203
+ participant Broker as Message Broker
1204
+ participant ExtSvc as External Service
1205
+
1206
+ Client->>API: POST /resource {payload}
1207
+ API->>Handler: CreateResourceCommand
1208
+ Handler->>Repo: findById(id)
1209
+ Repo-->>Handler: entity (or 404)
1210
+ Handler->>Domain: businessMethod()
1211
+ Domain-->>Handler: updated entity
1212
+ Handler->>Repo: save(entity)
1213
+ Handler->>Broker: publish(ResourceCreatedEvent)
1214
+ Broker-->>Handler: ack
1215
+ Handler-->>API: resourceId
1216
+ API-->>Client: 201 Created {resourceId}
1217
+ ```
1218
+
1219
+ > **Convención de actores en el diagrama de secuencia:**
1220
+ > - `actor Client` → usuarios humanos o sistemas externos que inician el flujo
1221
+ > - `participant API` → controlador REST del módulo
1222
+ > - `participant Handler` → CommandHandler / QueryHandler de la capa de aplicación
1223
+ > - `participant Domain` → entidad de dominio (incluir cuando la lógica de negocio es relevante para entender el flujo)
1224
+ > - `participant Repo` → repositorio (abstracción de persistencia)
1225
+ > - `participant Broker` → broker de mensajes — omitir si el módulo no emite eventos
1226
+ > - `participant ExtSvc` → servicio externo via `ports:` — omitir si no aplica; usar el nombre real del port (ej: `PaymentGateway`)
1227
+ > - Respuestas síncronas: `-->>` (línea punteada con flecha)
1228
+ > - Mensajes async / eventos publicados: `--)` (línea punteada sin flecha de retorno inmediato)
1229
+ > - Incluir un diagrama separado por cada flujo principal; omitir flujos triviales de solo lectura si no aportan información nueva
1230
+
1231
+ ## Casos de uso
1232
+ [Un apartado `###` por cada useCase declarado en `exposes:` y en `consumers[].useCase` donde este módulo es consumidor]
1233
+
1234
+ ### {NombreDelUseCase}
1235
+ **Tipo:** `HTTP` | `Evento entrante` (indicar cuál)
1236
+ **Qué hace:** [descripción detallada de la lógica de negocio]
1237
+ **Precondiciones:** [estados válidos, entidades que deben existir]
1238
+ **Postcondiciones:** [estado del sistema tras ejecución exitosa]
1239
+ **Invariantes verificadas:** [lista de IDs — ej: INV-01, INV-03]
1240
+ **Validaciones y errores:** [condiciones que lanzan excepción, tipo de error y HTTP status]
1241
+ **Eventos que emite:** [nombre del DomainEvent y condición, o "ninguno"]
1242
+
1243
+ **Diagrama de flujo:**
1244
+ ```mermaid
1245
+ flowchart TD
1246
+ IN["🔵 Trigger: METHOD /path · or EventName"] --> UC["⚙️ {NombreDelUseCase}"]
1247
+ UC --> INV{"Check invariants"}
1248
+ INV -->|"violated"| ERR[/"Exception / Error Response"/]
1249
+ INV -->|"passed"| BIZ["Execute business logic"]
1250
+ BIZ --> SAVE["Persist changes"]
1251
+ SAVE -->|"if applicable"| EV["📤 DomainEvent emitted"]
1252
+ SAVE -->|"no event"| FIN([end])
1253
+ ```
1254
+ > Adaptar al flujo real: incluir nodos y bifurcaciones de negocio relevantes; omitir los que no participan en este caso de uso específico.
1255
+
1256
+ ## Endpoints expuestos
1257
+ [Solo si el módulo tiene entradas en `exposes:`]
1258
+
1259
+ ### {METHOD} {/path}
1260
+ **Caso de uso:** `{UseCase}`
1261
+ **Propósito:** [descripción del endpoint]
1262
+ **Path params / Query params:** [descripción de cada parámetro]
1263
+ **Request body:** [campos esperados, tipos, restricciones de validación]
1264
+ **Response:** [campos devueltos y su significado de negocio]
1265
+ **Errores:** [HTTP status codes posibles y cuándo ocurren]
1266
+
1267
+ ## Eventos emitidos
1268
+ [Solo si el módulo es producer en `integrations.async`]
1269
+
1270
+ ### {NombreDelEvento}
1271
+ **Cuándo:** [condición exacta de negocio que dispara el evento]
1272
+ **Payload:** [campos del evento con descripción de cada uno]
1273
+ **Consumidores y sus acciones:**
1274
+ - `{modulo}` → `{useCase}`: [qué hace el consumidor al recibirlo]
1275
+
1276
+ ## Puertos (llamadas síncronas salientes)
1277
+ [Solo si el módulo aparece como `caller` en `integrations.sync`]
1278
+
1279
+ ### {NombreDelPort} → {modulo-destino}
1280
+ **Cuándo se llama:** [en qué caso de uso y bajo qué condición]
1281
+ **Endpoints usados:** [lista de METHOD /path]
1282
+ **Datos que obtiene y cómo los usa:** [descripción detallada]
1283
+ ```
1284
+
1285
+ ### Reglas de los archivos de módulo
1286
+
1287
+ - **Todo el contenido en inglés**: títulos, secciones, descripciones, invariantes, casos de uso — siempre en inglés.
1288
+ - **Las INVARIANTES son obligatorias**: todo módulo debe tener al menos 2–3 invariantes. Si no se te ocurren, analiza las reglas implícitas del negocio (unicidad, estados válidos, rangos, precondiciones de transición).
1289
+ - **El diagrama de interacciones (flowchart) es obligatorio**: debe incluir todos los endpoints y eventos entrantes del módulo. Si el módulo no tiene entradas HTTP ni eventos, usar un nodo `[[módulo pasivo]]`.
1290
+ - **El diagrama de secuencia es obligatorio**: generar al menos un diagrama `sequenceDiagram` cubriendo el flujo principal (happy path) del módulo. Agregar diagramas adicionales para flujos con bifurcaciones significativas (error, compensación, async). Actuar como experto en diagramas de sistemas: modelar todos los actores reales (usuarios, API, handlers, dominio, repositorio, broker, servicios externos).
1291
+ - **Cada caso de uso debe incluir su propio diagrama de flujo**: generar un `flowchart TD` dentro de cada sección `### {UseCase}` mostrando el trigger de entrada (endpoint HTTP o evento), las verificaciones de invariantes, la lógica de negocio y los eventos emitidos. Adaptar al flujo específico; omitir nodos que no participan.
1292
+ - **La máquina de estados es condicional**: incluirla solo si el módulo gestiona entidades con ciclo de vida (estados). Las restricciones de transición son invariantes implícitas y deben listarse también en la tabla de invariantes.
1293
+ - **Cada caso de uso debe referenciar las invariantes**: usar los IDs de la tabla (INV-01, INV-02…) en el campo `Invariants verified`.
1294
+ - **No duplicar**: el contenido de `system.md` es un resumen; el archivo de módulo es la especificación completa. No copiar exactamente el mismo texto.
1295
+ - Los archivos se guardan en `system/{nombre-del-modulo}.md` — mismo directorio que `system.yaml`.
1296
+
1297
+ ### Ejemplo condensado — `system/payments.md`
1298
+
1299
+ ```markdown
1300
+ # payments — Especificación técnica
1301
+
1302
+ ## Rol del módulo
1303
+
1304
+ El módulo `payments` es el único responsable del ciclo de vida de los pagos vinculados
1305
+ a reservas. Gestiona la comunicación con la pasarela de pago externa, registra el estado
1306
+ de cada transacción y decide cuándo emitir los eventos que desencadenan cambios en otros
1307
+ módulos. No conoce la lógica interna de reservas ni de notificaciones.
1308
+
1309
+ No es responsabilidad de este módulo confirmar reservas ni enviar notificaciones;
1310
+ esos flujos son iniciados por los eventos que payments emite tras cada transacción.
1311
+
1312
+ ## Invariantes
1313
+
1314
+ | ID | Invariante | Consecuencia de violación |
1315
+ |----|-----------|---------------------------|
1316
+ | INV-01 | Solo puede existir un pago activo (PENDING o APPROVED) por reserva en cualquier momento | Lanza `409 Conflict` — impide doble cobro |
1317
+ | INV-02 | Un pago CANCELLED o FAILED no puede pasar a APPROVED | Lanza `InvalidStateTransitionException` |
1318
+ | INV-03 | El monto del pago debe ser > 0 | Lanza `400 Bad Request` — ninguna operación de cobro puede procesarse con monto cero o negativo |
1319
+ | INV-04 | Un pago solo puede reembolsarse si está en estado APPROVED | Lanza `409 Conflict` — no se puede reembolsar lo que no fue cobrado |
1320
+
1321
+ ## Máquina de estados
1322
+
1323
+ ```mermaid
1324
+ stateDiagram-v2
1325
+ [*] --> PENDING : CreatePayment
1326
+ PENDING --> APPROVED : ApprovePayment (pasarela confirma)
1327
+ PENDING --> FAILED : FailPayment (pasarela rechaza)
1328
+ APPROVED --> REFUNDED : RefundPayment
1329
+ FAILED --> [*]
1330
+ REFUNDED --> [*]
1331
+ ```
1332
+
1333
+ > Restricciones: CANCELLED y FAILED son estados terminales (INV-02). Solo APPROVED puede transicionar a REFUNDED (INV-04).
1334
+
1335
+ ## Diagrama de interacciones
1336
+
1337
+ ```mermaid
1338
+ flowchart TD
1339
+ subgraph HTTP["Endpoints REST"]
1340
+ EP1["POST /payments"]
1341
+ EP2["GET /payments/{id}"]
1342
+ EP3["POST /payments/{id}/refund"]
1343
+ end
1344
+
1345
+ subgraph ASYNC_IN["Eventos entrantes"]
1346
+ EI1["📥 ReservationCreatedEvent"]
1347
+ end
1348
+
1349
+ subgraph USE_CASES["Casos de uso"]
1350
+ UC1["CreatePayment"]
1351
+ UC2["GetPayment"]
1352
+ UC3["RefundPayment"]
1353
+ UC4["HandleReservationCreated"]
1354
+ end
1355
+
1356
+ subgraph EVENTS_OUT["Eventos emitidos"]
1357
+ EO1["📤 PaymentApprovedEvent"]
1358
+ EO2["📤 PaymentFailedEvent"]
1359
+ EO3["📤 PaymentRefundedEvent"]
1360
+ end
1361
+
1362
+ EP1 --> UC1
1363
+ EP2 --> UC2
1364
+ EP3 --> UC3
1365
+ EI1 --> UC4
1366
+ UC4 --> UC1
1367
+ UC1 --> EO1
1368
+ UC1 --> EO2
1369
+ UC3 --> EO3
1370
+ UC2 -->|"solo lectura"| FIN([fin])
1371
+ ```
1372
+
1373
+ ## Diagrama de secuencia
1374
+
1375
+ ```mermaid
1376
+ sequenceDiagram
1377
+ actor Client as Client
1378
+ participant API as REST API (PaymentController)
1379
+ participant Handler as CreatePaymentHandler
1380
+ participant Domain as Payment (Domain Entity)
1381
+ participant Repo as PaymentRepository
1382
+ participant GW as PaymentGateway (port)
1383
+ participant Broker as Kafka
1384
+
1385
+ Note over Client,Broker: Flow: CreatePayment (triggered by ReservationCreatedEvent or direct HTTP)
1386
+
1387
+ Client->>API: POST /payments {reservationId, paymentMethod}
1388
+ API->>Handler: CreatePaymentCommand
1389
+ Handler->>Repo: findActiveByReservationId(reservationId)
1390
+ Repo-->>Handler: empty (INV-01 check passed)
1391
+ Handler->>GW: processPayment({amount, method})
1392
+ GW-->>Handler: {paymentId, status: APPROVED}
1393
+ Handler->>Domain: new Payment(...) + approve()
1394
+ Domain-->>Handler: Payment [APPROVED]
1395
+ Handler->>Repo: save(payment)
1396
+ Handler->>Broker: publish(PaymentApprovedEvent)
1397
+ Broker--)API: ack
1398
+ Handler-->>API: paymentId
1399
+ API-->>Client: 201 Created {paymentId, status}
1400
+
1401
+ Note over Client,Broker: Failure branch: gateway rejects the charge
1402
+ GW-->>Handler: error / rejection
1403
+ Handler->>Domain: new Payment(...) + fail()
1404
+ Domain-->>Handler: Payment [FAILED]
1405
+ Handler->>Repo: save(payment)
1406
+ Handler->>Broker: publish(PaymentFailedEvent)
1407
+ Handler-->>API: 422 Unprocessable Entity
1408
+ API-->>Client: 422 {reason}
1409
+ ```
1410
+
1411
+ ## Casos de uso
1412
+
1413
+ ### HandleReservationCreated
1414
+ **Tipo:** Evento entrante (`ReservationCreatedEvent`)
1415
+ **Qué hace:** Extrae `reservationId` y `totalAmount` del payload y dispara `CreatePayment`
1416
+ internamente para iniciar el cobro de forma automática.
1417
+ **Precondiciones:** Payload contiene `reservationId` válido y `totalAmount > 0`.
1418
+ **Postcondiciones:** Pago creado en estado PENDING.
1419
+ **Invariantes verificadas:** INV-01, INV-03
1420
+ **Validaciones y errores:** Si INV-01 se viola (ya existe pago activo), descarta el evento
1421
+ y registra warning. Si `totalAmount ≤ 0`, lanza `400`.
1422
+ **Eventos que emite:** ninguno directamente; la aprobación llega asíncronamente.
1423
+
1424
+ **Diagrama de flujo:**
1425
+ ```mermaid
1426
+ flowchart TD
1427
+ IN["📥 ReservationCreatedEvent"] --> UC["⚙️ HandleReservationCreated"]
1428
+ UC --> INV1{"INV-01: active payment for reservation?"}
1429
+ INV1 -->|"Yes"| WARN[/"warn — discard event"/]
1430
+ INV1 -->|"No"| INV2{"INV-03: amount > 0?"}
1431
+ INV2 -->|"No"| ERR[/"400 Bad Request"/]
1432
+ INV2 -->|"Yes"| CREATE["Create Payment PENDING"]
1433
+ CREATE --> SAVE["Persist Payment"]
1434
+ SAVE --> FIN([end — approval arrives async])
1435
+ ```
1436
+ ```
1437
+
1438
+ ---
1439
+
1440
+ ## Ciclo de refinamiento
1441
+
1442
+ Después de entregar el `system.yaml` v1, el usuario puede pedir ajustes:
1443
+ - Aplica el **cambio mínimo** necesario (no rehaces todo el archivo)
1444
+ - Vuelve a validar el checklist del Paso 4
1445
+ - Actualiza `system/system.md`, `system/system.mmd`, `system/{module}.yaml` **y** `system/{module}.md` del módulo afectado
1446
+ - Entrega solo el diff explicado, no los archivos completos de nuevo (salvo que se pida)