@fiado/type-kit 3.551.0 → 3.553.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 (119) hide show
  1. package/_test_/unit/common/InputPatternValidators.test.ts +64 -0
  2. package/_test_/unit/common/InputPatterns.test.ts +104 -0
  3. package/_test_/unit/retailCatalog/backofficeRequests.test.ts +1 -1
  4. package/_test_/unit/retailCatalog/singlePriceFields.test.ts +48 -0
  5. package/_test_/unit/retailIntegration/eventPayloads.test.ts +1 -0
  6. package/_test_/unit/retailOrg/dtos/requests/inputPatternRequests.test.ts +93 -0
  7. package/_test_/unit/retailPromotions/giftOptionValue.test.ts +20 -0
  8. package/_test_/unit/retailWizard/linesWithoutListPrice.test.ts +40 -0
  9. package/bin/common/constants/InputPatterns.d.ts +25 -0
  10. package/bin/common/constants/InputPatterns.js +28 -0
  11. package/bin/common/decorators/UpperCase.d.ts +2 -0
  12. package/bin/common/decorators/UpperCase.js +7 -0
  13. package/bin/common/helpers/imeiChecksum.d.ts +2 -0
  14. package/bin/common/helpers/imeiChecksum.js +20 -0
  15. package/bin/common/index.d.ts +8 -0
  16. package/bin/common/index.js +10 -0
  17. package/bin/common/validators/IsMxPhone.d.ts +3 -0
  18. package/bin/common/validators/IsMxPhone.js +8 -0
  19. package/bin/common/validators/IsMxZipCode.d.ts +3 -0
  20. package/bin/common/validators/IsMxZipCode.js +8 -0
  21. package/bin/common/validators/IsRfc.d.ts +3 -0
  22. package/bin/common/validators/IsRfc.js +8 -0
  23. package/bin/common/validators/IsSku.d.ts +3 -0
  24. package/bin/common/validators/IsSku.js +8 -0
  25. package/bin/common/validators/matchesInputPattern.d.ts +3 -0
  26. package/bin/common/validators/matchesInputPattern.js +16 -0
  27. package/bin/helpers/constans/regex.js +2 -1
  28. package/bin/loanCredit/dtos/LoanContractDetails.js +2 -2
  29. package/bin/loanCredit/dtos/LoanDeviceEnrollItem.js +4 -4
  30. package/bin/retailCatalog/constants/SkuPattern.d.ts +2 -2
  31. package/bin/retailCatalog/constants/SkuPattern.js +5 -3
  32. package/bin/retailCatalog/dtos/Product.d.ts +2 -1
  33. package/bin/retailCatalog/dtos/ProductComponent.js +1 -1
  34. package/bin/retailCatalog/dtos/requests/BackofficeCreateKitRequest.js +1 -1
  35. package/bin/retailCatalog/dtos/requests/BackofficeCreateProductRequest.js +1 -1
  36. package/bin/retailCatalog/dtos/requests/BackofficeCreateServiceRequest.js +1 -1
  37. package/bin/retailCatalog/dtos/requests/BackofficeCreateSkuPriceRequest.d.ts +1 -0
  38. package/bin/retailCatalog/dtos/requests/BackofficeCreateTransferRequest.js +1 -1
  39. package/bin/retailCatalog/dtos/requests/BackofficeRegisterAdjustmentRequest.js +1 -1
  40. package/bin/retailCatalog/dtos/requests/BackofficeRegisterEntryRequest.js +1 -1
  41. package/bin/retailCatalog/dtos/requests/CreateProductRequest.d.ts +1 -0
  42. package/bin/retailCatalog/dtos/requests/CreateProductRequest.js +1 -1
  43. package/bin/retailCatalog/dtos/requests/ReturnPieceCondition.js +1 -1
  44. package/bin/retailCatalog/dtos/requests/SyncInventoryRequest.d.ts +1 -0
  45. package/bin/retailCatalog/dtos/requests/SyncInventoryRequest.js +1 -1
  46. package/bin/retailCatalog/dtos/requests/UpdateProductRequest.d.ts +1 -0
  47. package/bin/retailCatalog/dtos/responses/InventoryLensResponse.d.ts +1 -1
  48. package/bin/retailCatalog/dtos/responses/ReserveStockResponse.d.ts +4 -1
  49. package/bin/retailCatalog/dtos/responses/SkuPriceEntry.d.ts +2 -1
  50. package/bin/retailCatalog/dtos/responses/StoreCatalogItem.d.ts +1 -1
  51. package/bin/retailIntegration/constants/retailIntegrationLimits.d.ts +1 -1
  52. package/bin/retailIntegration/constants/retailIntegrationLimits.js +2 -2
  53. package/bin/retailOrg/dtos/requests/CreateRetailUserRequest.js +2 -1
  54. package/bin/retailOrg/dtos/requests/CreateRetailerRequest.d.ts +1 -1
  55. package/bin/retailOrg/dtos/requests/CreateRetailerRequest.js +6 -2
  56. package/bin/retailOrg/dtos/requests/CreateStoreRequest.js +2 -1
  57. package/bin/retailOrg/dtos/requests/CreateSubdistributorRequest.d.ts +1 -1
  58. package/bin/retailOrg/dtos/requests/CreateSubdistributorRequest.js +6 -2
  59. package/bin/retailOrg/dtos/requests/ProvisionRetailUserRequest.js +2 -1
  60. package/bin/retailOrg/dtos/requests/RetailAddressInput.d.ts +1 -1
  61. package/bin/retailOrg/dtos/requests/RetailAddressInput.js +2 -2
  62. package/bin/retailOrg/dtos/requests/UpdateRetailUserRequest.js +2 -1
  63. package/bin/retailOrg/dtos/requests/UpdateRetailerRequest.js +2 -1
  64. package/bin/retailOrg/dtos/requests/UpdateStoreRequest.js +2 -1
  65. package/bin/retailPromotions/dtos/responses/WizardSalePromotionResponses.d.ts +4 -1
  66. package/bin/retailWizard/dtos/SaleLine.d.ts +2 -1
  67. package/bin/retailWizard/dtos/WizardGiftLine.d.ts +1 -1
  68. package/bin/retailWizard/dtos/WizardLinePromotionFields.d.ts +1 -1
  69. package/bin/retailWizard/dtos/WizardReservedLine.d.ts +2 -1
  70. package/bin/retailWizard/events/SaleCompletedV1.d.ts +1 -1
  71. package/bin/retailWizard/events/SaleCompletedV2.d.ts +1 -1
  72. package/package.json +1 -1
  73. package/src/common/constants/InputPatterns.ts +32 -0
  74. package/src/common/decorators/UpperCase.ts +5 -0
  75. package/src/common/helpers/imeiChecksum.ts +16 -0
  76. package/src/common/index.ts +12 -0
  77. package/src/common/validators/IsMxPhone.ts +7 -0
  78. package/src/common/validators/IsMxZipCode.ts +7 -0
  79. package/src/common/validators/IsRfc.ts +7 -0
  80. package/src/common/validators/IsSku.ts +7 -0
  81. package/src/common/validators/matchesInputPattern.ts +24 -0
  82. package/src/helpers/constans/regex.ts +2 -1
  83. package/src/loanCredit/dtos/LoanContractDetails.ts +1 -1
  84. package/src/loanCredit/dtos/LoanDeviceEnrollItem.ts +3 -3
  85. package/src/retailCatalog/constants/SkuPattern.ts +2 -2
  86. package/src/retailCatalog/dtos/Product.ts +2 -1
  87. package/src/retailCatalog/dtos/ProductComponent.ts +2 -2
  88. package/src/retailCatalog/dtos/requests/BackofficeCreateKitRequest.ts +2 -2
  89. package/src/retailCatalog/dtos/requests/BackofficeCreateProductRequest.ts +2 -2
  90. package/src/retailCatalog/dtos/requests/BackofficeCreateServiceRequest.ts +2 -2
  91. package/src/retailCatalog/dtos/requests/BackofficeCreateSkuPriceRequest.ts +1 -0
  92. package/src/retailCatalog/dtos/requests/BackofficeCreateTransferRequest.ts +2 -2
  93. package/src/retailCatalog/dtos/requests/BackofficeRegisterAdjustmentRequest.ts +2 -2
  94. package/src/retailCatalog/dtos/requests/BackofficeRegisterEntryRequest.ts +2 -2
  95. package/src/retailCatalog/dtos/requests/CreateProductRequest.ts +3 -2
  96. package/src/retailCatalog/dtos/requests/ReturnPieceCondition.ts +2 -2
  97. package/src/retailCatalog/dtos/requests/SyncInventoryRequest.ts +3 -2
  98. package/src/retailCatalog/dtos/requests/UpdateProductRequest.ts +1 -0
  99. package/src/retailCatalog/dtos/responses/InventoryLensResponse.ts +1 -1
  100. package/src/retailCatalog/dtos/responses/ReserveStockResponse.ts +4 -1
  101. package/src/retailCatalog/dtos/responses/SkuPriceEntry.ts +2 -1
  102. package/src/retailCatalog/dtos/responses/StoreCatalogItem.ts +1 -1
  103. package/src/retailIntegration/constants/retailIntegrationLimits.ts +2 -2
  104. package/src/retailOrg/dtos/requests/CreateRetailUserRequest.ts +2 -1
  105. package/src/retailOrg/dtos/requests/CreateRetailerRequest.ts +8 -4
  106. package/src/retailOrg/dtos/requests/CreateStoreRequest.ts +2 -1
  107. package/src/retailOrg/dtos/requests/CreateSubdistributorRequest.ts +8 -4
  108. package/src/retailOrg/dtos/requests/ProvisionRetailUserRequest.ts +2 -1
  109. package/src/retailOrg/dtos/requests/RetailAddressInput.ts +3 -3
  110. package/src/retailOrg/dtos/requests/UpdateRetailUserRequest.ts +2 -1
  111. package/src/retailOrg/dtos/requests/UpdateRetailerRequest.ts +2 -1
  112. package/src/retailOrg/dtos/requests/UpdateStoreRequest.ts +2 -1
  113. package/src/retailPromotions/dtos/responses/WizardSalePromotionResponses.ts +4 -1
  114. package/src/retailWizard/dtos/SaleLine.ts +2 -1
  115. package/src/retailWizard/dtos/WizardGiftLine.ts +1 -1
  116. package/src/retailWizard/dtos/WizardLinePromotionFields.ts +1 -1
  117. package/src/retailWizard/dtos/WizardReservedLine.ts +2 -1
  118. package/src/retailWizard/events/SaleCompletedV1.ts +1 -1
  119. package/src/retailWizard/events/SaleCompletedV2.ts +1 -1
@@ -0,0 +1,64 @@
1
+ import 'reflect-metadata';
2
+ import { plainToInstance } from 'class-transformer';
3
+ import { IsOptional, validateSync } from 'class-validator';
4
+ import { IsMxPhone, IsMxZipCode, IsRfc, IsSku, UpperCase } from '../../../src/common';
5
+
6
+ class Sample {
7
+ @IsOptional()
8
+ @IsMxPhone()
9
+ phone?: string;
10
+
11
+ @IsOptional()
12
+ @IsMxZipCode()
13
+ zipCode?: string;
14
+
15
+ @IsOptional()
16
+ @UpperCase()
17
+ @IsRfc()
18
+ rfc?: string;
19
+
20
+ @IsOptional()
21
+ @IsSku()
22
+ sku?: string;
23
+
24
+ @IsOptional()
25
+ @IsSku({ each: true })
26
+ skus?: string[];
27
+ }
28
+
29
+ const errorsOf = (plain: Record<string, unknown>): string[] =>
30
+ validateSync(plainToInstance(Sample, plain)).map((e) => e.property);
31
+
32
+ describe('Decoradores de InputPatterns', () => {
33
+ it('acepta un objeto vacío: los campos opcionales solo se validan si vienen', () => {
34
+ expect(errorsOf({})).toEqual([]);
35
+ });
36
+
37
+ it('acepta valores válidos', () => {
38
+ expect(
39
+ errorsOf({ phone: '+525512345678', zipCode: '01000', rfc: 'GODE561231GR8', sku: 'SAMSUNG A15', skus: ['A', 'B C'] }),
40
+ ).toEqual([]);
41
+ expect(errorsOf({ phone: '5512345678' })).toEqual([]);
42
+ });
43
+
44
+ it('rechaza cada campo inválido', () => {
45
+ expect(errorsOf({ phone: '55123' })).toEqual(['phone']);
46
+ expect(errorsOf({ zipCode: '6400' })).toEqual(['zipCode']);
47
+ expect(errorsOf({ rfc: 'XYZ' })).toEqual(['rfc']);
48
+ expect(errorsOf({ sku: ' A' })).toEqual(['sku']);
49
+ expect(errorsOf({ sku: 'A'.repeat(65) })).toEqual(['sku']);
50
+ expect(errorsOf({ skus: ['A', 'B '] })).toEqual(['skus']);
51
+ expect(errorsOf({ phone: 5512345678 })).toEqual(['phone']);
52
+ });
53
+
54
+ it('UpperCase normaliza el RFC antes de validarlo', () => {
55
+ const dto = plainToInstance(Sample, { rfc: 'gode561231gr8' });
56
+ expect(dto.rfc).toBe('GODE561231GR8');
57
+ expect(validateSync(dto)).toEqual([]);
58
+ });
59
+
60
+ it('mensajes en español', () => {
61
+ const [error] = validateSync(plainToInstance(Sample, { phone: '1' }));
62
+ expect(Object.values(error.constraints ?? {})[0]).toMatch(/10 dígitos/);
63
+ });
64
+ });
@@ -0,0 +1,104 @@
1
+ import 'reflect-metadata';
2
+ import {
3
+ EMAIL_MAX_LENGTH,
4
+ EMAIL_PATTERN,
5
+ HTTPS_URL_PATTERN,
6
+ IMEI_PATTERN,
7
+ PHONE_MX_ACCEPTED_PATTERN,
8
+ PHONE_MX_PATTERN,
9
+ RFC_PATTERN,
10
+ SKU_MAX_LENGTH,
11
+ SKU_PATTERN,
12
+ ZIP_CODE_MX_PATTERN,
13
+ isValidImei,
14
+ } from '../../../src/common';
15
+ import { SKU_PATTERN as CATALOG_SKU_PATTERN } from '../../../src/retailCatalog/constants/SkuPattern';
16
+ import { RETAIL_SKU_PATTERN } from '../../../src/retailIntegration/constants/retailIntegrationLimits';
17
+ import { regexPhoneMx } from '../../../src/helpers/constans/regex';
18
+
19
+ describe('InputPatterns', () => {
20
+ describe('PHONE_MX_PATTERN (UI, 10 dígitos)', () => {
21
+ it.each(['5512345678', '0000000000'])('acepta %s', (v) => expect(PHONE_MX_PATTERN.test(v)).toBe(true));
22
+ it.each(['+525512345678', '551234567', '55123456789', '55-1234-5678', '55 1234 5678', 'abcdefghij', ''])(
23
+ 'rechaza %s',
24
+ (v) => expect(PHONE_MX_PATTERN.test(v)).toBe(false),
25
+ );
26
+ it('helpers/regex reexporta el mismo patrón', () => expect(regexPhoneMx).toBe(PHONE_MX_PATTERN));
27
+ });
28
+
29
+ describe('PHONE_MX_ACCEPTED_PATTERN (backend, admite +52)', () => {
30
+ it.each(['5512345678', '+525512345678'])('acepta %s', (v) => expect(PHONE_MX_ACCEPTED_PATTERN.test(v)).toBe(true));
31
+ it.each(['525512345678', '+15512345678', '+52551234567', '+52 5512345678', '+5255123456789', ''])(
32
+ 'rechaza %s',
33
+ (v) => expect(PHONE_MX_ACCEPTED_PATTERN.test(v)).toBe(false),
34
+ );
35
+ });
36
+
37
+ describe('EMAIL_PATTERN', () => {
38
+ it.each(['ana@somosfiado.com', 'a.b+tag@sub.dominio.mx'])('acepta %s', (v) => expect(EMAIL_PATTERN.test(v)).toBe(true));
39
+ it.each(['ana', 'ana@', '@fiado.com', 'ana@fiado', 'ana @fiado.com', 'ana@@fiado.com', ''])(
40
+ 'rechaza %s',
41
+ (v) => expect(EMAIL_PATTERN.test(v)).toBe(false),
42
+ );
43
+ it('el tope es 254', () => expect(EMAIL_MAX_LENGTH).toBe(254));
44
+ });
45
+
46
+ describe('RFC_PATTERN', () => {
47
+ it.each(['ABC010203XY1', 'Ñ&A010203AB9', 'GODE561231GR8', 'ÑAAB800101A12'])('acepta %s', (v) =>
48
+ expect(RFC_PATTERN.test(v)).toBe(true),
49
+ );
50
+ it.each(['abc010203xy1', 'AB010203XY1', 'ABCDE010203XY1', 'ABC01020XY1', 'ABC010203XY', 'GODE561231GR8X', 'GODE 561231GR8', ''])(
51
+ 'rechaza %s',
52
+ (v) => expect(RFC_PATTERN.test(v)).toBe(false),
53
+ );
54
+ it('12 caracteres es persona moral y 13 persona física', () => {
55
+ expect('ABC010203XY1').toHaveLength(12);
56
+ expect('GODE561231GR8').toHaveLength(13);
57
+ });
58
+ });
59
+
60
+ describe('ZIP_CODE_MX_PATTERN', () => {
61
+ it.each(['01000', '64000'])('acepta %s', (v) => expect(ZIP_CODE_MX_PATTERN.test(v)).toBe(true));
62
+ it.each(['1000', '640000', '6400A', '64 000', ''])('rechaza %s', (v) => expect(ZIP_CODE_MX_PATTERN.test(v)).toBe(false));
63
+ });
64
+
65
+ describe('SKU_PATTERN', () => {
66
+ it.each(['IPH15-128-NEG', 'abc123', 'SAMSUNG A15 128GB', 'KIT-1 FUNDA 2', 'A B'])('acepta %p', (v) =>
67
+ expect(SKU_PATTERN.test(v)).toBe(true),
68
+ );
69
+ it.each([' SKU', 'SKU ', ' ', '', 'SKU_1', 'SKU#1', 'SKU\t1', 'SKU\n1', 'SKÚ'])('rechaza %p', (v) =>
70
+ expect(SKU_PATTERN.test(v)).toBe(false),
71
+ );
72
+ it('el tope es 64', () => expect(SKU_MAX_LENGTH).toBe(64));
73
+ it('retailCatalog reexporta el mismo patrón', () => expect(CATALOG_SKU_PATTERN).toBe(SKU_PATTERN));
74
+ });
75
+
76
+ describe('RETAIL_SKU_PATTERN (webhook, 1 a 64)', () => {
77
+ it.each(['SAMSUNG A15 128GB', 'A'.repeat(64), 'A B'])('acepta %p', (v) => expect(RETAIL_SKU_PATTERN.test(v)).toBe(true));
78
+ it.each(['A'.repeat(65), ' A', 'A ', '', 'A_B'])('rechaza %p', (v) => expect(RETAIL_SKU_PATTERN.test(v)).toBe(false));
79
+ });
80
+
81
+ describe('IMEI', () => {
82
+ it('IMEI_PATTERN exige 15 dígitos', () => {
83
+ expect(IMEI_PATTERN.test('490154203237518')).toBe(true);
84
+ expect(IMEI_PATTERN.test('49015420323751')).toBe(false);
85
+ expect(IMEI_PATTERN.test('4901542032375180')).toBe(false);
86
+ expect(IMEI_PATTERN.test('49015420323751A')).toBe(false);
87
+ });
88
+ it('isValidImei aplica Luhn', () => {
89
+ expect(isValidImei('490154203237518')).toBe(true);
90
+ expect(isValidImei('490154203237519')).toBe(false);
91
+ expect(isValidImei('49015420323751')).toBe(false);
92
+ });
93
+ });
94
+
95
+ describe('HTTPS_URL_PATTERN', () => {
96
+ it.each(['https://cdn.fiado.com/img/1.png', 'https://fiado.com', 'https://a.b/c?d=1#e'])('acepta %s', (v) =>
97
+ expect(HTTPS_URL_PATTERN.test(v)).toBe(true),
98
+ );
99
+ it.each(['http://fiado.com', 'ftp://fiado.com', 'https://', 'https://fiado com', 'fiado.com', 'javascript:alert(1)', ''])(
100
+ 'rechaza %s',
101
+ (v) => expect(HTTPS_URL_PATTERN.test(v)).toBe(false),
102
+ );
103
+ });
104
+ });
@@ -74,7 +74,7 @@ describe('BackofficeCreateKitRequest (backofficeCreateKit)', () => {
74
74
  });
75
75
  it('rechaza un kit sin componentes', () => expect(errorsOf(BackofficeCreateKitRequest, { ...ok, components: [] })).toContain('components'));
76
76
  it('valida cada componente', () => {
77
- const errors = errorsOf(BackofficeCreateKitRequest, { ...ok, components: [{ sku: 'SIM 1', qty: 0 }] });
77
+ const errors = errorsOf(BackofficeCreateKitRequest, { ...ok, components: [{ sku: 'SIM 1 ', qty: 0 }] });
78
78
  expect(errors).toEqual(expect.arrayContaining(['components.0.sku', 'components.0.qty']));
79
79
  });
80
80
  it('rechaza descripción en blanco', () => expect(errorsOf(BackofficeCreateKitRequest, { ...ok, description: ' ' })).toContain('description'));
@@ -0,0 +1,48 @@
1
+ import 'reflect-metadata';
2
+ import * as RetailCatalog from '../../../src/retailCatalog';
3
+
4
+ // ts-jest compila con type-check: si un campo sigue obligatorio o no existe, la suite no compila.
5
+ describe('retailCatalog — un solo precio y valor del regalo', () => {
6
+ const reservation: RetailCatalog.ReserveStockResponse = {
7
+ expiresAt: '2026-10-20T20:30:00.000Z',
8
+ basePriceCents: 0,
9
+ priceSk: '2026-10-01T00:00:00.000Z',
10
+ eligibleFinanciers: [],
11
+ sellItemCost: 1,
12
+ mdmEnabled: false,
13
+ mdmProvider: null,
14
+ };
15
+
16
+ it('ReserveStockResponse es válida sin listPriceCents ni giftValueCents', () => {
17
+ expect(reservation.giftValueCents).toBeUndefined();
18
+ expect(reservation.listPriceCents).toBeUndefined();
19
+ });
20
+
21
+ it('la reserva de regalo trae giftValueCents', () => {
22
+ const gift: RetailCatalog.ReserveStockResponse = { ...reservation, giftValueCents: 35000 };
23
+ expect(gift.giftValueCents).toBe(35000);
24
+ });
25
+
26
+ it('acepta los dos nombres con el mismo valor mientras dure la transición', () => {
27
+ const gift: RetailCatalog.ReserveStockResponse = { ...reservation, giftValueCents: 35000, listPriceCents: 35000 };
28
+ expect(gift.listPriceCents).toBe(gift.giftValueCents);
29
+ });
30
+
31
+ it('Product es válido sin listPriceCents', () => {
32
+ const product: Pick<RetailCatalog.Product, 'sku' | 'basePriceCents' | 'listPriceCents'> = {
33
+ sku: 'SKU-1',
34
+ basePriceCents: 129900,
35
+ };
36
+ expect(product.listPriceCents).toBeUndefined();
37
+ });
38
+
39
+ it('SkuPriceEntry es válido sin listPriceCents', () => {
40
+ const entry: RetailCatalog.SkuPriceEntry = {
41
+ effectiveFrom: '2026-10-20T00:00:00.000Z',
42
+ basePriceCents: 129900,
43
+ source: RetailCatalog.PriceSourceEnum.BACKOFFICE,
44
+ createdBy: 'backoffice-user-1',
45
+ };
46
+ expect(entry.listPriceCents).toBeUndefined();
47
+ });
48
+ });
@@ -94,6 +94,7 @@ describe('ProductEventPayload', () => {
94
94
  ['price.effectiveFrom', { price: { ...product.price, effectiveFrom: '2026-08-17T20:32:05' } }],
95
95
  ['eligibleFinanciers', { eligibleFinanciers: ['NADIE'] }],
96
96
  ['tier', { tier: undefined }],
97
+ ['price.listPrice', { price: { basePrice: '4299.00', effectiveFrom: '2026-08-17T20:32:05Z' } }],
97
98
  ])('rechaza %s', async (path, over) => {
98
99
  expect(await errorsOf(ProductEventPayload, { ...product, ...over })).toContain(path);
99
100
  });
@@ -0,0 +1,93 @@
1
+ import 'reflect-metadata';
2
+ import { plainToInstance } from 'class-transformer';
3
+ import { validateSync } from 'class-validator';
4
+ import { CreateRetailerRequest } from '../../../../../src/retailOrg/dtos/requests/CreateRetailerRequest';
5
+ import { CreateSubdistributorRequest } from '../../../../../src/retailOrg/dtos/requests/CreateSubdistributorRequest';
6
+ import { UpdateRetailerRequest } from '../../../../../src/retailOrg/dtos/requests/UpdateRetailerRequest';
7
+ import { CreateStoreRequest } from '../../../../../src/retailOrg/dtos/requests/CreateStoreRequest';
8
+ import { UpdateStoreRequest } from '../../../../../src/retailOrg/dtos/requests/UpdateStoreRequest';
9
+ import { CreateRetailUserRequest } from '../../../../../src/retailOrg/dtos/requests/CreateRetailUserRequest';
10
+ import { ProvisionRetailUserRequest } from '../../../../../src/retailOrg/dtos/requests/ProvisionRetailUserRequest';
11
+ import { UpdateRetailUserRequest } from '../../../../../src/retailOrg/dtos/requests/UpdateRetailUserRequest';
12
+ import { RetailAddressInput } from '../../../../../src/retailOrg/dtos/requests/RetailAddressInput';
13
+ import { RetailerTypeEnum } from '../../../../../src/retailOrg/enums/RetailerTypeEnum';
14
+ import { EmploymentTypeEnum } from '../../../../../src/retailOrg/enums/EmploymentTypeEnum';
15
+ import { PermissionScope } from '../../../../../src/platformRbac/enums/PermissionScope';
16
+
17
+ type Ctor = new () => object;
18
+ const invalidProps = (cls: Ctor, plain: object): string[] =>
19
+ validateSync(plainToInstance(cls, plain)).map((e) => e.property);
20
+
21
+ const address = { address1: 'Av. Reforma', city: 'CDMX', state: 'CDMX', zipCode: '01000' };
22
+ const retailer = {
23
+ name: 'Tienda',
24
+ legalName: 'Tienda SA',
25
+ rfc: 'VEN123456ABC',
26
+ contactEmail: 'a@b.mx',
27
+ contactPhone: '5512345678',
28
+ address,
29
+ };
30
+ const store = { zoneId: null, code: 'T1', name: 'Centro', address, location: { lat: 19.4, lng: -99.1 }, phone: '5512345678' };
31
+ const user = { cognitoSub: 'sub', homeStoreId: 's1', employmentType: Object.values(EmploymentTypeEnum)[0], commissionTier: 't', zoneId: null };
32
+ const provision = { ...user, cognitoSub: undefined, email: 'a@b.mx', roleId: 'r', scope: Object.values(PermissionScope)[0], scopeRef: 'x' };
33
+
34
+ describe('retailOrg: patrones de entrada', () => {
35
+ describe.each([
36
+ ['CreateRetailerRequest', CreateRetailerRequest, { ...retailer, type: RetailerTypeEnum.CHAIN }],
37
+ ['CreateSubdistributorRequest', CreateSubdistributorRequest, { ...retailer, type: RetailerTypeEnum.SUBDISTRIBUTOR }],
38
+ ])('%s', (_name, cls, valid) => {
39
+ it('acepta teléfono de 10 dígitos y con +52', () => {
40
+ expect(invalidProps(cls, valid)).toEqual([]);
41
+ expect(invalidProps(cls, { ...valid, contactPhone: '+525512345678' })).toEqual([]);
42
+ });
43
+ it('rechaza teléfono que no es MX de 10 dígitos', () => {
44
+ expect(invalidProps(cls, { ...valid, contactPhone: '55-1234-5678' })).toEqual(['contactPhone']);
45
+ expect(invalidProps(cls, { ...valid, contactPhone: '+15512345678' })).toEqual(['contactPhone']);
46
+ });
47
+ it('acepta RFC de 12 y 13 y normaliza minúsculas', () => {
48
+ expect(invalidProps(cls, { ...valid, rfc: 'GODE561231GR8' })).toEqual([]);
49
+ const dto = plainToInstance(cls, { ...valid, rfc: 'gode561231gr8' }) as { rfc: string };
50
+ expect(dto.rfc).toBe('GODE561231GR8');
51
+ expect(validateSync(dto)).toEqual([]);
52
+ });
53
+ it('rechaza RFC con forma inválida', () => {
54
+ expect(invalidProps(cls, { ...valid, rfc: '123456789012' })).toEqual(['rfc']);
55
+ });
56
+ });
57
+
58
+ it('UpdateRetailerRequest valida contactPhone solo si viene', () => {
59
+ expect(invalidProps(UpdateRetailerRequest, {})).toEqual([]);
60
+ expect(invalidProps(UpdateRetailerRequest, { contactPhone: '+525512345678' })).toEqual([]);
61
+ expect(invalidProps(UpdateRetailerRequest, { contactPhone: '12345' })).toEqual(['contactPhone']);
62
+ });
63
+
64
+ it('CreateStoreRequest exige teléfono MX', () => {
65
+ expect(invalidProps(CreateStoreRequest, store)).toEqual([]);
66
+ expect(invalidProps(CreateStoreRequest, { ...store, phone: '123' })).toEqual(['phone']);
67
+ });
68
+
69
+ it('UpdateStoreRequest valida phone solo si viene', () => {
70
+ expect(invalidProps(UpdateStoreRequest, {})).toEqual([]);
71
+ expect(invalidProps(UpdateStoreRequest, { phone: '+525512345678' })).toEqual([]);
72
+ expect(invalidProps(UpdateStoreRequest, { phone: '55 1234 5678' })).toEqual(['phone']);
73
+ });
74
+
75
+ describe.each([
76
+ ['CreateRetailUserRequest', CreateRetailUserRequest, user],
77
+ ['ProvisionRetailUserRequest', ProvisionRetailUserRequest, provision],
78
+ ['UpdateRetailUserRequest', UpdateRetailUserRequest, {}],
79
+ ])('%s', (_name, cls, valid) => {
80
+ it('phone opcional, MX cuando viene', () => {
81
+ expect(invalidProps(cls, valid)).toEqual([]);
82
+ expect(invalidProps(cls, { ...valid, phone: '5512345678' })).toEqual([]);
83
+ expect(invalidProps(cls, { ...valid, phone: '+525512345678' })).toEqual([]);
84
+ expect(invalidProps(cls, { ...valid, phone: '555123456789' })).toEqual(['phone']);
85
+ });
86
+ });
87
+
88
+ it('RetailAddressInput exige código postal de 5 dígitos', () => {
89
+ expect(invalidProps(RetailAddressInput, address)).toEqual([]);
90
+ expect(invalidProps(RetailAddressInput, { ...address, zipCode: '1000' })).toEqual(['zipCode']);
91
+ expect(invalidProps(RetailAddressInput, { ...address, zipCode: '0100A' })).toEqual(['zipCode']);
92
+ });
93
+ });
@@ -0,0 +1,20 @@
1
+ import 'reflect-metadata';
2
+ import type { GiftOption } from '../../../src/retailPromotions/index';
3
+
4
+ describe('GiftOption.valueCents', () => {
5
+ it('trae el valor del regalo sin listPriceCents', () => {
6
+ const option: GiftOption = { sku: 'CASE-1', name: 'Funda', valueCents: 35000, inStock: true };
7
+ expect(option.valueCents).toBe(35000);
8
+ expect(option.listPriceCents).toBeUndefined();
9
+ });
10
+
11
+ it('acepta los dos nombres con el mismo valor mientras dure la transición', () => {
12
+ const option: GiftOption = { sku: 'CASE-1', name: 'Funda', valueCents: 35000, listPriceCents: 35000, inStock: true };
13
+ expect(option.listPriceCents).toBe(option.valueCents);
14
+ });
15
+
16
+ it('sigue aceptando solo el nombre viejo, para no romper a promotions develop', () => {
17
+ const legacy: GiftOption = { sku: 'CASE-1', name: 'Funda', listPriceCents: 35000, inStock: true };
18
+ expect(legacy.valueCents).toBeUndefined();
19
+ });
20
+ });
@@ -0,0 +1,40 @@
1
+ import 'reflect-metadata';
2
+ import {
3
+ LinePaymentTypeEnum,
4
+ WizardLineStatusEnum,
5
+ type SaleLine,
6
+ type WizardReservedLine,
7
+ } from '../../../src/retailWizard';
8
+
9
+ describe('retailWizard — líneas sin precio de lista', () => {
10
+ it('WizardReservedLine es válida sin listPriceCents', () => {
11
+ const line: WizardReservedLine = {
12
+ lineId: 'L1',
13
+ sku: 'PHONE-1',
14
+ qty: 1,
15
+ status: WizardLineStatusEnum.RESERVED,
16
+ reservationId: 'r-1',
17
+ expiresAt: '2026-10-20T20:30:00.000Z',
18
+ basePriceCents: 349900,
19
+ priceSk: 'p',
20
+ eligibleFinanciers: ['SUREKEEP'],
21
+ financeable: true,
22
+ paymentType: LinePaymentTypeEnum.CREDIT,
23
+ sellItemCost: 1,
24
+ };
25
+ expect(line.listPriceCents).toBeUndefined();
26
+ });
27
+
28
+ it('SaleLine es válida sin listPriceCents', () => {
29
+ const line: SaleLine = {
30
+ lineId: 'L1',
31
+ sku: 'PHONE-1',
32
+ qty: 1,
33
+ basePriceCents: 349900,
34
+ priceSk: 'p',
35
+ paymentType: LinePaymentTypeEnum.CREDIT,
36
+ components: [{ sku: 'PHONE-1', qty: 1, identifiers: ['356938035643809'] }],
37
+ };
38
+ expect(line.listPriceCents).toBeUndefined();
39
+ });
40
+ });
@@ -0,0 +1,25 @@
1
+ /** Teléfono MX tal como se teclea en la UI: exactamente 10 dígitos. */
2
+ export declare const PHONE_MX_PATTERN: RegExp;
3
+ /** Teléfono MX en el backend: 10 dígitos, opcionalmente con `+52` para clientes que ya lo mandan. */
4
+ export declare const PHONE_MX_ACCEPTED_PATTERN: RegExp;
5
+ export declare const PHONE_MX_MESSAGE = "El tel\u00E9fono debe tener 10 d\u00EDgitos";
6
+ /** Correo pragmático: algo@dominio.tld, sin espacios. */
7
+ export declare const EMAIL_PATTERN: RegExp;
8
+ export declare const EMAIL_MAX_LENGTH = 254;
9
+ export declare const EMAIL_MESSAGE = "Ingresa un correo v\u00E1lido";
10
+ /** RFC en mayúsculas: 12 caracteres persona moral, 13 persona física. */
11
+ export declare const RFC_PATTERN: RegExp;
12
+ export declare const RFC_MESSAGE = "El RFC debe tener 12 o 13 caracteres con el formato del SAT";
13
+ /** Código postal MX: 5 dígitos. */
14
+ export declare const ZIP_CODE_MX_PATTERN: RegExp;
15
+ export declare const ZIP_CODE_MX_MESSAGE = "El c\u00F3digo postal debe tener 5 d\u00EDgitos";
16
+ /** SKU: letras, dígitos y guiones, con espacios solo entre palabras. */
17
+ export declare const SKU_PATTERN: RegExp;
18
+ export declare const SKU_MAX_LENGTH = 64;
19
+ export declare const SKU_MESSAGE = "El SKU admite letras, n\u00FAmeros, guiones y espacios entre palabras (m\u00E1ximo 64)";
20
+ /** IMEI: 15 dígitos; el checksum se valida con `isValidImei`. */
21
+ export declare const IMEI_PATTERN: RegExp;
22
+ export declare const IMEI_MESSAGE = "El IMEI debe tener 15 d\u00EDgitos";
23
+ /** URL https sin espacios; en DTOs de backend también sirve `@IsUrl({ protocols: ['https'], require_protocol: true })`. */
24
+ export declare const HTTPS_URL_PATTERN: RegExp;
25
+ export declare const HTTPS_URL_MESSAGE = "Ingresa una URL que empiece con https://";
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HTTPS_URL_MESSAGE = exports.HTTPS_URL_PATTERN = exports.IMEI_MESSAGE = exports.IMEI_PATTERN = exports.SKU_MESSAGE = exports.SKU_MAX_LENGTH = exports.SKU_PATTERN = exports.ZIP_CODE_MX_MESSAGE = exports.ZIP_CODE_MX_PATTERN = exports.RFC_MESSAGE = exports.RFC_PATTERN = exports.EMAIL_MESSAGE = exports.EMAIL_MAX_LENGTH = exports.EMAIL_PATTERN = exports.PHONE_MX_MESSAGE = exports.PHONE_MX_ACCEPTED_PATTERN = exports.PHONE_MX_PATTERN = void 0;
4
+ /** Teléfono MX tal como se teclea en la UI: exactamente 10 dígitos. */
5
+ exports.PHONE_MX_PATTERN = /^\d{10}$/;
6
+ /** Teléfono MX en el backend: 10 dígitos, opcionalmente con `+52` para clientes que ya lo mandan. */
7
+ exports.PHONE_MX_ACCEPTED_PATTERN = /^(?:\+52)?\d{10}$/;
8
+ exports.PHONE_MX_MESSAGE = 'El teléfono debe tener 10 dígitos';
9
+ /** Correo pragmático: algo@dominio.tld, sin espacios. */
10
+ exports.EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
11
+ exports.EMAIL_MAX_LENGTH = 254;
12
+ exports.EMAIL_MESSAGE = 'Ingresa un correo válido';
13
+ /** RFC en mayúsculas: 12 caracteres persona moral, 13 persona física. */
14
+ exports.RFC_PATTERN = /^[A-ZÑ&]{3,4}\d{6}[A-Z\d]{3}$/;
15
+ exports.RFC_MESSAGE = 'El RFC debe tener 12 o 13 caracteres con el formato del SAT';
16
+ /** Código postal MX: 5 dígitos. */
17
+ exports.ZIP_CODE_MX_PATTERN = /^\d{5}$/;
18
+ exports.ZIP_CODE_MX_MESSAGE = 'El código postal debe tener 5 dígitos';
19
+ /** SKU: letras, dígitos y guiones, con espacios solo entre palabras. */
20
+ exports.SKU_PATTERN = /^[A-Za-z0-9-]+(?: +[A-Za-z0-9-]+)*$/;
21
+ exports.SKU_MAX_LENGTH = 64;
22
+ exports.SKU_MESSAGE = 'El SKU admite letras, números, guiones y espacios entre palabras (máximo 64)';
23
+ /** IMEI: 15 dígitos; el checksum se valida con `isValidImei`. */
24
+ exports.IMEI_PATTERN = /^\d{15}$/;
25
+ exports.IMEI_MESSAGE = 'El IMEI debe tener 15 dígitos';
26
+ /** URL https sin espacios; en DTOs de backend también sirve `@IsUrl({ protocols: ['https'], require_protocol: true })`. */
27
+ exports.HTTPS_URL_PATTERN = /^https:\/\/[^\s/$.?#][^\s]*$/i;
28
+ exports.HTTPS_URL_MESSAGE = 'Ingresa una URL que empiece con https://';
@@ -0,0 +1,2 @@
1
+ /** Pasa un string a mayúsculas antes de validarlo; deja pasar cualquier otro tipo sin tocarlo. */
2
+ export declare const UpperCase: () => PropertyDecorator;
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.UpperCase = void 0;
4
+ const class_transformer_1 = require("class-transformer");
5
+ /** Pasa un string a mayúsculas antes de validarlo; deja pasar cualquier otro tipo sin tocarlo. */
6
+ const UpperCase = () => (0, class_transformer_1.Transform)(({ value }) => (typeof value === 'string' ? value.toUpperCase() : value));
7
+ exports.UpperCase = UpperCase;
@@ -0,0 +1,2 @@
1
+ /** Valida un IMEI: 15 dígitos y checksum Luhn. */
2
+ export declare function isValidImei(imei: string): boolean;
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isValidImei = isValidImei;
4
+ const InputPatterns_1 = require("../constants/InputPatterns");
5
+ /** Valida un IMEI: 15 dígitos y checksum Luhn. */
6
+ function isValidImei(imei) {
7
+ if (!InputPatterns_1.IMEI_PATTERN.test(imei))
8
+ return false;
9
+ let sum = 0;
10
+ for (let i = 0; i < 15; i++) {
11
+ let digit = Number(imei[i]);
12
+ if (i % 2 === 1) {
13
+ digit *= 2;
14
+ if (digit > 9)
15
+ digit -= 9;
16
+ }
17
+ sum += digit;
18
+ }
19
+ return sum % 10 === 0;
20
+ }
@@ -1,5 +1,13 @@
1
+ export * from './constants/InputPatterns';
2
+ export * from './helpers/imeiChecksum';
1
3
  export * from './decorators/Trim';
4
+ export * from './decorators/UpperCase';
2
5
  export * from './validators/IsStringValueRecord';
6
+ export * from './validators/matchesInputPattern';
7
+ export * from './validators/IsMxPhone';
8
+ export * from './validators/IsMxZipCode';
9
+ export * from './validators/IsRfc';
10
+ export * from './validators/IsSku';
3
11
  export * from './dtos/ProcessValidationResultDto';
4
12
  export * from './enums/ProcessValidationEnum';
5
13
  export * from './enums/ProcessResultEnum';
@@ -14,10 +14,20 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
+ //constants
18
+ __exportStar(require("./constants/InputPatterns"), exports);
19
+ //helpers
20
+ __exportStar(require("./helpers/imeiChecksum"), exports);
17
21
  //decorators
18
22
  __exportStar(require("./decorators/Trim"), exports);
23
+ __exportStar(require("./decorators/UpperCase"), exports);
19
24
  //validators
20
25
  __exportStar(require("./validators/IsStringValueRecord"), exports);
26
+ __exportStar(require("./validators/matchesInputPattern"), exports);
27
+ __exportStar(require("./validators/IsMxPhone"), exports);
28
+ __exportStar(require("./validators/IsMxZipCode"), exports);
29
+ __exportStar(require("./validators/IsRfc"), exports);
30
+ __exportStar(require("./validators/IsSku"), exports);
21
31
  //dtos
22
32
  __exportStar(require("./dtos/ProcessValidationResultDto"), exports);
23
33
  //Enums
@@ -0,0 +1,3 @@
1
+ import { ValidationOptions } from 'class-validator';
2
+ /** Teléfono MX de 10 dígitos; admite el prefijo `+52`. */
3
+ export declare const IsMxPhone: (validationOptions?: ValidationOptions) => PropertyDecorator;
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.IsMxPhone = void 0;
4
+ const InputPatterns_1 = require("../constants/InputPatterns");
5
+ const matchesInputPattern_1 = require("./matchesInputPattern");
6
+ /** Teléfono MX de 10 dígitos; admite el prefijo `+52`. */
7
+ const IsMxPhone = (validationOptions) => (0, matchesInputPattern_1.matchesInputPattern)('isMxPhone', InputPatterns_1.PHONE_MX_ACCEPTED_PATTERN, InputPatterns_1.PHONE_MX_MESSAGE, validationOptions);
8
+ exports.IsMxPhone = IsMxPhone;
@@ -0,0 +1,3 @@
1
+ import { ValidationOptions } from 'class-validator';
2
+ /** Código postal MX de 5 dígitos. */
3
+ export declare const IsMxZipCode: (validationOptions?: ValidationOptions) => PropertyDecorator;
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.IsMxZipCode = void 0;
4
+ const InputPatterns_1 = require("../constants/InputPatterns");
5
+ const matchesInputPattern_1 = require("./matchesInputPattern");
6
+ /** Código postal MX de 5 dígitos. */
7
+ const IsMxZipCode = (validationOptions) => (0, matchesInputPattern_1.matchesInputPattern)('isMxZipCode', InputPatterns_1.ZIP_CODE_MX_PATTERN, InputPatterns_1.ZIP_CODE_MX_MESSAGE, validationOptions);
8
+ exports.IsMxZipCode = IsMxZipCode;
@@ -0,0 +1,3 @@
1
+ import { ValidationOptions } from 'class-validator';
2
+ /** RFC en mayúsculas; combínalo con `@UpperCase()` para normalizar la entrada. */
3
+ export declare const IsRfc: (validationOptions?: ValidationOptions) => PropertyDecorator;
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.IsRfc = void 0;
4
+ const InputPatterns_1 = require("../constants/InputPatterns");
5
+ const matchesInputPattern_1 = require("./matchesInputPattern");
6
+ /** RFC en mayúsculas; combínalo con `@UpperCase()` para normalizar la entrada. */
7
+ const IsRfc = (validationOptions) => (0, matchesInputPattern_1.matchesInputPattern)('isRfc', InputPatterns_1.RFC_PATTERN, InputPatterns_1.RFC_MESSAGE, validationOptions);
8
+ exports.IsRfc = IsRfc;
@@ -0,0 +1,3 @@
1
+ import { ValidationOptions } from 'class-validator';
2
+ /** SKU de 1 a 64 caracteres: letras, dígitos, guiones y espacios entre palabras. */
3
+ export declare const IsSku: (validationOptions?: ValidationOptions) => PropertyDecorator;
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.IsSku = void 0;
4
+ const InputPatterns_1 = require("../constants/InputPatterns");
5
+ const matchesInputPattern_1 = require("./matchesInputPattern");
6
+ /** SKU de 1 a 64 caracteres: letras, dígitos, guiones y espacios entre palabras. */
7
+ const IsSku = (validationOptions) => (0, matchesInputPattern_1.matchesInputPattern)('isSku', InputPatterns_1.SKU_PATTERN, InputPatterns_1.SKU_MESSAGE, validationOptions, InputPatterns_1.SKU_MAX_LENGTH);
8
+ exports.IsSku = IsSku;
@@ -0,0 +1,3 @@
1
+ import { ValidationOptions } from 'class-validator';
2
+ /** Arma un decorador que exige un string que cumpla `pattern` y, si se da, `maxLength`. */
3
+ export declare function matchesInputPattern(name: string, pattern: RegExp, message: string, validationOptions?: ValidationOptions, maxLength?: number): PropertyDecorator;
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.matchesInputPattern = matchesInputPattern;
4
+ const class_validator_1 = require("class-validator");
5
+ /** Arma un decorador que exige un string que cumpla `pattern` y, si se da, `maxLength`. */
6
+ function matchesInputPattern(name, pattern, message, validationOptions, maxLength) {
7
+ return (0, class_validator_1.ValidateBy)({
8
+ name,
9
+ validator: {
10
+ validate: (value) => typeof value === 'string' &&
11
+ (maxLength === undefined || value.length <= maxLength) &&
12
+ pattern.test(value),
13
+ defaultMessage: () => message,
14
+ },
15
+ }, validationOptions);
16
+ }
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.regexPhoneMx = exports.regexCurp = exports.regexIso8601Date = exports.regexPhoneNumber = exports.regexIso8601 = exports.regexUuidV4 = void 0;
4
+ const InputPatterns_1 = require("../../common/constants/InputPatterns");
4
5
  // Expresión regular para validar UUID v4
5
6
  exports.regexUuidV4 = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
6
7
  // Expresión regular para validar fecha ISO 8601
@@ -17,4 +18,4 @@ exports.regexIso8601Date = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d{1,3})?(Z|
17
18
  exports.regexCurp = /^[A-Z][AEIOUX][A-Z]{2}\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])[HM][A-Z]{2}[B-DF-HJ-NP-TV-Z]{3}[A-Z\d]\d$/;
18
19
  // Teléfono MX: exactamente 10 dígitos (se normalizan espacios/guiones antes de validar).
19
20
  // Usado por retailCustomer y retailWizard. SureKeep Fase 2 — pista Retail.
20
- exports.regexPhoneMx = /^\d{10}$/;
21
+ exports.regexPhoneMx = InputPatterns_1.PHONE_MX_PATTERN;