@imolko/ultra-agent 0.1.0-beta
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.
- package/README.md +93 -0
- package/bin/cli.js +2 -0
- package/dist-node/bin.d.ts +2 -0
- package/dist-node/bin.d.ts.map +1 -0
- package/dist-node/bin.js +5 -0
- package/dist-node/bin.js.map +1 -0
- package/dist-node/cli.d.ts +41 -0
- package/dist-node/cli.d.ts.map +1 -0
- package/dist-node/cli.js +170 -0
- package/dist-node/cli.js.map +1 -0
- package/dist-node/index.d.ts +14 -0
- package/dist-node/index.d.ts.map +1 -0
- package/dist-node/index.js +40 -0
- package/dist-node/index.js.map +1 -0
- package/dist-node/init-lint.d.ts +43 -0
- package/dist-node/init-lint.d.ts.map +1 -0
- package/dist-node/init-lint.js +84 -0
- package/dist-node/init-lint.js.map +1 -0
- package/dist-node/manifest.d.ts +33 -0
- package/dist-node/manifest.d.ts.map +1 -0
- package/dist-node/manifest.js +59 -0
- package/dist-node/manifest.js.map +1 -0
- package/dist-node/materializer.d.ts +52 -0
- package/dist-node/materializer.d.ts.map +1 -0
- package/dist-node/materializer.js +194 -0
- package/dist-node/materializer.js.map +1 -0
- package/dist-node/package-root.d.ts +5 -0
- package/dist-node/package-root.d.ts.map +1 -0
- package/dist-node/package-root.js +17 -0
- package/dist-node/package-root.js.map +1 -0
- package/dist-node/payload.d.ts +22 -0
- package/dist-node/payload.d.ts.map +1 -0
- package/dist-node/payload.js +49 -0
- package/dist-node/payload.js.map +1 -0
- package/dist-node/pointer-hub.d.ts +22 -0
- package/dist-node/pointer-hub.d.ts.map +1 -0
- package/dist-node/pointer-hub.js +50 -0
- package/dist-node/pointer-hub.js.map +1 -0
- package/dist-node/publication.cli.d.ts +9 -0
- package/dist-node/publication.cli.d.ts.map +1 -0
- package/dist-node/publication.cli.js +17 -0
- package/dist-node/publication.cli.js.map +1 -0
- package/dist-node/publication.d.ts +23 -0
- package/dist-node/publication.d.ts.map +1 -0
- package/dist-node/publication.js +47 -0
- package/dist-node/publication.js.map +1 -0
- package/dist-node/reconcile.cli.d.ts +11 -0
- package/dist-node/reconcile.cli.d.ts.map +1 -0
- package/dist-node/reconcile.cli.js +39 -0
- package/dist-node/reconcile.cli.js.map +1 -0
- package/dist-node/reconcile.d.ts +30 -0
- package/dist-node/reconcile.d.ts.map +1 -0
- package/dist-node/reconcile.js +51 -0
- package/dist-node/reconcile.js.map +1 -0
- package/dist-node/ship/prompts/fix.md +22 -0
- package/dist-node/ship/prompts/implement.md +34 -0
- package/dist-node/ship/prompts/review.md +28 -0
- package/dist-node/ship/ship.agent.d.ts +22 -0
- package/dist-node/ship/ship.agent.d.ts.map +1 -0
- package/dist-node/ship/ship.agent.js +142 -0
- package/dist-node/ship/ship.agent.js.map +1 -0
- package/dist-node/ship/ship.args.d.ts +15 -0
- package/dist-node/ship/ship.args.d.ts.map +1 -0
- package/dist-node/ship/ship.args.js +93 -0
- package/dist-node/ship/ship.args.js.map +1 -0
- package/dist-node/ship/ship.context.d.ts +12 -0
- package/dist-node/ship/ship.context.d.ts.map +1 -0
- package/dist-node/ship/ship.context.js +3 -0
- package/dist-node/ship/ship.context.js.map +1 -0
- package/dist-node/ship/ship.d.ts +4 -0
- package/dist-node/ship/ship.d.ts.map +1 -0
- package/dist-node/ship/ship.js +221 -0
- package/dist-node/ship/ship.js.map +1 -0
- package/dist-node/ship/ship.mr.d.ts +9 -0
- package/dist-node/ship/ship.mr.d.ts.map +1 -0
- package/dist-node/ship/ship.mr.js +171 -0
- package/dist-node/ship/ship.mr.js.map +1 -0
- package/dist-node/ship/ship.output.d.ts +9 -0
- package/dist-node/ship/ship.output.d.ts.map +1 -0
- package/dist-node/ship/ship.output.js +21 -0
- package/dist-node/ship/ship.output.js.map +1 -0
- package/dist-node/ship/ship.tracker.d.ts +37 -0
- package/dist-node/ship/ship.tracker.d.ts.map +1 -0
- package/dist-node/ship/ship.tracker.js +220 -0
- package/dist-node/ship/ship.tracker.js.map +1 -0
- package/dist-node/version.cli.d.ts +9 -0
- package/dist-node/version.cli.d.ts.map +1 -0
- package/dist-node/version.cli.js +30 -0
- package/dist-node/version.cli.js.map +1 -0
- package/dist-node/version.d.ts +24 -0
- package/dist-node/version.d.ts.map +1 -0
- package/dist-node/version.js +76 -0
- package/dist-node/version.js.map +1 -0
- package/docs/adr/ADR-0000-proceso-y-formato-de-adrs.md +115 -0
- package/docs/adr/foundational/A-001-clean-architecture-4-capas.md +88 -0
- package/docs/adr/foundational/A-002-cqrs-semantico.md +58 -0
- package/docs/adr/foundational/A-003-tdd.md +97 -0
- package/docs/adr/foundational/A-005-either-return-custom-methods.md +80 -0
- package/docs/adr/foundational/A-006-feature-completion-criteria.md +47 -0
- package/docs/adr/foundational/A-007-data-contract-fail-fast.md +109 -0
- package/docs/adr/foundational/A-008-bounded-context-decomposition.md +130 -0
- package/docs/adr/foundational/A-009-use-case-taxonomy.md +99 -0
- package/docs/adr/foundational/A-010-propiedad-emision-eventos-dominio.md +95 -0
- package/docs/adr/foundational/A-011-characterization-first-refactoring.md +90 -0
- package/docs/adr/foundational/A-012-helm-mecanismo-primario-despliegue.md +67 -0
- package/docs/adr/foundational/A-013-contrato-settings-unica-fuente-configuracion.md +74 -0
- package/docs/adr/foundational/A-014-generacion-artefactos-despliegue-desde-contrato-settings.md +75 -0
- package/docs/adr/foundational/A-015-cloudevents-estandar-eventos.md +85 -0
- package/docs/adr/foundational/A-016-cicd-gitlab-pipeline-raiz-pipelines-modulares.md +92 -0
- package/docs/adr/foundational/A-017-taxonomia-repositorios-librerias-sistemas.md +93 -0
- package/docs/adr/foundational/A-018-practicas-build-imagenes-docker.md +85 -0
- package/docs/adr/foundational/A-019-convenciones-autoria-helm-charts.md +97 -0
- package/docs/adr/foundational/A-020-entorno-desarrollo-estandarizado-devcontainer.md +58 -0
- package/docs/adr/foundational/A-022-formato-reporte-tests-ctrf.md +55 -0
- package/docs/adr/foundational/A-023-composicion-bloques-prompt-agentes.md +65 -0
- package/docs/adr/foundational/A-024-features-slices-verticales-portables.md +41 -0
- package/docs/adr/foundational/A-025-anatomia-mecanica-feature-puertos-adaptadores-drivers.md +104 -0
- package/docs/adr/foundational/A-026-infraestructura-compartida.md +44 -0
- package/docs/adr/foundational/A-027-patron-wiring-entry-point.md +67 -0
- package/docs/adr/foundational/A-029-verbos-intencion-metodos-lado-negocio.md +54 -0
- package/docs/adr/foundational/A-030-global-development-tooling.md +52 -0
- package/docs/adr/foundational/A-031-ubiquitous-language.md +64 -0
- package/docs/adr/foundational/A-032-domain-artifact-documentation-format.md +128 -0
- package/docs/adr/foundational/A-033-estandar-mensajes-commit.md +127 -0
- package/docs/adr/foundational/A-034-canonical-skill-set.md +64 -0
- package/docs/adr/foundational/A-035-captura-ideas-reanclaje-candies.md +51 -0
- package/docs/adr/foundational/A-036-adopcion-wayfinder-ortogonalidad-labels.md +37 -0
- package/docs/adr/foundational/A-037-formalizacion-taxonomia-labels.md +98 -0
- package/docs/adr/foundational/A-038-issues-referencian-skills-nombre.md +54 -0
- package/docs/adr/foundational/A-039-pipeline-ship-issues-hijos-spec.md +64 -0
- package/docs/adr/foundational/A-040-punteros-configuracion-repo-skills.md +44 -0
- package/docs/adr/foundational/A-041-estrategia-de-ramas.md +45 -0
- package/docs/adr/foundational/A-042-formato-documentos-issue-tracker.md +78 -0
- package/docs/adr/foundational/A-043-vocabulario-etiquetas-adrs.md +42 -0
- package/docs/adr/foundational/A-044-composicion-funcional-procesos-operaciones.md +44 -0
- package/docs/adr/foundational/A-045-guardrails-aplicables-no-directrices.md +48 -0
- package/docs/adr/foundational/A-046-metricas-de-forma-y-legibilidad-del-codigo.md +50 -0
- package/docs/adr/foundational/A-047-dependencias-explicitas-sin-globales-ambientales.md +40 -0
- package/docs/adr/foundational/A-048-cobertura-minima-codigo.md +39 -0
- package/docs/adr/foundational/A-049-materializacion-bucle-guardrails-extension-nativa-pi.md +49 -0
- package/docs/adr/foundational/A-050-el-caso-de-uso-pide-al-dominio-no-lee-su-representacion.md +50 -0
- package/docs/adr/foundational/A-051-orden-imports-por-distancia.md +47 -0
- package/docs/adr/foundational/A-052-distribucion-metodologia-paquete-versionado.md +42 -0
- package/docs/adr/foundational/A-053-errores-compartidos-entre-features-dos-hogares-por-capa.md +43 -0
- package/docs/adr/foundational/A-054-contrato-costura-driver-port-raw-data-canal-error.md +65 -0
- package/docs/adr/foundational/index.md +98 -0
- package/docs/adr/index.md +19 -0
- package/docs/adr/python/P-001-dependencias-por-feature-fastapi-depends.md +63 -0
- package/docs/adr/python/P-002-inyeccion-token-por-request-sin-persistirlo.md +35 -0
- package/docs/adr/python/P-003-sqlalchemy-async-connection-pooling.md +59 -0
- package/docs/adr/python/P-004-serializer-json-custom-modelos-pydantic.md +47 -0
- package/docs/adr/python/P-005-patrones-pitfalls-nicegui.md +148 -0
- package/docs/adr/python/P-006-convenciones-pytest.md +96 -0
- package/docs/adr/python/P-007-ci-python-alineacion-poetry-version-unica.md +55 -0
- package/docs/adr/python/P-008-empaquetado-infraestructura-compartida-por-tecnologia.md +38 -0
- package/docs/adr/python/index.md +14 -0
- package/docs/adr/typescript/T-001-ddd-artifact-validation-zod-primitives.md +136 -0
- package/docs/adr/typescript/T-002-test-documentation-jsdoc.md +133 -0
- package/docs/adr/typescript/T-003-readme-formatting-domain-artifacts.md +51 -0
- package/docs/adr/typescript/T-005-input-optionality-use-case-not-entity.md +130 -0
- package/docs/adr/typescript/T-006-small-enums-inlined-zod-regex.md +77 -0
- package/docs/adr/typescript/T-007-controller-input-zod-schema.md +79 -0
- package/docs/adr/typescript/T-008-expressive-use-case-pipe.md +207 -0
- package/docs/adr/typescript/T-009-mdx-brace-escaping.md +80 -0
- package/docs/adr/typescript/T-010-stack-tooling-typescript-dominio.md +53 -0
- package/docs/adr/typescript/T-011-ultra-reporter-herramienta-canonica-documentacion.md +52 -0
- package/docs/adr/typescript/T-012-npm-workspaces-estructura-monorepo-clean-architecture.md +54 -0
- package/docs/adr/typescript/T-013-definicion-eventos-versionados-ultra-ddd.md +45 -0
- package/docs/adr/typescript/T-014-publicacion-distribucion-librerias-npm-semver.md +53 -0
- package/docs/adr/typescript/T-015-method-placement-definition.md +35 -0
- package/docs/adr/typescript/T-016-contrato-settings-zod.md +34 -0
- package/docs/adr/typescript/T-017-characterization-tests-golden-files.md +39 -0
- package/docs/adr/typescript/T-018-procedencia-campos-frontmatter.md +55 -0
- package/docs/adr/typescript/T-019-ultra-generator-herramienta-canonica-generacion.md +35 -0
- package/docs/adr/typescript/T-020-ultra-settings-herramienta-canonica-exportacion-despliegue.md +36 -0
- package/docs/adr/typescript/T-021-pipe-flujo-datos-operadores-semantica-contexto-inmutable.md +105 -0
- package/docs/adr/typescript/T-022-construction-is-execution-single-boundary.md +112 -0
- package/docs/adr/typescript/T-023-registry-cli-declara-dependencias-reales.md +84 -0
- package/docs/adr/typescript/T-024-paquete-ultra-agent-manifiesto-pi-peers-cli.md +39 -0
- package/docs/adr/typescript/T-025-publicacion-npm-trusted-publishing-procedencia.md +59 -0
- package/docs/adr/typescript/index.md +30 -0
- package/eslint/index.mjs +721 -0
- package/package.json +94 -0
- package/pi/extensions/handoff/README.md +92 -0
- package/pi/extensions/handoff/handoff-prompt.txt +9 -0
- package/pi/extensions/handoff/index.ts +296 -0
- package/pi/extensions/handoff/package.json +12 -0
- package/pi/extensions/qa-guardrail/README.md +108 -0
- package/pi/extensions/qa-guardrail/adapters/cli.ts +100 -0
- package/pi/extensions/qa-guardrail/adapters/eslint.ts +90 -0
- package/pi/extensions/qa-guardrail/adapters/registry.ts +45 -0
- package/pi/extensions/qa-guardrail/baseline.ts +74 -0
- package/pi/extensions/qa-guardrail/cerco.ts +134 -0
- package/pi/extensions/qa-guardrail/check.ts +185 -0
- package/pi/extensions/qa-guardrail/config.ts +189 -0
- package/pi/extensions/qa-guardrail/delta.ts +52 -0
- package/pi/extensions/qa-guardrail/feedback.ts +26 -0
- package/pi/extensions/qa-guardrail/index.ts +311 -0
- package/pi/extensions/qa-guardrail/nstop.ts +82 -0
- package/pi/extensions/qa-guardrail/package.json +12 -0
- package/pi/extensions/qa-guardrail/selection.ts +98 -0
- package/pi/extensions/qa-guardrail/types.ts +64 -0
- package/scripts/copy-assets.sh +10 -0
- package/scripts/reconcile-tags.sh +94 -0
- package/scripts/release-beta.sh +64 -0
- package/scripts/release-build.sh +53 -0
- package/scripts/release.sh +51 -0
- package/scripts/verify-adr-consistency.py +217 -0
- package/skills/code-review/SKILL.md +66 -0
- package/skills/codebase-design/SKILL.md +46 -0
- package/skills/configure-repo/SKILL.md +33 -0
- package/skills/diagnose-bug/SKILL.md +63 -0
- package/skills/domain-modeling/SKILL.md +56 -0
- package/skills/grill-me/SKILL.md +34 -0
- package/skills/i-have-an-idea/SKILL.md +166 -0
- package/skills/implement/SKILL.md +22 -0
- package/skills/research/SKILL.md +17 -0
- package/skills/ship/SKILL.md +26 -0
- package/skills/spike/SKILL.md +27 -0
- package/skills/tdd/SKILL.md +68 -0
- package/skills/tdd/references/deep-modules.md +53 -0
- package/skills/tdd/references/interface-design.md +74 -0
- package/skills/tdd/references/mocking.md +80 -0
- package/skills/tdd/references/refactoring.md +49 -0
- package/skills/tdd/references/tests.md +184 -0
- package/skills/to-specs/SKILL.md +141 -0
- package/skills/to-tasks/SKILL.md +143 -0
- package/skills/triage/SKILL.md +73 -0
- package/skills/ultra-generator/SKILL.md +263 -0
- package/skills/ultra-generator/references/aggregate-root.md +149 -0
- package/skills/ultra-generator/references/domain-event.md +174 -0
- package/skills/ultra-generator/references/entity.md +133 -0
- package/skills/ultra-generator/references/service.md +92 -0
- package/skills/ultra-generator/references/value-object.md +135 -0
- package/skills/wayfinder/SKILL.md +201 -0
- package/skills/workflow/SKILL.md +31 -0
- package/skills/writing-for-agents/SKILL-MECHANICS.md +32 -0
- package/skills/writing-for-agents/SKILL.md +86 -0
- package/skills/writing-for-agents/references/adrs.md +19 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# T-002: Documentación de tests con JSDoc para ultra-reporter
|
|
2
|
+
|
|
3
|
+
**Estado**: Aceptado
|
|
4
|
+
|
|
5
|
+
**Fecha**: 2026-05-15
|
|
6
|
+
**originally**: imolko
|
|
7
|
+
|
|
8
|
+
## Contexto
|
|
9
|
+
|
|
10
|
+
[`ultra-reporter`](./T-011-ultra-reporter-herramienta-canonica-documentacion.md) escanea el código fuente y genera documentación Docusaurus para cada artefacto del dominio y cada feature (command/query). En la página de cada artefacto o feature, se muestran las reglas de dominio documentadas en `README.md` (formato canónico en [A-032](../foundational/A-032-domain-artifact-documentation-format.md)) y los casos de prueba extraídos de los archivos `*.spec.ts`.
|
|
11
|
+
|
|
12
|
+
Para que ultra-reporter pueda generar documentación útil para expertos del dominio, los tests deben seguir una convención consistente de anotaciones JSDoc.
|
|
13
|
+
|
|
14
|
+
## Decisión
|
|
15
|
+
|
|
16
|
+
Todo test **de comportamiento** — unitarios de artefactos de dominio y de casos de uso (features) — debe incluir anotaciones JSDoc con las etiquetas `@name` y `@scenario`:
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
/**
|
|
20
|
+
* Descripción larga del test que explica el comportamiento verificado.
|
|
21
|
+
* @name crear ProductId vacío
|
|
22
|
+
* @scenario Se intenta crear un ProductId con un string vacío ('').
|
|
23
|
+
* La validación de longitud mínima (3 caracteres) lo rechaza
|
|
24
|
+
* con un error TooSmallError.
|
|
25
|
+
*/
|
|
26
|
+
it('crear ProductId vacío', () => {
|
|
27
|
+
// ...
|
|
28
|
+
});
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Reglas
|
|
32
|
+
|
|
33
|
+
| Regla | Descripción |
|
|
34
|
+
|-------|-------------|
|
|
35
|
+
| `@name` coincide con `it()` | El texto de `@name` debe ser **idéntico** al primer argumento de `it()`. Esto asegura que el título en la documentación generada coincida con el nombre del test en el runner. |
|
|
36
|
+
| `@scenario` describe el qué y por qué | Explica qué entrada se usa, qué regla se ejerce, y qué resultado se espera. Debe ser comprensible para un experto del dominio sin conocimientos técnicos. |
|
|
37
|
+
| Sin "falla si…" en títulos | El título del test (`it()` y `@name`) describe **el escenario**, no el resultado. Ej: `'crear ProductId vacío'` en lugar de `'crear ProductId vacío – falla si está vacío'`. El resultado lo expresa `@scenario`. |
|
|
38
|
+
| Idioma del repo | Los nombres y descripciones van en el idioma del repo (`issue_language`), fijado por [A-003](../foundational/A-003-tdd.md): en Guarura, español. |
|
|
39
|
+
| Primera línea: descripción larga | La primera línea del JSDoc (antes de `@name`) contiene una descripción extendida del comportamiento verificado. |
|
|
40
|
+
|
|
41
|
+
> **Ámbito**: la convención aplica a tests que documentan reglas de dominio o comportamiento de casos de uso. Los tests de contrato (validación de esquemas y tipos, por ejemplo `*.contract.spec.ts`) quedan fuera: validan estructura, no reglas de dominio, y no se convierten en casos documentados para expertos del dominio.
|
|
42
|
+
|
|
43
|
+
### Ejemplo completo (value-object)
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
/**
|
|
47
|
+
* Verificar que un ProductId con más caracteres de lo permitido es rechazado
|
|
48
|
+
* @name crear ProductId muy largo
|
|
49
|
+
* @scenario El valor tiene 51 caracteres, uno por encima del máximo
|
|
50
|
+
* de 50. La validación de longitud máxima lo rechaza con
|
|
51
|
+
* TooBigError.
|
|
52
|
+
*/
|
|
53
|
+
it('crear ProductId muy largo', () => {
|
|
54
|
+
const payload: Payload = { value: 'A'.repeat(51) };
|
|
55
|
+
const result = ProductId.of(payload);
|
|
56
|
+
expect(result).toBeInvalid();
|
|
57
|
+
expect(result).toHaveErrorLike({
|
|
58
|
+
type: 'http://journal/v8n/too_big',
|
|
59
|
+
title: 'Too Big',
|
|
60
|
+
detail: 'Product ID must be at most 50 characters',
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Ejemplo completo (feature use-case)
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
/**
|
|
69
|
+
* Verificar que el caso de uso retorna InvalidWarehouseError cuando
|
|
70
|
+
* el warehouseId no corresponde a un almacén existente en el sistema.
|
|
71
|
+
* @name warehouse no encontrado retorna InvalidWarehouseError
|
|
72
|
+
* @scenario Se envía warehouseId=WH-NONEXISTENT sin registrar ningún
|
|
73
|
+
* warehouse con ese ID. El producto y variante asociados sí
|
|
74
|
+
* existen para aislar la validación del warehouse.
|
|
75
|
+
*/
|
|
76
|
+
it('warehouse no encontrado retorna InvalidWarehouseError', async () => {
|
|
77
|
+
// ...
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
El formato es idéntico: la única diferencia es que los tests de features validan
|
|
82
|
+
el comportamiento del pipeline del caso de uso (orquestación, manejo de errores,
|
|
83
|
+
persistencia) en lugar de reglas de un value-object individual.
|
|
84
|
+
|
|
85
|
+
### Lo que ultra-reporter extrae
|
|
86
|
+
|
|
87
|
+
En la página de documentación del artefacto o feature, ultra-reporter muestra para cada test:
|
|
88
|
+
|
|
89
|
+
| Campo | Fuente |
|
|
90
|
+
|-------|--------|
|
|
91
|
+
| Título del caso | `@name` |
|
|
92
|
+
| Descripción | Texto libre antes de `@name` |
|
|
93
|
+
| Escenario | `@scenario` |
|
|
94
|
+
|
|
95
|
+
Además de estas anotaciones, ultra-reporter lee el resultado de los tests en formato CTRF (ver [A-022](../foundational/A-022-formato-reporte-tests-ctrf.md)); el pipeline completo se describe en [T-011](./T-011-ultra-reporter-herramienta-canonica-documentacion.md).
|
|
96
|
+
|
|
97
|
+
### Co-located tests
|
|
98
|
+
|
|
99
|
+
Los archivos de test viven junto al archivo fuente que testean, usando el patrón `<subject>.spec.ts`:
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
src/domain/value-objects/product-id/
|
|
103
|
+
├── ProductId.ts
|
|
104
|
+
├── conditions.ts
|
|
105
|
+
├── index.ts
|
|
106
|
+
└── ProductId.spec.ts ← co-located test
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Reglas:**
|
|
110
|
+
|
|
111
|
+
| Regla | Descripción |
|
|
112
|
+
|-------|-------------|
|
|
113
|
+
| Mismo directorio | El `.spec.ts` reside en la misma carpeta que el sujeto de prueba |
|
|
114
|
+
| Nomenclatura | `<nombre-del-archivo>.spec.ts` — ej: `ProductId.ts` → `ProductId.spec.ts` |
|
|
115
|
+
| Sin árbol `__tests__/` separado | No se usan directorios `__tests__/` dedicados |
|
|
116
|
+
|
|
117
|
+
Esta organización facilita el descubrimiento: al abrir cualquier archivo fuente, sus tests son inmediatamente visibles. El `testMatch` de Jest/Vitest se fija en `**/*.spec.ts` — el default de Jest ya lo cubre, así que no requiere configuración adicional salvo que se quiera excluir explícitamente un árbol `__tests__/`.
|
|
118
|
+
|
|
119
|
+
## Consecuencias
|
|
120
|
+
|
|
121
|
+
### Positivas
|
|
122
|
+
|
|
123
|
+
- **Documentación generada automáticamente**: cada test se convierte en un caso documentado en el sitio Docusaurus
|
|
124
|
+
- **Legible por expertos del dominio**: las descripciones en el idioma del repo y el formato `@scenario` permiten que stakeholders no técnicos validen las reglas implementadas
|
|
125
|
+
- **Consistencia**: todas las páginas de artefactos siguen el mismo formato, facilitando la navegación
|
|
126
|
+
- **Doble propósito**: el mismo texto sirve como documentación del test (para desarrolladores) y como especificación (para el negocio)
|
|
127
|
+
- **Trazabilidad**: cada regla de dominio documentada en `README.md` tiene su test correspondiente
|
|
128
|
+
|
|
129
|
+
### Negativas
|
|
130
|
+
|
|
131
|
+
- **Disciplina**: requiere que todo el equipo siga la convención en cada test
|
|
132
|
+
- **Mantenimiento dual**: si cambia el `it()`, debe cambiarse también `@name`
|
|
133
|
+
- **No automatizable**: el `@scenario` requiere redacción humana con conocimiento del dominio
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# T-003: Reconciliación del scaffold de ultra-generator con el formato canónico
|
|
2
|
+
|
|
3
|
+
**Estado**: Aceptado
|
|
4
|
+
|
|
5
|
+
**Fecha**: 2026-05-15
|
|
6
|
+
|
|
7
|
+
**originally**: imolko
|
|
8
|
+
|
|
9
|
+
## Contexto
|
|
10
|
+
|
|
11
|
+
El scaffold que produce `ultra-generator` (herramienta canónica: [T-019](./T-019-ultra-generator-herramienta-canonica-generacion.md)) para cada artefacto de dominio no es el `README.md` final: trae títulos en inglés y una sección `Relaciones` en bullet points, además de un archivo `.instructions.md` con instrucciones para el agente o desarrollador. El formato canónico del `README.md` es una decisión global, no específica de TypeScript, y vive en [A-032](../foundational/A-032-domain-artifact-documentation-format.md).
|
|
12
|
+
|
|
13
|
+
Este ADR fija la mecánica específica del ecosistema TypeScript para reconciliar el scaffold con A-032: completar el `README.md` y eliminar el archivo de trabajo `.instructions.md`.
|
|
14
|
+
|
|
15
|
+
## Decisión
|
|
16
|
+
|
|
17
|
+
El scaffold de `ultra-generator` es un punto de partida, no el `README.md` final. El desarrollador o agente debe **reconciliarlo con el formato canónico** de A-032 y, solo entonces, eliminar el archivo de trabajo:
|
|
18
|
+
|
|
19
|
+
1. **Completar el scaffold**: traducir las secciones a los títulos en español definidos en A-032 (`## Purpose` → `## Propósito`, etc.) y reemplazar los bullet points de `Relaciones` por un diagrama Mermaid.
|
|
20
|
+
2. **Eliminar `.instructions.md`**: una vez completada la implementación del artefacto, eliminar `.instructions.md` para que no contamine la documentación generada.
|
|
21
|
+
|
|
22
|
+
**Antes de eliminar `.instructions.md`**, se debe verificar que el `README.md` está completo según A-032:
|
|
23
|
+
|
|
24
|
+
- El `README.md` no contiene texto placeholder (ej: `_Describe the business purpose..._`, `## Purpose`).
|
|
25
|
+
- Las secciones usan los títulos en español definidos en A-032.
|
|
26
|
+
- La sección `## Relaciones` contiene un diagrama Mermaid.
|
|
27
|
+
- Para features, las secciones dependen del `type` declarado en el frontmatter (`command` o `query`, según A-032).
|
|
28
|
+
|
|
29
|
+
Si el `README.md` no cumple estas condiciones, `.instructions.md` **no debe eliminarse** hasta que el README esté completo. Si se vuelve a ejecutar `ultra-generator` sobre el mismo artefacto, `.instructions.md` se regenera y debe eliminarse nuevamente.
|
|
30
|
+
|
|
31
|
+
### Verificación
|
|
32
|
+
|
|
33
|
+
| Check | Comando |
|
|
34
|
+
|-------|---------|
|
|
35
|
+
| Sin `.instructions.md` | `test ! -f RUTA/.instructions.md` |
|
|
36
|
+
| Sin título H1 | `! grep -q '^# ' RUTA/README.md` |
|
|
37
|
+
| Frontmatter presente | `head -1 RUTA/README.md \| grep -q '^---$'` |
|
|
38
|
+
| Mermaid en Relaciones | `grep -q mermaid RUTA/README.md` |
|
|
39
|
+
|
|
40
|
+
## Consecuencias
|
|
41
|
+
|
|
42
|
+
### Positivas
|
|
43
|
+
|
|
44
|
+
- **Documentación limpia**: sin checklist de desarrollo ni títulos en inglés en las páginas finales.
|
|
45
|
+
- **Un solo eje**: la reconciliación (completar + eliminar) queda separada del formato (A-032) y de la procedencia del frontmatter (T-018).
|
|
46
|
+
|
|
47
|
+
### Negativas
|
|
48
|
+
|
|
49
|
+
- **Divergencia con el generador**: el scaffold produce títulos en inglés; el desarrollador o agente debe traducirlos siguiendo A-032.
|
|
50
|
+
- **Regeneración de `.instructions.md`**: si se re-ejecuta `ultra-generator` sobre un artefacto existente, `.instructions.md` se regenera y debe eliminarse manualmente.
|
|
51
|
+
- **Sin validación automática**: no hay un linter que verifique el formato; la revisión es manual o delegada al agente.
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# T-005: Contratos de input reflejan la interfaz externa; el caso de uso resuelve la opcionalidad
|
|
2
|
+
|
|
3
|
+
**Estado**: Aceptado
|
|
4
|
+
|
|
5
|
+
**Fecha**: 2026-05-27
|
|
6
|
+
**originally**: imolko
|
|
7
|
+
|
|
8
|
+
## Contexto
|
|
9
|
+
|
|
10
|
+
Los casos de uso operan sobre contratos de input (`InputDto`) que provienen de la capa de infraestructura (CLI, GraphQL, REST, mensajería). Estos contratos definen la interfaz externa del sistema: qué datos debe proveer el caller para invocar una operación.
|
|
11
|
+
|
|
12
|
+
La interfaz externa no siempre coincide con la forma que el dominio necesita:
|
|
13
|
+
|
|
14
|
+
| Capa | Forma del dato | Ejemplo |
|
|
15
|
+
|------|---------------|--------|
|
|
16
|
+
| Interfaz externa (InputDto) | El caller puede no conocer el lote | `batch?: string` |
|
|
17
|
+
| Dominio (Props de entidad) | Toda línea de movimiento tiene un lote | `batchId: BatchId` (requerido) |
|
|
18
|
+
|
|
19
|
+
### No es una bolsa de campos opcionales
|
|
20
|
+
|
|
21
|
+
Este ADR **no** autoriza DTOs con docenas de campos opcionales por conveniencia. Un `InputDto` debe ser un contrato bien definido, con la mínima cantidad de campos necesarios para la operación. Cada campo opcional debe justificarse por una razón de interfaz, no por conveniencia:
|
|
22
|
+
|
|
23
|
+
| ✅ Campo opcional justificado | ❌ Campo opcional injustificado |
|
|
24
|
+
|------------------------------|-------------------------------|
|
|
25
|
+
| `batch?`: el caller puede no conocer el lote al registrar una entrada | `createdBy?`: "por si acaso no viene" — toda operación requiere un responsable |
|
|
26
|
+
| `expirationDate?`: override explícito del shelf life por defecto | `warehouseId?`: "a veces lo mandan, a veces no" — si es requerido, que falle temprano |
|
|
27
|
+
| `documents?`: documentos anexos son genuinamente opcionales | `items?`: "por si la lista viene vacía" — una entrada sin ítems no tiene sentido |
|
|
28
|
+
|
|
29
|
+
Si un DTO acumula más de 3-4 campos opcionales, considerar si la operación debería dividirse en múltiples casos de uso con contratos específicos.
|
|
30
|
+
|
|
31
|
+
### Ejemplo: U01 Registrar Entrada de Mercancía
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
// Contrato de input — campos opcionales justificados por la interfaz
|
|
35
|
+
{
|
|
36
|
+
warehouseId: string, // requerido: siempre se sabe a qué almacén entra
|
|
37
|
+
items: [{
|
|
38
|
+
productVariantId: string, // requerido: siempre se sabe qué producto es
|
|
39
|
+
quantity: number, // requerido: siempre se sabe cuánto entra
|
|
40
|
+
batch?: string, // opcional: el caller puede no conocer el lote
|
|
41
|
+
expirationDate?: string, // opcional: override de shelf life por defecto
|
|
42
|
+
}],
|
|
43
|
+
documents?: Array<{ // opcional: puede no haber documentos anexos
|
|
44
|
+
type: string;
|
|
45
|
+
number: string;
|
|
46
|
+
}>,
|
|
47
|
+
createdBy: string // requerido: toda operación tiene un responsable
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Los artefactos del dominio tienen invariantes que NO admiten opcionalidad por conveniencia: la opcionalidad del contrato de input se resuelve en el caso de uso antes de construir el artefacto.
|
|
52
|
+
|
|
53
|
+
- Un campo que el negocio exige siempre presente es **requerido**, aunque el caller no lo provea (ej: un lote auto-generado)
|
|
54
|
+
- Una colección es **siempre una colección**: requerida, vacía si no se adjuntaron elementos
|
|
55
|
+
- Un autor o responsable es **requerido** en toda operación
|
|
56
|
+
|
|
57
|
+
Se presentan dos opciones:
|
|
58
|
+
|
|
59
|
+
**Opción A**: Debilitar las invariantes del dominio para que admitan `null`, reflejando directamente el contrato de input. Por ejemplo, hacer `batchId: BatchId | null` en `StockMovementLine`.
|
|
60
|
+
|
|
61
|
+
**Opción B**: Mantener las invariantes del dominio como requeridas y resolver la opcionalidad en el caso de uso antes de construir los artefactos.
|
|
62
|
+
|
|
63
|
+
## Decisión
|
|
64
|
+
|
|
65
|
+
**Opción B**: El caso de uso resuelve la opcionalidad del contrato externo (InputDto, que refleja la interfaz) antes de construir el modelo de dominio (invariantes estrictas). La traducción de primitivas a tipos de dominio ocurre antes, en el controlador ([T-007](./T-007-controller-input-zod-schema.md)); esta resolución es parte natural de la capa de features que orquesta el dominio ([A-001](../foundational/A-001-clean-architecture-4-capas.md)).
|
|
66
|
+
|
|
67
|
+
### Reglas
|
|
68
|
+
|
|
69
|
+
1. **Los artefactos de dominio no admiten campos opcionales por conveniencia del input**. Si el dominio dice "toda línea de movimiento tiene un lote", el artefacto refleja esa invariante con `batchId: BatchId` (requerido). Esto aplica a value-objects, entities y aggregates.
|
|
70
|
+
|
|
71
|
+
2. **El InputDto debe ser un contrato bien definido**, no una bolsa de campos opcionales. Cada campo (requerido u opcional) debe justificarse por la interfaz externa. Ante la duda, hacer el campo requerido y dejar que el validador falle temprano.
|
|
72
|
+
|
|
73
|
+
3. **El caso de uso es responsable de resolver la opcionalidad** antes de invocar constructores del dominio:
|
|
74
|
+
- Campos opcionales con valor por defecto → el caso de uso aplica el default (ej: `documents` → `[]`)
|
|
75
|
+
- Campos opcionales que requieren inferencia → el caso de uso ejecuta la lógica de inferencia (ej: `batch?` → auto-crear `Batch` con `productVariant.defaultShelfLifeDays`)
|
|
76
|
+
- Campos opcionales que son overrides → el caso de uso resuelve la precedencia (ej: `expirationDate?` sin `batch?` → usar `expirationDate` en lugar de `defaultShelfLifeDays`; con `batch?` → ignorar)
|
|
77
|
+
|
|
78
|
+
4. **El dominio nunca recibe `null` o `undefined`** en campos que el dominio considera invariantes. El caso de uso resuelve la opcionalidad: interfaz externa (flexible) → dominio (estricto).
|
|
79
|
+
|
|
80
|
+
La validación de forma (longitud, formato, tipo) usa las primitivas Zod de [T-001](./T-001-ddd-artifact-validation-zod-primitives.md); la opcionalidad (presencia o ausencia) se refleja en el contrato de input y se resuelve en el caso de uso.
|
|
81
|
+
|
|
82
|
+
### Ejemplo
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
// ❌ INCORRECTO: debilitar el dominio para acomodar el input
|
|
86
|
+
interface StockMovementLineProps {
|
|
87
|
+
batchId: BatchId | null; // ← NO: toda línea tiene lote
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
// ✅ CORRECTO: resolver en el caso de uso
|
|
93
|
+
async execute(input: RegisterInboundCommand): Promise<Validation<Errors, void>> {
|
|
94
|
+
// Resolver opcionalidad antes de tocar el dominio
|
|
95
|
+
const documents = input.documents ?? [];
|
|
96
|
+
|
|
97
|
+
const lines = await Promise.all(input.items.map(async (item) => {
|
|
98
|
+
const batch = item.batch ?? await this.createAutoBatch(item);
|
|
99
|
+
return StockMovementLine.create({ batchId: batch.id, ... });
|
|
100
|
+
}));
|
|
101
|
+
|
|
102
|
+
const movement = StockMovement.create({ lines, documents, ... });
|
|
103
|
+
// ...
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Cuándo SÍ usar opcionalidad en el dominio
|
|
108
|
+
|
|
109
|
+
La opcionalidad en artefactos de dominio solo se justifica cuando el **dominio mismo** la requiere:
|
|
110
|
+
|
|
111
|
+
- `StockMovement.originWarehouseId: WarehouseId | null` — en una entrada (Inbound), el origen es externo, no un almacén del sistema. El dominio define esta opcionalidad, no el input.
|
|
112
|
+
- `StockMovement.destinationWarehouseId: WarehouseId | null` — en una salida (Outbound), el destino es externo.
|
|
113
|
+
|
|
114
|
+
En estos casos, la opcionalidad es una regla de negocio, no una consecuencia de la forma del input.
|
|
115
|
+
|
|
116
|
+
## Consecuencias
|
|
117
|
+
|
|
118
|
+
### Positivas
|
|
119
|
+
|
|
120
|
+
- **Invariantes fuertes**: los artefactos del dominio reflejan fielmente las reglas del negocio, sin concesiones por la forma del input
|
|
121
|
+
- **Opcionalidad resuelta en el caso de uso**: el caso de uso tiene una responsabilidad clara y acotada: resolver la opcionalidad del contrato externo antes de construir el dominio
|
|
122
|
+
- **Reutilización**: si otro caso de uso construye `StockMovementLine` con reglas diferentes, el artefacto no necesita cambiar — cada caso de uso aplica su propia lógica de resolución
|
|
123
|
+
- **Testabilidad**: la lógica de resolución (auto-creación de lotes, defaults) se prueba en el caso de uso, y las invariantes del dominio se prueban de forma aislada
|
|
124
|
+
- **Contratos explícitos**: cada InputDto documenta exactamente qué espera la interfaz externa, sin campos sorpresa que "a veces se usan"
|
|
125
|
+
|
|
126
|
+
### Negativas
|
|
127
|
+
|
|
128
|
+
- **Duplicación potencial**: si múltiples casos de uso necesitan la misma lógica de resolución (ej: auto-creación de lotes), ésta podría duplicarse. Se mitiga extrayendo un servicio de dominio (ej: `BatchFactory`) cuando el patrón se repita 3+ veces
|
|
129
|
+
- **Más código en el caso de uso**: la resolución de opcionalidad agrega lógica al caso de uso que de otra forma estaría implícita en validadores de schema. Es un tradeoff deliberado: código explícito en el caso de uso vs invariantes implícitas en el schema
|
|
130
|
+
- **Disciplina en el DTO**: requiere que el desarrollador justifique cada campo opcional. Sin esta disciplina, el DTO degenera en una bolsa de campos opcionales
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# T-006: Enums pequeños y estables se inlinean con Zod regex
|
|
2
|
+
|
|
3
|
+
**Estado**: Aceptado
|
|
4
|
+
|
|
5
|
+
**Fecha**: 2026-05-27
|
|
6
|
+
**originally**: imolko
|
|
7
|
+
|
|
8
|
+
## Contexto
|
|
9
|
+
|
|
10
|
+
Un dominio modelado con DDD contiene múltiples tipos enumerados que modelan clasificaciones del negocio (tipos de movimiento, estados, estados de reserva, etc.). Extraer cada uno a un value-object separado exige un sub-árbol completo de artefactos — definición, condiciones, errores, tests, documentación y el propio enum — con independencia de si el código se escribe a mano, lo genera un agente o lo produce un scaffold. Ese coste no aporta valor cuando el enum es pequeño y estable — por ejemplo, un `type` con cuatro valores posibles que solo clasifica, sin comportamiento, relaciones ni ciclo de vida propios.
|
|
11
|
+
|
|
12
|
+
## Decisión
|
|
13
|
+
|
|
14
|
+
Cuando un tipo enumerado cumple **todas** las siguientes condiciones, se representa como un campo `string` con validación `z.string().regex()` inline (la primitiva Zod de [T-001](./T-001-ddd-artifact-validation-zod-primitives.md)), en lugar de extraerlo a un value-object separado:
|
|
15
|
+
|
|
16
|
+
### Criterios para inlinear
|
|
17
|
+
|
|
18
|
+
| Criterio | Justificación |
|
|
19
|
+
|----------|---------------|
|
|
20
|
+
| ≤5 valores estables | Pocos valores = baja probabilidad de cambio. Si el enum crece más allá de 5, considerar extraerlo. |
|
|
21
|
+
| Sin comportamiento propio | El tipo solo se usa para clasificar, no tiene métodos como `isExpired()`, `canTransitionTo()`, etc. |
|
|
22
|
+
| Sin ciclo de vida independiente | No se persiste, consulta, o referencia de forma autónoma. Solo existe como propiedad de otro artefacto. |
|
|
23
|
+
| Sin referencias externas | Ningún otro artefacto lo importa como tipo independiente. Si múltiples artefactos lo referencian, merece su propio archivo. |
|
|
24
|
+
|
|
25
|
+
### Criterios para extraer a value-object separado
|
|
26
|
+
|
|
27
|
+
| Criterio | Justificación |
|
|
28
|
+
|----------|---------------|
|
|
29
|
+
| ≥6 valores o historial de crecimiento | Más valores = más probabilidad de cambio. Un VO separado centraliza las modificaciones. |
|
|
30
|
+
| Comportamiento propio | Métodos como `isTerminal()`, `canTransitionTo()`, `requiresConfirmation()` justifican el VO. |
|
|
31
|
+
| Referenciado por múltiples artefactos | Si 3+ artefactos importan el tipo, extraerlo evita duplicación de constantes y validación. |
|
|
32
|
+
| Usado en proyecciones o read models | Si el enum aparece en consultas, eventos, o representaciones externas, tener un tipo canónico previene divergencia. |
|
|
33
|
+
|
|
34
|
+
### Ejemplo: DocumentRef.type (inlineado)
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
// domain/src/document-ref/conditions.ts
|
|
38
|
+
export const conditions = z.object({
|
|
39
|
+
type: z
|
|
40
|
+
.string()
|
|
41
|
+
.regex(
|
|
42
|
+
/^(SADA_GUIDE|INVOICE|DELIVERY_ORDER|OTHER)$/,
|
|
43
|
+
'Document type must be one of: SADA_GUIDE, INVOICE, DELIVERY_ORDER, OTHER',
|
|
44
|
+
),
|
|
45
|
+
reference: z.string().min(1).max(100),
|
|
46
|
+
issuer: z.string().min(1).max(200),
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Ejemplo: StockMovementType (extraído — CORRECTO)
|
|
51
|
+
|
|
52
|
+
`StockMovementType` fue extraído como value-object separado porque:
|
|
53
|
+
|
|
54
|
+
- Supera el umbral de 5 valores
|
|
55
|
+
- Es referenciado por `StockMovement`, `StockMovementLine`, y eventos como `StockMovementCreated`
|
|
56
|
+
- Aparece en el snapshot del agregado (persistencia vía Event Sourcing)
|
|
57
|
+
- Podría necesitar comportamiento en el futuro (ej: `requiresDestinationWarehouse(): boolean`)
|
|
58
|
+
|
|
59
|
+
### Cómo evaluar un enum existente
|
|
60
|
+
|
|
61
|
+
Para determinar si un enum existente debe extraerse o inlinearse, aplicar los criterios definidos en las tablas anteriores. Inlinear es aceptable cuando el dominio no otorga identidad propia al concepto: el enum es utilidad de validación sin lógica de dominio, del tipo que [A-008](../foundational/A-008-bounded-context-decomposition.md) agrupa en el contexto compartido. No mantener una lista hardcodeada de enums en este ADR — los criterios son la fuente canónica.
|
|
62
|
+
|
|
63
|
+
## Consecuencias
|
|
64
|
+
|
|
65
|
+
### Positivas
|
|
66
|
+
|
|
67
|
+
- **Menos boilerplate**: un campo inlineado ocupa ~4 líneas (la línea `.regex()`) frente a un sub-árbol completo de archivos para un VO separado
|
|
68
|
+
- **Navegabilidad**: el tipo y sus valores válidos son visibles en el mismo archivo de condiciones, sin tener que abrir otro directorio
|
|
69
|
+
- **Sin decisión prematura**: si un enum inlineado crece más allá de 5 valores, se puede extraer a VO sin romper la API pública (el cambio es interno a `conditions.ts`)
|
|
70
|
+
- **Fricción reducida**: modelar un campo clasificatorio no requiere generar un sub-árbol completo de archivos
|
|
71
|
+
|
|
72
|
+
### Negativas
|
|
73
|
+
|
|
74
|
+
- **Duplicación potencial**: si dos artefactos inlinean el mismo enum (ej: `SADA_GUIDE|INVOICE|...`), la regex y el mensaje de error se duplican. Se mitiga extrayendo a VO cuando ocurra la primera duplicación.
|
|
75
|
+
- **Sin tipo TypeScript**: `z.string().regex()` produce `string` en el tipo inferido, no un union type `'SADA_GUIDE' | 'INVOICE' | ...`. El type narrowing en el caso de uso requiere casts o type guards manuales.
|
|
76
|
+
- **Inconsistencia visual**: algunos enums son VOs completos, otros son strings con regex. El criterio de 5 valores es un umbral arbitrario que podría generar debate.
|
|
77
|
+
- **Descubribilidad**: los valores válidos de un enum inlineado solo son visibles leyendo el archivo de condiciones. Un VO separado con `enum.ts` es más fácil de descubrir con `grep` o navegación de IDE.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# T-007: Todo controlador de feature expone un esquema Zod para su InputDto
|
|
2
|
+
|
|
3
|
+
**Estado**: Aceptado
|
|
4
|
+
|
|
5
|
+
**Fecha**: 2026-05-28
|
|
6
|
+
**originally**: imolko
|
|
7
|
+
|
|
8
|
+
## Contexto
|
|
9
|
+
|
|
10
|
+
Cada caso de uso define un contrato de entrada (`InputDto`) que especifica los campos que recibe del exterior. Este contrato es una interfaz TypeScript — desaparece en tiempo de ejecución. Sin embargo, múltiples consumidores necesitan validar y/o inspeccionar este contrato en runtime:
|
|
11
|
+
|
|
12
|
+
- Un controlador de transporte (HTTP, CLI, mensajería) necesita validar el input externo antes de pasarlo al caso de uso
|
|
13
|
+
- Un consumidor puede introspectar los campos del contrato en runtime para generar prompts o formularios dinámicos
|
|
14
|
+
- Cualquier aplicación que exponga los casos de uso (REST, GraphQL, CLI, tests) necesita conocer la forma exacta del input
|
|
15
|
+
|
|
16
|
+
Sin un esquema runtime, cada consumidor debe duplicar la definición del contrato, con el riesgo de desincronización.
|
|
17
|
+
|
|
18
|
+
## Decisión
|
|
19
|
+
|
|
20
|
+
**Todo controlador de feature debe exportar un esquema Zod para el InputDto.** El controlador vive en `adapters/controller/` dentro de la capa `features/` ([A-001](../foundational/A-001-clean-architecture-4-capas.md), [A-025](../foundational/A-025-anatomia-mecanica-feature-puertos-adaptadores-drivers.md)).
|
|
21
|
+
|
|
22
|
+
El archivo `adapters/controller/input.dto.ts` es la ubicación canónica. Esto es intencional:
|
|
23
|
+
|
|
24
|
+
- El controlador recibe representaciones externas (JSON, strings, números) y las valida con Zod.
|
|
25
|
+
- Luego **traduce** esas representaciones a conceptos del dominio (`WarehouseId`, `Quantity`, etc.) antes de invocar el caso de uso.
|
|
26
|
+
- El caso de uso recibe un contrato con tipos de dominio, no primitivas. La traducción de primitivas a tipos de dominio es responsabilidad del controlador.
|
|
27
|
+
|
|
28
|
+
El esquema y el tipo inferido (`z.infer`) viven en el controlador:
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import * as z from 'zod';
|
|
32
|
+
|
|
33
|
+
export const InputSchema = z.object({
|
|
34
|
+
// campos requeridos con validaciones mínimas
|
|
35
|
+
campo: z.string().min(1, 'campo es requerido'),
|
|
36
|
+
// campos opcionales
|
|
37
|
+
opcional: z.string().optional(),
|
|
38
|
+
// arreglos anidados
|
|
39
|
+
items: z.array(z.object({
|
|
40
|
+
subcampo: z.string().min(1, 'subcampo es requerido'),
|
|
41
|
+
})).min(1, 'items debe tener al menos un elemento'),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
export type InputDto = z.infer<typeof InputSchema>;
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Reglas
|
|
48
|
+
|
|
49
|
+
1. **El esquema Zod vive en el controlador.** El archivo `adapters/controller/input.dto.ts` contiene el `InputSchema` con tipos primitivos (`z.string()`, `z.number()`). El tipo `InputDto` se infiere con `z.infer<typeof InputSchema>`. No se permite definir el tipo manualmente — el esquema es la fuente única de verdad.
|
|
50
|
+
|
|
51
|
+
2. **El controlador traduce primitivas → dominio.** El `adapter.ts` usa el `InputSchema` para validar, luego mapea `string` → `WarehouseId`, `number` → `Quantity`, etc. Al construir el tipo de dominio se aplican sus invariantes ([A-007](../foundational/A-007-data-contract-fail-fast.md)): un valor externo inválido se rechaza en la frontera y nunca llega al caso de uso. El caso de uso recibe **tipos de dominio**: value objects donde el dominio los modela, y primitivos tipados por el dominio donde el dominio modela el concepto como primitivo ([A-050](../foundational/A-050-el-caso-de-uso-pide-al-dominio-no-lee-su-representacion.md)); la resolución de opcionalidad y defaults sigue en el caso de uso ([T-005](./T-005-input-optionality-use-case-not-entity.md)).
|
|
52
|
+
|
|
53
|
+
El controlador **retorna `TaskExecution`** (lazy, [A-044](../foundational/A-044-composicion-funcional-procesos-operaciones.md)) y compone la invocación del caso de uso con `TE.chain`/`TE.map`; la ejecución ocurre cuando el transporte invoca la tarea. Al construir los value objects del input con `Xxx.of(...)`, un VO que rechaza el valor **propaga su error por el canal** (`TE.fromError`/`E.fromValidation`), nunca lanza: `Xxx.of(...).getValue()` sobre un `Validation` inválido lanza una excepción, así que la construcción se verifica (`isValid()`/`isInvalid()`) o se compone — no se asume válida.
|
|
54
|
+
|
|
55
|
+
3. **Validaciones mínimas; las invariantes viven en el artefacto del dominio.** El esquema Zod del controlador valida solo la forma de la representación externa (tipos, presencia, arrays no vacíos) y no duplica las reglas del concepto. Las invariantes — formato, longitud, charset — viven en el `rules()` del artefacto del dominio ([T-001](./T-001-ddd-artifact-validation-zod-primitives.md)). Ejemplo: `.min(1)` descarta el string vacío, `WarehouseId.rules()` descarta un `warehouseId` mal formado, y que exista el almacén lo resuelve el caso de uso.
|
|
56
|
+
|
|
57
|
+
4. **Campos opcionales usan `.optional()`.** Siguiendo [T-005](./T-005-input-optionality-use-case-not-entity.md), la opcionalidad del input se refleja en Zod con `.optional()`. El caso de uso es responsable de resolver defaults e inferencias.
|
|
58
|
+
|
|
59
|
+
5. **Esquemas anidados para objetos compuestos.** Si el InputDto contiene arrays de objetos o discriminated unions, se definen esquemas intermedios (`ItemSchema`, `DocumentSchema`) para mantener legibilidad.
|
|
60
|
+
|
|
61
|
+
6. **Mensajes de error en español.** Los mensajes de las validaciones Zod van en español, consistentes con el resto de la base de código.
|
|
62
|
+
|
|
63
|
+
### InputDto vacío o trivial
|
|
64
|
+
|
|
65
|
+
- El InputDto es vacío (`{}`) o solo tiene un campo trivial — en este caso el esquema es `z.object({})` y el tipo es `{}`. Sigue siendo válido exportarlo para mantener la convención.
|
|
66
|
+
|
|
67
|
+
## Consecuencias
|
|
68
|
+
|
|
69
|
+
### Positivas
|
|
70
|
+
|
|
71
|
+
- **Fuente única de verdad**: el esquema Zod define tanto la validación runtime como el tipo TypeScript. No hay duplicación.
|
|
72
|
+
- **Consumidores homogéneos**: el CLI, NestJS, tests y cualquier otro consumidor importan el mismo esquema.
|
|
73
|
+
- **Introspección**: `InputSchema.shape` permite que el CLI genere prompts dinámicos sin conocer los casos de uso de antemano.
|
|
74
|
+
- **Documentación viva**: el esquema Zod es más legible que una interfaz TypeScript para describir la forma del input.
|
|
75
|
+
|
|
76
|
+
### Negativas
|
|
77
|
+
|
|
78
|
+
- **Dependencia de Zod**: los controladores de feature ahora dependen de `zod`. Zod ya es dependencia del proyecto ([T-001](./T-001-ddd-artifact-validation-zod-primitives.md)), por lo que no introduce un nuevo paquete.
|
|
79
|
+
- **Posible divergencia**: si el esquema Zod y el `InputDto` manual se definen por separado, pueden divergir. Se mitiga usando `z.infer` como fuente del tipo.
|