@archi-code/ddd 0.0.0-stage → 3.0.0-alpha.1

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 (312) hide show
  1. package/LICENSE +46 -0
  2. package/README.md +2693 -2
  3. package/SKILL.md +104 -0
  4. package/ddd-skills/archi-code-ddd/imports.md +84 -0
  5. package/ddd-skills/archi-code-ddd/layers.md +299 -0
  6. package/ddd-skills/archi-code-ddd/patterns.md +676 -0
  7. package/ddd-skills/archi-code-ddd/what-not-to-do.md +189 -0
  8. package/ddd-skills/archi-code-ddd/where-each-class-goes.md +63 -0
  9. package/ddd-skills/ddd-agregados/GUIA.md +114 -0
  10. package/ddd-skills/ddd-agregados/references/consistencia-eventual.md +159 -0
  11. package/ddd-skills/ddd-agregados/references/ejemplo-ecommerce.md +52 -0
  12. package/ddd-skills/ddd-agregados/references/fabricas.md +196 -0
  13. package/ddd-skills/ddd-agregados/references/implementacion-ts.md +711 -0
  14. package/ddd-skills/ddd-arquitectura/GUIA.md +80 -0
  15. package/ddd-skills/ddd-arquitectura/assets/dependency-cruiser.cjs +92 -0
  16. package/ddd-skills/ddd-arquitectura/references/estilos-adicionales.md +51 -0
  17. package/ddd-skills/ddd-arquitectura/references/estructura-hexagonal-ts.md +352 -0
  18. package/ddd-skills/ddd-arquitectura/references/modulos.md +48 -0
  19. package/ddd-skills/ddd-auditoria-refactorizacion/GUIA.md +85 -0
  20. package/ddd-skills/ddd-auditoria-refactorizacion/references/ejemplo-auditoria.md +316 -0
  21. package/ddd-skills/ddd-auditoria-refactorizacion/references/lista-comprobacion.md +54 -0
  22. package/ddd-skills/ddd-auditoria-refactorizacion/references/plan-migracion.md +73 -0
  23. package/ddd-skills/ddd-auditoria-refactorizacion/scripts/scan_ddd_smells.py +264 -0
  24. package/ddd-skills/ddd-context-mapping/GUIA.md +83 -0
  25. package/ddd-skills/ddd-context-mapping/references/anticorrupcion-ts.md +404 -0
  26. package/ddd-skills/ddd-context-mapping/references/estilos-integracion.md +155 -0
  27. package/ddd-skills/ddd-event-sourcing-cqrs/GUIA.md +92 -0
  28. package/ddd-skills/ddd-event-sourcing-cqrs/references/event-sourcing-ts.md +471 -0
  29. package/ddd-skills/ddd-event-sourcing-cqrs/references/proyecciones-cqrs-ts.md +285 -0
  30. package/ddd-skills/ddd-eventos-dominio/GUIA.md +106 -0
  31. package/ddd-skills/ddd-eventos-dominio/references/diseno-eventos.md +131 -0
  32. package/ddd-skills/ddd-eventos-dominio/references/ejemplo-ecommerce.md +240 -0
  33. package/ddd-skills/ddd-eventos-dominio/references/publicacion-ts.md +526 -0
  34. package/ddd-skills/ddd-orquestador/GUIA.md +40 -0
  35. package/ddd-skills/ddd-orquestador/references/chuleta-reglas.md +35 -0
  36. package/ddd-skills/ddd-repositorios-persistencia/GUIA.md +121 -0
  37. package/ddd-skills/ddd-repositorios-persistencia/references/implementacion-ts.md +296 -0
  38. package/ddd-skills/ddd-repositorios-persistencia/references/tests-repositorios.md +177 -0
  39. package/ddd-skills/ddd-repositorios-persistencia/references/transacciones-ts.md +294 -0
  40. package/ddd-skills/ddd-servicios-capa-aplicacion/GUIA.md +99 -0
  41. package/ddd-skills/ddd-servicios-capa-aplicacion/references/capa-aplicacion-ts.md +264 -0
  42. package/ddd-skills/ddd-servicios-capa-aplicacion/references/ejemplo-ecommerce.md +324 -0
  43. package/ddd-skills/ddd-servicios-capa-aplicacion/references/servicios-dominio-ts.md +191 -0
  44. package/ddd-skills/ddd-value-objects-entidades/GUIA.md +118 -0
  45. package/ddd-skills/ddd-value-objects-entidades/references/ejemplo-ecommerce.md +87 -0
  46. package/ddd-skills/ddd-value-objects-entidades/references/entidades-ts.md +220 -0
  47. package/ddd-skills/ddd-value-objects-entidades/references/persistencia-valores.md +88 -0
  48. package/ddd-skills/ddd-value-objects-entidades/references/specification-y-diseno-flexible.md +119 -0
  49. package/ddd-skills/ddd-value-objects-entidades/references/validacion.md +96 -0
  50. package/ddd-skills/ddd-value-objects-entidades/references/value-objects-ts.md +377 -0
  51. package/dist/cjs/application/event/event-bus.d.ts +4 -0
  52. package/dist/cjs/application/event/event-bus.js +2 -0
  53. package/dist/cjs/application/event/event-listener.d.ts +4 -0
  54. package/dist/cjs/application/event/event-listener.js +6 -0
  55. package/dist/cjs/application/event/listens-to.d.ts +15 -0
  56. package/dist/cjs/application/event/listens-to.js +28 -0
  57. package/dist/cjs/application/index.d.ts +7 -0
  58. package/dist/cjs/application/index.js +9 -0
  59. package/dist/cjs/application/ports/clock.d.ts +3 -0
  60. package/dist/cjs/application/ports/clock.js +2 -0
  61. package/dist/cjs/application/ports/logger.d.ts +8 -0
  62. package/dist/cjs/application/ports/logger.js +2 -0
  63. package/dist/cjs/application/use-case/use-case.d.ts +6 -0
  64. package/dist/cjs/application/use-case/use-case.js +24 -0
  65. package/dist/cjs/domain/aggregate/aggregate-root.d.ts +14 -0
  66. package/dist/cjs/domain/aggregate/aggregate-root.js +33 -0
  67. package/dist/cjs/domain/entity/entity.d.ts +9 -0
  68. package/dist/cjs/domain/entity/entity.js +19 -0
  69. package/dist/cjs/domain/event/event-base.d.ts +29 -0
  70. package/dist/cjs/domain/event/event-base.js +51 -0
  71. package/dist/cjs/domain/index.d.ts +10 -0
  72. package/dist/cjs/domain/index.js +24 -0
  73. package/dist/cjs/domain/primitive/primitive-types.d.ts +22 -0
  74. package/dist/cjs/domain/primitive/primitive-types.js +2 -0
  75. package/dist/cjs/domain/repository/aggregate-finder.d.ts +4 -0
  76. package/dist/cjs/domain/repository/aggregate-finder.js +2 -0
  77. package/dist/cjs/domain/repository/aggregate-remover.d.ts +4 -0
  78. package/dist/cjs/domain/repository/aggregate-remover.js +2 -0
  79. package/dist/cjs/domain/repository/aggregate-saver.d.ts +4 -0
  80. package/dist/cjs/domain/repository/aggregate-saver.js +2 -0
  81. package/dist/cjs/domain/specification/specification.d.ts +9 -0
  82. package/dist/cjs/domain/specification/specification.js +44 -0
  83. package/dist/cjs/domain/type/abstract-type.d.ts +20 -0
  84. package/dist/cjs/domain/type/abstract-type.js +69 -0
  85. package/dist/cjs/domain/type/array-type.d.ts +16 -0
  86. package/dist/cjs/domain/type/array-type.js +65 -0
  87. package/dist/cjs/domain/type/boolean-type.d.ts +4 -0
  88. package/dist/cjs/domain/type/boolean-type.js +16 -0
  89. package/dist/cjs/domain/type/composite/composite-field.d.ts +19 -0
  90. package/dist/cjs/domain/type/composite/composite-field.js +63 -0
  91. package/dist/cjs/domain/type/composite/composite.d.ts +26 -0
  92. package/dist/cjs/domain/type/composite/composite.js +55 -0
  93. package/dist/cjs/domain/type/date-type.d.ts +18 -0
  94. package/dist/cjs/domain/type/date-type.js +35 -0
  95. package/dist/cjs/domain/type/enum-type.d.ts +7 -0
  96. package/dist/cjs/domain/type/enum-type.js +30 -0
  97. package/dist/cjs/domain/type/equality/value-equality.d.ts +7 -0
  98. package/dist/cjs/domain/type/equality/value-equality.js +41 -0
  99. package/dist/cjs/domain/type/id-type.d.ts +7 -0
  100. package/dist/cjs/domain/type/id-type.js +17 -0
  101. package/dist/cjs/domain/type/immutable/immutable-date.d.ts +4 -0
  102. package/dist/cjs/domain/type/immutable/immutable-date.js +37 -0
  103. package/dist/cjs/domain/type/immutable/immutable.d.ts +4 -0
  104. package/dist/cjs/domain/type/immutable/immutable.js +19 -0
  105. package/dist/cjs/domain/type/index.d.ts +17 -0
  106. package/dist/cjs/domain/type/index.js +33 -0
  107. package/dist/cjs/domain/type/json-type.d.ts +5 -0
  108. package/dist/cjs/domain/type/json-type.js +17 -0
  109. package/dist/cjs/domain/type/number-type.d.ts +4 -0
  110. package/dist/cjs/domain/type/number-type.js +16 -0
  111. package/dist/cjs/domain/type/required/required-mark.d.ts +9 -0
  112. package/dist/cjs/domain/type/required/required-mark.js +13 -0
  113. package/dist/cjs/domain/type/required/required.d.ts +7 -0
  114. package/dist/cjs/domain/type/required/required.js +21 -0
  115. package/dist/cjs/domain/type/string-type.d.ts +4 -0
  116. package/dist/cjs/domain/type/string-type.js +16 -0
  117. package/dist/cjs/domain/type/uuid-type.d.ts +7 -0
  118. package/dist/cjs/domain/type/uuid-type.js +27 -0
  119. package/dist/cjs/domain/type/value-objects/value-objects.d.ts +16 -0
  120. package/dist/cjs/domain/type/value-objects/value-objects.js +70 -0
  121. package/dist/cjs/exceptions/abstract.exception.d.ts +23 -0
  122. package/dist/cjs/exceptions/abstract.exception.js +66 -0
  123. package/dist/cjs/exceptions/application/aggregate-not-found.exception.d.ts +5 -0
  124. package/dist/cjs/exceptions/application/aggregate-not-found.exception.js +17 -0
  125. package/dist/cjs/exceptions/application/application.exception.d.ts +6 -0
  126. package/dist/cjs/exceptions/application/application.exception.js +17 -0
  127. package/dist/cjs/exceptions/application/concurrency.exception.d.ts +5 -0
  128. package/dist/cjs/exceptions/application/concurrency.exception.js +17 -0
  129. package/dist/cjs/exceptions/application/not-allowed.exception.d.ts +5 -0
  130. package/dist/cjs/exceptions/application/not-allowed.exception.js +17 -0
  131. package/dist/cjs/exceptions/brand/exception-brand.d.ts +6 -0
  132. package/dist/cjs/exceptions/brand/exception-brand.js +29 -0
  133. package/dist/cjs/exceptions/domain/domain.exception.d.ts +6 -0
  134. package/dist/cjs/exceptions/domain/domain.exception.js +17 -0
  135. package/dist/cjs/exceptions/domain/required-value.exception.d.ts +4 -0
  136. package/dist/cjs/exceptions/domain/required-value.exception.js +21 -0
  137. package/dist/cjs/exceptions/domain/type-primitive.exception.d.ts +4 -0
  138. package/dist/cjs/exceptions/domain/type-primitive.exception.js +21 -0
  139. package/dist/cjs/exceptions/domain/validation.exception.d.ts +14 -0
  140. package/dist/cjs/exceptions/domain/validation.exception.js +23 -0
  141. package/dist/cjs/exceptions/exception-code.d.ts +15 -0
  142. package/dist/cjs/exceptions/exception-code.js +19 -0
  143. package/dist/cjs/exceptions/index.d.ts +14 -0
  144. package/dist/cjs/exceptions/index.js +30 -0
  145. package/dist/cjs/exceptions/infrastructure/event-delivery.exception.d.ts +5 -0
  146. package/dist/cjs/exceptions/infrastructure/event-delivery.exception.js +20 -0
  147. package/dist/cjs/exceptions/infrastructure/infrastructure.exception.d.ts +6 -0
  148. package/dist/cjs/exceptions/infrastructure/infrastructure.exception.js +17 -0
  149. package/dist/cjs/exceptions/infrastructure/internal-error.exception.d.ts +5 -0
  150. package/dist/cjs/exceptions/infrastructure/internal-error.exception.js +17 -0
  151. package/dist/cjs/exceptions/infrastructure/unknown-event.exception.d.ts +4 -0
  152. package/dist/cjs/exceptions/infrastructure/unknown-event.exception.js +19 -0
  153. package/dist/cjs/exceptions/received/received-value.d.ts +4 -0
  154. package/dist/cjs/exceptions/received/received-value.js +13 -0
  155. package/dist/cjs/infrastructure/clock/fixed-clock.d.ts +8 -0
  156. package/dist/cjs/infrastructure/clock/fixed-clock.js +19 -0
  157. package/dist/cjs/infrastructure/clock/system-clock.d.ts +4 -0
  158. package/dist/cjs/infrastructure/clock/system-clock.js +9 -0
  159. package/dist/cjs/infrastructure/event/domain-event-bus.d.ts +16 -0
  160. package/dist/cjs/infrastructure/event/domain-event-bus.js +60 -0
  161. package/dist/cjs/infrastructure/event/event-channel.d.ts +9 -0
  162. package/dist/cjs/infrastructure/event/event-channel.js +2 -0
  163. package/dist/cjs/infrastructure/event/event-registry.d.ts +9 -0
  164. package/dist/cjs/infrastructure/event/event-registry.js +43 -0
  165. package/dist/cjs/infrastructure/event/in-memory/emitter.d.ts +11 -0
  166. package/dist/cjs/infrastructure/event/in-memory/emitter.js +54 -0
  167. package/dist/cjs/infrastructure/event/in-memory/in-memory-event-channel.d.ts +16 -0
  168. package/dist/cjs/infrastructure/event/in-memory/in-memory-event-channel.js +51 -0
  169. package/dist/cjs/infrastructure/http/http-error-mapper.d.ts +23 -0
  170. package/dist/cjs/infrastructure/http/http-error-mapper.js +63 -0
  171. package/dist/cjs/infrastructure/index.d.ts +16 -0
  172. package/dist/cjs/infrastructure/index.js +21 -0
  173. package/dist/cjs/infrastructure/logging/console-logger.d.ts +17 -0
  174. package/dist/cjs/infrastructure/logging/console-logger.js +37 -0
  175. package/dist/cjs/infrastructure/logging/in-memory-logger.d.ts +15 -0
  176. package/dist/cjs/infrastructure/logging/in-memory-logger.js +27 -0
  177. package/dist/cjs/infrastructure/logging/log-serializer.d.ts +7 -0
  178. package/dist/cjs/infrastructure/logging/log-serializer.js +59 -0
  179. package/dist/cjs/infrastructure/logging/silent-logger.d.ts +7 -0
  180. package/dist/cjs/infrastructure/logging/silent-logger.js +18 -0
  181. package/dist/esm/application/event/event-bus.d.ts +4 -0
  182. package/dist/esm/application/event/event-bus.js +1 -0
  183. package/dist/esm/application/event/event-listener.d.ts +4 -0
  184. package/dist/esm/application/event/event-listener.js +2 -0
  185. package/dist/esm/application/event/listens-to.d.ts +15 -0
  186. package/dist/esm/application/event/listens-to.js +25 -0
  187. package/dist/esm/application/index.d.ts +7 -0
  188. package/dist/esm/application/index.js +3 -0
  189. package/dist/esm/application/ports/clock.d.ts +3 -0
  190. package/dist/esm/application/ports/clock.js +1 -0
  191. package/dist/esm/application/ports/logger.d.ts +8 -0
  192. package/dist/esm/application/ports/logger.js +1 -0
  193. package/dist/esm/application/use-case/use-case.d.ts +6 -0
  194. package/dist/esm/application/use-case/use-case.js +20 -0
  195. package/dist/esm/domain/aggregate/aggregate-root.d.ts +14 -0
  196. package/dist/esm/domain/aggregate/aggregate-root.js +29 -0
  197. package/dist/esm/domain/entity/entity.d.ts +9 -0
  198. package/dist/esm/domain/entity/entity.js +15 -0
  199. package/dist/esm/domain/event/event-base.d.ts +29 -0
  200. package/dist/esm/domain/event/event-base.js +47 -0
  201. package/dist/esm/domain/index.d.ts +10 -0
  202. package/dist/esm/domain/index.js +6 -0
  203. package/dist/esm/domain/primitive/primitive-types.d.ts +22 -0
  204. package/dist/esm/domain/primitive/primitive-types.js +1 -0
  205. package/dist/esm/domain/repository/aggregate-finder.d.ts +4 -0
  206. package/dist/esm/domain/repository/aggregate-finder.js +1 -0
  207. package/dist/esm/domain/repository/aggregate-remover.d.ts +4 -0
  208. package/dist/esm/domain/repository/aggregate-remover.js +1 -0
  209. package/dist/esm/domain/repository/aggregate-saver.d.ts +4 -0
  210. package/dist/esm/domain/repository/aggregate-saver.js +1 -0
  211. package/dist/esm/domain/specification/specification.d.ts +9 -0
  212. package/dist/esm/domain/specification/specification.js +40 -0
  213. package/dist/esm/domain/type/abstract-type.d.ts +20 -0
  214. package/dist/esm/domain/type/abstract-type.js +65 -0
  215. package/dist/esm/domain/type/array-type.d.ts +16 -0
  216. package/dist/esm/domain/type/array-type.js +61 -0
  217. package/dist/esm/domain/type/boolean-type.d.ts +4 -0
  218. package/dist/esm/domain/type/boolean-type.js +12 -0
  219. package/dist/esm/domain/type/composite/composite-field.d.ts +19 -0
  220. package/dist/esm/domain/type/composite/composite-field.js +59 -0
  221. package/dist/esm/domain/type/composite/composite.d.ts +26 -0
  222. package/dist/esm/domain/type/composite/composite.js +52 -0
  223. package/dist/esm/domain/type/date-type.d.ts +18 -0
  224. package/dist/esm/domain/type/date-type.js +29 -0
  225. package/dist/esm/domain/type/enum-type.d.ts +7 -0
  226. package/dist/esm/domain/type/enum-type.js +26 -0
  227. package/dist/esm/domain/type/equality/value-equality.d.ts +7 -0
  228. package/dist/esm/domain/type/equality/value-equality.js +37 -0
  229. package/dist/esm/domain/type/id-type.d.ts +7 -0
  230. package/dist/esm/domain/type/id-type.js +13 -0
  231. package/dist/esm/domain/type/immutable/immutable-date.d.ts +4 -0
  232. package/dist/esm/domain/type/immutable/immutable-date.js +33 -0
  233. package/dist/esm/domain/type/immutable/immutable.d.ts +4 -0
  234. package/dist/esm/domain/type/immutable/immutable.js +15 -0
  235. package/dist/esm/domain/type/index.d.ts +17 -0
  236. package/dist/esm/domain/type/index.js +14 -0
  237. package/dist/esm/domain/type/json-type.d.ts +5 -0
  238. package/dist/esm/domain/type/json-type.js +13 -0
  239. package/dist/esm/domain/type/number-type.d.ts +4 -0
  240. package/dist/esm/domain/type/number-type.js +12 -0
  241. package/dist/esm/domain/type/required/required-mark.d.ts +9 -0
  242. package/dist/esm/domain/type/required/required-mark.js +9 -0
  243. package/dist/esm/domain/type/required/required.d.ts +7 -0
  244. package/dist/esm/domain/type/required/required.js +18 -0
  245. package/dist/esm/domain/type/string-type.d.ts +4 -0
  246. package/dist/esm/domain/type/string-type.js +12 -0
  247. package/dist/esm/domain/type/uuid-type.d.ts +7 -0
  248. package/dist/esm/domain/type/uuid-type.js +23 -0
  249. package/dist/esm/domain/type/value-objects/value-objects.d.ts +16 -0
  250. package/dist/esm/domain/type/value-objects/value-objects.js +66 -0
  251. package/dist/esm/exceptions/abstract.exception.d.ts +23 -0
  252. package/dist/esm/exceptions/abstract.exception.js +62 -0
  253. package/dist/esm/exceptions/application/aggregate-not-found.exception.d.ts +5 -0
  254. package/dist/esm/exceptions/application/aggregate-not-found.exception.js +13 -0
  255. package/dist/esm/exceptions/application/application.exception.d.ts +6 -0
  256. package/dist/esm/exceptions/application/application.exception.js +13 -0
  257. package/dist/esm/exceptions/application/concurrency.exception.d.ts +5 -0
  258. package/dist/esm/exceptions/application/concurrency.exception.js +13 -0
  259. package/dist/esm/exceptions/application/not-allowed.exception.d.ts +5 -0
  260. package/dist/esm/exceptions/application/not-allowed.exception.js +13 -0
  261. package/dist/esm/exceptions/brand/exception-brand.d.ts +6 -0
  262. package/dist/esm/exceptions/brand/exception-brand.js +25 -0
  263. package/dist/esm/exceptions/domain/domain.exception.d.ts +6 -0
  264. package/dist/esm/exceptions/domain/domain.exception.js +13 -0
  265. package/dist/esm/exceptions/domain/required-value.exception.d.ts +4 -0
  266. package/dist/esm/exceptions/domain/required-value.exception.js +17 -0
  267. package/dist/esm/exceptions/domain/type-primitive.exception.d.ts +4 -0
  268. package/dist/esm/exceptions/domain/type-primitive.exception.js +17 -0
  269. package/dist/esm/exceptions/domain/validation.exception.d.ts +14 -0
  270. package/dist/esm/exceptions/domain/validation.exception.js +19 -0
  271. package/dist/esm/exceptions/exception-code.d.ts +15 -0
  272. package/dist/esm/exceptions/exception-code.js +16 -0
  273. package/dist/esm/exceptions/index.d.ts +14 -0
  274. package/dist/esm/exceptions/index.js +14 -0
  275. package/dist/esm/exceptions/infrastructure/event-delivery.exception.d.ts +5 -0
  276. package/dist/esm/exceptions/infrastructure/event-delivery.exception.js +16 -0
  277. package/dist/esm/exceptions/infrastructure/infrastructure.exception.d.ts +6 -0
  278. package/dist/esm/exceptions/infrastructure/infrastructure.exception.js +13 -0
  279. package/dist/esm/exceptions/infrastructure/internal-error.exception.d.ts +5 -0
  280. package/dist/esm/exceptions/infrastructure/internal-error.exception.js +13 -0
  281. package/dist/esm/exceptions/infrastructure/unknown-event.exception.d.ts +4 -0
  282. package/dist/esm/exceptions/infrastructure/unknown-event.exception.js +15 -0
  283. package/dist/esm/exceptions/received/received-value.d.ts +4 -0
  284. package/dist/esm/exceptions/received/received-value.js +9 -0
  285. package/dist/esm/infrastructure/clock/fixed-clock.d.ts +8 -0
  286. package/dist/esm/infrastructure/clock/fixed-clock.js +15 -0
  287. package/dist/esm/infrastructure/clock/system-clock.d.ts +4 -0
  288. package/dist/esm/infrastructure/clock/system-clock.js +5 -0
  289. package/dist/esm/infrastructure/event/domain-event-bus.d.ts +16 -0
  290. package/dist/esm/infrastructure/event/domain-event-bus.js +56 -0
  291. package/dist/esm/infrastructure/event/event-channel.d.ts +9 -0
  292. package/dist/esm/infrastructure/event/event-channel.js +1 -0
  293. package/dist/esm/infrastructure/event/event-registry.d.ts +9 -0
  294. package/dist/esm/infrastructure/event/event-registry.js +39 -0
  295. package/dist/esm/infrastructure/event/in-memory/emitter.d.ts +11 -0
  296. package/dist/esm/infrastructure/event/in-memory/emitter.js +50 -0
  297. package/dist/esm/infrastructure/event/in-memory/in-memory-event-channel.d.ts +16 -0
  298. package/dist/esm/infrastructure/event/in-memory/in-memory-event-channel.js +47 -0
  299. package/dist/esm/infrastructure/http/http-error-mapper.d.ts +23 -0
  300. package/dist/esm/infrastructure/http/http-error-mapper.js +59 -0
  301. package/dist/esm/infrastructure/index.d.ts +16 -0
  302. package/dist/esm/infrastructure/index.js +9 -0
  303. package/dist/esm/infrastructure/logging/console-logger.d.ts +17 -0
  304. package/dist/esm/infrastructure/logging/console-logger.js +33 -0
  305. package/dist/esm/infrastructure/logging/in-memory-logger.d.ts +15 -0
  306. package/dist/esm/infrastructure/logging/in-memory-logger.js +23 -0
  307. package/dist/esm/infrastructure/logging/log-serializer.d.ts +7 -0
  308. package/dist/esm/infrastructure/logging/log-serializer.js +55 -0
  309. package/dist/esm/infrastructure/logging/silent-logger.d.ts +7 -0
  310. package/dist/esm/infrastructure/logging/silent-logger.js +14 -0
  311. package/dist/esm/package.json +3 -0
  312. package/package.json +106 -4
@@ -0,0 +1,85 @@
1
+ # Auditoría y refactorización con DDD
2
+
3
+ > Parte del skill `archi-code-ddd`: léela solo cuando ese skill te dirija aquí. Los ejemplos están escritos con la API de `@archi-code/ddd` (las clases de su `src`) y se copian tal cual; las guías son la fuente de verdad: el paquete solo se aparta de ellas donde lo marca `[Fuera de los libros: convención de @archi-code/ddd]`, y cualquier otra discrepancia es un defecto del paquete.
4
+ >
5
+ > **Cuándo consultar esta guía.** Auditar un proyecto existente contra las reglas de DDD y planificar su refactorización incremental. Incluye un script que escanea un repositorio TypeScript en busca de síntomas (dominio que importa frameworks u ORM, setters y campos públicos, servicios gigantes, repositorios de partes internas, importaciones entre contextos, transacciones con varios agregados, transacciones abiertas en la capa de aplicación), una lista de comprobación estratégica y táctica con severidad, un informe con formato fijo y un plan de migración por pasos (tests de caracterización, capa anticorrupción, extracción de contextos y agregados, estrangulamiento del legado). Consúltala siempre que alguien pida revisar, auditar, evaluar o "pasar a DDD" un código existente, modernizar un monolito, sacar un servicio de un sistema heredado, o pregunte "¿está bien hecho este DDD?", aunque solo pegue unos ficheros.
6
+
7
+ ## Con @archi-code/ddd
8
+
9
+ [Fuera de los libros: convención de @archi-code/ddd]
10
+
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).
13
+ * El reloj es un puerto (`Clock`; `FixedClock` en tests): `new Date()` en dominio o aplicación es un hallazgo (A9).
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
+
16
+ No puedes cambiar todo el código mal diseñado de una vez; puedes hacer bien tu parte e integrarte con el resto [IDDD cap. 2]. Esta guía convierte esa idea en un procedimiento: medir, priorizar y cambiar por pasos seguros.
17
+
18
+
19
+ ## Paso 1. Escaneo automático
20
+
21
+ Si tienes acceso al repositorio, desde la carpeta del paquete (la que contiene `ddd-skills/`; `SKILL.md` explica cómo localizarla):
22
+
23
+ ```bash
24
+ python3 -I ddd-skills/ddd-auditoria-refactorizacion/scripts/scan_ddd_smells.py <ruta-del-repo> --json informe-escaneo.json
25
+ ```
26
+
27
+ El script es heurístico y solo lee ficheros. Detecta los síntomas de `ddd-skills/ddd-auditoria-refactorizacion/references/lista-comprobacion.md` que se pueden ver con patrones de texto. Lo que no detecta (lenguaje, invariantes, límites de contexto) se revisa a mano en el paso 2.
28
+
29
+ Si solo tienes fragmentos pegados, aplica la misma lista leyendo el código.
30
+
31
+ ## Paso 2. Revisión manual
32
+
33
+ Recorre `ddd-skills/ddd-auditoria-refactorizacion/references/lista-comprobacion.md` en este orden. La integración entre contextos va primero porque un problema de límites invalida mejoras tácticas:
34
+
35
+ 1. **Integración entre contextos:** términos con varios significados en el mismo código, contextos implícitos, clases de dominio importadas entre contextos, integraciones por base de datos, sistemas externos sin ACL.
36
+ 2. **Arquitectura:** dependencias del dominio, dónde están las transacciones (con el paquete, solo dentro de los adaptadores de repositorio), qué expone la API.
37
+ 3. **Táctico:** modelo anémico (prueba de salud de IDDD), agregados grandes, referencias directas entre agregados en vez de ids, setters en vez de métodos de negocio, primitivos en el estado en vez de value objects, repositorios de partes o con métodos de negocio en vez de los roles `AggregateFinder`/`AggregateSaver`/`AggregateRemover`, casos de uso CRUD o que guardan varios agregados, listeners con reglas de negocio, eventos ausentes o mal nombrados.
38
+
39
+ Para modelo anémico usa las dos preguntas de IDDD: ¿el "modelo de dominio" son sobre todo getters y setters sin lógica de negocio? ¿Los componentes que lo usan contienen la mayor parte de la lógica? Dos "sí" indican anemia [IDDD cap. 1, reformulado]. Extender `AggregateRoot` no responde a ninguna de las dos: un agregado del paquete con `setStatus` y las reglas en el `handle` del caso de uso sigue siendo anémico.
40
+
41
+ ## Paso 3. Informe
42
+
43
+ Recoge el alcance, los hallazgos con evidencia (fichero y línea) y severidad, el plan por fases y la deuda de modelo aceptada.
44
+
45
+ Severidad `[Fuera de los libros]`:
46
+
47
+ | Nivel | Criterio |
48
+ |---|---|
49
+ | Crítica | Corrompe datos o reglas de negocio hoy (invariantes sin proteger, varios agregados en una transacción con conflictos, eventos que se pierden) |
50
+ | Alta | Impide cambiar el Core Domain con seguridad (lenguaje mezclado, dominio acoplado al ORM, lógica en controladores) |
51
+ | Media | Encarece el cambio (primitivos en el estado, setters, repositorios de partes, `new Date()` en vez de `Clock`) |
52
+ | Baja | Estilo y nomenclatura |
53
+
54
+ ## Paso 4. Plan de refactorización
55
+
56
+ Secuencia y técnicas en `ddd-skills/ddd-auditoria-refactorizacion/references/plan-migracion.md`. Principios:
57
+
58
+ * **Primero red de seguridad:** tests de caracterización sobre el comportamiento actual antes de mover nada `[Fuera de los libros]`.
59
+ * **Proteger antes de reconstruir:** una capa anticorrupción entre el código nuevo y el heredado, para no hablar su lenguaje [Destilado cap. 4].
60
+ * **Pasos pequeños y desplegables.**
61
+ * **No reescribir de golpe.** El estrangulamiento progresivo (strangler fig) y los contextos burbuja (bubble context, Evans) permiten convivir con el legado `[Fuera de los libros]`.
62
+
63
+ ## Cómo usar las demás guías
64
+
65
+ | Hallazgo | Guía para resolverlo |
66
+ |---|---|
67
+ | Integraciones frágiles, modelo ajeno filtrado | `ddd-skills/ddd-context-mapping/GUIA.md` |
68
+ | Dominio acoplado a frameworks | `ddd-skills/ddd-arquitectura/GUIA.md` |
69
+ | Primitivos en el estado, setters, identidades | `ddd-skills/ddd-value-objects-entidades/GUIA.md` |
70
+ | Agregados grandes o transacciones múltiples | `ddd-skills/ddd-agregados/GUIA.md` |
71
+ | Eventos perdidos o mal diseñados, listeners con reglas | `ddd-skills/ddd-eventos-dominio/GUIA.md` |
72
+ | Repositorios tipo DAO, versión sin comprobar, transacciones o unidades de trabajo en los casos de uso | `ddd-skills/ddd-repositorios-persistencia/GUIA.md` |
73
+ | Casos de uso gordos o CRUD | `ddd-skills/ddd-servicios-capa-aplicacion/GUIA.md` |
74
+ | Lecturas complejas | `ddd-skills/ddd-event-sourcing-cqrs/GUIA.md` |
75
+
76
+ ## Señales de que la auditoría va mal
77
+
78
+ * Recomendar "pasar todo a DDD". DDD táctico donde no hay complejidad es un coste sin beneficio [IDDD cap. 1].
79
+ * Proponer una reescritura completa.
80
+ * Hallazgos sin evidencia (sin fichero ni línea).
81
+ * Recomendar microservicios antes de tener claros los contextos.
82
+
83
+ ## Ejemplo
84
+
85
+ `ddd-skills/ddd-auditoria-refactorizacion/references/ejemplo-auditoria.md`: auditoría resumida de un backend de pedidos hecho con `@archi-code/ddd` y mal usado (agregado anémico con setters, `UpdateOrder` CRUD, pedido y cliente guardados en la misma transacción, casos de uso con su propia unidad de trabajo, estado como `string`), con el código antes y después, hallazgos y plan por fases.
@@ -0,0 +1,316 @@
1
+ # Ejemplo: auditoría de un backend de pedidos hecho con @archi-code/ddd
2
+
3
+ Caso inventado para mostrar el nivel esperado del informe. El equipo usa el paquete, pero lo usa mal: tiene las clases base, no el modelo.
4
+
5
+ ## Contexto
6
+
7
+ API en TypeScript con Express y MongoDB, contexto `sales`, unos 120 ficheros. `Order` extiende `AggregateRoot` y los casos de uso extienden `UseCase`. Síntomas: pedidos enviados sin líneas, puntos de fidelidad duplicados cuando dos pedidos del mismo cliente llegan a la vez (`ConcurrencyException` sobre `Customer` que tumba el alta del pedido) y estados escritos con erratas (`"shiped"`).
8
+
9
+ ## Escaneo
10
+
11
+ ```
12
+ [alta ] dominio-importa-capa-exterior-del-paquete: 2
13
+ [alta ] varios-agregados-en-una-transaccion: 3
14
+ [media] dinero-como-number: 4
15
+ [media] new-date-en-dominio-o-aplicacion: 7
16
+ [media] setter-en-dominio: 11
17
+ [media] transaccion-en-aplicacion: 2
18
+ [baja ] estado-como-string: 2
19
+ ```
20
+
21
+ La prueba de anemia da dos "sí" [IDDD cap. 1]: `Order` tiene `setStatus`, `setTotal` y `setLines`, y las reglas (no enviar un pedido vacío, no tocar uno enviado) están en `UpdateOrder`.
22
+
23
+ ## Código auditado (antes)
24
+
25
+ ```ts
26
+ import { AggregateRoot, CreatedAt, UpdatedAt } from '@archi-code/ddd/domain';
27
+ import { OrderMapper } from '../../infrastructure/order-mapper';
28
+
29
+ class Order extends AggregateRoot<OrderId, OrderEvent> {
30
+ private _status: string;
31
+ private _total: number;
32
+ // …
33
+ setStatus(status: string): void {
34
+ this._status = status;
35
+ this._updatedAt = new UpdatedAt(new Date());
36
+ }
37
+
38
+ setTotal(total: number): void {
39
+ this._total = total;
40
+ }
41
+
42
+ static mapper = new OrderMapper();
43
+ }
44
+
45
+ class UpdateOrder extends UseCase<UpdateOrderInput, UpdateOrderInput, void> {
46
+ protected toCommand(input: UpdateOrderInput): UpdateOrderInput {
47
+ return input;
48
+ }
49
+
50
+ protected handle(input: UpdateOrderInput): Promise<void> {
51
+ return this.unitOfWork.run(async () => {
52
+ const order = await this.orders.findById(new OrderId(input.orderId));
53
+ if (input.status === 'shipped' && order!.lines.length === 0) {
54
+ throw new Error('Empty order');
55
+ }
56
+ if (input.status) order!.setStatus(input.status);
57
+ if (input.total) order!.setTotal(input.total);
58
+ await this.orders.save(order!);
59
+ });
60
+ }
61
+ }
62
+
63
+ class PlaceOrder extends UseCase<PlaceOrderInput, PlaceOrderCommand, void> {
64
+ protected handle(command: PlaceOrderCommand): Promise<void> {
65
+ return this.unitOfWork.run(async () => {
66
+ const order = Order.place(command.orderId, command.customerId, this.clock);
67
+ const customer = await this.customers.findById(command.customerId);
68
+ customer!.awardPointsFor(command.orderId);
69
+ await this.orders.save(order);
70
+ await this.customers.save(customer!);
71
+ await this.events.publish(order.pullDomainEvents());
72
+ });
73
+ }
74
+ }
75
+ ```
76
+
77
+ ## Hallazgos principales
78
+
79
+ | # | Hallazgo | Evidencia | Severidad |
80
+ |---|---|---|---|
81
+ | 1 | `PlaceOrder` guarda `Order` y `Customer` en la misma `unitOfWork.run`: un conflicto de versión en el cliente anula el pedido | `application/place-order/place-order.ts:31` | Crítica |
82
+ | 2 | Reglas de envío en el caso de uso; `Order` solo tiene setters (modelo anémico) | `domain/order/order.ts:48`, `application/update-order/update-order.ts:22` | Alta |
83
+ | 3 | `UpdateOrder` hace varias cosas según el input (enviar, cambiar total, cambiar líneas): nombre CRUD, no una acción de negocio | `application/update-order/update-order.ts` | Alta |
84
+ | 4 | El dominio importa `OrderMapper`, un mapper escrito a mano en `infrastructure/`, y le delega la conversión a primitivos | `domain/order/order.ts:2` | Alta |
85
+ | 5 | Estado y total como primitivos (`_status: string`, `_total: number`) en vez de value objects | `domain/order/order.ts:5-6` | Media |
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
+ | 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 |
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
+
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).
92
+
93
+ ## Código propuesto (después)
94
+
95
+ El pedido pasa a ser el `Order` de `ddd-skills/ddd-agregados/references/implementacion-ts.md`, con sus value objects y eventos; no se redefine aquí. El estado es un value object con comportamiento (`OrderStatus`), el total deja de guardarse porque sale de las líneas, y las líneas son `OrderLines`, un `Composite` de `OrderLine` (con `Money`) cuya regla `hasLines` impide crear un pedido sin líneas. Lo que usan los casos de uso de este ejemplo:
96
+
97
+ ```ts
98
+ static place(
99
+ id: OrderId,
100
+ customerId: CustomerId,
101
+ lines: OrderLines,
102
+ address: ShippingAddress,
103
+ clock: { now(): Date },
104
+ ): Order;
105
+ addLine(line: OrderLine, clock: { now(): Date }): void;
106
+ ```
107
+
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
+
110
+ ```ts
111
+ import { ValueObjects } from '@archi-code/ddd/domain';
112
+ 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';
115
+ import type { Clock, EventBus } from '@archi-code/ddd/application';
116
+
117
+ interface AddOrderLineInput {
118
+ orderId: string;
119
+ sku: string;
120
+ quantity: number;
121
+ unitPrice: { amount: number; currency: string };
122
+ }
123
+
124
+ interface AddOrderLineCommand {
125
+ orderId: OrderId;
126
+ line: OrderLine;
127
+ }
128
+
129
+ class AddOrderLine extends UseCase<AddOrderLineInput, AddOrderLineCommand, void> {
130
+ constructor(
131
+ private readonly orders: AggregateFinder<Order> & AggregateSaver<Order>,
132
+ private readonly events: EventBus<OrderEvent>,
133
+ private readonly clock: Clock,
134
+ ) {
135
+ super();
136
+ }
137
+
138
+ protected toCommand(input: AddOrderLineInput): AddOrderLineCommand {
139
+ return ValueObjects.build({
140
+ orderId: () => new OrderId(input.orderId),
141
+ line: () =>
142
+ OrderLine.fromPrimitives({
143
+ sku: input.sku,
144
+ quantity: input.quantity,
145
+ unitPrice: input.unitPrice,
146
+ }),
147
+ });
148
+ }
149
+
150
+ protected async handle(command: AddOrderLineCommand): Promise<void> {
151
+ const order = await this.orders.findById(command.orderId);
152
+ if (order === null) {
153
+ throw new AggregateNotFoundException('Order', command.orderId.value);
154
+ }
155
+ order.addLine(command.line, this.clock);
156
+ await this.orders.save(order);
157
+ await this.events.publish(order.pullDomainEvents());
158
+ }
159
+ }
160
+ ```
161
+
162
+ `PlaceOrder` guarda solo el pedido; los puntos del cliente se conceden después, en su propia escritura atómica, cuando llega `OrderPlaced` (consistencia eventual fuera del límite del agregado [IDDD cap. 10]). El caso de uso que corre el listener no rechaza el hecho: si el cliente no existe, vuelve; y `Customer.awardPointsFor(orderId)` (un método de negocio del agregado `Customer`) ignora un pedido ya premiado, porque el canal entrega al menos una vez:
163
+
164
+ ```ts
165
+ import { EventListener, ListensTo } from '@archi-code/ddd/application';
166
+
167
+ interface PlaceOrderInput {
168
+ orderId: string;
169
+ customerId: string;
170
+ lines: { sku: string; quantity: number; unitPrice: { amount: number; currency: string } }[];
171
+ shippingAddress: { street: string; city: string; country: string };
172
+ }
173
+
174
+ interface PlaceOrderCommand {
175
+ orderId: OrderId;
176
+ customerId: CustomerId;
177
+ lines: OrderLines;
178
+ shippingAddress: ShippingAddress;
179
+ }
180
+
181
+ interface AwardLoyaltyPointsInput {
182
+ customerId: string;
183
+ orderId: string;
184
+ }
185
+
186
+ interface AwardLoyaltyPointsCommand {
187
+ customerId: CustomerId;
188
+ orderId: OrderId;
189
+ }
190
+
191
+ class PlaceOrder extends UseCase<PlaceOrderInput, PlaceOrderCommand, void> {
192
+ constructor(
193
+ private readonly orders: AggregateSaver<Order>,
194
+ private readonly events: EventBus<OrderEvent>,
195
+ private readonly clock: Clock,
196
+ ) {
197
+ super();
198
+ }
199
+
200
+ protected toCommand(input: PlaceOrderInput): PlaceOrderCommand {
201
+ return ValueObjects.build({
202
+ orderId: () => new OrderId(input.orderId),
203
+ customerId: () => new CustomerId(input.customerId),
204
+ lines: () => OrderLines.fromPrimitives({ items: input.lines }),
205
+ shippingAddress: () => ShippingAddress.fromPrimitives(input.shippingAddress),
206
+ });
207
+ }
208
+
209
+ protected async handle(command: PlaceOrderCommand): Promise<void> {
210
+ const order = Order.place(
211
+ command.orderId,
212
+ command.customerId,
213
+ command.lines,
214
+ command.shippingAddress,
215
+ this.clock,
216
+ );
217
+ await this.orders.save(order);
218
+ await this.events.publish(order.pullDomainEvents());
219
+ }
220
+ }
221
+
222
+ class AwardLoyaltyPoints extends UseCase<AwardLoyaltyPointsInput, AwardLoyaltyPointsCommand, void> {
223
+ constructor(private readonly customers: AggregateFinder<Customer> & AggregateSaver<Customer>) {
224
+ super();
225
+ }
226
+
227
+ protected toCommand(input: AwardLoyaltyPointsInput): AwardLoyaltyPointsCommand {
228
+ return ValueObjects.build({
229
+ customerId: () => new CustomerId(input.customerId),
230
+ orderId: () => new OrderId(input.orderId),
231
+ });
232
+ }
233
+
234
+ protected async handle(command: AwardLoyaltyPointsCommand): Promise<void> {
235
+ const customer = await this.customers.findById(command.customerId);
236
+ if (customer === null) {
237
+ return;
238
+ }
239
+ customer.awardPointsFor(command.orderId);
240
+ await this.customers.save(customer);
241
+ }
242
+ }
243
+
244
+ @ListensTo(OrderPlaced, { consumer: 'award-loyalty-points-on-order-placed' })
245
+ class AwardLoyaltyPointsOnOrderPlaced extends EventListener<OrderPlaced> {
246
+ constructor(private readonly awardLoyaltyPoints: AwardLoyaltyPoints) {
247
+ super();
248
+ }
249
+
250
+ async handle(event: OrderPlaced): Promise<void> {
251
+ await this.awardLoyaltyPoints.execute({
252
+ customerId: event.customerId,
253
+ orderId: event.aggregateId,
254
+ });
255
+ }
256
+ }
257
+ ```
258
+
259
+ Test de la corrección, con los repositorios en memoria del proyecto (`InMemoryOrderRepository` e `InMemoryCustomerRepository`, dobles de test en `test/support/` escritos como en `ddd-skills/ddd-repositorios-persistencia/references/tests-repositorios.md`) y los adaptadores en memoria del paquete (`OrderLineData` y `MoneyData` son los tipos del payload de los eventos, en el mismo fichero que `Order`):
260
+
261
+ ```ts
262
+ import { expect } from 'vitest';
263
+ import {
264
+ DomainEventBus,
265
+ EventRegistry,
266
+ FixedClock,
267
+ InMemoryEventChannel,
268
+ } from '@archi-code/ddd/infrastructure';
269
+ import { ValidationException } from '@archi-code/ddd/exceptions';
270
+ import { InMemoryCustomerRepository } from '../support/in-memory-customer-repository';
271
+ import { InMemoryOrderRepository } from '../support/in-memory-order-repository';
272
+
273
+ const orders = new InMemoryOrderRepository();
274
+ const customers = new InMemoryCustomerRepository();
275
+ const registry = new EventRegistry<OrderEvent>().register(
276
+ OrderPlaced,
277
+ (json) =>
278
+ new OrderPlaced(
279
+ json.aggregateId,
280
+ json.customerId as string,
281
+ json.lines as OrderLineData[],
282
+ json.total as MoneyData,
283
+ json,
284
+ ),
285
+ );
286
+ const channel = new InMemoryEventChannel();
287
+ const bus = new DomainEventBus<OrderEvent>(channel, registry);
288
+ await bus.subscribe(new AwardLoyaltyPointsOnOrderPlaced(new AwardLoyaltyPoints(customers)));
289
+ const clock = new FixedClock('2026-03-01T09:30:00Z');
290
+ const orderId = '9b2f6c1e-4d3a-4f8b-a1c2-3e4d5f6a7b8c';
291
+ const customerId = '3c1d2e4f-5a6b-4c7d-8e9f-0a1b2c3d4e5f';
292
+ const shippingAddress = { street: 'Calle Mayor 1', city: 'Huelva', country: 'ES' };
293
+ const placeOrder = new PlaceOrder(orders, bus, clock);
294
+
295
+ await placeOrder.execute({
296
+ orderId,
297
+ customerId,
298
+ lines: [{ sku: 'TORCH-01', quantity: 2, unitPrice: { amount: 850, currency: 'EUR' } }],
299
+ shippingAddress,
300
+ });
301
+ await expect(
302
+ placeOrder.execute({
303
+ orderId: '5d4c3b2a-1f0e-4d9c-8b7a-6f5e4d3c2b1a',
304
+ customerId,
305
+ lines: [],
306
+ shippingAddress,
307
+ }),
308
+ ).rejects.toThrow(ValidationException);
309
+ ```
310
+
311
+ ## Plan
312
+
313
+ * **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`.
315
+ * **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
+ * **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.
@@ -0,0 +1,54 @@
1
+ # Lista de comprobación DDD
2
+
3
+ La columna "Script" indica si `scan_ddd_smells.py` lo detecta (sí / parcial / no). Lo que dice "no" se revisa leyendo el código. En código hecho con `@archi-code/ddd`, cada comprobación nombra la pieza del paquete que la resuelve; recorre además `ddd-skills/archi-code-ddd/what-not-to-do.md`.
4
+
5
+ ## Integración entre contextos
6
+
7
+ | # | Comprobación | Fuente | Script | Severidad típica |
8
+ |---|---|---|---|---|
9
+ | E1 | ¿Hay un término con varios significados usado en el mismo código (Customer, Product, Order)? | [IDDD cap. 2; Destilado cap. 2] | no | Alta |
10
+ | E2 | ¿Se importan clases de dominio entre contextos (`contexts/sales` importando de `contexts/billing`) fuera de un `shared-kernel/` explícito? | [IDDD cap. 13; Destilado cap. 4] | sí | Alta |
11
+ | E3 | ¿Hay integración por base de datos compartida? | [Destilado cap. 4] | no | Alta |
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
+ | E5 | ¿Hay cadenas de llamadas síncronas entre servicios dentro de una petición? | [Destilado cap. 4] | no | Alta |
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 |
16
+
17
+ ## Arquitectura
18
+
19
+ | # | Comprobación | Fuente | Script | Severidad |
20
+ |---|---|---|---|---|
21
+ | A1 | ¿El dominio importa frameworks, ORM o clientes HTTP? | [Destilado cap. 2; IDDD cap. 4] | sí | Alta |
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 |
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
+ | 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
+ | A6 | ¿Módulos nombrados por patrón (`entities/`, `services/`, `listeners/`)? | [PHP: Modules] | no | Baja |
27
+ | A7 | ¿El dominio lee `process.env` o usa el logger? Solo la raíz de composición lee el entorno | `[Fuera de los libros]` | parcial (solo `process.env`) | Media |
28
+ | A8 | ¿El dominio importa `@archi-code/ddd/application`, `/infrastructure` (por ejemplo `SystemClock`) o un adaptador de `infrastructure/` del proyecto? | `[Fuera de los libros: convención de @archi-code/ddd]` | sí | Alta |
29
+ | A9 | ¿`new Date()` en dominio o aplicación en vez del `Clock` inyectado (`FixedClock` en tests)? | `[Fuera de los libros: convención de @archi-code/ddd]` | sí | Media |
30
+ | A10 | ¿Errores de negocio como `Error` o `ApplicationException` genéricos en vez de `DomainException(message, ['ORD001'])`, o errores traducidos sin `{ cause }`? | `[Fuera de los libros: convención de @archi-code/ddd]` | no | Media |
31
+
32
+ ## Táctico
33
+
34
+ | # | Comprobación | Fuente | Script | Severidad |
35
+ |---|---|---|---|---|
36
+ | T1 | Prueba de anemia: ¿el agregado solo tiene getters y setters? ¿la lógica está en los casos de uso? | [IDDD cap. 1] | parcial | Alta |
37
+ | T2 | ¿Setters públicos o campos públicos mutables en el dominio? Los cambios son métodos de negocio que llaman a `record()`; los campos son `private _x` con su getter, o `readonly` | [Destilado cap. 5] | sí | Media |
38
+ | T3 | ¿Primitivos en el estado (`private _total: number`, `private _status: string`)? Todo campo de estado es un value object: `Required(XType)` con `@Validations`, `Composite` (`Money`), `EnumType` (`OrderStatus`) o una entidad hija | [IDDD cap. 6] | parcial | Media |
39
+ | T4 | ¿Agregados con colecciones que crecen sin límite? | [IDDD cap. 10] | no | Alta |
40
+ | T5 | ¿Referencias a objetos de otros agregados en vez de identidades (`CustomerId extends IdType<'CustomerId'>`)? | [Destilado cap. 5] | no | Media |
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 |
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
+ | 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
+ | 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 |
46
+ | T11 | ¿El caso de uso publica con `events.publish(order.pullDomainEvents())` después de `save`, y, si un evento no puede perderse, el repositorio escribe el agregado y sus eventos pendientes en la misma escritura atómica (outbox) que un relay entrega por un `EventChannel`? | [Destilado cap. 6; IDDD cap. 8] | no | Crítica si se pierden |
47
+ | T12 | ¿Los listeners saltan un `eventId` ya procesado? ¿El caso de uso que ejecutan vuelve si el agregado no existe en vez de lanzar? | [Destilado cap. 4] | no | Alta |
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
+ | 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
+ | 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 |
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
+ | 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
+ | 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 |
@@ -0,0 +1,73 @@
1
+ # Plan de migración incremental
2
+
3
+ ## Fase 0. Red de seguridad
4
+
5
+ * Tests de caracterización del comportamiento actual en la zona a cambiar: fijan lo que hace hoy, aunque esté mal, para detectar cambios no deseados `[Fuera de los libros]`. Cuando el código ya usa el paquete, se escriben sobre el repositorio en memoria del proyecto (`new InMemoryOrderRepository()`, un doble de test en `test/support/`, ver `ddd-skills/ddd-repositorios-persistencia/references/tests-repositorios.md`) y `FixedClock`, y comprueban lo que transportó el canal (`InMemoryEventChannel.sent`).
6
+ * Reglas automáticas de dependencias (`ddd-skills/ddd-arquitectura/GUIA.md`) en modo aviso, para medir sin bloquear.
7
+
8
+ ## Fase 1. Proteger el código nuevo
9
+
10
+ * Crea un contexto nuevo y limpio para la parte que se va a tocar, con su propio lenguaje.
11
+ * Conéctalo con el legado mediante una ACL: el modelo nuevo no habla el lenguaje del legado [Destilado cap. 4]. El dominio declara el puerto con value objects; el adaptador de `infrastructure/` lee el legado y traduce:
12
+
13
+ ```ts
14
+ export interface ProductPricing {
15
+ priceOf(sku: Sku): Promise<Money>;
16
+ }
17
+ ```
18
+
19
+ ```ts
20
+ import { InternalErrorException } from '@archi-code/ddd/exceptions';
21
+
22
+ interface LegacyArticleRow {
23
+ article_code: string;
24
+ retail_price: string;
25
+ currency_code: string | null;
26
+ }
27
+
28
+ interface LegacyArticles {
29
+ findByCode(code: string): Promise<LegacyArticleRow>;
30
+ }
31
+
32
+ export class LegacyProductPricing implements ProductPricing {
33
+ constructor(private readonly articles: LegacyArticles) {}
34
+
35
+ async priceOf(sku: Sku): Promise<Money> {
36
+ let row: LegacyArticleRow;
37
+ try {
38
+ row = await this.articles.findByCode(sku.value);
39
+ } catch (error) {
40
+ throw new InternalErrorException('reading the legacy price', { cause: error });
41
+ }
42
+ return Money.fromPrimitives({
43
+ amount: Math.round(Number(row.retail_price.replace(',', '.')) * 100),
44
+ currency: row.currency_code ?? 'EUR',
45
+ });
46
+ }
47
+ }
48
+ ```
49
+
50
+ `Money.fromPrimitives` construye el `Composite` con todas sus reglas: un dato del legado que no las cumple lanza `ValidationException` en la frontera, no dentro del modelo.
51
+
52
+ La idea de Evans de un **contexto burbuja** (bubble context): un pequeño contexto nuevo, protegido por ACL, que convive con el legado y lo usa como fuente de datos `[Fuera de los libros: Evans, "Getting Started with DDD When Surrounded by Legacy Systems", 2013]`.
53
+
54
+ ## Fase 2. Desplazar funcionalidad
55
+
56
+ * **Estrangulamiento** (strangler fig): las nuevas funcionalidades y las que se rehacen se sirven desde el contexto nuevo; un enrutador decide qué va a cada lado; el legado se va quedando sin uso `[Fuera de los libros]`.
57
+ * **Rama por abstracción** (branch by abstraction) para sustituir piezas internas: puerto (`ProductPricing`), implementación vieja detrás (`LegacyProductPricing`), implementación nueva detrás, conmutación en la raíz de composición `[Fuera de los libros]`.
58
+ * Si el legado debe seguir viendo los cambios, el contexto nuevo publica sus eventos con `EventBus.publish` y un `EventListener` suscrito con `@ListensTo` ejecuta un caso de uso cuyo puerto escribe en el legado.
59
+
60
+ ## Fase 3. Refactorizar el táctico dentro de cada contexto
61
+
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]`.
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
+ 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
+ 4. Partir agregados grandes con el procedimiento de `ddd-skills/ddd-agregados/GUIA.md` (plazos de consistencia).
67
+ 5. Donde hoy un caso de uso guarda varios agregados: guardar uno, publicar su evento y ejecutar el resto con un listener `<Action>On<Event>`; si el evento no puede perderse, el repositorio lo escribe junto al agregado en la misma escritura atómica (outbox) y un relay lo entrega por un `EventChannel`.
68
+ 6. Donde un caso de uso o servicio de aplicación abre o maneja transacciones, o recibe un runner de transacciones o una unidad de trabajo: mover la transacción al adaptador del repositorio, que hace atómico cada `save`/`remove` (una escritura de un documento o, si el agregado ocupa varias tablas, una transacción de base de datos dentro de `save`/`remove`), y dejar el caso de uso con solo roles de repositorio, `EventBus` y `Clock` `[Fuera de los libros: convención de @archi-code/ddd]`.
69
+ 7. Separar consultas de lectura (interfaz en `application/`, implementada en `infrastructure/` sobre la base de datos) si los repositorios han crecido en métodos.
70
+
71
+ ## Despliegue
72
+
73
+ * Cada paso debe poder desplegarse y revertirse por separado.