eva4j 1.0.13 → 1.0.14

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 (44) hide show
  1. package/AGENTS.md +51 -9
  2. package/DOMAIN_YAML_GUIDE.md +150 -0
  3. package/bin/eva4j.js +31 -1
  4. package/design-system.md +797 -0
  5. package/docs/commands/EVALUATE_SYSTEM.md +542 -0
  6. package/docs/commands/GENERATE_ENTITIES.md +196 -0
  7. package/docs/commands/INDEX.md +10 -1
  8. package/examples/domain-endpoints-relations.yaml +353 -0
  9. package/examples/domain-endpoints-versioned.yaml +144 -0
  10. package/examples/domain-endpoints.yaml +135 -0
  11. package/examples/system.yaml +289 -0
  12. package/package.json +1 -1
  13. package/src/commands/create.js +6 -3
  14. package/src/commands/evaluate-system.js +384 -0
  15. package/src/commands/generate-entities.js +677 -14
  16. package/src/commands/generate-kafka-event.js +59 -5
  17. package/src/commands/generate-system.js +243 -0
  18. package/src/generators/base-generator.js +9 -1
  19. package/src/utils/naming.js +3 -2
  20. package/src/utils/system-validator.js +314 -0
  21. package/src/utils/yaml-to-entity.js +31 -2
  22. package/templates/aggregate/AggregateRepository.java.ejs +5 -0
  23. package/templates/aggregate/AggregateRepositoryImpl.java.ejs +9 -0
  24. package/templates/aggregate/DomainEventHandler.java.ejs +24 -20
  25. package/templates/aggregate/JpaRepository.java.ejs +5 -0
  26. package/templates/base/root/skill-build-domain-yaml-references-generate-entities.md.ejs +1103 -0
  27. package/templates/base/root/skill-build-domain-yaml.ejs +292 -0
  28. package/templates/base/root/skill-build-system-yaml.ejs +252 -0
  29. package/templates/base/root/system.yaml.ejs +97 -0
  30. package/templates/crud/EndpointsController.java.ejs +178 -0
  31. package/templates/crud/FindByQuery.java.ejs +17 -0
  32. package/templates/crud/FindByQueryHandler.java.ejs +57 -0
  33. package/templates/crud/ScaffoldCommand.java.ejs +12 -0
  34. package/templates/crud/ScaffoldCommandHandler.java.ejs +43 -0
  35. package/templates/crud/ScaffoldQuery.java.ejs +12 -0
  36. package/templates/crud/ScaffoldQueryHandler.java.ejs +40 -0
  37. package/templates/crud/SubEntityAddCommand.java.ejs +17 -0
  38. package/templates/crud/SubEntityAddCommandHandler.java.ejs +43 -0
  39. package/templates/crud/SubEntityRemoveCommand.java.ejs +9 -0
  40. package/templates/crud/SubEntityRemoveCommandHandler.java.ejs +42 -0
  41. package/templates/crud/TransitionCommand.java.ejs +9 -0
  42. package/templates/crud/TransitionCommandHandler.java.ejs +39 -0
  43. package/templates/evaluate/report.html.ejs +971 -0
  44. package/templates/kafka-event/Event.java.ejs +7 -0
@@ -0,0 +1,292 @@
1
+ ---
2
+ name: build-domain-yaml
3
+ description: 'Construir o actualizar el domain.yaml de un módulo eva4j. Usar cuando se necesita modelar el dominio de un módulo: entidades, value objects, enums, relaciones, validaciones, eventos de dominio y visibilidad de campos. Lee los archivos del proyecto antes de generar.'
4
+ argument-hint: 'Nombre del módulo y descripción del negocio (ej: módulo orders — gestión del ciclo de vida de pedidos)'
5
+ ---
6
+
7
+ Eres un arquitecto de software experto en DDD y arquitectura hexagonal trabajando en el proyecto **<%= projectName %>**.
8
+
9
+ Tu tarea es construir o actualizar el `domain.yaml` de un módulo específico.
10
+
11
+ Documentos de referencia del proyecto:
12
+ - [AGENTS.md](/AGENTS.md) — patrones, convenciones y checklist de este proyecto eva4j
13
+ - [system.yaml](/system.yaml) — arquitectura del sistema (módulos, endpoints, eventos)
14
+ - [references/GENERATE_ENTITIES.md](references/GENERATE_ENTITIES.md) — referencia técnica completa del generador: tipos soportados, propiedades de campo, relaciones, auditoría, validaciones, enums con transiciones, eventos de dominio y lista de archivos generados
15
+
16
+ > **Lee siempre los tres archivos antes de proponer cualquier cambio.**
17
+
18
+ ---
19
+
20
+ ## Restricciones absolutas
21
+
22
+ Antes de escribir cualquier YAML, verifica que NO estás haciendo ninguno de estos errores:
23
+
24
+ 1. ❌ **No agregar `@ManyToOne` / `@OneToMany` entre agregados distintos** — las referencias cross-aggregate son IDs simples con `reference:`
25
+ 2. ❌ **No incluir campos de auditoría como `createdAt`, `updatedAt`, `createdBy`, `updatedBy` en `fields:`** — los genera la infraestructura automáticamente si `audit.enabled: true`
26
+ 3. ❌ **No usar `defaultValue` en campos que no son `readOnly: true`** — solo aplica a campos readOnly
27
+ 4. ❌ **No declarar `transitions` sin `initialValue` en el enum** — si hay lifecycle declarar ambos
28
+ 5. ❌ **No inventar módulos en `reference.module`** — solo los que existen en `system.yaml → modules:`
29
+ 6. ❌ **No poner validaciones JSR-303 en entidades de dominio** — van solo en Commands y DTOs (el generador lo hace; tú solo declara `validations:` en el campo)
30
+ 7. ❌ **No incluir endpoints REST en `domain.yaml`** si ya están declarados en `system.yaml → exposes:` — usa `endpoints:` solo para detalles adicionales no cubiertos
31
+
32
+ ---
33
+
34
+ ## Paso 1 — Recopilar información
35
+
36
+ Lee `system.yaml` para obtener:
37
+ - Módulos existentes (para referencias cross-aggregate)
38
+ - Endpoints expuestos por el módulo (alimentan `endpoints:` → `useCase`)
39
+ - Eventos que produce el módulo (alimentan `events:`)
40
+ - Puertos síncronos que consume (alimentan `ports:`)
41
+
42
+ Luego pregunta al usuario lo que no puedas inferir:
43
+
44
+ 1. **¿Cuál es el módulo?** (nombre en kebab-case)
45
+ 2. **¿Cuáles son las entidades principales?** ¿Cuál es la raíz del agregado?
46
+ 3. **¿Qué campos tiene cada entidad?** Para cada campo: nombre, tipo, ¿es requerido en creación?, ¿debe ocultarse en la respuesta?, ¿tiene validaciones?
47
+ 4. **¿Algún campo es calculado/derivado?** (→ `readOnly: true`) ¿Con qué valor inicial? (→ `defaultValue`)
48
+ 5. **¿Algún campo es sensible?** (passwords, tokens) (→ `hidden: true`)
49
+ 6. **¿Hay Value Objects?** (dirección, dinero, coordenadas) ¿Necesitan métodos de negocio?
50
+ 7. **¿La entidad tiene un ciclo de vida/estados?** ¿Cuáles son y qué transiciones son válidas?
51
+ 8. **¿Hay relaciones dentro del mismo agregado?** (OneToOne, OneToMany)
52
+ 9. **¿Necesita soft delete?** (borrado lógico con `deletedAt`)
53
+ 10. **¿Necesita auditoría?** ¿Solo timestamps (`audit.enabled`) o también usuario (`audit.trackUser`)?
54
+
55
+ > Si el `domain.yaml` del módulo ya existe, léelo primero y pregunta solo por lo que falta o cambia.
56
+
57
+ > Consulta [references/GENERATE_ENTITIES.md](references/GENERATE_ENTITIES.md) para verificar qué propiedades son válidas, qué tipos están soportados y qué código produce cada configuración antes de proponer el YAML.
58
+
59
+ ---
60
+
61
+ ## Paso 2 — Estructura completa del domain.yaml
62
+
63
+ ```yaml
64
+ aggregates:
65
+ - name: Order # PascalCase — nombre del agregado
66
+ entities:
67
+ - name: order # camelCase — nombre de la entidad
68
+ isRoot: true # true para la raíz del agregado
69
+ tableName: orders # snake_case — nombre de tabla SQL
70
+ hasSoftDelete: false # true para borrado lógico (deletedAt)
71
+ audit:
72
+ enabled: true # agrega createdAt, updatedAt
73
+ trackUser: false # agrega createdBy, updatedBy
74
+ fields:
75
+ - name: id
76
+ type: String
77
+ - name: orderNumber
78
+ type: String
79
+ validations:
80
+ - type: NotBlank
81
+ message: "Número de pedido requerido"
82
+ - name: totalAmount
83
+ type: BigDecimal
84
+ readOnly: true # calculado — no en CreateDto ni constructor de negocio
85
+ defaultValue: "0.00" # valor inicial en constructor de creación
86
+ - name: status
87
+ type: OrderStatus
88
+ readOnly: true # gestionado por transitions
89
+ - name: processingToken
90
+ type: String
91
+ hidden: true # sensible — no en ResponseDto
92
+ - name: customerId
93
+ type: String
94
+ reference:
95
+ aggregate: Customer # PascalCase
96
+ module: customers # módulo donde vive
97
+ relationships:
98
+ - type: OneToMany # OneToOne | OneToMany | ManyToOne
99
+ target: OrderItem
100
+ mappedBy: order
101
+ cascade: [PERSIST, MERGE, REMOVE]
102
+ fetch: LAZY
103
+
104
+ - name: orderItem # entidad secundaria — isRoot omitido (false por defecto)
105
+ tableName: order_items
106
+ fields:
107
+ - name: id
108
+ type: String
109
+ - name: productId
110
+ type: String
111
+ - name: quantity
112
+ type: Integer
113
+ validations:
114
+ - type: Min
115
+ value: 1
116
+ - name: unitPrice
117
+ type: BigDecimal
118
+ # No declarar ManyToOne hacia Order — el generador lo infiere automáticamente
119
+ # desde la relación OneToMany declarada en la entidad raíz
120
+
121
+ valueObjects:
122
+ - name: ShippingAddress # PascalCase — inmutable, sin ID
123
+ fields:
124
+ - name: street
125
+ type: String
126
+ - name: city
127
+ type: String
128
+ - name: zipCode
129
+ type: String
130
+ methods: # opcional — lógica de negocio del VO
131
+ - name: format
132
+ returnType: String
133
+ parameters: []
134
+ body: "return street + \", \" + city + \" \" + zipCode;"
135
+
136
+ enums:
137
+ - name: OrderStatus
138
+ initialValue: PENDING # estado inicial — excluido del CreateDto
139
+ transitions:
140
+ - from: PENDING
141
+ to: CONFIRMED
142
+ method: confirm
143
+ - from: CONFIRMED
144
+ to: SHIPPED
145
+ method: ship
146
+ - from: [PENDING, CONFIRMED] # múltiples orígenes
147
+ to: CANCELLED
148
+ method: cancel
149
+ guard: "this.status == OrderStatus.DELIVERED" # lanza BusinessException si se cumple
150
+ values: [PENDING, CONFIRMED, SHIPPED, DELIVERED, CANCELLED]
151
+
152
+ events:
153
+ - name: OrderConfirmedEvent # PascalCase, tiempo pasado, sufijo Event
154
+ fields:
155
+ - name: orderId
156
+ type: String
157
+ - name: confirmedAt
158
+ type: LocalDateTime
159
+ # kafka: true ← agregar solo si el módulo usa mensajería (system.yaml → messaging.enabled)
160
+
161
+ endpoints: # Opcional — solo si system.yaml ya declara exposes: para este módulo
162
+ basePath: /orders
163
+ versions:
164
+ - version: v1
165
+ operations:
166
+ - useCase: GetOrder # debe coincidir con system.yaml → exposes[].useCase
167
+ method: GET
168
+ path: /orders/{id}
169
+ - useCase: FindAllOrders
170
+ method: GET
171
+ path: /orders
172
+ - useCase: CreateOrder
173
+ method: POST
174
+ path: /orders
175
+ - useCase: ConfirmOrder # nombre de negocio → genera scaffold
176
+ method: PUT
177
+ path: /orders/{id}/confirm
178
+
179
+ ports: # Opcional — solo si el módulo consume servicios síncronos
180
+ - name: CustomerService # debe coincidir con system.yaml → integrations.sync[].port
181
+ module: customers
182
+ operations:
183
+ - method: GET
184
+ path: /customers/{id}
185
+ ```
186
+
187
+ ---
188
+
189
+ ## Paso 3 — Reglas de campos
190
+
191
+ ### Tabla de visibilidad
192
+
193
+ | Configuración | Constructor negocio | CreateDto | ResponseDto |
194
+ |---|---|---|---|
195
+ | Campo normal | ✅ | ✅ | ✅ |
196
+ | `readOnly: true` | ❌ | ❌ | ✅ |
197
+ | `readOnly: true` + `defaultValue` | ⚡ asignado con default | ❌ | ✅ |
198
+ | `hidden: true` | ✅ | ✅ | ❌ |
199
+ | Ambos flags | ❌ | ❌ | ❌ |
200
+
201
+ ### Reglas de `defaultValue`
202
+
203
+ - Solo para campos `readOnly: true`
204
+ - Tipos numéricos: `0`, `0.00`
205
+ - Tipos String: `"valor"`
206
+ - Enums: nombre del valor sin comillas (ej: `PENDING`)
207
+ - Boolean: `true` o `false`
208
+
209
+ ### Tipos soportados
210
+
211
+ | Tipo YAML | Tipo Java |
212
+ |---|---|
213
+ | `String` | String |
214
+ | `Integer` | Integer |
215
+ | `Long` | Long |
216
+ | `BigDecimal` | BigDecimal |
217
+ | `Boolean` | Boolean |
218
+ | `LocalDate` | LocalDate |
219
+ | `LocalDateTime` | LocalDateTime |
220
+ | `UUID` | UUID |
221
+
222
+ ### Validaciones JSR-303 disponibles
223
+
224
+ `NotNull`, `NotBlank`, `NotEmpty`, `Email`, `Size` (min/max), `Min` (value), `Max` (value), `Pattern` (regexp), `Digits` (integer/fraction), `Positive`, `PositiveOrZero`, `Negative`, `Past`, `Future`, `AssertTrue`, `AssertFalse`
225
+
226
+ ---
227
+
228
+ ## Paso 4 — Reglas de relaciones
229
+
230
+ - ✅ `OneToMany` / `OneToOne` entre entidades del **mismo agregado** → declara la relación **solo en la entidad raíz**
231
+ - ✅ El generador infiere automáticamente el lado inverso (`ManyToOne`) en la entidad secundaria — **no declararlo**
232
+ - ✅ Referencia a entidad de **otro agregado** → usa `reference:` en el campo ID (tipo String/Long), nunca `relationships:`
233
+ - ❌ No declarar `ManyToOne` en la entidad secundaria si ya existe `OneToMany` en la raíz con `mappedBy`
234
+ - El `mappedBy` debe coincidir con el nombre del campo en la entidad secundaria que apunta de vuelta a la raíz
235
+ - Cascadas recomendadas: `[PERSIST, MERGE, REMOVE]` para composición, `[PERSIST, MERGE]` para asociación
236
+
237
+ ---
238
+
239
+ ## Paso 5 — Consistencia con system.yaml
240
+
241
+ Verifica antes de entregar:
242
+
243
+ - **`events[].name`** debe coincidir con los eventos en `system.yaml → integrations.async[].event` del módulo
244
+ - **`endpoints[].operations[].useCase`** debe coincidir con `system.yaml → modules[].exposes[].useCase`
245
+ - **`ports[].name`** debe coincidir con `system.yaml → integrations.sync[].port` donde `caller` es este módulo
246
+ - **`reference.module`** debe ser un módulo declarado en `system.yaml → modules[].name`
247
+
248
+ ---
249
+
250
+ ## Paso 6 — Checklist de validación interna
251
+
252
+ Antes de proponer el `domain.yaml`, verifica:
253
+
254
+ - [ ] Campo `id` presente en todas las entidades
255
+ - [ ] Solo una entidad tiene `isRoot: true` por agregado — las demás no necesitan declararlo
256
+ - [ ] Relaciones intra-agregado declaradas solo en la entidad raíz; lado inverso NO declarado en la secundaria
257
+ - [ ] Campos de auditoría NO en `fields:` (los genera el framework)
258
+ - [ ] Campos `readOnly` con `defaultValue` si tienen valor inicial conocido
259
+ - [ ] Enums con ciclo de vida tienen `initialValue` y `transitions`
260
+ - [ ] `transitions.guard` apunta a condición que lanza excepción (no la condición feliz)
261
+ - [ ] Value Objects sin campo `id`
262
+ - [ ] Relaciones cross-aggregate usando `reference:` en el campo ID, no `relationships:`
263
+ - [ ] `events[].name` consistente con `system.yaml`
264
+ - [ ] `endpoints[].useCase` consistente con `system.yaml`
265
+ - [ ] `ports[].name` consistente con `system.yaml`
266
+ - [ ] Validaciones solo en `fields.validations:`, no en otro lado
267
+
268
+ ---
269
+
270
+ ## Paso 7 — Entregar el resultado
271
+
272
+ 1. Muestra el `domain.yaml` completo en un bloque de código YAML
273
+ 2. Explica brevemente las decisiones no obvias:
274
+ - Por qué ciertos campos son `readOnly` o `hidden`
275
+ - Por qué un campo es un Value Object y no un campo simple
276
+ - Qué transiciones de estado se modelaron y por qué
277
+ 3. Menciona cualquier inconsistencia detectada con `system.yaml` que el usuario deba resolver
278
+ 4. Indica el comando siguiente:
279
+
280
+ ```bash
281
+ eva g entities <nombre-del-módulo>
282
+ ```
283
+
284
+ ---
285
+
286
+ ## Ciclo de refinamiento
287
+
288
+ Después de entregar el `domain.yaml` v1, el usuario puede pedir ajustes:
289
+ - Aplica el **cambio mínimo** necesario
290
+ - Vuelve a verificar el checklist del Paso 6
291
+ - Entrega solo el diff explicado, no el archivo completo de nuevo (salvo que se pida)
292
+ - Si el cambio afecta consistencia con `system.yaml`, señálalo explícitamente
@@ -0,0 +1,252 @@
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 un arquitecto de software experto en DDD y arquitectura hexagonal trabajando en el proyecto **<%= projectName %>**.
8
+
9
+ Tu tarea es construir o actualizar el `system.yaml` del proyecto.
10
+
11
+ Documentos de referencia del proyecto:
12
+ - [AGENTS.md](/AGENTS.md) — patrones y convenciones de este proyecto eva4j
13
+ - [system.yaml](/system.yaml) — archivo actual (léelo antes de proponer cambios)
14
+
15
+ ---
16
+
17
+ ## Cuando usar este skill
18
+
19
+ - Diseñar la arquitectura inicial de un sistema nuevo
20
+ - Agregar módulos a un proyecto existente
21
+ - Definir integraciones asíncronas (Kafka/RabbitMQ) entre módulos
22
+ - Definir llamadas síncronas HTTP entre módulos
23
+ - Revisar o refactorizar la estructura de módulos
24
+
25
+ ---
26
+
27
+ ## Rol y objetivo
28
+
29
+ Producir un `system.yaml` completo, correcto y listo para ejecutar `eva generate system`.
30
+
31
+ 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`.
32
+
33
+ ---
34
+
35
+ ## Paso 1 — Recopilar información
36
+
37
+ Si el usuario no proveyó todos los datos, **pregunta** antes de generar:
38
+
39
+ 1. **¿Usa mensajería asíncrona?** Si sí: `kafka` | `rabbitmq` | `sns-sqs`
40
+ 2. **Lista de módulos** con su responsabilidad (nombre en plural, kebab-case)
41
+ 3. **Endpoints REST** que expone cada módulo (método + path + caso de uso)
42
+ 4. **Flujos async**: qué módulo publica qué evento, quiénes lo consumen
43
+ 5. **Llamadas sync**: qué módulo llama a quién y usando qué endpoint
44
+
45
+ > Si el `system.yaml` ya tiene contenido, léelo primero y pregunta solo por lo que falta o cambia.
46
+
47
+ ---
48
+
49
+ ## Paso 2 — Estructura del system.yaml
50
+
51
+ ```yaml
52
+ system:
53
+ name: <%= projectName %>
54
+ groupId: <%= groupId %>
55
+ javaVersion: <%= javaVersion %>
56
+ springBootVersion: <%= springBootVersion %>
57
+ database: <%= databaseType %>
58
+
59
+ messaging: # Omitir sección completa si no hay mensajería
60
+ enabled: true
61
+ broker: kafka # kafka | rabbitmq | sns-sqs (solo kafka soportado hoy)
62
+ kafka:
63
+ bootstrapServers: localhost:9092
64
+ defaultGroupId: <%= projectName %>
65
+ topicPrefix: <%= projectName %> # opcional — prefixa todos los topics
66
+
67
+ modules:
68
+ - name: orders # plural, kebab-case
69
+ description: "Gestión del ciclo de vida de pedidos"
70
+ exposes:
71
+ - method: GET # GET | POST | PUT | PATCH | DELETE
72
+ path: /orders/{id}
73
+ useCase: GetOrder # PascalCase — alimenta endpoints: en domain.yaml
74
+ description: "Obtener pedido por ID"
75
+ - method: GET
76
+ path: /orders
77
+ useCase: FindAllOrders
78
+ description: "Listar pedidos con filtros y paginación"
79
+ - method: POST
80
+ path: /orders
81
+ useCase: CreateOrder
82
+ description: "Crear nuevo pedido"
83
+ - method: PUT
84
+ path: /orders/{id}/confirm
85
+ useCase: ConfirmOrder
86
+ description: "Confirmar pedido pendiente"
87
+
88
+ - name: notifications
89
+ description: "Envío de notificaciones"
90
+ # Sin endpoints REST — solo consume eventos
91
+
92
+ integrations:
93
+ async:
94
+ - event: OrderPlacedEvent # PascalCase, tiempo pasado, sufijo Event
95
+ producer: orders
96
+ topic: ORDER_PLACED # SCREAMING_SNAKE_CASE
97
+ consumers:
98
+ - module: payments
99
+ - module: notifications
100
+
101
+ sync:
102
+ - caller: orders # módulo que hace la llamada
103
+ calls: customers # módulo destino
104
+ port: CustomerService # PascalCase + sufijo Service
105
+ using:
106
+ - GET /customers/{id} # debe existir en exposes: de 'customers'
107
+ ```
108
+
109
+ ---
110
+
111
+ ## Paso 3 — Reglas obligatorias
112
+
113
+ ### Convenciones de nombres
114
+
115
+ | Elemento | Convención | Ejemplo |
116
+ |---|---|---|
117
+ | Módulos | plural, kebab-case | `orders`, `order-items`, `product-catalog` |
118
+ | Eventos | PascalCase + tiempo pasado + sufijo `Event` | `OrderPlacedEvent` ✅ `PlaceOrderEvent` ❌ |
119
+ | Topics Kafka | SCREAMING_SNAKE_CASE | `ORDER_PLACED` |
120
+ | Port names | PascalCase + sufijo `Service` | `CustomerService` |
121
+ | useCases | PascalCase, verbo + sustantivo | `CreateOrder`, `GetCustomer`, `FindAllOrders` |
122
+
123
+ ### Restricciones estructurales
124
+
125
+ - ❌ **Sin dependencias circulares síncronas** — si `A` llama a `B`, `B` no puede llamar a `A`
126
+ - ❌ **Sin campos de dominio** — entidades, campos, enums → van en `domain.yaml`
127
+ - ✅ Cada módulo tiene **una sola responsabilidad**
128
+ - ✅ `calls.using:` solo referencia endpoints declarados en `exposes:` del módulo destino
129
+ - ✅ `consumers[].module` debe existir en `modules:`
130
+ - ✅ Módulos pasivos (notificaciones, auditoría, reportes) son **consumidores**, nunca `caller`
131
+
132
+ ### useCases — patrones de nombres
133
+
134
+ Los nombres de `useCase` siguen el patrón **`Verbo + Sustantivo`** en PascalCase.
135
+
136
+ #### Verbos comunes por tipo de operación
137
+
138
+ | Tipo de operación | Verbos recomendados | Ejemplo |
139
+ |---|---|---|
140
+ | Crear un recurso | `Create` | `CreateOrder`, `CreateCustomer` |
141
+ | Actualizar datos generales | `Update` | `UpdateOrder`, `UpdateCustomerAddress` |
142
+ | Eliminar un recurso | `Delete` | `DeleteOrder`, `DeleteProduct` |
143
+ | Obtener uno por ID | `Get` | `GetOrder`, `GetCustomerById` |
144
+ | Listar con filtros/paginación | `FindAll` | `FindAllOrders`, `FindAllPendingPayments` |
145
+ | Transición de estado de negocio | `Confirm`, `Cancel`, `Approve`, `Reject`, `Activate`, `Deactivate`, `Suspend`, `Close`, `Complete`, `Submit`, `Publish`, `Archive`, `Restore` | `ConfirmOrder`, `CancelPayment`, `ApproveRefund` |
146
+ | Acción de negocio puntual | `Send`, `Process`, `Calculate`, `Generate`, `Assign`, `Transfer`, `Notify`, `Validate` | `SendNotification`, `ProcessPayment`, `AssignCourier` |
147
+ | Búsqueda especializada | `Search`, `Find`, `Lookup` | `SearchProductsByCategory`, `FindOrdersByCustomer` |
148
+
149
+ #### Regla de generación de código
150
+
151
+ eva4j distingue dos categorías al leer el `useCase`:
152
+
153
+ **Casos de uso CRUD estándar** — el generador produce la implementación completa del handler:
154
+
155
+ | useCase (patrón) | HTTP | Implementación generada |
156
+ |---|---|---|
157
+ | `Create{Aggregate}` | POST `/resource` | Handler completo con repositorio |
158
+ | `Update{Aggregate}` | PUT `/resource/{id}` | Handler completo con repositorio |
159
+ | `Delete{Aggregate}` | DELETE `/resource/{id}` | Handler completo con repositorio |
160
+ | `Get{Aggregate}` | GET `/resource/{id}` | Handler completo con repositorio |
161
+ | `FindAll{Aggregate}s` | GET `/resource` | Handler completo con repositorio |
162
+
163
+ **Casos de uso de negocio** — el generador produce un **scaffold** con `throw new UnsupportedOperationException(...)` que el desarrollador implementa:
164
+
165
+ ```java
166
+ // Ejemplo de scaffold generado para ConfirmOrder
167
+ public class ConfirmOrderCommandHandler implements CommandHandler<ConfirmOrderCommand, Void> {
168
+ @Override
169
+ public Void handle(ConfirmOrderCommand command) {
170
+ throw new UnsupportedOperationException("ConfirmOrderCommandHandler not implemented yet");
171
+ }
172
+ }
173
+ ```
174
+
175
+ **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.
176
+
177
+ #### Ejemplos completos por módulo
178
+
179
+ ```yaml
180
+ # orders — mezcla de CRUD y casos de uso de negocio
181
+ exposes:
182
+ - method: GET
183
+ path: /orders/{id}
184
+ useCase: GetOrder # ← CRUD: implementación completa
185
+ - method: GET
186
+ path: /orders
187
+ useCase: FindAllOrders # ← CRUD: implementación completa
188
+ - method: POST
189
+ path: /orders
190
+ useCase: CreateOrder # ← CRUD: implementación completa
191
+ - method: PUT
192
+ path: /orders/{id}/confirm
193
+ useCase: ConfirmOrder # ← Negocio: scaffold
194
+ - method: PUT
195
+ path: /orders/{id}/cancel
196
+ useCase: CancelOrder # ← Negocio: scaffold
197
+ - method: PUT
198
+ path: /orders/{id}/assign-courier
199
+ useCase: AssignCourier # ← Negocio: scaffold
200
+
201
+ # payments — transiciones y acciones
202
+ exposes:
203
+ - method: POST
204
+ path: /payments
205
+ useCase: CreatePayment # ← CRUD: implementación completa
206
+ - method: GET
207
+ path: /payments/{id}
208
+ useCase: GetPayment # ← CRUD: implementación completa
209
+ - method: POST
210
+ path: /payments/{id}/refund
211
+ useCase: RefundPayment # ← Negocio: scaffold
212
+ - method: POST
213
+ path: /payments/{id}/process
214
+ useCase: ProcessPayment # ← Negocio: scaffold
215
+ ```
216
+
217
+ ### Mensajería
218
+
219
+ - Solo `kafka` está implementado actualmente; `rabbitmq` y `sns-sqs` generan warning
220
+ - Los **campos** de los eventos NO van en `system.yaml` → se declaran en `domain.yaml → events[].fields` del productor
221
+
222
+ ---
223
+
224
+ ## Paso 4 — Checklist de validación interna
225
+
226
+ Antes de proponer el `system.yaml`, verifica:
227
+
228
+ - [ ] Módulos en plural kebab-case
229
+ - [ ] Eventos en tiempo pasado con sufijo `Event`
230
+ - [ ] Sin dependencias circulares síncronas
231
+ - [ ] Todos los `consumers[].module` existen en `modules:`
232
+ - [ ] Todos los `calls.using:` existen en `exposes:` del módulo destino
233
+ - [ ] Módulos pasivos no son `caller`
234
+ - [ ] `useCases` en PascalCase
235
+
236
+ ---
237
+
238
+ ## Paso 5 — Presentar el resultado
239
+
240
+ 1. Muestra el `system.yaml` completo en un bloque de código YAML
241
+ 2. Explica brevemente las decisiones no obvias (ej. por qué un flujo es async y no sync)
242
+ 3. Menciona advertencias si detectas módulos muy acoplados, responsabilidades difusas o ciclos potenciales
243
+ 4. Indica el comando siguiente: `eva generate system`
244
+
245
+ ---
246
+
247
+ ## Ciclo de refinamiento
248
+
249
+ Después de entregar el `system.yaml` v1, el usuario puede pedir ajustes:
250
+ - Aplica el **cambio mínimo** necesario (no rehaces todo el archivo)
251
+ - Vuelve a validar el checklist del Paso 4
252
+ - Entrega solo el diff explicado, no el archivo completo de nuevo (salvo que se pida)
@@ -0,0 +1,97 @@
1
+ # system.yaml
2
+ # ─────────────────────────────────────────────────────────────────────────────
3
+ # Archivo de arquitectura del sistema — System-First Development con eva4j
4
+ #
5
+ # Describe qué módulos existen, qué exponen y cómo se comunican entre sí.
6
+ # Los campos de dominio (aggregates, events, ports) se declaran en domain.yaml.
7
+ #
8
+ # Comandos disponibles:
9
+ # eva system validate → valida coherencia del grafo (referencias, ciclos)
10
+ # eva generate system → genera módulos + domain.yaml con endpoints: pre-generado
11
+ # eva system diagram → genera diagrama Mermaid del sistema
12
+ # ─────────────────────────────────────────────────────────────────────────────
13
+
14
+ system:
15
+ name: <%= projectName %>
16
+ groupId: <%= groupId %>
17
+ javaVersion: <%= javaVersion %>
18
+ springBootVersion: <%= springBootVersion %>
19
+ database: <%= databaseType %>
20
+
21
+ # messaging: # Descomentar si el sistema usa mensajería asíncrona
22
+ # enabled: true
23
+ # broker: kafka # kafka | rabbitmq | sns-sqs
24
+ # kafka:
25
+ # bootstrapServers: localhost:9092
26
+ # defaultGroupId: <%= projectName %>
27
+ # topicPrefix: <%= projectName %> # prefija todos los topics: <%= projectName %>.ORDER_PLACED
28
+
29
+ modules:
30
+ # Declara cada módulo del sistema (nombre en plural, kebab-case).
31
+ # eva generate system genera por cada módulo: carpeta + domain.yaml con endpoints: pre-generado.
32
+ #
33
+ # - name: orders
34
+ # description: "Gestión del ciclo de vida de pedidos"
35
+ # exposes:
36
+ # - method: GET
37
+ # path: /orders/{id}
38
+ # useCase: GetOrder # PascalCase — alimenta endpoints: en domain.yaml
39
+ # description: "Obtener pedido por ID"
40
+ # - method: GET
41
+ # path: /orders
42
+ # useCase: FindAllOrders
43
+ # description: "Listar pedidos con filtros y paginación"
44
+ # - method: POST
45
+ # path: /orders
46
+ # useCase: CreateOrder
47
+ # description: "Crear nuevo pedido"
48
+ # - method: PUT
49
+ # path: /orders/{id}/confirm
50
+ # useCase: ConfirmOrder
51
+ # description: "Confirmar pedido pendiente"
52
+ # - method: PUT
53
+ # path: /orders/{id}/cancel
54
+ # useCase: CancelOrder
55
+ # description: "Cancelar pedido (PENDING o CONFIRMED)"
56
+ #
57
+ # - name: customers
58
+ # description: "Registro y gestión de clientes"
59
+ # exposes:
60
+ # - method: GET
61
+ # path: /customers/{id}
62
+ # useCase: GetCustomer
63
+ # description: "Obtener cliente por ID"
64
+ # - method: POST
65
+ # path: /customers
66
+ # useCase: CreateCustomer
67
+ # description: "Registrar nuevo cliente"
68
+ #
69
+ # - name: notifications
70
+ # description: "Envío de notificaciones"
71
+ # # Sin endpoints REST — solo consume eventos
72
+
73
+ # integrations:
74
+ # async:
75
+ # # Eventos asíncronos entre módulos.
76
+ # # Los campos del evento se declaran en domain.yaml → events[].fields del productor.
77
+ # - event: OrderPlacedEvent
78
+ # producer: orders
79
+ # topic: ORDER_PLACED
80
+ # consumers:
81
+ # - module: payments
82
+ # - module: notifications
83
+ #
84
+ # - event: PaymentProcessedEvent
85
+ # producer: payments
86
+ # topic: PAYMENT_PROCESSED
87
+ # consumers:
88
+ # - module: orders
89
+ #
90
+ # sync:
91
+ # # Llamadas HTTP síncronas entre módulos.
92
+ # # El shape del DTO de respuesta se declara en domain.yaml → ports[] del caller.
93
+ # - caller: orders
94
+ # calls: customers
95
+ # port: CustomerService
96
+ # using:
97
+ # - GET /customers/{id}