eva4j 1.0.9 → 1.0.11

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 (42) hide show
  1. package/AGENTS.md +87 -6
  2. package/DOMAIN_YAML_GUIDE.md +393 -0
  3. package/FUTURE_FEATURES.md +968 -556
  4. package/README.md +3 -3
  5. package/bin/eva4j.js +1 -0
  6. package/examples/domain-field-visibility.yaml +235 -0
  7. package/examples/domain-multiple-relations.yaml +4 -4
  8. package/examples/domain-one-to-many.yaml +1 -1
  9. package/examples/domain-one-to-one.yaml +6 -3
  10. package/examples/domain-secondary-onetomany.yaml +106 -0
  11. package/examples/domain-secondary-onetoone.yaml +98 -0
  12. package/examples/domain-transitions.yaml +60 -0
  13. package/package.json +4 -3
  14. package/prettier.config.js +9 -0
  15. package/src/commands/detach.js +57 -0
  16. package/src/commands/generate-entities.js +258 -57
  17. package/src/commands/generate-kafka-event.js +7 -4
  18. package/src/commands/generate-resource.js +22 -25
  19. package/src/commands/generate-usecase.js +17 -41
  20. package/src/generators/base-generator.js +2 -1
  21. package/src/generators/shared-generator.js +20 -2
  22. package/src/utils/checksum-manager.js +130 -0
  23. package/src/utils/template-engine.js +63 -5
  24. package/src/utils/yaml-to-entity.js +284 -17
  25. package/templates/aggregate/AggregateMapper.java.ejs +52 -10
  26. package/templates/aggregate/AggregateRoot.java.ejs +75 -5
  27. package/templates/aggregate/DomainEntity.java.ejs +101 -4
  28. package/templates/aggregate/Enum.java.ejs +49 -1
  29. package/templates/aggregate/JpaEntity.java.ejs +10 -2
  30. package/templates/base/application/Application.java.ejs +1 -1
  31. package/templates/base/root/gitignore.ejs +3 -0
  32. package/templates/crud/ApplicationMapper.java.ejs +156 -9
  33. package/templates/crud/CreateCommand.java.ejs +7 -0
  34. package/templates/crud/CreateCommandHandler.java.ejs +55 -3
  35. package/templates/crud/CreateItemDto.java.ejs +10 -2
  36. package/templates/crud/CreateValueObjectDto.java.ejs +18 -0
  37. package/templates/crud/SecondaryEntityDto.java.ejs +7 -2
  38. package/templates/kafka-event/KafkaMessageBroker.java.ejs +3 -3
  39. package/templates/postman/Collection.json.ejs +95 -34
  40. package/templates/shared/customExceptions/BusinessException.java.ejs +11 -0
  41. package/templates/shared/customExceptions/InvalidStateTransitionException.java.ejs +11 -0
  42. package/templates/shared/handlerException/HandlerExceptions.java.ejs +16 -0
package/AGENTS.md CHANGED
@@ -543,6 +543,79 @@ public record UserResponseDto(
543
543
  | Instant | Instant | Timestamp UTC |
544
544
  | UUID | UUID | Identificador único |
545
545
 
546
+ ### Propiedades de Campo
547
+
548
+ Los campos en domain.yaml soportan las siguientes propiedades:
549
+
550
+ | Propiedad | Tipo | Default | Descripción |
551
+ |-----------|------|---------|-------------|
552
+ | `name` | String | - | Nombre del campo (obligatorio) |
553
+ | `type` | String | - | Tipo de dato Java (obligatorio) |
554
+ | `annotations` | Array | `[]` | Anotaciones JPA personalizadas |
555
+ | `isValueObject` | Boolean | `false` | Marca explícita de Value Object |
556
+ | `isEmbedded` | Boolean | `false` | Marca explícita de @Embedded |
557
+ | `enumValues` | Array | `[]` | Valores inline de enum |
558
+ | **`readOnly`** | Boolean | `false` | **Excluye del constructor de negocio y CreateDto** |
559
+ | **`hidden`** | Boolean | `false` | **Excluye del ResponseDto** |
560
+ | **`validations`** | Array | `[]` | **Anotaciones JSR-303 en el Command y CreateDto** |
561
+
562
+ #### Flags de Visibilidad: `readOnly` y `hidden`
563
+
564
+ **`readOnly: true`** - Campos calculados/derivados
565
+ - ❌ Excluido de: Constructor de negocio, CreateDto
566
+ - ✅ Incluido en: Constructor completo, ResponseDto
567
+ - **Uso:** Totales calculados, contadores, campos derivados
568
+
569
+ ```yaml
570
+ fields:
571
+ - name: totalAmount
572
+ type: BigDecimal
573
+ readOnly: true # Calculado de la suma de items
574
+ ```
575
+
576
+ **`hidden: true`** - Campos sensibles/internos
577
+ - ❌ Excluido de: ResponseDto
578
+ - ✅ Incluido en: Constructor de negocio, CreateDto
579
+ - **Uso:** Passwords, tokens, secrets, información sensible
580
+
581
+ ```yaml
582
+ fields:
583
+ - name: passwordHash
584
+ type: String
585
+ hidden: true # No exponer en API
586
+ ```
587
+
588
+ **Matriz de comportamiento:**
589
+
590
+ | Campo | Constructor Negocio | CreateDto | ResponseDto |
591
+ |-------|---------------------|-----------|-------------|
592
+ | Normal | ✅ | ✅ | ✅ |
593
+ | `readOnly: true` | ❌ | ❌ | ✅ |
594
+ | `hidden: true` | ✅ | ✅ | ❌ |
595
+ | Ambos flags | ❌ | ❌ | ❌ |
596
+
597
+ **Ejemplo práctico:**
598
+ ```yaml
599
+ entities:
600
+ - name: order
601
+ fields:
602
+ - name: orderNumber
603
+ type: String # ✅ Normal - en todos lados
604
+
605
+ - name: totalAmount
606
+ type: BigDecimal
607
+ readOnly: true # ⚙️ Calculado - no en constructor
608
+
609
+ - name: processingToken
610
+ type: String
611
+ hidden: true # 🔒 Sensible - no en respuesta
612
+
613
+ - name: internalFlag
614
+ type: Boolean
615
+ readOnly: true # 🔐 Calculado Y sensible
616
+ hidden: true
617
+ ```
618
+
546
619
  ### Tipos de Relaciones
547
620
 
548
621
  - `OneToOne` - Relación uno a uno
@@ -572,15 +645,18 @@ public record UserResponseDto(
572
645
  ### Al Generar Mappers
573
646
 
574
647
  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
648
+ 2. **NUNCA** mapear campos readOnly en métodos de creación (fromCommand, fromDto)
649
+ 3. **NUNCA** mapear campos hidden en métodos de respuesta (toDto, toResponseDto)
650
+ 4. **SIEMPRE** filtrar campos antes de usar `.builder()`
651
+ 5. **SIEMPRE** mapear bidireccionalidad en relaciones
577
652
 
578
653
  ### Al Generar DTOs
579
654
 
580
655
  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
656
+ 2. **NUNCA** incluir campos `readOnly` en CreateDto
657
+ 3. **NUNCA** incluir campos `hidden` en ResponseDto
658
+ 4. **SIEMPRE** usar Java Records para DTOs
659
+ 5. **SIEMPRE** filtrar campos según flags de visibilidad
584
660
 
585
661
  ---
586
662
 
@@ -677,13 +753,18 @@ Al generar o modificar código, verificar:
677
753
  - [ ] Métodos de negocio con **validaciones explícitas**
678
754
  - [ ] Entidades JPA con **Lombok y herencia correcta**
679
755
  - [ ] Mappers **excluyen campos de auditoría**
756
+ - [ ] Mappers **excluyen campos readOnly en creación**
757
+ - [ ] Mappers **excluyen campos hidden en respuestas**
680
758
  - [ ] DTOs de respuesta **sin createdBy/updatedBy**
759
+ - [ ] DTOs de respuesta **sin campos hidden**
760
+ - [ ] DTOs de creación **sin campos readOnly**
681
761
  - [ ] Relaciones bidireccionales con métodos `assign*()`
682
762
  - [ ] Value Objects **inmutables**
683
763
  - [ ] Configuración de auditoría cuando `trackUser: true`
764
+ - [ ] Validaciones JSR-303 **solo en Command y CreateDto, nunca en dominio**
684
765
 
685
766
  ---
686
767
 
687
- **Última actualización:** 2026-02-11
768
+ **Última actualización:** 2026-02-21
688
769
  **Versión de eva4j:** 1.x
689
770
  **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,398 @@ 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
+ ### Validaciones JSR-303
687
+
688
+ 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`.
689
+
690
+ El import `jakarta.validation.constraints.*` se agrega automáticamente cuando se detecta al menos una validación en los campos del comando.
691
+
692
+ #### Sintaxis
693
+
694
+ ```yaml
695
+ fields:
696
+ - name: email
697
+ type: String
698
+ validations:
699
+ - type: NotBlank
700
+ message: "Email es requerido"
701
+ - type: Email
702
+ message: "Email inválido"
703
+
704
+ - name: age
705
+ type: Integer
706
+ validations:
707
+ - type: Min
708
+ value: 18
709
+ message: "Edad mínima 18 años"
710
+ - type: Max
711
+ value: 120
712
+
713
+ - name: username
714
+ type: String
715
+ validations:
716
+ - type: Size
717
+ min: 3
718
+ max: 50
719
+ message: "Username entre 3 y 50 caracteres"
720
+
721
+ - name: code
722
+ type: String
723
+ validations:
724
+ - type: Pattern
725
+ regexp: "^[A-Z]{3}-[0-9]{4}$"
726
+ message: "Formato inválido"
727
+
728
+ - name: price
729
+ type: BigDecimal
730
+ validations:
731
+ - type: Digits
732
+ integer: 10
733
+ fraction: 2
734
+ ```
735
+
736
+ #### Propiedades por Tipo
737
+
738
+ | Propiedad | Tipos que la usan | Descripción |
739
+ |-----------|-------------------|-------------|
740
+ | `type` | Todos | Nombre de la anotación (`NotNull`, `NotBlank`, `Email`, `Min`, `Max`, `Size`, `Pattern`, `Digits`, `Positive`, `Negative`, `Past`, `Future`, etc.) |
741
+ | `message` | Todos (opcional) | Mensaje de error personalizado |
742
+ | `value` | `Min`, `Max` | Valor límite numérico |
743
+ | `min` | `Size` | Tamaño mínimo |
744
+ | `max` | `Size` | Tamaño máximo |
745
+ | `regexp` | `Pattern` | Expresión regular |
746
+ | `integer` | `Digits` | Máximo de dígitos enteros |
747
+ | `fraction` | `Digits` | Máximo de dígitos decimales |
748
+ | `inclusive` | `DecimalMin`, `DecimalMax` | Si el límite es inclusivo |
749
+
750
+ #### Anotaciones sin parámetros (solo `type` requerido)
751
+
752
+ `NotNull`, `NotBlank`, `NotEmpty`, `Email`, `Positive`, `PositiveOrZero`, `Negative`, `NegativeOrZero`, `Past`, `PastOrPresent`, `Future`, `FutureOrPresent`, `AssertTrue`, `AssertFalse`
753
+
754
+ #### Código generado
755
+
756
+ Para un campo con validaciones:
757
+
758
+ ```yaml
759
+ - name: email
760
+ type: String
761
+ validations:
762
+ - type: Email
763
+ message: "Email inválido"
764
+ - type: NotBlank
765
+ message: "Email es requerido"
766
+ ```
767
+
768
+ Se genera en `CreateUserCommand.java`:
769
+
770
+ ```java
771
+ import jakarta.validation.constraints.*;
772
+
773
+ public record CreateUserCommand(
774
+ @Email(message = "Email inválido")
775
+ @NotBlank(message = "Email es requerido")
776
+ String email,
777
+ ...
778
+ ) implements Command {
779
+ }
780
+ ```
781
+
782
+ #### Reglas de aplicación
783
+
784
+ - ✅ **Sí** se aplican en `Create<Aggregate>Command`
785
+ - ✅ **Sí** se aplican en `Create<Entity>Dto` (entidades secundarias)
786
+ - ❌ **No** se aplican a entidades de dominio (`Order.java`, etc.)
787
+ - ❌ **No** se aplican a campos con `readOnly: true` (ya están excluidos del command)
788
+ - ❌ **No** se aplican a campos con `hidden: true` si también son `readOnly: true`
789
+
790
+ ---
791
+
399
792
  ### Auditoría Automática
400
793
 
401
794
  eva4j soporta dos niveles de auditoría automática de entidades: