@fiado/type-kit 3.607.0 → 3.608.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: ['SUREKEEP'], mdmProvider: null, status: 'ACTIVE',
45
+ eligibleFinanciers: ['SUREKEEP'], mdmProvider: 'TRUSTONIC', mdmMode: 'SCHEDULED', 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: [] };
@@ -6,7 +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';
9
+ import { RetailMdmModeEnum, RetailProductTierEnum } from '../../../src/retailIntegration/enums';
10
+ import { MdmLockModeEnum } from '../../../src/retailCatalog/enums/MdmLockModeEnum';
10
11
  import { ProductTierEnum } from '../../../src/retailCatalog/enums/ProductTierEnum';
11
12
 
12
13
  function paths(errors: ValidationError[], prefix = ''): string[] {
@@ -80,7 +81,7 @@ const product = {
80
81
  sku: 'SAM-A17-128-NEG', externalId: '9001', brand: 'Samsung', model: 'A17', color: 'Negro', capacity: '128 GB', description: null,
81
82
  category: 'PHONE', trackingMode: 'IMEI', tier: null, currencyId: 'MXN',
82
83
  price: { basePrice: '4299.00', listPrice: null, effectiveFrom: '2026-08-17T20:32:05Z' },
83
- eligibleFinanciers: ['SUREKEEP'], mdmProvider: null, status: 'ACTIVE',
84
+ eligibleFinanciers: ['SUREKEEP'], mdmProvider: 'TRUSTONIC', mdmMode: 'SCHEDULED', status: 'ACTIVE',
84
85
  };
85
86
 
86
87
  const piece = { sku: 'SAM-A17-128-NEG', identifier: '356938035643809', storeId: '12', status: 'SOLD', reason: null };
@@ -91,10 +92,6 @@ describe('ProductEventPayload', () => {
91
92
  expect(await errorsOf(ProductEventPayload, product)).toEqual([]);
92
93
  });
93
94
 
94
- it.each(['TRUSTONIC', 'DATACULTR', 'MOTOSAFE'])('acepta mdmProvider %s', async (mdmProvider) => {
95
- expect(await errorsOf(ProductEventPayload, { ...product, mdmProvider })).toEqual([]);
96
- });
97
-
98
95
  it.each([
99
96
  ['ausente', undefined],
100
97
  ['null', null],
@@ -130,10 +127,60 @@ describe('ProductEventPayload', () => {
130
127
  expect(Object.values(RetailProductTierEnum)).toEqual(Object.values(ProductTierEnum));
131
128
  });
132
129
 
130
+ it.each([
131
+ ['TRUSTONIC', 'SCHEDULED'],
132
+ ['TRUSTONIC', 'ONDEMAND'],
133
+ ['DATACULTR', 'SCHEDULED'],
134
+ ['DATACULTR', 'ONDEMAND'],
135
+ ['MOTOSAFE', 'ONDEMAND'],
136
+ ])('acepta un teléfono IMEI con mdmProvider %s y mdmMode %s', async (mdmProvider, mdmMode) => {
137
+ expect(await errorsOf(ProductEventPayload, { ...product, mdmProvider, mdmMode })).toEqual([]);
138
+ });
139
+
133
140
  it('rechaza un mdmProvider fuera del enum', async () => {
134
141
  expect(await errorsOf(ProductEventPayload, { ...product, mdmProvider: 'KNOX' })).toContain('mdmProvider');
135
142
  });
136
143
 
144
+ it('rechaza un teléfono IMEI sin mdmProvider', async () => {
145
+ expect(await errorsOf(ProductEventPayload, { ...product, mdmProvider: null, mdmMode: null })).toEqual(['mdmProvider']);
146
+ expect(await errorsOf(ProductEventPayload, { ...product, mdmProvider: undefined, mdmMode: null })).toEqual(['mdmProvider']);
147
+ });
148
+
149
+ it('rechaza MOTOSAFE con SCHEDULED', async () => {
150
+ expect(await errorsOf(ProductEventPayload, { ...product, mdmProvider: 'MOTOSAFE', mdmMode: 'SCHEDULED' })).toEqual(['mdmMode']);
151
+ });
152
+
153
+ it.each([
154
+ ['null', null],
155
+ ['ausente', undefined],
156
+ ['fuera del enum', 'ALWAYS'],
157
+ ['con guion bajo', 'ON_DEMAND'],
158
+ ])('rechaza mdmMode %s cuando hay mdmProvider', async (_case, mdmMode) => {
159
+ expect(await errorsOf(ProductEventPayload, { ...product, mdmMode })).toEqual(['mdmMode']);
160
+ });
161
+
162
+ it.each([
163
+ ['ACCESSORY', 'QUANTITY'],
164
+ ['SIM', 'SERIAL'],
165
+ ])('acepta un %s %s con mdmProvider y mdmMode en null', async (category, trackingMode) => {
166
+ expect(await errorsOf(ProductEventPayload, { ...product, category, trackingMode, mdmProvider: null, mdmMode: null })).toEqual([]);
167
+ });
168
+
169
+ it('un producto sin IMEI rechaza mdmProvider y mdmMode', async () => {
170
+ const accessory = { ...product, category: 'ACCESSORY', trackingMode: 'QUANTITY' };
171
+ expect(await errorsOf(ProductEventPayload, { ...accessory, mdmMode: null })).toEqual(['mdmProvider', 'mdmMode']);
172
+ expect(await errorsOf(ProductEventPayload, { ...accessory, mdmProvider: null })).toEqual(['mdmMode']);
173
+ });
174
+
175
+ it('al transformar, mdmMode ausente queda null', () => {
176
+ const { mdmMode: _mode, ...withoutMode } = product;
177
+ expect(plainToInstance(ProductEventPayload, withoutMode).mdmMode).toBeNull();
178
+ });
179
+
180
+ it('el modo de MDM de la integración trae los mismos valores que el de catálogo', () => {
181
+ expect(Object.values(RetailMdmModeEnum).sort()).toEqual(Object.values(MdmLockModeEnum).sort());
182
+ });
183
+
137
184
  it.each([
138
185
  ['sku', { sku: 'SAM_A17' }],
139
186
  ['price.basePrice', { price: { ...product.price, basePrice: '4299' } }],
@@ -4,6 +4,7 @@ import {
4
4
  RetailCurrencyEnum,
5
5
  RetailFinancierEnum,
6
6
  RetailLinePaymentTypeEnum,
7
+ RetailMdmModeEnum,
7
8
  RetailMdmProviderEnum,
8
9
  RetailPaymentFrequencyEnum,
9
10
  RetailPaymentMethodEnum,
@@ -29,6 +30,7 @@ describe('enums de los objetos del retailer', () => {
29
30
  [RetailCurrencyEnum, ['MXN', 'USD']],
30
31
  [RetailFinancierEnum, ['SUREKEEP', 'PAYJOY', 'CREDIYA', 'LESPAGO', 'INNOVA']],
31
32
  [RetailMdmProviderEnum, ['TRUSTONIC', 'DATACULTR', 'MOTOSAFE']],
33
+ [RetailMdmModeEnum, ['SCHEDULED', 'ONDEMAND']],
32
34
  [RetailProductStatusEnum, ['ACTIVE', 'INACTIVE', 'DISCONTINUED']],
33
35
  [RetailPieceStatusEnum, ['AVAILABLE', 'IN_TRANSIT', 'SOLD', 'DAMAGED']],
34
36
  [RetailPieceReasonEnum, ['WARRANTY', 'DAMAGED_RETURN', 'SHRINKAGE']],
@@ -1,4 +1,4 @@
1
- import type { RetailCurrency, RetailFinancier, RetailMdmProvider, RetailProductCategory, RetailProductStatus, RetailProductTier, RetailSystemProduct, RetailSystemProductPrice, RetailTrackingMode } from '../models/RetailSystemProduct';
1
+ import type { RetailCurrency, RetailFinancier, RetailMdmMode, RetailMdmProvider, RetailProductCategory, RetailProductStatus, RetailProductTier, RetailSystemProduct, RetailSystemProductPrice, RetailTrackingMode } from '../models/RetailSystemProduct';
2
2
  /** Precio del producto en decimales string. */
3
3
  export declare class ProductEventPricePayload implements RetailSystemProductPrice {
4
4
  basePrice: string;
@@ -23,5 +23,7 @@ export declare class ProductEventPayload implements RetailSystemProduct {
23
23
  /** Opcional en el evento: ausente o `null` se vuelve la lista de todas las financieras. */
24
24
  eligibleFinanciers: RetailFinancier[];
25
25
  mdmProvider: RetailMdmProvider | null;
26
+ /** Opcional en el evento: ausente se vuelve `null`. */
27
+ mdmMode: RetailMdmMode | null;
26
28
  status: RetailProductStatus;
27
29
  }
@@ -16,6 +16,7 @@ const retailIntegrationLimits_1 = require("../constants/retailIntegrationLimits"
16
16
  const enums_1 = require("../enums");
17
17
  const IsIsoDateTimeWithZone_1 = require("../validators/IsIsoDateTimeWithZone");
18
18
  const payloadValidators_1 = require("../validators/payloadValidators");
19
+ const productValidators_1 = require("../validators/productValidators");
19
20
  /** Precio del producto en decimales string. */
20
21
  class ProductEventPricePayload {
21
22
  }
@@ -118,10 +119,15 @@ __decorate([
118
119
  __metadata("design:type", Array)
119
120
  ], ProductEventPayload.prototype, "eligibleFinanciers", void 0);
120
121
  __decorate([
121
- (0, class_validator_1.ValidateIf)(payloadValidators_1.isNullOrValidated),
122
- (0, class_validator_1.IsIn)(Object.values(enums_1.RetailMdmProviderEnum)),
122
+ (0, class_validator_1.Validate)(productValidators_1.MdmProviderMatchesTracking),
123
123
  __metadata("design:type", String)
124
124
  ], ProductEventPayload.prototype, "mdmProvider", void 0);
125
+ __decorate([
126
+ (0, class_transformer_1.Expose)(),
127
+ (0, class_transformer_1.Transform)(({ value }) => value ?? null),
128
+ (0, class_validator_1.Validate)(productValidators_1.MdmModeMatchesProvider),
129
+ __metadata("design:type", String)
130
+ ], ProductEventPayload.prototype, "mdmMode", void 0);
125
131
  __decorate([
126
132
  (0, class_validator_1.IsIn)(Object.values(enums_1.RetailProductStatusEnum)),
127
133
  __metadata("design:type", String)
@@ -0,0 +1,5 @@
1
+ /** Modo del candado MDM del equipo; mismos valores que `MdmLockModeEnum` de retailCatalog. */
2
+ export declare enum RetailMdmModeEnum {
3
+ SCHEDULED = "SCHEDULED",
4
+ ONDEMAND = "ONDEMAND"
5
+ }
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RetailMdmModeEnum = void 0;
4
+ /** Modo del candado MDM del equipo; mismos valores que `MdmLockModeEnum` de retailCatalog. */
5
+ var RetailMdmModeEnum;
6
+ (function (RetailMdmModeEnum) {
7
+ RetailMdmModeEnum["SCHEDULED"] = "SCHEDULED";
8
+ RetailMdmModeEnum["ONDEMAND"] = "ONDEMAND";
9
+ })(RetailMdmModeEnum || (exports.RetailMdmModeEnum = RetailMdmModeEnum = {}));
@@ -10,6 +10,7 @@ export * from './RetailProductTierEnum';
10
10
  export * from './RetailCurrencyEnum';
11
11
  export * from './RetailFinancierEnum';
12
12
  export * from './RetailMdmProviderEnum';
13
+ export * from './RetailMdmModeEnum';
13
14
  export * from './RetailProductStatusEnum';
14
15
  export * from './RetailPieceStatusEnum';
15
16
  export * from './RetailPieceReasonEnum';
@@ -26,6 +26,7 @@ __exportStar(require("./RetailProductTierEnum"), exports);
26
26
  __exportStar(require("./RetailCurrencyEnum"), exports);
27
27
  __exportStar(require("./RetailFinancierEnum"), exports);
28
28
  __exportStar(require("./RetailMdmProviderEnum"), exports);
29
+ __exportStar(require("./RetailMdmModeEnum"), exports);
29
30
  __exportStar(require("./RetailProductStatusEnum"), exports);
30
31
  __exportStar(require("./RetailPieceStatusEnum"), exports);
31
32
  __exportStar(require("./RetailPieceReasonEnum"), exports);
@@ -5,6 +5,7 @@ export * from './validators/IsIsoDateTimeWithZone';
5
5
  export * from './validators/IsMovementsRangeValid';
6
6
  export * from './validators/IsCalendarDate';
7
7
  export * from './validators/saleValidators';
8
+ export * from './validators/productValidators';
8
9
  export * from './dtos/RetailEventEnvelope';
9
10
  export * from './dtos/ReceiveStoreEventRequest';
10
11
  export * from './dtos/ReceiveRetailUserEventRequest';
@@ -21,6 +21,7 @@ __exportStar(require("./validators/IsIsoDateTimeWithZone"), exports);
21
21
  __exportStar(require("./validators/IsMovementsRangeValid"), exports);
22
22
  __exportStar(require("./validators/IsCalendarDate"), exports);
23
23
  __exportStar(require("./validators/saleValidators"), exports);
24
+ __exportStar(require("./validators/productValidators"), exports);
24
25
  __exportStar(require("./dtos/RetailEventEnvelope"), exports);
25
26
  __exportStar(require("./dtos/ReceiveStoreEventRequest"), exports);
26
27
  __exportStar(require("./dtos/ReceiveRetailUserEventRequest"), exports);
@@ -1,10 +1,11 @@
1
- import { RetailCurrencyEnum, RetailFinancierEnum, RetailMdmProviderEnum, RetailProductCategoryEnum, RetailProductStatusEnum, RetailProductTierEnum, RetailTrackingModeEnum } from '../enums';
1
+ import { RetailCurrencyEnum, RetailFinancierEnum, RetailMdmModeEnum, RetailMdmProviderEnum, RetailProductCategoryEnum, RetailProductStatusEnum, RetailProductTierEnum, RetailTrackingModeEnum } from '../enums';
2
2
  export type RetailProductCategory = `${RetailProductCategoryEnum}`;
3
3
  export type RetailTrackingMode = `${RetailTrackingModeEnum}`;
4
4
  export type RetailProductTier = `${RetailProductTierEnum}`;
5
5
  export type RetailCurrency = `${RetailCurrencyEnum}`;
6
6
  export type RetailFinancier = `${RetailFinancierEnum}`;
7
7
  export type RetailMdmProvider = `${RetailMdmProviderEnum}`;
8
+ export type RetailMdmMode = `${RetailMdmModeEnum}`;
8
9
  export type RetailProductStatus = `${RetailProductStatusEnum}`;
9
10
  /** Precios como decimal en string con dos decimales. */
10
11
  export interface RetailSystemProductPrice {
@@ -28,5 +29,7 @@ export interface RetailSystemProduct {
28
29
  price: RetailSystemProductPrice;
29
30
  eligibleFinanciers: RetailFinancier[];
30
31
  mdmProvider: RetailMdmProvider | null;
32
+ /** Modo del candado; obligatorio cuando hay `mdmProvider`. */
33
+ mdmMode?: RetailMdmMode | null;
31
34
  status: RetailProductStatus;
32
35
  }
@@ -0,0 +1,13 @@
1
+ import { ValidationArguments, ValidatorConstraintInterface } from 'class-validator';
2
+ /** Un producto rastreado por IMEI lleva MDM. */
3
+ export declare function requiresMdm(trackingMode: unknown): boolean;
4
+ /** `mdmProvider`: obligatorio con `trackingMode` IMEI y `null` en los demás productos. */
5
+ export declare class MdmProviderMatchesTracking implements ValidatorConstraintInterface {
6
+ validate(value: unknown, args: ValidationArguments): boolean;
7
+ defaultMessage(): string;
8
+ }
9
+ /** `mdmMode`: obligatorio con `mdmProvider` y `null` sin él; MOTOSAFE solo opera en ONDEMAND. */
10
+ export declare class MdmModeMatchesProvider implements ValidatorConstraintInterface {
11
+ validate(value: unknown, args: ValidationArguments): boolean;
12
+ defaultMessage(): string;
13
+ }
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.MdmModeMatchesProvider = exports.MdmProviderMatchesTracking = void 0;
10
+ exports.requiresMdm = requiresMdm;
11
+ const class_validator_1 = require("class-validator");
12
+ const enums_1 = require("../enums");
13
+ /** Un producto rastreado por IMEI lleva MDM. */
14
+ function requiresMdm(trackingMode) {
15
+ return trackingMode === enums_1.RetailTrackingModeEnum.IMEI;
16
+ }
17
+ /** `mdmProvider`: obligatorio con `trackingMode` IMEI y `null` en los demás productos. */
18
+ let MdmProviderMatchesTracking = class MdmProviderMatchesTracking {
19
+ validate(value, args) {
20
+ if (!requiresMdm(args.object.trackingMode))
21
+ return value === null;
22
+ return Object.values(enums_1.RetailMdmProviderEnum).includes(value);
23
+ }
24
+ defaultMessage() {
25
+ return 'mdmProvider es obligatorio con trackingMode IMEI y null en los demás productos';
26
+ }
27
+ };
28
+ exports.MdmProviderMatchesTracking = MdmProviderMatchesTracking;
29
+ exports.MdmProviderMatchesTracking = MdmProviderMatchesTracking = __decorate([
30
+ (0, class_validator_1.ValidatorConstraint)({ name: 'mdmProviderMatchesTracking', async: false })
31
+ ], MdmProviderMatchesTracking);
32
+ /** `mdmMode`: obligatorio con `mdmProvider` y `null` sin él; MOTOSAFE solo opera en ONDEMAND. */
33
+ let MdmModeMatchesProvider = class MdmModeMatchesProvider {
34
+ validate(value, args) {
35
+ const provider = args.object.mdmProvider;
36
+ if (provider === null || provider === undefined)
37
+ return value === null;
38
+ if (provider === enums_1.RetailMdmProviderEnum.MOTOSAFE)
39
+ return value === enums_1.RetailMdmModeEnum.ONDEMAND;
40
+ return Object.values(enums_1.RetailMdmModeEnum).includes(value);
41
+ }
42
+ defaultMessage() {
43
+ return 'mdmMode es obligatorio con mdmProvider (MOTOSAFE solo ONDEMAND) y null sin él';
44
+ }
45
+ };
46
+ exports.MdmModeMatchesProvider = MdmModeMatchesProvider;
47
+ exports.MdmModeMatchesProvider = MdmModeMatchesProvider = __decorate([
48
+ (0, class_validator_1.ValidatorConstraint)({ name: 'mdmModeMatchesProvider', async: false })
49
+ ], MdmModeMatchesProvider);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fiado/type-kit",
3
- "version": "3.607.0",
3
+ "version": "3.608.0",
4
4
  "description": "",
5
5
  "main": "bin/index.js",
6
6
  "types": "bin/index.d.ts",
@@ -11,7 +11,6 @@ import {
11
11
  import {
12
12
  RetailCurrencyEnum,
13
13
  RetailFinancierEnum,
14
- RetailMdmProviderEnum,
15
14
  RetailProductCategoryEnum,
16
15
  RetailProductStatusEnum,
17
16
  RetailProductTierEnum,
@@ -20,6 +19,7 @@ import {
20
19
  import type {
21
20
  RetailCurrency,
22
21
  RetailFinancier,
22
+ RetailMdmMode,
23
23
  RetailMdmProvider,
24
24
  RetailProductCategory,
25
25
  RetailProductStatus,
@@ -30,6 +30,7 @@ import type {
30
30
  } from '../models/RetailSystemProduct';
31
31
  import { IsIsoDateTimeWithZone } from '../validators/IsIsoDateTimeWithZone';
32
32
  import { isNullOrValidated } from '../validators/payloadValidators';
33
+ import { MdmModeMatchesProvider, MdmProviderMatchesTracking } from '../validators/productValidators';
33
34
 
34
35
  /** Precio del producto en decimales string. */
35
36
  export class ProductEventPricePayload implements RetailSystemProductPrice {
@@ -58,6 +59,9 @@ export class ProductEventPayload implements RetailSystemProduct {
58
59
  @Expose() @Transform(({ value }) => value ?? Object.values(RetailFinancierEnum))
59
60
  @IsOptional() @IsArray() @ArrayNotEmpty() @ArrayUnique() @ArrayMaxSize(RETAIL_ELIGIBLE_FINANCIERS_MAX) @IsIn(Object.values(RetailFinancierEnum), { each: true })
60
61
  eligibleFinanciers!: RetailFinancier[];
61
- @ValidateIf(isNullOrValidated) @IsIn(Object.values(RetailMdmProviderEnum)) mdmProvider!: RetailMdmProvider | null;
62
+ @Validate(MdmProviderMatchesTracking) mdmProvider!: RetailMdmProvider | null;
63
+ /** Opcional en el evento: ausente se vuelve `null`. */
64
+ @Expose() @Transform(({ value }) => value ?? null)
65
+ @Validate(MdmModeMatchesProvider) mdmMode!: RetailMdmMode | null;
62
66
  @IsIn(Object.values(RetailProductStatusEnum)) status!: RetailProductStatus;
63
67
  }
@@ -0,0 +1,5 @@
1
+ /** Modo del candado MDM del equipo; mismos valores que `MdmLockModeEnum` de retailCatalog. */
2
+ export enum RetailMdmModeEnum {
3
+ SCHEDULED = 'SCHEDULED',
4
+ ONDEMAND = 'ONDEMAND',
5
+ }
@@ -10,6 +10,7 @@ export * from './RetailProductTierEnum';
10
10
  export * from './RetailCurrencyEnum';
11
11
  export * from './RetailFinancierEnum';
12
12
  export * from './RetailMdmProviderEnum';
13
+ export * from './RetailMdmModeEnum';
13
14
  export * from './RetailProductStatusEnum';
14
15
  export * from './RetailPieceStatusEnum';
15
16
  export * from './RetailPieceReasonEnum';
@@ -5,6 +5,7 @@ export * from './validators/IsIsoDateTimeWithZone';
5
5
  export * from './validators/IsMovementsRangeValid';
6
6
  export * from './validators/IsCalendarDate';
7
7
  export * from './validators/saleValidators';
8
+ export * from './validators/productValidators';
8
9
  export * from './dtos/RetailEventEnvelope';
9
10
  export * from './dtos/ReceiveStoreEventRequest';
10
11
  export * from './dtos/ReceiveRetailUserEventRequest';
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  RetailCurrencyEnum,
3
3
  RetailFinancierEnum,
4
+ RetailMdmModeEnum,
4
5
  RetailMdmProviderEnum,
5
6
  RetailProductCategoryEnum,
6
7
  RetailProductStatusEnum,
@@ -14,6 +15,7 @@ export type RetailProductTier = `${RetailProductTierEnum}`;
14
15
  export type RetailCurrency = `${RetailCurrencyEnum}`;
15
16
  export type RetailFinancier = `${RetailFinancierEnum}`;
16
17
  export type RetailMdmProvider = `${RetailMdmProviderEnum}`;
18
+ export type RetailMdmMode = `${RetailMdmModeEnum}`;
17
19
  export type RetailProductStatus = `${RetailProductStatusEnum}`;
18
20
 
19
21
  /** Precios como decimal en string con dos decimales. */
@@ -39,5 +41,7 @@ export interface RetailSystemProduct {
39
41
  price: RetailSystemProductPrice;
40
42
  eligibleFinanciers: RetailFinancier[];
41
43
  mdmProvider: RetailMdmProvider | null;
44
+ /** Modo del candado; obligatorio cuando hay `mdmProvider`. */
45
+ mdmMode?: RetailMdmMode | null;
42
46
  status: RetailProductStatus;
43
47
  }
@@ -0,0 +1,35 @@
1
+ import { ValidationArguments, ValidatorConstraint, ValidatorConstraintInterface } from 'class-validator';
2
+ import { RetailMdmModeEnum, RetailMdmProviderEnum, RetailTrackingModeEnum } from '../enums';
3
+
4
+ /** Un producto rastreado por IMEI lleva MDM. */
5
+ export function requiresMdm(trackingMode: unknown): boolean {
6
+ return trackingMode === RetailTrackingModeEnum.IMEI;
7
+ }
8
+
9
+ /** `mdmProvider`: obligatorio con `trackingMode` IMEI y `null` en los demás productos. */
10
+ @ValidatorConstraint({ name: 'mdmProviderMatchesTracking', async: false })
11
+ export class MdmProviderMatchesTracking implements ValidatorConstraintInterface {
12
+ validate(value: unknown, args: ValidationArguments): boolean {
13
+ if (!requiresMdm((args.object as { trackingMode?: unknown }).trackingMode)) return value === null;
14
+ return (Object.values(RetailMdmProviderEnum) as unknown[]).includes(value);
15
+ }
16
+
17
+ defaultMessage(): string {
18
+ return 'mdmProvider es obligatorio con trackingMode IMEI y null en los demás productos';
19
+ }
20
+ }
21
+
22
+ /** `mdmMode`: obligatorio con `mdmProvider` y `null` sin él; MOTOSAFE solo opera en ONDEMAND. */
23
+ @ValidatorConstraint({ name: 'mdmModeMatchesProvider', async: false })
24
+ export class MdmModeMatchesProvider implements ValidatorConstraintInterface {
25
+ validate(value: unknown, args: ValidationArguments): boolean {
26
+ const provider = (args.object as { mdmProvider?: unknown }).mdmProvider;
27
+ if (provider === null || provider === undefined) return value === null;
28
+ if (provider === RetailMdmProviderEnum.MOTOSAFE) return value === RetailMdmModeEnum.ONDEMAND;
29
+ return (Object.values(RetailMdmModeEnum) as unknown[]).includes(value);
30
+ }
31
+
32
+ defaultMessage(): string {
33
+ return 'mdmMode es obligatorio con mdmProvider (MOTOSAFE solo ONDEMAND) y null sin él';
34
+ }
35
+ }