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
@@ -19,6 +19,7 @@
19
19
  13. [Generated files](#13-generated-files)
20
20
  14. [Complete examples](#14-complete-examples)
21
21
  15. [Prerequisites and common errors](#15-prerequisites-and-common-errors)
22
+ 16. [Declarative endpoints — use case patterns](#16-declarative-endpoints-endpoints--use-case-patterns)
22
23
 
23
24
  ---
24
25
 
@@ -907,3 +908,198 @@ aggregates:
907
908
  | `Column 'x_id' is duplicated` | ManyToOne defined manually + auto-generated | Remove the manual ManyToOne; let eva4j generate it |
908
909
  | File not regenerated | File was manually modified (checksum) | Use `--force` to overwrite |
909
910
  | Import errors | Field `type` doesn't match name in `enums:` or `valueObjects:` | Verify names match exactly |
911
+
912
+ ---
913
+
914
+ ## 16. Declarative endpoints (`endpoints:`) — Use case patterns
915
+
916
+ When `domain.yaml` includes an `endpoints:` section, the generator examines each `useCase` name and classifies it semantically before generating code. This determines whether a full, working implementation or a scaffold stub is produced.
917
+
918
+ ### 16.1 Pattern table
919
+
920
+ | Pattern | Category | Recognition condition | What is generated |
921
+ |---------|----------|----------------------|-------------------|
922
+ | `Create{Aggregate}` | **standard** | Exact string match | Full `CreateCommand` + `CreateCommandHandler` (`ApplicationMapper.fromCommand → save`) |
923
+ | `Update{Aggregate}` | **standard** | Exact string match | Full `UpdateCommand` + `UpdateCommandHandler` |
924
+ | `Delete{Aggregate}` | **standard** | Exact string match | Full `DeleteCommand` + `DeleteCommandHandler` |
925
+ | `Get{Aggregate}` | **standard** | Exact string match | Full `GetQuery` + `GetQueryHandler` (find + `mapper.toDto`) |
926
+ | `FindAll{Aggregate}s` | **standard** | Exact string match (trailing literal `s`) | Full `ListQuery` + `ListQueryHandler` (paginated) |
927
+ | `{MethodPascal}{Aggregate}` | **transition** | `MethodPascal` is `toPascalCase(transitions[n].method)` for any enum in the aggregate | Full `TransitionCommand(id)` + handler that calls `entity.{method}() → save()` |
928
+ | `Add{EntityName}` | **subEntityAdd** | `EntityName` is the `target` of a `OneToMany` relationship on the root | Full `AddCommand(id, entityFields…)` + handler that calls `entity.add{Entity}(new {Entity}(…)) → save()` |
929
+ | `Remove{EntityName}` | **subEntityRemove** | Same `target` from a `OneToMany` relationship | Full `RemoveCommand(id, itemId)` + handler that calls `entity.remove{Entity}ById(itemId) → save()` |
930
+ | `FindAll{Aggregate}sBy{FieldPascal}` | **findBy** | `FieldPascal` is `toPascalCase(fieldName)` for any field in the root entity | Full `FindByQuery` + `FindByQueryHandler` + `findBy{FieldPascal}` added to `{Aggregate}Repository`, `{Aggregate}RepositoryImpl`, and `{Aggregate}JpaRepository` |
931
+ | _anything else_ | **scaffold** | No pattern matched | `*Command(id)` or `*Query(id)` + handler that throws `UnsupportedOperationException` with a TODO comment |
932
+
933
+ > **Note on `FindAll{Aggregate}s`:** The trailing `s` is a literal character. For `Aggregate = Order` the standard name is `FindAllOrders` (not `FindAllOrder`). Irregular plurals are not supported — use the exact pattern or it will be classified as scaffold.
934
+
935
+ ### 16.2 Transition pattern in detail
936
+
937
+ Enumerate state transitions in the `enums:` block using `transitions`:
938
+
939
+ ```yaml
940
+ enums:
941
+ - name: OrderStatus
942
+ transitions:
943
+ - from: PENDING
944
+ to: CONFIRMED
945
+ method: confirm # → recognized as ConfirmOrder (PascalCase(confirm) + Order)
946
+ - from: [PENDING, CONFIRMED]
947
+ to: CANCELLED
948
+ method: cancel # → CancelOrder
949
+ - from: CONFIRMED
950
+ to: SHIPPED
951
+ method: ship # → ShipOrder
952
+ values: [PENDING, CONFIRMED, SHIPPED, DELIVERED, CANCELLED]
953
+ ```
954
+
955
+ Declare the corresponding use cases in `endpoints:`:
956
+
957
+ ```yaml
958
+ endpoints:
959
+ basePath: /orders
960
+ versions:
961
+ - version: v1
962
+ operations:
963
+ - method: PUT
964
+ path: /{id}/confirm
965
+ useCase: ConfirmOrder # ← transition pattern: confirm + Order
966
+ type: command
967
+ - method: PUT
968
+ path: /{id}/cancel
969
+ useCase: CancelOrder
970
+ type: command
971
+ ```
972
+
973
+ **Generated output:**
974
+
975
+ ```java
976
+ // ConfirmOrderCommand.java
977
+ public record ConfirmOrderCommand(String id) implements Command {}
978
+
979
+ // ConfirmOrderCommandHandler.java
980
+ @Transactional
981
+ public void handle(ConfirmOrderCommand command) {
982
+ Order entity = repository.findById(command.id())
983
+ .orElseThrow(() -> new NotFoundException("Order not found with id: " + command.id()));
984
+ entity.confirm(); // ← the domain method from transitions[].method
985
+ repository.save(entity);
986
+ }
987
+ ```
988
+
989
+ ### 16.3 Sub-entity add/remove pattern in detail
990
+
991
+ Requirements:
992
+ - A `OneToMany` relationship must be declared on the root entity pointing to the target entity.
993
+ - The aggregate root must expose `add{EntityName}({EntityName} item)` and `remove{EntityName}ById(String id)` domain methods (generated automatically by `eva g entities`).
994
+
995
+ ```yaml
996
+ # aggregates: section
997
+ relationships:
998
+ - type: OneToMany
999
+ target: OrderItem # ← entityName used to match Add/Remove pattern
1000
+ fieldName: items
1001
+ ...
1002
+
1003
+ # endpoints: section
1004
+ operations:
1005
+ - method: POST
1006
+ path: /{id}/items
1007
+ useCase: AddOrderItem # ← Add + OrderItem (target name)
1008
+ type: command
1009
+ - method: DELETE
1010
+ path: /{id}/items/{itemId}
1011
+ useCase: RemoveOrderItem # ← Remove + OrderItem
1012
+ type: command
1013
+ ```
1014
+
1015
+ **Generated output:**
1016
+
1017
+ ```java
1018
+ // AddOrderItemCommand.java — fields taken from OrderItem (non-id, non-audit, non-readOnly)
1019
+ public record AddOrderItemCommand(
1020
+ String id,
1021
+ String productId,
1022
+ String productName,
1023
+ Integer quantity,
1024
+ BigDecimal unitPrice
1025
+ ) implements Command {}
1026
+
1027
+ // AddOrderItemCommandHandler.java
1028
+ @Transactional
1029
+ public void handle(AddOrderItemCommand command) {
1030
+ Order entity = repository.findById(command.id()) ...;
1031
+ OrderItem item = new OrderItem(command.productId(), command.productName(),
1032
+ command.quantity(), command.unitPrice());
1033
+ entity.addOrderItem(item);
1034
+ repository.save(entity);
1035
+ }
1036
+
1037
+ // RemoveOrderItemCommand.java
1038
+ public record RemoveOrderItemCommand(String id, String itemId) implements Command {}
1039
+
1040
+ // RemoveOrderItemCommandHandler.java
1041
+ @Transactional
1042
+ public void handle(RemoveOrderItemCommand command) {
1043
+ Order entity = repository.findById(command.id()) ...;
1044
+ entity.removeOrderItemById(command.itemId());
1045
+ repository.save(entity);
1046
+ }
1047
+ ```
1048
+
1049
+ ### 16.4 FindBy pattern in detail
1050
+
1051
+ Strict pattern: `FindAll{Aggregate}sBy{FieldPascal}` — both parts are required.
1052
+
1053
+ When detected, the generator:
1054
+ 1. Creates a paginated `FindBy{Field}Query` + `FindBy{Field}QueryHandler`.
1055
+ 2. Re-generates `{Aggregate}Repository.java` (domain interface), `{Aggregate}JpaRepository.java`, and `{Aggregate}RepositoryImpl.java` with the `findBy{FieldPascal}(FieldType value, Pageable pageable)` method added. Checksum protection still applies — manually modified files are skipped unless `--force` is used.
1056
+
1057
+ ```yaml
1058
+ # Root entity field
1059
+ fields:
1060
+ - name: customerId
1061
+ type: String
1062
+
1063
+ # Endpoint
1064
+ operations:
1065
+ - method: GET
1066
+ path: /customer/{customerId}
1067
+ useCase: FindAllOrdersByCustomerId # ← FindAll + Order + s + By + CustomerId
1068
+ type: query
1069
+ ```
1070
+
1071
+ **Generated output:**
1072
+
1073
+ ```java
1074
+ // FindAllOrdersByCustomerIdQuery.java
1075
+ public record FindAllOrdersByCustomerIdQuery(
1076
+ String customerId, int page, int size, String sortBy, String sortDirection
1077
+ ) implements Query<PagedResponse<OrderResponseDto>> {}
1078
+
1079
+ // OrderRepository.java (domain interface — method appended)
1080
+ Page<Order> findByCustomerId(String customerId, Pageable pageable);
1081
+
1082
+ // OrderJpaRepository.java (Spring Data JPA — method auto-implemented)
1083
+ Page<OrderJpa> findByCustomerId(String customerId, Pageable pageable);
1084
+ ```
1085
+
1086
+ ### 16.5 Scaffold (fallback)
1087
+
1088
+ Any `useCase` name that does not match any pattern above becomes a scaffold. A scaffold generates:
1089
+
1090
+ - A minimal `{UseCase}Command(String id)` or `{UseCase}Query(String id)` record.
1091
+ - A handler that throws `UnsupportedOperationException` and includes a step-by-step TODO comment.
1092
+
1093
+ This is intentional: the developer fills in the custom business logic while the wiring (registration, mediator dispatch, controller method) is already in place.
1094
+
1095
+ ### 16.6 Naming rules
1096
+
1097
+ | What | Convention | Example |
1098
+ |------|-----------|---------|
1099
+ | Aggregate name | PascalCase | `Order` |
1100
+ | Use case name in YAML | PascalCase | `ConfirmOrder`, `FindAllOrdersByCustomerId` |
1101
+ | Transition method in YAML | camelCase | `confirm`, `cancelOrder` |
1102
+ | Pattern `{MethodPascal}` | `toPascalCase(method)` | `confirm` → `Confirm` |
1103
+ | Pattern `{FieldPascal}` | `toPascalCase(fieldName)` | `customerId` → `CustomerId` |
1104
+ | Sub-entity target | PascalCase (must match entity `name:`) | `OrderItem` |
1105
+
@@ -112,7 +112,16 @@ Commands for transitioning from monolith to microservices.
112
112
  - Service independence
113
113
  - Zero code rewrite
114
114
 
115
- ### 📊 Utility Commands
115
+ ### 📊 Analysis & Utility Commands
116
+
117
+ - **[evaluate system](./EVALUATE_SYSTEM.md)** - Statically analyze a system.yaml for architectural problems
118
+ - Referential integrity (producers, consumers, sync endpoints)
119
+ - Synchronous cycle detection (DFS — deadlock prevention)
120
+ - Module role analysis (isolation, pure consumers, autonomous modules)
121
+ - Behavior gap detection (scheduler verbs with no trigger)
122
+ - Coupling pattern analysis (asymmetric sync↔async dependencies)
123
+ - Interactive HTML report with flow simulator + network diagram
124
+ - Domain-agnostic: works for any microservices design
116
125
 
117
126
  - **info** - Display project information
118
127
  - *Documentation coming soon*
@@ -0,0 +1,353 @@
1
+ # EJEMPLO: ENDPOINTS + RELACIONES
2
+ #
3
+ # Muestra cómo funciona la sección endpoints: cuando el agregado tiene
4
+ # - Entidad raíz : Order
5
+ # - OneToMany : OrderItem
6
+ # - OneToOne : ShippingAddress
7
+ #
8
+ # REGLA CLAVE:
9
+ # La sección `endpoints:` controla CUÁLES use cases se generan.
10
+ # La sección `aggregates:` controla LA ESTRUCTURA de cada use case
11
+ # (campos, listas anidadas, DTOs de relaciones).
12
+ #
13
+ # Ambas secciones son INDEPENDIENTES entre sí. El generador combina
14
+ # la información de ambas para producir el código correcto.
15
+ #
16
+ # ─────────────────────────────────────────────────────────────────────────
17
+ # QUÉ SE GENERA
18
+ # ─────────────────────────────────────────────────────────────────────────
19
+ #
20
+ # CreateOrder → implementación COMPLETA
21
+ # Porque su nombre coincide con el patrón estándar Create{Aggregate}.
22
+ # El Command incluirá automáticamente:
23
+ # - Los campos simples de Order (orderNumber, customerId)
24
+ # - List<CreateOrderItemDto> items ← por la relación OneToMany
25
+ # - CreateShippingAddressDto shipping ← por la relación OneToOne
26
+ #
27
+ # GetOrder / FindAllOrders → implementación COMPLETA
28
+ # El ResponseDto incluirá:
29
+ # - Campos de Order
30
+ # - List<OrderItemDto> items ← OneToMany en respuesta
31
+ # - ShippingAddressDto shippingAddress ← OneToOne en respuesta
32
+ #
33
+ # ConfirmOrder / CancelOrder / ShipOrder → Cat. 1 TRANSITION
34
+ # Porque su nombre es PascalCase(method) + Aggregate para una transición
35
+ # declarada en enums[].transitions. Handler completo generado automáticamente.
36
+ #
37
+ # AddOrderItem / RemoveOrderItem → Cat. 2 SUB-ENTITY ADD/REMOVE
38
+ # Porque su nombre es Add{Target} / Remove{Target} para una relación OneToMany.
39
+ # Handler completo generado automáticamente.
40
+ #
41
+ # FindAllOrdersByCustomerId → Cat. 3 FIND-BY
42
+ # Porque su nombre sigue FindAll{Aggregate}sBy{FieldPascal} para un campo de la raíz.
43
+ # Query + handler + método en el repositorio generados automáticamente.
44
+ #
45
+ # ProcessOrderPayment → SCAFFOLD (nombre libre)
46
+ # No coincide con ningún patrón → genera stubs con TODO.
47
+ # El desarrollador añade los campos al Command e implementa el Handler.
48
+
49
+ aggregates:
50
+ - name: Order
51
+ entities:
52
+
53
+ # ── Raíz del agregado ──────────────────────────────────────────────
54
+ - name: Order
55
+ isRoot: true
56
+ tableName: orders
57
+ audit:
58
+ enabled: true
59
+ trackUser: true
60
+ fields:
61
+ - name: id
62
+ type: String
63
+ - name: orderNumber
64
+ type: String
65
+ validations:
66
+ - type: NotBlank
67
+ message: "El número de pedido es obligatorio"
68
+ - name: customerId
69
+ type: String
70
+ reference:
71
+ aggregate: Customer
72
+ module: customers
73
+ - name: status
74
+ type: OrderStatus
75
+ readOnly: true # Manejado por transitions — no va en CreateCommand
76
+ - name: totalAmount
77
+ type: BigDecimal
78
+ readOnly: true # Calculado — no va en CreateCommand
79
+ defaultValue: "0.00"
80
+ - name: notes
81
+ type: String
82
+ relationships:
83
+ # El generador crea automáticamente la relación inversa (ManyToOne) en OrderItem
84
+ # No es necesario declarar la relación inversa en OrderItem
85
+ - type: OneToMany
86
+ target: OrderItem
87
+ mappedBy: order # → OrderItem tendrá un campo 'order' (FK: order_id)
88
+ cascade: [PERSIST, MERGE, REMOVE]
89
+ fetch: LAZY
90
+ # El generador crea automáticamente la relación inversa (OneToOne) en ShippingAddress
91
+ # No es necesario declarar la relación inversa en ShippingAddress
92
+ - type: OneToOne
93
+ target: ShippingAddress
94
+ mappedBy: order # → ShippingAddress tendrá un campo 'order' (FK: order_id)
95
+ cascade: [PERSIST, MERGE, REMOVE]
96
+ fetch: LAZY
97
+
98
+ # ── OneToMany: líneas del pedido ───────────────────────────────────
99
+ - name: OrderItem
100
+ tableName: order_items
101
+ fields:
102
+ - name: id
103
+ type: String
104
+ - name: productId
105
+ type: String
106
+ reference:
107
+ aggregate: Product
108
+ module: catalog
109
+ - name: productName
110
+ type: String
111
+ - name: quantity
112
+ type: Integer
113
+ validations:
114
+ - type: Min
115
+ value: 1
116
+ message: "La cantidad mínima es 1"
117
+ - name: unitPrice
118
+ type: BigDecimal
119
+ - name: subtotal
120
+ type: BigDecimal
121
+ readOnly: true # Calculado por el método de negocio addOrderItem()
122
+ defaultValue: "0.00"
123
+ # ❌ No declarar aquí la relación inversa (ManyToOne hacia Order):
124
+ # el generador la infiere automáticamente desde la definición de Order
125
+
126
+ # ── OneToOne: dirección de envío ───────────────────────────────────
127
+ - name: ShippingAddress
128
+ tableName: order_shipping_addresses
129
+ fields:
130
+ - name: id
131
+ type: String
132
+ - name: street
133
+ type: String
134
+ validations:
135
+ - type: NotBlank
136
+ - name: city
137
+ type: String
138
+ validations:
139
+ - type: NotBlank
140
+ - name: postalCode
141
+ type: String
142
+ - name: country
143
+ type: String
144
+ # ❌ No declarar aquí la relación inversa (OneToOne hacia Order):
145
+ # el generador la infiere automáticamente desde la definición de Order
146
+
147
+ enums:
148
+ - name: OrderStatus
149
+ initialValue: PENDING
150
+ transitions:
151
+ - from: PENDING
152
+ to: CONFIRMED
153
+ method: confirm
154
+ - from: [PENDING, CONFIRMED]
155
+ to: CANCELLED
156
+ method: cancel
157
+ - from: CONFIRMED
158
+ to: SHIPPED
159
+ method: ship
160
+ values: [PENDING, CONFIRMED, SHIPPED, DELIVERED, CANCELLED]
161
+
162
+ events:
163
+ - name: OrderConfirmedEvent
164
+ fields:
165
+ - name: orderId
166
+ type: String
167
+ - name: customerId
168
+ type: String
169
+
170
+
171
+ # ─────────────────────────────────────────────────────────────────────────
172
+ # ENDPOINTS
173
+ # ─────────────────────────────────────────────────────────────────────────
174
+ #
175
+ # Solo controla CUÁLES use cases existen y bajo qué URLs viven.
176
+ # La estructura (campos anidados, listas) la define aggregates: arriba.
177
+
178
+ endpoints:
179
+ basePath: /orders
180
+ versions:
181
+ - version: v1
182
+ operations:
183
+
184
+ # ── CRUD estándar ─────────────────────────────────────────────────
185
+
186
+ - method: POST
187
+ path: /
188
+ description: "Crear pedido con sus líneas y dirección de envío"
189
+ useCase: CreateOrder
190
+ # El Command generado incluirá automáticamente:
191
+ # String orderNumber
192
+ # String customerId
193
+ # String notes
194
+ # List<CreateOrderItemDto> items ← OneToMany
195
+ # CreateShippingAddressDto shippingAddress ← OneToOne
196
+
197
+ - method: GET
198
+ path: /{id}
199
+ description: "Obtener pedido completo con líneas y dirección"
200
+ useCase: GetOrder
201
+ # El ResponseDto tendrá:
202
+ # campos de Order + List<OrderItemDto> + ShippingAddressDto
203
+
204
+ - method: GET
205
+ path: /
206
+ description: "Listar pedidos con paginación"
207
+ useCase: FindAllOrders
208
+
209
+ - method: DELETE
210
+ path: /{id}
211
+ description: "Cancelar y eliminar un pedido"
212
+ useCase: DeleteOrder
213
+
214
+ # ── Operaciones de negocio (scaffold) ────────────────────────────
215
+
216
+ - method: PUT
217
+ path: /{id}/confirm
218
+ description: "Confirmar un pedido pendiente"
219
+ useCase: ConfirmOrder
220
+ # Cat. 1 TRANSITION — nombre coincide con PascalCase(confirm) + Order
221
+ # Genera ConfirmOrderCommand(String id) + handler completo:
222
+ # repository.findById(id) → entity.confirm() → repository.save(entity)
223
+ # type inferido: PUT → command
224
+
225
+ - method: PUT
226
+ path: /{id}/cancel
227
+ description: "Cancelar un pedido"
228
+ useCase: CancelOrder
229
+ # Cat. 1 TRANSITION — cancel + Order
230
+
231
+ - method: PUT
232
+ path: /{id}/ship
233
+ description: "Marcar pedido como enviado"
234
+ useCase: ShipOrder
235
+ # Cat. 1 TRANSITION — ship + Order
236
+
237
+ # ── Operaciones sobre sub-entidades (scaffold) ───────────────────
238
+
239
+ - method: POST
240
+ path: /{id}/items
241
+ description: "Agregar línea a un pedido existente"
242
+ useCase: AddOrderItem
243
+ # Cat. 2a SUB-ENTITY ADD — patrón: Add{target} donde target=OrderItem (OneToMany)
244
+ # Patrón de nombre: Add + rel.target → AddOrderItem
245
+ # Genera AddOrderItemCommand(String id, String productId, String productName, Integer quantity, BigDecimal unitPrice)
246
+ # Handler: findById → entity.addOrderItem(productId, productName, quantity, unitPrice) → save
247
+ # Nota: el campo generado en Order se llama 'orderItems' (auto-pluralizado de OrderItem)
248
+
249
+ - method: DELETE
250
+ path: /{id}/items/{itemId}
251
+ description: "Eliminar una línea del pedido"
252
+ useCase: RemoveOrderItem
253
+ # Cat. 2b SUB-ENTITY REMOVE — patrón: Remove{target} donde target=OrderItem
254
+ # Patrón de nombre: Remove + rel.target → RemoveOrderItem
255
+ # Genera RemoveOrderItemCommand(String id, String itemId)
256
+ # Handler: findById → entity.removeOrderItemById(itemId) → save
257
+
258
+ - method: GET
259
+ path: /customer/{customerId}
260
+ description: "Buscar pedidos por cliente"
261
+ useCase: FindAllOrdersByCustomerId
262
+ # Cat. 3 FIND-BY — patrón FindAll{Aggregate}sBy{FieldPascal}
263
+ # Genera FindAllOrdersByCustomerIdQuery(customerId, page, size, sortBy, sortDirection)
264
+ # Handler: repository.findByCustomerId(customerId, pageable)
265
+ # Además actualiza OrderRepository + OrderJpaRepository + OrderRepositoryImpl
266
+ # type inferido: GET → query
267
+
268
+ # ── SCAFFOLD (nombre libre — ningún patrón lo reconoce) ──────────
269
+
270
+ - method: POST
271
+ path: /{id}/payment
272
+ description: "Procesar el pago de un pedido"
273
+ useCase: ProcessOrderPayment
274
+ # ⚠️ SCAFFOLD — el nombre no coincide con ningún patrón:
275
+ # ✗ No es Create/Get/FindAll/Update/Delete{Aggregate} (estándar)
276
+ # ✗ No es {MethodPascal}{Aggregate} de ninguna transition (Cat. 1)
277
+ # ✗ No es Add{Target} / Remove{Target} de un OneToMany (Cat. 2)
278
+ # ✗ No es FindAll{Aggregate}sBy{Field} (Cat. 3)
279
+ #
280
+ # Por eso genera stubs con TODO y UnsupportedOperationException:
281
+ #
282
+ # ProcessOrderPaymentCommand.java ← record con /* TODO: add fields */
283
+ # ProcessOrderPaymentCommandHandler.java ← lanza UnsupportedOperationException
284
+ #
285
+ # El desarrollador debe:
286
+ # 1. Añadir los campos al Command (paymentMethod, amount, etc.)
287
+ # 2. Implementar la lógica en el Handler
288
+ #
289
+ # type inferido: POST → command (o declarar explícitamente si es GET)
290
+
291
+ # ─────────────────────────────────────────────────────────────────────────
292
+ # RESUMEN DE ARCHIVOS GENERADOS
293
+ # ─────────────────────────────────────────────────────────────────────────
294
+ #
295
+ # domain/models/entities/
296
+ # ├── Order.java (raíz — con métodos confirm/cancel/ship/addOrderItem/removeOrderItemById)
297
+ # ├── OrderItem.java
298
+ # └── ShippingAddress.java
299
+ #
300
+ # domain/models/enums/
301
+ # └── OrderStatus.java
302
+ #
303
+ # domain/models/events/
304
+ # └── OrderConfirmedEvent.java
305
+ #
306
+ # domain/repositories/
307
+ # └── OrderRepository.java ← actualizado con findByCustomerId (Cat.3)
308
+ #
309
+ # infrastructure/database/repositories/
310
+ # ├── OrderJpaRepository.java ← actualizado con findByCustomerId
311
+ # └── OrderRepositoryImpl.java ← actualizado con findByCustomerId
312
+ #
313
+ # application/commands/
314
+ # ├── CreateOrderCommand.java ← ESTÁNDAR — incluye items[] y shippingAddress
315
+ # ├── DeleteOrderCommand.java ← ESTÁNDAR
316
+ # ├── ConfirmOrderCommand.java ← TRANSITION Cat.1 — record(String id)
317
+ # ├── CancelOrderCommand.java ← TRANSITION Cat.1
318
+ # ├── ShipOrderCommand.java ← TRANSITION Cat.1
319
+ # ├── AddOrderItemCommand.java ← SUB-ENTITY ADD Cat.2 — incluye campos de OrderItem
320
+ # ├── RemoveOrderItemCommand.java ← SUB-ENTITY REMOVE Cat.2 — record(id, itemId)
321
+ # └── ProcessOrderPaymentCommand.java ← SCAFFOLD — record con /* TODO: add fields */
322
+ #
323
+ # application/queries/
324
+ # ├── GetOrderQuery.java ← ESTÁNDAR
325
+ # ├── FindAllOrdersQuery.java ← ESTÁNDAR (FindAll + Order + s)
326
+ # └── FindAllOrdersByCustomerIdQuery.java ← FIND-BY Cat.3 — filtra por customerId
327
+ #
328
+ # application/usecases/
329
+ # ├── CreateOrderCommandHandler.java ← ESTÁNDAR — completo
330
+ # ├── DeleteOrderCommandHandler.java ← ESTÁNDAR — completo
331
+ # ├── GetOrderQueryHandler.java ← ESTÁNDAR — completo
332
+ # ├── FindAllOrdersQueryHandler.java ← ESTÁNDAR — paginado completo
333
+ # ├── ConfirmOrderCommandHandler.java ← TRANSITION — findById → entity.confirm() → save
334
+ # ├── CancelOrderCommandHandler.java ← TRANSITION — findById → entity.cancel() → save
335
+ # ├── ShipOrderCommandHandler.java ← TRANSITION — findById → entity.ship() → save
336
+ # ├── AddOrderItemCommandHandler.java ← SUB-ENTITY ADD — findById → entity.addOrderItem(new OrderItem(...)) → save
337
+ # ├── RemoveOrderItemCommandHandler.java ← SUB-ENTITY REMOVE — findById → entity.removeOrderItemById(itemId) → save
338
+ # ├── FindAllOrdersByCustomerIdQueryHandler.java ← FIND-BY — repository.findByCustomerId(customerId, pageable)
339
+ # └── ProcessOrderPaymentCommandHandler.java ← SCAFFOLD — UnsupportedOperationException (TODO)
340
+ #
341
+ # application/dtos/
342
+ # ├── OrderResponseDto.java
343
+ # ├── OrderItemDto.java
344
+ # ├── ShippingAddressDto.java
345
+ # ├── CreateOrderItemDto.java ← para el comando de creación
346
+ # └── CreateShippingAddressDto.java ← para el comando de creación
347
+ #
348
+ # application/mappers/
349
+ # └── OrderApplicationMapper.java
350
+ #
351
+ # infrastructure/rest/controllers/order/
352
+ # └── v1/
353
+ # └── OrderV1Controller.java ← 10 endpoints declarados