eva4j 1.0.11 → 1.0.13

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 (73) hide show
  1. package/AGENTS.md +441 -14
  2. package/DOMAIN_YAML_GUIDE.md +425 -21
  3. package/FUTURE_FEATURES.md +315 -115
  4. package/QUICK_REFERENCE.md +101 -153
  5. package/README.md +77 -70
  6. package/bin/eva4j.js +57 -1
  7. package/config/defaults.json +3 -0
  8. package/docs/commands/GENERATE_ENTITIES.md +662 -1968
  9. package/docs/commands/GENERATE_HTTP_EXCHANGE.md +274 -450
  10. package/docs/commands/GENERATE_KAFKA_EVENT.md +219 -498
  11. package/docs/commands/GENERATE_KAFKA_LISTENER.md +18 -18
  12. package/docs/commands/GENERATE_RECORD.md +335 -311
  13. package/docs/commands/GENERATE_TEMPORAL_ACTIVITY.md +174 -0
  14. package/docs/commands/GENERATE_TEMPORAL_FLOW.md +237 -0
  15. package/docs/commands/GENERATE_USECASE.md +216 -282
  16. package/docs/commands/INDEX.md +36 -7
  17. package/examples/doctor-evaluation.yaml +3 -3
  18. package/examples/domain-audit-complete.yaml +2 -2
  19. package/examples/domain-collections.yaml +2 -2
  20. package/examples/domain-ecommerce.yaml +2 -2
  21. package/examples/domain-events.yaml +201 -0
  22. package/examples/domain-field-visibility.yaml +11 -5
  23. package/examples/domain-multi-aggregate.yaml +12 -6
  24. package/examples/domain-one-to-many.yaml +1 -1
  25. package/examples/domain-one-to-one.yaml +1 -1
  26. package/examples/domain-secondary-onetomany.yaml +1 -1
  27. package/examples/domain-secondary-onetoone.yaml +1 -1
  28. package/examples/domain-simple.yaml +1 -1
  29. package/examples/domain-soft-delete.yaml +3 -3
  30. package/examples/domain-transitions.yaml +1 -1
  31. package/examples/domain-value-objects.yaml +1 -1
  32. package/package.json +2 -2
  33. package/src/commands/add-kafka-client.js +3 -1
  34. package/src/commands/add-temporal-client.js +286 -0
  35. package/src/commands/generate-entities.js +75 -4
  36. package/src/commands/generate-kafka-event.js +273 -89
  37. package/src/commands/generate-temporal-activity.js +228 -0
  38. package/src/commands/generate-temporal-flow.js +216 -0
  39. package/src/generators/module-generator.js +1 -0
  40. package/src/generators/shared-generator.js +26 -0
  41. package/src/utils/yaml-to-entity.js +93 -4
  42. package/templates/aggregate/AggregateRepository.java.ejs +3 -2
  43. package/templates/aggregate/AggregateRepositoryImpl.java.ejs +15 -7
  44. package/templates/aggregate/AggregateRoot.java.ejs +38 -2
  45. package/templates/aggregate/DomainEntity.java.ejs +6 -2
  46. package/templates/aggregate/DomainEventHandler.java.ejs +62 -0
  47. package/templates/aggregate/DomainEventRecord.java.ejs +50 -0
  48. package/templates/aggregate/JpaAggregateRoot.java.ejs +3 -1
  49. package/templates/aggregate/JpaEntity.java.ejs +3 -1
  50. package/templates/base/docker/kafka-services.yaml.ejs +2 -2
  51. package/templates/base/docker/temporal-services.yaml.ejs +29 -0
  52. package/templates/base/resources/parameters/develop/temporal.yaml.ejs +9 -0
  53. package/templates/base/resources/parameters/local/temporal.yaml.ejs +9 -0
  54. package/templates/base/resources/parameters/production/temporal.yaml.ejs +9 -0
  55. package/templates/base/resources/parameters/test/temporal.yaml.ejs +9 -0
  56. package/templates/base/root/AGENTS.md.ejs +916 -51
  57. package/templates/crud/Controller.java.ejs +36 -6
  58. package/templates/crud/ListQuery.java.ejs +6 -2
  59. package/templates/crud/ListQueryHandler.java.ejs +24 -10
  60. package/templates/crud/UpdateCommand.java.ejs +52 -0
  61. package/templates/crud/UpdateCommandHandler.java.ejs +105 -0
  62. package/templates/kafka-event/DomainEventHandlerMethod.ejs +1 -0
  63. package/templates/kafka-event/Event.java.ejs +23 -0
  64. package/templates/shared/application/dtos/PagedResponse.java.ejs +30 -0
  65. package/templates/shared/configurations/temporalConfig/TemporalConfig.java.ejs +104 -0
  66. package/templates/shared/domain/DomainEvent.java.ejs +40 -0
  67. package/templates/shared/interfaces/HeavyActivity.java.ejs +4 -0
  68. package/templates/shared/interfaces/LightActivity.java.ejs +4 -0
  69. package/templates/temporal-activity/ActivityImpl.java.ejs +14 -0
  70. package/templates/temporal-activity/ActivityInterface.java.ejs +11 -0
  71. package/templates/temporal-flow/WorkFlowImpl.java.ejs +64 -0
  72. package/templates/temporal-flow/WorkFlowInterface.java.ejs +19 -0
  73. package/templates/temporal-flow/WorkFlowService.java.ejs +49 -0
package/AGENTS.md CHANGED
@@ -387,19 +387,28 @@ void assignUser(User user) { // package-private
387
387
 
388
388
  ```bash
389
389
  # Crear proyecto
390
- eva4j create my-app
390
+ eva create my-app
391
391
 
392
392
  # Agregar módulo
393
- eva4j add module users
393
+ eva add module users
394
394
 
395
395
  # Generar entidades desde YAML
396
- eva4j g entities users
396
+ eva g entities users
397
397
 
398
398
  # Generar use case
399
- eva4j g usecase users ActivateUser
399
+ eva g usecase users ActivateUser
400
400
 
401
401
  # Generar resource (REST)
402
- eva4j g resource users
402
+ eva g resource users
403
+
404
+ # Agregar cliente Temporal
405
+ eva add temporal-client
406
+
407
+ # Generar workflow Temporal
408
+ eva g temporal-flow users
409
+
410
+ # Generar actividad Temporal
411
+ eva g temporal-activity users
403
412
  ```
404
413
 
405
414
  ### Estructura de domain.yaml
@@ -442,11 +451,194 @@ aggregates:
442
451
  - ACTIVE
443
452
  - INACTIVE
444
453
  - SUSPENDED
454
+
455
+ events:
456
+ - name: UserRegisteredEvent
457
+ fields:
458
+ - name: userId
459
+ type: String
460
+ # kafka: true # opcional — publica a Kafka tras commit
461
+ ```
462
+
463
+ ---
464
+
465
+ ## ⚡ Características Avanzadas del domain.yaml
466
+
467
+ ### Value Objects con Métodos
468
+
469
+ Los Value Objects pueden declarar métodos de negocio directamente en `domain.yaml`:
470
+
471
+ ```yaml
472
+ valueObjects:
473
+ - name: Money
474
+ fields:
475
+ - name: amount
476
+ type: BigDecimal
477
+ - name: currency
478
+ type: String
479
+ methods:
480
+ - name: add
481
+ returnType: Money
482
+ parameters:
483
+ - name: other
484
+ type: Money
485
+ body: "return new Money(this.amount.add(other.getAmount()), this.currency);"
486
+ - name: isPositive
487
+ returnType: boolean
488
+ parameters: []
489
+ body: "return this.amount.compareTo(BigDecimal.ZERO) > 0;"
490
+ ```
491
+
492
+ ### Enums con Ciclo de Vida (Transitions)
493
+
494
+ Cuando un enum representa estados de negocio, declara `transitions` e `initialValue`:
495
+
496
+ ```yaml
497
+ enums:
498
+ - name: OrderStatus
499
+ initialValue: PENDING # Auto-inicializa en constructor; excluido del CreateDto
500
+ transitions:
501
+ - from: PENDING
502
+ to: CONFIRMED
503
+ method: confirm
504
+ - from: CONFIRMED
505
+ to: SHIPPED
506
+ method: ship
507
+ - from: [PENDING, CONFIRMED] # múltiples orígenes
508
+ to: CANCELLED
509
+ method: cancel
510
+ guard: "this.status == OrderStatus.DELIVERED" # lanza BusinessException si se cumple
511
+ values: [PENDING, CONFIRMED, SHIPPED, DELIVERED, CANCELLED]
512
+ ```
513
+
514
+ Genera automáticamente **en la entidad raíz**: `confirm()`, `ship()`, `cancel()`, helpers `isPending()`, `isConfirmed()`, `canConfirm()`, `canCancel()`.
515
+ Genera automáticamente **en el enum**: `VALID_TRANSITIONS`, `canTransitionTo()`, `transitionTo()` (lanza `InvalidStateTransitionException` si la transición no es válida).
516
+
517
+ **Nota:** El campo con `initialValue` se trata como `readOnly: true` — no aparece en el constructor de negocio ni en el `CreateDto`.
518
+
519
+ ### Eventos de Dominio (`events[]`)
520
+
521
+ ```yaml
522
+ aggregates:
523
+ - name: Order
524
+ entities: [...]
525
+ events:
526
+ - name: OrderConfirmedEvent
527
+ fields:
528
+ - name: orderId
529
+ type: String
530
+ - name: confirmedAt
531
+ type: LocalDateTime
532
+ kafka: true # opcional — genera publicación a MessageBroker
533
+ ```
534
+
535
+ Genera `OrderConfirmedEvent.java` (en `domain/models/events/`) que extiende `DomainEvent`, y `OrderDomainEventHandler.java` (en `application/usecases/`) con `@TransactionalEventListener(AFTER_COMMIT)`.
536
+
537
+ Publicar desde la entidad raíz usando `raise()` heredado:
538
+
539
+ ```java
540
+ public void confirm() {
541
+ this.status = this.status.transitionTo(OrderStatus.CONFIRMED);
542
+ raise(new OrderConfirmedEvent(this.id, this.id, LocalDateTime.now()));
543
+ }
544
+ ```
545
+
546
+ ---
547
+
548
+ ## �️ Soft Delete
549
+
550
+ Cuando una entidad tiene `hasSoftDelete: true`, eva4j genera eliminación lógica en lugar de física.
551
+
552
+ ### Configuración en domain.yaml
553
+
554
+ ```yaml
555
+ entities:
556
+ - name: product
557
+ isRoot: true
558
+ tableName: products
559
+ hasSoftDelete: true # ✅ Activa soft delete
560
+ audit:
561
+ enabled: true
562
+ fields:
563
+ - name: id
564
+ type: String
565
+ - name: name
566
+ type: String
445
567
  ```
446
568
 
569
+ ### Comportamiento generado
570
+
571
+ ```java
572
+ // Entidad JPA — filtrado automático con @SQLRestriction
573
+ @Entity
574
+ @SQLRestriction("deleted_at IS NULL")
575
+ public class ProductJpa extends AuditableEntity {
576
+ @Column(name = "deleted_at")
577
+ private LocalDateTime deletedAt;
578
+ }
579
+ ```
580
+
581
+ ```java
582
+ // Entidad de dominio — método de negocio
583
+ public class Product {
584
+ private LocalDateTime deletedAt;
585
+
586
+ public void softDelete() {
587
+ if (this.deletedAt != null) {
588
+ throw new IllegalStateException("Product is already deleted");
589
+ }
590
+ this.deletedAt = LocalDateTime.now();
591
+ }
592
+
593
+ public boolean isDeleted() {
594
+ return this.deletedAt != null;
595
+ }
596
+ }
597
+ ```
598
+
599
+ ### Reglas para Agentes
600
+
601
+ - **NUNCA** usar `repository.deleteById()` cuando hay soft delete
602
+ - **SIEMPRE** usar `entity.softDelete()` + `repository.save(entity)`
603
+ - **NUNCA** exponer `deletedAt` en ResponseDtos
604
+ - **SIEMPRE** usar `@SQLRestriction("deleted_at IS NULL")` en la entidad JPA
605
+
606
+ ---
607
+
608
+ ## ⏱️ Temporal Workflows
609
+
610
+ Cuando se agrega soporte de Temporal con `eva add temporal-client`, se genera infraestructura para workflows duraderos.
611
+
612
+ ### Archivos generados por `eva g temporal-flow <module>`
613
+
614
+ ```
615
+ [module]/
616
+ ├── application/workflows/
617
+ │ ├── OrderWorkflow.java # Interface (@WorkflowInterface)
618
+ │ └── OrderWorkflowImpl.java # Implementación (determinista)
619
+ └── infrastructure/temporal/
620
+ ├── activities/
621
+ │ ├── OrderActivity.java # Interface (@ActivityInterface)
622
+ │ └── OrderActivityImpl.java # Implementación (con I/O)
623
+ └── workers/
624
+ └── OrderWorker.java # Registro del worker
625
+ ```
626
+
627
+ ### Principios clave
628
+
629
+ - Los **Workflows deben ser deterministas** — sin `Math.random()`, `new Date()`, ni I/O
630
+ - Toda operación con efectos secundarios (DB, HTTP, emails) va en **Activities**
631
+ - Los **Use Cases** orquestan los workflows; las **Activities** ejecutan infraestructura
632
+ - Configuración de conexión en `resources/parameters/{env}/temporal.yaml`
633
+
634
+ ### Templates relacionados en eva4j
635
+
636
+ - `templates/temporal-flow/` — workflow interface e implementación
637
+ - `templates/temporal-activity/` — activity interface, implementación y worker
638
+
447
639
  ---
448
640
 
449
- ## 🚨 Errores Comunes a Evitar
641
+ ## �🚨 Errores Comunes a Evitar
450
642
 
451
643
  ### ❌ NO Crear Constructor Vacío en Dominio
452
644
 
@@ -557,7 +749,9 @@ Los campos en domain.yaml soportan las siguientes propiedades:
557
749
  | `enumValues` | Array | `[]` | Valores inline de enum |
558
750
  | **`readOnly`** | Boolean | `false` | **Excluye del constructor de negocio y CreateDto** |
559
751
  | **`hidden`** | Boolean | `false` | **Excluye del ResponseDto** |
752
+ | **`defaultValue`** | String/Number/Boolean | `null` | **Valor inicial en el constructor de creación (solo para `readOnly`)** |
560
753
  | **`validations`** | Array | `[]` | **Anotaciones JSR-303 en el Command y CreateDto** |
754
+ | **`reference`** | Object | `null` | **Declara referencia semántica a otro agregado (genera comentario Javadoc)** |
561
755
 
562
756
  #### Flags de Visibilidad: `readOnly` y `hidden`
563
757
 
@@ -591,9 +785,57 @@ fields:
591
785
  |-------|---------------------|-----------|-------------|
592
786
  | Normal | ✅ | ✅ | ✅ |
593
787
  | `readOnly: true` | ❌ | ❌ | ✅ |
788
+ | `readOnly` + `defaultValue` | ⚡ Asignado con default | ❌ | ✅ |
594
789
  | `hidden: true` | ✅ | ✅ | ❌ |
595
790
  | Ambos flags | ❌ | ❌ | ❌ |
596
791
 
792
+ #### 🎯 `defaultValue` - Valor Inicial para campos `readOnly`
793
+
794
+ Cuando un campo `readOnly` tiene un valor inicial predecible (ej: contadores, estados iniciales), se puede declarar en `domain.yaml` con `defaultValue`. El generador emite la asignación en el **constructor de creación** del dominio y `@Builder.Default` en la entidad JPA.
795
+
796
+ ```yaml
797
+ fields:
798
+ - name: totalAmount
799
+ type: BigDecimal
800
+ readOnly: true
801
+ defaultValue: "0.00" # ✅ Acumulador inicializado
802
+
803
+ - name: status
804
+ type: OrderStatus
805
+ readOnly: true
806
+ defaultValue: PENDING # ✅ Estado inicial del enum
807
+
808
+ - name: itemCount
809
+ type: Integer
810
+ readOnly: true
811
+ defaultValue: 0 # ✅ Contador inicializado
812
+ ```
813
+
814
+ ```java
815
+ // Constructor de creación — defaultValues aplicados
816
+ public Order(String orderNumber, String customerId) {
817
+ this.orderNumber = orderNumber;
818
+ this.customerId = customerId;
819
+ this.totalAmount = new BigDecimal("0.00"); // ← defaultValue
820
+ this.status = OrderStatus.PENDING; // ← defaultValue
821
+ this.itemCount = 0; // ← defaultValue
822
+ }
823
+ ```
824
+
825
+ ```java
826
+ // JPA — @Builder.Default para respetar el valor en el builder
827
+ @Builder.Default
828
+ private BigDecimal totalAmount = new BigDecimal("0.00");
829
+
830
+ @Enumerated(EnumType.STRING)
831
+ @Builder.Default
832
+ private OrderStatus status = OrderStatus.PENDING;
833
+ ```
834
+
835
+ **Restricción:** `defaultValue` **solo aplica** a campos con `readOnly: true`. Usarlo en un campo no-readOnly genera un warning y es ignorado.
836
+
837
+ ---
838
+
597
839
  **Ejemplo práctico:**
598
840
  ```yaml
599
841
  entities:
@@ -616,6 +858,87 @@ entities:
616
858
  hidden: true
617
859
  ```
618
860
 
861
+ ### Validaciones JSR-303 (`validations`)
862
+
863
+ Se declaran en el campo y se aplican **únicamente** en el `Command` y `CreateDto` de la capa de aplicación. **Nunca** en las entidades de dominio.
864
+
865
+ ```yaml
866
+ fields:
867
+ - name: email
868
+ type: String
869
+ validations:
870
+ - type: NotBlank
871
+ message: "Email es requerido"
872
+ - type: Email
873
+ message: "Email inválido"
874
+
875
+ - name: username
876
+ type: String
877
+ validations:
878
+ - type: Size
879
+ min: 3
880
+ max: 50
881
+ message: "Username entre 3 y 50 caracteres"
882
+
883
+ - name: age
884
+ type: Integer
885
+ validations:
886
+ - type: Min
887
+ value: 18
888
+ - type: Max
889
+ value: 120
890
+
891
+ - name: price
892
+ type: BigDecimal
893
+ validations:
894
+ - type: Positive
895
+ ```
896
+
897
+ **Anotaciones disponibles:** `NotNull`, `NotBlank`, `NotEmpty`, `Email`, `Size` (min/max), `Min` (value), `Max` (value), `Pattern` (regexp), `Digits` (integer/fraction), `Positive`, `PositiveOrZero`, `Negative`, `Past`, `Future`, `AssertTrue`, `AssertFalse`.
898
+
899
+ **Código generado en `CreateUserCommand.java`:**
900
+ ```java
901
+ public record CreateUserCommand(
902
+ @NotBlank(message = "Email es requerido")
903
+ @Email(message = "Email inválido")
904
+ String email,
905
+
906
+ @Size(min = 3, max = 50, message = "Username entre 3 y 50 caracteres")
907
+ String username
908
+ ) implements Command {}
909
+ ```
910
+
911
+ ### Referencias entre Agregados (`reference`)
912
+
913
+ Declara explícitamente que un campo es un ID de otro agregado. El tipo Java **no cambia** — sigue siendo `String`, `Long`, etc. — pero se genera un comentario Javadoc que documenta la dependencia. **No genera `@ManyToOne`** (correcto en DDD: cada agregado es una unidad transaccional independiente).
914
+
915
+ ```yaml
916
+ fields:
917
+ - name: customerId
918
+ type: String
919
+ reference:
920
+ aggregate: Customer # Nombre del agregado referenciado (PascalCase)
921
+ module: customers # Módulo donde vive (opcional si es el mismo módulo)
922
+
923
+ - name: productId
924
+ type: String
925
+ reference:
926
+ aggregate: Product
927
+ module: catalog
928
+ ```
929
+
930
+ **Código generado:**
931
+ ```java
932
+ // En Order.java (domain entity)
933
+ /** Cross-aggregate reference → Customer (module: customers) */
934
+ private String customerId;
935
+
936
+ // En OrderJpa.java
937
+ @Column(name = "customer_id")
938
+ /** Cross-aggregate reference → Customer (module: customers) */
939
+ private String customerId;
940
+ ```
941
+
619
942
  ### Tipos de Relaciones
620
943
 
621
944
  - `OneToOne` - Relación uno a uno
@@ -627,6 +950,14 @@ entities:
627
950
 
628
951
  ## 🎯 Mejores Prácticas para Agentes
629
952
 
953
+ ### Al Generar domain.yaml (Flujo SDD)
954
+
955
+ 1. **SIEMPRE** incluir campo `id` en todas las entidades
956
+ 2. **SI** el módulo requiere ciclo de vida → usar `transitions` + `initialValue` en el enum
957
+ 3. **SI** un valor tiene lógica de negocio → declararlo como `valueObject` con `methods`
958
+ 4. **SI** ocurren hechos relevantes de negocio → declarar `events[]` en el agregado
959
+ 5. **DESPUÉS** de generar el `domain.yaml` → ejecutar `eva g entities <module>`
960
+
630
961
  ### Al Generar Código de Dominio
631
962
 
632
963
  1. **NUNCA** crear constructor vacío en entidades de dominio
@@ -712,13 +1043,13 @@ HTTP Response (sin createdBy/updatedBy)
712
1043
 
713
1044
  ## 🧪 Testing
714
1045
 
715
- ### Tests de Dominio
1046
+ ### Tests de Dominio (Unidad Pura)
716
1047
 
717
1048
  ```java
718
1049
  @Test
719
1050
  void shouldCreateUserWithValidData() {
720
1051
  User user = new User("john", "john@example.com");
721
-
1052
+
722
1053
  assertEquals("john", user.getUsername());
723
1054
  assertEquals("john@example.com", user.getEmail());
724
1055
  }
@@ -726,13 +1057,82 @@ void shouldCreateUserWithValidData() {
726
1057
  @Test
727
1058
  void shouldValidateBusinessRules() {
728
1059
  User user = new User("john", "john@example.com");
729
-
1060
+
730
1061
  assertThrows(IllegalArgumentException.class, () -> {
731
1062
  user.changeEmail("invalid-email");
732
1063
  });
733
1064
  }
734
1065
  ```
735
1066
 
1067
+ ### Object Mother Pattern
1068
+
1069
+ ```java
1070
+ // src/test/java/[package]/user/domain/UserMother.java
1071
+ public class UserMother {
1072
+
1073
+ public static User valid() {
1074
+ return new User("john_doe", "john@example.com");
1075
+ }
1076
+
1077
+ public static User withEmail(String email) {
1078
+ return new User("john_doe", email);
1079
+ }
1080
+ }
1081
+ ```
1082
+
1083
+ ### Repositorio Fake (In-Memory)
1084
+
1085
+ Para testear Use Cases sin base de datos:
1086
+
1087
+ ```java
1088
+ public class UserRepositoryFake implements UserRepository {
1089
+ private final Map<String, User> store = new HashMap<>();
1090
+
1091
+ @Override
1092
+ public User save(User user) {
1093
+ store.put(user.getId(), user);
1094
+ return user;
1095
+ }
1096
+
1097
+ @Override
1098
+ public Optional<User> findById(String id) {
1099
+ return Optional.ofNullable(store.get(id));
1100
+ }
1101
+
1102
+ public int count() { return store.size(); }
1103
+ }
1104
+ ```
1105
+
1106
+ ### Tests de Use Cases
1107
+
1108
+ ```java
1109
+ class CreateUserCommandHandlerTest {
1110
+ private final UserRepositoryFake userRepository = new UserRepositoryFake();
1111
+ private final CreateUserCommandHandler handler =
1112
+ new CreateUserCommandHandler(userRepository);
1113
+
1114
+ @Test
1115
+ void shouldCreateUser() {
1116
+ CreateUserCommand command = new CreateUserCommand("john", "john@example.com");
1117
+
1118
+ String userId = handler.handle(command);
1119
+
1120
+ assertNotNull(userId);
1121
+ assertEquals(1, userRepository.count());
1122
+ }
1123
+ }
1124
+ ```
1125
+
1126
+ ### Estrategia por Capa
1127
+
1128
+ | Capa | Tipo de Test | Framework |
1129
+ |------|--------------|-----------|
1130
+ | Domain entities | Unidad pura | JUnit 5 |
1131
+ | Use cases | Unidad con Fakes | JUnit 5 + Fake repos |
1132
+ | Application mappers | Unidad | JUnit 5 |
1133
+ | Repository implementations | Integración | Testcontainers |
1134
+ | REST controllers | Integración | MockMvc |
1135
+
736
1136
  ---
737
1137
 
738
1138
  ## 📖 Documentos Relacionados
@@ -748,23 +1148,50 @@ void shouldValidateBusinessRules() {
748
1148
 
749
1149
  Al generar o modificar código, verificar:
750
1150
 
1151
+ **Entidades de Dominio:**
751
1152
  - [ ] Entidades de dominio **sin constructor vacío**
752
1153
  - [ ] Entidades de dominio **sin setters públicos**
753
1154
  - [ ] Métodos de negocio con **validaciones explícitas**
1155
+ - [ ] Value Objects **inmutables**
1156
+ - [ ] Sin anotaciones JSR-303 en entidades de dominio
1157
+
1158
+ **Entidades JPA:**
754
1159
  - [ ] Entidades JPA con **Lombok y herencia correcta**
1160
+ - [ ] `@SQLRestriction("deleted_at IS NULL")` cuando `hasSoftDelete: true`
1161
+ - [ ] No incluye campos de auditoría heredados en `@Builder`
1162
+
1163
+ **Mappers:**
755
1164
  - [ ] Mappers **excluyen campos de auditoría**
756
1165
  - [ ] Mappers **excluyen campos readOnly en creación**
757
1166
  - [ ] Mappers **excluyen campos hidden en respuestas**
1167
+ - [ ] Relaciones bidireccionales con métodos `assign*()`
1168
+
1169
+ **DTOs:**
758
1170
  - [ ] DTOs de respuesta **sin createdBy/updatedBy**
759
1171
  - [ ] DTOs de respuesta **sin campos hidden**
760
1172
  - [ ] DTOs de creación **sin campos readOnly**
761
- - [ ] Relaciones bidireccionales con métodos `assign*()`
762
- - [ ] Value Objects **inmutables**
763
- - [ ] Configuración de auditoría cuando `trackUser: true`
1173
+ - [ ] Usando Java Records
1174
+
1175
+ **Validaciones:**
764
1176
  - [ ] Validaciones JSR-303 **solo en Command y CreateDto, nunca en dominio**
1177
+ - [ ] `@Valid` en parámetros de endpoints REST
1178
+
1179
+ **Auditoría:**
1180
+ - [ ] Configuración de auditoría cuando `trackUser: true`
1181
+ - [ ] `@EnableJpaAuditing` con `auditorAwareRef = "auditorProvider"` en Application
1182
+
1183
+ **Soft Delete (cuando aplica):**
1184
+ - [ ] Usar `entity.softDelete()` + `repository.save()` — nunca `deleteById()`
1185
+ - [ ] `deletedAt` no expuesto en ResponseDto
1186
+
1187
+ **Características Avanzadas (cuando aplica):**
1188
+ - [ ] Enum con ciclo de vida → usar `transitions` + `initialValue`, no setters manuales
1189
+ - [ ] Value Object con comportamiento → declarar `methods` en lugar de lógica en entidad
1190
+ - [ ] Evento de dominio → declarar en `events[]`, publicar con `raise()` en método de negocio
1191
+ - [ ] Evento con Kafka → agregar `kafka: true` al evento
765
1192
 
766
1193
  ---
767
1194
 
768
- **Última actualización:** 2026-02-21
769
- **Versión de eva4j:** 1.x
1195
+ **Última actualización:** 2026-03-02
1196
+ **Versión de eva4j:** 1.0.12
770
1197
  **Estado:** Documento de referencia para agentes IA