@fiado/type-kit 3.583.0 → 3.584.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.
@@ -0,0 +1,147 @@
1
+ import 'reflect-metadata';
2
+ import { plainToInstance } from 'class-transformer';
3
+ import { validate, ValidationError } from 'class-validator';
4
+ import { StoreEventPayload } from '../../../src/retailIntegration/dtos/StoreEventPayload';
5
+ import { RetailUserEventPayload } from '../../../src/retailIntegration/dtos/RetailUserEventPayload';
6
+ import { ProductEventPayload } from '../../../src/retailIntegration/dtos/ProductEventPayload';
7
+ import { InventoryEventPayload } from '../../../src/retailIntegration/dtos/InventoryEventPayload';
8
+ import {
9
+ RETAIL_ELIGIBLE_FINANCIERS_MAX,
10
+ RETAIL_EVENT_STOCK_MAX,
11
+ RETAIL_ID_TEXT_MAX_LENGTH,
12
+ RETAIL_LONG_TEXT_MAX_LENGTH,
13
+ RETAIL_SHORT_TEXT_MAX_LENGTH,
14
+ } from '../../../src/retailIntegration/constants/retailIntegrationLimits';
15
+
16
+ function paths(errors: ValidationError[], prefix = ''): string[] {
17
+ return errors.flatMap((e) => {
18
+ const p = prefix ? `${prefix}.${e.property}` : e.property;
19
+ return e.children && e.children.length > 0 ? paths(e.children, p) : [p];
20
+ });
21
+ }
22
+
23
+ async function errorsOf(cls: new () => object, plain: Record<string, unknown>): Promise<string[]> {
24
+ return paths(await validate(plainToInstance(cls, plain)));
25
+ }
26
+
27
+ const text = (n: number): string => 'a'.repeat(n);
28
+ const SHORT = RETAIL_SHORT_TEXT_MAX_LENGTH;
29
+ const LONG = RETAIL_LONG_TEXT_MAX_LENGTH;
30
+ const ID = RETAIL_ID_TEXT_MAX_LENGTH;
31
+
32
+ const address = { address1: 'Av. Juárez', externalNumber: '120', internalNumber: null, neighborhood: 'Centro', city: 'Toluca', state: 'Estado de México', zipCode: '50000', country: 'MX', reference: null };
33
+ const store = {
34
+ storeId: '12', code: 'T12', name: 'Sucursal Centro', zoneId: 'SUR', address,
35
+ location: { lat: 19.2826, lng: -99.6557 }, phone: '7221234567', schedule: 'Lun a Dom 10:00 a 20:00', status: 'ACTIVE',
36
+ };
37
+ const user = {
38
+ retailUserId: '431', displayName: 'Grecia Cruz Mendoza', email: 'gcruz@ejemplo.mx', phone: null,
39
+ homeStoreId: '12', zoneId: null, role: 'SELLER', status: 'ACTIVE',
40
+ };
41
+ const price = { basePrice: '4299.00', listPrice: null, effectiveFrom: '2026-08-17T20:32:05Z' };
42
+ const product = {
43
+ sku: 'SAM-A17-128-NEG', externalId: '9001', brand: 'Samsung', model: 'A17', color: 'Negro', capacity: '128 GB', description: null,
44
+ category: 'PHONE', trackingMode: 'IMEI', tier: null, currencyId: 'MXN', price,
45
+ eligibleFinanciers: [], mdmProvider: null, status: 'ACTIVE',
46
+ };
47
+ const piece = { sku: 'SAM-A17-128-NEG', identifier: '356938035643809', storeId: '12', status: 'SOLD', reason: null };
48
+ const movement = { movementType: 'SALE', storeId: '12', targetStoreId: null, pieces: [piece], stock: [] };
49
+
50
+ describe('StoreEventPayload: topes', () => {
51
+ it('acepta textos justo en su tope', async () => {
52
+ const atMax = {
53
+ ...store, name: text(SHORT), phone: text(SHORT), schedule: text(SHORT),
54
+ address: { ...address, address1: text(SHORT), externalNumber: text(SHORT), internalNumber: text(SHORT), neighborhood: text(SHORT), city: text(SHORT), state: text(SHORT), country: text(SHORT), reference: text(LONG) },
55
+ };
56
+ expect(await errorsOf(StoreEventPayload, atMax)).toEqual([]);
57
+ });
58
+
59
+ it.each([
60
+ ['name', { name: text(SHORT + 1) }],
61
+ ['phone', { phone: text(SHORT + 1) }],
62
+ ['schedule', { schedule: text(SHORT + 1) }],
63
+ ['address.address1', { address: { ...address, address1: text(SHORT + 1) } }],
64
+ ['address.externalNumber', { address: { ...address, externalNumber: text(SHORT + 1) } }],
65
+ ['address.internalNumber', { address: { ...address, internalNumber: text(SHORT + 1) } }],
66
+ ['address.neighborhood', { address: { ...address, neighborhood: text(SHORT + 1) } }],
67
+ ['address.city', { address: { ...address, city: text(SHORT + 1) } }],
68
+ ['address.state', { address: { ...address, state: text(SHORT + 1) } }],
69
+ ['address.country', { address: { ...address, country: text(SHORT + 1) } }],
70
+ ['address.reference', { address: { ...address, reference: text(LONG + 1) } }],
71
+ ])('rechaza %s sobre su tope', async (path, over) => {
72
+ expect(await errorsOf(StoreEventPayload, { ...store, ...over })).toContain(path);
73
+ });
74
+
75
+ it.each([
76
+ ['address', 'Av. Juárez 120'],
77
+ ['address', [address]],
78
+ ['location', 'lat,lng'],
79
+ ['location', [19.2, -99.6]],
80
+ ])('rechaza %s que no es objeto', async (path, value) => {
81
+ const errors = await validate(plainToInstance(StoreEventPayload, { ...store, [path]: value }));
82
+ expect(errors.find((e) => e.property === path)?.constraints).toHaveProperty('isObject');
83
+ });
84
+ });
85
+
86
+ describe('RetailUserEventPayload: topes', () => {
87
+ it('acepta textos justo en su tope', async () => {
88
+ // IsEmail ya topa en 254, uno abajo de RETAIL_SHORT_TEXT_MAX_LENGTH.
89
+ const email = `${text(64)}@${'b'.repeat(63)}.${'c'.repeat(63)}.${'d'.repeat(58)}.mx`;
90
+ expect(email).toHaveLength(SHORT - 1);
91
+ expect(await errorsOf(RetailUserEventPayload, { ...user, displayName: text(SHORT), phone: text(SHORT), email })).toEqual([]);
92
+ });
93
+
94
+ it.each([
95
+ ['displayName', { displayName: text(SHORT + 1) }],
96
+ ['phone', { phone: text(SHORT + 1) }],
97
+ ['email', { email: `${text(SHORT)}@ejemplo.mx` }],
98
+ ])('rechaza %s sobre su tope', async (path, over) => {
99
+ expect(await errorsOf(RetailUserEventPayload, { ...user, ...over })).toContain(path);
100
+ });
101
+ });
102
+
103
+ describe('ProductEventPayload: topes', () => {
104
+ it('acepta textos, financieras y montos justo en su tope', async () => {
105
+ const atMax = {
106
+ ...product, externalId: text(ID), brand: text(SHORT), model: text(SHORT), color: text(SHORT), capacity: text(SHORT), description: text(LONG),
107
+ price: { ...price, basePrice: '999999999999.99', listPrice: '999999999999.99' },
108
+ eligibleFinanciers: Array.from({ length: RETAIL_ELIGIBLE_FINANCIERS_MAX }, () => 'SUREKEEP'),
109
+ };
110
+ expect(await errorsOf(ProductEventPayload, atMax)).toEqual([]);
111
+ });
112
+
113
+ it.each([
114
+ ['externalId', { externalId: text(ID + 1) }],
115
+ ['brand', { brand: text(SHORT + 1) }],
116
+ ['model', { model: text(SHORT + 1) }],
117
+ ['color', { color: text(SHORT + 1) }],
118
+ ['capacity', { capacity: text(SHORT + 1) }],
119
+ ['description', { description: text(LONG + 1) }],
120
+ ['price.basePrice', { price: { ...price, basePrice: '1000000000000.00' } }],
121
+ ['price.listPrice', { price: { ...price, listPrice: '1000000000000.00' } }],
122
+ ['eligibleFinanciers', { eligibleFinanciers: Array.from({ length: RETAIL_ELIGIBLE_FINANCIERS_MAX + 1 }, () => 'SUREKEEP') }],
123
+ ])('rechaza %s', async (path, over) => {
124
+ expect(await errorsOf(ProductEventPayload, { ...product, ...over })).toContain(path);
125
+ });
126
+
127
+ it.each([['4299.00'], [[price]]])('rechaza price que no es objeto: %j', async (value) => {
128
+ const errors = await validate(plainToInstance(ProductEventPayload, { ...product, price: value }));
129
+ expect(errors.find((e) => e.property === 'price')?.constraints).toHaveProperty('isObject');
130
+ });
131
+ });
132
+
133
+ describe('InventoryEventPayload: topes', () => {
134
+ const stockOf = (n: number) => Array.from({ length: n }, (_, i) => ({ sku: `MICA-${String(i)}`, storeId: '12', qty: 1 }));
135
+
136
+ it('acepta identifier y stock justo en su tope', async () => {
137
+ expect(await errorsOf(InventoryEventPayload, { ...movement, pieces: [{ ...piece, identifier: text(SHORT) }], stock: stockOf(RETAIL_EVENT_STOCK_MAX) })).toEqual([]);
138
+ });
139
+
140
+ it('rechaza identifier sobre su tope', async () => {
141
+ expect(await errorsOf(InventoryEventPayload, { ...movement, pieces: [{ ...piece, identifier: text(SHORT + 1) }] })).toContain('pieces.0.identifier');
142
+ });
143
+
144
+ it('rechaza stock sobre su tope', async () => {
145
+ expect(await errorsOf(InventoryEventPayload, { ...movement, stock: stockOf(RETAIL_EVENT_STOCK_MAX + 1) })).toContain('stock');
146
+ });
147
+ });
@@ -5,6 +5,7 @@ import { StoreEventPayload } from '../../../src/retailIntegration/dtos/StoreEven
5
5
  import { RetailUserEventPayload } from '../../../src/retailIntegration/dtos/RetailUserEventPayload';
6
6
  import { ProductEventPayload } from '../../../src/retailIntegration/dtos/ProductEventPayload';
7
7
  import { InventoryEventPayload } from '../../../src/retailIntegration/dtos/InventoryEventPayload';
8
+ import { RETAIL_EVENT_PIECES_MAX } from '../../../src/retailIntegration/constants/retailIntegrationLimits';
8
9
 
9
10
  function paths(errors: ValidationError[], prefix = ''): string[] {
10
11
  return errors.flatMap((e) => {
@@ -130,4 +131,11 @@ describe('InventoryEventPayload', () => {
130
131
  expect(await errorsOf(InventoryEventPayload, { ...movement, stock: [s, s] })).toContain('stock');
131
132
  expect(await errorsOf(InventoryEventPayload, { ...movement, stock: [{ ...s, qty: -1 }] })).toContain('stock.0.qty');
132
133
  });
134
+
135
+ it('acepta hasta RETAIL_EVENT_PIECES_MAX piezas y rechaza una más', async () => {
136
+ const piecesOf = (n: number) => Array.from({ length: n }, (_, i) => ({ ...piece, identifier: `35693803564${String(i).padStart(4, '0')}` }));
137
+ expect(RETAIL_EVENT_PIECES_MAX).toBe(30);
138
+ expect(await errorsOf(InventoryEventPayload, { ...movement, pieces: piecesOf(RETAIL_EVENT_PIECES_MAX) })).toEqual([]);
139
+ expect(await errorsOf(InventoryEventPayload, { ...movement, pieces: piecesOf(RETAIL_EVENT_PIECES_MAX + 1) })).toContain('pieces');
140
+ });
133
141
  });
@@ -20,3 +20,17 @@ export declare const RETAIL_SKU_PATTERN: RegExp;
20
20
  export declare const RETAIL_DECIMAL_PATTERN: RegExp;
21
21
  /** Tope de identificadores por consulta de inventario en vivo. */
22
22
  export declare const RETAIL_LIVE_CHECK_IDENTIFIERS_MAX = 50;
23
+ /** Tope de piezas por evento `InventoryMovementV1`; más se rechaza con INVALID_PAYLOAD. */
24
+ export declare const RETAIL_EVENT_PIECES_MAX = 30;
25
+ /** Tope de ids y códigos libres del retailer, como `externalId`. */
26
+ export declare const RETAIL_ID_TEXT_MAX_LENGTH = 64;
27
+ /** Tope de textos cortos de los payloads: nombres, dirección, teléfono, marca, modelo, `identifier`. */
28
+ export declare const RETAIL_SHORT_TEXT_MAX_LENGTH = 255;
29
+ /** Tope de textos largos de los payloads: descripción y referencia. */
30
+ export declare const RETAIL_LONG_TEXT_MAX_LENGTH = 1000;
31
+ /** Tope de existencias por evento `InventoryMovementV1`. */
32
+ export declare const RETAIL_EVENT_STOCK_MAX = 200;
33
+ /** Tope de financieras elegibles por producto. */
34
+ export declare const RETAIL_ELIGIBLE_FINANCIERS_MAX = 50;
35
+ /** Decimal de los eventos del retailer: hasta 12 enteros y dos decimales, nunca negativo. */
36
+ export declare const RETAIL_EVENT_DECIMAL_PATTERN: RegExp;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.RETAIL_LIVE_CHECK_IDENTIFIERS_MAX = exports.RETAIL_DECIMAL_PATTERN = exports.RETAIL_SKU_PATTERN = exports.RETAIL_ZIP_CODE_MAX_LENGTH = exports.RETAIL_ZIP_CODE_MIN_LENGTH = exports.RETAIL_STORE_CODE_MAX_LENGTH = exports.RETAIL_ZONE_ID_PATTERN = exports.RETAIL_ENTITY_ID_PATTERN = exports.RETAIL_TENANT_ID_PATTERN = exports.RETAIL_LIST_LIMIT_MAX = exports.RETAIL_LIST_LIMIT_MIN = exports.RETAIL_EVENT_ID_MAX_LENGTH = void 0;
3
+ exports.RETAIL_EVENT_DECIMAL_PATTERN = exports.RETAIL_ELIGIBLE_FINANCIERS_MAX = exports.RETAIL_EVENT_STOCK_MAX = exports.RETAIL_LONG_TEXT_MAX_LENGTH = exports.RETAIL_SHORT_TEXT_MAX_LENGTH = exports.RETAIL_ID_TEXT_MAX_LENGTH = exports.RETAIL_EVENT_PIECES_MAX = exports.RETAIL_LIVE_CHECK_IDENTIFIERS_MAX = exports.RETAIL_DECIMAL_PATTERN = exports.RETAIL_SKU_PATTERN = exports.RETAIL_ZIP_CODE_MAX_LENGTH = exports.RETAIL_ZIP_CODE_MIN_LENGTH = exports.RETAIL_STORE_CODE_MAX_LENGTH = exports.RETAIL_ZONE_ID_PATTERN = exports.RETAIL_ENTITY_ID_PATTERN = exports.RETAIL_TENANT_ID_PATTERN = exports.RETAIL_LIST_LIMIT_MAX = exports.RETAIL_LIST_LIMIT_MIN = exports.RETAIL_EVENT_ID_MAX_LENGTH = void 0;
4
4
  /** Tope de `eventId`: el límite seguro de una llave de DynamoDB. */
5
5
  exports.RETAIL_EVENT_ID_MAX_LENGTH = 1024;
6
6
  /** Rango de `limit` en las listas paginadas del retailer. */
@@ -23,3 +23,17 @@ exports.RETAIL_SKU_PATTERN = /^(?=.{1,64}$)[A-Za-z0-9-]+(?: +[A-Za-z0-9-]+)*$/;
23
23
  exports.RETAIL_DECIMAL_PATTERN = /^\d+\.\d{2}$/;
24
24
  /** Tope de identificadores por consulta de inventario en vivo. */
25
25
  exports.RETAIL_LIVE_CHECK_IDENTIFIERS_MAX = 50;
26
+ /** Tope de piezas por evento `InventoryMovementV1`; más se rechaza con INVALID_PAYLOAD. */
27
+ exports.RETAIL_EVENT_PIECES_MAX = 30;
28
+ /** Tope de ids y códigos libres del retailer, como `externalId`. */
29
+ exports.RETAIL_ID_TEXT_MAX_LENGTH = 64;
30
+ /** Tope de textos cortos de los payloads: nombres, dirección, teléfono, marca, modelo, `identifier`. */
31
+ exports.RETAIL_SHORT_TEXT_MAX_LENGTH = 255;
32
+ /** Tope de textos largos de los payloads: descripción y referencia. */
33
+ exports.RETAIL_LONG_TEXT_MAX_LENGTH = 1000;
34
+ /** Tope de existencias por evento `InventoryMovementV1`. */
35
+ exports.RETAIL_EVENT_STOCK_MAX = 200;
36
+ /** Tope de financieras elegibles por producto. */
37
+ exports.RETAIL_ELIGIBLE_FINANCIERS_MAX = 50;
38
+ /** Decimal de los eventos del retailer: hasta 12 enteros y dos decimales, nunca negativo. */
39
+ exports.RETAIL_EVENT_DECIMAL_PATTERN = /^\d{1,12}\.\d{2}$/;
@@ -27,6 +27,7 @@ __decorate([
27
27
  __decorate([
28
28
  (0, class_validator_1.IsString)(),
29
29
  (0, class_validator_1.Matches)(/\S/),
30
+ (0, class_validator_1.MaxLength)(retailIntegrationLimits_1.RETAIL_SHORT_TEXT_MAX_LENGTH),
30
31
  __metadata("design:type", String)
31
32
  ], PieceEventPayload.prototype, "identifier", void 0);
32
33
  __decorate([
@@ -80,6 +81,7 @@ __decorate([
80
81
  ], InventoryEventPayload.prototype, "targetStoreId", void 0);
81
82
  __decorate([
82
83
  (0, class_validator_1.IsArray)(),
84
+ (0, class_validator_1.ArrayMaxSize)(retailIntegrationLimits_1.RETAIL_EVENT_PIECES_MAX),
83
85
  (0, class_validator_1.ValidateNested)({ each: true }),
84
86
  (0, class_transformer_1.Type)(() => PieceEventPayload),
85
87
  (0, class_validator_1.Validate)(payloadValidators_1.HasUniqueKeys, ['identifier']),
@@ -87,6 +89,7 @@ __decorate([
87
89
  ], InventoryEventPayload.prototype, "pieces", void 0);
88
90
  __decorate([
89
91
  (0, class_validator_1.IsArray)(),
92
+ (0, class_validator_1.ArrayMaxSize)(retailIntegrationLimits_1.RETAIL_EVENT_STOCK_MAX),
90
93
  (0, class_validator_1.ValidateNested)({ each: true }),
91
94
  (0, class_transformer_1.Type)(() => StockEventPayload),
92
95
  (0, class_validator_1.Validate)(payloadValidators_1.HasUniqueKeys, ['storeId', 'sku']),
@@ -22,13 +22,13 @@ class ProductEventPricePayload {
22
22
  exports.ProductEventPricePayload = ProductEventPricePayload;
23
23
  __decorate([
24
24
  (0, class_validator_1.IsString)(),
25
- (0, class_validator_1.Matches)(retailIntegrationLimits_1.RETAIL_DECIMAL_PATTERN),
25
+ (0, class_validator_1.Matches)(retailIntegrationLimits_1.RETAIL_EVENT_DECIMAL_PATTERN),
26
26
  __metadata("design:type", String)
27
27
  ], ProductEventPricePayload.prototype, "basePrice", void 0);
28
28
  __decorate([
29
29
  (0, class_validator_1.ValidateIf)(payloadValidators_1.isNullOrValidated),
30
30
  (0, class_validator_1.IsString)(),
31
- (0, class_validator_1.Matches)(retailIntegrationLimits_1.RETAIL_DECIMAL_PATTERN),
31
+ (0, class_validator_1.Matches)(retailIntegrationLimits_1.RETAIL_EVENT_DECIMAL_PATTERN),
32
32
  __metadata("design:type", String)
33
33
  ], ProductEventPricePayload.prototype, "listPrice", void 0);
34
34
  __decorate([
@@ -47,31 +47,37 @@ __decorate([
47
47
  __decorate([
48
48
  (0, class_validator_1.IsString)(),
49
49
  (0, class_validator_1.Matches)(/\S/),
50
+ (0, class_validator_1.MaxLength)(retailIntegrationLimits_1.RETAIL_ID_TEXT_MAX_LENGTH),
50
51
  __metadata("design:type", String)
51
52
  ], ProductEventPayload.prototype, "externalId", void 0);
52
53
  __decorate([
53
54
  (0, class_validator_1.IsString)(),
54
55
  (0, class_validator_1.Matches)(/\S/),
56
+ (0, class_validator_1.MaxLength)(retailIntegrationLimits_1.RETAIL_SHORT_TEXT_MAX_LENGTH),
55
57
  __metadata("design:type", String)
56
58
  ], ProductEventPayload.prototype, "brand", void 0);
57
59
  __decorate([
58
60
  (0, class_validator_1.IsString)(),
59
61
  (0, class_validator_1.Matches)(/\S/),
62
+ (0, class_validator_1.MaxLength)(retailIntegrationLimits_1.RETAIL_SHORT_TEXT_MAX_LENGTH),
60
63
  __metadata("design:type", String)
61
64
  ], ProductEventPayload.prototype, "model", void 0);
62
65
  __decorate([
63
66
  (0, class_validator_1.ValidateIf)(payloadValidators_1.isNullOrValidated),
64
67
  (0, class_validator_1.IsString)(),
68
+ (0, class_validator_1.MaxLength)(retailIntegrationLimits_1.RETAIL_SHORT_TEXT_MAX_LENGTH),
65
69
  __metadata("design:type", String)
66
70
  ], ProductEventPayload.prototype, "color", void 0);
67
71
  __decorate([
68
72
  (0, class_validator_1.ValidateIf)(payloadValidators_1.isNullOrValidated),
69
73
  (0, class_validator_1.IsString)(),
74
+ (0, class_validator_1.MaxLength)(retailIntegrationLimits_1.RETAIL_SHORT_TEXT_MAX_LENGTH),
70
75
  __metadata("design:type", String)
71
76
  ], ProductEventPayload.prototype, "capacity", void 0);
72
77
  __decorate([
73
78
  (0, class_validator_1.ValidateIf)(payloadValidators_1.isNullOrValidated),
74
79
  (0, class_validator_1.IsString)(),
80
+ (0, class_validator_1.MaxLength)(retailIntegrationLimits_1.RETAIL_LONG_TEXT_MAX_LENGTH),
75
81
  __metadata("design:type", String)
76
82
  ], ProductEventPayload.prototype, "description", void 0);
77
83
  __decorate([
@@ -93,12 +99,14 @@ __decorate([
93
99
  ], ProductEventPayload.prototype, "currencyId", void 0);
94
100
  __decorate([
95
101
  (0, class_validator_1.IsDefined)(),
102
+ (0, class_validator_1.IsObject)(),
96
103
  (0, class_validator_1.ValidateNested)(),
97
104
  (0, class_transformer_1.Type)(() => ProductEventPricePayload),
98
105
  __metadata("design:type", ProductEventPricePayload)
99
106
  ], ProductEventPayload.prototype, "price", void 0);
100
107
  __decorate([
101
108
  (0, class_validator_1.IsArray)(),
109
+ (0, class_validator_1.ArrayMaxSize)(retailIntegrationLimits_1.RETAIL_ELIGIBLE_FINANCIERS_MAX),
102
110
  (0, class_validator_1.IsIn)(Object.values(enums_1.RetailFinancierEnum), { each: true }),
103
111
  __metadata("design:type", Array)
104
112
  ], ProductEventPayload.prototype, "eligibleFinanciers", void 0);
@@ -26,15 +26,18 @@ __decorate([
26
26
  __decorate([
27
27
  (0, class_validator_1.IsString)(),
28
28
  (0, class_validator_1.Matches)(/\S/),
29
+ (0, class_validator_1.MaxLength)(retailIntegrationLimits_1.RETAIL_SHORT_TEXT_MAX_LENGTH),
29
30
  __metadata("design:type", String)
30
31
  ], RetailUserEventPayload.prototype, "displayName", void 0);
31
32
  __decorate([
32
33
  (0, class_validator_1.IsEmail)(),
34
+ (0, class_validator_1.MaxLength)(retailIntegrationLimits_1.RETAIL_SHORT_TEXT_MAX_LENGTH),
33
35
  __metadata("design:type", String)
34
36
  ], RetailUserEventPayload.prototype, "email", void 0);
35
37
  __decorate([
36
38
  (0, class_validator_1.ValidateIf)(payloadValidators_1.isNullOrValidated),
37
39
  (0, class_validator_1.IsString)(),
40
+ (0, class_validator_1.MaxLength)(retailIntegrationLimits_1.RETAIL_SHORT_TEXT_MAX_LENGTH),
38
41
  __metadata("design:type", String)
39
42
  ], RetailUserEventPayload.prototype, "phone", void 0);
40
43
  __decorate([
@@ -24,31 +24,37 @@ exports.StoreEventAddressPayload = StoreEventAddressPayload;
24
24
  __decorate([
25
25
  (0, class_validator_1.IsString)(),
26
26
  (0, class_validator_1.Matches)(/\S/),
27
+ (0, class_validator_1.MaxLength)(retailIntegrationLimits_1.RETAIL_SHORT_TEXT_MAX_LENGTH),
27
28
  __metadata("design:type", String)
28
29
  ], StoreEventAddressPayload.prototype, "address1", void 0);
29
30
  __decorate([
30
31
  (0, class_validator_1.ValidateIf)(payloadValidators_1.isNullOrValidated),
31
32
  (0, class_validator_1.IsString)(),
33
+ (0, class_validator_1.MaxLength)(retailIntegrationLimits_1.RETAIL_SHORT_TEXT_MAX_LENGTH),
32
34
  __metadata("design:type", String)
33
35
  ], StoreEventAddressPayload.prototype, "externalNumber", void 0);
34
36
  __decorate([
35
37
  (0, class_validator_1.ValidateIf)(payloadValidators_1.isNullOrValidated),
36
38
  (0, class_validator_1.IsString)(),
39
+ (0, class_validator_1.MaxLength)(retailIntegrationLimits_1.RETAIL_SHORT_TEXT_MAX_LENGTH),
37
40
  __metadata("design:type", String)
38
41
  ], StoreEventAddressPayload.prototype, "internalNumber", void 0);
39
42
  __decorate([
40
43
  (0, class_validator_1.ValidateIf)(payloadValidators_1.isNullOrValidated),
41
44
  (0, class_validator_1.IsString)(),
45
+ (0, class_validator_1.MaxLength)(retailIntegrationLimits_1.RETAIL_SHORT_TEXT_MAX_LENGTH),
42
46
  __metadata("design:type", String)
43
47
  ], StoreEventAddressPayload.prototype, "neighborhood", void 0);
44
48
  __decorate([
45
49
  (0, class_validator_1.IsString)(),
46
50
  (0, class_validator_1.Matches)(/\S/),
51
+ (0, class_validator_1.MaxLength)(retailIntegrationLimits_1.RETAIL_SHORT_TEXT_MAX_LENGTH),
47
52
  __metadata("design:type", String)
48
53
  ], StoreEventAddressPayload.prototype, "city", void 0);
49
54
  __decorate([
50
55
  (0, class_validator_1.IsString)(),
51
56
  (0, class_validator_1.Matches)(/\S/),
57
+ (0, class_validator_1.MaxLength)(retailIntegrationLimits_1.RETAIL_SHORT_TEXT_MAX_LENGTH),
52
58
  __metadata("design:type", String)
53
59
  ], StoreEventAddressPayload.prototype, "state", void 0);
54
60
  __decorate([
@@ -59,11 +65,13 @@ __decorate([
59
65
  __decorate([
60
66
  (0, class_validator_1.ValidateIf)(payloadValidators_1.isNullOrValidated),
61
67
  (0, class_validator_1.IsString)(),
68
+ (0, class_validator_1.MaxLength)(retailIntegrationLimits_1.RETAIL_SHORT_TEXT_MAX_LENGTH),
62
69
  __metadata("design:type", String)
63
70
  ], StoreEventAddressPayload.prototype, "country", void 0);
64
71
  __decorate([
65
72
  (0, class_validator_1.ValidateIf)(payloadValidators_1.isNullOrValidated),
66
73
  (0, class_validator_1.IsString)(),
74
+ (0, class_validator_1.MaxLength)(retailIntegrationLimits_1.RETAIL_LONG_TEXT_MAX_LENGTH),
67
75
  __metadata("design:type", String)
68
76
  ], StoreEventAddressPayload.prototype, "reference", void 0);
69
77
  /** Coordenada de la tienda; `null` si no se informa. */
@@ -101,6 +109,7 @@ __decorate([
101
109
  __decorate([
102
110
  (0, class_validator_1.IsString)(),
103
111
  (0, class_validator_1.Matches)(/\S/),
112
+ (0, class_validator_1.MaxLength)(retailIntegrationLimits_1.RETAIL_SHORT_TEXT_MAX_LENGTH),
104
113
  __metadata("design:type", String)
105
114
  ], StoreEventPayload.prototype, "name", void 0);
106
115
  __decorate([
@@ -111,12 +120,14 @@ __decorate([
111
120
  ], StoreEventPayload.prototype, "zoneId", void 0);
112
121
  __decorate([
113
122
  (0, class_validator_1.IsDefined)(),
123
+ (0, class_validator_1.IsObject)(),
114
124
  (0, class_validator_1.ValidateNested)(),
115
125
  (0, class_transformer_1.Type)(() => StoreEventAddressPayload),
116
126
  __metadata("design:type", StoreEventAddressPayload)
117
127
  ], StoreEventPayload.prototype, "address", void 0);
118
128
  __decorate([
119
129
  (0, class_validator_1.IsDefined)(),
130
+ (0, class_validator_1.IsObject)(),
120
131
  (0, class_validator_1.ValidateNested)(),
121
132
  (0, class_transformer_1.Type)(() => StoreEventLocationPayload),
122
133
  __metadata("design:type", StoreEventLocationPayload)
@@ -124,11 +135,13 @@ __decorate([
124
135
  __decorate([
125
136
  (0, class_validator_1.IsString)(),
126
137
  (0, class_validator_1.Matches)(/\S/),
138
+ (0, class_validator_1.MaxLength)(retailIntegrationLimits_1.RETAIL_SHORT_TEXT_MAX_LENGTH),
127
139
  __metadata("design:type", String)
128
140
  ], StoreEventPayload.prototype, "phone", void 0);
129
141
  __decorate([
130
142
  (0, class_validator_1.ValidateIf)(payloadValidators_1.isNullOrValidated),
131
143
  (0, class_validator_1.IsString)(),
144
+ (0, class_validator_1.MaxLength)(retailIntegrationLimits_1.RETAIL_SHORT_TEXT_MAX_LENGTH),
132
145
  __metadata("design:type", String)
133
146
  ], StoreEventPayload.prototype, "schedule", void 0);
134
147
  __decorate([
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fiado/type-kit",
3
- "version": "3.583.0",
3
+ "version": "3.584.0",
4
4
  "description": "",
5
5
  "main": "bin/index.js",
6
6
  "types": "bin/index.d.ts",
@@ -29,3 +29,24 @@ export const RETAIL_DECIMAL_PATTERN = /^\d+\.\d{2}$/;
29
29
 
30
30
  /** Tope de identificadores por consulta de inventario en vivo. */
31
31
  export const RETAIL_LIVE_CHECK_IDENTIFIERS_MAX = 50;
32
+
33
+ /** Tope de piezas por evento `InventoryMovementV1`; más se rechaza con INVALID_PAYLOAD. */
34
+ export const RETAIL_EVENT_PIECES_MAX = 30;
35
+
36
+ /** Tope de ids y códigos libres del retailer, como `externalId`. */
37
+ export const RETAIL_ID_TEXT_MAX_LENGTH = 64;
38
+
39
+ /** Tope de textos cortos de los payloads: nombres, dirección, teléfono, marca, modelo, `identifier`. */
40
+ export const RETAIL_SHORT_TEXT_MAX_LENGTH = 255;
41
+
42
+ /** Tope de textos largos de los payloads: descripción y referencia. */
43
+ export const RETAIL_LONG_TEXT_MAX_LENGTH = 1000;
44
+
45
+ /** Tope de existencias por evento `InventoryMovementV1`. */
46
+ export const RETAIL_EVENT_STOCK_MAX = 200;
47
+
48
+ /** Tope de financieras elegibles por producto. */
49
+ export const RETAIL_ELIGIBLE_FINANCIERS_MAX = 50;
50
+
51
+ /** Decimal de los eventos del retailer: hasta 12 enteros y dos decimales, nunca negativo. */
52
+ export const RETAIL_EVENT_DECIMAL_PATTERN = /^\d{1,12}\.\d{2}$/;
@@ -1,6 +1,12 @@
1
1
  import { Type } from 'class-transformer';
2
- import { IsArray, IsIn, IsInt, IsString, Matches, Min, Validate, ValidateNested } from 'class-validator';
3
- import { RETAIL_ENTITY_ID_PATTERN, RETAIL_SKU_PATTERN } from '../constants/retailIntegrationLimits';
2
+ import { ArrayMaxSize, IsArray, IsIn, IsInt, IsString, Matches, MaxLength, Min, Validate, ValidateNested } from 'class-validator';
3
+ import {
4
+ RETAIL_EVENT_PIECES_MAX,
5
+ RETAIL_ENTITY_ID_PATTERN,
6
+ RETAIL_EVENT_STOCK_MAX,
7
+ RETAIL_SHORT_TEXT_MAX_LENGTH,
8
+ RETAIL_SKU_PATTERN,
9
+ } from '../constants/retailIntegrationLimits';
4
10
  import { RetailMovementTypeEnum, RetailPieceStatusEnum } from '../enums';
5
11
  import type { RetailPieceReason, RetailPieceStatus, RetailSystemPiece, RetailSystemStock } from '../models/RetailSystemInventory';
6
12
  import { HasUniqueKeys, ReasonMatchesStatus, TargetStoreMatchesMovement } from '../validators/payloadValidators';
@@ -10,7 +16,7 @@ export type RetailMovementType = `${RetailMovementTypeEnum}`;
10
16
  /** Pieza con IMEI o serie, como quedó después del movimiento. */
11
17
  export class PieceEventPayload implements RetailSystemPiece {
12
18
  @IsString() @Matches(RETAIL_SKU_PATTERN) sku!: string;
13
- @IsString() @Matches(/\S/) identifier!: string;
19
+ @IsString() @Matches(/\S/) @MaxLength(RETAIL_SHORT_TEXT_MAX_LENGTH) identifier!: string;
14
20
  @IsString() @Matches(RETAIL_ENTITY_ID_PATTERN) storeId!: string;
15
21
  @IsIn(Object.values(RetailPieceStatusEnum)) status!: RetailPieceStatus;
16
22
  @Validate(ReasonMatchesStatus) reason!: RetailPieceReason | null;
@@ -28,6 +34,6 @@ export class InventoryEventPayload {
28
34
  @IsIn(Object.values(RetailMovementTypeEnum)) movementType!: RetailMovementType;
29
35
  @IsString() @Matches(RETAIL_ENTITY_ID_PATTERN) storeId!: string;
30
36
  @Validate(TargetStoreMatchesMovement) targetStoreId!: string | null;
31
- @IsArray() @ValidateNested({ each: true }) @Type(() => PieceEventPayload) @Validate(HasUniqueKeys, ['identifier']) pieces!: PieceEventPayload[];
32
- @IsArray() @ValidateNested({ each: true }) @Type(() => StockEventPayload) @Validate(HasUniqueKeys, ['storeId', 'sku']) stock!: StockEventPayload[];
37
+ @IsArray() @ArrayMaxSize(RETAIL_EVENT_PIECES_MAX) @ValidateNested({ each: true }) @Type(() => PieceEventPayload) @Validate(HasUniqueKeys, ['identifier']) pieces!: PieceEventPayload[];
38
+ @IsArray() @ArrayMaxSize(RETAIL_EVENT_STOCK_MAX) @ValidateNested({ each: true }) @Type(() => StockEventPayload) @Validate(HasUniqueKeys, ['storeId', 'sku']) stock!: StockEventPayload[];
33
39
  }
@@ -1,6 +1,13 @@
1
1
  import { Type } from 'class-transformer';
2
- import { IsArray, IsDefined, IsIn, IsString, Matches, Validate, ValidateIf, ValidateNested } from 'class-validator';
3
- import { RETAIL_DECIMAL_PATTERN, RETAIL_SKU_PATTERN } from '../constants/retailIntegrationLimits';
2
+ import { ArrayMaxSize, IsArray, IsDefined, IsIn, IsObject, IsString, Matches, MaxLength, Validate, ValidateIf, ValidateNested } from 'class-validator';
3
+ import {
4
+ RETAIL_ELIGIBLE_FINANCIERS_MAX,
5
+ RETAIL_EVENT_DECIMAL_PATTERN,
6
+ RETAIL_ID_TEXT_MAX_LENGTH,
7
+ RETAIL_LONG_TEXT_MAX_LENGTH,
8
+ RETAIL_SHORT_TEXT_MAX_LENGTH,
9
+ RETAIL_SKU_PATTERN,
10
+ } from '../constants/retailIntegrationLimits';
4
11
  import {
5
12
  RetailCurrencyEnum,
6
13
  RetailFinancierEnum,
@@ -26,26 +33,26 @@ import { isNullOrValidated } from '../validators/payloadValidators';
26
33
 
27
34
  /** Precio del producto en decimales string. */
28
35
  export class ProductEventPricePayload implements RetailSystemProductPrice {
29
- @IsString() @Matches(RETAIL_DECIMAL_PATTERN) basePrice!: string;
30
- @ValidateIf(isNullOrValidated) @IsString() @Matches(RETAIL_DECIMAL_PATTERN) listPrice!: string | null;
36
+ @IsString() @Matches(RETAIL_EVENT_DECIMAL_PATTERN) basePrice!: string;
37
+ @ValidateIf(isNullOrValidated) @IsString() @Matches(RETAIL_EVENT_DECIMAL_PATTERN) listPrice!: string | null;
31
38
  @Validate(IsIsoDateTimeWithZone) effectiveFrom!: string;
32
39
  }
33
40
 
34
41
  /** Payload de los eventos de producto: el producto completo. */
35
42
  export class ProductEventPayload implements RetailSystemProduct {
36
43
  @IsString() @Matches(RETAIL_SKU_PATTERN) sku!: string;
37
- @IsString() @Matches(/\S/) externalId!: string;
38
- @IsString() @Matches(/\S/) brand!: string;
39
- @IsString() @Matches(/\S/) model!: string;
40
- @ValidateIf(isNullOrValidated) @IsString() color!: string | null;
41
- @ValidateIf(isNullOrValidated) @IsString() capacity!: string | null;
42
- @ValidateIf(isNullOrValidated) @IsString() description!: string | null;
44
+ @IsString() @Matches(/\S/) @MaxLength(RETAIL_ID_TEXT_MAX_LENGTH) externalId!: string;
45
+ @IsString() @Matches(/\S/) @MaxLength(RETAIL_SHORT_TEXT_MAX_LENGTH) brand!: string;
46
+ @IsString() @Matches(/\S/) @MaxLength(RETAIL_SHORT_TEXT_MAX_LENGTH) model!: string;
47
+ @ValidateIf(isNullOrValidated) @IsString() @MaxLength(RETAIL_SHORT_TEXT_MAX_LENGTH) color!: string | null;
48
+ @ValidateIf(isNullOrValidated) @IsString() @MaxLength(RETAIL_SHORT_TEXT_MAX_LENGTH) capacity!: string | null;
49
+ @ValidateIf(isNullOrValidated) @IsString() @MaxLength(RETAIL_LONG_TEXT_MAX_LENGTH) description!: string | null;
43
50
  @IsIn(Object.values(RetailProductCategoryEnum)) category!: RetailProductCategory;
44
51
  @IsIn(Object.values(RetailTrackingModeEnum)) trackingMode!: RetailTrackingMode;
45
52
  @ValidateIf(isNullOrValidated) @IsIn(Object.values(RetailProductTierEnum)) tier!: RetailProductTier | null;
46
53
  @IsIn(Object.values(RetailCurrencyEnum)) currencyId!: RetailCurrency;
47
- @IsDefined() @ValidateNested() @Type(() => ProductEventPricePayload) price!: ProductEventPricePayload;
48
- @IsArray() @IsIn(Object.values(RetailFinancierEnum), { each: true }) eligibleFinanciers!: RetailFinancier[];
54
+ @IsDefined() @IsObject() @ValidateNested() @Type(() => ProductEventPricePayload) price!: ProductEventPricePayload;
55
+ @IsArray() @ArrayMaxSize(RETAIL_ELIGIBLE_FINANCIERS_MAX) @IsIn(Object.values(RetailFinancierEnum), { each: true }) eligibleFinanciers!: RetailFinancier[];
49
56
  @ValidateIf(isNullOrValidated) @IsIn(Object.values(RetailMdmProviderEnum)) mdmProvider!: RetailMdmProvider | null;
50
57
  @IsIn(Object.values(RetailProductStatusEnum)) status!: RetailProductStatus;
51
58
  }
@@ -1,5 +1,5 @@
1
- import { IsEmail, IsIn, IsString, Matches, ValidateIf } from 'class-validator';
2
- import { RETAIL_ENTITY_ID_PATTERN, RETAIL_ZONE_ID_PATTERN } from '../constants/retailIntegrationLimits';
1
+ import { IsEmail, IsIn, IsString, Matches, MaxLength, ValidateIf } from 'class-validator';
2
+ import { RETAIL_ENTITY_ID_PATTERN, RETAIL_SHORT_TEXT_MAX_LENGTH, RETAIL_ZONE_ID_PATTERN } from '../constants/retailIntegrationLimits';
3
3
  import { RetailUserAccessStatusEnum, RetailUserRoleEnum } from '../enums';
4
4
  import type { RetailSystemUser, RetailUserAccessStatus, RetailUserRole } from '../models/RetailSystemUser';
5
5
  import { isNullOrValidated } from '../validators/payloadValidators';
@@ -7,9 +7,9 @@ import { isNullOrValidated } from '../validators/payloadValidators';
7
7
  /** Payload de los cuatro eventos de usuario: el usuario completo. */
8
8
  export class RetailUserEventPayload implements RetailSystemUser {
9
9
  @IsString() @Matches(RETAIL_ENTITY_ID_PATTERN) retailUserId!: string;
10
- @IsString() @Matches(/\S/) displayName!: string;
11
- @IsEmail() email!: string;
12
- @ValidateIf(isNullOrValidated) @IsString() phone!: string | null;
10
+ @IsString() @Matches(/\S/) @MaxLength(RETAIL_SHORT_TEXT_MAX_LENGTH) displayName!: string;
11
+ @IsEmail() @MaxLength(RETAIL_SHORT_TEXT_MAX_LENGTH) email!: string;
12
+ @ValidateIf(isNullOrValidated) @IsString() @MaxLength(RETAIL_SHORT_TEXT_MAX_LENGTH) phone!: string | null;
13
13
  @IsString() @Matches(RETAIL_ENTITY_ID_PATTERN) homeStoreId!: string;
14
14
  @ValidateIf(isNullOrValidated) @IsString() @Matches(RETAIL_ZONE_ID_PATTERN) zoneId!: string | null;
15
15
  @IsIn(Object.values(RetailUserRoleEnum)) role!: RetailUserRole;
@@ -1,7 +1,9 @@
1
1
  import { Type } from 'class-transformer';
2
- import { IsDefined, IsIn, IsNumber, IsString, Length, Matches, Max, Min, ValidateIf, ValidateNested } from 'class-validator';
2
+ import { IsDefined, IsIn, IsNumber, IsObject, IsString, Length, Matches, Max, MaxLength, Min, ValidateIf, ValidateNested } from 'class-validator';
3
3
  import {
4
4
  RETAIL_ENTITY_ID_PATTERN,
5
+ RETAIL_LONG_TEXT_MAX_LENGTH,
6
+ RETAIL_SHORT_TEXT_MAX_LENGTH,
5
7
  RETAIL_STORE_CODE_MAX_LENGTH,
6
8
  RETAIL_ZIP_CODE_MAX_LENGTH,
7
9
  RETAIL_ZIP_CODE_MIN_LENGTH,
@@ -16,15 +18,15 @@ const LNG_MAX = 180;
16
18
 
17
19
  /** Dirección de la tienda; los opcionales llegan como `null`, nunca se omiten. */
18
20
  export class StoreEventAddressPayload implements RetailSystemStoreAddress {
19
- @IsString() @Matches(/\S/) address1!: string;
20
- @ValidateIf(isNullOrValidated) @IsString() externalNumber!: string | null;
21
- @ValidateIf(isNullOrValidated) @IsString() internalNumber!: string | null;
22
- @ValidateIf(isNullOrValidated) @IsString() neighborhood!: string | null;
23
- @IsString() @Matches(/\S/) city!: string;
24
- @IsString() @Matches(/\S/) state!: string;
21
+ @IsString() @Matches(/\S/) @MaxLength(RETAIL_SHORT_TEXT_MAX_LENGTH) address1!: string;
22
+ @ValidateIf(isNullOrValidated) @IsString() @MaxLength(RETAIL_SHORT_TEXT_MAX_LENGTH) externalNumber!: string | null;
23
+ @ValidateIf(isNullOrValidated) @IsString() @MaxLength(RETAIL_SHORT_TEXT_MAX_LENGTH) internalNumber!: string | null;
24
+ @ValidateIf(isNullOrValidated) @IsString() @MaxLength(RETAIL_SHORT_TEXT_MAX_LENGTH) neighborhood!: string | null;
25
+ @IsString() @Matches(/\S/) @MaxLength(RETAIL_SHORT_TEXT_MAX_LENGTH) city!: string;
26
+ @IsString() @Matches(/\S/) @MaxLength(RETAIL_SHORT_TEXT_MAX_LENGTH) state!: string;
25
27
  @IsString() @Length(RETAIL_ZIP_CODE_MIN_LENGTH, RETAIL_ZIP_CODE_MAX_LENGTH) zipCode!: string;
26
- @ValidateIf(isNullOrValidated) @IsString() country!: string | null;
27
- @ValidateIf(isNullOrValidated) @IsString() reference!: string | null;
28
+ @ValidateIf(isNullOrValidated) @IsString() @MaxLength(RETAIL_SHORT_TEXT_MAX_LENGTH) country!: string | null;
29
+ @ValidateIf(isNullOrValidated) @IsString() @MaxLength(RETAIL_LONG_TEXT_MAX_LENGTH) reference!: string | null;
28
30
  }
29
31
 
30
32
  /** Coordenada de la tienda; `null` si no se informa. */
@@ -37,11 +39,11 @@ export class StoreEventLocationPayload implements RetailSystemStoreLocation {
37
39
  export class StoreEventPayload implements RetailSystemStore {
38
40
  @IsString() @Matches(RETAIL_ENTITY_ID_PATTERN) storeId!: string;
39
41
  @IsString() @Length(1, RETAIL_STORE_CODE_MAX_LENGTH) code!: string;
40
- @IsString() @Matches(/\S/) name!: string;
42
+ @IsString() @Matches(/\S/) @MaxLength(RETAIL_SHORT_TEXT_MAX_LENGTH) name!: string;
41
43
  @ValidateIf(isNullOrValidated) @IsString() @Matches(RETAIL_ZONE_ID_PATTERN) zoneId!: string | null;
42
- @IsDefined() @ValidateNested() @Type(() => StoreEventAddressPayload) address!: StoreEventAddressPayload;
43
- @IsDefined() @ValidateNested() @Type(() => StoreEventLocationPayload) location!: StoreEventLocationPayload;
44
- @IsString() @Matches(/\S/) phone!: string;
45
- @ValidateIf(isNullOrValidated) @IsString() schedule!: string | null;
44
+ @IsDefined() @IsObject() @ValidateNested() @Type(() => StoreEventAddressPayload) address!: StoreEventAddressPayload;
45
+ @IsDefined() @IsObject() @ValidateNested() @Type(() => StoreEventLocationPayload) location!: StoreEventLocationPayload;
46
+ @IsString() @Matches(/\S/) @MaxLength(RETAIL_SHORT_TEXT_MAX_LENGTH) phone!: string;
47
+ @ValidateIf(isNullOrValidated) @IsString() @MaxLength(RETAIL_SHORT_TEXT_MAX_LENGTH) schedule!: string | null;
46
48
  @IsIn(Object.values(RetailStoreStatusEnum)) status!: RetailStoreStatus;
47
49
  }