@fiado/type-kit 3.460.0 → 3.462.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 (199) hide show
  1. package/_test_/unit/loanCredit/customerPaymentDtos.test.ts +87 -0
  2. package/_test_/unit/loanCredit/dtos/createCustomerPaymentRequest.test.ts +78 -0
  3. package/_test_/unit/retailCatalog/catalogEnums.test.ts +34 -0
  4. package/_test_/unit/retailCatalog/stockRequests.test.ts +156 -0
  5. package/_test_/unit/retailLoan/paymentContract.test.ts +128 -0
  6. package/_test_/unit/retailWizard/multiItemRequests.test.ts +114 -0
  7. package/_test_/unit/retailWizard/multiItemShapes.test.ts +126 -0
  8. package/bin/loanCredit/dtos/requests/CreateCustomerPaymentRequest.d.ts +9 -0
  9. package/bin/loanCredit/dtos/requests/CreateCustomerPaymentRequest.js +35 -0
  10. package/bin/loanCredit/dtos/responses/LoanAdHocChargeRequested.d.ts +6 -0
  11. package/bin/loanCredit/dtos/responses/LoanCustomerPaymentOptions.d.ts +33 -0
  12. package/bin/loanCredit/dtos/responses/LoanCustomerPaymentOptions.js +2 -0
  13. package/bin/loanCredit/dtos/responses/LoanCustomerPaymentResult.d.ts +14 -0
  14. package/bin/loanCredit/dtos/responses/LoanCustomerPaymentResult.js +2 -0
  15. package/bin/loanCredit/enums/LoanCustomerPaymentTypeEnum.d.ts +5 -0
  16. package/bin/loanCredit/enums/LoanCustomerPaymentTypeEnum.js +9 -0
  17. package/bin/loanCredit/enums/LoanNextInstallmentModeEnum.d.ts +7 -0
  18. package/bin/loanCredit/enums/LoanNextInstallmentModeEnum.js +11 -0
  19. package/bin/loanCredit/enums/LoanPaymentOptionUnavailableReasonEnum.d.ts +13 -0
  20. package/bin/loanCredit/enums/LoanPaymentOptionUnavailableReasonEnum.js +17 -0
  21. package/bin/loanCredit/index.d.ts +6 -0
  22. package/bin/loanCredit/index.js +7 -0
  23. package/bin/retailCatalog/dtos/Product.d.ts +15 -1
  24. package/bin/retailCatalog/dtos/ProductComponent.d.ts +5 -0
  25. package/bin/retailCatalog/dtos/ProductComponent.js +29 -0
  26. package/bin/retailCatalog/dtos/requests/GetInventorySkuQuery.d.ts +10 -0
  27. package/bin/retailCatalog/dtos/requests/GetInventorySkuQuery.js +42 -0
  28. package/bin/retailCatalog/dtos/requests/InventoryLensQuery.d.ts +5 -0
  29. package/bin/retailCatalog/dtos/requests/InventoryLensQuery.js +11 -0
  30. package/bin/retailCatalog/dtos/requests/ReleaseStockRequest.d.ts +8 -0
  31. package/bin/retailCatalog/dtos/requests/ReleaseStockRequest.js +34 -0
  32. package/bin/retailCatalog/dtos/requests/RenewReservationsRequest.d.ts +6 -0
  33. package/bin/retailCatalog/dtos/requests/RenewReservationsRequest.js +30 -0
  34. package/bin/retailCatalog/dtos/requests/ReservationLineRef.d.ts +5 -0
  35. package/bin/retailCatalog/dtos/requests/ReservationLineRef.js +25 -0
  36. package/bin/retailCatalog/dtos/requests/ReserveStockRequest.d.ts +10 -0
  37. package/bin/retailCatalog/dtos/requests/ReserveStockRequest.js +42 -0
  38. package/bin/retailCatalog/dtos/requests/ReturnStockRequest.d.ts +9 -0
  39. package/bin/retailCatalog/dtos/requests/ReturnStockRequest.js +38 -0
  40. package/bin/retailCatalog/dtos/requests/SellStockRequest.d.ts +7 -0
  41. package/bin/retailCatalog/dtos/requests/SellStockRequest.js +34 -0
  42. package/bin/retailCatalog/dtos/requests/StockLine.d.ts +14 -0
  43. package/bin/retailCatalog/dtos/requests/StockLine.js +56 -0
  44. package/bin/retailCatalog/dtos/requests/SuggestPiecesQuery.d.ts +5 -0
  45. package/bin/retailCatalog/dtos/requests/SuggestPiecesQuery.js +26 -0
  46. package/bin/retailCatalog/dtos/requests/SyncInventoryRequest.d.ts +47 -0
  47. package/bin/retailCatalog/dtos/requests/SyncInventoryRequest.js +173 -0
  48. package/bin/retailCatalog/dtos/responses/GetInventorySkuResponse.d.ts +34 -0
  49. package/bin/retailCatalog/dtos/responses/GetInventorySkuResponse.js +2 -0
  50. package/bin/retailCatalog/dtos/responses/InventoryLensResponse.d.ts +43 -2
  51. package/bin/retailCatalog/dtos/responses/ListSkuPricesResponse.d.ts +6 -0
  52. package/bin/retailCatalog/dtos/responses/ListSkuPricesResponse.js +2 -0
  53. package/bin/retailCatalog/dtos/responses/ReleaseStockResponse.d.ts +10 -0
  54. package/bin/retailCatalog/dtos/responses/ReleaseStockResponse.js +2 -0
  55. package/bin/retailCatalog/dtos/responses/RenewReservationsResponse.d.ts +10 -0
  56. package/bin/retailCatalog/dtos/responses/RenewReservationsResponse.js +2 -0
  57. package/bin/retailCatalog/dtos/responses/ReserveStockResponse.d.ts +25 -0
  58. package/bin/retailCatalog/dtos/responses/ReserveStockResponse.js +2 -0
  59. package/bin/retailCatalog/dtos/responses/ReturnStockResponse.d.ts +4 -0
  60. package/bin/retailCatalog/dtos/responses/ReturnStockResponse.js +2 -0
  61. package/bin/retailCatalog/dtos/responses/SellStockResponse.d.ts +18 -0
  62. package/bin/retailCatalog/dtos/responses/SellStockResponse.js +2 -0
  63. package/bin/retailCatalog/dtos/responses/SkuPriceEntry.d.ts +9 -0
  64. package/bin/retailCatalog/dtos/responses/SkuPriceEntry.js +2 -0
  65. package/bin/retailCatalog/dtos/responses/StoreCatalogItem.d.ts +29 -0
  66. package/bin/retailCatalog/dtos/responses/StoreCatalogItem.js +2 -0
  67. package/bin/retailCatalog/dtos/responses/SuggestPiecesResponse.d.ts +6 -0
  68. package/bin/retailCatalog/dtos/responses/SuggestPiecesResponse.js +2 -0
  69. package/bin/retailCatalog/dtos/responses/SyncInventoryResponse.d.ts +13 -0
  70. package/bin/retailCatalog/dtos/responses/SyncInventoryResponse.js +2 -0
  71. package/bin/retailCatalog/dtos/validation/ValidateProductResponse.d.ts +6 -1
  72. package/bin/retailCatalog/enums/InventoryLensFilterEnum.d.ts +3 -1
  73. package/bin/retailCatalog/enums/InventoryLensFilterEnum.js +2 -0
  74. package/bin/retailCatalog/enums/InventoryLensPeriodEnum.d.ts +7 -0
  75. package/bin/retailCatalog/enums/InventoryLensPeriodEnum.js +11 -0
  76. package/bin/retailCatalog/enums/InventoryMovementTypeEnum.d.ts +9 -0
  77. package/bin/retailCatalog/enums/InventoryMovementTypeEnum.js +13 -0
  78. package/bin/retailCatalog/enums/InventorySyncRejectionCodeEnum.d.ts +9 -0
  79. package/bin/retailCatalog/enums/InventorySyncRejectionCodeEnum.js +13 -0
  80. package/bin/retailCatalog/enums/InventorySyncResultStatusEnum.d.ts +6 -0
  81. package/bin/retailCatalog/enums/InventorySyncResultStatusEnum.js +10 -0
  82. package/bin/retailCatalog/enums/PriceSourceEnum.d.ts +4 -0
  83. package/bin/retailCatalog/enums/PriceSourceEnum.js +8 -0
  84. package/bin/retailCatalog/enums/ProductCategoryEnum.d.ts +9 -0
  85. package/bin/retailCatalog/enums/ProductCategoryEnum.js +13 -0
  86. package/bin/retailCatalog/enums/TrackingModeEnum.d.ts +6 -0
  87. package/bin/retailCatalog/enums/TrackingModeEnum.js +10 -0
  88. package/bin/retailCatalog/index.d.ts +29 -0
  89. package/bin/retailCatalog/index.js +30 -0
  90. package/bin/retailLoan/dtos/CreateMyPaymentRequest.d.ts +7 -0
  91. package/bin/retailLoan/dtos/CreateMyPaymentRequest.js +30 -0
  92. package/bin/retailLoan/dtos/CreateMyPaymentResponse.d.ts +9 -0
  93. package/bin/retailLoan/dtos/CreateMyPaymentResponse.js +2 -0
  94. package/bin/retailLoan/dtos/MyPaymentOptions.d.ts +34 -0
  95. package/bin/retailLoan/dtos/MyPaymentOptions.js +2 -0
  96. package/bin/retailLoan/enums/MyCollectionOutcomeEnum.d.ts +11 -0
  97. package/bin/retailLoan/enums/MyCollectionOutcomeEnum.js +15 -0
  98. package/bin/retailLoan/enums/MyNextInstallmentModeEnum.d.ts +5 -0
  99. package/bin/retailLoan/enums/MyNextInstallmentModeEnum.js +9 -0
  100. package/bin/retailLoan/enums/MyPaymentTypeEnum.d.ts +6 -0
  101. package/bin/retailLoan/enums/MyPaymentTypeEnum.js +10 -0
  102. package/bin/retailLoan/enums/MyPaymentUnavailableReasonEnum.d.ts +8 -0
  103. package/bin/retailLoan/enums/MyPaymentUnavailableReasonEnum.js +12 -0
  104. package/bin/retailLoan/index.d.ts +7 -0
  105. package/bin/retailLoan/index.js +8 -0
  106. package/bin/retailWizard/dtos/Sale.d.ts +7 -0
  107. package/bin/retailWizard/dtos/SaleLine.d.ts +16 -0
  108. package/bin/retailWizard/dtos/SaleLine.js +2 -0
  109. package/bin/retailWizard/dtos/WizardLineIdentifiers.d.ts +5 -0
  110. package/bin/retailWizard/dtos/WizardLineIdentifiers.js +2 -0
  111. package/bin/retailWizard/dtos/WizardLineProduct.d.ts +15 -0
  112. package/bin/retailWizard/dtos/WizardLineProduct.js +2 -0
  113. package/bin/retailWizard/dtos/WizardProduct.d.ts +6 -0
  114. package/bin/retailWizard/dtos/WizardReservedLine.d.ts +35 -0
  115. package/bin/retailWizard/dtos/WizardReservedLine.js +2 -0
  116. package/bin/retailWizard/dtos/WizardSession.d.ts +5 -0
  117. package/bin/retailWizard/dtos/requests/AddProductRequest.d.ts +4 -8
  118. package/bin/retailWizard/dtos/requests/AddProductRequest.js +9 -8
  119. package/bin/retailWizard/dtos/requests/CompleteSaleRequest.d.ts +16 -0
  120. package/bin/retailWizard/dtos/requests/CompleteSaleRequest.js +59 -0
  121. package/bin/retailWizard/dtos/requests/DeleteProductRequest.d.ts +3 -1
  122. package/bin/retailWizard/dtos/requests/DeleteProductRequest.js +7 -1
  123. package/bin/retailWizard/dtos/requests/QuoteRequest.d.ts +2 -0
  124. package/bin/retailWizard/dtos/requests/QuoteRequest.js +7 -0
  125. package/bin/retailWizard/dtos/requests/SuggestIdentifiersQuery.d.ts +5 -0
  126. package/bin/retailWizard/dtos/requests/SuggestIdentifiersQuery.js +30 -0
  127. package/bin/retailWizard/enums/LinePaymentTypeEnum.d.ts +7 -0
  128. package/bin/retailWizard/enums/LinePaymentTypeEnum.js +11 -0
  129. package/bin/retailWizard/enums/WizardLineStatusEnum.d.ts +9 -0
  130. package/bin/retailWizard/enums/WizardLineStatusEnum.js +13 -0
  131. package/bin/retailWizard/index.d.ts +8 -0
  132. package/bin/retailWizard/index.js +8 -0
  133. package/package.json +1 -1
  134. package/src/loanCredit/dtos/requests/CreateCustomerPaymentRequest.ts +21 -0
  135. package/src/loanCredit/dtos/responses/LoanAdHocChargeRequested.ts +3 -0
  136. package/src/loanCredit/dtos/responses/LoanCustomerPaymentOptions.ts +32 -0
  137. package/src/loanCredit/dtos/responses/LoanCustomerPaymentResult.ts +12 -0
  138. package/src/loanCredit/enums/LoanCustomerPaymentTypeEnum.ts +5 -0
  139. package/src/loanCredit/enums/LoanNextInstallmentModeEnum.ts +7 -0
  140. package/src/loanCredit/enums/LoanPaymentOptionUnavailableReasonEnum.ts +13 -0
  141. package/src/loanCredit/index.ts +8 -0
  142. package/src/retailCatalog/dtos/Product.ts +17 -1
  143. package/src/retailCatalog/dtos/ProductComponent.ts +14 -0
  144. package/src/retailCatalog/dtos/requests/GetInventorySkuQuery.ts +27 -0
  145. package/src/retailCatalog/dtos/requests/InventoryLensQuery.ts +12 -1
  146. package/src/retailCatalog/dtos/requests/ReleaseStockRequest.ts +19 -0
  147. package/src/retailCatalog/dtos/requests/RenewReservationsRequest.ts +15 -0
  148. package/src/retailCatalog/dtos/requests/ReservationLineRef.ts +10 -0
  149. package/src/retailCatalog/dtos/requests/ReserveStockRequest.ts +24 -0
  150. package/src/retailCatalog/dtos/requests/ReturnStockRequest.ts +22 -0
  151. package/src/retailCatalog/dtos/requests/SellStockRequest.ts +18 -0
  152. package/src/retailCatalog/dtos/requests/StockLine.ts +36 -0
  153. package/src/retailCatalog/dtos/requests/SuggestPiecesQuery.ts +11 -0
  154. package/src/retailCatalog/dtos/requests/SyncInventoryRequest.ts +145 -0
  155. package/src/retailCatalog/dtos/responses/GetInventorySkuResponse.ts +38 -0
  156. package/src/retailCatalog/dtos/responses/InventoryLensResponse.ts +48 -2
  157. package/src/retailCatalog/dtos/responses/ListSkuPricesResponse.ts +7 -0
  158. package/src/retailCatalog/dtos/responses/ReleaseStockResponse.ts +11 -0
  159. package/src/retailCatalog/dtos/responses/RenewReservationsResponse.ts +11 -0
  160. package/src/retailCatalog/dtos/responses/ReserveStockResponse.ts +27 -0
  161. package/src/retailCatalog/dtos/responses/ReturnStockResponse.ts +4 -0
  162. package/src/retailCatalog/dtos/responses/SellStockResponse.ts +20 -0
  163. package/src/retailCatalog/dtos/responses/SkuPriceEntry.ts +10 -0
  164. package/src/retailCatalog/dtos/responses/StoreCatalogItem.ts +31 -0
  165. package/src/retailCatalog/dtos/responses/SuggestPiecesResponse.ts +7 -0
  166. package/src/retailCatalog/dtos/responses/SyncInventoryResponse.ts +15 -0
  167. package/src/retailCatalog/dtos/validation/ValidateProductResponse.ts +6 -1
  168. package/src/retailCatalog/enums/InventoryLensFilterEnum.ts +2 -0
  169. package/src/retailCatalog/enums/InventoryLensPeriodEnum.ts +7 -0
  170. package/src/retailCatalog/enums/InventoryMovementTypeEnum.ts +9 -0
  171. package/src/retailCatalog/enums/InventorySyncRejectionCodeEnum.ts +9 -0
  172. package/src/retailCatalog/enums/InventorySyncResultStatusEnum.ts +6 -0
  173. package/src/retailCatalog/enums/PriceSourceEnum.ts +4 -0
  174. package/src/retailCatalog/enums/ProductCategoryEnum.ts +9 -0
  175. package/src/retailCatalog/enums/TrackingModeEnum.ts +6 -0
  176. package/src/retailCatalog/index.ts +31 -0
  177. package/src/retailLoan/dtos/CreateMyPaymentRequest.ts +16 -0
  178. package/src/retailLoan/dtos/CreateMyPaymentResponse.ts +10 -0
  179. package/src/retailLoan/dtos/MyPaymentOptions.ts +28 -0
  180. package/src/retailLoan/enums/MyCollectionOutcomeEnum.ts +11 -0
  181. package/src/retailLoan/enums/MyNextInstallmentModeEnum.ts +5 -0
  182. package/src/retailLoan/enums/MyPaymentTypeEnum.ts +6 -0
  183. package/src/retailLoan/enums/MyPaymentUnavailableReasonEnum.ts +8 -0
  184. package/src/retailLoan/index.ts +9 -0
  185. package/src/retailWizard/dtos/Sale.ts +7 -0
  186. package/src/retailWizard/dtos/SaleLine.ts +17 -0
  187. package/src/retailWizard/dtos/WizardLineIdentifiers.ts +5 -0
  188. package/src/retailWizard/dtos/WizardLineProduct.ts +16 -0
  189. package/src/retailWizard/dtos/WizardProduct.ts +7 -0
  190. package/src/retailWizard/dtos/WizardReservedLine.ts +36 -0
  191. package/src/retailWizard/dtos/WizardSession.ts +5 -0
  192. package/src/retailWizard/dtos/requests/AddProductRequest.ts +9 -8
  193. package/src/retailWizard/dtos/requests/CompleteSaleRequest.ts +41 -0
  194. package/src/retailWizard/dtos/requests/DeleteProductRequest.ts +7 -1
  195. package/src/retailWizard/dtos/requests/QuoteRequest.ts +8 -1
  196. package/src/retailWizard/dtos/requests/SuggestIdentifiersQuery.ts +15 -0
  197. package/src/retailWizard/enums/LinePaymentTypeEnum.ts +7 -0
  198. package/src/retailWizard/enums/WizardLineStatusEnum.ts +9 -0
  199. package/src/retailWizard/index.ts +8 -0
@@ -0,0 +1,87 @@
1
+ import 'reflect-metadata';
2
+ import {
3
+ LoanAdHocChargeKindEnum,
4
+ LoanAdHocChargeRequested,
5
+ LoanCustomerPaymentOptions,
6
+ LoanCustomerPaymentResult,
7
+ LoanCustomerPaymentTypeEnum,
8
+ LoanNextInstallmentModeEnum,
9
+ LoanPaymentOptionUnavailableReasonEnum,
10
+ } from '../../../src/loanCredit/index';
11
+ import { CollectionResultStatus } from '../../../src/collection/enums/CollectionResultStatus';
12
+
13
+ describe('loanCredit — opciones y resultado del pago del cliente', () => {
14
+ it('la liquidación trae su cotización y el monto es el de la cotización', () => {
15
+ const options: LoanCustomerPaymentOptions = {
16
+ creditId: 'CR-1',
17
+ nextInstallment: {
18
+ available: true,
19
+ amountCents: 105000,
20
+ mode: LoanNextInstallmentModeEnum.ADVANCE,
21
+ installmentNumber: 4,
22
+ dueDate: '2026-10-01',
23
+ },
24
+ advance: { available: false, unavailableReason: LoanPaymentOptionUnavailableReasonEnum.NOT_AVAILABLE_YET },
25
+ settlement: {
26
+ available: true,
27
+ amountCents: 912345,
28
+ quote: {
29
+ creditId: 'CR-1',
30
+ overdueInstallmentsCents: 0,
31
+ outstandingCapitalCents: 890000,
32
+ currentPeriodInterestCents: 19263,
33
+ currentPeriodInterestIvaCents: 3082,
34
+ elapsedDays: 3,
35
+ periodDays: 7,
36
+ settlementAmountCents: 912345,
37
+ quotedAt: '2026-09-24T15:00:00.000Z',
38
+ },
39
+ savingsCents: 32655,
40
+ },
41
+ };
42
+
43
+ expect(options.settlement.quote?.settlementAmountCents).toBe(options.settlement.amountCents);
44
+ expect(options.pendingCharge).toBeUndefined();
45
+ });
46
+
47
+ it('un cargo en curso se informa con su tipo y su monto', () => {
48
+ const blocked = { available: false, unavailableReason: LoanPaymentOptionUnavailableReasonEnum.PAYMENT_IN_PROGRESS };
49
+ const options: LoanCustomerPaymentOptions = {
50
+ creditId: 'CR-1',
51
+ pendingCharge: { kind: LoanAdHocChargeKindEnum.ADVANCE, amountCents: 105000 },
52
+ nextInstallment: blocked,
53
+ advance: blocked,
54
+ settlement: blocked,
55
+ };
56
+
57
+ expect(options.pendingCharge?.kind).toBe('ADVANCE');
58
+ expect(options.nextInstallment.amountCents).toBeUndefined();
59
+ });
60
+
61
+ it('el resultado del pago puede venir sin la respuesta del motor', () => {
62
+ const result: LoanCustomerPaymentResult = {
63
+ creditId: 'CR-1',
64
+ type: LoanCustomerPaymentTypeEnum.NEXT_INSTALLMENT,
65
+ chargeId: 'CR-1#ADVANCE#1',
66
+ amountCents: 105000,
67
+ };
68
+
69
+ expect(result.collection).toBeUndefined();
70
+ });
71
+
72
+ it('el 202 del cargo ad hoc suma la respuesta del motor como campo opcional', () => {
73
+ const withoutCollection: LoanAdHocChargeRequested = {
74
+ creditId: 'CR-1',
75
+ chargeKind: LoanAdHocChargeKindEnum.ADVANCE,
76
+ chargeId: 'CR-1#ADVANCE#1',
77
+ amountCents: 105000,
78
+ };
79
+ const withCollection: LoanAdHocChargeRequested = {
80
+ ...withoutCollection,
81
+ collection: { status: CollectionResultStatus.PARTIAL, amountCollectedCents: 50000 },
82
+ };
83
+
84
+ expect(withoutCollection.collection).toBeUndefined();
85
+ expect(withCollection.collection?.amountCollectedCents).toBe(50000);
86
+ });
87
+ });
@@ -0,0 +1,78 @@
1
+ import 'reflect-metadata';
2
+ import { plainToInstance } from 'class-transformer';
3
+ import { validate } from 'class-validator';
4
+ import { LoanCredit } from '../../../../src/index';
5
+ import {
6
+ CreateCustomerPaymentRequest,
7
+ LoanCustomerPaymentTypeEnum,
8
+ LoanNextInstallmentModeEnum,
9
+ LoanPaymentOptionUnavailableReasonEnum,
10
+ } from '../../../../src/loanCredit/index';
11
+
12
+ const sortedValues = (enumObject: Record<string, string>) => Object.values(enumObject).sort();
13
+
14
+ const errorProperties = async (body: object): Promise<string[]> => {
15
+ const dto = plainToInstance(CreateCustomerPaymentRequest, body);
16
+ const errors = await validate(dto, { whitelist: true, forbidNonWhitelisted: false });
17
+ return errors.map(error => error.property);
18
+ };
19
+
20
+ const VALID = {
21
+ type: LoanCustomerPaymentTypeEnum.SETTLEMENT,
22
+ expectedAmountCents: 912345,
23
+ idempotencyKey: '3f1c2a9e-8b7d-4c6e-9f10-2a3b4c5d6e7f',
24
+ };
25
+
26
+ describe.each([
27
+ ['LoanCustomerPaymentTypeEnum', LoanCustomerPaymentTypeEnum, ['NEXT_INSTALLMENT', 'SETTLEMENT']],
28
+ ['LoanNextInstallmentModeEnum', LoanNextInstallmentModeEnum, ['ADVANCE', 'COLLECT_DUE']],
29
+ [
30
+ 'LoanPaymentOptionUnavailableReasonEnum',
31
+ LoanPaymentOptionUnavailableReasonEnum,
32
+ ['PAYMENT_IN_PROGRESS', 'CREDIT_NOT_CURRENT', 'USE_SETTLEMENT', 'NOT_AVAILABLE_YET', 'CREDIT_CLOSED'],
33
+ ],
34
+ ])('%s', (_name, enumObject, expected) => {
35
+ it('tiene exactamente los valores del contrato', () => {
36
+ expect(sortedValues(enumObject)).toEqual([...expected].sort());
37
+ });
38
+
39
+ it('cada clave es igual a su valor', () => {
40
+ for (const [key, value] of Object.entries(enumObject)) {
41
+ expect(key).toBe(value);
42
+ }
43
+ });
44
+ });
45
+
46
+ describe('CreateCustomerPaymentRequest', () => {
47
+ it.each([[LoanCustomerPaymentTypeEnum.NEXT_INSTALLMENT], [LoanCustomerPaymentTypeEnum.SETTLEMENT]])(
48
+ 'acepta el tipo %s',
49
+ async type => {
50
+ expect(await errorProperties({ ...VALID, type })).toEqual([]);
51
+ },
52
+ );
53
+
54
+ it('exige tipo, monto esperado y llave de idempotencia', async () => {
55
+ expect(await errorProperties({})).toEqual(
56
+ expect.arrayContaining(['type', 'expectedAmountCents', 'idempotencyKey']),
57
+ );
58
+ });
59
+
60
+ it.each([['ADVANCE'], ['PARTIAL']])('rechaza el tipo %s', async type => {
61
+ expect(await errorProperties({ ...VALID, type })).toEqual(['type']);
62
+ });
63
+
64
+ it.each([[0], [-100], [10.5], ['912345']])('rechaza el monto %p', async expectedAmountCents => {
65
+ expect(await errorProperties({ ...VALID, expectedAmountCents })).toEqual(['expectedAmountCents']);
66
+ });
67
+
68
+ it.each([['abc'], [''], [123]])('rechaza la llave %p', async idempotencyKey => {
69
+ expect(await errorProperties({ ...VALID, idempotencyKey })).toEqual(['idempotencyKey']);
70
+ });
71
+ });
72
+
73
+ describe('export por el index raíz', () => {
74
+ it('expone el request y sus enums en el namespace LoanCredit', () => {
75
+ expect(LoanCredit.CreateCustomerPaymentRequest).toBeDefined();
76
+ expect(LoanCredit.LoanCustomerPaymentTypeEnum.SETTLEMENT).toBe('SETTLEMENT');
77
+ });
78
+ });
@@ -0,0 +1,34 @@
1
+ import 'reflect-metadata';
2
+ import * as RetailCatalog from '../../../src/retailCatalog';
3
+
4
+ describe('retailCatalog — enums del inventario multi-item', () => {
5
+ it('expone las categorías de producto', () => {
6
+ expect(Object.values(RetailCatalog.ProductCategoryEnum)).toEqual(['PHONE', 'SIM', 'MODEM', 'ACCESSORY', 'BUNDLE']);
7
+ });
8
+
9
+ it('expone los modos de rastreo', () => {
10
+ expect(Object.values(RetailCatalog.TrackingModeEnum)).toEqual(['IMEI', 'SERIAL', 'QUANTITY']);
11
+ });
12
+
13
+ it('expone los tipos de movimiento', () => {
14
+ expect(Object.values(RetailCatalog.InventoryMovementTypeEnum)).toEqual([
15
+ 'ENTRY', 'RESERVATION', 'RELEASE', 'SALE', 'RETURN', 'ADJUSTMENT',
16
+ ]);
17
+ });
18
+
19
+ it('expone el resultado por ítem de la puerta de entrada y sus códigos de rechazo', () => {
20
+ expect(Object.values(RetailCatalog.InventorySyncResultStatusEnum)).toEqual(['APPLIED', 'NOOP', 'REJECTED']);
21
+ expect(Object.values(RetailCatalog.InventorySyncRejectionCodeEnum)).toEqual([
22
+ 'DUPLICATE_IDENTIFIER', 'ADJUSTMENT_EXCEEDS_AVAILABLE', 'FUTURE_PRICE_NOT_SUPPORTED',
23
+ ]);
24
+ });
25
+
26
+ it('expone el origen del precio y los periodos del lente', () => {
27
+ expect(Object.values(RetailCatalog.PriceSourceEnum)).toEqual(['ZENTRAL_SYNC']);
28
+ expect(Object.values(RetailCatalog.InventoryLensPeriodEnum)).toEqual(['7d', '30d', '90d', 'all']);
29
+ });
30
+
31
+ it('agrega el modo lista al filtro del lente sin quitar los existentes', () => {
32
+ expect(Object.values(RetailCatalog.InventoryLensFilterEnum)).toEqual(['critical', 'stale', 'gap', 'transfers', 'list']);
33
+ });
34
+ });
@@ -0,0 +1,156 @@
1
+ import 'reflect-metadata';
2
+ import { plainToInstance, type ClassConstructor } from 'class-transformer';
3
+ import { validateSync, type ValidationError } from 'class-validator';
4
+ import {
5
+ GetInventorySkuQuery,
6
+ InventoryLensFilterEnum,
7
+ InventoryLensQuery,
8
+ MdmLockModeEnum,
9
+ MdmProviderEnum,
10
+ ProductCategoryEnum,
11
+ ReleaseStockRequest,
12
+ RenewReservationsRequest,
13
+ ReserveStockRequest,
14
+ ReturnStockRequest,
15
+ SellStockRequest,
16
+ SuggestPiecesQuery,
17
+ SyncInventoryRequest,
18
+ TrackingModeEnum,
19
+ } from '../../../src/retailCatalog';
20
+
21
+ function paths(errors: ValidationError[], prefix = ''): string[] {
22
+ return errors.flatMap((e) => {
23
+ const p = prefix ? `${prefix}.${e.property}` : e.property;
24
+ return e.children && e.children.length > 0 ? paths(e.children, p) : [p];
25
+ });
26
+ }
27
+
28
+ function errorsOf<T extends object>(cls: ClassConstructor<T>, payload: object): string[] {
29
+ return paths(validateSync(plainToInstance(cls, payload) as object));
30
+ }
31
+
32
+ const saleLine = { lineId: 'L1', sku: 'KIT-1', qty: 1, components: [{ sku: 'SIM-1', qty: 1, identifiers: ['8952'] }] };
33
+
34
+ describe('ReserveStockRequest', () => {
35
+ const ok = { sessionId: 's', lineId: 'L1', reservationId: 'r', retailerId: 'ret', sku: 'SKU-1', qty: 2 };
36
+ it('acepta un request completo', () => expect(errorsOf(ReserveStockRequest, ok)).toEqual([]));
37
+ it('rechaza qty menor a 1', () => expect(errorsOf(ReserveStockRequest, { ...ok, qty: 0 })).toContain('qty'));
38
+ it('rechaza sin reservationId', () => {
39
+ const { reservationId: _omit, ...rest } = ok;
40
+ expect(errorsOf(ReserveStockRequest, rest)).toContain('reservationId');
41
+ });
42
+ });
43
+
44
+ describe('RenewReservationsRequest y ReleaseStockRequest', () => {
45
+ it('acepta líneas con lineId y reservationId', () => {
46
+ expect(errorsOf(RenewReservationsRequest, { sessionId: 's', lines: [{ lineId: 'L1', reservationId: 'r' }] })).toEqual([]);
47
+ });
48
+ it('valida cada línea anidada', () => {
49
+ expect(errorsOf(RenewReservationsRequest, { sessionId: 's', lines: [{ lineId: 'L1' }] })).toContain('lines.0.reservationId');
50
+ });
51
+ it('release exige onlyExpired booleano', () => {
52
+ const base = { sessionId: 's', lines: [{ lineId: 'L1', reservationId: 'r' }] };
53
+ expect(errorsOf(ReleaseStockRequest, { ...base, onlyExpired: false })).toEqual([]);
54
+ expect(errorsOf(ReleaseStockRequest, base)).toContain('onlyExpired');
55
+ });
56
+ });
57
+
58
+ describe('SuggestPiecesQuery', () => {
59
+ it('acepta un prefijo de 4 caracteres', () => expect(errorsOf(SuggestPiecesQuery, { sku: 'SKU-1', prefix: '3569' })).toEqual([]));
60
+ it('rechaza un prefijo corto', () => expect(errorsOf(SuggestPiecesQuery, { sku: 'SKU-1', prefix: '356' })).toContain('prefix'));
61
+ });
62
+
63
+ describe('SellStockRequest y ReturnStockRequest', () => {
64
+ it('acepta una venta con componentes e identificadores', () => {
65
+ expect(errorsOf(SellStockRequest, { sessionId: 's', retailerId: 'ret', lines: [saleLine] })).toEqual([]);
66
+ });
67
+ it('valida los identificadores del componente', () => {
68
+ const bad = { ...saleLine, components: [{ sku: 'SIM-1', qty: 1, identifiers: [123] }] };
69
+ expect(errorsOf(SellStockRequest, { sessionId: 's', retailerId: 'ret', lines: [bad] })).toContain('lines.0.components.0.identifiers');
70
+ });
71
+ it('devolución exige saleId y soldAt ISO', () => {
72
+ const ok = { saleId: 'sale', retailerId: 'ret', soldAt: '2026-09-23T10:00:00.000Z', lines: [saleLine] };
73
+ expect(errorsOf(ReturnStockRequest, ok)).toEqual([]);
74
+ expect(errorsOf(ReturnStockRequest, { ...ok, soldAt: 'ayer' })).toContain('soldAt');
75
+ });
76
+ });
77
+
78
+ describe('SyncInventoryRequest', () => {
79
+ const product = {
80
+ sku: 'KIT-1',
81
+ category: ProductCategoryEnum.BUNDLE,
82
+ trackingMode: TrackingModeEnum.QUANTITY,
83
+ sellable: true,
84
+ showInSalesFlow: true,
85
+ eligibleFinanciers: ['SUREKEEP'],
86
+ components: [{ sku: 'SIM-1', qty: 1 }],
87
+ tier: null,
88
+ price: { listPriceCents: 100000, basePriceCents: 90000, effectiveFrom: '2026-09-23T00:00:00.000Z' },
89
+ };
90
+ it('acepta productos y stock por cantidad o por identificadores', () => {
91
+ const stock = [{ storeId: 'st', sku: 'SIM-1', qty: 5 }, { storeId: 'st', sku: 'PH-1', identifiers: ['356938035643809'] }];
92
+ expect(errorsOf(SyncInventoryRequest, { products: [product], stock })).toEqual([]);
93
+ });
94
+ it('rechaza categoría desconocida y precio sin vigencia', () => {
95
+ const bad = { ...product, category: 'TABLET', price: { basePriceCents: 1 } };
96
+ const errors = errorsOf(SyncInventoryRequest, { products: [bad], stock: [] });
97
+ expect(errors).toEqual(expect.arrayContaining(['products.0.category', 'products.0.price.effectiveFrom']));
98
+ });
99
+ it('valida los componentes del kit', () => {
100
+ const bad = { ...product, components: [{ sku: 'SIM-1', qty: 0 }] };
101
+ expect(errorsOf(SyncInventoryRequest, { products: [bad], stock: [] })).toContain('products.0.components.0.qty');
102
+ });
103
+ it('acepta los datos descriptivos y MDM del producto nuevo', () => {
104
+ const full = {
105
+ ...product,
106
+ brand: 'Samsung',
107
+ model: 'A15',
108
+ capacity: '128GB',
109
+ color: 'Negro',
110
+ description: 'Equipo de gama media',
111
+ imageUrl: 'https://cdn.example.com/a15.png',
112
+ mdmEnabled: true,
113
+ mdmProvider: MdmProviderEnum.TRUSTONIC,
114
+ mdmLockMode: MdmLockModeEnum.ONDEMAND,
115
+ };
116
+ expect(errorsOf(SyncInventoryRequest, { products: [full], stock: [] })).toEqual([]);
117
+ });
118
+ it('acepta mdmProvider y mdmLockMode en null', () => {
119
+ const noMdm = { ...product, mdmEnabled: false, mdmProvider: null, mdmLockMode: null };
120
+ expect(errorsOf(SyncInventoryRequest, { products: [noMdm], stock: [] })).toEqual([]);
121
+ });
122
+ it('rechaza tipos incorrectos en los datos descriptivos y MDM', () => {
123
+ const bad = {
124
+ ...product,
125
+ brand: 1,
126
+ model: 2,
127
+ capacity: 3,
128
+ color: 4,
129
+ description: 5,
130
+ imageUrl: 6,
131
+ mdmEnabled: 'yes',
132
+ mdmProvider: 'ACME',
133
+ mdmLockMode: 'NEVER',
134
+ };
135
+ const errors = errorsOf(SyncInventoryRequest, { products: [bad], stock: [] });
136
+ const fields = ['brand', 'model', 'capacity', 'color', 'description', 'imageUrl', 'mdmEnabled', 'mdmProvider', 'mdmLockMode'];
137
+ expect(errors).toEqual(expect.arrayContaining(fields.map((f) => `products.0.${f}`)));
138
+ });
139
+ });
140
+
141
+ describe('InventoryLensQuery en modo lista', () => {
142
+ it('acepta filter=list con period y storeId', () => {
143
+ expect(errorsOf(InventoryLensQuery, { filter: InventoryLensFilterEnum.LIST, period: '30d', storeId: 'st' })).toEqual([]);
144
+ });
145
+ it('sigue aceptando filter solo', () => expect(errorsOf(InventoryLensQuery, { filter: 'critical' })).toEqual([]));
146
+ it('rechaza un periodo desconocido', () => expect(errorsOf(InventoryLensQuery, { filter: 'list', period: '15d' })).toContain('period'));
147
+ });
148
+
149
+ describe('GetInventorySkuQuery', () => {
150
+ it('convierte limit del query string a número', () => {
151
+ const dto = plainToInstance(GetInventorySkuQuery, { limit: '20', period: '7d' });
152
+ expect(dto.limit).toBe(20);
153
+ expect(validateSync(dto)).toEqual([]);
154
+ });
155
+ it('rechaza limit fuera de rango', () => expect(errorsOf(GetInventorySkuQuery, { limit: '0' })).toContain('limit'));
156
+ });
@@ -0,0 +1,128 @@
1
+ import 'reflect-metadata';
2
+ import { plainToInstance } from 'class-transformer';
3
+ import { validate } from 'class-validator';
4
+ import { RetailLoan } from '../../../src/index';
5
+ import {
6
+ CreateMyPaymentRequest,
7
+ CreateMyPaymentResponse,
8
+ MyCollectionOutcomeEnum,
9
+ MyNextInstallmentModeEnum,
10
+ MyPaymentOptions,
11
+ MyPaymentTypeEnum,
12
+ MyPaymentUnavailableReasonEnum,
13
+ } from '../../../src/retailLoan/index';
14
+ import { LoanNextInstallmentModeEnum, LoanPaymentOptionUnavailableReasonEnum } from '../../../src/loanCredit/index';
15
+
16
+ const sortedValues = (enumObject: Record<string, string>) => Object.values(enumObject).sort();
17
+
18
+ const errorProperties = async (body: object): Promise<string[]> => {
19
+ const dto = plainToInstance(CreateMyPaymentRequest, body);
20
+ const errors = await validate(dto, { whitelist: true, forbidNonWhitelisted: false });
21
+ return errors.map(error => error.property);
22
+ };
23
+
24
+ describe.each([
25
+ ['MyPaymentTypeEnum', MyPaymentTypeEnum, ['NEXT_INSTALLMENT', 'ADVANCE', 'SETTLEMENT']],
26
+ ['MyNextInstallmentModeEnum', MyNextInstallmentModeEnum, ['ADVANCE', 'COLLECT_DUE']],
27
+ [
28
+ 'MyPaymentUnavailableReasonEnum',
29
+ MyPaymentUnavailableReasonEnum,
30
+ ['PAYMENT_IN_PROGRESS', 'CREDIT_NOT_CURRENT', 'USE_SETTLEMENT', 'NOT_AVAILABLE_YET', 'CREDIT_CLOSED'],
31
+ ],
32
+ ['MyCollectionOutcomeEnum', MyCollectionOutcomeEnum, ['COLLECTED', 'PARTIAL', 'NOT_COLLECTED', 'UNKNOWN']],
33
+ ])('%s', (_name, enumObject, expected) => {
34
+ it('tiene exactamente los valores del contrato', () => {
35
+ expect(sortedValues(enumObject)).toEqual([...expected].sort());
36
+ });
37
+
38
+ it('cada clave es igual a su valor', () => {
39
+ for (const [key, value] of Object.entries(enumObject)) {
40
+ expect(key).toBe(value);
41
+ }
42
+ });
43
+ });
44
+
45
+ describe('paridad con el contrato privado de loan-credit', () => {
46
+ it('los motivos de no disponible son los mismos', () => {
47
+ expect(sortedValues(MyPaymentUnavailableReasonEnum)).toEqual(sortedValues(LoanPaymentOptionUnavailableReasonEnum));
48
+ });
49
+
50
+ it('los modos de la siguiente cuota son los mismos', () => {
51
+ expect(sortedValues(MyNextInstallmentModeEnum)).toEqual(sortedValues(LoanNextInstallmentModeEnum));
52
+ });
53
+ });
54
+
55
+ describe('CreateMyPaymentRequest', () => {
56
+ it.each([
57
+ [{ type: MyPaymentTypeEnum.NEXT_INSTALLMENT, expectedAmount: 1050 }],
58
+ [{ type: MyPaymentTypeEnum.SETTLEMENT, expectedAmount: 9123.45 }],
59
+ [{ type: MyPaymentTypeEnum.ADVANCE, expectedAmount: 2100.5 }],
60
+ ])('acepta %j', async body => {
61
+ expect(await errorProperties(body)).toEqual([]);
62
+ });
63
+
64
+ it('exige tipo y monto esperado', async () => {
65
+ expect(await errorProperties({})).toEqual(expect.arrayContaining(['type', 'expectedAmount']));
66
+ });
67
+
68
+ it('rechaza un tipo fuera del enum', async () => {
69
+ expect(await errorProperties({ type: 'PARTIAL', expectedAmount: 1050 })).toEqual(['type']);
70
+ });
71
+
72
+ it.each([[0], [-1050], [10.123], ['1050']])('rechaza el monto %p', async expectedAmount => {
73
+ expect(await errorProperties({ type: MyPaymentTypeEnum.SETTLEMENT, expectedAmount })).toEqual(['expectedAmount']);
74
+ });
75
+ });
76
+
77
+ describe('retailLoan — opciones y respuesta del pago', () => {
78
+ it('la liquidación trae su desglose y su ahorro en pesos', () => {
79
+ const options: MyPaymentOptions = {
80
+ nextInstallment: {
81
+ available: true,
82
+ amount: 1050,
83
+ mode: MyNextInstallmentModeEnum.ADVANCE,
84
+ installmentNumber: 4,
85
+ dueDate: '2026-10-01',
86
+ },
87
+ advance: { available: false, unavailableReason: MyPaymentUnavailableReasonEnum.NOT_AVAILABLE_YET },
88
+ settlement: {
89
+ available: true,
90
+ amount: 9123.45,
91
+ breakdown: { overdue: 0, capital: 8900, interest: 192.63, iva: 30.82 },
92
+ savings: 326.55,
93
+ quotedAt: '2026-09-24T15:00:00.000Z',
94
+ },
95
+ };
96
+
97
+ expect(options.settlement.breakdown?.capital).toBe(8900);
98
+ expect(options.pendingPayment).toBeUndefined();
99
+ });
100
+
101
+ it('un pago en curso se informa con su tipo y su monto', () => {
102
+ const blocked = { available: false, unavailableReason: MyPaymentUnavailableReasonEnum.PAYMENT_IN_PROGRESS };
103
+ const options: MyPaymentOptions = {
104
+ pendingPayment: { type: MyPaymentTypeEnum.NEXT_INSTALLMENT, amount: 1050 },
105
+ nextInstallment: blocked,
106
+ advance: blocked,
107
+ settlement: blocked,
108
+ };
109
+
110
+ expect(options.pendingPayment?.type).toBe('NEXT_INSTALLMENT');
111
+ });
112
+
113
+ it('la respuesta del pago siempre trae el desenlace del cobro', () => {
114
+ const response: CreateMyPaymentResponse = {
115
+ creditId: 'CR-1',
116
+ type: MyPaymentTypeEnum.SETTLEMENT,
117
+ amount: 9123.45,
118
+ collection: MyCollectionOutcomeEnum.UNKNOWN,
119
+ };
120
+
121
+ expect(response.collection).toBe('UNKNOWN');
122
+ });
123
+
124
+ it('expone el request en el namespace RetailLoan del index raíz', () => {
125
+ expect(RetailLoan.CreateMyPaymentRequest).toBeDefined();
126
+ expect(RetailLoan.MyCollectionOutcomeEnum.NOT_COLLECTED).toBe('NOT_COLLECTED');
127
+ });
128
+ });
@@ -0,0 +1,114 @@
1
+ import 'reflect-metadata';
2
+ import { plainToInstance, type ClassConstructor } from 'class-transformer';
3
+ import { validateSync, type ValidationError } from 'class-validator';
4
+ import {
5
+ AddProductRequest,
6
+ CompleteSaleRequest,
7
+ DeleteProductRequest,
8
+ QuoteRequest,
9
+ SuggestIdentifiersQuery,
10
+ } from '../../../src/retailWizard';
11
+
12
+ function paths(errors: ValidationError[], prefix = ''): string[] {
13
+ return errors.flatMap((e) => {
14
+ const p = prefix ? `${prefix}.${e.property}` : e.property;
15
+ return e.children && e.children.length > 0 ? paths(e.children, p) : [p];
16
+ });
17
+ }
18
+
19
+ function toDto<T extends object>(cls: ClassConstructor<T>, payload: object): T {
20
+ return plainToInstance(cls, payload, { excludeExtraneousValues: true });
21
+ }
22
+
23
+ function errorsOf<T extends object>(cls: ClassConstructor<T>, payload: object): string[] {
24
+ return paths(validateSync(toDto(cls, payload) as object));
25
+ }
26
+
27
+ describe('AddProductRequest — qty por línea', () => {
28
+ it('acepta solo el sku (qty ausente = 1 lo resuelve el silo)', () => {
29
+ expect(errorsOf(AddProductRequest, { sku: 'SKU-1' })).toEqual([]);
30
+ });
31
+ it('acepta qty explícita', () => {
32
+ expect(errorsOf(AddProductRequest, { sku: 'SKU-1', qty: 3 })).toEqual([]);
33
+ expect(toDto(AddProductRequest, { sku: 'SKU-1', qty: 3 }).qty).toBe(3);
34
+ });
35
+ it('rechaza qty menor a 1', () => {
36
+ expect(errorsOf(AddProductRequest, { sku: 'SKU-1', qty: 0 })).toContain('qty');
37
+ });
38
+ it('rechaza qty no entera', () => {
39
+ expect(errorsOf(AddProductRequest, { sku: 'SKU-1', qty: 1.5 })).toContain('qty');
40
+ });
41
+ });
42
+
43
+ describe('DeleteProductRequest — quitar por lineId', () => {
44
+ it('acepta lineId', () => {
45
+ expect(errorsOf(DeleteProductRequest, { lineId: 'L1' })).toEqual([]);
46
+ expect(toDto(DeleteProductRequest, { lineId: 'L1' }).lineId).toBe('L1');
47
+ });
48
+ it('sigue aceptando el request vacío', () => {
49
+ expect(errorsOf(DeleteProductRequest, {})).toEqual([]);
50
+ });
51
+ it('rechaza lineId que no es string', () => {
52
+ expect(errorsOf(DeleteProductRequest, { lineId: 7 })).toContain('lineId');
53
+ });
54
+ });
55
+
56
+ describe('QuoteRequest — financedLineId', () => {
57
+ const base = { downPaymentCents: 1000, termWeeks: 12 };
58
+ it('acepta sin financedLineId (compat)', () => {
59
+ expect(errorsOf(QuoteRequest, base)).toEqual([]);
60
+ });
61
+ it('acepta financedLineId', () => {
62
+ expect(errorsOf(QuoteRequest, { ...base, financedLineId: 'L2' })).toEqual([]);
63
+ expect(toDto(QuoteRequest, { ...base, financedLineId: 'L2' }).financedLineId).toBe('L2');
64
+ });
65
+ it('rechaza financedLineId vacío', () => {
66
+ expect(errorsOf(QuoteRequest, { ...base, financedLineId: '' })).toContain('financedLineId');
67
+ });
68
+ });
69
+
70
+ describe('CompleteSaleRequest — líneas con identificadores por componente', () => {
71
+ const ok = {
72
+ lines: [
73
+ { lineId: 'L1', components: [{ sku: 'PHONE-1', identifiers: ['356938035643809'] }] },
74
+ { lineId: 'L2', components: [{ sku: 'SIM-1', identifiers: ['8952140'] }, { sku: 'CASE-1', identifiers: [] }] },
75
+ ],
76
+ };
77
+ it('acepta un request completo', () => {
78
+ expect(errorsOf(CompleteSaleRequest, ok)).toEqual([]);
79
+ const dto = toDto(CompleteSaleRequest, ok);
80
+ expect(dto.lines[1].components[0].identifiers).toEqual(['8952140']);
81
+ });
82
+ it('acepta lista de líneas vacía (venta solo por cantidad)', () => {
83
+ expect(errorsOf(CompleteSaleRequest, { lines: [] })).toEqual([]);
84
+ });
85
+ it('acepta sin lines (front sin body)', () => {
86
+ expect(errorsOf(CompleteSaleRequest, {})).toEqual([]);
87
+ });
88
+ it('rechaza lines que no es arreglo', () => {
89
+ expect(errorsOf(CompleteSaleRequest, { lines: 'L1' })).toContain('lines');
90
+ });
91
+ it('rechaza una línea sin lineId', () => {
92
+ expect(errorsOf(CompleteSaleRequest, { lines: [{ components: [] }] })).toContain('lines.0.lineId');
93
+ });
94
+ it('rechaza un identificador que no es string', () => {
95
+ const bad = { lines: [{ lineId: 'L1', components: [{ sku: 'SIM-1', identifiers: [123] }] }] };
96
+ expect(errorsOf(CompleteSaleRequest, bad)).toContain('lines.0.components.0.identifiers');
97
+ });
98
+ it('rechaza un componente sin sku', () => {
99
+ const bad = { lines: [{ lineId: 'L1', components: [{ identifiers: ['x'] }] }] };
100
+ expect(errorsOf(CompleteSaleRequest, bad)).toContain('lines.0.components.0.sku');
101
+ });
102
+ });
103
+
104
+ describe('SuggestIdentifiersQuery — prefijo mínimo 4', () => {
105
+ it('acepta sku y prefijo de 4', () => {
106
+ expect(errorsOf(SuggestIdentifiersQuery, { sku: 'SIM-1', prefix: '8952' })).toEqual([]);
107
+ });
108
+ it('rechaza prefijo corto', () => {
109
+ expect(errorsOf(SuggestIdentifiersQuery, { sku: 'SIM-1', prefix: '895' })).toContain('prefix');
110
+ });
111
+ it('rechaza sin sku', () => {
112
+ expect(errorsOf(SuggestIdentifiersQuery, { prefix: '8952' })).toContain('sku');
113
+ });
114
+ });