@fiado/type-kit 3.605.0 → 3.607.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.
@@ -42,7 +42,7 @@ const price = { basePrice: '4299.00', listPrice: null, effectiveFrom: '2026-08-1
42
42
  const product = {
43
43
  sku: 'SAM-A17-128-NEG', externalId: '9001', brand: 'Samsung', model: 'A17', color: 'Negro', capacity: '128 GB', description: null,
44
44
  category: 'PHONE', trackingMode: 'IMEI', tier: null, currencyId: 'MXN', price,
45
- eligibleFinanciers: [], mdmProvider: null, status: 'ACTIVE',
45
+ eligibleFinanciers: ['SUREKEEP'], mdmProvider: null, status: 'ACTIVE',
46
46
  };
47
47
  const piece = { sku: 'SAM-A17-128-NEG', identifier: '356938035643809', storeId: '12', status: 'SOLD', reason: null };
48
48
  const movement = { movementType: 'SALE', storeId: '12', targetStoreId: null, pieces: [piece], stock: [] };
@@ -105,7 +105,7 @@ describe('ProductEventPayload: topes', () => {
105
105
  const atMax = {
106
106
  ...product, externalId: text(ID), brand: text(SHORT), model: text(SHORT), color: text(SHORT), capacity: text(SHORT), description: text(LONG),
107
107
  price: { ...price, basePrice: '999999999999.99', listPrice: '999999999999.99' },
108
- eligibleFinanciers: Array.from({ length: RETAIL_ELIGIBLE_FINANCIERS_MAX }, () => 'SUREKEEP'),
108
+ eligibleFinanciers: ['SUREKEEP', 'PAYJOY', 'CREDIYA', 'LESPAGO', 'INNOVA'],
109
109
  };
110
110
  expect(await errorsOf(ProductEventPayload, atMax)).toEqual([]);
111
111
  });
@@ -6,6 +6,8 @@ import { RetailUserEventPayload } from '../../../src/retailIntegration/dtos/Reta
6
6
  import { ProductEventPayload } from '../../../src/retailIntegration/dtos/ProductEventPayload';
7
7
  import { InventoryEventPayload } from '../../../src/retailIntegration/dtos/InventoryEventPayload';
8
8
  import { RETAIL_EVENT_PIECES_MAX } from '../../../src/retailIntegration/constants/retailIntegrationLimits';
9
+ import { RetailProductTierEnum } from '../../../src/retailIntegration/enums';
10
+ import { ProductTierEnum } from '../../../src/retailCatalog/enums/ProductTierEnum';
9
11
 
10
12
  function paths(errors: ValidationError[], prefix = ''): string[] {
11
13
  return errors.flatMap((e) => {
@@ -78,7 +80,7 @@ const product = {
78
80
  sku: 'SAM-A17-128-NEG', externalId: '9001', brand: 'Samsung', model: 'A17', color: 'Negro', capacity: '128 GB', description: null,
79
81
  category: 'PHONE', trackingMode: 'IMEI', tier: null, currencyId: 'MXN',
80
82
  price: { basePrice: '4299.00', listPrice: null, effectiveFrom: '2026-08-17T20:32:05Z' },
81
- eligibleFinanciers: [], mdmProvider: null, status: 'ACTIVE',
83
+ eligibleFinanciers: ['SUREKEEP'], mdmProvider: null, status: 'ACTIVE',
82
84
  };
83
85
 
84
86
  const piece = { sku: 'SAM-A17-128-NEG', identifier: '356938035643809', storeId: '12', status: 'SOLD', reason: null };
@@ -93,6 +95,41 @@ describe('ProductEventPayload', () => {
93
95
  expect(await errorsOf(ProductEventPayload, { ...product, mdmProvider })).toEqual([]);
94
96
  });
95
97
 
98
+ it.each([
99
+ ['ausente', undefined],
100
+ ['null', null],
101
+ ['de la gama', 'PREMIUM'],
102
+ ])('acepta tier %s', async (_case, tier) => {
103
+ expect(await errorsOf(ProductEventPayload, { ...product, tier })).toEqual([]);
104
+ });
105
+
106
+ it.each([
107
+ ['ausente', undefined],
108
+ ['null', null],
109
+ ['con financieras', ['SUREKEEP', 'PAYJOY']],
110
+ ])('acepta eligibleFinanciers %s', async (_case, eligibleFinanciers) => {
111
+ expect(await errorsOf(ProductEventPayload, { ...product, eligibleFinanciers })).toEqual([]);
112
+ });
113
+
114
+ it('al transformar, tier ausente queda null y eligibleFinanciers ausente o null queda con todas', () => {
115
+ const { tier: _tier, eligibleFinanciers: _financiers, ...withoutKeys } = product;
116
+ const all = ['SUREKEEP', 'PAYJOY', 'CREDIYA', 'LESPAGO', 'INNOVA'];
117
+ const fromAbsent = plainToInstance(ProductEventPayload, withoutKeys);
118
+ expect(fromAbsent.tier).toBeNull();
119
+ expect(fromAbsent.eligibleFinanciers).toEqual(all);
120
+ expect(plainToInstance(ProductEventPayload, { ...product, eligibleFinanciers: null }).eligibleFinanciers).toEqual(all);
121
+ });
122
+
123
+ it('al transformar, conserva tier y eligibleFinanciers cuando vienen', () => {
124
+ const dto = plainToInstance(ProductEventPayload, { ...product, tier: 'MID', eligibleFinanciers: ['PAYJOY'] });
125
+ expect(dto.tier).toBe('MID');
126
+ expect(dto.eligibleFinanciers).toEqual(['PAYJOY']);
127
+ });
128
+
129
+ it('la gama de la integración trae los mismos valores que la de catálogo', () => {
130
+ expect(Object.values(RetailProductTierEnum)).toEqual(Object.values(ProductTierEnum));
131
+ });
132
+
96
133
  it('rechaza un mdmProvider fuera del enum', async () => {
97
134
  expect(await errorsOf(ProductEventPayload, { ...product, mdmProvider: 'KNOX' })).toContain('mdmProvider');
98
135
  });
@@ -102,7 +139,11 @@ describe('ProductEventPayload', () => {
102
139
  ['price.basePrice', { price: { ...product.price, basePrice: '4299' } }],
103
140
  ['price.effectiveFrom', { price: { ...product.price, effectiveFrom: '2026-08-17T20:32:05' } }],
104
141
  ['eligibleFinanciers', { eligibleFinanciers: ['NADIE'] }],
105
- ['tier', { tier: undefined }],
142
+ ['eligibleFinanciers', { eligibleFinanciers: [] }],
143
+ ['eligibleFinanciers', { eligibleFinanciers: ['SUREKEEP', 'SUREKEEP'] }],
144
+ ['eligibleFinanciers', { eligibleFinanciers: 'SUREKEEP' }],
145
+ ['tier', { tier: 'ULTRA' }],
146
+ ['tier', { tier: 'low' }],
106
147
  ['price.listPrice', { price: { basePrice: '4299.00', effectiveFrom: '2026-08-17T20:32:05Z' } }],
107
148
  ])('rechaza %s', async (path, over) => {
108
149
  expect(await errorsOf(ProductEventPayload, { ...product, ...over })).toContain(path);
@@ -45,6 +45,13 @@ describe('CreateDiscountCodeRequest', () => {
45
45
  });
46
46
  expect(paths).toEqual(expect.arrayContaining(['discount.percentBps', 'maxUses', 'appliesTo']));
47
47
  });
48
+
49
+ it('acepta paymentOptions como lista del catálogo o null, y rechaza otra cosa', async () => {
50
+ expect(await errorPaths(CreateDiscountCodeRequest, { ...base, paymentOptions: ['CASH', 'CARD'] })).toHaveLength(0);
51
+ expect(await errorPaths(CreateDiscountCodeRequest, { ...base, paymentOptions: null })).toHaveLength(0);
52
+ expect(await errorPaths(CreateDiscountCodeRequest, { ...base, paymentOptions: ['BITCOIN'] })).toEqual(['paymentOptions']);
53
+ expect(await errorPaths(CreateDiscountCodeRequest, { ...base, paymentOptions: 'CASH' })).toEqual(['paymentOptions']);
54
+ });
48
55
  });
49
56
 
50
57
  describe('ChangeDiscountCodeStatusRequest', () => {
@@ -16,9 +16,11 @@ export declare class ProductEventPayload implements RetailSystemProduct {
16
16
  description: string | null;
17
17
  category: RetailProductCategory;
18
18
  trackingMode: RetailTrackingMode;
19
+ /** Opcional en el evento: ausente se vuelve `null`. */
19
20
  tier: RetailProductTier | null;
20
21
  currencyId: RetailCurrency;
21
22
  price: ProductEventPricePayload;
23
+ /** Opcional en el evento: ausente o `null` se vuelve la lista de todas las financieras. */
22
24
  eligibleFinanciers: RetailFinancier[];
23
25
  mdmProvider: RetailMdmProvider | null;
24
26
  status: RetailProductStatus;
@@ -89,7 +89,9 @@ __decorate([
89
89
  __metadata("design:type", String)
90
90
  ], ProductEventPayload.prototype, "trackingMode", void 0);
91
91
  __decorate([
92
- (0, class_validator_1.ValidateIf)(payloadValidators_1.isNullOrValidated),
92
+ (0, class_transformer_1.Expose)(),
93
+ (0, class_transformer_1.Transform)(({ value }) => value ?? null),
94
+ (0, class_validator_1.IsOptional)(),
93
95
  (0, class_validator_1.IsIn)(Object.values(enums_1.RetailProductTierEnum)),
94
96
  __metadata("design:type", String)
95
97
  ], ProductEventPayload.prototype, "tier", void 0);
@@ -105,7 +107,12 @@ __decorate([
105
107
  __metadata("design:type", ProductEventPricePayload)
106
108
  ], ProductEventPayload.prototype, "price", void 0);
107
109
  __decorate([
110
+ (0, class_transformer_1.Expose)(),
111
+ (0, class_transformer_1.Transform)(({ value }) => value ?? Object.values(enums_1.RetailFinancierEnum)),
112
+ (0, class_validator_1.IsOptional)(),
108
113
  (0, class_validator_1.IsArray)(),
114
+ (0, class_validator_1.ArrayNotEmpty)(),
115
+ (0, class_validator_1.ArrayUnique)(),
109
116
  (0, class_validator_1.ArrayMaxSize)(retailIntegrationLimits_1.RETAIL_ELIGIBLE_FINANCIERS_MAX),
110
117
  (0, class_validator_1.IsIn)(Object.values(enums_1.RetailFinancierEnum), { each: true }),
111
118
  __metadata("design:type", Array)
@@ -1,4 +1,4 @@
1
- /** Gama de un producto. */
1
+ /** Gama de un producto; mismos valores que `ProductTierEnum` de retailCatalog. */
2
2
  export declare enum RetailProductTierEnum {
3
3
  LOW = "LOW",
4
4
  MID = "MID",
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.RetailProductTierEnum = void 0;
4
- /** Gama de un producto. */
4
+ /** Gama de un producto; mismos valores que `ProductTierEnum` de retailCatalog. */
5
5
  var RetailProductTierEnum;
6
6
  (function (RetailProductTierEnum) {
7
7
  RetailProductTierEnum["LOW"] = "LOW";
@@ -3,6 +3,7 @@ import { CouponStatusEnum } from '../enums/CouponStatusEnum';
3
3
  import { DiscountCodeOriginEnum } from '../enums/DiscountCodeOriginEnum';
4
4
  import { DiscountCodeStatusEnum } from '../enums/DiscountCodeStatusEnum';
5
5
  import { PromotionAppliesToEnum } from '../enums/PromotionAppliesToEnum';
6
+ import { PromotionPaymentOptionEnum } from '../enums/PromotionPaymentOptionEnum';
6
7
  import { DiscountValue } from './DiscountValue';
7
8
  /**
8
9
  * Código de descuento de Admin VL o cupón emitido por una venta (según `origin`).
@@ -22,7 +23,10 @@ export interface DiscountCode {
22
23
  skus: string[] | null;
23
24
  storeIds: string[] | null;
24
25
  announceInStore: boolean;
26
+ /** @deprecated Usa `paymentOptions`. */
25
27
  appliesTo: PromotionAppliesToEnum;
28
+ /** Formas de pago con las que se canjea; `null` = cualquiera. El cupón hereda las de su promo. */
29
+ paymentOptions: PromotionPaymentOptionEnum[] | null;
26
30
  ownerCustomerId: string | null;
27
31
  issuedBySaleId: string | null;
28
32
  issuingStoreId: string | null;
@@ -1,4 +1,5 @@
1
1
  import { PromotionAppliesToEnum } from '../../enums/PromotionAppliesToEnum';
2
+ import { PromotionPaymentOptionEnum } from '../../enums/PromotionPaymentOptionEnum';
2
3
  import { DiscountValueInput } from './DiscountValueInput';
3
4
  /** Body de publicCreateDiscountCode. Que el texto sea único entre los vigentes es `CODE_TEXT_TAKEN`. */
4
5
  export declare class CreateDiscountCodeRequest {
@@ -12,6 +13,8 @@ export declare class CreateDiscountCodeRequest {
12
13
  skus?: string[] | null;
13
14
  storeIds?: string[] | null;
14
15
  announceInStore?: boolean;
15
- /** Ausente = `SUREKEEP_ONLY`. */
16
+ /** @deprecated Usa `paymentOptions`; solo se lee si `paymentOptions` no viene. Ausente = `SUREKEEP_ONLY`. */
16
17
  appliesTo?: PromotionAppliesToEnum;
18
+ /** Formas de pago con las que se canjea; `null` = cualquiera. Ausente = la que diga `appliesTo`. */
19
+ paymentOptions?: PromotionPaymentOptionEnum[] | null;
17
20
  }
@@ -13,6 +13,7 @@ exports.CreateDiscountCodeRequest = void 0;
13
13
  const class_transformer_1 = require("class-transformer");
14
14
  const class_validator_1 = require("class-validator");
15
15
  const PromotionAppliesToEnum_1 = require("../../enums/PromotionAppliesToEnum");
16
+ const PromotionPaymentOptionEnum_1 = require("../../enums/PromotionPaymentOptionEnum");
16
17
  const DiscountValueInput_1 = require("./DiscountValueInput");
17
18
  /** Body de publicCreateDiscountCode. Que el texto sea único entre los vigentes es `CODE_TEXT_TAKEN`. */
18
19
  class CreateDiscountCodeRequest {
@@ -76,3 +77,10 @@ __decorate([
76
77
  (0, class_validator_1.IsEnum)(PromotionAppliesToEnum_1.PromotionAppliesToEnum),
77
78
  __metadata("design:type", String)
78
79
  ], CreateDiscountCodeRequest.prototype, "appliesTo", void 0);
80
+ __decorate([
81
+ (0, class_transformer_1.Expose)(),
82
+ (0, class_validator_1.IsOptional)(),
83
+ (0, class_validator_1.IsArray)(),
84
+ (0, class_validator_1.IsEnum)(PromotionPaymentOptionEnum_1.PromotionPaymentOptionEnum, { each: true }),
85
+ __metadata("design:type", Array)
86
+ ], CreateDiscountCodeRequest.prototype, "paymentOptions", void 0);
@@ -13,4 +13,6 @@ export interface PromotionPaymentNotEligibleDetails {
13
13
  promotionName: string;
14
14
  /** `PromotionPaymentOptionEnum` que acepta esa promo. */
15
15
  acceptedOptions: string;
16
+ /** Código apartado que no admite el medio; si solo falla el código, promotionName es su texto. */
17
+ discountCode?: string;
16
18
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fiado/type-kit",
3
- "version": "3.605.0",
3
+ "version": "3.607.0",
4
4
  "description": "",
5
5
  "main": "bin/index.js",
6
6
  "types": "bin/index.d.ts",
@@ -1,5 +1,5 @@
1
- import { Type } from 'class-transformer';
2
- import { ArrayMaxSize, IsArray, IsDefined, IsIn, IsObject, IsString, Matches, MaxLength, Validate, ValidateIf, ValidateNested } from 'class-validator';
1
+ import { Expose, Transform, Type } from 'class-transformer';
2
+ import { ArrayMaxSize, ArrayNotEmpty, ArrayUnique, IsArray, IsDefined, IsIn, IsObject, IsOptional, IsString, Matches, MaxLength, Validate, ValidateIf, ValidateNested } from 'class-validator';
3
3
  import {
4
4
  RETAIL_ELIGIBLE_FINANCIERS_MAX,
5
5
  RETAIL_EVENT_DECIMAL_PATTERN,
@@ -49,10 +49,15 @@ export class ProductEventPayload implements RetailSystemProduct {
49
49
  @ValidateIf(isNullOrValidated) @IsString() @MaxLength(RETAIL_LONG_TEXT_MAX_LENGTH) description!: string | null;
50
50
  @IsIn(Object.values(RetailProductCategoryEnum)) category!: RetailProductCategory;
51
51
  @IsIn(Object.values(RetailTrackingModeEnum)) trackingMode!: RetailTrackingMode;
52
- @ValidateIf(isNullOrValidated) @IsIn(Object.values(RetailProductTierEnum)) tier!: RetailProductTier | null;
52
+ /** Opcional en el evento: ausente se vuelve `null`. */
53
+ @Expose() @Transform(({ value }) => value ?? null)
54
+ @IsOptional() @IsIn(Object.values(RetailProductTierEnum)) tier!: RetailProductTier | null;
53
55
  @IsIn(Object.values(RetailCurrencyEnum)) currencyId!: RetailCurrency;
54
56
  @IsDefined() @IsObject() @ValidateNested() @Type(() => ProductEventPricePayload) price!: ProductEventPricePayload;
55
- @IsArray() @ArrayMaxSize(RETAIL_ELIGIBLE_FINANCIERS_MAX) @IsIn(Object.values(RetailFinancierEnum), { each: true }) eligibleFinanciers!: RetailFinancier[];
57
+ /** Opcional en el evento: ausente o `null` se vuelve la lista de todas las financieras. */
58
+ @Expose() @Transform(({ value }) => value ?? Object.values(RetailFinancierEnum))
59
+ @IsOptional() @IsArray() @ArrayNotEmpty() @ArrayUnique() @ArrayMaxSize(RETAIL_ELIGIBLE_FINANCIERS_MAX) @IsIn(Object.values(RetailFinancierEnum), { each: true })
60
+ eligibleFinanciers!: RetailFinancier[];
56
61
  @ValidateIf(isNullOrValidated) @IsIn(Object.values(RetailMdmProviderEnum)) mdmProvider!: RetailMdmProvider | null;
57
62
  @IsIn(Object.values(RetailProductStatusEnum)) status!: RetailProductStatus;
58
63
  }
@@ -1,4 +1,4 @@
1
- /** Gama de un producto. */
1
+ /** Gama de un producto; mismos valores que `ProductTierEnum` de retailCatalog. */
2
2
  export enum RetailProductTierEnum {
3
3
  LOW = 'LOW',
4
4
  MID = 'MID',
@@ -3,6 +3,7 @@ import { CouponStatusEnum } from '../enums/CouponStatusEnum';
3
3
  import { DiscountCodeOriginEnum } from '../enums/DiscountCodeOriginEnum';
4
4
  import { DiscountCodeStatusEnum } from '../enums/DiscountCodeStatusEnum';
5
5
  import { PromotionAppliesToEnum } from '../enums/PromotionAppliesToEnum';
6
+ import { PromotionPaymentOptionEnum } from '../enums/PromotionPaymentOptionEnum';
6
7
  import { DiscountValue } from './DiscountValue';
7
8
 
8
9
  /**
@@ -23,7 +24,10 @@ export interface DiscountCode {
23
24
  skus: string[] | null;
24
25
  storeIds: string[] | null;
25
26
  announceInStore: boolean;
27
+ /** @deprecated Usa `paymentOptions`. */
26
28
  appliesTo: PromotionAppliesToEnum;
29
+ /** Formas de pago con las que se canjea; `null` = cualquiera. El cupón hereda las de su promo. */
30
+ paymentOptions: PromotionPaymentOptionEnum[] | null;
27
31
  ownerCustomerId: string | null;
28
32
  issuedBySaleId: string | null;
29
33
  issuingStoreId: string | null;
@@ -13,6 +13,7 @@ import {
13
13
  ValidateNested,
14
14
  } from 'class-validator';
15
15
  import { PromotionAppliesToEnum } from '../../enums/PromotionAppliesToEnum';
16
+ import { PromotionPaymentOptionEnum } from '../../enums/PromotionPaymentOptionEnum';
16
17
  import { DiscountValueInput } from './DiscountValueInput';
17
18
 
18
19
  /** Body de publicCreateDiscountCode. Que el texto sea único entre los vigentes es `CODE_TEXT_TAKEN`. */
@@ -63,9 +64,16 @@ export class CreateDiscountCodeRequest {
63
64
  @IsBoolean()
64
65
  announceInStore?: boolean;
65
66
 
66
- /** Ausente = `SUREKEEP_ONLY`. */
67
+ /** @deprecated Usa `paymentOptions`; solo se lee si `paymentOptions` no viene. Ausente = `SUREKEEP_ONLY`. */
67
68
  @Expose()
68
69
  @IsOptional()
69
70
  @IsEnum(PromotionAppliesToEnum)
70
71
  appliesTo?: PromotionAppliesToEnum;
72
+
73
+ /** Formas de pago con las que se canjea; `null` = cualquiera. Ausente = la que diga `appliesTo`. */
74
+ @Expose()
75
+ @IsOptional()
76
+ @IsArray()
77
+ @IsEnum(PromotionPaymentOptionEnum, { each: true })
78
+ paymentOptions?: PromotionPaymentOptionEnum[] | null;
71
79
  }
@@ -14,4 +14,6 @@ export interface PromotionPaymentNotEligibleDetails {
14
14
  promotionName: string;
15
15
  /** `PromotionPaymentOptionEnum` que acepta esa promo. */
16
16
  acceptedOptions: string;
17
+ /** Código apartado que no admite el medio; si solo falla el código, promotionName es su texto. */
18
+ discountCode?: string;
17
19
  }