@fiado/type-kit 3.653.0 → 3.655.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,33 @@
1
+ import 'reflect-metadata';
2
+ import { PHONE_SALES_EVENTS_SOURCE, PhoneSalesEventDetailTypeEnum, PhoneSalesCodeRedeemedEventDetail } from '../../../src/phoneSales';
3
+ import { PhoneSales } from '../../../src';
4
+
5
+ it('constantes del evento', () => {
6
+ expect(PHONE_SALES_EVENTS_SOURCE).toBe('fiado.phone-sales');
7
+ expect(PhoneSalesEventDetailTypeEnum.CODE_REDEEMED).toBe('PhoneSalesCodeRedeemed');
8
+ expect(PhoneSales.PHONE_SALES_EVENTS_SOURCE).toBe('fiado.phone-sales');
9
+ });
10
+
11
+ it('detail tiene la forma exacta del contrato', () => {
12
+ const detail = {
13
+ eventId: 'e1', occurredAt: '2026-11-05T12:00:00.000Z', saleId: 's1',
14
+ redeemer: { directoryId: 'd-redeemer' }, buyer: { directoryId: 'd-buyer' },
15
+ line: { phone: '5512345678', operatorId: 'at-t' },
16
+ sale: { variantId: 'v1', model: 'A15', storage: '128GB', color: 'Negro', currency: 'MXN', deliveryType: 'HOME', purchasedAt: '2026-11-05T11:00:00.000Z', amount: 4999 },
17
+ } satisfies PhoneSalesCodeRedeemedEventDetail;
18
+ expect(Object.keys(detail).sort()).toEqual(['buyer', 'eventId', 'line', 'occurredAt', 'redeemer', 'sale', 'saleId']);
19
+ expect(Object.keys(detail.sale).sort()).toEqual(['amount', 'color', 'currency', 'deliveryType', 'model', 'purchasedAt', 'storage', 'variantId']);
20
+ });
21
+
22
+ it('amount y currency son opcionales (ventas previas a DEC-011)', () => {
23
+ const sale = {
24
+ variantId: 'v1', model: 'A15', storage: '128GB', color: 'Negro', deliveryType: 'HOME', purchasedAt: '2026-11-05T11:00:00.000Z',
25
+ } satisfies PhoneSalesCodeRedeemedEventDetail['sale'];
26
+ expect(Object.keys(sale)).not.toContain('amount');
27
+ });
28
+
29
+ it('rechaza campos faltantes', () => {
30
+ // @ts-expect-error falta redeemer
31
+ const missing: PhoneSalesCodeRedeemedEventDetail = { eventId: 'e', occurredAt: 'x', saleId: 's', buyer: { directoryId: 'b' }, line: { phone: '1', operatorId: 'bait' }, sale: {} };
32
+ void missing;
33
+ });
@@ -0,0 +1,34 @@
1
+ import 'reflect-metadata';
2
+ import { plainToInstance } from 'class-transformer';
3
+ import { validate } from 'class-validator';
4
+ import { RedeemSaleCodeRequestDto, SaleResponseDto } from '../../../src/phoneSales';
5
+
6
+ const ok = { referenceCode: 'ABC123', linePhone: '55 1234 5678', lineOperatorId: 'at-t' };
7
+ const props = async (plain: object) =>
8
+ (await validate(plainToInstance(RedeemSaleCodeRequestDto, plain))).map((e) => e.property);
9
+
10
+ describe('RedeemSaleCodeRequestDto', () => {
11
+ it('válido: operador slug y teléfono string sin normalizar', async () => {
12
+ expect(await props(ok)).toEqual([]);
13
+ });
14
+ it.each(['Bait', 'a', 'x'.repeat(41), 'at_t', ''])('lineOperatorId %p inválido', async (lineOperatorId) => {
15
+ expect(await props({ ...ok, lineOperatorId })).toEqual(['lineOperatorId']);
16
+ });
17
+ it('linePhone no string inválido; ausente inválido', async () => {
18
+ expect(await props({ ...ok, linePhone: 5512345678 })).toEqual(['linePhone']);
19
+ expect(await props({ referenceCode: 'A', lineOperatorId: 'bait' })).toEqual(['linePhone']);
20
+ });
21
+ it('ya no exige newAccountDirectorId y la clase no lo declara', async () => {
22
+ expect(await props(ok)).toEqual([]);
23
+ expect('newAccountDirectorId' in new RedeemSaleCodeRequestDto()).toBe(false);
24
+ });
25
+ });
26
+
27
+ describe('SaleResponseDto.line', () => {
28
+ it('es opcional y acepta PhoneSalesLineDetail', () => {
29
+ const withLine: SaleResponseDto['line'] = { phone: '5512345678', operatorId: 'bait' };
30
+ const without: SaleResponseDto['line'] = undefined;
31
+ expect(withLine?.operatorId).toBe('bait');
32
+ expect(without).toBeUndefined();
33
+ });
34
+ });
@@ -45,6 +45,21 @@ describe('CatalogItemRequest', () => {
45
45
  expect(await props(asCatalog(override))).toContain(property);
46
46
  });
47
47
 
48
+ it('costCenterId es opcional: ausente, null o un texto de hasta 64', async () => {
49
+ expect(await validate(asCatalog())).toHaveLength(0);
50
+ expect(await validate(asCatalog({ costCenterId: null }))).toHaveLength(0);
51
+ expect(await validate(asCatalog({ costCenterId: 'CC-VENTA-CELULARES' }))).toHaveLength(0);
52
+ expect(asCatalog({ costCenterId: 'CC-1' }).costCenterId).toBe('CC-1');
53
+ });
54
+
55
+ it.each<[string, JsonValue]>([
56
+ ['vacío', ''],
57
+ ['> 64', 'c'.repeat(65)],
58
+ ['no texto', 5],
59
+ ])('rechaza costCenterId %s', async (_nombre, costCenterId) => {
60
+ expect(await props(asCatalog({ costCenterId }))).toContain('costCenterId');
61
+ });
62
+
48
63
  it('acepta costo 0 (recompensa gratuita para el aliado)', async () => {
49
64
  expect(await validate(asCatalog({ cost: { amount: 0, currency: 'MXN' } }))).toHaveLength(0);
50
65
  });
@@ -1,4 +1,7 @@
1
1
  export declare class RedeemSaleCodeRequestDto {
2
2
  referenceCode: string;
3
- newAccountDirectorId: string;
3
+ /** Teléfono de la línea tal como lo escribe el usuario; la normalización a 10 dígitos la hace el backend. */
4
+ linePhone: string;
5
+ /** Slug del operador tal como lo da benefits (`bait`, `telcel`, `at-t`). */
6
+ lineOperatorId: string;
4
7
  }
@@ -21,4 +21,8 @@ __decorate([
21
21
  __decorate([
22
22
  (0, class_validator_1.IsString)(),
23
23
  __metadata("design:type", String)
24
- ], RedeemSaleCodeRequestDto.prototype, "newAccountDirectorId", void 0);
24
+ ], RedeemSaleCodeRequestDto.prototype, "linePhone", void 0);
25
+ __decorate([
26
+ (0, class_validator_1.Matches)(/^[a-z0-9-]{2,40}$/),
27
+ __metadata("design:type", String)
28
+ ], RedeemSaleCodeRequestDto.prototype, "lineOperatorId", void 0);
@@ -1,3 +1,4 @@
1
+ import { PhoneSalesLineDetail } from './events/PhoneSalesCodeRedeemedEventDetail';
1
2
  import { CurrencyId } from '../currency';
2
3
  import { PickupDeliveryData, HomeDeliveryData } from './SaleDeliveryData';
3
4
  export declare class SaleResponseDto {
@@ -57,4 +58,6 @@ export declare class SaleResponseDto {
57
58
  chargedAmount: number | null;
58
59
  /** Moneda en la que se cobró `chargedAmount` — mismo motivo de nullability. */
59
60
  chargedCurrencyId: CurrencyId | null;
61
+ /** Línea declarada al canjear; ausente en ventas anteriores o sin canjear. */
62
+ line?: PhoneSalesLineDetail;
60
63
  }
@@ -0,0 +1,40 @@
1
+ export declare const PHONE_SALES_EVENTS_SOURCE = "fiado.phone-sales";
2
+ export declare enum PhoneSalesEventDetailTypeEnum {
3
+ /** Un código de venta se canjeó con éxito. Solo lo publica quien ganó la escritura condicional del canje. */
4
+ CODE_REDEEMED = "PhoneSalesCodeRedeemed"
5
+ }
6
+ export declare class PhoneSalesEventParty {
7
+ directoryId: string;
8
+ }
9
+ export declare class PhoneSalesLineDetail {
10
+ /** 10 dígitos MX normalizados (sin +52, espacios ni guiones). */
11
+ phone: string;
12
+ /** Slug del operador tal como lo da benefits (`bait`, `telcel`, `at-t`). */
13
+ operatorId: string;
14
+ }
15
+ export declare class PhoneSalesSaleDetail {
16
+ variantId: string;
17
+ model: string;
18
+ storage: string;
19
+ color: string;
20
+ deliveryType: string;
21
+ /** ISO-8601 UTC de la compra. */
22
+ purchasedAt: string;
23
+ /** Se omite si la venta no lo tiene (ventas previas a DEC-011); nunca 0. */
24
+ amount?: number;
25
+ /** `USD` / `MXN`. Se omite si la venta no lo tiene; nunca vacío. */
26
+ currency?: string;
27
+ }
28
+ export declare class PhoneSalesCodeRedeemedEventDetail {
29
+ /** uuid generado al publicar. */
30
+ eventId: string;
31
+ /** ISO-8601 UTC del momento del canje. */
32
+ occurredAt: string;
33
+ saleId: string;
34
+ /** Quien canjea (sale del token). */
35
+ redeemer: PhoneSalesEventParty;
36
+ /** Quien compró (`buyerDirectorId`). */
37
+ buyer: PhoneSalesEventParty;
38
+ line: PhoneSalesLineDetail;
39
+ sale: PhoneSalesSaleDetail;
40
+ }
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PhoneSalesCodeRedeemedEventDetail = exports.PhoneSalesSaleDetail = exports.PhoneSalesLineDetail = exports.PhoneSalesEventParty = exports.PhoneSalesEventDetailTypeEnum = exports.PHONE_SALES_EVENTS_SOURCE = void 0;
4
+ // Origen de los eventos de EventBridge que publica phone-sales (bus fiado-directory-events).
5
+ exports.PHONE_SALES_EVENTS_SOURCE = 'fiado.phone-sales';
6
+ var PhoneSalesEventDetailTypeEnum;
7
+ (function (PhoneSalesEventDetailTypeEnum) {
8
+ /** Un código de venta se canjeó con éxito. Solo lo publica quien ganó la escritura condicional del canje. */
9
+ PhoneSalesEventDetailTypeEnum["CODE_REDEEMED"] = "PhoneSalesCodeRedeemed";
10
+ })(PhoneSalesEventDetailTypeEnum || (exports.PhoneSalesEventDetailTypeEnum = PhoneSalesEventDetailTypeEnum = {}));
11
+ class PhoneSalesEventParty {
12
+ }
13
+ exports.PhoneSalesEventParty = PhoneSalesEventParty;
14
+ class PhoneSalesLineDetail {
15
+ }
16
+ exports.PhoneSalesLineDetail = PhoneSalesLineDetail;
17
+ class PhoneSalesSaleDetail {
18
+ }
19
+ exports.PhoneSalesSaleDetail = PhoneSalesSaleDetail;
20
+ // `detail` del evento PhoneSalesCodeRedeemed. El código canjeado NO viaja (funciona como secreto).
21
+ class PhoneSalesCodeRedeemedEventDetail {
22
+ }
23
+ exports.PhoneSalesCodeRedeemedEventDetail = PhoneSalesCodeRedeemedEventDetail;
@@ -13,3 +13,4 @@ export * from './DeviceVariantPriceRequestDto';
13
13
  export * from './SetDeviceVariantPricesRequestDto';
14
14
  export * from './enums/DeviceImageContentTypeEnum';
15
15
  export * from './enums/DeviceVariantStatusEnum';
16
+ export * from './events/PhoneSalesCodeRedeemedEventDetail';
@@ -29,3 +29,4 @@ __exportStar(require("./DeviceVariantPriceRequestDto"), exports);
29
29
  __exportStar(require("./SetDeviceVariantPricesRequestDto"), exports);
30
30
  __exportStar(require("./enums/DeviceImageContentTypeEnum"), exports);
31
31
  __exportStar(require("./enums/DeviceVariantStatusEnum"), exports);
32
+ __exportStar(require("./events/PhoneSalesCodeRedeemedEventDetail"), exports);
@@ -15,5 +15,10 @@ export declare class CatalogItemRequest {
15
15
  };
16
16
  cost: RewardCostInput;
17
17
  chargeTo: RewardChargeToInput;
18
+ /**
19
+ * Centro de costo con que el ledger imputa lo que esta recompensa paga (saldo, recarga). Viaja en cada
20
+ * `disburse`. Ausente o `null` = sin centro: el pago se asienta como `UNASSIGNED`.
21
+ */
22
+ costCenterId?: string | null;
18
23
  status: RewardStatusEnum;
19
24
  }
@@ -12,6 +12,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.CatalogItemRequest = void 0;
13
13
  const class_transformer_1 = require("class-transformer");
14
14
  const class_validator_1 = require("class-validator");
15
+ const costCenter_1 = require("../../../transaction/constants/costCenter");
15
16
  const BackofficeLimits_1 = require("../../constants/BackofficeLimits");
16
17
  const RewardStatusEnum_1 = require("../../enums/RewardStatusEnum");
17
18
  const RewardChargeToInput_1 = require("./RewardChargeToInput");
@@ -63,6 +64,14 @@ __decorate([
63
64
  (0, class_transformer_1.Type)(() => RewardChargeToInput_1.RewardChargeToInput),
64
65
  __metadata("design:type", RewardChargeToInput_1.RewardChargeToInput)
65
66
  ], CatalogItemRequest.prototype, "chargeTo", void 0);
67
+ __decorate([
68
+ (0, class_transformer_1.Expose)(),
69
+ (0, class_validator_1.IsOptional)(),
70
+ (0, class_validator_1.IsString)(),
71
+ (0, class_validator_1.IsNotEmpty)(),
72
+ (0, class_validator_1.MaxLength)(costCenter_1.COST_CENTER_ID_MAX_LENGTH),
73
+ __metadata("design:type", String)
74
+ ], CatalogItemRequest.prototype, "costCenterId", void 0);
66
75
  __decorate([
67
76
  (0, class_transformer_1.Expose)(),
68
77
  (0, class_validator_1.IsEnum)(RewardStatusEnum_1.RewardStatusEnum),
@@ -13,6 +13,8 @@ export declare class CatalogItemResponse {
13
13
  };
14
14
  cost: RewardCost;
15
15
  chargeTo: RewardChargeTo;
16
+ /** Centro de costo de lo que paga esta recompensa; ausente = `UNASSIGNED` en el ledger. */
17
+ costCenterId?: string;
16
18
  /** Solo lectura: las calcula el tipo a partir de `config`. */
17
19
  dependencies: RewardDependency[];
18
20
  status: RewardStatusEnum;
@@ -11,4 +11,6 @@ export interface MobileTopupContributionResponse {
11
11
  carrier?: MobileTopupContributionCarrier;
12
12
  /** Precio unitario cobrado al aliado (MXN, 2 decimales); no cambia nunca. Solo falta en aportes legacy. */
13
13
  unitPrice?: number;
14
+ /** Centro de costo del ítem al otorgar; lo llevan todas las recargas del aporte. Ausente = `UNASSIGNED`. */
15
+ costCenterId?: string;
14
16
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fiado/type-kit",
3
- "version": "3.653.0",
3
+ "version": "3.655.0",
4
4
  "description": "",
5
5
  "main": "bin/index.js",
6
6
  "types": "bin/index.d.ts",
@@ -1,9 +1,14 @@
1
- import { IsString } from 'class-validator';
1
+ import { IsString, Matches } from 'class-validator';
2
2
 
3
3
  export class RedeemSaleCodeRequestDto {
4
4
  @IsString()
5
5
  referenceCode!: string;
6
6
 
7
+ /** Teléfono de la línea tal como lo escribe el usuario; la normalización a 10 dígitos la hace el backend. */
7
8
  @IsString()
8
- newAccountDirectorId!: string;
9
+ linePhone!: string;
10
+
11
+ /** Slug del operador tal como lo da benefits (`bait`, `telcel`, `at-t`). */
12
+ @Matches(/^[a-z0-9-]{2,40}$/)
13
+ lineOperatorId!: string;
9
14
  }
@@ -1,3 +1,4 @@
1
+ import { PhoneSalesLineDetail } from './events/PhoneSalesCodeRedeemedEventDetail';
1
2
  import { CurrencyId } from '../currency';
2
3
  import { PickupDeliveryData, HomeDeliveryData } from './SaleDeliveryData';
3
4
 
@@ -58,6 +59,8 @@ export class SaleResponseDto {
58
59
  chargedAmount!: number | null;
59
60
  /** Moneda en la que se cobró `chargedAmount` — mismo motivo de nullability. */
60
61
  chargedCurrencyId!: CurrencyId | null;
62
+ /** Línea declarada al canjear; ausente en ventas anteriores o sin canjear. */
63
+ line?: PhoneSalesLineDetail;
61
64
  // Nota (v1, 2026-08-03): `buyerDirectorId` (arriba) sigue siendo el ID crudo del comprador a
62
65
  // propósito. Se evaluó agregar `buyerName` resuelto server-side (vía servicio de directorios,
63
66
  // @fiado/api-invoker IDirectoriesApi) pero requería wiring de infra pendiente
@@ -0,0 +1,47 @@
1
+ // Origen de los eventos de EventBridge que publica phone-sales (bus fiado-directory-events).
2
+ export const PHONE_SALES_EVENTS_SOURCE = 'fiado.phone-sales';
3
+
4
+ export enum PhoneSalesEventDetailTypeEnum {
5
+ /** Un código de venta se canjeó con éxito. Solo lo publica quien ganó la escritura condicional del canje. */
6
+ CODE_REDEEMED = 'PhoneSalesCodeRedeemed',
7
+ }
8
+
9
+ export class PhoneSalesEventParty {
10
+ directoryId!: string;
11
+ }
12
+
13
+ export class PhoneSalesLineDetail {
14
+ /** 10 dígitos MX normalizados (sin +52, espacios ni guiones). */
15
+ phone!: string;
16
+ /** Slug del operador tal como lo da benefits (`bait`, `telcel`, `at-t`). */
17
+ operatorId!: string;
18
+ }
19
+
20
+ export class PhoneSalesSaleDetail {
21
+ variantId!: string;
22
+ model!: string;
23
+ storage!: string;
24
+ color!: string;
25
+ deliveryType!: string;
26
+ /** ISO-8601 UTC de la compra. */
27
+ purchasedAt!: string;
28
+ /** Se omite si la venta no lo tiene (ventas previas a DEC-011); nunca 0. */
29
+ amount?: number;
30
+ /** `USD` / `MXN`. Se omite si la venta no lo tiene; nunca vacío. */
31
+ currency?: string;
32
+ }
33
+
34
+ // `detail` del evento PhoneSalesCodeRedeemed. El código canjeado NO viaja (funciona como secreto).
35
+ export class PhoneSalesCodeRedeemedEventDetail {
36
+ /** uuid generado al publicar. */
37
+ eventId!: string;
38
+ /** ISO-8601 UTC del momento del canje. */
39
+ occurredAt!: string;
40
+ saleId!: string;
41
+ /** Quien canjea (sale del token). */
42
+ redeemer!: PhoneSalesEventParty;
43
+ /** Quien compró (`buyerDirectorId`). */
44
+ buyer!: PhoneSalesEventParty;
45
+ line!: PhoneSalesLineDetail;
46
+ sale!: PhoneSalesSaleDetail;
47
+ }
@@ -13,3 +13,4 @@ export * from './DeviceVariantPriceRequestDto';
13
13
  export * from './SetDeviceVariantPricesRequestDto';
14
14
  export * from './enums/DeviceImageContentTypeEnum';
15
15
  export * from './enums/DeviceVariantStatusEnum';
16
+ export * from './events/PhoneSalesCodeRedeemedEventDetail';
@@ -1,6 +1,7 @@
1
1
  import { Expose, Type } from 'class-transformer';
2
- import { IsEnum, IsNotEmpty, IsObject, IsString, Matches, MaxLength, ValidateNested } from 'class-validator';
2
+ import { IsEnum, IsNotEmpty, IsObject, IsOptional, IsString, Matches, MaxLength, ValidateNested } from 'class-validator';
3
3
  import { JsonValue } from '../../../common';
4
+ import { COST_CENTER_ID_MAX_LENGTH } from '../../../transaction/constants/costCenter';
4
5
  import { REWARD_ID_PATTERN } from '../../constants/BackofficeLimits';
5
6
  import { RewardStatusEnum } from '../../enums/RewardStatusEnum';
6
7
  import { RewardChargeToInput } from './RewardChargeToInput';
@@ -46,6 +47,17 @@ export class CatalogItemRequest {
46
47
  @Type(() => RewardChargeToInput)
47
48
  chargeTo: RewardChargeToInput;
48
49
 
50
+ /**
51
+ * Centro de costo con que el ledger imputa lo que esta recompensa paga (saldo, recarga). Viaja en cada
52
+ * `disburse`. Ausente o `null` = sin centro: el pago se asienta como `UNASSIGNED`.
53
+ */
54
+ @Expose()
55
+ @IsOptional()
56
+ @IsString()
57
+ @IsNotEmpty()
58
+ @MaxLength(COST_CENTER_ID_MAX_LENGTH)
59
+ costCenterId?: string | null;
60
+
49
61
  @Expose()
50
62
  @IsEnum(RewardStatusEnum)
51
63
  status: RewardStatusEnum;
@@ -12,6 +12,8 @@ export class CatalogItemResponse {
12
12
  config: { [key: string]: JsonValue };
13
13
  cost: RewardCost;
14
14
  chargeTo: RewardChargeTo;
15
+ /** Centro de costo de lo que paga esta recompensa; ausente = `UNASSIGNED` en el ledger. */
16
+ costCenterId?: string;
15
17
  /** Solo lectura: las calcula el tipo a partir de `config`. */
16
18
  dependencies: RewardDependency[];
17
19
  status: RewardStatusEnum;
@@ -12,4 +12,6 @@ export interface MobileTopupContributionResponse {
12
12
  carrier?: MobileTopupContributionCarrier;
13
13
  /** Precio unitario cobrado al aliado (MXN, 2 decimales); no cambia nunca. Solo falta en aportes legacy. */
14
14
  unitPrice?: number;
15
+ /** Centro de costo del ítem al otorgar; lo llevan todas las recargas del aporte. Ausente = `UNASSIGNED`. */
16
+ costCenterId?: string;
15
17
  }