@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,324 @@
1
+ # Ejemplo: dónde va cada lógica en Ventas
2
+
3
+ | Lógica | Dónde | Por qué |
4
+ |---|---|---|
5
+ | El total es la suma de las líneas | `OrderLines` (composite `Composite({ items: [OrderLine] })`, método `total()`) | Cálculo sobre valores |
6
+ | Un pedido confirmado no admite líneas nuevas | `Order` (agregado) | Regla sobre su estado |
7
+ | Gastos de envío según zona y promociones | `ShippingCostCalculator` (servicio de dominio) | Necesita el pedido, la zona, las tarifas y la promoción; forzado en cualquiera de ellos |
8
+ | Envío gratis a partir de un importe | `OrderAbove` (`Specification<Order>`) | Regla con nombre que decide si un pedido cumple un criterio |
9
+ | Solo el cliente que hizo el pedido puede cancelarlo | `OrderCancellation` (servicio de dominio), con concepto `Customer`, sobre `Order.cancel` | Regla de negocio [IDDD cap. 2] |
10
+ | Quien pide cancelar tiene permiso para cancelar pedidos (rol, sesión activa) | `SalesPermissions` (puerto de aplicación) consultado por `CancelOrder`, que lanza `NotAllowedException` | Autorización del caso de uso [IDDD cap. 14] |
11
+ | Cargar pedido, cancelar, guardar, publicar | `CancelOrder` (caso de uso) | Coordinación |
12
+ | Cuando Almacén reserva, registrar en el pedido | listener `RegisterReservationOnStockReserved` y su caso de uso `RegisterReservation` `[Fuera de los libros: convención de @archi-code/ddd]` | Reacción a evento; invoca al agregado |
13
+ | Precio vigente de una referencia | `ProductPricing` (puerto de dominio, ACL en infraestructura) | Viene de otro contexto |
14
+
15
+ ## El modelo
16
+
17
+ `Order` y sus value objects (`OrderId`, `CustomerId`, `Sku`, `Quantity`, `Money`, `OrderLine`, `OrderLines`, `ShippingAddress`, `OrderStatus`, `ReservationId`, `CancellationReason`), sus eventos (`OrderPlaced`, `OrderLineAdded`, `OrderCancelled`…, unidos en `OrderEvent`) y sus errores son los de `ddd-skills/ddd-agregados/references/implementacion-ts.md`; no se redefinen aquí. Lo que usan los ejemplos de esta guía (`ddd-skills/ddd-servicios-capa-aplicacion/references/servicios-dominio-ts.md` y `ddd-skills/ddd-servicios-capa-aplicacion/references/capa-aplicacion-ts.md`):
18
+
19
+ ```ts
20
+ static place(
21
+ id: OrderId,
22
+ customerId: CustomerId,
23
+ lines: OrderLines,
24
+ address: ShippingAddress,
25
+ clock: { now(): Date },
26
+ ): Order;
27
+ addLine(line: OrderLine, clock: { now(): Date }): void;
28
+ registerStockReservation(reservation: ReservationId, clock: { now(): Date }): void;
29
+ cancel(reason: CancellationReason, clock: { now(): Date }): void;
30
+ total(): Money;
31
+ ```
32
+
33
+ Lo que añade Ventas en esta guía:
34
+
35
+ ```ts
36
+ import { AggregateRoot, BooleanType, Required, Specification, StringType } from '@archi-code/ddd/domain';
37
+ import type { PrimitiveTypes } from '@archi-code/ddd/domain';
38
+ import { DomainException } from '@archi-code/ddd/exceptions';
39
+
40
+ export class OrderAbove extends Specification<Order> {
41
+ constructor(private readonly minimum: Money) {
42
+ super();
43
+ }
44
+
45
+ isSatisfiedBy(order: Order): boolean {
46
+ return order.total().covers(this.minimum);
47
+ }
48
+ }
49
+
50
+ export class CustomerBlocked extends DomainException {
51
+ constructor(customerId: string) {
52
+ super(`Customer ${customerId} is blocked and cannot place orders`, ['CUS001']);
53
+ }
54
+ }
55
+
56
+ export class CustomerName extends Required(StringType) {}
57
+ export class Blocked extends Required(BooleanType) {}
58
+
59
+ export class Customer extends AggregateRoot<CustomerId> {
60
+ private constructor(
61
+ id: CustomerId,
62
+ readonly name: CustomerName,
63
+ private _blocked: Blocked,
64
+ ) {
65
+ super(id);
66
+ }
67
+
68
+ static fromPrimitives(primitives: PrimitiveTypes<Customer>): Customer {
69
+ return new Customer(
70
+ new CustomerId(primitives.id),
71
+ new CustomerName(primitives.name),
72
+ new Blocked(primitives.blocked),
73
+ );
74
+ }
75
+
76
+ get blocked(): Blocked {
77
+ return this._blocked;
78
+ }
79
+
80
+ placeOrder(
81
+ id: OrderId,
82
+ lines: OrderLines,
83
+ address: ShippingAddress,
84
+ clock: { now(): Date },
85
+ ): Order {
86
+ if (this._blocked.value) {
87
+ throw new CustomerBlocked(this.id.value);
88
+ }
89
+ return Order.place(id, this.id, lines, address, clock);
90
+ }
91
+
92
+ toPrimitives(): PrimitiveTypes<Customer> {
93
+ return { id: this.id.value, name: this.name.value, blocked: this._blocked.value };
94
+ }
95
+ }
96
+
97
+ export class NotTheCustomerOfOrder extends DomainException {
98
+ constructor(orderId: string) {
99
+ super(`Only the customer who placed order ${orderId} can cancel it`, ['ORD004']);
100
+ }
101
+ }
102
+
103
+ export class OrderCancellation {
104
+ cancel(
105
+ order: Order,
106
+ requester: CustomerId,
107
+ reason: CancellationReason,
108
+ clock: { now(): Date },
109
+ ): void {
110
+ if (!order.customerId.equals(requester)) {
111
+ throw new NotTheCustomerOfOrder(order.id.value);
112
+ }
113
+ order.cancel(reason, clock);
114
+ }
115
+ }
116
+ ```
117
+
118
+ * `OrderAbove` usa `Money.covers` (misma moneda e importe mayor o igual).
119
+ * `OrderCancellation` es un servicio de dominio: la regla relaciona a quien pide con el pedido (`CustomerId`), y el estado lo sigue protegiendo `Order.cancel`, que lanza `OrderCannotBeCancelled` si el pedido ya está cancelado.
120
+
121
+ ## `PlaceOrder`
122
+
123
+ `ProductPricing` y `ProductNotSellable` son los de `ddd-skills/ddd-servicios-capa-aplicacion/references/capa-aplicacion-ts.md`.
124
+
125
+ ```ts
126
+ import { ValueObjects } from '@archi-code/ddd/domain';
127
+ import type { AggregateFinder, AggregateSaver } from '@archi-code/ddd/domain';
128
+ import { UseCase } from '@archi-code/ddd/application';
129
+ import type { Clock, EventBus } from '@archi-code/ddd/application';
130
+ import { AggregateNotFoundException } from '@archi-code/ddd/exceptions';
131
+
132
+ export interface PlaceOrderInput {
133
+ customerId: string;
134
+ lines: { sku: string; quantity: number }[];
135
+ shippingAddress: { street: string; city: string; country: string };
136
+ }
137
+
138
+ interface PlaceOrderCommand {
139
+ customerId: CustomerId;
140
+ lines: { sku: Sku; quantity: Quantity }[];
141
+ shippingAddress: ShippingAddress;
142
+ }
143
+
144
+ export class PlaceOrder extends UseCase<PlaceOrderInput, PlaceOrderCommand, string> {
145
+ constructor(
146
+ private readonly customers: AggregateFinder<Customer>,
147
+ private readonly orders: AggregateSaver<Order>,
148
+ private readonly pricing: ProductPricing,
149
+ private readonly events: EventBus<OrderEvent>,
150
+ private readonly clock: Clock,
151
+ ) {
152
+ super();
153
+ }
154
+
155
+ protected toCommand(input: PlaceOrderInput): PlaceOrderCommand {
156
+ return ValueObjects.build({
157
+ customerId: () => new CustomerId(input.customerId),
158
+ lines: () =>
159
+ ValueObjects.buildList(input.lines, (line) =>
160
+ ValueObjects.build({
161
+ sku: () => new Sku(line.sku),
162
+ quantity: () => new Quantity(line.quantity),
163
+ }),
164
+ ),
165
+ shippingAddress: () => ShippingAddress.fromPrimitives(input.shippingAddress),
166
+ });
167
+ }
168
+
169
+ protected async handle(command: PlaceOrderCommand): Promise<string> {
170
+ const customer = await this.customers.findById(command.customerId);
171
+ if (customer === null) {
172
+ throw new AggregateNotFoundException('Customer', command.customerId.value);
173
+ }
174
+ const items = await Promise.all(
175
+ command.lines.map(async ({ sku, quantity }) => {
176
+ const unitPrice = await this.pricing.priceForSale(sku);
177
+ if (unitPrice === null) {
178
+ throw new ProductNotSellable(sku.value);
179
+ }
180
+ return new OrderLine({ sku, quantity, unitPrice });
181
+ }),
182
+ );
183
+ const lines = new OrderLines({ items });
184
+ const order = customer.placeOrder(
185
+ OrderId.generate(),
186
+ lines,
187
+ command.shippingAddress,
188
+ this.clock,
189
+ );
190
+ await this.orders.save(order);
191
+ await this.events.publish(order.pullDomainEvents());
192
+ return order.id.value;
193
+ }
194
+ }
195
+ ```
196
+
197
+ * Las lecturas de otros agregados y contextos (`Customer`, precios) van antes de invocar al dominio [IDDD cap. 10].
198
+ * `Customer` actúa como fábrica (comprueba que no está bloqueado) pero no se modifica ni se guarda: el caso de uso pide `AggregateFinder<Customer>` y `AggregateSaver<Order>`, y solo se guarda `Order`: una escritura atómica, un agregado; que esa escritura sea atómica lo garantiza el repositorio, no el caso de uso `[Fuera de los libros: convención de @archi-code/ddd]`.
199
+ * El id nuevo sale del value object (`OrderId.generate()`), no del repositorio.
200
+ * `toCommand` informa de todos los campos a la vez, también los de cada línea (`lines.0.quantity`).
201
+
202
+ Los gastos de envío se han dejado fuera de este caso de uso para mantenerlo alineado con el `Order` de `ddd-skills/ddd-agregados/GUIA.md`; si el negocio los incorpora al pedido, el caso de uso carga lo que `ShippingCostCalculator` necesita, lo invoca y pasa el `Money` resultante a `placeOrder`.
203
+
204
+ ## Registrar la reserva de stock
205
+
206
+ Una reacción a un evento es un listener `<Acción>On<Evento>`, decorado con `@ListensTo` y con un nombre de consumidor estable, que solo traduce el evento a la entrada de un caso de uso. Ese caso de uso registra un hecho que ya ocurrió: si el pedido no existe, retorna en vez de lanzar (el canal lo reentregaría sin fin), y `registerStockReservation` es idempotente, porque la entrega es al menos una vez.
207
+
208
+ ```ts
209
+ import { EventBase, ValueObjects } from '@archi-code/ddd/domain';
210
+ import type { AggregateFinder, AggregateSaver, EventMetadata } from '@archi-code/ddd/domain';
211
+ import { EventListener, ListensTo, UseCase } from '@archi-code/ddd/application';
212
+ import type { Clock, EventBus } from '@archi-code/ddd/application';
213
+
214
+ export class StockReserved extends EventBase {
215
+ static readonly eventName = 'StockReserved';
216
+
217
+ constructor(
218
+ reservationId: string,
219
+ readonly orderId: string,
220
+ metadata?: EventMetadata,
221
+ ) {
222
+ super(reservationId, metadata);
223
+ }
224
+ }
225
+
226
+ export interface RegisterReservationInput {
227
+ orderId: string;
228
+ reservationId: string;
229
+ }
230
+
231
+ interface RegisterReservationCommand {
232
+ orderId: OrderId;
233
+ reservationId: ReservationId;
234
+ }
235
+
236
+ export class RegisterReservation extends UseCase<
237
+ RegisterReservationInput,
238
+ RegisterReservationCommand,
239
+ void
240
+ > {
241
+ constructor(
242
+ private readonly orders: AggregateFinder<Order> & AggregateSaver<Order>,
243
+ private readonly events: EventBus<OrderEvent>,
244
+ private readonly clock: Clock,
245
+ ) {
246
+ super();
247
+ }
248
+
249
+ protected toCommand(input: RegisterReservationInput): RegisterReservationCommand {
250
+ return ValueObjects.build({
251
+ orderId: () => new OrderId(input.orderId),
252
+ reservationId: () => new ReservationId(input.reservationId),
253
+ });
254
+ }
255
+
256
+ protected async handle(command: RegisterReservationCommand): Promise<void> {
257
+ const order = await this.orders.findById(command.orderId);
258
+ if (order === null) {
259
+ return;
260
+ }
261
+ order.registerStockReservation(command.reservationId, this.clock);
262
+ await this.orders.save(order);
263
+ await this.events.publish(order.pullDomainEvents());
264
+ }
265
+ }
266
+
267
+ @ListensTo(StockReserved, { consumer: 'register-reservation-on-stock-reserved' })
268
+ export class RegisterReservationOnStockReserved extends EventListener<StockReserved> {
269
+ constructor(private readonly registerReservation: RegisterReservation) {
270
+ super();
271
+ }
272
+
273
+ async handle(event: StockReserved): Promise<void> {
274
+ await this.registerReservation.execute({
275
+ orderId: event.orderId,
276
+ reservationId: event.aggregateId,
277
+ });
278
+ }
279
+ }
280
+ ```
281
+
282
+ ## Test del flujo
283
+
284
+ El test comprueba lo que el listener provoca, no que se le llame, y entrega el mismo evento dos veces. `aPendingOrderOf`, `laura` y `orderId` son fábricas y datos de `test/support/` del proyecto, y `InMemoryOrderRepository` es el repositorio en memoria que el proyecto escribe allí (`ddd-skills/ddd-repositorios-persistencia/references/tests-repositorios.md`), como en `ddd-skills/ddd-servicios-capa-aplicacion/references/capa-aplicacion-ts.md`:
285
+
286
+ ```ts
287
+ import { describe, expect, it } from 'vitest';
288
+ import {
289
+ DomainEventBus,
290
+ EventRegistry,
291
+ FixedClock,
292
+ InMemoryEventChannel,
293
+ } from '@archi-code/ddd/infrastructure';
294
+ import { InMemoryOrderRepository } from '../support/in-memory-order-repository';
295
+
296
+ const clock = new FixedClock('2026-03-01T09:30:00Z');
297
+ const reservationId = '0f1e2d3c-4b5a-4968-8776-655443322110';
298
+
299
+ describe('Registering the stock reservation of an order', () => {
300
+ it('the order keeps the reservation the warehouse made, also when it is delivered twice', async () => {
301
+ const orders = new InMemoryOrderRepository();
302
+ const registry = new EventRegistry<StockReserved>().register(
303
+ StockReserved,
304
+ (json) => new StockReserved(json.aggregateId, json.orderId as string, json),
305
+ );
306
+ const channel = new InMemoryEventChannel();
307
+ const bus = new DomainEventBus<StockReserved>(channel, registry);
308
+ const sales = new DomainEventBus<OrderEvent>(channel, new EventRegistry<OrderEvent>());
309
+ await bus.subscribe(
310
+ new RegisterReservationOnStockReserved(
311
+ new RegisterReservation(orders, sales, clock),
312
+ ),
313
+ );
314
+ await orders.save(aPendingOrderOf(laura));
315
+ const reserved = new StockReserved(reservationId, orderId, { clock });
316
+
317
+ await bus.publish([reserved]);
318
+ await bus.publish([reserved]);
319
+
320
+ const order = await orders.findById(new OrderId(orderId));
321
+ expect(order?.stockReservation?.value).toBe(reservationId);
322
+ });
323
+ });
324
+ ```
@@ -0,0 +1,191 @@
1
+ # Servicios de dominio en TypeScript
2
+
3
+ ## Cuándo
4
+
5
+ IDDD enumera tres situaciones [IDDD cap. 7]:
6
+ * realizar un proceso de negocio significativo;
7
+ * transformar un objeto de dominio de una composición a otra;
8
+ * calcular un valor que requiere datos de más de un objeto de dominio (el caso más común).
9
+
10
+ Ejemplo de IDDD: autenticar a un usuario solo si su tenant está activo. Ponerlo en `User` obliga al cliente a conocer `Tenant` y las reglas de cifrado; un servicio `AuthenticationService` expresa la operación del lenguaje y oculta los detalles [IDDD cap. 7].
11
+
12
+ ## Forma
13
+
14
+ El paquete no trae una clase base para servicios de dominio: son clases normales en `domain/`, con el nombre de la operación de negocio. Reciben agregados ya cargados y value objects, y devuelven un value object.
15
+
16
+ ```ts
17
+ import { Composite, Required, StringType } from '@archi-code/ddd/domain';
18
+ import type { SatisfiedBy } from '@archi-code/ddd/domain';
19
+ import { DomainException } from '@archi-code/ddd/exceptions';
20
+ import { Validations } from '@archi-code/validation';
21
+
22
+ @Validations([{ validator: 'isIn', properties: { values: ['PENINSULA', 'BALEARES', 'CANARIAS'] } }])
23
+ export class ShippingZone extends Required(StringType) {}
24
+
25
+ export class ShippingRate extends Composite({ zone: ShippingZone, price: Money }) {}
26
+
27
+ export class NoShippingToZone extends DomainException {
28
+ constructor(zone: string) {
29
+ super(`There is no shipping rate for ${zone}`, ['SHP001']);
30
+ }
31
+ }
32
+
33
+ export class ShippingRates extends Composite({ items: [ShippingRate] }) {
34
+ priceFor(zone: ShippingZone): Money {
35
+ const rate = this.items.find((candidate) => candidate.zone.equals(zone));
36
+ if (rate === undefined) {
37
+ throw new NoShippingToZone(zone.value);
38
+ }
39
+ return rate.price;
40
+ }
41
+ }
42
+
43
+ export class ShippingCostCalculator {
44
+ costFor(
45
+ order: Order,
46
+ zone: ShippingZone,
47
+ rates: ShippingRates,
48
+ freeShipping: SatisfiedBy<Order>,
49
+ ): Money {
50
+ const price = rates.priceFor(zone);
51
+ if (!freeShipping.isSatisfiedBy(order)) {
52
+ return price;
53
+ }
54
+ return new Money({ amount: new Cents(0), currency: price.currency });
55
+ }
56
+ }
57
+ ```
58
+
59
+ * Sin estado; recibe objetos de dominio (`Order`, la tabla de tarifas como composite, la zona) y devuelve un value object (`Money`, el composite de `Cents` y `Currency`).
60
+ * La condición de envío gratis es una `Specification<Order>` que recibe ya construida (`new OrderAbove(euros(5000))`); `SatisfiedBy<Order>` acepta también cualquier objeto con `isSatisfiedBy`, y se combina con `and`, `or` y `not`.
61
+ * El nombre de la clase y del método salen del lenguaje ubicuo.
62
+ * No guarda nada ni publica nada; eso lo hace el caso de uso que lo invoca.
63
+ * El servicio puede leer por repositorio [IDDD cap. 7]: con el paquete recibe un `AggregateFinder` (solo lectura) o los agregados ya cargados por el caso de uso, como aquí; nunca guarda ni elimina.
64
+
65
+ ## Interfaz separada o no
66
+
67
+ Si solo habrá una implementación y no depende de infraestructura, una clase concreta basta. IDDD discute la interfaz separada y advierte contra nombres de implementación sin significado como `Impl`: si hay interfaz, la implementación se nombra por lo que la distingue [IDDD cap. 7].
68
+
69
+ Cuando la implementación necesita tecnología (consultar otro contexto, un servicio de tipos de cambio), la interfaz va en el dominio, con value objects, y la implementación en infraestructura [IDDD cap. 7]. El adaptador envuelve los errores del cliente en `InternalErrorException` con `cause` y redondea de forma explícita (el dinero va en céntimos enteros):
70
+
71
+ ```ts
72
+ import { DateType, Required } from '@archi-code/ddd/domain';
73
+
74
+ export class RateDay extends Required(DateType) {}
75
+
76
+ export interface ExchangeRates {
77
+ convert(amount: Money, to: Currency, on: RateDay): Promise<Money>;
78
+ }
79
+ ```
80
+
81
+ ```ts
82
+ import { InternalErrorException } from '@archi-code/ddd/exceptions';
83
+
84
+ interface EcbClient {
85
+ rate(from: string, to: string, day: string): Promise<number>;
86
+ }
87
+
88
+ export class EcbExchangeRates implements ExchangeRates {
89
+ constructor(private readonly ecb: EcbClient) {}
90
+
91
+ async convert(amount: Money, to: Currency, on: RateDay): Promise<Money> {
92
+ let rate: number;
93
+ try {
94
+ rate = await this.ecb.rate(amount.currency.value, to.value, on.value.toISOString().slice(0, 10));
95
+ } catch (error) {
96
+ throw new InternalErrorException('reading the ECB exchange rates', { cause: error });
97
+ }
98
+ return new Money({ amount: new Cents(Math.round(amount.amount.value * rate)), currency: to });
99
+ }
100
+ }
101
+ ```
102
+
103
+ ## Validación de composiciones
104
+
105
+ Un servicio de dominio es buen sitio para validar que varios agregados sean coherentes entre sí [IDDD cap. 5]. Recibe los agregados que el caso de uso ya cargó y comprueba. Si detecta un problema, informa (error o evento); no corrige varios agregados a la vez: cada agregado se guarda en su propia escritura atómica.
106
+
107
+ Para informar de todos los problemas a la vez, reúne `ValidationIssue`s y lanza una sola `ValidationException` (HTTP 422 con `HttpErrorMapper`, cada problema con su `field` y su `code`); si basta con uno, lanza una `DomainException` con nombre y código propio:
108
+
109
+ ```ts
110
+ import { ValidationException } from '@archi-code/ddd/exceptions';
111
+ import type { ValidationIssue } from '@archi-code/ddd/exceptions';
112
+
113
+ export class ShipmentCheck {
114
+ assertCanShip(order: Order, customer: Customer): void {
115
+ const issues: ValidationIssue[] = [];
116
+ if (!order.customerId.equals(customer.id)) {
117
+ issues.push({ field: 'customer', code: 'SHP002', message: `Order ${order.id.value} belongs to another customer` });
118
+ }
119
+ if (customer.blocked.value) {
120
+ issues.push({ field: 'customer', code: 'CUS001', message: `Customer ${customer.id.value} is blocked` });
121
+ }
122
+ if (!order.status.isPending()) {
123
+ issues.push({ field: 'order', code: 'ORD001', message: `Order ${order.id.value} is no longer pending` });
124
+ }
125
+ if (issues.length > 0) {
126
+ throw new ValidationException(issues);
127
+ }
128
+ }
129
+ }
130
+ ```
131
+
132
+ ## Tests
133
+
134
+ Los servicios se prueban como cualquier objeto de dominio, con datos realistas y nombres del lenguaje [IDDD cap. 7]. Con Vitest, las fábricas de datos de prueba (`anOrderWorth`, `euros`, `standardRates`) viven en `test/support/` del proyecto:
135
+
136
+ ```ts
137
+ import { describe, expect, it } from 'vitest';
138
+
139
+ describe('ShippingCostCalculator', () => {
140
+ it('ships for free to the peninsula from 50 EUR', () => {
141
+ const cost = new ShippingCostCalculator().costFor(
142
+ anOrderWorth(5200),
143
+ new ShippingZone('PENINSULA'),
144
+ standardRates(),
145
+ new OrderAbove(euros(5000)),
146
+ );
147
+
148
+ expect(cost.toPrimitives()).toEqual({ amount: 0, currency: 'EUR' });
149
+ });
150
+
151
+ it('charges the rate of the Canary Islands below the minimum', () => {
152
+ const cost = new ShippingCostCalculator().costFor(
153
+ anOrderWorth(3000),
154
+ new ShippingZone('CANARIAS'),
155
+ standardRates(),
156
+ new OrderAbove(euros(5000)),
157
+ );
158
+
159
+ expect(cost.equals(euros(1295))).toBe(true);
160
+ });
161
+ });
162
+ ```
163
+
164
+ ```ts
165
+ import { FixedClock } from '@archi-code/ddd/infrastructure';
166
+
167
+ const clock = new FixedClock('2026-03-01T09:30:00Z');
168
+
169
+ export const euros = (cents: number): Money => Money.fromPrimitives({ amount: cents, currency: 'EUR' });
170
+
171
+ export const anOrderWorth = (cents: number): Order =>
172
+ Order.place(
173
+ OrderId.generate(),
174
+ new CustomerId('3c1d2e4f-5a6b-4c7d-8e9f-0a1b2c3d4e5f'),
175
+ OrderLines.fromPrimitives({
176
+ items: [{ sku: 'TORCH-01', quantity: 1, unitPrice: { amount: cents, currency: 'EUR' } }],
177
+ }),
178
+ ShippingAddress.fromPrimitives({ street: 'Calle Mayor 1', city: 'Huelva', country: 'ES' }),
179
+ clock,
180
+ );
181
+
182
+ export const standardRates = (): ShippingRates =>
183
+ ShippingRates.fromPrimitives({
184
+ items: [
185
+ { zone: 'PENINSULA', price: { amount: 495, currency: 'EUR' } },
186
+ { zone: 'CANARIAS', price: { amount: 1295, currency: 'EUR' } },
187
+ ],
188
+ });
189
+ ```
190
+
191
+ `Order`, `OrderLines`, `ShippingAddress`, `Money` y `Cents` son los de `ddd-skills/ddd-agregados/references/implementacion-ts.md`, y `OrderAbove` y `Customer`, los de `ddd-skills/ddd-servicios-capa-aplicacion/references/ejemplo-ecommerce.md`.
@@ -0,0 +1,118 @@
1
+ # Value Objects y entidades
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.** Diseño táctico DDD de Value Objects y entidades en TypeScript. Decide si un concepto es value object o entidad, cómo implementar value objects inmutables con igualdad por valor y comportamiento sin efectos secundarios (Money, identificadores, rangos, direcciones, tipos estándar y estados), cómo diseñar entidades con identidad estable y comportamiento con nombres del negocio en lugar de setters, estrategias y momento de generación de identidad, los tres niveles de validación (atributo, objeto completo, composición) y cómo persistir value objects sin que la base de datos deforme el modelo. Consúltala siempre que se cree o revise una clase del dominio, aparezcan primitivos sueltos (string para emails, number para dinero), getters y setters públicos, dudas sobre igualdad, ids o validaciones, o se pregunte "¿esto es una entidad?", aunque no se mencione DDD.
6
+
7
+ ## Con @archi-code/ddd
8
+
9
+ [Fuera de los libros: convención de @archi-code/ddd]
10
+
11
+ * Un value object es una subclase con nombre de un tipo del paquete (`class Email extends Required(StringType) {}`) con sus reglas en `@Validations` (de `@archi-code/validation`): se crea con `new` y su constructor lo deja siempre válido; no hay factorías `of()` ni `assertValid()`, y la normalización va en `filter`.
12
+ * Un valor de varios atributos (`Money`, `DateRange`, `OrderLine`, `OrderLines`) es `Composite({...})`, con las reglas que combinan campos como `custom`; un estado, un `EnumType`; un id, `IdType`, generado por el propio value object (`OrderId.generate()`), nunca por el repositorio.
13
+ * `Money` va en céntimos enteros (`Cents`, `Required(NumberType)` con `isInt`) en lugar de `bigint`: exacto hasta `Number.MAX_SAFE_INTEGER` (9.007.199.254.740.991 céntimos); el paquete no tiene tipo `bigint`.
14
+ * Los errores de una entrada se recogen con `ValueObjects.build({...})` y `ValidationException.errors`, no con un manejador de notificación.
15
+ * Un value object es opcional por defecto (`value: T | null`) y `Required(XType)` lo hace obligatorio. Los libros crean siempre el valor con contenido y modelan lo opcional en el campo (`reservation: ReservationId | null`); con el paquete valen las dos formas.
16
+ * La identidad de una entidad o raíz siempre es un value object obligatorio (`IdType` o `Required(...)`): un id sin valor lanza `RequiredValueException`, así dos entidades nuevas nunca son iguales por un id vacío [IDDD cap. 5].
17
+ * Todo el estado de un agregado es value objects (también un booleano), cada campo guardado tiene un getter público con su nombre sin `_` y los valores derivados son métodos, nunca getters. La reconstitución es `static fromPrimitives(PrimitiveTypes<X>)` con su `toPrimitives()`, que el repositorio llama directamente y cuyo viaje de ida y vuelta prueba el spec del agregado (nada lo comprueba en ejecución). Los libros solo piden getters donde hacen falta y un mapper en infraestructura.
18
+
19
+ Dos piezas básicas del modelo. Vernon insiste en un sesgo concreto: **modela con value objects siempre que puedas**. Incluso una entidad debería ser sobre todo un contenedor de value objects, no de entidades hijas, porque los valores son más fáciles de crear, probar, usar, optimizar y mantener [IDDD cap. 6].
20
+
21
+
22
+ ## Paso 1. ¿Value object o entidad?
23
+
24
+ Aplica estas preguntas en orden [IDDD cap. 5 y cap. 6]:
25
+
26
+ 1. ¿Importa distinguir **este** ejemplar de cualquier otro, aunque todos sus atributos coincidan, y seguirlo a lo largo de cambios? Sí → **entidad**.
27
+ 2. Si no, comprueba las características de valor. Si cumple la mayoría → **value object**:
28
+ * mide, cuantifica o describe algo del dominio (no es una cosa en sí);
29
+ * puede ser inmutable;
30
+ * forma un todo conceptual con atributos que solo tienen sentido juntos;
31
+ * se reemplaza entero cuando cambia la descripción;
32
+ * se compara por igualdad de valor;
33
+ * ofrece comportamiento sin efectos secundarios.
34
+ 3. ¿Te inclinas por entidad solo porque la base de datos lo guarda en su propia tabla? Entonces sigue siendo un value object. El modelo de datos se subordina al de dominio, no al revés [IDDD cap. 6].
35
+
36
+ Si dudas, empieza por value object: es más barato pasar de valor a entidad que deshacer una red de entidades [IDDD cap. 6].
37
+
38
+ | Concepto | Decisión típica | Por qué |
39
+ |---|---|---|
40
+ | Importe con moneda | Value object `Money` | Cuantifica; dos "17,00 €" son intercambiables |
41
+ | Dirección de envío de un pedido | Value object | Describe el pedido; si cambia, se reemplaza entera |
42
+ | Dirección en una agenda de clientes con historial y verificación | Puede ser entidad | Se sigue su ciclo de vida |
43
+ | Línea de pedido | Normalmente value object | Se identifica por su producto dentro del pedido |
44
+ | Pedido, Cliente, Pago | Entidad (y raíz de agregado) | Ciclo de vida e identidad propios |
45
+ | `OrderId`, `CustomerId` | Value object que contiene la identidad | Inmutable y estable [IDDD cap. 5] |
46
+ | Estado del pedido | Tipo estándar como value object | Describe el tipo de estado y puede llevar comportamiento [IDDD cap. 6] |
47
+
48
+ ## Paso 2. Implementar
49
+
50
+ * Value objects: `ddd-skills/ddd-value-objects-entidades/references/value-objects-ts.md`. Qué clase del paquete usar en cada caso y ejemplos de `Money`, identificadores, `Email`, `DateRange`, colecciones de valores y estados.
51
+ * Entidades: `ddd-skills/ddd-value-objects-entidades/references/entidades-ts.md`. Identidad, `Entity` y `AggregateRoot`, constructor completo, comportamiento con nombres del lenguaje, protección de invariantes, reconstitución con `fromPrimitives`.
52
+
53
+ Reglas mínimas que no se negocian (los nombres de los métodos son interfaces que revelan intención, ver Paso 5):
54
+
55
+ **Value objects**
56
+ * Inmutables: propiedades `readonly`, sin métodos que muten. Las operaciones devuelven nuevas instancias [IDDD cap. 6].
57
+ * Válidos desde su creación: el constructor rechaza estados imposibles (`Required(...)` y `@Validations`) [IDDD cap. 5; PHP: Value Objects].
58
+ * Igualdad por valor con `equals`, que el paquete ya da (clase y valor; fechas por instante, listas por contenido); en TypeScript `===` compara referencias `[Fuera de los libros]`.
59
+ * Los métodos reciben otros value objects, no entidades, para garantizar que no hay efectos secundarios [IDDD cap. 6].
60
+ * Nada de primitivos sueltos para conceptos del dominio: un `string` no puede tener comportamiento del negocio [IDDD cap. 6; PHP: Entities, "Objects vs. Primitive Types"].
61
+
62
+ **Entidades**
63
+ * Identidad estable, protegida contra cambios: se pasa a `super(id)` y queda `readonly` [IDDD cap. 5].
64
+ * Igualdad por identidad: `equals` de `Entity` y `AggregateRoot` compara clase e id.
65
+ * Sin setters públicos: métodos con verbos del negocio que protegen reglas: `backlogItem.commitTo(sprint)` y no `setSprintId(...)` + `setStatus(Committed)`, que deja la regla en cada cliente y el objeto a medias [IDDD cap. 1 y cap. 5].
66
+ * Diseño inicial mínimo: identidad, lo que sirve para encontrarla y el comportamiento esencial con los atributos que ese comportamiento necesita. El resto, después [IDDD cap. 5].
67
+
68
+ ## Paso 3. Identidad
69
+
70
+ Elige la estrategia con `ddd-skills/ddd-value-objects-entidades/references/entidades-ts.md` (sección Identidad). Resumen [IDDD cap. 5; PHP: Entities]:
71
+
72
+ | Estrategia | Riesgo principal |
73
+ |---|---|
74
+ | La aporta el usuario | Única pero incorrecta; cambiarla después es caro |
75
+ | La genera la aplicación (UUID) | Ninguno relevante; requiere un generador fiable |
76
+ | La genera la base de datos (secuencia) | Llega tarde: el evento de creación sale sin id, y los objetos nuevos parecen iguales en colecciones |
77
+ | La asigna otro contexto | Sincronización con el origen; la más compleja |
78
+
79
+ Recomendación por defecto: identidad generada por la aplicación, **temprana** (antes de persistir), generada por el propio value object de identidad (`OrderId.generate()`), nunca por el repositorio [IDDD cap. 5 para la identidad temprana; `[Fuera de los libros: convención de @archi-code/ddd]` para quién la genera]. Si conviene orden temporal en índices, `new OrderId(uuidv7())`: `IdType` acepta cualquier versión de UUID `[Fuera de los libros]`.
80
+
81
+ ## Paso 4. Validación en tres niveles
82
+
83
+ [IDDD cap. 5; PHP: Entities]
84
+
85
+ 1. **Atributo:** en el constructor del value object (`Required(...)` y `@Validations`).
86
+ 2. **Objeto completo:** reglas que combinan atributos (las de un valor de varios atributos, reglas `custom` de su `Composite`). IDDD sugiere un validador separado cuando esa validación cambia más que la entidad.
87
+ 3. **Composición:** reglas sobre varios objetos o agregados. Un servicio de dominio que recibe lo cargado o lee lo necesario con `AggregateFinder`.
88
+
89
+ Detalle, cómo acumular todos los errores (`ValueObjects.build` en lugar del patrón de notificación) y qué validar en la interfaz frente al dominio en `ddd-skills/ddd-value-objects-entidades/references/validacion.md`.
90
+
91
+ ## Paso 5. Diseño flexible y Specification
92
+
93
+ Revisa el diseño con los patrones de diseño flexible (Supple Design): interfaces que revelan intención, funciones sin efectos secundarios, aserciones, contornos conceptuales, clases autónomas y clausura de operaciones [IDDD cap. 5 y cap. 6 aplican varios]. Cuando una regla tiene nombre en el negocio, se usa en varios sitios o se combina con otras, modélala como **Specification** (`Specification<T>` de `@archi-code/ddd/domain`) para validar, seleccionar o construir [IDDD cap. 5; PHP: Repositories]. Guía y código en `ddd-skills/ddd-value-objects-entidades/references/specification-y-diseno-flexible.md`.
94
+
95
+ ## Paso 6. Persistencia sin fugas
96
+
97
+ Cómo guardar value objects (columnas embebidas, JSON, tabla propia para colecciones) sin convertirlos en entidades del modelo: `ddd-skills/ddd-value-objects-entidades/references/persistencia-valores.md`. La regla: diseña el modelo de datos al servicio del modelo de dominio [IDDD cap. 6]. Con el paquete, el repositorio parte de `order.toPrimitives()` (y carga con `Order.fromPrimitives(row).restoreVersion(version)`) y decide en infraestructura cómo reparte los primitivos en columnas.
98
+
99
+ ## Señales de alarma
100
+
101
+ * Clases con todos los campos públicos y `get`/`set` por cada uno: modelo anémico [IDDD cap. 1].
102
+ * `amount: number` para dinero, `email: string`, `status: string`.
103
+ * Métodos `update(dto)` que copian campos del DTO a la entidad.
104
+ * Entidades hijas con id propio que nadie busca por ese id (deberían ser valores).
105
+ * `equals` que compara ids `undefined` de objetos aún no guardados [IDDD cap. 5].
106
+ * Clases base propias (`ValueObject`, `Entity`, `deepEqual`) escritas en el proyecto: vienen del paquete. Tampoco factorías `of()`: un value object se crea con `new`.
107
+ * Getters calculados (`get total()`) en agregados, entidades o composites: rompen `toPrimitives()`; escribe un método.
108
+ * Un value object con un método que modifica una entidad recibida por parámetro [IDDD cap. 6].
109
+ * Validaciones de negocio repetidas en controlador, DTO y entidad, con criterios distintos.
110
+
111
+ ## Ejemplo
112
+
113
+ `ddd-skills/ddd-value-objects-entidades/references/ejemplo-ecommerce.md` diseña `Money`, `Quantity`, `OrderLine`, `OrderLines`, `ShippingAddress` y `OrderStatus`, y explica por qué `Order` es una entidad (la clase completa, con sus límites de agregado, está en `ddd-skills/ddd-agregados/references/implementacion-ts.md`; los límites se deciden en `ddd-skills/ddd-agregados/GUIA.md`).
114
+
115
+ ## Siguiente guía
116
+
117
+ * Dónde termina cada objeto y qué se guarda junto → `ddd-skills/ddd-agregados/GUIA.md`.
118
+ * Cómo se guardan → `ddd-skills/ddd-repositorios-persistencia/GUIA.md`.