@fiado/type-kit 3.472.0 → 3.473.0

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 (261) hide show
  1. package/_test_/unit/retailCatalog/catalogDebt.test.ts +123 -0
  2. package/_test_/unit/retailCatalog/catalogEnums.test.ts +2 -2
  3. package/_test_/unit/retailCatalog/ventaSaga.test.ts +152 -0
  4. package/_test_/unit/retailPromotions/computeBudgetUsedBps.test.ts +30 -0
  5. package/_test_/unit/retailPromotions/constants.test.ts +71 -0
  6. package/_test_/unit/retailPromotions/discountCodeAndWizardRequests.test.ts +84 -0
  7. package/_test_/unit/retailPromotions/salePromotionRequests.test.ts +151 -0
  8. package/_test_/unit/retailWizard/promotionM2mRequests.test.ts +189 -0
  9. package/_test_/unit/retailWizard/ventaSagaMessages.test.ts +163 -0
  10. package/_test_/unit/retailWizard/ventaSagaRequests.test.ts +59 -0
  11. package/_test_/unit/retailWizard/ventaSagaShapes.test.ts +99 -0
  12. package/bin/index.d.ts +1 -0
  13. package/bin/index.js +5 -2
  14. package/bin/retailCatalog/dtos/Product.d.ts +6 -0
  15. package/bin/retailCatalog/dtos/requests/BackofficeCreateProductRequest.d.ts +9 -0
  16. package/bin/retailCatalog/dtos/requests/BackofficeCreateProductRequest.js +30 -0
  17. package/bin/retailCatalog/dtos/requests/BackofficeRegisterAdjustmentRequest.d.ts +2 -0
  18. package/bin/retailCatalog/dtos/requests/BackofficeRegisterAdjustmentRequest.js +6 -0
  19. package/bin/retailCatalog/dtos/requests/BackofficeUpdateProductRequest.d.ts +9 -0
  20. package/bin/retailCatalog/dtos/requests/BackofficeUpdateProductRequest.js +30 -0
  21. package/bin/retailCatalog/dtos/requests/HoldStockRequest.d.ts +12 -0
  22. package/bin/retailCatalog/dtos/requests/HoldStockRequest.js +47 -0
  23. package/bin/retailCatalog/dtos/requests/ReserveStockRequest.d.ts +3 -0
  24. package/bin/retailCatalog/dtos/requests/ReserveStockRequest.js +6 -0
  25. package/bin/retailCatalog/dtos/requests/SuggestPiecesQuery.d.ts +2 -0
  26. package/bin/retailCatalog/dtos/requests/SuggestPiecesQuery.js +6 -0
  27. package/bin/retailCatalog/dtos/responses/GetInventorySkuResponse.d.ts +3 -0
  28. package/bin/retailCatalog/dtos/responses/HoldStockResponse.d.ts +8 -0
  29. package/bin/retailCatalog/dtos/responses/HoldStockResponse.js +2 -0
  30. package/bin/retailCatalog/dtos/responses/StoreCatalogItem.d.ts +5 -1
  31. package/bin/retailCatalog/enums/InventoryInboundMessageTypeEnum.d.ts +5 -0
  32. package/bin/retailCatalog/enums/InventoryInboundMessageTypeEnum.js +9 -0
  33. package/bin/retailCatalog/enums/InventoryItemStatusEnum.d.ts +4 -2
  34. package/bin/retailCatalog/enums/InventoryItemStatusEnum.js +3 -1
  35. package/bin/retailCatalog/enums/ReservePurposeEnum.d.ts +4 -0
  36. package/bin/retailCatalog/enums/ReservePurposeEnum.js +8 -0
  37. package/bin/retailCatalog/events/InventoryInboundMessage.d.ts +4 -0
  38. package/bin/retailCatalog/events/InventoryInboundMessage.js +2 -0
  39. package/bin/retailCatalog/events/InventoryReturnMessage.d.ts +12 -0
  40. package/bin/retailCatalog/events/InventoryReturnMessage.js +42 -0
  41. package/bin/retailCatalog/events/InventorySaleAppliedMessage.d.ts +12 -0
  42. package/bin/retailCatalog/events/InventorySaleAppliedMessage.js +42 -0
  43. package/bin/retailCatalog/index.d.ts +7 -0
  44. package/bin/retailCatalog/index.js +8 -0
  45. package/bin/retailPromotions/constants/bonusPreMoney.d.ts +7 -0
  46. package/bin/retailPromotions/constants/bonusPreMoney.js +21 -0
  47. package/bin/retailPromotions/constants/retailPromotionsErrorHttpStatus.d.ts +3 -0
  48. package/bin/retailPromotions/constants/retailPromotionsErrorHttpStatus.js +23 -0
  49. package/bin/retailPromotions/constants/salePromotionTransitions.d.ts +7 -0
  50. package/bin/retailPromotions/constants/salePromotionTransitions.js +13 -0
  51. package/bin/retailPromotions/dtos/DiscountCode.d.ts +35 -0
  52. package/bin/retailPromotions/dtos/DiscountCode.js +2 -0
  53. package/bin/retailPromotions/dtos/DiscountValue.d.ts +7 -0
  54. package/bin/retailPromotions/dtos/DiscountValue.js +2 -0
  55. package/bin/retailPromotions/dtos/LinePromotionDiscount.d.ts +10 -0
  56. package/bin/retailPromotions/dtos/LinePromotionDiscount.js +2 -0
  57. package/bin/retailPromotions/dtos/SalePromotion.d.ts +35 -0
  58. package/bin/retailPromotions/dtos/SalePromotion.js +2 -0
  59. package/bin/retailPromotions/dtos/SalePromotionLevers.d.ts +58 -0
  60. package/bin/retailPromotions/dtos/SalePromotionLevers.js +2 -0
  61. package/bin/retailPromotions/dtos/SalePromotionLogEntry.d.ts +18 -0
  62. package/bin/retailPromotions/dtos/SalePromotionLogEntry.js +2 -0
  63. package/bin/retailPromotions/dtos/requests/ChangeDiscountCodeStatusRequest.d.ts +6 -0
  64. package/bin/retailPromotions/dtos/requests/ChangeDiscountCodeStatusRequest.js +30 -0
  65. package/bin/retailPromotions/dtos/requests/ChangeSalePromotionStatusRequest.d.ts +6 -0
  66. package/bin/retailPromotions/dtos/requests/ChangeSalePromotionStatusRequest.js +30 -0
  67. package/bin/retailPromotions/dtos/requests/CreateDiscountCodeRequest.d.ts +17 -0
  68. package/bin/retailPromotions/dtos/requests/CreateDiscountCodeRequest.js +78 -0
  69. package/bin/retailPromotions/dtos/requests/CreateSalePromotionRequest.d.ts +19 -0
  70. package/bin/retailPromotions/dtos/requests/CreateSalePromotionRequest.js +83 -0
  71. package/bin/retailPromotions/dtos/requests/DiscountValueInput.d.ts +8 -0
  72. package/bin/retailPromotions/dtos/requests/DiscountValueInput.js +36 -0
  73. package/bin/retailPromotions/dtos/requests/EvaluateSalePromotionsRequest.d.ts +5 -0
  74. package/bin/retailPromotions/dtos/requests/EvaluateSalePromotionsRequest.js +31 -0
  75. package/bin/retailPromotions/dtos/requests/HoldSalePromotionRequest.d.ts +11 -0
  76. package/bin/retailPromotions/dtos/requests/HoldSalePromotionRequest.js +48 -0
  77. package/bin/retailPromotions/dtos/requests/ListDiscountCodesQuery.d.ts +10 -0
  78. package/bin/retailPromotions/dtos/requests/ListDiscountCodesQuery.js +49 -0
  79. package/bin/retailPromotions/dtos/requests/ListSalePromotionLogQuery.d.ts +8 -0
  80. package/bin/retailPromotions/dtos/requests/ListSalePromotionLogQuery.js +46 -0
  81. package/bin/retailPromotions/dtos/requests/ListSalePromotionsQuery.d.ts +7 -0
  82. package/bin/retailPromotions/dtos/requests/ListSalePromotionsQuery.js +40 -0
  83. package/bin/retailPromotions/dtos/requests/ListStoreSalePromotionsQuery.d.ts +4 -0
  84. package/bin/retailPromotions/dtos/requests/ListStoreSalePromotionsQuery.js +24 -0
  85. package/bin/retailPromotions/dtos/requests/SalePromotionLeversInput.d.ts +40 -0
  86. package/bin/retailPromotions/dtos/requests/SalePromotionLeversInput.js +166 -0
  87. package/bin/retailPromotions/dtos/requests/SalePromotionScopeInput.d.ts +14 -0
  88. package/bin/retailPromotions/dtos/requests/SalePromotionScopeInput.js +65 -0
  89. package/bin/retailPromotions/dtos/requests/UpdateSalePromotionRequest.d.ts +19 -0
  90. package/bin/retailPromotions/dtos/requests/UpdateSalePromotionRequest.js +85 -0
  91. package/bin/retailPromotions/dtos/responses/DiscountCodeResponses.d.ts +7 -0
  92. package/bin/retailPromotions/dtos/responses/DiscountCodeResponses.js +2 -0
  93. package/bin/retailPromotions/dtos/responses/SalePromotionCrudResponses.d.ts +30 -0
  94. package/bin/retailPromotions/dtos/responses/SalePromotionCrudResponses.js +2 -0
  95. package/bin/retailPromotions/dtos/responses/WizardSalePromotionResponses.d.ts +102 -0
  96. package/bin/retailPromotions/dtos/responses/WizardSalePromotionResponses.js +2 -0
  97. package/bin/retailPromotions/enums/CouponScopeEnum.d.ts +11 -0
  98. package/bin/retailPromotions/enums/CouponScopeEnum.js +15 -0
  99. package/bin/retailPromotions/enums/CouponStatusEnum.d.ts +11 -0
  100. package/bin/retailPromotions/enums/CouponStatusEnum.js +15 -0
  101. package/bin/retailPromotions/enums/DiscountCodeOriginEnum.d.ts +8 -0
  102. package/bin/retailPromotions/enums/DiscountCodeOriginEnum.js +12 -0
  103. package/bin/retailPromotions/enums/DiscountCodeStatusEnum.d.ts +11 -0
  104. package/bin/retailPromotions/enums/DiscountCodeStatusEnum.js +15 -0
  105. package/bin/retailPromotions/enums/DiscountCodeTransitionEnum.d.ts +7 -0
  106. package/bin/retailPromotions/enums/DiscountCodeTransitionEnum.js +11 -0
  107. package/bin/retailPromotions/enums/DiscountTypeEnum.d.ts +8 -0
  108. package/bin/retailPromotions/enums/DiscountTypeEnum.js +12 -0
  109. package/bin/retailPromotions/enums/EntitlementKindEnum.d.ts +9 -0
  110. package/bin/retailPromotions/enums/EntitlementKindEnum.js +13 -0
  111. package/bin/retailPromotions/enums/EntitlementStateEnum.d.ts +13 -0
  112. package/bin/retailPromotions/enums/EntitlementStateEnum.js +17 -0
  113. package/bin/retailPromotions/enums/GiftModeEnum.d.ts +8 -0
  114. package/bin/retailPromotions/enums/GiftModeEnum.js +12 -0
  115. package/bin/retailPromotions/enums/PromotionAppliesToEnum.d.ts +9 -0
  116. package/bin/retailPromotions/enums/PromotionAppliesToEnum.js +13 -0
  117. package/bin/retailPromotions/enums/RetailPromotionsErrorCodeEnum.d.ts +23 -0
  118. package/bin/retailPromotions/enums/RetailPromotionsErrorCodeEnum.js +27 -0
  119. package/bin/retailPromotions/enums/SaleCouponAvailabilityEnum.d.ts +10 -0
  120. package/bin/retailPromotions/enums/SaleCouponAvailabilityEnum.js +14 -0
  121. package/bin/retailPromotions/enums/SalePromotionLeverEnum.d.ts +13 -0
  122. package/bin/retailPromotions/enums/SalePromotionLeverEnum.js +17 -0
  123. package/bin/retailPromotions/enums/SalePromotionLogEventEnum.d.ts +14 -0
  124. package/bin/retailPromotions/enums/SalePromotionLogEventEnum.js +18 -0
  125. package/bin/retailPromotions/enums/SalePromotionLogFormatEnum.d.ts +5 -0
  126. package/bin/retailPromotions/enums/SalePromotionLogFormatEnum.js +9 -0
  127. package/bin/retailPromotions/enums/SalePromotionPhaseEnum.d.ts +11 -0
  128. package/bin/retailPromotions/enums/SalePromotionPhaseEnum.js +15 -0
  129. package/bin/retailPromotions/enums/SalePromotionStatusEnum.d.ts +12 -0
  130. package/bin/retailPromotions/enums/SalePromotionStatusEnum.js +16 -0
  131. package/bin/retailPromotions/enums/SalePromotionTransitionEnum.d.ts +13 -0
  132. package/bin/retailPromotions/enums/SalePromotionTransitionEnum.js +17 -0
  133. package/bin/retailPromotions/helpers/computeBudgetUsedBps.d.ts +3 -0
  134. package/bin/retailPromotions/helpers/computeBudgetUsedBps.js +15 -0
  135. package/bin/retailPromotions/helpers/resolveSalePromotionTransition.d.ts +4 -0
  136. package/bin/retailPromotions/helpers/resolveSalePromotionTransition.js +9 -0
  137. package/bin/retailPromotions/index.d.ts +46 -0
  138. package/bin/retailPromotions/index.js +67 -0
  139. package/bin/retailWizard/dtos/Sale.d.ts +8 -8
  140. package/bin/retailWizard/dtos/SaleLine.d.ts +4 -1
  141. package/bin/retailWizard/dtos/WizardFinancingPreview.d.ts +11 -0
  142. package/bin/retailWizard/dtos/WizardFinancingPreview.js +2 -0
  143. package/bin/retailWizard/dtos/WizardGiftLine.d.ts +8 -0
  144. package/bin/retailWizard/dtos/WizardGiftLine.js +2 -0
  145. package/bin/retailWizard/dtos/WizardLinePromotionFields.d.ts +15 -0
  146. package/bin/retailWizard/dtos/WizardLinePromotionFields.js +2 -0
  147. package/bin/retailWizard/dtos/WizardReservedLine.d.ts +4 -1
  148. package/bin/retailWizard/dtos/WizardSession.d.ts +10 -0
  149. package/bin/retailWizard/dtos/requests/AddProductRequest.d.ts +2 -0
  150. package/bin/retailWizard/dtos/requests/AddProductRequest.js +7 -0
  151. package/bin/retailWizard/dtos/requests/ApplyLinePromotionRequest.d.ts +15 -0
  152. package/bin/retailWizard/dtos/requests/ApplyLinePromotionRequest.js +58 -0
  153. package/bin/retailWizard/dtos/requests/ApplySalePromotionRequest.d.ts +9 -0
  154. package/bin/retailWizard/dtos/requests/ApplySalePromotionRequest.js +36 -0
  155. package/bin/retailWizard/dtos/requests/PinSessionPiecesRequest.d.ts +14 -0
  156. package/bin/retailWizard/dtos/requests/PinSessionPiecesRequest.js +60 -0
  157. package/bin/retailWizard/dtos/requests/QuoteRequest.d.ts +1 -1
  158. package/bin/retailWizard/dtos/requests/SetAnchorGiftsRequest.d.ts +14 -0
  159. package/bin/retailWizard/dtos/requests/SetAnchorGiftsRequest.js +56 -0
  160. package/bin/retailWizard/dtos/responses/PrivateWizardSessionResponse.d.ts +29 -0
  161. package/bin/retailWizard/dtos/responses/PrivateWizardSessionResponse.js +2 -0
  162. package/bin/retailWizard/dtos/responses/SetAnchorGiftsResponse.d.ts +5 -0
  163. package/bin/retailWizard/dtos/responses/SetAnchorGiftsResponse.js +2 -0
  164. package/bin/retailWizard/enums/RetailPromotionInboundMessageTypeEnum.d.ts +5 -0
  165. package/bin/retailWizard/enums/RetailPromotionInboundMessageTypeEnum.js +9 -0
  166. package/bin/retailWizard/enums/WizardLineRoleEnum.d.ts +4 -0
  167. package/bin/retailWizard/enums/WizardLineRoleEnum.js +8 -0
  168. package/bin/retailWizard/events/RetailCommissionInboundMessage.d.ts +19 -0
  169. package/bin/retailWizard/events/RetailCommissionInboundMessage.js +78 -0
  170. package/bin/retailWizard/events/RetailPromotionInboundMessage.d.ts +40 -0
  171. package/bin/retailWizard/events/RetailPromotionInboundMessage.js +160 -0
  172. package/bin/retailWizard/events/SaleCompletedV1.d.ts +2 -3
  173. package/bin/retailWizard/index.d.ts +13 -0
  174. package/bin/retailWizard/index.js +14 -0
  175. package/package.json +1 -1
  176. package/src/index.ts +3 -0
  177. package/src/retailCatalog/dtos/Product.ts +6 -0
  178. package/src/retailCatalog/dtos/requests/BackofficeCreateProductRequest.ts +30 -1
  179. package/src/retailCatalog/dtos/requests/BackofficeRegisterAdjustmentRequest.ts +6 -0
  180. package/src/retailCatalog/dtos/requests/BackofficeUpdateProductRequest.ts +30 -1
  181. package/src/retailCatalog/dtos/requests/HoldStockRequest.ts +28 -0
  182. package/src/retailCatalog/dtos/requests/ReserveStockRequest.ts +7 -1
  183. package/src/retailCatalog/dtos/requests/SuggestPiecesQuery.ts +7 -1
  184. package/src/retailCatalog/dtos/responses/GetInventorySkuResponse.ts +3 -0
  185. package/src/retailCatalog/dtos/responses/HoldStockResponse.ts +9 -0
  186. package/src/retailCatalog/dtos/responses/StoreCatalogItem.ts +6 -1
  187. package/src/retailCatalog/enums/InventoryInboundMessageTypeEnum.ts +5 -0
  188. package/src/retailCatalog/enums/InventoryItemStatusEnum.ts +3 -1
  189. package/src/retailCatalog/enums/ReservePurposeEnum.ts +4 -0
  190. package/src/retailCatalog/events/InventoryInboundMessage.ts +5 -0
  191. package/src/retailCatalog/events/InventoryReturnMessage.ts +26 -0
  192. package/src/retailCatalog/events/InventorySaleAppliedMessage.ts +26 -0
  193. package/src/retailCatalog/index.ts +9 -0
  194. package/src/retailPromotions/constants/bonusPreMoney.ts +19 -0
  195. package/src/retailPromotions/constants/retailPromotionsErrorHttpStatus.ts +21 -0
  196. package/src/retailPromotions/constants/salePromotionTransitions.ts +11 -0
  197. package/src/retailPromotions/dtos/DiscountCode.ts +36 -0
  198. package/src/retailPromotions/dtos/DiscountValue.ts +8 -0
  199. package/src/retailPromotions/dtos/LinePromotionDiscount.ts +10 -0
  200. package/src/retailPromotions/dtos/SalePromotion.ts +36 -0
  201. package/src/retailPromotions/dtos/SalePromotionLevers.ts +67 -0
  202. package/src/retailPromotions/dtos/SalePromotionLogEntry.ts +19 -0
  203. package/src/retailPromotions/dtos/requests/ChangeDiscountCodeStatusRequest.ts +15 -0
  204. package/src/retailPromotions/dtos/requests/ChangeSalePromotionStatusRequest.ts +15 -0
  205. package/src/retailPromotions/dtos/requests/CreateDiscountCodeRequest.ts +71 -0
  206. package/src/retailPromotions/dtos/requests/CreateSalePromotionRequest.ts +73 -0
  207. package/src/retailPromotions/dtos/requests/DiscountValueInput.ts +21 -0
  208. package/src/retailPromotions/dtos/requests/EvaluateSalePromotionsRequest.ts +16 -0
  209. package/src/retailPromotions/dtos/requests/HoldSalePromotionRequest.ts +32 -0
  210. package/src/retailPromotions/dtos/requests/ListDiscountCodesQuery.ts +33 -0
  211. package/src/retailPromotions/dtos/requests/ListSalePromotionLogQuery.ts +29 -0
  212. package/src/retailPromotions/dtos/requests/ListSalePromotionsQuery.ts +24 -0
  213. package/src/retailPromotions/dtos/requests/ListStoreSalePromotionsQuery.ts +10 -0
  214. package/src/retailPromotions/dtos/requests/SalePromotionLeversInput.ts +136 -0
  215. package/src/retailPromotions/dtos/requests/SalePromotionScopeInput.ts +45 -0
  216. package/src/retailPromotions/dtos/requests/UpdateSalePromotionRequest.ts +74 -0
  217. package/src/retailPromotions/dtos/responses/DiscountCodeResponses.ts +9 -0
  218. package/src/retailPromotions/dtos/responses/SalePromotionCrudResponses.ts +35 -0
  219. package/src/retailPromotions/dtos/responses/WizardSalePromotionResponses.ts +115 -0
  220. package/src/retailPromotions/enums/CouponScopeEnum.ts +11 -0
  221. package/src/retailPromotions/enums/CouponStatusEnum.ts +11 -0
  222. package/src/retailPromotions/enums/DiscountCodeOriginEnum.ts +8 -0
  223. package/src/retailPromotions/enums/DiscountCodeStatusEnum.ts +11 -0
  224. package/src/retailPromotions/enums/DiscountCodeTransitionEnum.ts +7 -0
  225. package/src/retailPromotions/enums/DiscountTypeEnum.ts +8 -0
  226. package/src/retailPromotions/enums/EntitlementKindEnum.ts +9 -0
  227. package/src/retailPromotions/enums/EntitlementStateEnum.ts +13 -0
  228. package/src/retailPromotions/enums/GiftModeEnum.ts +8 -0
  229. package/src/retailPromotions/enums/PromotionAppliesToEnum.ts +9 -0
  230. package/src/retailPromotions/enums/RetailPromotionsErrorCodeEnum.ts +23 -0
  231. package/src/retailPromotions/enums/SaleCouponAvailabilityEnum.ts +10 -0
  232. package/src/retailPromotions/enums/SalePromotionLeverEnum.ts +13 -0
  233. package/src/retailPromotions/enums/SalePromotionLogEventEnum.ts +14 -0
  234. package/src/retailPromotions/enums/SalePromotionLogFormatEnum.ts +5 -0
  235. package/src/retailPromotions/enums/SalePromotionPhaseEnum.ts +11 -0
  236. package/src/retailPromotions/enums/SalePromotionStatusEnum.ts +12 -0
  237. package/src/retailPromotions/enums/SalePromotionTransitionEnum.ts +13 -0
  238. package/src/retailPromotions/helpers/computeBudgetUsedBps.ts +15 -0
  239. package/src/retailPromotions/helpers/resolveSalePromotionTransition.ts +12 -0
  240. package/src/retailPromotions/index.ts +55 -0
  241. package/src/retailWizard/dtos/Sale.ts +8 -8
  242. package/src/retailWizard/dtos/SaleLine.ts +4 -1
  243. package/src/retailWizard/dtos/WizardFinancingPreview.ts +12 -0
  244. package/src/retailWizard/dtos/WizardGiftLine.ts +9 -0
  245. package/src/retailWizard/dtos/WizardLinePromotionFields.ts +16 -0
  246. package/src/retailWizard/dtos/WizardReservedLine.ts +4 -1
  247. package/src/retailWizard/dtos/WizardSession.ts +10 -0
  248. package/src/retailWizard/dtos/requests/AddProductRequest.ts +8 -1
  249. package/src/retailWizard/dtos/requests/ApplyLinePromotionRequest.ts +45 -0
  250. package/src/retailWizard/dtos/requests/ApplySalePromotionRequest.ts +24 -0
  251. package/src/retailWizard/dtos/requests/PinSessionPiecesRequest.ts +40 -0
  252. package/src/retailWizard/dtos/requests/QuoteRequest.ts +1 -1
  253. package/src/retailWizard/dtos/requests/SetAnchorGiftsRequest.ts +40 -0
  254. package/src/retailWizard/dtos/responses/PrivateWizardSessionResponse.ts +30 -0
  255. package/src/retailWizard/dtos/responses/SetAnchorGiftsResponse.ts +6 -0
  256. package/src/retailWizard/enums/RetailPromotionInboundMessageTypeEnum.ts +5 -0
  257. package/src/retailWizard/enums/WizardLineRoleEnum.ts +4 -0
  258. package/src/retailWizard/events/RetailCommissionInboundMessage.ts +54 -0
  259. package/src/retailWizard/events/RetailPromotionInboundMessage.ts +138 -0
  260. package/src/retailWizard/events/SaleCompletedV1.ts +2 -3
  261. package/src/retailWizard/index.ts +15 -0
@@ -0,0 +1,123 @@
1
+ import 'reflect-metadata';
2
+ import { plainToInstance, type ClassConstructor } from 'class-transformer';
3
+ import { validateSync, type ValidationError } from 'class-validator';
4
+ import {
5
+ BackofficeCreateProductRequest,
6
+ BackofficeRegisterAdjustmentRequest,
7
+ BackofficeUpdateProductRequest,
8
+ InventoryMovementReasonEnum,
9
+ InventoryMovementTypeEnum,
10
+ Product,
11
+ ProductCategoryEnum,
12
+ TrackingModeEnum,
13
+ type InventoryMovementEntry,
14
+ type ProductResponse,
15
+ } from '../../../src/retailCatalog';
16
+
17
+ function errorsOf<T extends object>(cls: ClassConstructor<T>, payload: object): string[] {
18
+ return validateSync(plainToInstance(cls, payload) as object, { whitelist: true, forbidNonWhitelisted: true }).map(
19
+ (e: ValidationError) => e.property,
20
+ );
21
+ }
22
+
23
+ const sheet = {
24
+ salePriority: 100,
25
+ highlights: ['Cámara de 50 MP', 'Batería de 5000 mAh'],
26
+ warrantyMonths: 12,
27
+ launchDate: '2026-03-01',
28
+ specs: { ram: '8GB', pantalla: '6.5"' },
29
+ };
30
+
31
+ const createOk = {
32
+ sku: 'PH-A15',
33
+ category: ProductCategoryEnum.PHONE,
34
+ trackingMode: TrackingModeEnum.IMEI,
35
+ sellable: true,
36
+ showInSalesFlow: true,
37
+ };
38
+
39
+ const productWrites: [string, ClassConstructor<object>, object][] = [
40
+ ['BackofficeCreateProductRequest', BackofficeCreateProductRequest, createOk],
41
+ ['BackofficeUpdateProductRequest', BackofficeUpdateProductRequest, {}],
42
+ ];
43
+
44
+ describe.each(productWrites)('%s — prioridad de venta y ficha comercial', (_name, cls, base) => {
45
+ it('acepta la prioridad y la ficha completa', () => {
46
+ expect(errorsOf(cls, { ...base, ...sheet })).toEqual([]);
47
+ });
48
+ it('sigue aceptando el cuerpo sin los campos nuevos', () => {
49
+ expect(errorsOf(cls, base)).toEqual([]);
50
+ });
51
+ it.each([0, 100])('acepta salePriority %p (límite)', (salePriority) => {
52
+ expect(errorsOf(cls, { ...base, salePriority })).toEqual([]);
53
+ });
54
+ it.each([-1, 101, 50.5, '50'])('rechaza salePriority %p', (salePriority) => {
55
+ expect(errorsOf(cls, { ...base, salePriority })).toContain('salePriority');
56
+ });
57
+ it('rechaza highlights que no son lista de textos o traen uno en blanco', () => {
58
+ expect(errorsOf(cls, { ...base, highlights: 'Cámara' })).toContain('highlights');
59
+ expect(errorsOf(cls, { ...base, highlights: ['ok', ' '] })).toContain('highlights');
60
+ expect(errorsOf(cls, { ...base, highlights: [1] })).toContain('highlights');
61
+ });
62
+ it.each([-1, 1.5, '12'])('rechaza warrantyMonths %p', (warrantyMonths) => {
63
+ expect(errorsOf(cls, { ...base, warrantyMonths })).toContain('warrantyMonths');
64
+ });
65
+ it('acepta warrantyMonths 0 (sin garantía)', () => {
66
+ expect(errorsOf(cls, { ...base, warrantyMonths: 0 })).toEqual([]);
67
+ });
68
+ it.each(['marzo 2026', '2026-13-01', 20260301])('rechaza launchDate %p', (launchDate) => {
69
+ expect(errorsOf(cls, { ...base, launchDate })).toContain('launchDate');
70
+ });
71
+ it('rechaza specs que no son objeto', () => {
72
+ expect(errorsOf(cls, { ...base, specs: 'ram 8GB' })).toContain('specs');
73
+ expect(errorsOf(cls, { ...base, specs: [['ram', '8GB']] })).toContain('specs');
74
+ });
75
+ });
76
+
77
+ describe('Product — prioridad de venta y ficha comercial en la lectura', () => {
78
+ it('acepta los campos nuevos y sigue válido sin ellos', () => {
79
+ const product = new Product();
80
+ product.salePriority = 100;
81
+ product.highlights = ['Cámara de 50 MP'];
82
+ product.warrantyMonths = 12;
83
+ product.launchDate = '2026-03-01';
84
+ const response: ProductResponse = product;
85
+ expect(response.salePriority).toBe(100);
86
+ expect(response.highlights).toEqual(['Cámara de 50 MP']);
87
+ expect(response.warrantyMonths).toBe(12);
88
+ expect(response.launchDate).toBe('2026-03-01');
89
+ const bare = new Product();
90
+ expect(bare.salePriority).toBeUndefined();
91
+ expect(bare.highlights).toBeUndefined();
92
+ });
93
+ });
94
+
95
+ describe('BackofficeRegisterAdjustmentRequest — reversesMovementId', () => {
96
+ const ok = { storeId: 'ST-1', sku: 'PH-A15', qty: 1, reason: InventoryMovementReasonEnum.ADJUSTMENT };
97
+ it('acepta el ajuste que corrige un movimiento', () => {
98
+ expect(errorsOf(BackofficeRegisterAdjustmentRequest, { ...ok, reversesMovementId: 'mov-123' })).toEqual([]);
99
+ });
100
+ it('sigue aceptando el ajuste sin él', () => {
101
+ expect(errorsOf(BackofficeRegisterAdjustmentRequest, ok)).toEqual([]);
102
+ });
103
+ it.each(['', ' ', 'mov 1', 42])('rechaza reversesMovementId %p', (reversesMovementId) => {
104
+ expect(errorsOf(BackofficeRegisterAdjustmentRequest, { ...ok, reversesMovementId })).toContain('reversesMovementId');
105
+ });
106
+ });
107
+
108
+ describe('InventoryMovementEntry — reason', () => {
109
+ it('expone el motivo del movimiento y sigue válido sin él', () => {
110
+ const base: InventoryMovementEntry = {
111
+ storeId: 'ST-1',
112
+ sku: 'PH-A15',
113
+ type: InventoryMovementTypeEnum.ADJUSTMENT,
114
+ qty: 1,
115
+ identifier: null,
116
+ occurredAt: '2026-09-28T10:00:00.000Z',
117
+ createdBy: 'user-1',
118
+ };
119
+ const withReason: InventoryMovementEntry = { ...base, reason: InventoryMovementReasonEnum.SHRINKAGE };
120
+ expect(withReason.reason).toBe('SHRINKAGE');
121
+ expect(base.reason).toBeUndefined();
122
+ });
123
+ });
@@ -39,9 +39,9 @@ describe('retailCatalog — enums de la operación independiente', () => {
39
39
  expect(Object.values(RetailCatalog.ItemTypeEnum)).toEqual(['PRODUCT', 'SERVICE']);
40
40
  });
41
41
 
42
- it('agrega IN_TRANSIT al estado de pieza sin quitar los existentes', () => {
42
+ it('agrega IN_TRANSIT y HELD al estado de pieza sin quitar los existentes', () => {
43
43
  expect(Object.values(RetailCatalog.InventoryItemStatusEnum)).toEqual([
44
- 'AVAILABLE', 'RESERVED', 'SOLD', 'RETURNED', 'DAMAGED', 'IN_TRANSIT',
44
+ 'AVAILABLE', 'RESERVED', 'SOLD', 'RETURNED', 'DAMAGED', 'IN_TRANSIT', 'HELD',
45
45
  ]);
46
46
  });
47
47
 
@@ -0,0 +1,152 @@
1
+ import 'reflect-metadata';
2
+ import { plainToInstance, type ClassConstructor } from 'class-transformer';
3
+ import { validateSync, type ValidationError } from 'class-validator';
4
+ import {
5
+ HoldStockRequest,
6
+ type HoldStockResponse,
7
+ InventoryInboundMessageTypeEnum,
8
+ InventoryItemStatusEnum,
9
+ InventoryReturnMessage,
10
+ InventorySaleAppliedMessage,
11
+ ProductCategoryEnum,
12
+ ReservePurposeEnum,
13
+ ReserveStockRequest,
14
+ type StoreCatalogItem,
15
+ SuggestPiecesQuery,
16
+ } from '../../../src/retailCatalog';
17
+
18
+ function paths(errors: ValidationError[], prefix = ''): string[] {
19
+ return errors.flatMap((e) => {
20
+ const p = prefix ? `${prefix}.${e.property}` : e.property;
21
+ return e.children && e.children.length > 0 ? paths(e.children, p) : [p];
22
+ });
23
+ }
24
+
25
+ function errorsOf<T extends object>(cls: ClassConstructor<T>, payload: object): string[] {
26
+ return paths(validateSync(plainToInstance(cls, payload) as object));
27
+ }
28
+
29
+ const saleLine = { lineId: 'L1', sku: 'KIT-1', qty: 1, components: [{ sku: 'SIM-1', qty: 1, identifiers: ['8952'] }] };
30
+
31
+ describe('InventoryItemStatusEnum — HELD', () => {
32
+ it('agrega HELD sin quitar los existentes', () => {
33
+ expect(InventoryItemStatusEnum.HELD).toBe('HELD');
34
+ expect(InventoryItemStatusEnum.AVAILABLE).toBe('AVAILABLE');
35
+ expect(InventoryItemStatusEnum.IN_TRANSIT).toBe('IN_TRANSIT');
36
+ });
37
+ });
38
+
39
+ describe('HoldStockRequest', () => {
40
+ const ok = {
41
+ lineId: 'L1',
42
+ reservationId: 'r',
43
+ sessionId: 's',
44
+ retailerId: 'ret',
45
+ components: [{ sku: 'PHONE-1', identifiers: ['356938035643809'] }],
46
+ };
47
+ it('acepta una línea con sus componentes', () => expect(errorsOf(HoldStockRequest, ok)).toEqual([]));
48
+ it('exige lineId y reservationId de la línea', () => {
49
+ const { lineId: _l, reservationId: _r, ...rest } = ok;
50
+ expect(errorsOf(HoldStockRequest, rest)).toEqual(expect.arrayContaining(['lineId', 'reservationId']));
51
+ });
52
+ it('exige sessionId y retailerId', () => {
53
+ const { sessionId: _s, retailerId: _r, ...rest } = ok;
54
+ expect(errorsOf(HoldStockRequest, rest)).toEqual(expect.arrayContaining(['sessionId', 'retailerId']));
55
+ });
56
+ it('rechaza sin componentes', () => expect(errorsOf(HoldStockRequest, { ...ok, components: [] })).toContain('components'));
57
+ it('valida cada componente anidado', () => {
58
+ expect(errorsOf(HoldStockRequest, { ...ok, components: [{ sku: 'PHONE-1', identifiers: [3] }] })).toContain(
59
+ 'components.0.identifiers',
60
+ );
61
+ expect(errorsOf(HoldStockRequest, { ...ok, components: [{ identifiers: ['x'] }] })).toContain('components.0.sku');
62
+ });
63
+ it('la respuesta es el espejo de la línea', () => {
64
+ const res: HoldStockResponse = { lineId: 'L1', reservationId: 'r', sessionId: 's', components: ok.components };
65
+ expect(res.components[0].identifiers).toEqual(['356938035643809']);
66
+ });
67
+ });
68
+
69
+ describe('InventorySaleAppliedMessage', () => {
70
+ const ok = {
71
+ type: 'SALE_APPLIED',
72
+ tenantId: 't',
73
+ storeId: 'st',
74
+ saleId: 'sale',
75
+ soldAt: '2026-09-28T10:00:00.000Z',
76
+ sessionId: 's',
77
+ retailerId: 'ret',
78
+ lines: [saleLine],
79
+ };
80
+ it('acepta el aviso de venta completo', () => expect(errorsOf(InventorySaleAppliedMessage, ok)).toEqual([]));
81
+ it('fija el type en SALE_APPLIED', () => {
82
+ expect(InventoryInboundMessageTypeEnum.SALE_APPLIED).toBe('SALE_APPLIED');
83
+ expect(errorsOf(InventorySaleAppliedMessage, { ...ok, type: 'RETURN' })).toContain('type');
84
+ });
85
+ it('exige tenantId, storeId, saleId y soldAt ISO', () => {
86
+ const { tenantId: _t, storeId: _s, saleId: _sa, ...rest } = ok;
87
+ expect(errorsOf(InventorySaleAppliedMessage, { ...rest, soldAt: 'ayer' })).toEqual(
88
+ expect.arrayContaining(['tenantId', 'storeId', 'saleId', 'soldAt']),
89
+ );
90
+ });
91
+ it('valida las líneas como SellStockRequest', () => {
92
+ expect(errorsOf(InventorySaleAppliedMessage, { ...ok, lines: [{ ...saleLine, qty: 0 }] })).toContain('lines.0.qty');
93
+ });
94
+ });
95
+
96
+ describe('InventoryReturnMessage', () => {
97
+ const ok = {
98
+ type: 'RETURN',
99
+ tenantId: 't',
100
+ storeId: 'st',
101
+ sessionId: 's',
102
+ returnedAt: '2026-09-29T10:00:00.000Z',
103
+ saleId: 'sale',
104
+ retailerId: 'ret',
105
+ soldAt: '2026-09-28T10:00:00.000Z',
106
+ lines: [saleLine],
107
+ };
108
+ it('acepta el aviso de devolución completo', () => expect(errorsOf(InventoryReturnMessage, ok)).toEqual([]));
109
+ it('fija el type en RETURN', () => {
110
+ expect(InventoryInboundMessageTypeEnum.RETURN).toBe('RETURN');
111
+ expect(errorsOf(InventoryReturnMessage, { ...ok, type: 'SALE_APPLIED' })).toContain('type');
112
+ });
113
+ it('exige tenantId, storeId, sessionId y returnedAt ISO', () => {
114
+ const { tenantId: _t, storeId: _s, sessionId: _se, ...rest } = ok;
115
+ expect(errorsOf(InventoryReturnMessage, { ...rest, returnedAt: 'hoy' })).toEqual(
116
+ expect.arrayContaining(['tenantId', 'storeId', 'sessionId', 'returnedAt']),
117
+ );
118
+ });
119
+ it('valida las líneas como ReturnStockRequest', () => {
120
+ expect(errorsOf(InventoryReturnMessage, { ...ok, lines: [] })).toContain('lines');
121
+ });
122
+ });
123
+
124
+ describe('ReserveStockRequest.purpose', () => {
125
+ const ok = { sessionId: 's', lineId: 'L1', reservationId: 'r', retailerId: 'ret', sku: 'SKU-1', qty: 1 };
126
+ it('sigue válido sin purpose', () => expect(errorsOf(ReserveStockRequest, ok)).toEqual([]));
127
+ it('acepta purpose GIFT', () => {
128
+ expect(ReservePurposeEnum.GIFT).toBe('GIFT');
129
+ expect(errorsOf(ReserveStockRequest, { ...ok, purpose: 'GIFT' })).toEqual([]);
130
+ });
131
+ it('rechaza un purpose desconocido', () => expect(errorsOf(ReserveStockRequest, { ...ok, purpose: 'PROMO' })).toContain('purpose'));
132
+ });
133
+
134
+ describe('SuggestPiecesQuery.sessionId', () => {
135
+ const ok = { sku: 'SKU-1', prefix: '3569' };
136
+ it('sigue válido sin sessionId', () => expect(errorsOf(SuggestPiecesQuery, ok)).toEqual([]));
137
+ it('acepta sessionId', () => expect(errorsOf(SuggestPiecesQuery, { ...ok, sessionId: 's' })).toEqual([]));
138
+ it('rechaza sessionId vacío', () => expect(errorsOf(SuggestPiecesQuery, { ...ok, sessionId: '' })).toContain('sessionId'));
139
+ });
140
+
141
+ describe('StoreCatalogItem.components[].category', () => {
142
+ it('acepta la categoría por componente y sigue válido sin ella', () => {
143
+ const item: Pick<StoreCatalogItem, 'components'> = {
144
+ components: [
145
+ { sku: 'PHONE-1', qty: 1, category: ProductCategoryEnum.PHONE },
146
+ { sku: 'SIM-1', qty: 1 },
147
+ ],
148
+ };
149
+ expect(item.components?.[0].category).toBe(ProductCategoryEnum.PHONE);
150
+ expect(item.components?.[1].category).toBeUndefined();
151
+ });
152
+ });
@@ -0,0 +1,30 @@
1
+ import 'reflect-metadata';
2
+ import { computeBudgetUsedBps } from '../../../src/retailPromotions/index';
3
+
4
+ describe('computeBudgetUsedBps', () => {
5
+ it('devuelve null si ninguna promo tiene tope', () => {
6
+ expect(computeBudgetUsedBps([])).toBeNull();
7
+ expect(computeBudgetUsedBps([{ budgetMaxCents: null, budgetConsumedCents: 5000 }])).toBeNull();
8
+ });
9
+
10
+ it('agrega consumido sobre tope solo de las promos con tope', () => {
11
+ const bps = computeBudgetUsedBps([
12
+ { budgetMaxCents: 100000, budgetConsumedCents: 25000 },
13
+ { budgetMaxCents: 300000, budgetConsumedCents: 75000 },
14
+ { budgetMaxCents: null, budgetConsumedCents: 999999 },
15
+ ]);
16
+ expect(bps).toBe(2500);
17
+ });
18
+
19
+ it('redondea hacia abajo a punto base entero', () => {
20
+ expect(computeBudgetUsedBps([{ budgetMaxCents: 300, budgetConsumedCents: 100 }])).toBe(3333);
21
+ });
22
+
23
+ it('refleja un consumo por arriba del tope sin recortarlo', () => {
24
+ expect(computeBudgetUsedBps([{ budgetMaxCents: 1000, budgetConsumedCents: 1100 }])).toBe(11000);
25
+ });
26
+
27
+ it('con tope 0 lo trata como sin presupuesto disponible', () => {
28
+ expect(computeBudgetUsedBps([{ budgetMaxCents: 0, budgetConsumedCents: 0 }])).toBeNull();
29
+ });
30
+ });
@@ -0,0 +1,71 @@
1
+ import 'reflect-metadata';
2
+ import {
3
+ BONUS_PRE_MONEY_CODES,
4
+ PRE_HANDLER_HTTP_STATUSES,
5
+ RETAIL_PROMOTIONS_ERROR_HTTP_STATUS,
6
+ RetailPromotionsErrorCodeEnum,
7
+ SalePromotionStatusEnum as S,
8
+ SalePromotionTransitionEnum as T,
9
+ SalePromotionLeverEnum,
10
+ resolveSalePromotionTransition,
11
+ } from '../../../src/retailPromotions/index';
12
+
13
+ describe('resolveSalePromotionTransition', () => {
14
+ it.each([
15
+ [S.DRAFT, T.PUBLISH, S.ACTIVE],
16
+ [S.ACTIVE, T.PAUSE, S.PAUSED],
17
+ [S.PAUSED, T.RESUME, S.ACTIVE],
18
+ [S.ACTIVE, T.EXPIRE, S.EXPIRED],
19
+ [S.PAUSED, T.EXPIRE, S.EXPIRED],
20
+ [S.DRAFT, T.ARCHIVE, S.ARCHIVED],
21
+ [S.EXPIRED, T.ARCHIVE, S.ARCHIVED],
22
+ ])('%s + %s → %s', (from, transition, to) => {
23
+ expect(resolveSalePromotionTransition(from, transition)).toBe(to);
24
+ });
25
+
26
+ it.each([
27
+ [S.DRAFT, T.PAUSE],
28
+ [S.DRAFT, T.EXPIRE],
29
+ [S.ACTIVE, T.PUBLISH],
30
+ [S.EXPIRED, T.RESUME],
31
+ [S.ARCHIVED, T.ARCHIVE],
32
+ [S.ARCHIVED, T.PUBLISH],
33
+ ])('%s + %s no se permite', (from, transition) => {
34
+ expect(resolveSalePromotionTransition(from, transition)).toBeNull();
35
+ });
36
+ });
37
+
38
+ describe('catálogo de errores', () => {
39
+ it('cada code tiene su status del catálogo cerrado', () => {
40
+ for (const code of Object.values(RetailPromotionsErrorCodeEnum)) {
41
+ expect([404, 409, 422]).toContain(RETAIL_PROMOTIONS_ERROR_HTTP_STATUS[code]);
42
+ }
43
+ expect(RETAIL_PROMOTIONS_ERROR_HTTP_STATUS.CODE_NOT_FOUND).toBe(404);
44
+ expect(RETAIL_PROMOTIONS_ERROR_HTTP_STATUS.DISCOUNT_AFTER_QUOTE).toBe(409);
45
+ expect(RETAIL_PROMOTIONS_ERROR_HTTP_STATUS.CODE_NOT_OWNED).toBe(422);
46
+ expect(Object.keys(RetailPromotionsErrorCodeEnum)).toHaveLength(16);
47
+ });
48
+
49
+ it('promo y código con rangos, signo o palancas inválidos son 422', () => {
50
+ expect(RetailPromotionsErrorCodeEnum.INVALID_SALE_PROMOTION).toBe('INVALID_SALE_PROMOTION');
51
+ expect(RetailPromotionsErrorCodeEnum.INVALID_DISCOUNT_CODE).toBe('INVALID_DISCOUNT_CODE');
52
+ expect(RETAIL_PROMOTIONS_ERROR_HTTP_STATUS.INVALID_SALE_PROMOTION).toBe(422);
53
+ expect(RETAIL_PROMOTIONS_ERROR_HTTP_STATUS.INVALID_DISCOUNT_CODE).toBe(422);
54
+ });
55
+ });
56
+
57
+ describe('palancas', () => {
58
+ it('son siete', () => {
59
+ expect(Object.values(SalePromotionLeverEnum)).toHaveLength(7);
60
+ });
61
+ });
62
+
63
+ describe('lista pre-dinero del bono', () => {
64
+ it('es la whitelist del wizard: SYSTEM_ERROR queda fuera', () => {
65
+ expect(BONUS_PRE_MONEY_CODES.has('ACCOUNT_NOT_FOUND')).toBe(true);
66
+ expect(BONUS_PRE_MONEY_CODES.has('IDEMPOTENCY_KEY_REQUIRED')).toBe(true);
67
+ expect(BONUS_PRE_MONEY_CODES.has('SYSTEM_ERROR')).toBe(false);
68
+ expect(BONUS_PRE_MONEY_CODES.size).toBe(9);
69
+ expect([...PRE_HANDLER_HTTP_STATUSES].sort()).toEqual([401, 403, 404, 405]);
70
+ });
71
+ });
@@ -0,0 +1,84 @@
1
+ import 'reflect-metadata';
2
+ import { plainToInstance } from 'class-transformer';
3
+ import { validate } from 'class-validator';
4
+ import {
5
+ ChangeDiscountCodeStatusRequest,
6
+ CreateDiscountCodeRequest,
7
+ EvaluateSalePromotionsRequest,
8
+ HoldSalePromotionRequest,
9
+ ListDiscountCodesQuery,
10
+ ListStoreSalePromotionsQuery,
11
+ } from '../../../src/retailPromotions/index';
12
+
13
+ type Dto = new () => object;
14
+
15
+ const errorPaths = async (dtoClass: Dto, raw: Record<string, unknown>): Promise<string[]> => {
16
+ const errors = await validate(plainToInstance(dtoClass, raw) as object);
17
+ const walk = (list: typeof errors, prefix: string): string[] =>
18
+ list.flatMap((e) => {
19
+ const path = prefix ? `${prefix}.${e.property}` : e.property;
20
+ return e.children && e.children.length > 0 ? walk(e.children, path) : [path];
21
+ });
22
+ return walk(errors, '');
23
+ };
24
+
25
+ describe('CreateDiscountCodeRequest', () => {
26
+ const base = { codeText: 'BUENFIN', discount: { type: 'AMOUNT', amountCents: 20000 } };
27
+
28
+ it('valida el alta mínima', async () => {
29
+ expect(await errorPaths(CreateDiscountCodeRequest, base)).toHaveLength(0);
30
+ });
31
+
32
+ it('rechaza texto vacío o demasiado largo', async () => {
33
+ expect(await errorPaths(CreateDiscountCodeRequest, { ...base, codeText: '' })).toContain('codeText');
34
+ expect(await errorPaths(CreateDiscountCodeRequest, { codeText: 'X' })).toContain('discount');
35
+ expect(await errorPaths(CreateDiscountCodeRequest, { ...base, codeText: 'X'.repeat(33) })).toContain('codeText');
36
+ });
37
+
38
+ it('valida el descuento anidado, los usos enteros y el appliesTo', async () => {
39
+ const paths = await errorPaths(CreateDiscountCodeRequest, {
40
+ ...base,
41
+ discount: { type: 'PERCENTAGE' },
42
+ maxUses: 1.5,
43
+ appliesTo: 'OTRA',
44
+ });
45
+ expect(paths).toEqual(expect.arrayContaining(['discount.percentBps', 'maxUses', 'appliesTo']));
46
+ });
47
+ });
48
+
49
+ describe('ChangeDiscountCodeStatusRequest', () => {
50
+ it('solo acepta VOID', async () => {
51
+ expect(await errorPaths(ChangeDiscountCodeStatusRequest, { transition: 'VOID' })).toHaveLength(0);
52
+ expect(await errorPaths(ChangeDiscountCodeStatusRequest, { transition: 'ACTIVATE' })).toContain('transition');
53
+ });
54
+ });
55
+
56
+ describe('ListDiscountCodesQuery', () => {
57
+ it('acepta estados de código y de cupón, y rechaza otros', async () => {
58
+ expect(await errorPaths(ListDiscountCodesQuery, { origin: 'SALE_COUPON', status: 'USED' })).toHaveLength(0);
59
+ expect(await errorPaths(ListDiscountCodesQuery, { origin: 'ADMIN', status: 'EXHAUSTED' })).toHaveLength(0);
60
+ expect(await errorPaths(ListDiscountCodesQuery, { status: 'DRAFT' })).toContain('status');
61
+ });
62
+ });
63
+
64
+ describe('piezas del wizard', () => {
65
+ it('listar y evaluar exigen sessionId', async () => {
66
+ expect(await errorPaths(ListStoreSalePromotionsQuery, {})).toContain('sessionId');
67
+ expect(await errorPaths(EvaluateSalePromotionsRequest, { sessionId: '' })).toContain('sessionId');
68
+ expect(await errorPaths(EvaluateSalePromotionsRequest, { sessionId: 's1', discountCode: 'BUENFIN' })).toHaveLength(0);
69
+ });
70
+
71
+ it('apartar acepta null como «Ninguna» y valida versión entera y SKUs de regalo', async () => {
72
+ expect(await errorPaths(HoldSalePromotionRequest, { promotionId: null, discountCode: null })).toHaveLength(0);
73
+ expect(
74
+ await errorPaths(HoldSalePromotionRequest, { promotionId: 'p1', promotionVersion: 2, giftSkus: ['SKU-9'] }),
75
+ ).toHaveLength(0);
76
+ expect(
77
+ await errorPaths(HoldSalePromotionRequest, { promotionId: 'p1', promotionVersion: '2', giftSkus: [1] }),
78
+ ).toEqual(expect.arrayContaining(['promotionVersion', 'giftSkus']));
79
+ });
80
+
81
+ it('apartar pide versión cuando trae promo', async () => {
82
+ expect(await errorPaths(HoldSalePromotionRequest, { promotionId: 'p1' })).toContain('promotionVersion');
83
+ });
84
+ });
@@ -0,0 +1,151 @@
1
+ import 'reflect-metadata';
2
+ import { plainToInstance } from 'class-transformer';
3
+ import { validate } from 'class-validator';
4
+ import {
5
+ ChangeSalePromotionStatusRequest,
6
+ CreateSalePromotionRequest,
7
+ DiscountTypeEnum,
8
+ ListSalePromotionLogQuery,
9
+ ListSalePromotionsQuery,
10
+ PromotionAppliesToEnum,
11
+ SalePromotionTransitionEnum,
12
+ UpdateSalePromotionRequest,
13
+ } from '../../../src/retailPromotions/index';
14
+
15
+ type Dto = new () => object;
16
+
17
+ /** Rutas de propiedad con error, incluidas las anidadas (`levers.gift.skus`). */
18
+ const errorPaths = async (dtoClass: Dto, raw: Record<string, unknown>): Promise<string[]> => {
19
+ const instance = plainToInstance(dtoClass, raw);
20
+ const errors = await validate(instance as object);
21
+ const walk = (list: typeof errors, prefix: string): string[] =>
22
+ list.flatMap((e) => {
23
+ const path = prefix ? `${prefix}.${e.property}` : e.property;
24
+ return e.children && e.children.length > 0 ? walk(e.children, path) : [path];
25
+ });
26
+ return walk(errors, '');
27
+ };
28
+
29
+ const base = {
30
+ name: 'Regalo de audífonos',
31
+ customerText: 'Llévate unos audífonos gratis',
32
+ levers: { deviceDiscount: { type: 'AMOUNT', amountCents: 50000 } },
33
+ };
34
+
35
+ describe('CreateSalePromotionRequest', () => {
36
+ it('valida el alta mínima y deja appliesTo ausente para que el lambda ponga el default', async () => {
37
+ const dto = plainToInstance(CreateSalePromotionRequest, base);
38
+ expect(await validate(dto as object)).toHaveLength(0);
39
+ expect(dto.appliesTo).toBeUndefined();
40
+ expect(dto.levers.deviceDiscount?.type).toBe(DiscountTypeEnum.AMOUNT);
41
+ });
42
+
43
+ it('exige nombre no vacío y palancas', async () => {
44
+ const paths = await errorPaths(CreateSalePromotionRequest, { customerText: 'x', name: '' });
45
+ expect(paths).toEqual(expect.arrayContaining(['name', 'levers']));
46
+ });
47
+
48
+ it('rechaza porcentaje en puntos base con decimales', async () => {
49
+ const paths = await errorPaths(CreateSalePromotionRequest, {
50
+ ...base,
51
+ levers: { deviceDiscount: { type: 'PERCENTAGE', percentBps: 12.5 } },
52
+ });
53
+ expect(paths).toContain('levers.deviceDiscount.percentBps');
54
+ });
55
+
56
+ it('pide el campo que corresponde al tipo de descuento', async () => {
57
+ const amount = await errorPaths(CreateSalePromotionRequest, {
58
+ ...base,
59
+ levers: { deviceDiscount: { type: 'AMOUNT', percentBps: 1000 } },
60
+ });
61
+ expect(amount).toContain('levers.deviceDiscount.amountCents');
62
+ const percent = await errorPaths(CreateSalePromotionRequest, {
63
+ ...base,
64
+ levers: { deviceDiscount: { type: 'PERCENTAGE', percentBps: 1000 } },
65
+ });
66
+ expect(percent).toHaveLength(0);
67
+ });
68
+
69
+ it('regalo por lista exige SKUs; por gama exige categoría y precio máximo', async () => {
70
+ const list = await errorPaths(CreateSalePromotionRequest, {
71
+ ...base,
72
+ levers: { gift: { mode: 'LIST', chooseCount: 1 } },
73
+ });
74
+ expect(list).toContain('levers.gift.skus');
75
+ const range = await errorPaths(CreateSalePromotionRequest, {
76
+ ...base,
77
+ levers: { gift: { mode: 'RANGE', chooseCount: 1 } },
78
+ });
79
+ expect(range).toEqual(expect.arrayContaining(['levers.gift.category', 'levers.gift.maxPriceCents']));
80
+ const ok = await errorPaths(CreateSalePromotionRequest, {
81
+ ...base,
82
+ levers: { gift: { mode: 'RANGE', category: 'AUDIO', maxPriceCents: 30000, chooseCount: 1 } },
83
+ });
84
+ expect(ok).toHaveLength(0);
85
+ });
86
+
87
+ it('valida las 7 palancas juntas', async () => {
88
+ const paths = await errorPaths(CreateSalePromotionRequest, {
89
+ ...base,
90
+ levers: {
91
+ deviceDiscount: { type: 'PERCENTAGE', percentBps: 1000 },
92
+ accessoryDiscount: { percentBps: 2000, maxAmountCents: null },
93
+ gift: { mode: 'LIST', skus: ['SKU-1'], chooseCount: 1 },
94
+ coupon: { type: 'AMOUNT', amountCents: 20000, validityDays: 30, scope: 'CHAIN' },
95
+ airtime: { rechargeCount: 3, rechargeAmountCents: 10000 },
96
+ extendedWarranty: { extraMonths: 6, costCents: 45000 },
97
+ walletBonus: { amountCents: 15000 },
98
+ },
99
+ appliesTo: 'ANY_FINANCIER',
100
+ scope: { tiers: ['HIGH'], productCategories: ['PHONE'], skus: null, storeIds: ['S1'] },
101
+ segmentation: { segmentIds: ['SEG_GOLD'], requiresActiveSurekeepCredit: true },
102
+ budgetMaxCents: null,
103
+ validFrom: '2026-10-01',
104
+ validUntil: null,
105
+ oncePerCustomer: true,
106
+ });
107
+ expect(paths).toHaveLength(0);
108
+ });
109
+
110
+ it('rechaza cupón sin alcance válido, appliesTo desconocido y fecha no ISO', async () => {
111
+ const paths = await errorPaths(CreateSalePromotionRequest, {
112
+ ...base,
113
+ levers: { coupon: { type: 'AMOUNT', amountCents: 100, validityDays: 30, scope: 'WORLD' } },
114
+ appliesTo: 'ANY',
115
+ validUntil: 'el viernes',
116
+ scope: { tiers: ['GIGANTE'] },
117
+ });
118
+ expect(paths).toEqual(
119
+ expect.arrayContaining(['levers.coupon.scope', 'appliesTo', 'validUntil', 'scope.tiers']),
120
+ );
121
+ });
122
+ });
123
+
124
+ describe('UpdateSalePromotionRequest', () => {
125
+ it('el parche vacío es válido y un campo mal tipado no', async () => {
126
+ expect(await errorPaths(UpdateSalePromotionRequest, {})).toHaveLength(0);
127
+ expect(await errorPaths(UpdateSalePromotionRequest, { appliesTo: PromotionAppliesToEnum.SUREKEEP_ONLY })).toHaveLength(0);
128
+ expect(await errorPaths(UpdateSalePromotionRequest, { budgetMaxCents: 10.5 })).toContain('budgetMaxCents');
129
+ });
130
+ });
131
+
132
+ describe('ChangeSalePromotionStatusRequest', () => {
133
+ it('acepta una transición del enum y rechaza un estado', async () => {
134
+ expect(await errorPaths(ChangeSalePromotionStatusRequest, { transition: SalePromotionTransitionEnum.PAUSE, reason: 'fin de stock' })).toHaveLength(0);
135
+ expect(await errorPaths(ChangeSalePromotionStatusRequest, { transition: 'ACTIVE' })).toContain('transition');
136
+ });
137
+ });
138
+
139
+ describe('queries paginadas', () => {
140
+ it('convierte limit del query string y lo acota', async () => {
141
+ const dto = plainToInstance(ListSalePromotionsQuery, { limit: '25', status: 'ARCHIVED' });
142
+ expect(await validate(dto as object)).toHaveLength(0);
143
+ expect(dto.limit).toBe(25);
144
+ expect(await errorPaths(ListSalePromotionsQuery, { limit: '500' })).toContain('limit');
145
+ });
146
+
147
+ it('el log acepta formato CSV y rechaza otro', async () => {
148
+ expect(await errorPaths(ListSalePromotionLogQuery, { format: 'CSV', promotionId: 'p1' })).toHaveLength(0);
149
+ expect(await errorPaths(ListSalePromotionLogQuery, { format: 'XLSX' })).toContain('format');
150
+ });
151
+ });