@fiado/type-kit 3.463.0 → 3.465.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 (198) hide show
  1. package/_test_/unit/moneyTransfer/dtos.test.ts +298 -0
  2. package/_test_/unit/moneyTransfer/enums.test.ts +56 -0
  3. package/_test_/unit/retailCatalog/backofficeRequests.test.ts +283 -0
  4. package/_test_/unit/retailCatalog/catalogEnums.test.ts +28 -1
  5. package/_test_/unit/retailCatalog/responseFields.test.ts +53 -0
  6. package/_test_/unit/retailCatalog/returnConditions.test.ts +64 -0
  7. package/_test_/unit/retailWizard/cancelSaleConditions.test.ts +41 -0
  8. package/_test_/unit/stpSpei/additive.test.ts +84 -0
  9. package/bin/bankAccount/enums/BeneficiaryAccountTypeEnum.d.ts +20 -0
  10. package/bin/bankAccount/enums/BeneficiaryAccountTypeEnum.js +24 -0
  11. package/bin/bankAccount/index.d.ts +1 -0
  12. package/bin/bankAccount/index.js +1 -0
  13. package/bin/index.d.ts +1 -0
  14. package/bin/index.js +5 -1
  15. package/bin/moneyTransfer/dtos/BatchScheduleRequest.d.ts +7 -0
  16. package/bin/moneyTransfer/dtos/BatchScheduleRequest.js +28 -0
  17. package/bin/moneyTransfer/dtos/CreateSettlementRequest.d.ts +12 -0
  18. package/bin/moneyTransfer/dtos/CreateSettlementRequest.js +42 -0
  19. package/bin/moneyTransfer/dtos/MoneyTransferProductRequest.d.ts +37 -0
  20. package/bin/moneyTransfer/dtos/MoneyTransferProductRequest.js +131 -0
  21. package/bin/moneyTransfer/dtos/MoneyTransferServiceCreateRequest.d.ts +22 -0
  22. package/bin/moneyTransfer/dtos/MoneyTransferServiceCreateRequest.js +88 -0
  23. package/bin/moneyTransfer/dtos/MoneyTransferServiceUpdateRequest.d.ts +22 -0
  24. package/bin/moneyTransfer/dtos/MoneyTransferServiceUpdateRequest.js +103 -0
  25. package/bin/moneyTransfer/dtos/PendingDetailsResponse.d.ts +8 -0
  26. package/bin/moneyTransfer/dtos/PendingDetailsResponse.js +7 -0
  27. package/bin/moneyTransfer/dtos/RescheduleSettlementRequest.d.ts +5 -0
  28. package/bin/moneyTransfer/dtos/RescheduleSettlementRequest.js +23 -0
  29. package/bin/moneyTransfer/dtos/SettlementActionRequest.d.ts +4 -0
  30. package/bin/moneyTransfer/dtos/SettlementActionRequest.js +21 -0
  31. package/bin/moneyTransfer/dtos/SettlementListResponse.d.ts +6 -0
  32. package/bin/moneyTransfer/dtos/SettlementListResponse.js +7 -0
  33. package/bin/moneyTransfer/dtos/SettlementResponse.d.ts +36 -0
  34. package/bin/moneyTransfer/dtos/SettlementResponse.js +10 -0
  35. package/bin/moneyTransfer/dtos/SettlementStatusMessage.d.ts +12 -0
  36. package/bin/moneyTransfer/dtos/SettlementStatusMessage.js +38 -0
  37. package/bin/moneyTransfer/dtos/TransactionDetailResponse.d.ts +24 -0
  38. package/bin/moneyTransfer/dtos/TransactionDetailResponse.js +10 -0
  39. package/bin/moneyTransfer/enums/SettlementModeEnum.d.ts +5 -0
  40. package/bin/moneyTransfer/enums/SettlementModeEnum.js +9 -0
  41. package/bin/moneyTransfer/enums/SettlementStatusEnum.d.ts +11 -0
  42. package/bin/moneyTransfer/enums/SettlementStatusEnum.js +15 -0
  43. package/bin/moneyTransfer/enums/SpeiOrderStatusEnum.d.ts +11 -0
  44. package/bin/moneyTransfer/enums/SpeiOrderStatusEnum.js +15 -0
  45. package/bin/moneyTransfer/enums/TransactionDetailStatusEnum.d.ts +7 -0
  46. package/bin/moneyTransfer/enums/TransactionDetailStatusEnum.js +11 -0
  47. package/bin/moneyTransfer/enums/TransferChannelEnum.d.ts +5 -0
  48. package/bin/moneyTransfer/enums/TransferChannelEnum.js +9 -0
  49. package/bin/moneyTransfer/index.d.ts +17 -0
  50. package/bin/moneyTransfer/index.js +40 -0
  51. package/bin/productCatalog/enums/ProductTypeEnum.d.ts +6 -1
  52. package/bin/productCatalog/enums/ProductTypeEnum.js +5 -0
  53. package/bin/provider/enums/Provider.d.ts +2 -1
  54. package/bin/provider/enums/Provider.js +1 -0
  55. package/bin/retailCatalog/constants/AdjustmentReasons.d.ts +3 -0
  56. package/bin/retailCatalog/constants/AdjustmentReasons.js +10 -0
  57. package/bin/retailCatalog/constants/NonBundleCategories.d.ts +3 -0
  58. package/bin/retailCatalog/constants/NonBundleCategories.js +6 -0
  59. package/bin/retailCatalog/dtos/Product.d.ts +3 -0
  60. package/bin/retailCatalog/dtos/requests/BackofficeCreateKitRequest.d.ts +14 -0
  61. package/bin/retailCatalog/dtos/requests/BackofficeCreateKitRequest.js +66 -0
  62. package/bin/retailCatalog/dtos/requests/BackofficeCreateProductRequest.d.ts +30 -0
  63. package/bin/retailCatalog/dtos/requests/BackofficeCreateProductRequest.js +117 -0
  64. package/bin/retailCatalog/dtos/requests/BackofficeCreateServiceRequest.d.ts +9 -0
  65. package/bin/retailCatalog/dtos/requests/BackofficeCreateServiceRequest.js +43 -0
  66. package/bin/retailCatalog/dtos/requests/BackofficeCreateSkuPriceRequest.d.ts +7 -0
  67. package/bin/retailCatalog/dtos/requests/BackofficeCreateSkuPriceRequest.js +34 -0
  68. package/bin/retailCatalog/dtos/requests/BackofficeCreateTransferRequest.d.ts +9 -0
  69. package/bin/retailCatalog/dtos/requests/BackofficeCreateTransferRequest.js +48 -0
  70. package/bin/retailCatalog/dtos/requests/BackofficeListTransfersQuery.d.ts +5 -0
  71. package/bin/retailCatalog/dtos/requests/BackofficeListTransfersQuery.js +23 -0
  72. package/bin/retailCatalog/dtos/requests/BackofficeRegisterAdjustmentRequest.d.ts +12 -0
  73. package/bin/retailCatalog/dtos/requests/BackofficeRegisterAdjustmentRequest.js +49 -0
  74. package/bin/retailCatalog/dtos/requests/BackofficeRegisterEntryRequest.d.ts +9 -0
  75. package/bin/retailCatalog/dtos/requests/BackofficeRegisterEntryRequest.js +43 -0
  76. package/bin/retailCatalog/dtos/requests/BackofficeUpdateProductRequest.d.ts +27 -0
  77. package/bin/retailCatalog/dtos/requests/BackofficeUpdateProductRequest.js +118 -0
  78. package/bin/retailCatalog/dtos/requests/ChangeInventoryStatusRequest.d.ts +1 -0
  79. package/bin/retailCatalog/dtos/requests/ChangeInventoryStatusRequest.js +1 -0
  80. package/bin/retailCatalog/dtos/requests/CreateProductRequest.d.ts +4 -1
  81. package/bin/retailCatalog/dtos/requests/CreateProductRequest.js +4 -1
  82. package/bin/retailCatalog/dtos/requests/RegisterInventoryRequest.d.ts +4 -1
  83. package/bin/retailCatalog/dtos/requests/RegisterInventoryRequest.js +4 -1
  84. package/bin/retailCatalog/dtos/requests/ReturnPieceCondition.d.ts +11 -0
  85. package/bin/retailCatalog/dtos/requests/ReturnPieceCondition.js +57 -0
  86. package/bin/retailCatalog/dtos/requests/ReturnStockLine.d.ts +6 -0
  87. package/bin/retailCatalog/dtos/requests/ReturnStockLine.js +27 -0
  88. package/bin/retailCatalog/dtos/requests/ReturnStockRequest.d.ts +2 -2
  89. package/bin/retailCatalog/dtos/requests/ReturnStockRequest.js +2 -2
  90. package/bin/retailCatalog/dtos/requests/TransferInventoryRequest.d.ts +4 -1
  91. package/bin/retailCatalog/dtos/requests/TransferInventoryRequest.js +4 -1
  92. package/bin/retailCatalog/dtos/requests/UpdateProductRequest.d.ts +4 -1
  93. package/bin/retailCatalog/dtos/requests/UpdateProductRequest.js +4 -1
  94. package/bin/retailCatalog/dtos/requests/UpdateProductStatusRequest.d.ts +4 -1
  95. package/bin/retailCatalog/dtos/requests/UpdateProductStatusRequest.js +4 -1
  96. package/bin/retailCatalog/dtos/responses/InventoryLensResponse.d.ts +3 -0
  97. package/bin/retailCatalog/dtos/responses/ListTransfersResponse.d.ts +5 -0
  98. package/bin/retailCatalog/dtos/responses/ListTransfersResponse.js +2 -0
  99. package/bin/retailCatalog/dtos/responses/ReserveStockResponse.d.ts +2 -0
  100. package/bin/retailCatalog/dtos/responses/SkuPriceEntry.d.ts +2 -0
  101. package/bin/retailCatalog/dtos/responses/StoreCatalogItem.d.ts +3 -0
  102. package/bin/retailCatalog/dtos/responses/TransferResponse.d.ts +16 -0
  103. package/bin/retailCatalog/dtos/responses/TransferResponse.js +2 -0
  104. package/bin/retailCatalog/enums/InventoryItemStatusEnum.d.ts +3 -1
  105. package/bin/retailCatalog/enums/InventoryItemStatusEnum.js +2 -0
  106. package/bin/retailCatalog/enums/InventoryMovementReasonEnum.d.ts +7 -0
  107. package/bin/retailCatalog/enums/InventoryMovementReasonEnum.js +11 -0
  108. package/bin/retailCatalog/enums/InventoryMovementTypeEnum.d.ts +4 -1
  109. package/bin/retailCatalog/enums/InventoryMovementTypeEnum.js +3 -0
  110. package/bin/retailCatalog/enums/ItemTypeEnum.d.ts +5 -0
  111. package/bin/retailCatalog/enums/ItemTypeEnum.js +9 -0
  112. package/bin/retailCatalog/enums/PriceSourceEnum.d.ts +3 -2
  113. package/bin/retailCatalog/enums/PriceSourceEnum.js +2 -1
  114. package/bin/retailCatalog/enums/ReturnConditionEnum.d.ts +5 -0
  115. package/bin/retailCatalog/enums/ReturnConditionEnum.js +9 -0
  116. package/bin/retailCatalog/enums/TransferStateEnum.d.ts +6 -0
  117. package/bin/retailCatalog/enums/TransferStateEnum.js +10 -0
  118. package/bin/retailCatalog/index.d.ts +20 -0
  119. package/bin/retailCatalog/index.js +21 -0
  120. package/bin/retailCatalog/validators/IsPieceRefConstraint.d.ts +6 -0
  121. package/bin/retailCatalog/validators/IsPieceRefConstraint.js +26 -0
  122. package/bin/retailWizard/dtos/requests/BackofficeCancelSaleRequest.d.ts +3 -0
  123. package/bin/retailWizard/dtos/requests/BackofficeCancelSaleRequest.js +9 -0
  124. package/bin/retailWizard/dtos/requests/CancelSaleRequest.d.ts +3 -0
  125. package/bin/retailWizard/dtos/requests/CancelSaleRequest.js +9 -0
  126. package/bin/stpSpei/dtos/ConsultStpOrderRequest.d.ts +8 -0
  127. package/bin/stpSpei/dtos/ConsultStpOrderRequest.js +6 -0
  128. package/bin/stpSpei/dtos/PutStpOrderRequest.d.ts +16 -8
  129. package/bin/stpSpei/enums/StpOrderSourceEnum.d.ts +9 -0
  130. package/bin/stpSpei/enums/StpOrderSourceEnum.js +13 -0
  131. package/bin/stpSpei/index.d.ts +2 -0
  132. package/bin/stpSpei/index.js +2 -0
  133. package/package.json +1 -1
  134. package/src/bankAccount/enums/BeneficiaryAccountTypeEnum.ts +20 -0
  135. package/src/bankAccount/index.ts +1 -0
  136. package/src/index.ts +4 -0
  137. package/src/moneyTransfer/dtos/BatchScheduleRequest.ts +15 -0
  138. package/src/moneyTransfer/dtos/CreateSettlementRequest.ts +26 -0
  139. package/src/moneyTransfer/dtos/MoneyTransferProductRequest.ts +111 -0
  140. package/src/moneyTransfer/dtos/MoneyTransferServiceCreateRequest.ts +62 -0
  141. package/src/moneyTransfer/dtos/MoneyTransferServiceUpdateRequest.ts +87 -0
  142. package/src/moneyTransfer/dtos/PendingDetailsResponse.ts +9 -0
  143. package/src/moneyTransfer/dtos/RescheduleSettlementRequest.ts +9 -0
  144. package/src/moneyTransfer/dtos/SettlementActionRequest.ts +7 -0
  145. package/src/moneyTransfer/dtos/SettlementListResponse.ts +7 -0
  146. package/src/moneyTransfer/dtos/SettlementResponse.ts +37 -0
  147. package/src/moneyTransfer/dtos/SettlementStatusMessage.ts +23 -0
  148. package/src/moneyTransfer/dtos/TransactionDetailResponse.ts +25 -0
  149. package/src/moneyTransfer/enums/SettlementModeEnum.ts +5 -0
  150. package/src/moneyTransfer/enums/SettlementStatusEnum.ts +11 -0
  151. package/src/moneyTransfer/enums/SpeiOrderStatusEnum.ts +11 -0
  152. package/src/moneyTransfer/enums/TransactionDetailStatusEnum.ts +7 -0
  153. package/src/moneyTransfer/enums/TransferChannelEnum.ts +5 -0
  154. package/src/moneyTransfer/index.ts +27 -0
  155. package/src/productCatalog/enums/ProductTypeEnum.ts +6 -1
  156. package/src/provider/enums/Provider.ts +2 -1
  157. package/src/retailCatalog/constants/AdjustmentReasons.ts +8 -0
  158. package/src/retailCatalog/constants/NonBundleCategories.ts +4 -0
  159. package/src/retailCatalog/dtos/Product.ts +3 -0
  160. package/src/retailCatalog/dtos/requests/BackofficeCreateKitRequest.ts +47 -0
  161. package/src/retailCatalog/dtos/requests/BackofficeCreateProductRequest.ts +92 -0
  162. package/src/retailCatalog/dtos/requests/BackofficeCreateServiceRequest.ts +26 -0
  163. package/src/retailCatalog/dtos/requests/BackofficeCreateSkuPriceRequest.ts +19 -0
  164. package/src/retailCatalog/dtos/requests/BackofficeCreateTransferRequest.ts +31 -0
  165. package/src/retailCatalog/dtos/requests/BackofficeListTransfersQuery.ts +9 -0
  166. package/src/retailCatalog/dtos/requests/BackofficeRegisterAdjustmentRequest.ts +34 -0
  167. package/src/retailCatalog/dtos/requests/BackofficeRegisterEntryRequest.ts +28 -0
  168. package/src/retailCatalog/dtos/requests/BackofficeUpdateProductRequest.ts +93 -0
  169. package/src/retailCatalog/dtos/requests/ChangeInventoryStatusRequest.ts +1 -0
  170. package/src/retailCatalog/dtos/requests/CreateProductRequest.ts +4 -1
  171. package/src/retailCatalog/dtos/requests/RegisterInventoryRequest.ts +4 -1
  172. package/src/retailCatalog/dtos/requests/ReturnPieceCondition.ts +48 -0
  173. package/src/retailCatalog/dtos/requests/ReturnStockLine.ts +13 -0
  174. package/src/retailCatalog/dtos/requests/ReturnStockRequest.ts +3 -3
  175. package/src/retailCatalog/dtos/requests/TransferInventoryRequest.ts +4 -1
  176. package/src/retailCatalog/dtos/requests/UpdateProductRequest.ts +4 -1
  177. package/src/retailCatalog/dtos/requests/UpdateProductStatusRequest.ts +4 -1
  178. package/src/retailCatalog/dtos/responses/InventoryLensResponse.ts +3 -0
  179. package/src/retailCatalog/dtos/responses/ListTransfersResponse.ts +6 -0
  180. package/src/retailCatalog/dtos/responses/ReserveStockResponse.ts +2 -0
  181. package/src/retailCatalog/dtos/responses/SkuPriceEntry.ts +2 -0
  182. package/src/retailCatalog/dtos/responses/StoreCatalogItem.ts +3 -0
  183. package/src/retailCatalog/dtos/responses/TransferResponse.ts +17 -0
  184. package/src/retailCatalog/enums/InventoryItemStatusEnum.ts +2 -0
  185. package/src/retailCatalog/enums/InventoryMovementReasonEnum.ts +7 -0
  186. package/src/retailCatalog/enums/InventoryMovementTypeEnum.ts +3 -0
  187. package/src/retailCatalog/enums/ItemTypeEnum.ts +5 -0
  188. package/src/retailCatalog/enums/PriceSourceEnum.ts +2 -1
  189. package/src/retailCatalog/enums/ReturnConditionEnum.ts +5 -0
  190. package/src/retailCatalog/enums/TransferStateEnum.ts +6 -0
  191. package/src/retailCatalog/index.ts +22 -0
  192. package/src/retailCatalog/validators/IsPieceRefConstraint.ts +19 -0
  193. package/src/retailWizard/dtos/requests/BackofficeCancelSaleRequest.ts +11 -2
  194. package/src/retailWizard/dtos/requests/CancelSaleRequest.ts +11 -2
  195. package/src/stpSpei/dtos/ConsultStpOrderRequest.ts +9 -0
  196. package/src/stpSpei/dtos/PutStpOrderRequest.ts +18 -9
  197. package/src/stpSpei/enums/StpOrderSourceEnum.ts +9 -0
  198. package/src/stpSpei/index.ts +2 -0
@@ -0,0 +1,298 @@
1
+ import "reflect-metadata";
2
+ import { plainToInstance } from "class-transformer";
3
+ import { validateSync } from "class-validator";
4
+ import {
5
+ BatchScheduleRequest,
6
+ CreateSettlementRequest,
7
+ MoneyTransferProductRequest,
8
+ MoneyTransferServiceCreateRequest,
9
+ MoneyTransferServiceUpdateRequest,
10
+ RescheduleSettlementRequest,
11
+ SettlementActionRequest,
12
+ SettlementModeEnum,
13
+ SettlementStatusMessage,
14
+ SpeiOrderStatusEnum,
15
+ TransferChannelEnum,
16
+ } from "../../../src/moneyTransfer";
17
+ import { AmountTypeEnum } from "../../../src/servicePayment/enums/AmountTypeEnum";
18
+ import { ReferenceTypeEnum } from "../../../src/servicePayment/enums/ReferenceTypeEnum";
19
+
20
+ const propertiesWithErrors = (dto: object) => validateSync(dto).map((error) => error.property);
21
+
22
+ const VALID_UUID = "3f6c2c5e-8b1a-4f3e-9d2a-1c4b5e6f7a8b";
23
+
24
+ const PRODUCTO_COMPLETO = {
25
+ idProducto: "SPEI-BATCH-001",
26
+ producto: "Dispersión SPEI en lote",
27
+ precio: 0,
28
+ amountType: AmountTypeEnum.VARIABLE,
29
+ fee: 5,
30
+ tipoReferencia: ReferenceTypeEnum.CLIENT_NUMBER,
31
+ verificateReference: false,
32
+ hasDigitoVerificador: false,
33
+ showAyuda: false,
34
+ legend: "",
35
+ logoSrc: "",
36
+ refSrc: "",
37
+ tipoFront: 1,
38
+ idCatTipoServicio: 7,
39
+ enabled: true,
40
+ channel: TransferChannelEnum.SPEI,
41
+ settlementMode: SettlementModeEnum.BATCH,
42
+ beneficiaryName: "ACME SA DE CV",
43
+ beneficiaryRfc: "ACM010101AB1",
44
+ minAmount: 1,
45
+ maxAmount: 50000,
46
+ batchSchedule: { cron: "0 18 * * 1-5", timezone: "America/Mexico_City" },
47
+ };
48
+
49
+ const SERVICIO_BASE = {
50
+ idServicio: 90001,
51
+ servicio: "Transferencias ACME",
52
+ categoria: "Transferencias",
53
+ subcategoria: "SPEI",
54
+ categoryOrder: 1,
55
+ subcategoryOrder: 1,
56
+ serviceOrder: 1,
57
+ beneficeId: "ben-1",
58
+ enabled: true,
59
+ currencyId: "MXN",
60
+ serviceCountryId: "MEX",
61
+ logoSrc: "",
62
+ idCatTipoServicio: 7,
63
+ };
64
+
65
+ describe("SettlementStatusMessage", () => {
66
+ it("acepta claveRastreo de hasta 30 chars y status del enum", () => {
67
+ const dto = plainToInstance(SettlementStatusMessage, {
68
+ claveRastreo: "A".repeat(30),
69
+ status: SpeiOrderStatusEnum.LQ,
70
+ });
71
+ expect(validateSync(dto)).toHaveLength(0);
72
+ });
73
+
74
+ it("rechaza claveRastreo de más de 30 chars", () => {
75
+ const dto = plainToInstance(SettlementStatusMessage, {
76
+ claveRastreo: "A".repeat(31),
77
+ status: SpeiOrderStatusEnum.LQ,
78
+ });
79
+ expect(propertiesWithErrors(dto)).toContain("claveRastreo");
80
+ });
81
+
82
+ it("rechaza claveRastreo vacía", () => {
83
+ const dto = plainToInstance(SettlementStatusMessage, { claveRastreo: "", status: SpeiOrderStatusEnum.D });
84
+ expect(propertiesWithErrors(dto)).toContain("claveRastreo");
85
+ });
86
+
87
+ it("rechaza status fuera del enum", () => {
88
+ const dto = plainToInstance(SettlementStatusMessage, { claveRastreo: "ABC123", status: "LIQUIDADO" });
89
+ expect(propertiesWithErrors(dto)).toContain("status");
90
+ });
91
+
92
+ it("reason es opcional pero acotado a 256 chars", () => {
93
+ const ok = plainToInstance(SettlementStatusMessage, { claveRastreo: "ABC", status: SpeiOrderStatusEnum.CN });
94
+ expect(validateSync(ok)).toHaveLength(0);
95
+ const largo = plainToInstance(SettlementStatusMessage, {
96
+ claveRastreo: "ABC",
97
+ status: SpeiOrderStatusEnum.CN,
98
+ reason: "x".repeat(257),
99
+ });
100
+ expect(propertiesWithErrors(largo)).toContain("reason");
101
+ });
102
+ });
103
+
104
+ describe("BatchScheduleRequest", () => {
105
+ it("exige cron y timezone no vacíos", () => {
106
+ const dto = plainToInstance(BatchScheduleRequest, { cron: "", timezone: "" });
107
+ const props = propertiesWithErrors(dto);
108
+ expect(props).toContain("cron");
109
+ expect(props).toContain("timezone");
110
+ });
111
+ });
112
+
113
+ describe("MoneyTransferProductRequest", () => {
114
+ it("un producto completo con batchSchedule válido pasa", () => {
115
+ const dto = plainToInstance(MoneyTransferProductRequest, PRODUCTO_COMPLETO);
116
+ expect(validateSync(dto)).toHaveLength(0);
117
+ expect(dto.batchSchedule).toBeInstanceOf(BatchScheduleRequest);
118
+ });
119
+
120
+ it("batchSchedule es opcional", () => {
121
+ const { batchSchedule: _omit, ...sinSchedule } = PRODUCTO_COMPLETO;
122
+ const dto = plainToInstance(MoneyTransferProductRequest, sinSchedule);
123
+ expect(validateSync(dto)).toHaveLength(0);
124
+ });
125
+
126
+ it("rechaza batchSchedule con cron vacío (validación anidada)", () => {
127
+ const dto = plainToInstance(MoneyTransferProductRequest, {
128
+ ...PRODUCTO_COMPLETO,
129
+ batchSchedule: { cron: "", timezone: "America/Mexico_City" },
130
+ });
131
+ expect(propertiesWithErrors(dto)).toContain("batchSchedule");
132
+ });
133
+
134
+ it("rechaza beneficiaryRfc fuera de 12-13 chars y beneficiaryName > 40", () => {
135
+ const dto = plainToInstance(MoneyTransferProductRequest, {
136
+ ...PRODUCTO_COMPLETO,
137
+ beneficiaryRfc: "CORTO",
138
+ beneficiaryName: "N".repeat(41),
139
+ });
140
+ const props = propertiesWithErrors(dto);
141
+ expect(props).toContain("beneficiaryRfc");
142
+ expect(props).toContain("beneficiaryName");
143
+ });
144
+
145
+ it("rechaza channel y settlementMode fuera de sus enums", () => {
146
+ const dto = plainToInstance(MoneyTransferProductRequest, {
147
+ ...PRODUCTO_COMPLETO,
148
+ channel: "SWIFT",
149
+ settlementMode: "LATER",
150
+ });
151
+ const props = propertiesWithErrors(dto);
152
+ expect(props).toContain("channel");
153
+ expect(props).toContain("settlementMode");
154
+ });
155
+ });
156
+
157
+ describe("MoneyTransferServiceCreateRequest", () => {
158
+ it("un servicio completo con un producto completo pasa", () => {
159
+ const dto = plainToInstance(MoneyTransferServiceCreateRequest, {
160
+ ...SERVICIO_BASE,
161
+ productoDTOList: [PRODUCTO_COMPLETO],
162
+ });
163
+ expect(validateSync(dto)).toHaveLength(0);
164
+ expect(dto.productoDTOList[0]).toBeInstanceOf(MoneyTransferProductRequest);
165
+ });
166
+
167
+ it("con un producto incompleto produce errores de validación anidados", () => {
168
+ const dto = plainToInstance(MoneyTransferServiceCreateRequest, {
169
+ ...SERVICIO_BASE,
170
+ productoDTOList: [{ idProducto: "X" }],
171
+ });
172
+ const errors = validateSync(dto);
173
+ const listError = errors.find((error) => error.property === "productoDTOList");
174
+ expect(listError).toBeDefined();
175
+ const nested = listError!.children?.[0]?.children?.map((child) => child.property) ?? [];
176
+ expect(nested).toContain("producto");
177
+ expect(nested).toContain("channel");
178
+ expect(nested).toContain("beneficiaryName");
179
+ });
180
+
181
+ it("exige al menos un producto", () => {
182
+ const dto = plainToInstance(MoneyTransferServiceCreateRequest, { ...SERVICIO_BASE, productoDTOList: [] });
183
+ expect(propertiesWithErrors(dto)).toContain("productoDTOList");
184
+ });
185
+
186
+ it("exige idServicio >= 90000 y currencyId de 3 chars", () => {
187
+ const dto = plainToInstance(MoneyTransferServiceCreateRequest, {
188
+ ...SERVICIO_BASE,
189
+ idServicio: 100,
190
+ currencyId: "MXNN",
191
+ productoDTOList: [PRODUCTO_COMPLETO],
192
+ });
193
+ const props = propertiesWithErrors(dto);
194
+ expect(props).toContain("idServicio");
195
+ expect(props).toContain("currencyId");
196
+ });
197
+ });
198
+
199
+ describe("MoneyTransferServiceUpdateRequest", () => {
200
+ it("acepta un payload vacío (todos los campos opcionales)", () => {
201
+ const dto = plainToInstance(MoneyTransferServiceUpdateRequest, {});
202
+ expect(validateSync(dto)).toHaveLength(0);
203
+ });
204
+
205
+ it("valida el producto anidado cuando viene productoDTOList", () => {
206
+ const dto = plainToInstance(MoneyTransferServiceUpdateRequest, {
207
+ productoDTOList: [{ idProducto: "X" }],
208
+ });
209
+ expect(propertiesWithErrors(dto)).toContain("productoDTOList");
210
+ });
211
+
212
+ it("sigue validando el tipo de los campos presentes", () => {
213
+ const dto = plainToInstance(MoneyTransferServiceUpdateRequest, { idServicio: 100, enabled: "si" });
214
+ const props = propertiesWithErrors(dto);
215
+ expect(props).toContain("idServicio");
216
+ expect(props).toContain("enabled");
217
+ });
218
+ });
219
+
220
+ describe("CreateSettlementRequest", () => {
221
+ it("acepta idempotencyKey UUID, ≥1 folio y sendNow", () => {
222
+ const dto = plainToInstance(CreateSettlementRequest, {
223
+ idempotencyKey: VALID_UUID,
224
+ transactionNumbers: ["TX-1", "TX-2"],
225
+ sendNow: true,
226
+ });
227
+ expect(validateSync(dto)).toHaveLength(0);
228
+ });
229
+
230
+ it("rechaza idempotencyKey que no es UUID", () => {
231
+ const dto = plainToInstance(CreateSettlementRequest, {
232
+ idempotencyKey: "no-uuid",
233
+ transactionNumbers: ["TX-1"],
234
+ sendNow: false,
235
+ });
236
+ expect(propertiesWithErrors(dto)).toContain("idempotencyKey");
237
+ });
238
+
239
+ it("rechaza transactionNumbers vacío", () => {
240
+ const dto = plainToInstance(CreateSettlementRequest, {
241
+ idempotencyKey: VALID_UUID,
242
+ transactionNumbers: [],
243
+ sendNow: false,
244
+ });
245
+ expect(propertiesWithErrors(dto)).toContain("transactionNumbers");
246
+ });
247
+
248
+ it("rechaza más de 1000 folios y elementos no string", () => {
249
+ const muchos = plainToInstance(CreateSettlementRequest, {
250
+ idempotencyKey: VALID_UUID,
251
+ transactionNumbers: Array.from({ length: 1001 }, (_, i) => `TX-${i}`),
252
+ sendNow: false,
253
+ });
254
+ expect(propertiesWithErrors(muchos)).toContain("transactionNumbers");
255
+ const noString = plainToInstance(CreateSettlementRequest, {
256
+ idempotencyKey: VALID_UUID,
257
+ transactionNumbers: [123],
258
+ sendNow: false,
259
+ });
260
+ expect(propertiesWithErrors(noString)).toContain("transactionNumbers");
261
+ });
262
+
263
+ it("scheduledAt es opcional, entero y >= 0", () => {
264
+ const dto = plainToInstance(CreateSettlementRequest, {
265
+ idempotencyKey: VALID_UUID,
266
+ transactionNumbers: ["TX-1"],
267
+ sendNow: false,
268
+ scheduledAt: -1,
269
+ });
270
+ expect(propertiesWithErrors(dto)).toContain("scheduledAt");
271
+ });
272
+ });
273
+
274
+ describe("SettlementActionRequest / RescheduleSettlementRequest", () => {
275
+ it("SettlementActionRequest exige idempotencyKey UUID", () => {
276
+ expect(propertiesWithErrors(plainToInstance(SettlementActionRequest, { idempotencyKey: "x" }))).toContain(
277
+ "idempotencyKey",
278
+ );
279
+ expect(validateSync(plainToInstance(SettlementActionRequest, { idempotencyKey: VALID_UUID }))).toHaveLength(0);
280
+ });
281
+
282
+ it("RescheduleSettlementRequest hereda idempotencyKey y exige scheduledAt entero >= 0", () => {
283
+ const dto = plainToInstance(RescheduleSettlementRequest, { idempotencyKey: "x", scheduledAt: 1.5 });
284
+ const props = propertiesWithErrors(dto);
285
+ expect(props).toContain("idempotencyKey");
286
+ expect(props).toContain("scheduledAt");
287
+ const ok = plainToInstance(RescheduleSettlementRequest, { idempotencyKey: VALID_UUID, scheduledAt: 0 });
288
+ expect(validateSync(ok)).toHaveLength(0);
289
+ });
290
+ });
291
+
292
+ describe("MoneyTransferServiceUpdateRequest — productoDTOList vacía", () => {
293
+ it("rechaza productoDTOList: [] (reemplaza la lista completa; no puede quedar vacía)", () => {
294
+ const dto = plainToInstance(MoneyTransferServiceUpdateRequest, { productoDTOList: [] });
295
+ const errors = validateSync(dto);
296
+ expect(errors.map(e => e.property)).toContain("productoDTOList");
297
+ });
298
+ });
@@ -0,0 +1,56 @@
1
+ import "reflect-metadata";
2
+ import {
3
+ SettlementModeEnum,
4
+ SettlementStatusEnum,
5
+ SpeiOrderStatusEnum,
6
+ TransactionDetailStatusEnum,
7
+ TransferChannelEnum,
8
+ } from "../../../src/moneyTransfer";
9
+
10
+ describe("moneyTransfer enums", () => {
11
+ it("TransferChannelEnum tiene SPEI y ACH con valores string estables", () => {
12
+ expect(TransferChannelEnum.SPEI).toBe("SPEI");
13
+ expect(TransferChannelEnum.ACH).toBe("ACH");
14
+ expect(Object.values(TransferChannelEnum).sort()).toEqual(["ACH", "SPEI"]);
15
+ });
16
+
17
+ it("SettlementModeEnum tiene IMMEDIATE y BATCH", () => {
18
+ expect(SettlementModeEnum.IMMEDIATE).toBe("IMMEDIATE");
19
+ expect(SettlementModeEnum.BATCH).toBe("BATCH");
20
+ expect(Object.values(SettlementModeEnum).sort()).toEqual(["BATCH", "IMMEDIATE"]);
21
+ });
22
+
23
+ it("SettlementStatusEnum tiene exactamente los 8 estados del ciclo de vida", () => {
24
+ expect(Object.values(SettlementStatusEnum).sort()).toEqual([
25
+ "CANCELLED",
26
+ "CREATED",
27
+ "FAILED",
28
+ "RETURNED",
29
+ "SCHEDULED",
30
+ "SENDING",
31
+ "SENT",
32
+ "SETTLED",
33
+ ]);
34
+ expect(SettlementStatusEnum.CREATED).toBe("CREATED");
35
+ expect(SettlementStatusEnum.SETTLED).toBe("SETTLED");
36
+ });
37
+
38
+ it("TransactionDetailStatusEnum tiene los 4 estados del detalle", () => {
39
+ expect(Object.values(TransactionDetailStatusEnum).sort()).toEqual([
40
+ "FAILED",
41
+ "PENDING_SETTLEMENT",
42
+ "RETURNED",
43
+ "SETTLED",
44
+ ]);
45
+ expect(TransactionDetailStatusEnum.PENDING_SETTLEMENT).toBe("PENDING_SETTLEMENT");
46
+ });
47
+
48
+ it("SpeiOrderStatusEnum refleja los códigos de STP más PENDING/UNKNOWN", () => {
49
+ expect(SpeiOrderStatusEnum.LQ).toBe("LQ");
50
+ expect(SpeiOrderStatusEnum.D).toBe("D");
51
+ expect(SpeiOrderStatusEnum.CN).toBe("CN");
52
+ expect(SpeiOrderStatusEnum.PENDING).toBe("PENDING");
53
+ expect(SpeiOrderStatusEnum.UNKNOWN).toBe("UNKNOWN");
54
+ expect(Object.values(SpeiOrderStatusEnum).sort()).toEqual(["CN", "D", "LQ", "PENDING", "UNKNOWN"]);
55
+ });
56
+ });
@@ -0,0 +1,283 @@
1
+ import 'reflect-metadata';
2
+ import { plainToInstance, type ClassConstructor } from 'class-transformer';
3
+ import { validateSync, type ValidationError } from 'class-validator';
4
+ import {
5
+ BackofficeCreateKitRequest,
6
+ BackofficeCreateProductRequest,
7
+ BackofficeCreateServiceRequest,
8
+ BackofficeCreateSkuPriceRequest,
9
+ BackofficeCreateTransferRequest,
10
+ BackofficeListTransfersQuery,
11
+ BackofficeRegisterAdjustmentRequest,
12
+ BackofficeRegisterEntryRequest,
13
+ BackofficeUpdateProductRequest,
14
+ ADJUSTMENT_REASONS,
15
+ InventoryMovementReasonEnum,
16
+ IsPieceRefConstraint,
17
+ NON_BUNDLE_CATEGORIES,
18
+ ProductCategoryEnum,
19
+ TrackingModeEnum,
20
+ TransferStateEnum,
21
+ type ListTransfersResponse,
22
+ type TransferResponse,
23
+ } from '../../../src/retailCatalog';
24
+
25
+ function paths(errors: ValidationError[], prefix = ''): string[] {
26
+ return errors.flatMap((e) => {
27
+ const p = prefix ? `${prefix}.${e.property}` : e.property;
28
+ return e.children && e.children.length > 0 ? paths(e.children, p) : [p];
29
+ });
30
+ }
31
+
32
+ function errorsOf<T extends object>(cls: ClassConstructor<T>, payload: object): string[] {
33
+ return paths(validateSync(plainToInstance(cls, payload) as object, { whitelist: true, forbidNonWhitelisted: true }));
34
+ }
35
+
36
+ function without<T extends object>(obj: T, key: keyof T): object {
37
+ return Object.fromEntries(Object.entries(obj).filter(([k]) => k !== key));
38
+ }
39
+
40
+ describe('BackofficeCreateProductRequest (backofficeCreateProduct)', () => {
41
+ const ok = {
42
+ sku: 'PH-A15',
43
+ category: ProductCategoryEnum.PHONE,
44
+ trackingMode: TrackingModeEnum.IMEI,
45
+ sellable: true,
46
+ showInSalesFlow: true,
47
+ };
48
+ it('acepta el mínimo', () => expect(errorsOf(BackofficeCreateProductRequest, ok)).toEqual([]));
49
+ it('acepta los datos descriptivos opcionales', () => {
50
+ const full = { ...ok, brand: 'Samsung', model: 'A15', capacity: '128GB', color: 'Negro', description: 'Gama media', imageUrl: 'https://x/y.png', tier: 'MID' };
51
+ expect(errorsOf(BackofficeCreateProductRequest, full)).toEqual([]);
52
+ });
53
+ it.each(['sku', 'category', 'trackingMode', 'sellable', 'showInSalesFlow'] as const)('rechaza sin %s', (key) => {
54
+ expect(errorsOf(BackofficeCreateProductRequest, without(ok, key))).toContain(key);
55
+ });
56
+ it('rechaza BUNDLE: el kit tiene su propio endpoint', () => {
57
+ expect(errorsOf(BackofficeCreateProductRequest, { ...ok, category: ProductCategoryEnum.BUNDLE })).toContain('category');
58
+ });
59
+ it('rechaza enums inválidos y booleanos como string', () => {
60
+ const errors = errorsOf(BackofficeCreateProductRequest, { ...ok, category: 'TABLET', trackingMode: 'RFID', sellable: 'true', tier: 'ULTRA' });
61
+ expect(errors).toEqual(expect.arrayContaining(['category', 'trackingMode', 'sellable', 'tier']));
62
+ });
63
+ it('rechaza sku con espacios y descripción en blanco', () => {
64
+ const errors = errorsOf(BackofficeCreateProductRequest, { ...ok, sku: ' ', description: ' ' });
65
+ expect(errors).toEqual(expect.arrayContaining(['sku', 'description']));
66
+ });
67
+ });
68
+
69
+ describe('BackofficeCreateKitRequest (backofficeCreateKit)', () => {
70
+ const ok = { sku: 'KIT-A15', sellable: true, showInSalesFlow: true, components: [{ sku: 'PH-A15', qty: 1 }, { sku: 'SIM-1', qty: 1 }] };
71
+ it('acepta un kit con componentes', () => expect(errorsOf(BackofficeCreateKitRequest, ok)).toEqual([]));
72
+ it.each(['sku', 'sellable', 'showInSalesFlow', 'components'] as const)('rechaza sin %s', (key) => {
73
+ expect(errorsOf(BackofficeCreateKitRequest, without(ok, key))).toContain(key);
74
+ });
75
+ it('rechaza un kit sin componentes', () => expect(errorsOf(BackofficeCreateKitRequest, { ...ok, components: [] })).toContain('components'));
76
+ it('valida cada componente', () => {
77
+ const errors = errorsOf(BackofficeCreateKitRequest, { ...ok, components: [{ sku: 'SIM 1', qty: 0 }] });
78
+ expect(errors).toEqual(expect.arrayContaining(['components.0.sku', 'components.0.qty']));
79
+ });
80
+ it('rechaza descripción en blanco', () => expect(errorsOf(BackofficeCreateKitRequest, { ...ok, description: ' ' })).toContain('description'));
81
+ });
82
+
83
+ describe('BackofficeCreateServiceRequest (backofficeCreateService)', () => {
84
+ const ok = { sku: 'TA-100', description: 'Tiempo aire 100', sellable: true, showInSalesFlow: true };
85
+ it('acepta un servicio', () => expect(errorsOf(BackofficeCreateServiceRequest, ok)).toEqual([]));
86
+ it.each(['sku', 'description', 'sellable', 'showInSalesFlow'] as const)('rechaza sin %s', (key) => {
87
+ expect(errorsOf(BackofficeCreateServiceRequest, without(ok, key))).toContain(key);
88
+ });
89
+ it('rechaza descripción en blanco y sellable no booleano', () => {
90
+ const errors = errorsOf(BackofficeCreateServiceRequest, { ...ok, description: ' ', sellable: 1 });
91
+ expect(errors).toEqual(expect.arrayContaining(['description', 'sellable']));
92
+ });
93
+ });
94
+
95
+ describe('BackofficeUpdateProductRequest (backofficeUpdateProduct)', () => {
96
+ it('acepta un cuerpo vacío (parcial)', () => expect(errorsOf(BackofficeUpdateProductRequest, {})).toEqual([]));
97
+ it('acepta flags, categoría, datos y componentes', () => {
98
+ const body = { sellable: false, showInSalesFlow: true, category: 'ACCESSORY', description: 'Funda', components: [{ sku: 'SIM-1', qty: 2 }] };
99
+ expect(errorsOf(BackofficeUpdateProductRequest, body)).toEqual([]);
100
+ });
101
+ it('rechaza pasar a BUNDLE y componentes inválidos', () => {
102
+ const errors = errorsOf(BackofficeUpdateProductRequest, { category: 'BUNDLE', components: [{ sku: 'SIM-1', qty: 0 }] });
103
+ expect(errors).toEqual(expect.arrayContaining(['category', 'components.0.qty']));
104
+ });
105
+ it('rechaza tipos incorrectos y texto en blanco', () => {
106
+ const errors = errorsOf(BackofficeUpdateProductRequest, { sellable: 'no', brand: ' ', tier: 'ULTRA' });
107
+ expect(errors).toEqual(expect.arrayContaining(['sellable', 'brand', 'tier']));
108
+ });
109
+ it('no expone itemType ni trackingMode (inmutables)', () => {
110
+ const dto = new BackofficeUpdateProductRequest();
111
+ expect('itemType' in dto || 'trackingMode' in dto).toBe(false);
112
+ });
113
+ });
114
+
115
+ describe('BackofficeCreateSkuPriceRequest (backofficeCreateSkuPrice)', () => {
116
+ const ok = { basePriceCents: 90000 };
117
+ it('acepta solo el precio base', () => expect(errorsOf(BackofficeCreateSkuPriceRequest, ok)).toEqual([]));
118
+ it('acepta lista y combo', () => {
119
+ expect(errorsOf(BackofficeCreateSkuPriceRequest, { ...ok, listPriceCents: 100000, comboPriceCents: 85000 })).toEqual([]);
120
+ expect(errorsOf(BackofficeCreateSkuPriceRequest, { ...ok, listPriceCents: null })).toEqual([]);
121
+ });
122
+ it('rechaza sin precio base', () => expect(errorsOf(BackofficeCreateSkuPriceRequest, {})).toContain('basePriceCents'));
123
+ it('rechaza negativos, decimales y strings', () => {
124
+ const errors = errorsOf(BackofficeCreateSkuPriceRequest, { basePriceCents: -1, listPriceCents: 10.5, comboPriceCents: '100' });
125
+ expect(errors).toEqual(expect.arrayContaining(['basePriceCents', 'listPriceCents', 'comboPriceCents']));
126
+ });
127
+ });
128
+
129
+ describe('BackofficeRegisterEntryRequest (backofficeRegisterEntry)', () => {
130
+ const ok = { storeId: 'st-1', sku: 'SIM-1', qty: 5 };
131
+ it('acepta una entrada por cantidad', () => expect(errorsOf(BackofficeRegisterEntryRequest, ok)).toEqual([]));
132
+ it('acepta una entrada con identificadores', () => {
133
+ expect(errorsOf(BackofficeRegisterEntryRequest, { ...ok, qty: 2, identifiers: ['356938035643809', '356938035643810'] })).toEqual([]);
134
+ });
135
+ it.each(['storeId', 'sku', 'qty'] as const)('rechaza sin %s', (key) => {
136
+ expect(errorsOf(BackofficeRegisterEntryRequest, without(ok, key))).toContain(key);
137
+ });
138
+ it('rechaza qty 0, storeId en blanco e identificadores vacíos, en blanco o repetidos', () => {
139
+ expect(errorsOf(BackofficeRegisterEntryRequest, { ...ok, qty: 0, storeId: ' ' })).toEqual(expect.arrayContaining(['qty', 'storeId']));
140
+ expect(errorsOf(BackofficeRegisterEntryRequest, { ...ok, identifiers: [] })).toContain('identifiers');
141
+ expect(errorsOf(BackofficeRegisterEntryRequest, { ...ok, identifiers: [' '] })).toContain('identifiers');
142
+ expect(errorsOf(BackofficeRegisterEntryRequest, { ...ok, identifiers: ['A1', 'A1'] })).toContain('identifiers');
143
+ });
144
+ });
145
+
146
+ describe('BackofficeRegisterAdjustmentRequest (backofficeRegisterAdjustment)', () => {
147
+ const ok = { storeId: 'st-1', sku: 'SIM-1', qty: 1, reason: InventoryMovementReasonEnum.SHRINKAGE };
148
+ it('acepta una merma', () => expect(errorsOf(BackofficeRegisterAdjustmentRequest, ok)).toEqual([]));
149
+ it('acepta ajuste y garantía con identificador', () => {
150
+ expect(errorsOf(BackofficeRegisterAdjustmentRequest, { ...ok, reason: 'ADJUSTMENT' })).toEqual([]);
151
+ expect(errorsOf(BackofficeRegisterAdjustmentRequest, { ...ok, reason: 'WARRANTY', identifiers: ['356938035643809'] })).toEqual([]);
152
+ });
153
+ it.each(['storeId', 'sku', 'qty', 'reason'] as const)('rechaza sin %s', (key) => {
154
+ expect(errorsOf(BackofficeRegisterAdjustmentRequest, without(ok, key))).toContain(key);
155
+ });
156
+ it('rechaza DAMAGED_RETURN (es de devoluciones) y motivos desconocidos', () => {
157
+ expect(errorsOf(BackofficeRegisterAdjustmentRequest, { ...ok, reason: 'DAMAGED_RETURN' })).toContain('reason');
158
+ expect(errorsOf(BackofficeRegisterAdjustmentRequest, { ...ok, reason: 'LOST' })).toContain('reason');
159
+ });
160
+ it('rechaza qty negativa y sku en blanco', () => {
161
+ expect(errorsOf(BackofficeRegisterAdjustmentRequest, { ...ok, qty: -2, sku: ' ' })).toEqual(expect.arrayContaining(['qty', 'sku']));
162
+ });
163
+ });
164
+
165
+ describe('BackofficeCreateTransferRequest (backofficeCreateTransfer)', () => {
166
+ const ok = { fromStoreId: 'st-1', toStoreId: 'st-2', sku: 'SIM-1', qty: 3 };
167
+ it('acepta un traspaso por cantidad', () => expect(errorsOf(BackofficeCreateTransferRequest, ok)).toEqual([]));
168
+ it('acepta un traspaso con identificadores', () => {
169
+ expect(errorsOf(BackofficeCreateTransferRequest, { ...ok, qty: 1, identifiers: ['356938035643809'] })).toEqual([]);
170
+ });
171
+ it('deja pasar misma tienda: la rechaza el dominio con SAME_STORE_TRANSFER', () => {
172
+ expect(errorsOf(BackofficeCreateTransferRequest, { ...ok, toStoreId: 'st-1' })).toEqual([]);
173
+ });
174
+ it.each(['fromStoreId', 'toStoreId', 'sku', 'qty'] as const)('rechaza sin %s', (key) => {
175
+ expect(errorsOf(BackofficeCreateTransferRequest, without(ok, key))).toContain(key);
176
+ });
177
+ it('rechaza tiendas en blanco, qty 0 e identificadores no string', () => {
178
+ const errors = errorsOf(BackofficeCreateTransferRequest, { ...ok, fromStoreId: ' ', qty: 0, identifiers: [123] });
179
+ expect(errors).toEqual(expect.arrayContaining(['fromStoreId', 'qty', 'identifiers']));
180
+ });
181
+ });
182
+
183
+ describe('BackofficeListTransfersQuery (backofficeListTransfers)', () => {
184
+ it('acepta state=OPEN y sin state', () => {
185
+ expect(errorsOf(BackofficeListTransfersQuery, { state: 'OPEN' })).toEqual([]);
186
+ expect(errorsOf(BackofficeListTransfersQuery, {})).toEqual([]);
187
+ });
188
+ it('rechaza un estado desconocido', () => expect(errorsOf(BackofficeListTransfersQuery, { state: 'PENDING' })).toContain('state'));
189
+ });
190
+
191
+ describe('TransferResponse y ListTransfersResponse', () => {
192
+ it('describen un traspaso abierto', () => {
193
+ const transfer: TransferResponse = {
194
+ transferId: 'trf-1',
195
+ fromStoreId: 'st-1',
196
+ toStoreId: 'st-2',
197
+ sku: 'PH-A15',
198
+ qty: 1,
199
+ identifiers: ['356938035643809'],
200
+ state: TransferStateEnum.OPEN,
201
+ createdBy: 'user-1',
202
+ updatedBy: 'user-1',
203
+ createdAt: '2026-09-24T10:00:00.000Z',
204
+ updatedAt: '2026-09-24T10:00:00.000Z',
205
+ };
206
+ const list: ListTransfersResponse = { items: [transfer] };
207
+ expect(list.items[0].state).toBe('OPEN');
208
+ });
209
+ });
210
+
211
+ describe('financiamiento, MDM y externalId en las altas y edición backoffice', () => {
212
+ const product = { sku: 'PH-A15', category: ProductCategoryEnum.PHONE, trackingMode: TrackingModeEnum.IMEI, sellable: true, showInSalesFlow: true };
213
+ const kit = { sku: 'KIT-A15', sellable: true, showInSalesFlow: true, components: [{ sku: 'PH-A15', qty: 1 }] };
214
+ const financed = { eligibleFinanciers: ['SUREKEEP'], mdmEnabled: true, mdmProvider: 'TRUSTONIC', mdmLockMode: 'ONDEMAND', externalId: 'ZC-1' };
215
+ const cases: [string, ClassConstructor<object>, object][] = [
216
+ ['BackofficeCreateProductRequest', BackofficeCreateProductRequest, product],
217
+ ['BackofficeUpdateProductRequest', BackofficeUpdateProductRequest, {}],
218
+ ];
219
+
220
+ describe.each(cases)('%s', (_name, cls, ok) => {
221
+ it('acepta sin los campos (opcionales)', () => expect(errorsOf(cls, ok)).toEqual([]));
222
+ it('acepta financieras, MDM coherente y externalId', () => {
223
+ expect(errorsOf(cls, { ...ok, ...financed })).toEqual([]);
224
+ expect(errorsOf(cls, { ...ok, eligibleFinanciers: [], mdmEnabled: false })).toEqual([]);
225
+ });
226
+ it('rechaza tipos incorrectos', () => {
227
+ const errors = errorsOf(cls, { ...ok, eligibleFinanciers: 'SUREKEEP', mdmEnabled: 'true', externalId: 5 });
228
+ expect(errors).toEqual(expect.arrayContaining(['eligibleFinanciers', 'mdmEnabled', 'externalId']));
229
+ expect(errorsOf(cls, { ...ok, eligibleFinanciers: [' '], externalId: ' ' })).toEqual(expect.arrayContaining(['eligibleFinanciers', 'externalId']));
230
+ });
231
+ it('rechaza enums MDM inválidos', () => {
232
+ const errors = errorsOf(cls, { ...ok, ...financed, mdmProvider: 'KNOX', mdmLockMode: 'NEVER' });
233
+ expect(errors).toEqual(expect.arrayContaining(['mdmProvider', 'mdmLockMode']));
234
+ });
235
+ it('rechaza MDM incoherente', () => {
236
+ expect(errorsOf(cls, { ...ok, mdmEnabled: true })).toContain('mdmEnabled');
237
+ expect(errorsOf(cls, { ...ok, mdmEnabled: false, mdmProvider: 'TRUSTONIC', mdmLockMode: 'ONDEMAND' })).toContain('mdmEnabled');
238
+ });
239
+ });
240
+
241
+ describe('BackofficeCreateProductRequest con MDM a medias', () => {
242
+ it('rechaza mdmProvider o mdmLockMode sin mdmEnabled', () => {
243
+ expect(errorsOf(BackofficeCreateProductRequest, { ...product, mdmProvider: 'TRUSTONIC', mdmLockMode: 'ONDEMAND' })).toContain('mdmEnabled');
244
+ expect(errorsOf(BackofficeCreateProductRequest, { ...product, mdmProvider: 'TRUSTONIC' })).toContain('mdmEnabled');
245
+ expect(errorsOf(BackofficeCreateProductRequest, { ...product, mdmLockMode: 'ONDEMAND' })).toContain('mdmEnabled');
246
+ });
247
+ it('rechaza mdmEnabled false con proveedor', () => {
248
+ expect(errorsOf(BackofficeCreateProductRequest, { ...product, mdmEnabled: false, mdmProvider: 'TRUSTONIC' })).toContain('mdmEnabled');
249
+ });
250
+ it('acepta la config coherente y la ausencia total de MDM', () => {
251
+ expect(errorsOf(BackofficeCreateProductRequest, { ...product, mdmEnabled: true, mdmProvider: 'TRUSTONIC', mdmLockMode: 'ONDEMAND' })).toEqual([]);
252
+ expect(errorsOf(BackofficeCreateProductRequest, { ...product, mdmEnabled: false })).toEqual([]);
253
+ expect(errorsOf(BackofficeCreateProductRequest, product)).toEqual([]);
254
+ });
255
+ });
256
+
257
+ it('el kit lleva financieras y externalId, pero no MDM (sale del teléfono componente)', () => {
258
+ expect(errorsOf(BackofficeCreateKitRequest, { ...kit, eligibleFinanciers: ['SUREKEEP'], externalId: 'ZC-2' })).toEqual([]);
259
+ expect(errorsOf(BackofficeCreateKitRequest, { ...kit, eligibleFinanciers: 'SUREKEEP', externalId: ' ' })).toEqual(expect.arrayContaining(['eligibleFinanciers', 'externalId']));
260
+ expect(errorsOf(BackofficeCreateKitRequest, { ...kit, mdmEnabled: true })).toContain('mdmEnabled');
261
+ const dto = new BackofficeCreateKitRequest();
262
+ expect(['mdmEnabled', 'mdmProvider', 'mdmLockMode'].some((k) => k in dto)).toBe(false);
263
+ });
264
+
265
+ it('el servicio solo lleva externalId', () => {
266
+ const service = { sku: 'TA-100', description: 'Tiempo aire 100', sellable: true, showInSalesFlow: true };
267
+ expect(errorsOf(BackofficeCreateServiceRequest, { ...service, externalId: 'ZC-9' })).toEqual([]);
268
+ expect(errorsOf(BackofficeCreateServiceRequest, { ...service, externalId: ' ' })).toContain('externalId');
269
+ const dto = new BackofficeCreateServiceRequest();
270
+ expect(['eligibleFinanciers', 'mdmEnabled', 'mdmProvider', 'mdmLockMode'].some((k) => k in dto)).toBe(false);
271
+ });
272
+ });
273
+
274
+ describe('constantes y validadores compartidos del catálogo', () => {
275
+ it('NON_BUNDLE_CATEGORIES excluye BUNDLE', () => {
276
+ expect(NON_BUNDLE_CATEGORIES).not.toContain(ProductCategoryEnum.BUNDLE);
277
+ expect(NON_BUNDLE_CATEGORIES).toHaveLength(Object.values(ProductCategoryEnum).length - 1);
278
+ });
279
+ it('ADJUSTMENT_REASONS no incluye DAMAGED_RETURN', () => {
280
+ expect(ADJUSTMENT_REASONS).toEqual([InventoryMovementReasonEnum.SHRINKAGE, InventoryMovementReasonEnum.ADJUSTMENT, InventoryMovementReasonEnum.WARRANTY]);
281
+ });
282
+ it('IsPieceRefConstraint se exporta desde validators', () => expect(typeof IsPieceRefConstraint).toBe('function'));
283
+ });