@archi-code/ddd 3.0.0-alpha.1 → 3.0.0-alpha.2

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 (282) hide show
  1. package/README.md +478 -376
  2. package/SKILL.md +10 -8
  3. package/ddd-skills/archi-code-ddd/imports.md +28 -18
  4. package/ddd-skills/archi-code-ddd/layers.md +68 -53
  5. package/ddd-skills/archi-code-ddd/patterns.md +50 -43
  6. package/ddd-skills/archi-code-ddd/what-not-to-do.md +25 -18
  7. package/ddd-skills/archi-code-ddd/where-each-class-goes.md +11 -10
  8. package/ddd-skills/ddd-agregados/GUIA.md +2 -2
  9. package/ddd-skills/ddd-agregados/references/consistencia-eventual.md +5 -6
  10. package/ddd-skills/ddd-agregados/references/fabricas.md +5 -8
  11. package/ddd-skills/ddd-agregados/references/implementacion-ts.md +22 -27
  12. package/ddd-skills/ddd-arquitectura/GUIA.md +4 -4
  13. package/ddd-skills/ddd-arquitectura/assets/dependency-cruiser.cjs +13 -6
  14. package/ddd-skills/ddd-arquitectura/references/estilos-adicionales.md +2 -2
  15. package/ddd-skills/ddd-arquitectura/references/estructura-hexagonal-ts.md +70 -16
  16. package/ddd-skills/ddd-auditoria-refactorizacion/GUIA.md +1 -1
  17. package/ddd-skills/ddd-auditoria-refactorizacion/references/ejemplo-auditoria.md +9 -11
  18. package/ddd-skills/ddd-auditoria-refactorizacion/references/lista-comprobacion.md +4 -4
  19. package/ddd-skills/ddd-auditoria-refactorizacion/references/plan-migracion.md +2 -2
  20. package/ddd-skills/ddd-context-mapping/GUIA.md +5 -5
  21. package/ddd-skills/ddd-context-mapping/references/anticorrupcion-ts.md +33 -18
  22. package/ddd-skills/ddd-context-mapping/references/estilos-integracion.md +4 -7
  23. package/ddd-skills/ddd-event-sourcing-cqrs/GUIA.md +2 -2
  24. package/ddd-skills/ddd-event-sourcing-cqrs/references/event-sourcing-ts.md +32 -27
  25. package/ddd-skills/ddd-event-sourcing-cqrs/references/proyecciones-cqrs-ts.md +7 -7
  26. package/ddd-skills/ddd-eventos-dominio/GUIA.md +3 -3
  27. package/ddd-skills/ddd-eventos-dominio/references/diseno-eventos.md +15 -16
  28. package/ddd-skills/ddd-eventos-dominio/references/ejemplo-ecommerce.md +11 -11
  29. package/ddd-skills/ddd-eventos-dominio/references/publicacion-ts.md +34 -31
  30. package/ddd-skills/ddd-repositorios-persistencia/GUIA.md +7 -7
  31. package/ddd-skills/ddd-repositorios-persistencia/references/implementacion-ts.md +50 -32
  32. package/ddd-skills/ddd-repositorios-persistencia/references/tests-repositorios.md +31 -17
  33. package/ddd-skills/ddd-repositorios-persistencia/references/transacciones-ts.md +21 -14
  34. package/ddd-skills/ddd-servicios-capa-aplicacion/GUIA.md +2 -2
  35. package/ddd-skills/ddd-servicios-capa-aplicacion/references/capa-aplicacion-ts.md +19 -19
  36. package/ddd-skills/ddd-servicios-capa-aplicacion/references/ejemplo-ecommerce.md +12 -15
  37. package/ddd-skills/ddd-servicios-capa-aplicacion/references/servicios-dominio-ts.md +5 -6
  38. package/ddd-skills/ddd-value-objects-entidades/GUIA.md +3 -3
  39. package/ddd-skills/ddd-value-objects-entidades/references/entidades-ts.md +7 -9
  40. package/ddd-skills/ddd-value-objects-entidades/references/persistencia-valores.md +5 -5
  41. package/ddd-skills/ddd-value-objects-entidades/references/validacion.md +9 -9
  42. package/ddd-skills/ddd-value-objects-entidades/references/value-objects-ts.md +2 -3
  43. package/dist/cjs/application/event/listens-to.js +4 -4
  44. package/dist/cjs/{exceptions/application → application/exceptions}/aggregate-not-found.exception.d.ts +1 -1
  45. package/dist/cjs/{exceptions/application → application/exceptions}/aggregate-not-found.exception.js +4 -4
  46. package/dist/cjs/application/exceptions/application-exception-code.d.ts +7 -0
  47. package/dist/cjs/application/exceptions/application-exception-code.js +11 -0
  48. package/dist/cjs/application/exceptions/application.exception.d.ts +6 -0
  49. package/dist/cjs/application/exceptions/application.exception.js +16 -0
  50. package/dist/cjs/{exceptions/application → application/exceptions}/concurrency.exception.d.ts +1 -1
  51. package/dist/cjs/{exceptions/application → application/exceptions}/concurrency.exception.js +4 -4
  52. package/dist/cjs/{exceptions/application → application/exceptions}/not-allowed.exception.d.ts +1 -1
  53. package/dist/cjs/{exceptions/application → application/exceptions}/not-allowed.exception.js +4 -4
  54. package/dist/cjs/application/exceptions/unexpected-error.exception.d.ts +5 -0
  55. package/dist/cjs/application/exceptions/unexpected-error.exception.js +17 -0
  56. package/dist/cjs/application/index.d.ts +7 -0
  57. package/dist/cjs/application/index.js +15 -1
  58. package/dist/cjs/application/use-case/use-case.js +3 -2
  59. package/dist/{esm/domain/type/value-objects/value-objects.d.ts → cjs/application/value-object-builder/value-object-builder.d.ts} +3 -3
  60. package/dist/cjs/application/value-object-builder/value-object-builder.js +51 -0
  61. package/dist/cjs/domain/aggregate/aggregate-root.d.ts +0 -5
  62. package/dist/cjs/domain/aggregate/aggregate-root.js +4 -20
  63. package/dist/cjs/domain/entity/entity.js +2 -2
  64. package/dist/cjs/domain/event/event-base.d.ts +3 -9
  65. package/dist/cjs/domain/event/event-base.js +8 -38
  66. package/dist/cjs/domain/exceptions/domain-exception-code.d.ts +7 -0
  67. package/dist/cjs/domain/exceptions/domain-exception-code.js +11 -0
  68. package/dist/cjs/domain/exceptions/domain.exception.d.ts +6 -0
  69. package/dist/cjs/domain/exceptions/domain.exception.js +16 -0
  70. package/dist/cjs/domain/exceptions/invalid-event-definition.exception.d.ts +4 -0
  71. package/dist/cjs/domain/exceptions/invalid-event-definition.exception.js +19 -0
  72. package/dist/cjs/{exceptions/domain → domain/exceptions}/required-value.exception.js +5 -6
  73. package/dist/cjs/{exceptions/domain → domain/exceptions}/type-primitive.exception.js +5 -6
  74. package/dist/cjs/{exceptions/domain → domain/exceptions}/validation.exception.js +5 -5
  75. package/dist/cjs/domain/index.d.ts +8 -1
  76. package/dist/cjs/domain/index.js +14 -2
  77. package/dist/cjs/domain/primitive/primitive-types.d.ts +1 -3
  78. package/dist/cjs/domain/type/abstract-type.d.ts +6 -8
  79. package/dist/cjs/domain/type/abstract-type.js +15 -23
  80. package/dist/cjs/domain/type/array-type.d.ts +2 -1
  81. package/dist/cjs/domain/type/array-type.js +17 -18
  82. package/dist/cjs/domain/type/boolean-type.js +2 -2
  83. package/dist/cjs/domain/type/composite/composite-brand.d.ts +4 -0
  84. package/dist/cjs/domain/type/composite/composite-brand.js +15 -0
  85. package/dist/cjs/domain/type/composite/composite-field.d.ts +7 -8
  86. package/dist/cjs/domain/type/composite/composite-field.js +70 -47
  87. package/dist/cjs/domain/type/composite/composite-issues.d.ts +6 -0
  88. package/dist/cjs/domain/type/composite/composite-issues.js +35 -0
  89. package/dist/cjs/domain/type/composite/composite.js +15 -19
  90. package/dist/cjs/domain/type/date-type.d.ts +3 -2
  91. package/dist/cjs/domain/type/date-type.js +42 -7
  92. package/dist/cjs/domain/type/enum-type.js +2 -2
  93. package/dist/cjs/domain/type/id-type.d.ts +1 -1
  94. package/dist/cjs/domain/type/id-type.js +4 -4
  95. package/dist/cjs/domain/type/index.d.ts +2 -4
  96. package/dist/cjs/domain/type/index.js +3 -7
  97. package/dist/cjs/domain/type/json-type.d.ts +4 -0
  98. package/dist/cjs/domain/type/json-type.js +33 -4
  99. package/dist/cjs/domain/type/number-type.js +2 -2
  100. package/dist/cjs/domain/type/required/required-mark.d.ts +0 -1
  101. package/dist/cjs/domain/type/string-type.js +2 -2
  102. package/dist/cjs/domain/type/uuid-type.js +2 -2
  103. package/dist/cjs/infrastructure/event/domain-event-bus.js +9 -9
  104. package/dist/cjs/infrastructure/event/event-registry.js +9 -9
  105. package/dist/cjs/infrastructure/event/in-memory/emitter.js +2 -2
  106. package/dist/cjs/infrastructure/event/in-memory/in-memory-event-channel.d.ts +1 -1
  107. package/dist/cjs/infrastructure/event/in-memory/in-memory-event-channel.js +6 -7
  108. package/dist/cjs/{exceptions/infrastructure → infrastructure/exceptions}/event-delivery.exception.js +4 -4
  109. package/dist/cjs/infrastructure/exceptions/infrastructure-exception-code.d.ts +6 -0
  110. package/dist/cjs/infrastructure/exceptions/infrastructure-exception-code.js +10 -0
  111. package/dist/cjs/infrastructure/exceptions/infrastructure.exception.d.ts +6 -0
  112. package/dist/cjs/infrastructure/exceptions/infrastructure.exception.js +16 -0
  113. package/dist/cjs/{exceptions/infrastructure → infrastructure/exceptions}/internal-error.exception.d.ts +1 -1
  114. package/dist/cjs/{exceptions/infrastructure → infrastructure/exceptions}/internal-error.exception.js +4 -4
  115. package/dist/cjs/infrastructure/exceptions/kind/exception-classifier.d.ts +4 -0
  116. package/dist/cjs/infrastructure/exceptions/kind/exception-classifier.js +34 -0
  117. package/dist/cjs/infrastructure/exceptions/kind/exception-kind.d.ts +9 -0
  118. package/dist/cjs/infrastructure/exceptions/kind/exception-kind.js +13 -0
  119. package/dist/cjs/{exceptions/infrastructure → infrastructure/exceptions}/unknown-event.exception.js +4 -4
  120. package/dist/cjs/infrastructure/index.d.ts +7 -2
  121. package/dist/cjs/infrastructure/index.js +15 -3
  122. package/dist/cjs/infrastructure/logging/console-logger.js +2 -2
  123. package/dist/{esm → cjs/shared}/exceptions/abstract.exception.d.ts +6 -4
  124. package/dist/cjs/{exceptions → shared/exceptions}/abstract.exception.js +9 -16
  125. package/dist/cjs/shared/index.d.ts +8 -0
  126. package/dist/cjs/shared/index.js +13 -0
  127. package/dist/cjs/shared/issue-collection-error.d.ts +9 -0
  128. package/dist/cjs/shared/issue-collection-error.js +11 -0
  129. package/dist/cjs/shared/issue-collector.d.ts +8 -0
  130. package/dist/cjs/shared/issue-collector.js +41 -0
  131. package/dist/esm/application/event/listens-to.js +1 -1
  132. package/dist/esm/{exceptions/application → application/exceptions}/aggregate-not-found.exception.d.ts +1 -1
  133. package/dist/esm/application/exceptions/aggregate-not-found.exception.js +13 -0
  134. package/dist/esm/application/exceptions/application-exception-code.d.ts +7 -0
  135. package/dist/esm/application/exceptions/application-exception-code.js +8 -0
  136. package/dist/esm/application/exceptions/application.exception.d.ts +6 -0
  137. package/dist/esm/application/exceptions/application.exception.js +12 -0
  138. package/dist/esm/{exceptions/application → application/exceptions}/concurrency.exception.d.ts +1 -1
  139. package/dist/esm/{exceptions/application → application/exceptions}/concurrency.exception.js +4 -4
  140. package/dist/esm/{exceptions/application → application/exceptions}/not-allowed.exception.d.ts +1 -1
  141. package/dist/esm/application/exceptions/not-allowed.exception.js +13 -0
  142. package/dist/esm/application/exceptions/unexpected-error.exception.d.ts +5 -0
  143. package/dist/esm/application/exceptions/unexpected-error.exception.js +13 -0
  144. package/dist/esm/application/index.d.ts +7 -0
  145. package/dist/esm/application/index.js +7 -0
  146. package/dist/esm/application/use-case/use-case.js +3 -2
  147. package/dist/{cjs/domain/type/value-objects/value-objects.d.ts → esm/application/value-object-builder/value-object-builder.d.ts} +3 -3
  148. package/dist/esm/application/value-object-builder/value-object-builder.js +47 -0
  149. package/dist/esm/domain/aggregate/aggregate-root.d.ts +0 -5
  150. package/dist/esm/domain/aggregate/aggregate-root.js +4 -20
  151. package/dist/esm/domain/entity/entity.js +1 -1
  152. package/dist/esm/domain/event/event-base.d.ts +3 -9
  153. package/dist/esm/domain/event/event-base.js +8 -38
  154. package/dist/esm/domain/exceptions/domain-exception-code.d.ts +7 -0
  155. package/dist/esm/domain/exceptions/domain-exception-code.js +8 -0
  156. package/dist/esm/domain/exceptions/domain.exception.d.ts +6 -0
  157. package/dist/esm/domain/exceptions/domain.exception.js +12 -0
  158. package/dist/esm/domain/exceptions/invalid-event-definition.exception.d.ts +4 -0
  159. package/dist/esm/domain/exceptions/invalid-event-definition.exception.js +15 -0
  160. package/dist/esm/{exceptions/domain → domain/exceptions}/required-value.exception.js +4 -5
  161. package/dist/esm/{exceptions/domain → domain/exceptions}/type-primitive.exception.js +4 -5
  162. package/dist/esm/{exceptions/domain → domain/exceptions}/validation.exception.js +5 -5
  163. package/dist/esm/domain/index.d.ts +7 -0
  164. package/dist/esm/domain/index.js +6 -0
  165. package/dist/esm/domain/primitive/primitive-types.d.ts +1 -3
  166. package/dist/esm/domain/type/abstract-type.d.ts +6 -8
  167. package/dist/esm/domain/type/abstract-type.js +14 -22
  168. package/dist/esm/domain/type/array-type.d.ts +2 -1
  169. package/dist/esm/domain/type/array-type.js +16 -17
  170. package/dist/esm/domain/type/boolean-type.js +1 -1
  171. package/dist/esm/domain/type/composite/composite-brand.d.ts +4 -0
  172. package/dist/esm/domain/type/composite/composite-brand.js +11 -0
  173. package/dist/esm/domain/type/composite/composite-field.d.ts +7 -8
  174. package/dist/esm/domain/type/composite/composite-field.js +69 -46
  175. package/dist/esm/domain/type/composite/composite-issues.d.ts +6 -0
  176. package/dist/esm/domain/type/composite/composite-issues.js +31 -0
  177. package/dist/esm/domain/type/composite/composite.js +13 -17
  178. package/dist/esm/domain/type/date-type.d.ts +3 -2
  179. package/dist/esm/domain/type/date-type.js +39 -4
  180. package/dist/esm/domain/type/enum-type.js +1 -1
  181. package/dist/esm/domain/type/id-type.d.ts +1 -1
  182. package/dist/esm/domain/type/id-type.js +2 -2
  183. package/dist/esm/domain/type/index.d.ts +2 -4
  184. package/dist/esm/domain/type/index.js +1 -3
  185. package/dist/esm/domain/type/json-type.d.ts +4 -0
  186. package/dist/esm/domain/type/json-type.js +32 -3
  187. package/dist/esm/domain/type/number-type.js +1 -1
  188. package/dist/esm/domain/type/required/required-mark.d.ts +0 -1
  189. package/dist/esm/domain/type/string-type.js +1 -1
  190. package/dist/esm/domain/type/uuid-type.js +1 -1
  191. package/dist/esm/infrastructure/event/domain-event-bus.js +5 -5
  192. package/dist/esm/infrastructure/event/event-registry.js +4 -4
  193. package/dist/esm/infrastructure/event/in-memory/emitter.js +1 -1
  194. package/dist/esm/infrastructure/event/in-memory/in-memory-event-channel.d.ts +1 -1
  195. package/dist/esm/infrastructure/event/in-memory/in-memory-event-channel.js +5 -6
  196. package/dist/esm/{exceptions/infrastructure → infrastructure/exceptions}/event-delivery.exception.js +4 -4
  197. package/dist/esm/infrastructure/exceptions/infrastructure-exception-code.d.ts +6 -0
  198. package/dist/esm/infrastructure/exceptions/infrastructure-exception-code.js +7 -0
  199. package/dist/esm/infrastructure/exceptions/infrastructure.exception.d.ts +6 -0
  200. package/dist/esm/infrastructure/exceptions/infrastructure.exception.js +12 -0
  201. package/dist/esm/{exceptions/infrastructure → infrastructure/exceptions}/internal-error.exception.d.ts +1 -1
  202. package/dist/esm/infrastructure/exceptions/internal-error.exception.js +13 -0
  203. package/dist/esm/infrastructure/exceptions/kind/exception-classifier.d.ts +4 -0
  204. package/dist/esm/infrastructure/exceptions/kind/exception-classifier.js +30 -0
  205. package/dist/esm/infrastructure/exceptions/kind/exception-kind.d.ts +9 -0
  206. package/dist/esm/infrastructure/exceptions/kind/exception-kind.js +10 -0
  207. package/dist/esm/{exceptions/infrastructure → infrastructure/exceptions}/unknown-event.exception.js +4 -4
  208. package/dist/esm/infrastructure/index.d.ts +7 -2
  209. package/dist/esm/infrastructure/index.js +7 -1
  210. package/dist/esm/infrastructure/logging/console-logger.js +1 -1
  211. package/dist/{cjs → esm/shared}/exceptions/abstract.exception.d.ts +6 -4
  212. package/dist/esm/{exceptions → shared/exceptions}/abstract.exception.js +9 -16
  213. package/dist/esm/shared/index.d.ts +8 -0
  214. package/dist/esm/shared/index.js +5 -0
  215. package/dist/esm/shared/issue-collection-error.d.ts +9 -0
  216. package/dist/esm/shared/issue-collection-error.js +7 -0
  217. package/dist/esm/shared/issue-collector.d.ts +8 -0
  218. package/dist/esm/shared/issue-collector.js +37 -0
  219. package/package.json +7 -18
  220. package/dist/cjs/domain/type/equality/value-equality.d.ts +0 -7
  221. package/dist/cjs/domain/type/equality/value-equality.js +0 -41
  222. package/dist/cjs/domain/type/immutable/immutable-date.d.ts +0 -4
  223. package/dist/cjs/domain/type/immutable/immutable-date.js +0 -37
  224. package/dist/cjs/domain/type/immutable/immutable.d.ts +0 -4
  225. package/dist/cjs/domain/type/immutable/immutable.js +0 -19
  226. package/dist/cjs/domain/type/value-objects/value-objects.js +0 -70
  227. package/dist/cjs/exceptions/application/application.exception.d.ts +0 -6
  228. package/dist/cjs/exceptions/application/application.exception.js +0 -17
  229. package/dist/cjs/exceptions/domain/domain.exception.d.ts +0 -6
  230. package/dist/cjs/exceptions/domain/domain.exception.js +0 -17
  231. package/dist/cjs/exceptions/exception-code.d.ts +0 -15
  232. package/dist/cjs/exceptions/exception-code.js +0 -19
  233. package/dist/cjs/exceptions/index.d.ts +0 -14
  234. package/dist/cjs/exceptions/index.js +0 -30
  235. package/dist/cjs/exceptions/infrastructure/infrastructure.exception.d.ts +0 -6
  236. package/dist/cjs/exceptions/infrastructure/infrastructure.exception.js +0 -17
  237. package/dist/cjs/infrastructure/http/http-error-mapper.d.ts +0 -23
  238. package/dist/cjs/infrastructure/http/http-error-mapper.js +0 -63
  239. package/dist/esm/domain/type/equality/value-equality.d.ts +0 -7
  240. package/dist/esm/domain/type/equality/value-equality.js +0 -37
  241. package/dist/esm/domain/type/immutable/immutable-date.d.ts +0 -4
  242. package/dist/esm/domain/type/immutable/immutable-date.js +0 -33
  243. package/dist/esm/domain/type/immutable/immutable.d.ts +0 -4
  244. package/dist/esm/domain/type/immutable/immutable.js +0 -15
  245. package/dist/esm/domain/type/value-objects/value-objects.js +0 -66
  246. package/dist/esm/exceptions/application/aggregate-not-found.exception.js +0 -13
  247. package/dist/esm/exceptions/application/application.exception.d.ts +0 -6
  248. package/dist/esm/exceptions/application/application.exception.js +0 -13
  249. package/dist/esm/exceptions/application/not-allowed.exception.js +0 -13
  250. package/dist/esm/exceptions/domain/domain.exception.d.ts +0 -6
  251. package/dist/esm/exceptions/domain/domain.exception.js +0 -13
  252. package/dist/esm/exceptions/exception-code.d.ts +0 -15
  253. package/dist/esm/exceptions/exception-code.js +0 -16
  254. package/dist/esm/exceptions/index.d.ts +0 -14
  255. package/dist/esm/exceptions/index.js +0 -14
  256. package/dist/esm/exceptions/infrastructure/infrastructure.exception.d.ts +0 -6
  257. package/dist/esm/exceptions/infrastructure/infrastructure.exception.js +0 -13
  258. package/dist/esm/exceptions/infrastructure/internal-error.exception.js +0 -13
  259. package/dist/esm/infrastructure/http/http-error-mapper.d.ts +0 -23
  260. package/dist/esm/infrastructure/http/http-error-mapper.js +0 -59
  261. /package/dist/cjs/{exceptions/domain → domain/exceptions}/required-value.exception.d.ts +0 -0
  262. /package/dist/cjs/{exceptions/domain → domain/exceptions}/type-primitive.exception.d.ts +0 -0
  263. /package/dist/cjs/{exceptions/domain → domain/exceptions}/validation.exception.d.ts +0 -0
  264. /package/dist/cjs/domain/type/required/{required.d.ts → required-type.d.ts} +0 -0
  265. /package/dist/cjs/domain/type/required/{required.js → required-type.js} +0 -0
  266. /package/dist/cjs/{exceptions/infrastructure → infrastructure/exceptions}/event-delivery.exception.d.ts +0 -0
  267. /package/dist/cjs/{exceptions/infrastructure → infrastructure/exceptions}/unknown-event.exception.d.ts +0 -0
  268. /package/dist/cjs/{exceptions → shared/exceptions}/brand/exception-brand.d.ts +0 -0
  269. /package/dist/cjs/{exceptions → shared/exceptions}/brand/exception-brand.js +0 -0
  270. /package/dist/cjs/{exceptions → shared/exceptions}/received/received-value.d.ts +0 -0
  271. /package/dist/cjs/{exceptions → shared/exceptions}/received/received-value.js +0 -0
  272. /package/dist/esm/{exceptions/domain → domain/exceptions}/required-value.exception.d.ts +0 -0
  273. /package/dist/esm/{exceptions/domain → domain/exceptions}/type-primitive.exception.d.ts +0 -0
  274. /package/dist/esm/{exceptions/domain → domain/exceptions}/validation.exception.d.ts +0 -0
  275. /package/dist/esm/domain/type/required/{required.d.ts → required-type.d.ts} +0 -0
  276. /package/dist/esm/domain/type/required/{required.js → required-type.js} +0 -0
  277. /package/dist/esm/{exceptions/infrastructure → infrastructure/exceptions}/event-delivery.exception.d.ts +0 -0
  278. /package/dist/esm/{exceptions/infrastructure → infrastructure/exceptions}/unknown-event.exception.d.ts +0 -0
  279. /package/dist/esm/{exceptions → shared/exceptions}/brand/exception-brand.d.ts +0 -0
  280. /package/dist/esm/{exceptions → shared/exceptions}/brand/exception-brand.js +0 -0
  281. /package/dist/esm/{exceptions → shared/exceptions}/received/received-value.d.ts +0 -0
  282. /package/dist/esm/{exceptions → shared/exceptions}/received/received-value.js +0 -0
@@ -48,6 +48,7 @@ src/
48
48
  interfaces/
49
49
  http/
50
50
  ordersRouter.ts
51
+ httpError.ts (traduce ExceptionKind a estado HTTP)
51
52
  messaging/
52
53
  StockReservedConsumer.ts (traduce el contrato publicado por Almacén al input de un caso de uso)
53
54
  composition.ts ← composition root del contexto
@@ -64,7 +65,7 @@ test/
64
65
  infrastructure/ (integración con BD real o contenedor)
65
66
  ```
66
67
 
67
- Lo que no aparece porque lo da `@archi-code/ddd`: clases base (`AggregateRoot`, `Entity`, `EventBase`, los tipos de value object, `Composite`), una interfaz `OrderRepository` (los roles `AggregateFinder`, `AggregateSaver` y `AggregateRemover` vienen de `@archi-code/ddd/domain`), un `OrderMapper` (la conversión es del agregado, `toPrimitives()` y `static fromPrimitives()`, y cada repositorio los llama directamente: guarda `order.toPrimitives()` con `order.version + 1` y carga con `Order.fromPrimitives(row).restoreVersion(row.version)`), un publicador de eventos (`DomainEventBus` implementa `EventBus`) y el puerto `Clock`. Tampoco hay un runner de transacciones ni una unidad de trabajo: la transacción es del repositorio `[Fuera de los libros: convención de @archi-code/ddd]`.
68
+ Lo que no aparece porque lo da `@archi-code/ddd`: clases base (`AggregateRoot`, `Entity`, `EventBase`, los tipos de value object, `Composite`), una interfaz `OrderRepository` (los roles `AggregateFinder`, `AggregateSaver` y `AggregateRemover` vienen de `@archi-code/ddd/domain`), un `OrderMapper` (la conversión es del agregado, `toPrimitives()` y `static fromPrimitives()`, y cada repositorio los llama directamente: guarda `order.toPrimitives()` con la versión siguiente al lado y carga con `Order.fromPrimitives(row.primitives)`; la versión la recuerda el propio repositorio en un `WeakMap<Order, number>`, nunca el agregado), un publicador de eventos (`DomainEventBus` implementa `EventBus`) y el puerto `Clock`. Tampoco hay un runner de transacciones ni una unidad de trabajo: la transacción es del repositorio `[Fuera de los libros: convención de @archi-code/ddd]`.
68
69
 
69
70
  Lo que el paquete no da y escribe el proyecto: cada repositorio, uno por agregado. Su constructor recibe solo las dependencias de su tecnología (la `collection` de Mongo, el `pool` de Postgres), nunca la clase del agregado ni un mapper; el agregado solo entra y sale por sus métodos. Nada comprueba en ejecución la forma del agregado: que su estado sean value objects expuestos por getters y que `toPrimitives()`/`fromPrimitives()` no pierdan campos lo garantizan el agregado y su test de ida y vuelta. El repositorio en memoria también es del proyecto y es un doble de test: vive en `test/support/`, nunca en `src/` (ver `InMemoryOrderRepository` en `ddd-skills/ddd-repositorios-persistencia/references/tests-repositorios.md`) `[Fuera de los libros: convención de @archi-code/ddd]`.
70
71
 
@@ -116,7 +117,6 @@ import { MongoClient } from 'mongodb';
116
117
  import {
117
118
  DomainEventBus,
118
119
  EventRegistry,
119
- HttpErrorMapper,
120
120
  SystemClock,
121
121
  } from '@archi-code/ddd/infrastructure';
122
122
  import { OrderLineAdded, OrderPlaced } from './domain/order/events';
@@ -152,7 +152,7 @@ export async function composeSales(
152
152
  OrderPlaced,
153
153
  (json) =>
154
154
  new OrderPlaced(
155
- json.aggregateId,
155
+ json.orderId as string,
156
156
  json.customerId as string,
157
157
  json.lines as OrderLineData[],
158
158
  json.total as MoneyData,
@@ -163,7 +163,7 @@ export async function composeSales(
163
163
  OrderLineAdded,
164
164
  (json) =>
165
165
  new OrderLineAdded(
166
- json.aggregateId,
166
+ json.orderId as string,
167
167
  json.sku as string,
168
168
  json.quantity as number,
169
169
  json.unitPrice as MoneyData,
@@ -188,10 +188,7 @@ export async function composeSales(
188
188
  ];
189
189
 
190
190
  return {
191
- httpRouter: ordersRouter(
192
- { placeOrder, addOrderLine },
193
- new HttpErrorMapper({ statusByCode: { ORD001: 409 }, logger: deps.logger }),
194
- ),
191
+ httpRouter: ordersRouter({ placeOrder, addOrderLine }, deps.logger),
195
192
  close: async () => {
196
193
  await Promise.all(subscriptions.map((subscription) => subscription.unsubscribe()));
197
194
  await mongo.close();
@@ -201,8 +198,8 @@ export async function composeSales(
201
198
  ```
202
199
 
203
200
  Qué hace cada pieza:
204
- * Del paquete, en `@archi-code/ddd/infrastructure`: `EventRegistry` (reconstruye cada evento desde su JSON; la factoría pasa el JSON como metadata para conservar `eventId` y `occurredOn`), `DomainEventBus` (implementa `EventBus`, lo único que reciben los casos de uso; su `subscribe(listener)` solo lo llama el composition root), `SystemClock` (implementa `Clock`) y `HttpErrorMapper` (códigos propios como `ORD001` se sobrescriben por código).
205
- * Código del proyecto sobre los puertos del paquete `[Fuera de los libros: convención de @archi-code/ddd]`: `MongoOrderRepository` (roles de repositorio de `Order`; su constructor recibe solo la `collection`; llama a `order.toPrimitives()` y a `Order.fromPrimitives(row).restoreVersion(row.version)` y controla la `version`; responsable de toda la persistencia: cada `save`/`remove` es una escritura atómica de un documento, y si un agregado ocupara varias tablas SQL abriría y cerraría la transacción de la base de datos dentro de `save`/`remove`; ver `ddd-skills/ddd-repositorios-persistencia/GUIA.md`), `RabbitMqEventChannel` (implementa `EventChannel`, ver `ddd-skills/ddd-eventos-dominio/GUIA.md`) y `HttpWarehouse` (implementa el puerto `Warehouse`).
201
+ * Del paquete, en `@archi-code/ddd/infrastructure`: `EventRegistry` (reconstruye cada evento desde su JSON; la factoría pasa el JSON como metadata para conservar `eventId` y `occurredOn`), `DomainEventBus` (implementa `EventBus`, lo único que reciben los casos de uso; su `subscribe(listener)` solo lo llama el composition root), y `SystemClock` (implementa `Clock`).
202
+ * Código del proyecto sobre los puertos del paquete `[Fuera de los libros: convención de @archi-code/ddd]`: `MongoOrderRepository` (roles de repositorio de `Order`; su constructor recibe solo la `collection`; llama a `order.toPrimitives()` y a `Order.fromPrimitives(row.primitives)` y controla la versión, que recuerda por agregado en un `WeakMap` y guarda junto a los primitivos; responsable de toda la persistencia: cada `save`/`remove` es una escritura atómica de un documento, y si un agregado ocupara varias tablas SQL abriría y cerraría la transacción de la base de datos dentro de `save`/`remove`; ver `ddd-skills/ddd-repositorios-persistencia/GUIA.md`), `RabbitMqEventChannel` (implementa `EventChannel`, ver `ddd-skills/ddd-eventos-dominio/GUIA.md`) y `HttpWarehouse` (implementa el puerto `Warehouse`).
206
203
  * Cada listener se suscribe con `bus.subscribe(listener)`, que lee su `@ListensTo(OrderLineAdded, { consumer: 'prepare-shipment-on-order-line-added' })` y rechaza un evento que el registro no sabe reconstruir.
207
204
  * Cambiar RabbitMQ por un outbox o por memoria cambia solo la línea del canal.
208
205
  * Ni los casos de uso ni los repositorios reciben un runner de transacciones: el composition root no construye ninguno.
@@ -236,7 +233,7 @@ export async function salesForTests(warehouse: Warehouse) {
236
233
  OrderLineAdded,
237
234
  (json) =>
238
235
  new OrderLineAdded(
239
- json.aggregateId,
236
+ json.orderId as string,
240
237
  json.sku as string,
241
238
  json.quantity as number,
242
239
  json.unitPrice as MoneyData,
@@ -264,18 +261,19 @@ El test comprueba lo que causó el listener (el `Warehouse` de prueba) y lo que
264
261
  // src/contexts/sales/interfaces/http/ordersRouter.ts
265
262
  import { Router } from 'express';
266
263
  import type { Response } from 'express';
267
- import type { HttpErrorMapper } from '@archi-code/ddd/infrastructure';
264
+ import type { Logger } from '@archi-code/ddd/application';
268
265
  import type { AddOrderLine } from '../../application/add-order-line/AddOrderLine';
269
266
  import type { PlaceOrder } from '../../application/place-order/PlaceOrder';
267
+ import { toHttpError } from './httpError';
270
268
 
271
269
  export function ordersRouter(
272
270
  useCases: { placeOrder: PlaceOrder; addOrderLine: AddOrderLine },
273
- errors: HttpErrorMapper,
271
+ logger: Logger,
274
272
  ): Router {
275
273
  const router = Router();
276
274
 
277
275
  const fail = (res: Response, error: unknown) => {
278
- const { status, body } = errors.map(error);
276
+ const { status, body } = toHttpError(error, logger);
279
277
  return res.status(status).json(body);
280
278
  };
281
279
 
@@ -340,13 +338,69 @@ export function ordersRouter(
340
338
  }
341
339
  ```
342
340
 
343
- El controlador no sabe qué es un `Order`. Comprueba solo la forma de la petición (un campo es texto o número) [IDDD cap. 4]; las reglas de cada valor son de los value objects que `toCommand` construye con `ValueObjects.build`, y llegan como una `ValidationException` con todos los campos (422). La identidad del cliente sale del token, no del body. `HttpErrorMapper` traduce los errores: 404 `AggregateNotFoundException`, 403 `NotAllowedException`, 409 `ConcurrencyException`, 422 `ValidationException` y cualquier otra `DomainException`, 500 con un mensaje genérico para lo demás. Con `logger`, el mapper registra cada 5xx una vez como `error` (con su causa) y los 4xx solo en `debug`; el controlador no vuelve a registrarlos.
341
+ El controlador no sabe qué es un `Order`. Comprueba solo la forma de la petición (un campo es texto o número) [IDDD cap. 4]; las reglas de cada valor son de los value objects que `toCommand` construye con `ValueObjectBuilder.build`, y llegan como una `ValidationException` con todos los campos (422). La identidad del cliente sale del token, no del body. `toHttpError` traduce los errores. El paquete no trae nada de HTTP: `ExceptionClassifier.classify(error)` de `@archi-code/ddd/infrastructure` devuelve el `ExceptionKind` del fallo y cada protocolo (HTTP, un consumidor de mensajes, una CLI) lo traduce a sus códigos `[Fuera de los libros: convención de @archi-code/ddd]`:
342
+
343
+ ```ts
344
+ // src/contexts/sales/interfaces/http/httpError.ts
345
+ import { DomainException, ValidationException } from '@archi-code/ddd/domain';
346
+ import type { ValidationIssue } from '@archi-code/ddd/domain';
347
+ import { ApplicationException, ApplicationExceptionCode } from '@archi-code/ddd/application';
348
+ import type { Logger } from '@archi-code/ddd/application';
349
+ import {
350
+ ExceptionClassifier,
351
+ ExceptionKind,
352
+ InfrastructureException,
353
+ } from '@archi-code/ddd/infrastructure';
354
+
355
+ export interface HttpError {
356
+ status: number;
357
+ body: { code: string; message: string; errors?: readonly ValidationIssue[] };
358
+ }
359
+
360
+ const STATUS_BY_KIND: Readonly<Record<ExceptionKind, number>> = {
361
+ [ExceptionKind.NotFound]: 404,
362
+ [ExceptionKind.NotAllowed]: 403,
363
+ [ExceptionKind.Conflict]: 409,
364
+ [ExceptionKind.Malformed]: 400,
365
+ [ExceptionKind.Invalid]: 422,
366
+ [ExceptionKind.Rejected]: 400,
367
+ [ExceptionKind.Internal]: 500,
368
+ };
369
+
370
+ const STATUS_BY_CODE = new Map<string, number>([['ORD001', 409]]);
371
+
372
+ type PackageException = DomainException | ApplicationException | InfrastructureException;
373
+
374
+ function isPackageException(error: unknown): error is PackageException {
375
+ return (
376
+ error instanceof DomainException ||
377
+ error instanceof ApplicationException ||
378
+ error instanceof InfrastructureException
379
+ );
380
+ }
381
+
382
+ export function toHttpError(error: unknown, logger: Logger): HttpError {
383
+ const code = isPackageException(error) ? error.code : ApplicationExceptionCode.UnexpectedError;
384
+ const status = STATUS_BY_CODE.get(code) ?? STATUS_BY_KIND[ExceptionClassifier.classify(error)];
385
+ if (status >= 500 || !isPackageException(error)) {
386
+ logger.error('http_request_failed', { status, code, error });
387
+ return { status, body: { code, message: 'Internal server error' } };
388
+ }
389
+ logger.debug('http_request_rejected', { status, code });
390
+ if (error instanceof ValidationException) {
391
+ return { status, body: { code, message: error.message, errors: error.errors } };
392
+ }
393
+ return { status, body: { code, message: error.message } };
394
+ }
395
+ ```
396
+
397
+ En HTTP: 404 `AggregateNotFoundException`, 403 `NotAllowedException`, 409 `ConcurrencyException`, 400 un error de conversión u obligatoriedad, 422 `ValidationException` y cualquier otra `DomainException`, 400 otros errores de aplicación y 500 con un mensaje genérico para lo demás (también `UnexpectedErrorException`, el error ajeno que `UseCase.execute` envuelve); los códigos propios (`ORD001`) se resuelven antes que el tipo. Cada 5xx se registra una vez como `error` (con su causa) y los 4xx solo en `debug`; el controlador no vuelve a registrarlos. Un consumidor de mensajes traduce el mismo `ExceptionKind` a confirmar, descartar o reintentar.
344
398
 
345
399
  ## Configuración de TypeScript
346
400
 
347
401
  `[Fuera de los libros]`
348
402
  * `strict: true`. El dominio se apoya en el tipado para expresar invariantes.
349
403
  * `experimentalDecorators: true`: `@Validations` (value objects) y `@ListensTo` (listeners) son decoradores de clase.
350
- * `moduleResolution` `node16`, `nodenext` o `bundler` para resolver los subpaths `@archi-code/ddd/domain`, `/application`, `/infrastructure` y `/exceptions`; con `node` los resuelve el `typesVersions` del paquete.
404
+ * `moduleResolution` `node16`, `nodenext` o `bundler` para resolver los subpaths `@archi-code/ddd/domain`, `/application` y `/infrastructure` (cada uno exporta las excepciones de su capa); con `node` los resuelve el `typesVersions` del paquete.
351
405
  * Alias por contexto (`"@sales/*": ["src/contexts/sales/*"]`, usado como `@sales/domain/order/Order`) para que los imports revelen la zona.
352
406
  * `exactOptionalPropertyTypes` y `noUncheckedIndexedAccess` ayudan a que los value objects no acepten estados a medias.
@@ -9,7 +9,7 @@
9
9
  [Fuera de los libros: convención de @archi-code/ddd]
10
10
 
11
11
  * En código hecho con el paquete, aplica además cada punto de `ddd-skills/archi-code-ddd/what-not-to-do.md` como lista de comprobación: el paquete da las clases base, pero no impide un agregado anémico con setters, un caso de uso CRUD, dos agregados guardados en el mismo caso de uso o un caso de uso que abre su propia transacción (la transacción es del repositorio).
12
- * La dirección de dependencias es la del paquete: el dominio importa solo `@archi-code/ddd/domain`, `@archi-code/ddd/exceptions` y `@archi-code/validation`; importar `/application` o `/infrastructure` (por ejemplo `SystemClock`) o un adaptador de `infrastructure/` del proyecto desde el dominio es un hallazgo de severidad alta (A8 de la lista).
12
+ * La dirección de dependencias es la del paquete: el dominio importa solo `@archi-code/ddd/domain` (que exporta también las excepciones de dominio) y `@archi-code/validation`; importar `/application` o `/infrastructure` (por ejemplo `SystemClock`) o un adaptador de `infrastructure/` del proyecto desde el dominio es un hallazgo de severidad alta (A8 de la lista).
13
13
  * El reloj es un puerto (`Clock`; `FixedClock` en tests): `new Date()` en dominio o aplicación es un hallazgo (A9).
14
14
  * El escáner detecta guardados de cualquier variable (`this.orders.save(...)`, `this.customers.remove(...)`) dentro del mismo bloque transaccional o de la misma clase de aplicación, transacciones abiertas o recibidas en dominio y aplicación (bloques transaccionales, `startSession`, runners de transacciones, unidades de trabajo), imports de `@archi-code/ddd/application` o `/infrastructure` desde el dominio, `new Date()` en dominio y aplicación, y campos de estado primitivos con el prefijo `_` del paquete (`private _status: string`). Marca `amount: number` o `status: string` también dentro de los eventos: ahí son esperables, porque el payload de un evento son primitivos.
15
15
 
@@ -85,7 +85,7 @@ class PlaceOrder extends UseCase<PlaceOrderInput, PlaceOrderCommand, void> {
85
85
  | 5 | Estado y total como primitivos (`_status: string`, `_total: number`) en vez de value objects | `domain/order/order.ts:5-6` | Media |
86
86
  | 6 | `toCommand` devuelve el input tal cual: ningún value object se construye ni valida antes de `handle` | `application/update-order/update-order.ts:14` | Media |
87
87
  | 7 | `new Date()` en el agregado: fechas no deterministas en tests | `domain/order/order.ts:10` | Media |
88
- | 8 | `throw new Error(...)` en vez de una `DomainException` con código: el `HttpErrorMapper` responde 500 | `application/update-order/update-order.ts:18` | Media |
88
+ | 8 | `throw new Error(...)` en vez de una `DomainException` con código: `ExceptionClassifier` lo clasifica como `ExceptionKind.Internal` y el cliente recibe 500 | `application/update-order/update-order.ts:18` | Media |
89
89
  | 9 | Los casos de uso reciben un `UnitOfWork` del proyecto y abren la transacción con `unitOfWork.run`: la transacción y todo lo de persistencia son del adaptador del repositorio, que hace atómico cada `save` | `application/update-order/update-order.ts:17`, `application/place-order/place-order.ts:27` | Media |
90
90
 
91
91
  Los hallazgos 2, 3 y 6 se cruzan además con `ddd-skills/archi-code-ddd/what-not-to-do.md` (casos de uso CRUD, value objects fuera de `toCommand`, reglas de negocio fuera del agregado).
@@ -108,10 +108,8 @@ addLine(line: OrderLine, clock: { now(): Date }): void;
108
108
  Enviar no es trabajo del pedido de Ventas: lo hace `Shipment` en Almacén, que publica `ShipmentDispatched` (`ddd-skills/ddd-agregados/references/ejemplo-ecommerce.md`), así que `status: 'shipped'` y su errata desaparecen de Ventas. `UpdateOrder` se parte en un caso de uso por acción de negocio (`AddOrderLine` aquí; el envío, en Almacén); cada uno construye sus value objects en `toCommand` y guarda un solo agregado:
109
109
 
110
110
  ```ts
111
- import { ValueObjects } from '@archi-code/ddd/domain';
112
111
  import type { AggregateFinder, AggregateSaver } from '@archi-code/ddd/domain';
113
- import { AggregateNotFoundException } from '@archi-code/ddd/exceptions';
114
- import { UseCase } from '@archi-code/ddd/application';
112
+ import { AggregateNotFoundException, UseCase, ValueObjectBuilder } from '@archi-code/ddd/application';
115
113
  import type { Clock, EventBus } from '@archi-code/ddd/application';
116
114
 
117
115
  interface AddOrderLineInput {
@@ -136,7 +134,7 @@ class AddOrderLine extends UseCase<AddOrderLineInput, AddOrderLineCommand, void>
136
134
  }
137
135
 
138
136
  protected toCommand(input: AddOrderLineInput): AddOrderLineCommand {
139
- return ValueObjects.build({
137
+ return ValueObjectBuilder.build({
140
138
  orderId: () => new OrderId(input.orderId),
141
139
  line: () =>
142
140
  OrderLine.fromPrimitives({
@@ -198,7 +196,7 @@ class PlaceOrder extends UseCase<PlaceOrderInput, PlaceOrderCommand, void> {
198
196
  }
199
197
 
200
198
  protected toCommand(input: PlaceOrderInput): PlaceOrderCommand {
201
- return ValueObjects.build({
199
+ return ValueObjectBuilder.build({
202
200
  orderId: () => new OrderId(input.orderId),
203
201
  customerId: () => new CustomerId(input.customerId),
204
202
  lines: () => OrderLines.fromPrimitives({ items: input.lines }),
@@ -225,7 +223,7 @@ class AwardLoyaltyPoints extends UseCase<AwardLoyaltyPointsInput, AwardLoyaltyPo
225
223
  }
226
224
 
227
225
  protected toCommand(input: AwardLoyaltyPointsInput): AwardLoyaltyPointsCommand {
228
- return ValueObjects.build({
226
+ return ValueObjectBuilder.build({
229
227
  customerId: () => new CustomerId(input.customerId),
230
228
  orderId: () => new OrderId(input.orderId),
231
229
  });
@@ -250,7 +248,7 @@ class AwardLoyaltyPointsOnOrderPlaced extends EventListener<OrderPlaced> {
250
248
  async handle(event: OrderPlaced): Promise<void> {
251
249
  await this.awardLoyaltyPoints.execute({
252
250
  customerId: event.customerId,
253
- orderId: event.aggregateId,
251
+ orderId: event.orderId,
254
252
  });
255
253
  }
256
254
  }
@@ -266,7 +264,7 @@ import {
266
264
  FixedClock,
267
265
  InMemoryEventChannel,
268
266
  } from '@archi-code/ddd/infrastructure';
269
- import { ValidationException } from '@archi-code/ddd/exceptions';
267
+ import { ValidationException } from '@archi-code/ddd/domain';
270
268
  import { InMemoryCustomerRepository } from '../support/in-memory-customer-repository';
271
269
  import { InMemoryOrderRepository } from '../support/in-memory-order-repository';
272
270
 
@@ -276,7 +274,7 @@ const registry = new EventRegistry<OrderEvent>().register(
276
274
  OrderPlaced,
277
275
  (json) =>
278
276
  new OrderPlaced(
279
- json.aggregateId,
277
+ json.orderId as string,
280
278
  json.customerId as string,
281
279
  json.lines as OrderLineData[],
282
280
  json.total as MoneyData,
@@ -311,6 +309,6 @@ await expect(
311
309
  ## Plan
312
310
 
313
311
  * **Fase 0:** tests de caracterización de alta, envío y puntos de fidelidad sobre `InMemoryOrderRepository` e `InMemoryCustomerRepository` (dobles de test del proyecto en `test/support/`), `InMemoryEventChannel` y `FixedClock`.
314
- * **Fase 1:** borrar `OrderMapper`: la conversión pasa al agregado (`toPrimitives()` y `static fromPrimitives()`, con un test de ida y vuelta) y `MongoOrderRepository` la llama directamente (su constructor recibe solo la `collection`), sustituir `new Date()` por el `Clock` inyectado y `Error` por `DomainException` con código; `HttpErrorMapper` responde 422 a las reglas de negocio. Quitar el `UnitOfWork` de los casos de uso: `MongoOrderRepository` hace atómico cada `save` (una escritura de un documento con condición de versión) y los casos de uso solo reciben roles de repositorio, `EventBus` y `Clock`.
312
+ * **Fase 1:** borrar `OrderMapper`: la conversión pasa al agregado (`toPrimitives()` y `static fromPrimitives()`, con un test de ida y vuelta) y `MongoOrderRepository` la llama directamente (su constructor recibe solo la `collection`), sustituir `new Date()` por el `Clock` inyectado y `Error` por `DomainException` con código; `ExceptionClassifier` clasifica las reglas de negocio como `ExceptionKind.Invalid` (422 en HTTP). Quitar el `UnitOfWork` de los casos de uso: `MongoOrderRepository` hace atómico cada `save` (una escritura de un documento con condición de versión) y los casos de uso solo reciben roles de repositorio, `EventBus` y `Clock`.
315
313
  * **Fase 2:** `OrderStatus`, `OrderLines` (con `OrderLine` y `Money`) como value objects; `addLine` sustituye a los setters; `UpdateOrder` se parte en `AddOrderLine` y el envío pasa a `Shipment` en Almacén.
316
314
  * **Fase 3:** `PlaceOrder` guarda solo el pedido; `AwardLoyaltyPointsOnOrderPlaced` ejecuta `AwardLoyaltyPoints` por evento. Para que `OrderPlaced` no se pierda si el proceso cae entre `save` y `publish`, `MongoOrderRepository` escribe el pedido y sus eventos pendientes en el mismo documento (outbox) y un relay los entrega después por el `EventChannel`; los casos de uso no cambian.
@@ -12,7 +12,7 @@ La columna "Script" indica si `scan_ddd_smells.py` lo detecta (sí / parcial / n
12
12
  | E4 | ¿Las integraciones con sistemas externos pasan por una ACL (puerto del dominio con value objects, adaptador y traductor en `infrastructure/`)? | [Destilado cap. 4] | no | Alta |
13
13
  | E5 | ¿Hay cadenas de llamadas síncronas entre servicios dentro de una petición? | [Destilado cap. 4] | no | Alta |
14
14
  | E6 | ¿Usuarios y permisos mezclados en el modelo del núcleo? La autorización va en el caso de uso (`NotAllowedException`), no en el agregado | [IDDD cap. 2] | no | Media |
15
- | E7 | ¿Se envía `event.toJSON()` de un evento de dominio a otro contexto en vez de un contrato publicado y versionado? | [IDDD cap. 13] | no | Alta |
15
+ | E7 | ¿Se envía un evento de dominio tal cual (`{ ...event }`) a otro contexto en vez de un contrato publicado y versionado? | [IDDD cap. 13] | no | Alta |
16
16
 
17
17
  ## Arquitectura
18
18
 
@@ -20,7 +20,7 @@ La columna "Script" indica si `scan_ddd_smells.py` lo detecta (sí / parcial / n
20
20
  |---|---|---|---|---|
21
21
  | A1 | ¿El dominio importa frameworks, ORM o clientes HTTP? | [Destilado cap. 2; IDDD cap. 4] | sí | Alta |
22
22
  | A2 | ¿Decoradores del ORM en clases de dominio? | [IDDD cap. 6] | sí | Alta |
23
- | A3 | ¿Controladores con lógica de negocio, que importan repositorios o acceden a la base de datos? Un punto de entrada solo llama a casos de uso y traduce errores con `HttpErrorMapper` | [IDDD cap. 4] | parcial | Alta |
23
+ | A3 | ¿Controladores con lógica de negocio, que importan repositorios o acceden a la base de datos? Un punto de entrada solo llama a casos de uso y traduce errores según el `ExceptionKind` de `ExceptionClassifier` | [IDDD cap. 4] | parcial | Alta |
24
24
  | A4 | ¿Un caso de uso, servicio de aplicación o el dominio abre o maneja transacciones (`BEGIN`/`COMMIT`, `startSession`, `withTransaction`) o recibe un runner de transacciones o una unidad de trabajo? IDDD pone la transacción en la aplicación [IDDD cap. 12; IDDD cap. 14]; con el paquete se mueve al adaptador del repositorio, que hace atómico cada `save`/`remove` | `[Fuera de los libros: convención de @archi-code/ddd]` | sí | Media |
25
25
  | A5 | ¿Un caso de uso devuelve el agregado al controlador o la API expone `toPrimitives()` tal cual? | [IDDD cap. 4; Destilado cap. 4] | no | Media |
26
26
  | A6 | ¿Módulos nombrados por patrón (`entities/`, `services/`, `listeners/`)? | [PHP: Modules] | no | Baja |
@@ -39,7 +39,7 @@ La columna "Script" indica si `scan_ddd_smells.py` lo detecta (sí / parcial / n
39
39
  | T4 | ¿Agregados con colecciones que crecen sin límite? | [IDDD cap. 10] | no | Alta |
40
40
  | T5 | ¿Referencias a objetos de otros agregados en vez de identidades (`CustomerId extends IdType<'CustomerId'>`)? | [Destilado cap. 5] | no | Media |
41
41
  | T6 | ¿Un caso de uso guarda más de un agregado (o los junta en una misma transacción) sin una excepción de IDDD cap. 10 escrita como decisión? Una escritura atómica por agregado, garantizada por el repositorio | [Destilado cap. 5; IDDD cap. 10] | parcial | Alta |
42
- | T7 | ¿El repositorio usa la `version` de `AggregateRoot`: guarda con condición de versión, lanza `ConcurrencyException`, guarda `order.toPrimitives()` con `order.version + 1`, llama a `markPersisted()` y carga con `Order.fromPrimitives(row).restoreVersion(row.version)`? | [IDDD cap. 10] | no | Alta si hay concurrencia |
42
+ | T7 | ¿El repositorio lleva la versión de concurrencia optimista él mismo: recuerda la de cada agregado que carga o guarda en un `WeakMap<Order, number>` (`0` si es nuevo), guarda con condición de versión, lanza `ConcurrencyException`, guarda `order.toPrimitives()` con `version + 1` al lado (nunca dentro) y actualiza el `WeakMap` solo tras escribir con éxito? ¿El agregado no declara ni mapea ninguna versión? | [IDDD cap. 10]; `[Fuera de los libros: convención de @archi-code/ddd]` para el `WeakMap` | no | Alta si hay concurrencia |
43
43
  | T8 | ¿Repositorios de partes internas, una interfaz `Repository` todo en uno, métodos con nombres o reglas de negocio, o lecturas que cargan agregados? Solo `AggregateFinder`/`AggregateSaver`/`AggregateRemover`, y las lecturas son consultas de aplicación | [IDDD cap. 12] | parcial | Media |
44
44
  | T9 | ¿Casos de uso con nombres CRUD o técnicos (`UpdateOrder`, `OrderService`), que hacen varias cosas según el input, que llaman a otro caso de uso, o clases `Manager`/`Helper`? | [IDDD cap. 14; Destilado cap. 5] | parcial | Media |
45
45
  | T10 | ¿Eventos con nombre en pasado y del lenguaje (`static readonly eventName = 'OrderPlaced'`)? ¿O `OrderUpdated` con todo el estado o con value objects en el payload? | [Destilado cap. 6] | no | Media |
@@ -48,7 +48,7 @@ La columna "Script" indica si `scan_ddd_smells.py` lo detecta (sí / parcial / n
48
48
  | T13 | ¿Identidad generada temprano (`OrderId.generate()` antes de crear) y pasada a `super(id)`? ¿Algún id opcional (`extends StringType` sin `Required`)? | [IDDD cap. 5] | no | Media |
49
49
  | T14 | ¿Tests de aceptación con lenguaje del negocio y datos realistas (el `InMemoryOrderRepository` del proyecto en `test/support/`, `FixedClock`, `channel.sent`)? | [Destilado cap. 2; IDDD cap. 1] | no | Media |
50
50
  | T15 | ¿Cada listener se llama `<Action>On<Event>`, tiene `@ListensTo(Event, { consumer: '…' })` con un nombre literal estable y solo traduce el evento a la entrada de un caso de uso? | `[Fuera de los libros: convención de @archi-code/ddd]` | no | Media |
51
- | T16 | ¿Los value objects se construyen en `toCommand` con `ValueObjects.build` (todos los campos a la vez) y no en `handle` ni en el controlador? | `[Fuera de los libros: convención de @archi-code/ddd]` | no | Media |
51
+ | T16 | ¿Los value objects se construyen en `toCommand` con `ValueObjectBuilder.build` (todos los campos a la vez) y no en `handle` ni en el controlador? | `[Fuera de los libros: convención de @archi-code/ddd]` | no | Media |
52
52
  | T17 | ¿Un repositorio genérico compartido por varios agregados, o un repositorio cuyo constructor recibe la clase del agregado o un mapper? Cada repositorio es de un agregado (`MongoOrderRepository` implementa los roles de `Order`), su constructor recibe solo las dependencias de su tecnología (`collection`, `pool`) y llama directamente a `toPrimitives()`/`fromPrimitives()` del agregado | `[Fuera de los libros: convención de @archi-code/ddd]` | no | Media |
53
53
  | T18 | ¿La conversión a o desde primitivos está fuera del agregado (un servicio de dominio o un mapper escrito a mano)? Es del agregado: `toPrimitives()` y `static fromPrimitives()`, que el repositorio llama directamente; un test de ida y vuelta por agregado comprueba que no se pierde ningún campo | `[Fuera de los libros: convención de @archi-code/ddd]` | no | Media |
54
54
  | T19 | ¿Un repositorio en memoria en `src/`? Es un doble de test del proyecto (`InMemoryOrderRepository`, ver `ddd-skills/ddd-repositorios-persistencia/references/tests-repositorios.md`): se mueve a `test/support/` | `[Fuera de los libros: convención de @archi-code/ddd]` | no | Baja |
@@ -17,7 +17,7 @@ export interface ProductPricing {
17
17
  ```
18
18
 
19
19
  ```ts
20
- import { InternalErrorException } from '@archi-code/ddd/exceptions';
20
+ import { InternalErrorException } from '@archi-code/ddd/infrastructure';
21
21
 
22
22
  interface LegacyArticleRow {
23
23
  article_code: string;
@@ -60,7 +60,7 @@ La idea de Evans de un **contexto burbuja** (bubble context): un pequeño contex
60
60
  ## Fase 3. Refactorizar el táctico dentro de cada contexto
61
61
 
62
62
  Orden recomendado `[Fuera de los libros, basado en dependencias entre reglas]`:
63
- 1. Separar dominio de infraestructura: el agregado expone `static fromPrimitives(PrimitiveTypes<X>)` y `toPrimitives()`, los repositorios implementan `AggregateFinder`/`AggregateSaver`/`AggregateRemover` de un solo agregado en `infrastructure/`, que llaman directamente a `x.toPrimitives()` y a `X.fromPrimitives(row).restoreVersion(row.version)`, con un constructor que recibe solo las dependencias de su tecnología (nunca la clase del agregado ni un mapper), sin decoradores del ORM ni imports de `/infrastructure` en el dominio. Un repositorio genérico compartido por varios agregados se parte en uno por agregado; un servicio de dominio o un mapper escrito a mano que convierte a primitivos se sustituye por `toPrimitives()`/`fromPrimitives()` del agregado, con un test de ida y vuelta por agregado; un repositorio en memoria que esté en `src/` se mueve a `test/support/` `[Fuera de los libros: convención de @archi-code/ddd]`.
63
+ 1. Separar dominio de infraestructura: el agregado expone `static fromPrimitives(PrimitiveTypes<X>)` y `toPrimitives()`, los repositorios implementan `AggregateFinder`/`AggregateSaver`/`AggregateRemover` de un solo agregado en `infrastructure/`, que llaman directamente a `x.toPrimitives()` y a `X.fromPrimitives(row.primitives)` y recuerdan la versión de cada agregado en un `WeakMap` propio (el agregado no la declara ni la mapea), con un constructor que recibe solo las dependencias de su tecnología (nunca la clase del agregado ni un mapper), sin decoradores del ORM ni imports de `/infrastructure` en el dominio. Un repositorio genérico compartido por varios agregados se parte en uno por agregado; un servicio de dominio o un mapper escrito a mano que convierte a primitivos se sustituye por `toPrimitives()`/`fromPrimitives()` del agregado, con un test de ida y vuelta por agregado; un repositorio en memoria que esté en `src/` se mueve a `test/support/` `[Fuera de los libros: convención de @archi-code/ddd]`.
64
64
  2. Introducir value objects para los primitivos más usados: `Money` como `Composite({ amount: Cents, currency: Currency })`, identificadores `class OrderId extends IdType<'OrderId'>` (la marca hace que no se confundan ids de agregados distintos), estados `extends Required(StatusType)` sobre `EnumType`.
65
65
  3. Sustituir setters por métodos de negocio con nombres del lenguaje (`ship`, `addLine`) que comprueban la invariante y llaman a `record()`, moviendo la lógica desde los casos de uso; partir los casos de uso CRUD (`UpdateOrder`) en uno por acción (`ShipOrder`, `AddOrderLine`).
66
66
  4. Partir agregados grandes con el procedimiento de `ddd-skills/ddd-agregados/GUIA.md` (plazos de consistencia).
@@ -8,10 +8,10 @@
8
8
 
9
9
  [Fuera de los libros: convención de @archi-code/ddd]
10
10
 
11
- * El paquete no trae capa anticorrupción, Open Host Service ni Published Language: se escriben en el proyecto sobre sus clases y puertos. El puerto hacia otro contexto (`ProductPricing`) es una interfaz en `domain/` que recibe y devuelve value objects (`Sku`, un `Composite` `SellableProduct`); adaptador (`CatalogHttpProductPricing`) y traductor (`CatalogTranslator`) van en `infrastructure/` y lanzan una `InfrastructureException` con código propio (`CAT001`), que `HttpErrorMapper` traduce (`new HttpErrorMapper({ statusByCode: { CAT001: 503 } })`).
12
- * Un webhook entrante es un punto de entrada en `interfaces/` que traduce el lenguaje del emisor a la entrada de un `UseCase` y responde con `HttpErrorMapper`.
13
- * Los mensajes entre contextos viajan como `SerializedEvent` plano (`{ eventName, eventVersion, eventId, aggregateId, occurredOn, ...datos }`) por un `EventChannel` (tu canal de broker o de outbox), no con el envoltorio de IDDD `{ notificationId, typeName, version, occurredOn, event }`: `eventName` hace de `typeName` (`'sales.order.OrderPlaced'`), `eventVersion` de `version` y `eventId` identifica el mensaje para deduplicar. No hay `notificationId` secuencial: si un feed REST lo necesita, lo asigna tu canal duradero.
14
- * Lo que sale se traduce al Published Language en infraestructura, con un `EventChannel` propio en el adaptador del broker (`ContractPublishingChannel` + `SalesContracts`, en `ddd-skills/ddd-eventos-dominio/references/publicacion-ts.md`); nunca se publica fuera del contexto el `toJSON()` de un evento interno tal cual.
11
+ * El paquete no trae capa anticorrupción, Open Host Service ni Published Language: se escriben en el proyecto sobre sus clases y puertos. El puerto hacia otro contexto (`ProductPricing`) es una interfaz en `domain/` que recibe y devuelve value objects (`Sku`, un `Composite` `SellableProduct`); adaptador (`CatalogHttpProductPricing`) y traductor (`CatalogTranslator`) van en `infrastructure/` y lanzan una `InfrastructureException` con código propio (`CAT001`), que el punto de entrada traduce por su código (`CAT001` → 503 en HTTP; sin traducción propia, `ExceptionClassifier.classify` la clasifica como `ExceptionKind.Internal`).
12
+ * Un webhook entrante es un punto de entrada en `interfaces/` que traduce el lenguaje del emisor a la entrada de un `UseCase` y responde según el `ExceptionKind` que devuelve `ExceptionClassifier.classify(error)` (de `@archi-code/ddd/infrastructure`).
13
+ * Los mensajes entre contextos viajan como `SerializedEvent` plano (`{ eventName, eventId, occurredOn, ...datos }`, con la identidad del agregado entre los datos, como `orderId`) por un `EventChannel` (tu canal de broker o de outbox), no con el envoltorio de IDDD `{ notificationId, typeName, version, occurredOn, event }`: `eventName` hace de `typeName` (`'sales.order.OrderPlaced'`) y también identifica la versión del contrato (una versión incompatible se publica con otro nombre, como `'sales.order.OrderPlacedV2'`), y `eventId` identifica el mensaje para deduplicar. No hay `notificationId` secuencial: si un feed REST lo necesita, lo asigna tu canal duradero.
14
+ * Lo que sale se traduce al Published Language en infraestructura, con un `EventChannel` propio en el adaptador del broker (`ContractPublishingChannel` + `SalesContracts`, en `ddd-skills/ddd-eventos-dominio/references/publicacion-ts.md`); nunca se publica fuera del contexto un evento interno tal cual (`{ ...event }`).
15
15
  * Lo que entra de otro contexto se lee con una clase propia `extends EventBase` que lleva el nombre publicado y solo los campos que este contexto usa; la factoría de `EventRegistry` es el traductor, y un `EventListener` con `@ListensTo(Evento, { consumer })` ejecuta un caso de uso, que valida cada campo con value objects en `toCommand`.
16
16
  * Los procesos entre contextos se encadenan con eventos y listeners, cada uno con su caso de uso.
17
17
 
@@ -72,7 +72,7 @@ Para cada evento que cruza un contexto, decide entre **evento enriquecido** (má
72
72
  * API REST que devuelve el agregado serializado con todos sus campos.
73
73
  * Una petición de usuario que atraviesa tres servicios en cadena de forma síncrona.
74
74
  * Un consumidor de eventos sin control de duplicados.
75
- * Un contexto publica hacia fuera el `toJSON()` de sus eventos internos, o un listener recibe la clase de evento de otro contexto importada de su código.
75
+ * Un contexto publica hacia fuera sus eventos internos tal cual (`{ ...event }`), o un listener recibe la clase de evento de otro contexto importada de su código.
76
76
  * "Shared Kernel" usado como excusa para compartir una librería de modelos que crece sin acuerdo [IDDD cap. 13].
77
77
  * El código copia sin traducir el modelo de un proveedor.
78
78
 
@@ -104,7 +104,7 @@ La regla "solo se vende lo activo con precio web" es una decisión de traducció
104
104
 
105
105
  ```ts
106
106
  // src/contexts/sales/infrastructure/catalog/CatalogHttpProductPricing.ts
107
- import { InfrastructureException } from '@archi-code/ddd/exceptions';
107
+ import { InfrastructureException } from '@archi-code/ddd/infrastructure';
108
108
  import type { ProductPricing, SellableProduct } from '../../domain/catalog/ProductPricing';
109
109
  import type { Sku } from '../../domain/order/OrderLine';
110
110
  import { CatalogTranslator } from './CatalogTranslator';
@@ -166,15 +166,14 @@ export class CatalogHttpProductPricing implements ProductPricing {
166
166
  Decisiones visibles:
167
167
  * Timeout explícito: la red falla y hay latencia [IDDD cap. 13].
168
168
  * Validación del contrato al entrar: si Catálogo rompe el esquema, el error se detecta en el borde y no dentro del modelo. Un valor inválido de Catálogo no llega al usuario como `ValidationException` de Ventas: el adaptador la envuelve en `CatalogContractViolation` con la original como `cause`.
169
- * Errores con nombre y código propios (`InfrastructureException` con `CAT001`, `CAT002`). El punto de entrada los traduce con `HttpErrorMapper`, que responde 500 por defecto a cualquier error de infraestructura; para avisar de que Catálogo no está disponible se configura por código: `new HttpErrorMapper({ statusByCode: { CAT001: 503 } })` (con 5xx el cuerpo lleva un mensaje genérico, nunca el del error).
169
+ * Errores con nombre y código propios (`InfrastructureException` con `CAT001`, `CAT002`). `ExceptionClassifier.classify(error)` clasifica cualquier error de infraestructura como `ExceptionKind.Internal` (500 en HTTP); para avisar de que Catálogo no está disponible, el punto de entrada traduce su código (`CAT001` → 503) antes de mirar el tipo (con 5xx el cuerpo lleva un mensaje genérico, nunca el del error).
170
170
 
171
171
  ### 5. Uso desde la capa de aplicación
172
172
 
173
173
  ```ts
174
174
  // src/contexts/sales/application/place-order/PlaceOrder.ts
175
- import { ValueObjects } from '@archi-code/ddd/domain';
176
175
  import type { AggregateSaver } from '@archi-code/ddd/domain';
177
- import { UseCase } from '@archi-code/ddd/application';
176
+ import { UseCase, ValueObjectBuilder } from '@archi-code/ddd/application';
178
177
  import type { Clock, EventBus } from '@archi-code/ddd/application';
179
178
  import type { ProductPricing } from '../../domain/catalog/ProductPricing';
180
179
  import { CustomerId } from '../../domain/customer/CustomerId';
@@ -207,12 +206,12 @@ export class PlaceOrder extends UseCase<PlaceOrderInput, PlaceOrderCommand, stri
207
206
  }
208
207
 
209
208
  protected toCommand(input: PlaceOrderInput): PlaceOrderCommand {
210
- return ValueObjects.build({
209
+ return ValueObjectBuilder.build({
211
210
  orderId: () => OrderId.generate(),
212
211
  customerId: () => new CustomerId(input.customerId),
213
212
  items: () =>
214
- ValueObjects.buildList(input.items, (item) =>
215
- ValueObjects.build({
213
+ ValueObjectBuilder.buildList(input.items, (item) =>
214
+ ValueObjectBuilder.build({
216
215
  sku: () => new Sku(item.sku),
217
216
  quantity: () => new Quantity(item.quantity),
218
217
  }),
@@ -246,16 +245,16 @@ export class PlaceOrder extends UseCase<PlaceOrderInput, PlaceOrderCommand, stri
246
245
  }
247
246
  ```
248
247
 
249
- El precio se copia en la línea del pedido: Ventas guarda el estado remoto **mínimo** que necesita y deja de depender de Catálogo para ese pedido [IDDD cap. 3]. La consulta a Catálogo va antes de crear y guardar el pedido; como la transacción solo existe dentro del `save` del repositorio, nunca queda una abierta mientras se espera a la red `[Fuera de los libros: convención de @archi-code/ddd]`. `ProductNotSellable` es una `DomainException` de Ventas con código propio (`ORD003`), que `HttpErrorMapper` responde con 422.
248
+ El precio se copia en la línea del pedido: Ventas guarda el estado remoto **mínimo** que necesita y deja de depender de Catálogo para ese pedido [IDDD cap. 3]. La consulta a Catálogo va antes de crear y guardar el pedido; como la transacción solo existe dentro del `save` del repositorio, nunca queda una abierta mientras se espera a la red `[Fuera de los libros: convención de @archi-code/ddd]`. `ProductNotSellable` es una `DomainException` de Ventas con código propio (`ORD003`), que `ExceptionClassifier` clasifica como `ExceptionKind.Invalid` (422 en HTTP).
250
249
 
251
250
  ## ACL con un webhook entrante
252
251
 
253
- Mismo patrón con un producto externo que llama a este contexto: el punto de entrada es el adaptador, la forma del mensaje es el contrato y la traducción produce la entrada de un caso de uso en el lenguaje local. Vive en `interfaces/` (como cualquier controlador) y solo conoce casos de uso y `HttpErrorMapper` [IDDD cap. 4]. El router ya ha comprobado la firma del emisor y la forma del cuerpo antes de llamarlo `[Fuera de los libros]`.
252
+ Mismo patrón con un producto externo que llama a este contexto: el punto de entrada es el adaptador, la forma del mensaje es el contrato y la traducción produce la entrada de un caso de uso en el lenguaje local. Vive en `interfaces/` (como cualquier controlador) y solo conoce casos de uso y `ExceptionClassifier` de `@archi-code/ddd/infrastructure` [IDDD cap. 4]. Traduce cada `ExceptionKind` a lo que entiende el emisor: lo que merece reintento (un conflicto, un error interno) responde 5xx para que la pasarela vuelva a enviarlo, y lo que no cambiaría al reintentar responde 4xx `[Fuera de los libros]`. El router ya ha comprobado la firma del emisor y la forma del cuerpo antes de llamarlo `[Fuera de los libros]`.
254
253
 
255
254
  ```ts
256
255
  // src/contexts/payments/interfaces/http/PaymentGatewayWebhook.ts
257
- import { HttpErrorMapper } from '@archi-code/ddd/infrastructure';
258
- import type { HttpErrorBody } from '@archi-code/ddd/infrastructure';
256
+ import { ExceptionClassifier, ExceptionKind } from '@archi-code/ddd/infrastructure';
257
+ import type { Logger } from '@archi-code/ddd/application';
259
258
  import type { RecordPaymentAuthorization } from '../../application/record-payment-authorization/RecordPaymentAuthorization';
260
259
  import type { RecordPaymentRejection } from '../../application/record-payment-rejection/RecordPaymentRejection';
261
260
 
@@ -268,14 +267,26 @@ export interface GatewayEvent {
268
267
  metadata: { paymentId: string };
269
268
  }
270
269
 
270
+ function statusOf(kind: ExceptionKind): number {
271
+ switch (kind) {
272
+ case ExceptionKind.Conflict:
273
+ case ExceptionKind.Internal:
274
+ return 503;
275
+ case ExceptionKind.NotFound:
276
+ return 404;
277
+ default:
278
+ return 422;
279
+ }
280
+ }
281
+
271
282
  export class PaymentGatewayWebhook {
272
283
  constructor(
273
284
  private readonly recordAuthorization: RecordPaymentAuthorization,
274
285
  private readonly recordRejection: RecordPaymentRejection,
275
- private readonly errors: HttpErrorMapper,
286
+ private readonly logger: Logger,
276
287
  ) {}
277
288
 
278
- async receive(event: GatewayEvent): Promise<{ status: number; body?: HttpErrorBody }> {
289
+ async receive(event: GatewayEvent): Promise<{ status: number }> {
279
290
  try {
280
291
  switch (event.type) {
281
292
  case 'charge.authorized':
@@ -294,7 +305,11 @@ export class PaymentGatewayWebhook {
294
305
  }
295
306
  return { status: 204 };
296
307
  } catch (error) {
297
- return this.errors.map(error);
308
+ const kind = ExceptionClassifier.classify(error);
309
+ if (kind === ExceptionKind.Internal) {
310
+ this.logger.error('payment_webhook_failed', { type: event.type, error });
311
+ }
312
+ return { status: statusOf(kind) };
298
313
  }
299
314
  }
300
315
  }
@@ -315,11 +330,11 @@ export class StockReserved extends EventBase {
315
330
  static readonly eventName = 'warehouse.stock.StockReserved';
316
331
 
317
332
  constructor(
318
- reservationId: string,
333
+ readonly reservationId: string,
319
334
  readonly orderId: string,
320
335
  metadata?: EventMetadata,
321
336
  ) {
322
- super(reservationId, metadata);
337
+ super(metadata);
323
338
  }
324
339
  }
325
340
  ```
@@ -339,7 +354,7 @@ export class RegisterStockReservationOnStockReserved extends EventListener<Stock
339
354
  async handle(event: StockReserved): Promise<void> {
340
355
  await this.registerStockReservation.execute({
341
356
  orderId: event.orderId,
342
- reservationId: event.aggregateId,
357
+ reservationId: event.reservationId,
343
358
  });
344
359
  }
345
360
  }
@@ -351,7 +366,7 @@ import { DomainEventBus, EventRegistry } from '@archi-code/ddd/infrastructure';
351
366
 
352
367
  const warehouseEvents = new EventRegistry<StockReserved>().register(
353
368
  StockReserved,
354
- (json) => new StockReserved(json.aggregateId, json.orderId as string, json),
369
+ (json) => new StockReserved(json.reservationId as string, json.orderId as string, json),
355
370
  );
356
371
  const warehouseBus = new DomainEventBus<StockReserved>(
357
372
  new RabbitMqEventChannel(amqpChannel, 'warehouse.published'),
@@ -40,9 +40,8 @@ El evento de Almacén llega por un `EventChannel` a un listener (`RegisterStockR
40
40
 
41
41
  ```ts
42
42
  // src/contexts/sales/application/register-stock-reservation/RegisterStockReservation.ts
43
- import { ValueObjects } from '@archi-code/ddd/domain';
44
43
  import type { AggregateFinder, AggregateSaver } from '@archi-code/ddd/domain';
45
- import { UseCase } from '@archi-code/ddd/application';
44
+ import { UseCase, ValueObjectBuilder } from '@archi-code/ddd/application';
46
45
  import type { Clock, EventBus } from '@archi-code/ddd/application';
47
46
  import { Order, OrderEvent, OrderId, ReservationId } from '../../domain/order/Order';
48
47
 
@@ -70,7 +69,7 @@ export class RegisterStockReservation extends UseCase<
70
69
  }
71
70
 
72
71
  protected toCommand(input: RegisterStockReservationInput): RegisterStockReservationCommand {
73
- return ValueObjects.build({
72
+ return ValueObjectBuilder.build({
74
73
  orderId: () => new OrderId(input.orderId),
75
74
  reservationId: () => new ReservationId(input.reservationId),
76
75
  });
@@ -109,14 +108,12 @@ Aplicar dos veces "reserva registrada" no cambia nada la segunda vez ni graba un
109
108
  * Los eventos y notificaciones llevan versión. Si se diseñan con cuidado, muchos consumidores pueden leer cualquier versión como si fuera la 1 y no necesitan cambiar [IDDD cap. 13].
110
109
  * Los value objects estables (identificadores) se pueden incluir en el evento; quedan congelados en la estructura [IDDD cap. 13]. Con el paquete, el payload lleva solo primitivos tomados de los value objects (`orderId: string`), también los identificadores `[Fuera de los libros: convención de @archi-code/ddd]`.
111
110
 
112
- La notificación de IDDD envuelve el evento en `{ notificationId, typeName, version, occurredOn, event }` [IDDD cap. 13]. Con el paquete, el mensaje publicado es un `SerializedEvent` plano, con el nombre y la versión del Published Language `[Fuera de los libros: convención de @archi-code/ddd]`:
111
+ La notificación de IDDD envuelve el evento en `{ notificationId, typeName, version, occurredOn, event }` [IDDD cap. 13]. Con el paquete, el mensaje publicado es un `SerializedEvent` plano, con el nombre del Published Language, que también identifica su versión `[Fuera de los libros: convención de @archi-code/ddd]`:
113
112
 
114
113
  ```json
115
114
  {
116
115
  "eventName": "sales.order.OrderPlaced",
117
- "eventVersion": 1,
118
116
  "eventId": "6f1c2a3b-8d4e-4f5a-9b6c-7d8e9f0a1b2c",
119
- "aggregateId": "9b2f6c1e-4d3a-4f8b-a1c2-3e4d5f6a7b8c",
120
117
  "occurredOn": "2026-10-18T10:15:00.000Z",
121
118
  "orderId": "9b2f6c1e-4d3a-4f8b-a1c2-3e4d5f6a7b8c",
122
119
  "customerId": "1c5e7a90-2b3d-4e6f-8a1b-2c3d4e5f6a7b",
@@ -127,7 +124,7 @@ La notificación de IDDD envuelve el evento en `{ notificationId, typeName, vers
127
124
  }
128
125
  ```
129
126
 
130
- `eventName` hace de `typeName`, `eventVersion` de `version` y `eventId` es el identificador con el que el consumidor deduplica. No hay `notificationId` secuencial: si publicas un feed REST de notificaciones, la secuencia la asigna tu canal duradero (la fila del outbox).
127
+ `eventName` hace de `typeName` y de `version`: el mensaje no lleva campo de versión, y una versión incompatible del contrato se publica con otro nombre (`sales.order.OrderPlacedV2`) mientras los consumidores migran. `eventId` es el identificador con el que el consumidor deduplica. No hay `notificationId` secuencial: si publicas un feed REST de notificaciones, la secuencia la asigna tu canal duradero (la fila del outbox).
131
128
 
132
129
  El evento interno no sale tal cual. La traducción al Published Language vive en infraestructura, en el adaptador que habla con el broker: un `EventChannel` del proyecto (`ContractPublishingChannel`) que pasa cada `SerializedEvent` por `SalesContracts` (qué eventos salen y con qué contrato, uno por versión publicada, con `messageId` = `eventId`) y entrega todos al canal interno. El código está en `ddd-skills/ddd-eventos-dominio/references/publicacion-ts.md` ("Lenguaje publicado en el adaptador del broker"); los casos de uso siguen viendo solo `EventBus`, y un evento sin contrato es interno y no sale del contexto. Para no perder mensajes, la publicación al broker la hace el reenviador del outbox después del commit (ver "Publicar de forma fiable").
133
130
 
@@ -9,7 +9,7 @@
9
9
  [Fuera de los libros: convención de @archi-code/ddd]
10
10
 
11
11
  * El paquete no trae event store, repositorios con Event Sourcing, snapshots ni proyecciones con checkpoint: se construyen en la infraestructura del proyecto sobre sus clases, y solo si la tabla de decisión de esta guía lo justifica.
12
- * La `version` de `AggregateRoot` crece 1 por guardado, no por evento: un agregado con Event Sourcing lleva su propia versión de stream (`streamVersion()`), se reconstruye con `fromHistory` a partir de los eventos que reconstruye `EventRegistry` y no usa `toPrimitives()`/`fromPrimitives()`.
12
+ * `AggregateRoot` no lleva versión: la de concurrencia optimista de los repositorios de instantáneas la recuerda cada repositorio en un `WeakMap` y crece 1 por guardado, no por evento. Un agregado con Event Sourcing lleva su propia versión de stream (`streamVersion()`), se reconstruye con `fromHistory` a partir de los eventos que reconstruye `EventRegistry` y no usa `toPrimitives()`/`fromPrimitives()`.
13
13
  * El contrato del stream (`load`/`append`) lo declara el dominio y no es `AggregateFinder`/`AggregateSaver`; su adaptador es responsable de la persistencia: anexa los eventos de un `append` en una sola escritura atómica con la versión esperada y lanza `ConcurrencyException` si otro escribió antes. La aplicación no abre transacciones; el reintento ante `ConcurrencyException` va en el `handle` del caso de uso, cada intento vuelve a cargar y vuelve a anexar.
14
14
  * Una proyección es un `EventListener` con `@ListensTo(Event, { consumer })` que llama a un puerto de proyección (no a un caso de uso), idempotente por `eventId`; un `EventChannel` no guarda historial, así que reconstruirla exige un log de eventos propio (con Event Sourcing, el propio event store).
15
15
  * Una consulta de lectura es una interfaz en `application/` implementada en infraestructura directamente sobre la base de datos.
@@ -82,7 +82,7 @@ En Event Sourcing los eventos sirven para persistir el agregado **y** para comun
82
82
  * Eventos tipo `OrderUpdated` con el estado completo en el stream.
83
83
  * Proyecciones que no se pueden reconstruir (dependen de estado externo no reproducible).
84
84
  * Proyecciones no idempotentes (sin comprobar el `eventId` ya aplicado).
85
- * Un agregado con Event Sourcing que usa la `version` de `AggregateRoot` como versión de stream o se guarda como instantánea con `toPrimitives()`.
85
+ * Un agregado con Event Sourcing que usa la versión de guardados de un repositorio de instantáneas como versión de stream o se guarda como instantánea con `toPrimitives()`.
86
86
  * Agregados que consultan read models para proteger invariantes (las invariantes viven en el agregado).
87
87
  * Datos personales en eventos sin estrategia de supresión.
88
88
  * CQRS con dos bases de datos para un CRUD de cinco pantallas.