eva4j 1.0.10 → 1.0.12

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 (87) hide show
  1. package/AGENTS.md +102 -11
  2. package/DOMAIN_YAML_GUIDE.md +444 -0
  3. package/FUTURE_FEATURES.md +1075 -556
  4. package/QUICK_REFERENCE.md +101 -153
  5. package/README.md +80 -73
  6. package/bin/eva4j.js +58 -1
  7. package/config/defaults.json +3 -0
  8. package/docs/commands/GENERATE_ENTITIES.md +886 -40
  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_TEMPORAL_ACTIVITY.md +174 -0
  13. package/docs/commands/GENERATE_TEMPORAL_FLOW.md +237 -0
  14. package/docs/commands/GENERATE_USECASE.md +216 -282
  15. package/docs/commands/INDEX.md +36 -7
  16. package/examples/domain-events.yaml +201 -0
  17. package/examples/domain-field-visibility.yaml +235 -0
  18. package/examples/domain-multi-aggregate.yaml +6 -0
  19. package/examples/domain-multiple-relations.yaml +4 -4
  20. package/examples/domain-one-to-many.yaml +1 -1
  21. package/examples/domain-one-to-one.yaml +6 -3
  22. package/examples/domain-secondary-onetomany.yaml +106 -0
  23. package/examples/domain-secondary-onetoone.yaml +98 -0
  24. package/examples/domain-transitions.yaml +60 -0
  25. package/package.json +5 -4
  26. package/prettier.config.js +9 -0
  27. package/src/commands/add-kafka-client.js +3 -1
  28. package/src/commands/add-temporal-client.js +286 -0
  29. package/src/commands/detach.js +57 -0
  30. package/src/commands/generate-entities.js +333 -61
  31. package/src/commands/generate-kafka-event.js +276 -89
  32. package/src/commands/generate-resource.js +22 -25
  33. package/src/commands/generate-temporal-activity.js +228 -0
  34. package/src/commands/generate-temporal-flow.js +216 -0
  35. package/src/commands/generate-usecase.js +17 -41
  36. package/src/generators/base-generator.js +2 -1
  37. package/src/generators/module-generator.js +1 -0
  38. package/src/generators/shared-generator.js +46 -2
  39. package/src/utils/checksum-manager.js +130 -0
  40. package/src/utils/template-engine.js +63 -5
  41. package/src/utils/yaml-to-entity.js +308 -19
  42. package/templates/aggregate/AggregateMapper.java.ejs +52 -10
  43. package/templates/aggregate/AggregateRepository.java.ejs +3 -2
  44. package/templates/aggregate/AggregateRepositoryImpl.java.ejs +15 -7
  45. package/templates/aggregate/AggregateRoot.java.ejs +107 -5
  46. package/templates/aggregate/DomainEntity.java.ejs +101 -4
  47. package/templates/aggregate/DomainEventHandler.java.ejs +62 -0
  48. package/templates/aggregate/DomainEventRecord.java.ejs +50 -0
  49. package/templates/aggregate/Enum.java.ejs +49 -1
  50. package/templates/aggregate/JpaAggregateRoot.java.ejs +1 -0
  51. package/templates/aggregate/JpaEntity.java.ejs +12 -3
  52. package/templates/base/application/Application.java.ejs +1 -1
  53. package/templates/base/docker/kafka-services.yaml.ejs +2 -2
  54. package/templates/base/docker/temporal-services.yaml.ejs +29 -0
  55. package/templates/base/resources/parameters/develop/temporal.yaml.ejs +9 -0
  56. package/templates/base/resources/parameters/local/temporal.yaml.ejs +9 -0
  57. package/templates/base/resources/parameters/production/temporal.yaml.ejs +9 -0
  58. package/templates/base/resources/parameters/test/temporal.yaml.ejs +9 -0
  59. package/templates/base/root/gitignore.ejs +3 -0
  60. package/templates/crud/ApplicationMapper.java.ejs +156 -9
  61. package/templates/crud/Controller.java.ejs +36 -6
  62. package/templates/crud/CreateCommand.java.ejs +7 -0
  63. package/templates/crud/CreateCommandHandler.java.ejs +55 -3
  64. package/templates/crud/CreateItemDto.java.ejs +10 -2
  65. package/templates/crud/CreateValueObjectDto.java.ejs +18 -0
  66. package/templates/crud/ListQuery.java.ejs +6 -2
  67. package/templates/crud/ListQueryHandler.java.ejs +24 -10
  68. package/templates/crud/SecondaryEntityDto.java.ejs +7 -2
  69. package/templates/crud/UpdateCommand.java.ejs +52 -0
  70. package/templates/crud/UpdateCommandHandler.java.ejs +105 -0
  71. package/templates/kafka-event/DomainEventHandlerMethod.ejs +1 -0
  72. package/templates/kafka-event/Event.java.ejs +23 -0
  73. package/templates/kafka-event/KafkaMessageBroker.java.ejs +3 -3
  74. package/templates/postman/Collection.json.ejs +95 -34
  75. package/templates/shared/application/dtos/PagedResponse.java.ejs +30 -0
  76. package/templates/shared/configurations/temporalConfig/TemporalConfig.java.ejs +104 -0
  77. package/templates/shared/customExceptions/BusinessException.java.ejs +11 -0
  78. package/templates/shared/customExceptions/InvalidStateTransitionException.java.ejs +11 -0
  79. package/templates/shared/domain/DomainEvent.java.ejs +40 -0
  80. package/templates/shared/handlerException/HandlerExceptions.java.ejs +16 -0
  81. package/templates/shared/interfaces/HeavyActivity.java.ejs +4 -0
  82. package/templates/shared/interfaces/LightActivity.java.ejs +4 -0
  83. package/templates/temporal-activity/ActivityImpl.java.ejs +14 -0
  84. package/templates/temporal-activity/ActivityInterface.java.ejs +11 -0
  85. package/templates/temporal-flow/WorkFlowImpl.java.ejs +64 -0
  86. package/templates/temporal-flow/WorkFlowInterface.java.ejs +19 -0
  87. 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
@@ -543,6 +552,80 @@ public record UserResponseDto(
543
552
  | Instant | Instant | Timestamp UTC |
544
553
  | UUID | UUID | Identificador único |
545
554
 
555
+ ### Propiedades de Campo
556
+
557
+ Los campos en domain.yaml soportan las siguientes propiedades:
558
+
559
+ | Propiedad | Tipo | Default | Descripción |
560
+ |-----------|------|---------|-------------|
561
+ | `name` | String | - | Nombre del campo (obligatorio) |
562
+ | `type` | String | - | Tipo de dato Java (obligatorio) |
563
+ | `annotations` | Array | `[]` | Anotaciones JPA personalizadas |
564
+ | `isValueObject` | Boolean | `false` | Marca explícita de Value Object |
565
+ | `isEmbedded` | Boolean | `false` | Marca explícita de @Embedded |
566
+ | `enumValues` | Array | `[]` | Valores inline de enum |
567
+ | **`readOnly`** | Boolean | `false` | **Excluye del constructor de negocio y CreateDto** |
568
+ | **`hidden`** | Boolean | `false` | **Excluye del ResponseDto** |
569
+ | **`validations`** | Array | `[]` | **Anotaciones JSR-303 en el Command y CreateDto** |
570
+ | **`reference`** | Object | `null` | **Declara referencia semántica a otro agregado (genera comentario Javadoc)** |
571
+
572
+ #### Flags de Visibilidad: `readOnly` y `hidden`
573
+
574
+ **`readOnly: true`** - Campos calculados/derivados
575
+ - ❌ Excluido de: Constructor de negocio, CreateDto
576
+ - ✅ Incluido en: Constructor completo, ResponseDto
577
+ - **Uso:** Totales calculados, contadores, campos derivados
578
+
579
+ ```yaml
580
+ fields:
581
+ - name: totalAmount
582
+ type: BigDecimal
583
+ readOnly: true # Calculado de la suma de items
584
+ ```
585
+
586
+ **`hidden: true`** - Campos sensibles/internos
587
+ - ❌ Excluido de: ResponseDto
588
+ - ✅ Incluido en: Constructor de negocio, CreateDto
589
+ - **Uso:** Passwords, tokens, secrets, información sensible
590
+
591
+ ```yaml
592
+ fields:
593
+ - name: passwordHash
594
+ type: String
595
+ hidden: true # No exponer en API
596
+ ```
597
+
598
+ **Matriz de comportamiento:**
599
+
600
+ | Campo | Constructor Negocio | CreateDto | ResponseDto |
601
+ |-------|---------------------|-----------|-------------|
602
+ | Normal | ✅ | ✅ | ✅ |
603
+ | `readOnly: true` | ❌ | ❌ | ✅ |
604
+ | `hidden: true` | ✅ | ✅ | ❌ |
605
+ | Ambos flags | ❌ | ❌ | ❌ |
606
+
607
+ **Ejemplo práctico:**
608
+ ```yaml
609
+ entities:
610
+ - name: order
611
+ fields:
612
+ - name: orderNumber
613
+ type: String # ✅ Normal - en todos lados
614
+
615
+ - name: totalAmount
616
+ type: BigDecimal
617
+ readOnly: true # ⚙️ Calculado - no en constructor
618
+
619
+ - name: processingToken
620
+ type: String
621
+ hidden: true # 🔒 Sensible - no en respuesta
622
+
623
+ - name: internalFlag
624
+ type: Boolean
625
+ readOnly: true # 🔐 Calculado Y sensible
626
+ hidden: true
627
+ ```
628
+
546
629
  ### Tipos de Relaciones
547
630
 
548
631
  - `OneToOne` - Relación uno a uno
@@ -572,15 +655,18 @@ public record UserResponseDto(
572
655
  ### Al Generar Mappers
573
656
 
574
657
  1. **NUNCA** mapear campos de auditoría (createdAt, updatedAt, createdBy, updatedBy)
575
- 2. **SIEMPRE** filtrar campos antes de usar `.builder()`
576
- 3. **SIEMPRE** mapear bidireccionalidad en relaciones
658
+ 2. **NUNCA** mapear campos readOnly en métodos de creación (fromCommand, fromDto)
659
+ 3. **NUNCA** mapear campos hidden en métodos de respuesta (toDto, toResponseDto)
660
+ 4. **SIEMPRE** filtrar campos antes de usar `.builder()`
661
+ 5. **SIEMPRE** mapear bidireccionalidad en relaciones
577
662
 
578
663
  ### Al Generar DTOs
579
664
 
580
665
  1. **NUNCA** exponer `createdBy` y `updatedBy` en respuestas
581
- 2. **SIEMPRE** exponer `createdAt` y `updatedAt`
582
- 3. **SIEMPRE** usar Java Records para DTOs
583
- 4. **SIEMPRE** filtrar campos al crear contextos de template
666
+ 2. **NUNCA** incluir campos `readOnly` en CreateDto
667
+ 3. **NUNCA** incluir campos `hidden` en ResponseDto
668
+ 4. **SIEMPRE** usar Java Records para DTOs
669
+ 5. **SIEMPRE** filtrar campos según flags de visibilidad
584
670
 
585
671
  ---
586
672
 
@@ -677,13 +763,18 @@ Al generar o modificar código, verificar:
677
763
  - [ ] Métodos de negocio con **validaciones explícitas**
678
764
  - [ ] Entidades JPA con **Lombok y herencia correcta**
679
765
  - [ ] Mappers **excluyen campos de auditoría**
766
+ - [ ] Mappers **excluyen campos readOnly en creación**
767
+ - [ ] Mappers **excluyen campos hidden en respuestas**
680
768
  - [ ] DTOs de respuesta **sin createdBy/updatedBy**
769
+ - [ ] DTOs de respuesta **sin campos hidden**
770
+ - [ ] DTOs de creación **sin campos readOnly**
681
771
  - [ ] Relaciones bidireccionales con métodos `assign*()`
682
772
  - [ ] Value Objects **inmutables**
683
773
  - [ ] Configuración de auditoría cuando `trackUser: true`
774
+ - [ ] Validaciones JSR-303 **solo en Command y CreateDto, nunca en dominio**
684
775
 
685
776
  ---
686
777
 
687
- **Última actualización:** 2026-02-11
778
+ **Última actualización:** 2026-02-21
688
779
  **Versión de eva4j:** 1.x
689
780
  **Estado:** Documento de referencia para agentes IA
@@ -10,6 +10,7 @@
10
10
  - [Entidades](#entidades)
11
11
  - [Value Objects](#value-objects)
12
12
  - [Enums](#enums)
13
+ - [Validaciones JSR-303](#validaciones-jsr-303)
13
14
  - [Relaciones](#relaciones)
14
15
  - [Tipos de Datos](#tipos-de-datos)
15
16
  - [Ejemplos Completos](#ejemplos-completos)
@@ -396,6 +397,449 @@ fields:
396
397
 
397
398
  ---
398
399
 
400
+ ### Control de Visibilidad de Campos
401
+
402
+ Eva4j permite controlar qué campos participan en constructores, DTOs de creación y DTOs de respuesta mediante dos flags opcionales: **`readOnly`** y **`hidden`**.
403
+
404
+ #### 📋 Matriz de Comportamiento
405
+
406
+ | Campo | Constructor Negocio | Constructor Completo | CreateDto | ResponseDto |
407
+ |-------|---------------------|----------------------|-----------|-------------|
408
+ | **Normal** | ✅ Incluido | ✅ Incluido | ✅ Incluido | ✅ Incluido |
409
+ | **`readOnly: true`** | ❌ Excluido | ✅ Incluido | ❌ Excluido | ✅ Incluido |
410
+ | **`hidden: true`** | ✅ Incluido | ✅ Incluido | ✅ Incluido | ❌ Excluido |
411
+ | **Ambos flags** | ❌ Excluido | ✅ Incluido | ❌ Excluido | ❌ Excluido |
412
+
413
+ #### 🔒 `readOnly: true` - Campos Calculados/Derivados
414
+
415
+ Marca campos que **se calculan internamente** y no deben pasarse como parámetros en constructores o DTOs de creación.
416
+
417
+ **Casos de uso típicos:**
418
+ - Totales calculados (suma de items)
419
+ - Contadores automáticos
420
+ - Campos derivados de otros datos
421
+ - Timestamps calculados
422
+
423
+ **Sintaxis:**
424
+ ```yaml
425
+ fields:
426
+ - name: totalAmount
427
+ type: BigDecimal
428
+ readOnly: true # ✅ No en constructor ni CreateDto
429
+ ```
430
+
431
+ **Ejemplo completo:**
432
+ ```yaml
433
+ entities:
434
+ - name: order
435
+ isRoot: true
436
+ tableName: orders
437
+ audit:
438
+ enabled: true
439
+ fields:
440
+ - name: id
441
+ type: String
442
+ - name: orderNumber
443
+ type: String
444
+ - name: customerId
445
+ type: String
446
+ # Campo readOnly - calculado de los items
447
+ - name: totalAmount
448
+ type: BigDecimal
449
+ readOnly: true
450
+ # Campo readOnly - contador de items
451
+ - name: itemCount
452
+ type: Integer
453
+ readOnly: true
454
+ ```
455
+
456
+ **Código generado:**
457
+ ```java
458
+ // Constructor de negocio - SIN fields readOnly
459
+ public Order(String orderNumber, String customerId) {
460
+ this.orderNumber = orderNumber;
461
+ this.customerId = customerId;
462
+ // totalAmount e itemCount NO están aquí
463
+ }
464
+
465
+ // Constructor completo - CON fields readOnly (reconstrucción desde DB)
466
+ public Order(String id, String orderNumber, String customerId,
467
+ BigDecimal totalAmount, Integer itemCount,
468
+ LocalDateTime createdAt, LocalDateTime updatedAt) {
469
+ // Todos los campos incluidos
470
+ }
471
+
472
+ // CreateDto - SIN fields readOnly
473
+ public record CreateOrderDto(
474
+ String orderNumber,
475
+ String customerId
476
+ // totalAmount e itemCount NO están aquí
477
+ ) {}
478
+
479
+ // ResponseDto - CON fields readOnly (mostrar valores calculados)
480
+ public record OrderResponseDto(
481
+ String id,
482
+ String orderNumber,
483
+ String customerId,
484
+ BigDecimal totalAmount, // ✅ Incluido
485
+ Integer itemCount, // ✅ Incluido
486
+ LocalDateTime createdAt,
487
+ LocalDateTime updatedAt
488
+ ) {}
489
+ ```
490
+
491
+ #### 🙈 `hidden: true` - Campos Sensibles/Internos
492
+
493
+ Marca campos que **NO deben exponerse** en respuestas de API pero sí pueden recibirse en creación.
494
+
495
+ **Casos de uso típicos:**
496
+ - Passwords/hashes de seguridad
497
+ - Tokens internos
498
+ - Secrets y claves de API
499
+ - Información sensible (SSN, datos privados)
500
+ - Flags de control interno
501
+
502
+ **Sintaxis:**
503
+ ```yaml
504
+ fields:
505
+ - name: passwordHash
506
+ type: String
507
+ hidden: true # ✅ No en ResponseDto
508
+ ```
509
+
510
+ **Ejemplo completo:**
511
+ ```yaml
512
+ entities:
513
+ - name: user
514
+ isRoot: true
515
+ tableName: users
516
+ audit:
517
+ enabled: true
518
+ trackUser: true
519
+ fields:
520
+ - name: id
521
+ type: String
522
+ - name: username
523
+ type: String
524
+ - name: email
525
+ type: String
526
+ # Campo hidden - NO en ResponseDto
527
+ - name: passwordHash
528
+ type: String
529
+ hidden: true
530
+ # Campo hidden - token interno
531
+ - name: resetPasswordToken
532
+ type: String
533
+ hidden: true
534
+ ```
535
+
536
+ **Código generado:**
537
+ ```java
538
+ // Constructor de negocio - CON fields hidden
539
+ public User(String username, String email,
540
+ String passwordHash, String resetPasswordToken) {
541
+ this.username = username;
542
+ this.email = email;
543
+ this.passwordHash = passwordHash;
544
+ this.resetPasswordToken = resetPasswordToken;
545
+ }
546
+
547
+ // CreateDto - CON fields hidden (para recibirlos en creación)
548
+ public record CreateUserDto(
549
+ String username,
550
+ String email,
551
+ String passwordHash, // ✅ Se puede recibir
552
+ String resetPasswordToken // ✅ Se puede recibir
553
+ ) {}
554
+
555
+ // ResponseDto - SIN fields hidden (proteger datos sensibles)
556
+ public record UserResponseDto(
557
+ String id,
558
+ String username,
559
+ String email,
560
+ LocalDateTime createdAt,
561
+ LocalDateTime updatedAt
562
+ // passwordHash y resetPasswordToken NO están aquí
563
+ ) {}
564
+ ```
565
+
566
+ #### 🔐 Combinando Ambos Flags
567
+
568
+ Puedes combinar `readOnly` y `hidden` para campos que son **calculados internamente Y sensibles**.
569
+
570
+ **Ejemplo:**
571
+ ```yaml
572
+ fields:
573
+ - name: isLocked
574
+ type: Boolean
575
+ readOnly: true # Calculado internamente
576
+ hidden: true # NO exponer en API
577
+ ```
578
+
579
+ **Resultado:**
580
+ - ❌ NO en constructor de negocio (es readOnly)
581
+ - ❌ NO en CreateDto (es readOnly)
582
+ - ❌ NO en ResponseDto (es hidden)
583
+ - ✅ SÍ en constructor completo (para reconstrucción desde DB)
584
+
585
+ #### 📘 Ejemplo Completo: Sistema de Órdenes
586
+
587
+ ```yaml
588
+ aggregates:
589
+ - name: Order
590
+ entities:
591
+ - name: order
592
+ isRoot: true
593
+ tableName: orders
594
+ audit:
595
+ enabled: true
596
+ trackUser: true
597
+ fields:
598
+ - name: id
599
+ type: String
600
+
601
+ # Campos normales
602
+ - name: orderNumber
603
+ type: String
604
+ - name: customerId
605
+ type: String
606
+ - name: status
607
+ type: String
608
+
609
+ # Campos readOnly (calculados)
610
+ - name: totalAmount
611
+ type: BigDecimal
612
+ readOnly: true # Suma de items
613
+ - name: itemCount
614
+ type: Integer
615
+ readOnly: true # Cuenta de items
616
+
617
+ # Campo hidden (interno)
618
+ - name: processingToken
619
+ type: String
620
+ hidden: true # Token de procesamiento interno
621
+
622
+ # Campo readOnly + hidden (calculado e interno)
623
+ - name: riskScore
624
+ type: Integer
625
+ readOnly: true
626
+ hidden: true # Puntaje de riesgo interno
627
+ ```
628
+
629
+ **Constructor de negocio generado:**
630
+ ```java
631
+ public Order(String orderNumber, String customerId,
632
+ String status, String processingToken) {
633
+ // totalAmount, itemCount, riskScore NO están (readOnly)
634
+ // processingToken SÍ está (solo hidden, no readOnly)
635
+ }
636
+ ```
637
+
638
+ **CreateOrderDto generado:**
639
+ ```java
640
+ public record CreateOrderDto(
641
+ String orderNumber,
642
+ String customerId,
643
+ String status,
644
+ String processingToken // ✅ hidden pero SÍ en create
645
+ // totalAmount, itemCount, riskScore NO están (readOnly)
646
+ ) {}
647
+ ```
648
+
649
+ **OrderResponseDto generado:**
650
+ ```java
651
+ public record OrderResponseDto(
652
+ String id,
653
+ String orderNumber,
654
+ String customerId,
655
+ String status,
656
+ BigDecimal totalAmount, // ✅ readOnly pero SÍ en response
657
+ Integer itemCount, // ✅ readOnly pero SÍ en response
658
+ LocalDateTime createdAt,
659
+ LocalDateTime updatedAt
660
+ // processingToken NO está (hidden)
661
+ // riskScore NO está (hidden)
662
+ ) {}
663
+ ```
664
+
665
+ #### ⚡ Comportamiento por Defecto
666
+
667
+ Si no especificas `readOnly` ni `hidden`:
668
+ - ✅ El comportamiento actual se mantiene sin cambios
669
+ - ✅ Campos normales aparecen en todos lados
670
+ - ✅ Solo los campos de auditoría (`createdBy`, `updatedBy`) se excluyen automáticamente de ResponseDto
671
+
672
+ ```yaml
673
+ # Sin flags - comportamiento estándar
674
+ fields:
675
+ - name: productName
676
+ type: String # ✅ En constructor, CreateDto Y ResponseDto
677
+ ```
678
+
679
+ #### 📚 Ver También
680
+
681
+ - **Ejemplo completo:** [examples/domain-field-visibility.yaml](../examples/domain-field-visibility.yaml)
682
+ - **Campos de auditoría:** Los campos `createdAt`, `updatedAt`, `createdBy`, `updatedBy` siguen su propio comportamiento especial definido en la sección de Auditoría
683
+
684
+ ---
685
+
686
+ ### Referencias entre Agregados (`reference:`)
687
+
688
+ La propiedad `reference:` declara explícitamente que un campo es un puntero intencional a la raíz de otro agregado. El campo sigue siendo un tipo primitivo (`String`, `Long`, etc.) — **no se genera ningún `@ManyToOne`**.
689
+
690
+ #### Sintaxis
691
+
692
+ ```yaml
693
+ fields:
694
+ - name: customerId
695
+ type: String
696
+ reference:
697
+ aggregate: Customer # Nombre del agregado referenciado (PascalCase) — obligatorio
698
+ module: customers # Módulo donde vive el agregado — opcional
699
+ - name: productId
700
+ type: String
701
+ reference:
702
+ aggregate: Product
703
+ module: catalog
704
+ ```
705
+
706
+ #### Comportamiento
707
+
708
+ - El tipo Java **no cambia** — sigue siendo `String`, `Long`, UUID, etc.
709
+ - JPA genera `@Column` normal — **sin** `@ManyToOne` ni `@JoinColumn`.
710
+ - En la entidad de dominio y en la entidad JPA se genera un **comentario Javadoc** que documenta la referencia.
711
+ - `module:` es opcional: puede omitirse si el agregado referenciado está en el mismo módulo.
712
+ - Si `reference:` está malformado (falta `aggregate`), eva4j lanza un error descriptivo.
713
+
714
+ #### Código Generado
715
+
716
+ ```java
717
+ // domain/models/entities/Order.java
718
+ /** Cross-aggregate reference → Customer (module: customers) */
719
+ private String customerId;
720
+ ```
721
+
722
+ ```java
723
+ // infrastructure/database/entities/OrderJpa.java
724
+ @Column(name = "customer_id")
725
+ /** Cross-aggregate reference → Customer (module: customers) */
726
+ private String customerId;
727
+ ```
728
+
729
+ #### Por qué no usar `@ManyToOne` entre agregados
730
+
731
+ En DDD cada agregado es una unidad transaccional independiente. Un `@ManyToOne` cruzando límites crea un único grafo JPA que viola los límites transaccionales y crea dependencias de carga invisibles. La referencia por ID es el patrón correcto: el handler que necesite los datos del otro agregado los obtiene explícitamente via su propio repositorio.
732
+
733
+ - **Ejemplo completo:** [examples/domain-multi-aggregate.yaml](../examples/domain-multi-aggregate.yaml)
734
+
735
+ ---
736
+
737
+ ### Validaciones JSR-303
738
+
739
+ Eva4j soporta anotaciones Bean Validation (JSR-303/Jakarta Validation) en campos del `domain.yaml`. Las validaciones se generan **únicamente en la capa de aplicación**: en el `Create<Aggregate>Command` y en los `Create<Entity>Dto` de entidades secundarias. **No se aplican a entidades de dominio** ni a campos con `readOnly: true`.
740
+
741
+ El import `jakarta.validation.constraints.*` se agrega automáticamente cuando se detecta al menos una validación en los campos del comando.
742
+
743
+ #### Sintaxis
744
+
745
+ ```yaml
746
+ fields:
747
+ - name: email
748
+ type: String
749
+ validations:
750
+ - type: NotBlank
751
+ message: "Email es requerido"
752
+ - type: Email
753
+ message: "Email inválido"
754
+
755
+ - name: age
756
+ type: Integer
757
+ validations:
758
+ - type: Min
759
+ value: 18
760
+ message: "Edad mínima 18 años"
761
+ - type: Max
762
+ value: 120
763
+
764
+ - name: username
765
+ type: String
766
+ validations:
767
+ - type: Size
768
+ min: 3
769
+ max: 50
770
+ message: "Username entre 3 y 50 caracteres"
771
+
772
+ - name: code
773
+ type: String
774
+ validations:
775
+ - type: Pattern
776
+ regexp: "^[A-Z]{3}-[0-9]{4}$"
777
+ message: "Formato inválido"
778
+
779
+ - name: price
780
+ type: BigDecimal
781
+ validations:
782
+ - type: Digits
783
+ integer: 10
784
+ fraction: 2
785
+ ```
786
+
787
+ #### Propiedades por Tipo
788
+
789
+ | Propiedad | Tipos que la usan | Descripción |
790
+ |-----------|-------------------|-------------|
791
+ | `type` | Todos | Nombre de la anotación (`NotNull`, `NotBlank`, `Email`, `Min`, `Max`, `Size`, `Pattern`, `Digits`, `Positive`, `Negative`, `Past`, `Future`, etc.) |
792
+ | `message` | Todos (opcional) | Mensaje de error personalizado |
793
+ | `value` | `Min`, `Max` | Valor límite numérico |
794
+ | `min` | `Size` | Tamaño mínimo |
795
+ | `max` | `Size` | Tamaño máximo |
796
+ | `regexp` | `Pattern` | Expresión regular |
797
+ | `integer` | `Digits` | Máximo de dígitos enteros |
798
+ | `fraction` | `Digits` | Máximo de dígitos decimales |
799
+ | `inclusive` | `DecimalMin`, `DecimalMax` | Si el límite es inclusivo |
800
+
801
+ #### Anotaciones sin parámetros (solo `type` requerido)
802
+
803
+ `NotNull`, `NotBlank`, `NotEmpty`, `Email`, `Positive`, `PositiveOrZero`, `Negative`, `NegativeOrZero`, `Past`, `PastOrPresent`, `Future`, `FutureOrPresent`, `AssertTrue`, `AssertFalse`
804
+
805
+ #### Código generado
806
+
807
+ Para un campo con validaciones:
808
+
809
+ ```yaml
810
+ - name: email
811
+ type: String
812
+ validations:
813
+ - type: Email
814
+ message: "Email inválido"
815
+ - type: NotBlank
816
+ message: "Email es requerido"
817
+ ```
818
+
819
+ Se genera en `CreateUserCommand.java`:
820
+
821
+ ```java
822
+ import jakarta.validation.constraints.*;
823
+
824
+ public record CreateUserCommand(
825
+ @Email(message = "Email inválido")
826
+ @NotBlank(message = "Email es requerido")
827
+ String email,
828
+ ...
829
+ ) implements Command {
830
+ }
831
+ ```
832
+
833
+ #### Reglas de aplicación
834
+
835
+ - ✅ **Sí** se aplican en `Create<Aggregate>Command`
836
+ - ✅ **Sí** se aplican en `Create<Entity>Dto` (entidades secundarias)
837
+ - ❌ **No** se aplican a entidades de dominio (`Order.java`, etc.)
838
+ - ❌ **No** se aplican a campos con `readOnly: true` (ya están excluidos del command)
839
+ - ❌ **No** se aplican a campos con `hidden: true` si también son `readOnly: true`
840
+
841
+ ---
842
+
399
843
  ### Auditoría Automática
400
844
 
401
845
  eva4j soporta dos niveles de auditoría automática de entidades: