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
@@ -45,30 +45,49 @@
45
45
  #
46
46
  # ─────────────────────────────────────────────────────────────────────────────
47
47
  #
48
- # 3. orders/domain/models/entities/Order.java (modificaciones al agregado)
48
+ # 3. orders/domain/models/entities/Order.java (con triggers: declarados en events[])
49
49
  #
50
- # public class Order {
51
- # // ... campos normales ...
50
+ # // El desarrollador NO escribe raise() manualmente — el generador lo emite
51
+ # // automáticamente basándose en triggers: declarados en domain.yaml.
52
52
  #
53
- # // ─── Domain Events ───────────────────────────────────────
54
- # private final List<DomainEvent> _domainEvents = new ArrayList<>();
53
+ # // Construcción de args (en orden):
54
+ # // 1º aggregateId → this.getId() (primer arg del constructor DomainEvent, siempre)
55
+ # // 2º+ campos del evento → resolución automática:
56
+ # // · {entityName}Id (ej: orderId en Order) → this.getId()
57
+ # // · coincide con campo de la entidad → this.get{Field}()
58
+ # // · nombre termina en At + tipo LocalDateTime → LocalDateTime.now()
59
+ # // · no resuelto → null /* TODO: provide X */
55
60
  #
56
- # protected void raise(DomainEvent event) {
57
- # _domainEvents.add(event);
58
- # }
61
+ # public void place() {
62
+ # this.status = this.status.transitionTo(OrderStatus.PLACED);
63
+ # raise(new OrderPlaced(this.getId(), this.getCustomerId(), this.getOrderNumber(), this.getTotalAmount()));
64
+ # // ^—aggregateId ^—customerId ^—orderNumber ^—totalAmount
65
+ # // orderId no se pasa — disponible como event.getAggregateId() en el consumidor
66
+ # }
67
+ #
68
+ # public void cancel() {
69
+ # this.status = this.status.transitionTo(OrderStatus.CANCELLED);
70
+ # raise(new OrderCancelled(this.getId(), null /* TODO: provide reason */));
71
+ # // ^—aggregateId ^—reason no resuelto automáticamente
72
+ # }
73
+ #
74
+ # public void ship() {
75
+ # this.status = this.status.transitionTo(OrderStatus.SHIPPED);
76
+ # raise(new OrderShipped(this.getId(), null /* TODO: provide shippingAddress */, null /* TODO: provide estimatedDeliveryDate */));
77
+ # }
59
78
  #
60
- # public List<DomainEvent> pullDomainEvents() {
61
79
  # List<DomainEvent> events =
62
80
  # Collections.unmodifiableList(new ArrayList<>(_domainEvents));
63
81
  # _domainEvents.clear();
64
82
  # return events; // retorna y limpia (operación atómica)
65
83
  # }
66
84
  #
67
- # // El desarrollador llama a raise() dentro de sus métodos de negocio:
68
- # public void place(String customerId, BigDecimal total) {
69
- # this.status = this.status.transitionTo(OrderStatus.PLACED); // lanza InvalidStateTransitionException si no es válida
70
- # raise(new OrderPlacedEvent(this.id, customerId, this.orderNumber, total));
71
- # }
85
+ # // Con triggers: auto-generados NO hace falta escribir raise() a mano. Ver arriba.
86
+ # // Si un evento NO declara triggers, el dev puede llamar a raise() explicitamente:
87
+ # // public void placeManual(String customerId, BigDecimal total) {
88
+ # // this.status = OrderStatus.PLACED;
89
+ # // raise(new OrderPlaced(this.id, customerId, orderNumber, total));
90
+ # // }
72
91
  # }
73
92
  #
74
93
  # ─────────────────────────────────────────────────────────────────────────────
@@ -123,6 +142,65 @@
123
142
  # Para conectar un broker externo después de generar:
124
143
  # eva4j g kafka-event orders OrderPlaced
125
144
  # (el selector muestra los eventos declarados en este domain.yaml como opciones)
145
+ #
146
+ # ─────────────────────────────────────────────────────────────────────────────
147
+ #
148
+ # SECCIÓN listeners: — eventos externos que CONSUME este módulo
149
+ #
150
+ # Complemento de events: (producción). A diferencia de events:, que vive
151
+ # DENTRO del agregado porque pertenece al modelo de dominio, listeners: vive
152
+ # en el NIVEL RAÍZ del domain.yaml porque es una preocupación de integración.
153
+ #
154
+ # Estructura generada por cada listener:
155
+ #
156
+ # ┌─ application/events/
157
+ # │ PaymentApprovedIntegrationEvent.java ← record tipado con los fields
158
+ # │
159
+ # └─ infrastructure/kafkaListener/
160
+ # PaymentApprovedKafkaListener.java ← @KafkaListener que despacha
161
+ # al useCase via UseCaseMediator
162
+ #
163
+ # Código generado de ejemplo:
164
+ #
165
+ # // application/events/PaymentApprovedIntegrationEvent.java
166
+ # public record PaymentApprovedIntegrationEvent(
167
+ # String orderId,
168
+ # String paymentId,
169
+ # LocalDateTime approvedAt
170
+ # ) {}
171
+ #
172
+ # // infrastructure/kafkaListener/PaymentApprovedKafkaListener.java
173
+ # @Component
174
+ # public class PaymentApprovedKafkaListener {
175
+ #
176
+ # private final UseCaseMediator useCaseMediator;
177
+ #
178
+ # @Value("${kafka.topics.PAYMENT_APPROVED}")
179
+ # private String paymentApprovedTopic;
180
+ #
181
+ # @KafkaListener(topics = "${kafka.topics.PAYMENT_APPROVED}")
182
+ # public void handle(EventEnvelope<PaymentApprovedIntegrationEvent> envelope,
183
+ # Acknowledgment ack) {
184
+ # PaymentApprovedIntegrationEvent event = envelope.data();
185
+ # useCaseMediator.dispatch(new ConfirmOrderCommand(event.orderId()));
186
+ # ack.acknowledge();
187
+ # }
188
+ # }
189
+ #
190
+ # Propiedades:
191
+ # event (requerido) — Nombre del evento consumido (PascalCase + sufijo Event)
192
+ # producer (requerido) — Módulo que produce el evento (referencia documental)
193
+ # topic (requerido) — Topic Kafka. Si el proyecto tiene system.yaml el generador
194
+ # puede inferirlo; en módulos standalone es obligatorio
195
+ # declararlo explícitamente (no hay otra fuente de verdad).
196
+ # useCase (requerido) — Caso de uso que maneja el evento (PascalCase)
197
+ # fields (requerido) — Payload recibido; genera el record IntegrationEvent
198
+ # y tipifica el Command despachado desde el listener.
199
+ # nestedTypes (opcional) — Tipos auxiliares para campos objeto en fields.
200
+ # Genera {Name}.java en application/events/ por cada entrada.
201
+ # Usar cuando un field no es un tipo Java primitivo sino
202
+ # un objeto estructurado del dominio del productor.
203
+ # El name se declara camelCase y se genera en PascalCase.
126
204
 
127
205
  aggregates:
128
206
  - name: Order
@@ -160,42 +238,110 @@ aggregates:
160
238
  transitions:
161
239
  - from: DRAFT
162
240
  to: PLACED
241
+ method: place
163
242
  - from: DRAFT
164
243
  to: CANCELLED
244
+ method: cancel
165
245
  - from: PLACED
166
246
  to: CONFIRMED
247
+ method: confirm
167
248
  - from: PLACED
168
249
  to: CANCELLED
250
+ method: cancel
169
251
  - from: CONFIRMED
170
252
  to: SHIPPED
253
+ method: ship
171
254
  - from: CONFIRMED
172
255
  to: CANCELLED
256
+ method: cancel
173
257
  - from: SHIPPED
174
258
  to: DELIVERED
259
+ method: deliver
175
260
 
176
261
  # ─── Domain Events ────────────────────────────────────────────────────────
177
262
  # Siblings de entities:, enums: y valueObjects:
178
263
  # La declaración es agnóstica al broker — ningún campo de infraestructura aquí.
179
264
  # Cada entrada genera: domain/models/events/{Name}Event.java
265
+ #
266
+ # triggers: (lista de nombres de métodos de transición)
267
+ # Conecta automáticamente una transición de estado con la publicación de este evento.
268
+ # El generador emite raise(new XEvent(...)) dentro del método de transición.
269
+ # Si no se declara triggers, el dev debe llamar a raise() manualmente.
270
+ #
271
+ # Reglas de resolución de argumentos (automática):
272
+ # 1. aggregateId → siempre this.getId() (primer arg del constructor de DomainEvent)
273
+ # 2. {entityName}Id (ej: orderId en Order) → IGNORADO — el id ya está en aggregateId
274
+ # El consumidor usa event.getAggregateId(); no declarar este campo en events[].fields
275
+ # 3. coincide con campo de la entidad → this.get{Field}()
276
+ # 4. nombre termina en At + tipo LocalDateTime → LocalDateTime.now()
277
+ # 5. no resuelto → null /* TODO: provide {fieldName} */
180
278
 
181
279
  events:
182
280
  - name: OrderPlaced
281
+ triggers:
282
+ - place # auto-genera raise() en el método place() de Order
183
283
  fields:
184
- - name: customerId
284
+ # orderId NO se declara aquí — ya disponible como event.getAggregateId()
285
+ - name: customerId # → this.getCustomerId() (campo de la entidad)
185
286
  type: String
186
- - name: orderNumber
287
+ - name: orderNumber # → this.getOrderNumber()
187
288
  type: String
188
- - name: totalAmount
289
+ - name: totalAmount # → this.getTotalAmount()
189
290
  type: BigDecimal
190
291
 
191
292
  - name: OrderCancelled
293
+ triggers:
294
+ - cancel # auto-genera raise() en cancel() de Order
192
295
  fields:
193
- - name: reason
296
+ - name: reason # no resuelto → null /* TODO: provide reason */
194
297
  type: String
195
298
 
196
299
  - name: OrderShipped
300
+ triggers:
301
+ - ship
197
302
  fields:
198
- - name: shippingAddress
303
+ - name: shippingAddress # no resuelto → null /* TODO */
199
304
  type: String
200
- - name: estimatedDeliveryDate
305
+ - name: estimatedDeliveryDate # no resuelto → null /* TODO */
201
306
  type: LocalDate
307
+
308
+ # ─── Eventos externos que este módulo CONSUME ────────────────────────────────
309
+ # Nivel raíz, sibling de aggregates:
310
+ # Requiere broker instalado (eva add kafka-client) para generación.
311
+ # topic: es obligatorio en módulos standalone (sin system.yaml).
312
+
313
+ listeners:
314
+ - event: PaymentApprovedEvent
315
+ producer: payments # módulo origen (solo documental)
316
+ topic: PAYMENT_APPROVED # obligatorio si no hay system.yaml
317
+ useCase: ConfirmOrder # Command handler que se ejecuta
318
+ fields: # payload del Integration Event recibido
319
+ - name: orderId
320
+ type: String
321
+ - name: approvedAt
322
+ type: LocalDateTime
323
+ - name: details
324
+ type: PaymentDetails # objeto estructurado → declarado en nestedTypes
325
+ nestedTypes: # genera PaymentDetails.java en application/events/
326
+ - name: paymentDetails
327
+ fields:
328
+ - name: paymentId
329
+ type: String
330
+ - name: method
331
+ type: String
332
+ - name: currency
333
+ type: String
334
+ - name: amount
335
+ type: BigDecimal
336
+
337
+ - event: PaymentRejectedEvent
338
+ producer: payments
339
+ topic: PAYMENT_REJECTED
340
+ useCase: CancelOrder
341
+ fields:
342
+ - name: orderId
343
+ type: String
344
+ - name: rejectedAt
345
+ type: LocalDateTime
346
+ - name: reason
347
+ type: String
@@ -0,0 +1,212 @@
1
+ # EJEMPLO: LISTENERS — eventos externos que CONSUME un módulo
2
+ # Complemento de domain-events.yaml (producción).
3
+ #
4
+ # La sección listeners: vive en el NIVEL RAÍZ del domain.yaml,
5
+ # como sibling de aggregates:, porque es una responsabilidad de
6
+ # integración/infraestructura, no de dominio.
7
+ #
8
+ # Generación:
9
+ # eva4j g entities reservations
10
+ #
11
+ # Requiere broker instalado:
12
+ # eva add kafka-client
13
+ #
14
+ # ─── ARCHIVOS GENERADOS ───────────────────────────────────────────────────────
15
+ #
16
+ # Por cada entrada en listeners: se generan CINCO archivos:
17
+ #
18
+ # application/events/{Name}IntegrationEvent.java ← record contrato (documentación)
19
+ # application/commands/{UseCase}Command.java ← comando tipado despachado
20
+ # application/usecases/{UseCase}CommandHandler.java ← handler stub (implementar)
21
+ # infrastructure/kafkaListener/{Name}KafkaListener.java
22
+ # kafka.yaml (todas las envs) ← topic registrado
23
+ #
24
+ # Si se declaran nestedTypes:, se genera también:
25
+ # application/events/{NestedName}.java ← record auxiliar por tipo objeto
26
+ #
27
+ # ─────────────────────────────────────────────────────────────────────────────
28
+ #
29
+ # EJEMPLO con nestedTypes: — campo 'details' de tipo PaymentDetails
30
+ #
31
+ # 0. reservations/application/events/PaymentDetails.java (nested type)
32
+ #
33
+ # public record PaymentDetails(
34
+ # String paymentId,
35
+ # String method,
36
+ # String currency,
37
+ # BigDecimal amount
38
+ # ) {}
39
+ #
40
+ # 1. reservations/application/events/PaymentApprovedIntegrationEvent.java
41
+ #
42
+ # public record PaymentApprovedIntegrationEvent(
43
+ # String reservationId,
44
+ # LocalDateTime approvedAt,
45
+ # PaymentDetails details
46
+ # ) {}
47
+ #
48
+ # ─────────────────────────────────────────────────────────────────────────────
49
+ #
50
+ # 2. reservations/infrastructure/kafkaListener/PaymentApprovedKafkaListener.java
51
+ #
52
+ # @Component
53
+ # public class PaymentApprovedKafkaListener {
54
+ #
55
+ # private final UseCaseMediator useCaseMediator;
56
+ # private final ObjectMapper objectMapper;
57
+ #
58
+ # @Value("${topics.payment-approved}")
59
+ # private String paymentApprovedTopic;
60
+ #
61
+ # public PaymentApprovedKafkaListener(UseCaseMediator useCaseMediator,
62
+ # ObjectMapper objectMapper) {
63
+ # this.useCaseMediator = useCaseMediator;
64
+ # this.objectMapper = objectMapper;
65
+ # }
66
+ #
67
+ # @KafkaListener(topics = "${topics.payment-approved}")
68
+ # public void handle(EventEnvelope<Map<String, Object>> event, Acknowledgment ack) {
69
+ # String reservationId = objectMapper.convertValue(
70
+ # event.data().get("reservationId"), String.class);
71
+ # LocalDateTime approvedAt = objectMapper.convertValue(
72
+ # event.data().get("approvedAt"), LocalDateTime.class);
73
+ # PaymentDetails details = objectMapper.convertValue(
74
+ # event.data().get("details"), PaymentDetails.class);
75
+ # useCaseMediator.dispatch(new ConfirmReservationCommand(
76
+ # reservationId, approvedAt, details));
77
+ # ack.acknowledge();
78
+ # }
79
+ # }
80
+ #
81
+ # ─────────────────────────────────────────────────────────────────────────────
82
+ #
83
+ # REGLA DE topic:
84
+ #
85
+ # • Módulo standalone (sin system.yaml) → topic: OBLIGATORIO
86
+ # No hay ninguna otra fuente de verdad para el nombre del topic.
87
+ #
88
+ # • Proyecto con system.yaml → topic: puede omitirse.
89
+ # El generador lo infiere de integrations.async[].topic donde coincidan
90
+ # event y producer.
91
+ #
92
+ # • topic: declarado explícitamente + system.yaml → el valor declarado
93
+ # tiene PRECEDENCIA sobre la inferencia del system.yaml.
94
+ #
95
+ # ─────────────────────────────────────────────────────────────────────────────
96
+ #
97
+ # CONTRASTE: producción vs. consumo
98
+ #
99
+ # domain.yaml
100
+ # ├── aggregates:
101
+ # │ └── [Aggregate]
102
+ # │ └── events: → Domain Events que PRODUCE (domain/models/events/)
103
+ # │
104
+ # └── listeners: → Integration Events que CONSUME (infrastructure/kafkaListener/)
105
+
106
+ aggregates:
107
+ - name: Reservation
108
+ entities:
109
+ - name: reservation
110
+ isRoot: true
111
+ tableName: reservations
112
+ audit:
113
+ enabled: true
114
+ fields:
115
+ - name: id
116
+ type: String
117
+ - name: screeningId
118
+ type: String
119
+ - name: customerId
120
+ type: String
121
+ - name: status
122
+ type: ReservationStatus
123
+ - name: totalAmount
124
+ type: BigDecimal
125
+ readOnly: true
126
+
127
+ enums:
128
+ - name: ReservationStatus
129
+ initialValue: PENDING
130
+ values:
131
+ - PENDING
132
+ - CONFIRMED
133
+ - CANCELLED
134
+ - EXPIRED
135
+ transitions:
136
+ - from: PENDING
137
+ to: CONFIRMED
138
+ method: confirm
139
+ - from: PENDING
140
+ to: CANCELLED
141
+ method: cancel
142
+ - from: PENDING
143
+ to: EXPIRED
144
+ method: expire
145
+
146
+ events:
147
+ - name: ReservationConfirmed
148
+ triggers:
149
+ - confirm
150
+ fields:
151
+ # reservationId NO se declara — disponible como event.getAggregateId()
152
+ - name: customerId
153
+ type: String
154
+ - name: confirmedAt
155
+ type: LocalDateTime
156
+
157
+ - name: ReservationCancelled
158
+ fields:
159
+ - name: reservationId
160
+ type: String
161
+ - name: reason
162
+ type: String
163
+
164
+ # ─── Eventos externos que este módulo CONSUME ────────────────────────────────
165
+ # Nivel raíz, sibling de aggregates:
166
+ # topic: es obligatorio en módulos standalone (sin system.yaml).
167
+
168
+ listeners:
169
+ - event: PaymentApprovedEvent
170
+ producer: payments # módulo origen (solo referencia documental)
171
+ topic: PAYMENT_APPROVED # obligatorio: no hay system.yaml en este ejemplo
172
+ useCase: ConfirmReservation # command handler invocado al recibir el evento
173
+ fields: # payload del Integration Event recibido
174
+ - name: reservationId
175
+ type: String
176
+ - name: approvedAt
177
+ type: LocalDateTime
178
+ - name: details
179
+ type: PaymentDetails # objeto estructurado → declarado en nestedTypes
180
+ nestedTypes: # genera PaymentDetails.java en application/events/
181
+ - name: paymentDetails # camelCase → PascalCase en el record generado
182
+ fields:
183
+ - name: paymentId
184
+ type: String
185
+ - name: method
186
+ type: String
187
+ - name: currency
188
+ type: String
189
+ - name: amount
190
+ type: BigDecimal
191
+
192
+ - event: PaymentRejectedEvent
193
+ producer: payments
194
+ topic: PAYMENT_REJECTED
195
+ useCase: ExpireReservation
196
+ fields:
197
+ - name: reservationId
198
+ type: String
199
+ - name: rejectedAt
200
+ type: LocalDateTime
201
+ - name: reason
202
+ type: String
203
+
204
+ - event: ScreeningCancelledEvent
205
+ producer: screenings
206
+ topic: SCREENING_CANCELLED
207
+ useCase: CancelReservationsByScreening
208
+ fields:
209
+ - name: screeningId
210
+ type: String
211
+ - name: cancelledAt
212
+ type: LocalDateTime
@@ -8,6 +8,7 @@ aggregates:
8
8
  - name: Order
9
9
  isRoot: true
10
10
  tableName: orders
11
+ hasSoftDelete: true
11
12
  audit:
12
13
  enabled: true # Agrega createdAt, updatedAt
13
14
  trackUser: true # Agrega createdBy, updatedBy
@@ -7,6 +7,7 @@ aggregates:
7
7
  entities:
8
8
  - name: User
9
9
  isRoot: true
10
+ hasSoftDelete: true
10
11
  tableName: users
11
12
  audit:
12
13
  enabled: true # Agrega createdAt, updatedAt