@fiado/type-kit 3.518.0 → 3.519.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,57 @@
1
+ import { SaleTypeEnum } from '../../../../src/retailWizard/enums/SaleTypeEnum';
2
+ import { SaleFinancierEnum } from '../../../../src/retailWizard/enums/SaleFinancierEnum';
3
+ import type { Sale } from '../../../../src/retailWizard/dtos/Sale';
4
+ import type { SaleCompletedV1 } from '../../../../src/retailWizard/events/SaleCompletedV1';
5
+ import type { SaleCompletedV2 } from '../../../../src/retailWizard/events/SaleCompletedV2';
6
+ import type { CommissionSaleInput } from '../../../../src/retailCommission/dtos/CommissionAccrual';
7
+
8
+ // Venta de contado sin teléfono; el test lo compila con strict, como los consumidores.
9
+ export const saleSku: Sale['sku'] = null;
10
+
11
+ export const completedV1: SaleCompletedV1 = {
12
+ saleId: 's-1',
13
+ folio: 'LG-T001-VTA-abcde-2026W40',
14
+ retailerId: 'r-1',
15
+ storeId: 'st-1',
16
+ retailCustomerId: 'c-1',
17
+ sku: null,
18
+ imei: null,
19
+ amountCents: 35000,
20
+ type: SaleTypeEnum.CASH,
21
+ soldAt: '2026-10-08T12:00:00.000Z',
22
+ occurredAt: '2026-10-08T12:00:00.000Z',
23
+ };
24
+
25
+ export const completedV2: SaleCompletedV2 = {
26
+ saleId: 's-1',
27
+ folio: 'LG-T001-VTA-abcde-2026W40',
28
+ retailerId: 'r-1',
29
+ storeId: 'st-1',
30
+ sellerRetailUserId: 'u-1',
31
+ retailCustomerId: 'c-1',
32
+ sku: null,
33
+ brand: null,
34
+ imei: null,
35
+ amountCents: 35000,
36
+ equipmentPriceCents: null,
37
+ downPaymentCents: null,
38
+ creditId: null,
39
+ type: SaleTypeEnum.CASH,
40
+ externalProvider: null,
41
+ soldAt: '2026-10-08T12:00:00.000Z',
42
+ occurredAt: '2026-10-08T12:00:00.000Z',
43
+ };
44
+
45
+ export const commissionInput: CommissionSaleInput = {
46
+ saleId: 's-1',
47
+ sellerId: 'u-1',
48
+ retailerId: 'r-1',
49
+ storeId: 'st-1',
50
+ sku: null,
51
+ brand: null,
52
+ equipmentPriceCents: null,
53
+ amountCents: 35000,
54
+ financier: Object.values(SaleFinancierEnum)[0],
55
+ paymentType: 'CASH',
56
+ soldAt: '2026-10-08T12:00:00.000Z',
57
+ };
@@ -0,0 +1,46 @@
1
+ import 'reflect-metadata';
2
+ import * as path from 'path';
3
+ import * as ts from 'typescript';
4
+ import {
5
+ commissionInput,
6
+ completedV1,
7
+ completedV2,
8
+ saleSku,
9
+ } from './fixtures/saleWithoutPhone.strict';
10
+
11
+ const FIXTURE = path.join(__dirname, 'fixtures', 'saleWithoutPhone.strict.ts');
12
+
13
+ // Los consumidores compilan con strict; sin strictNullChecks null cabe en string y no prueba nada.
14
+ function strictDiagnostics(): string[] {
15
+ const program = ts.createProgram([FIXTURE], {
16
+ strict: true,
17
+ noEmit: true,
18
+ skipLibCheck: true,
19
+ experimentalDecorators: true,
20
+ emitDecoratorMetadata: true,
21
+ target: ts.ScriptTarget.ES2020,
22
+ module: ts.ModuleKind.CommonJS,
23
+ moduleResolution: ts.ModuleResolutionKind.Node10,
24
+ });
25
+ return ts
26
+ .getPreEmitDiagnostics(program)
27
+ .filter((d) => d.file?.fileName === FIXTURE)
28
+ .map((d) => ts.flattenDiagnosticMessageText(d.messageText, '\n'));
29
+ }
30
+
31
+ describe('venta sin teléfono — sku nullable', () => {
32
+ it('Sale, SaleCompletedV1/V2 y CommissionSaleInput aceptan sku null en strict', () => {
33
+ expect(strictDiagnostics()).toEqual([]);
34
+ });
35
+
36
+ it('el payload viaja con sku null', () => {
37
+ expect(saleSku).toBeNull();
38
+ expect(completedV2.sku).toBeNull();
39
+ expect(commissionInput.sku).toBeNull();
40
+ });
41
+
42
+ it('SaleCompletedV1 omite equipmentPriceCents sin equipo', () => {
43
+ expect(completedV1.sku).toBeNull();
44
+ expect(completedV1).not.toHaveProperty('equipmentPriceCents');
45
+ });
46
+ });
@@ -0,0 +1,82 @@
1
+ import 'reflect-metadata';
2
+ import { plainToInstance } from 'class-transformer';
3
+ import { validateSync, type ValidationError } from 'class-validator';
4
+ import {
5
+ LinePaymentTypeEnum,
6
+ RetailCommissionInboundMessage,
7
+ RetailCommissionInboundMessageTypeEnum,
8
+ SaleTypeEnum,
9
+ type WizardLineProduct,
10
+ } from '../../../src/retailWizard';
11
+ import { ItemTypeEnum, ProductCategoryEnum, TrackingModeEnum } from '../../../src/retailCatalog';
12
+
13
+ function paths(errors: ValidationError[], prefix = ''): string[] {
14
+ return errors.flatMap((e) => {
15
+ const p = prefix ? `${prefix}.${e.property}` : e.property;
16
+ return e.children && e.children.length > 0 ? paths(e.children, p) : [p];
17
+ });
18
+ }
19
+
20
+ function errorsOf(payload: object): string[] {
21
+ return paths(validateSync(plainToInstance(RetailCommissionInboundMessage, payload) as object));
22
+ }
23
+
24
+ describe('venta sin teléfono — phoneCount en el mensaje a comisiones', () => {
25
+ const kit = {
26
+ lineId: 'L1',
27
+ sku: 'KIT-1',
28
+ qty: 1,
29
+ netCents: 349900,
30
+ itemType: ItemTypeEnum.PRODUCT,
31
+ category: ProductCategoryEnum.ACCESSORY,
32
+ tier: null,
33
+ brand: null,
34
+ paymentType: LinePaymentTypeEnum.CASH,
35
+ };
36
+ const message = (line: object) => ({
37
+ type: RetailCommissionInboundMessageTypeEnum.SALE_COMPLETED,
38
+ tenantId: 't',
39
+ retailerId: 'ret',
40
+ storeId: 'st',
41
+ saleId: 'sale-1',
42
+ sessionId: 'ses-1',
43
+ sellerRetailUserId: 'seller-1',
44
+ saleType: SaleTypeEnum.CASH,
45
+ externalProvider: null,
46
+ creditId: null,
47
+ soldAt: '2026-10-08T20:00:00.000Z',
48
+ occurredAt: '2026-10-08T20:00:00.000Z',
49
+ lines: [line],
50
+ });
51
+
52
+ it('un mensaje viejo sin phoneCount sigue validando', () => expect(errorsOf(message(kit))).toEqual([]));
53
+
54
+ it('acepta phoneCount en 0 y positivo', () => {
55
+ expect(errorsOf(message({ ...kit, phoneCount: 0 }))).toEqual([]);
56
+ expect(errorsOf(message({ ...kit, phoneCount: 2 }))).toEqual([]);
57
+ });
58
+
59
+ it('rechaza phoneCount negativo o no entero', () => {
60
+ expect(errorsOf(message({ ...kit, phoneCount: -1 }))).toEqual(['lines.0.phoneCount']);
61
+ expect(errorsOf(message({ ...kit, phoneCount: 1.5 }))).toEqual(['lines.0.phoneCount']);
62
+ });
63
+ });
64
+
65
+ describe('venta sin teléfono — itemType en la ficha de la línea de sesión', () => {
66
+ const base: WizardLineProduct = {
67
+ brand: 'Genérico',
68
+ model: 'Funda',
69
+ capacity: null,
70
+ color: null,
71
+ imageUrl: null,
72
+ tier: null,
73
+ category: ProductCategoryEnum.ACCESSORY,
74
+ trackingMode: TrackingModeEnum.QUANTITY,
75
+ };
76
+
77
+ it('acepta itemType y también su ausencia', () => {
78
+ const service: WizardLineProduct = { ...base, itemType: ItemTypeEnum.SERVICE };
79
+ expect(service.itemType).toBe(ItemTypeEnum.SERVICE);
80
+ expect(base.itemType).toBeUndefined();
81
+ });
82
+ });
@@ -69,7 +69,8 @@ export interface CommissionSaleInput {
69
69
  sellerId: string;
70
70
  retailerId: string;
71
71
  storeId: string;
72
- sku: string;
72
+ /** `null` en la venta sin teléfono: se comisiona solo por accesorios. */
73
+ sku: string | null;
73
74
  brand: string | null;
74
75
  /**
75
76
  * 🔴 **La base de comisión es el PRECIO DEL EQUIPO, no lo cobrado en caja.**
@@ -20,8 +20,9 @@ export interface Sale {
20
20
  wizardSessionId: string;
21
21
  type: SaleTypeEnum;
22
22
  externalProvider: ExternalProviderEnum | null;
23
- sku: string;
24
- /** Líneas vendidas; `sku` e `imei` siguen siendo los del teléfono financiado o de la línea principal. */
23
+ /** SKU del teléfono de la venta; `null` en la venta sin teléfono (en DDB se omite). */
24
+ sku: string | null;
25
+ /** Líneas vendidas; `sku` e `imei` son los del teléfono, o `null` si la venta no lleva teléfono. */
25
26
  lines?: SaleLine[];
26
27
  /** SKUs de líneas y componentes, para filtrar ventas por SKU. */
27
28
  skus?: string[];
@@ -1,3 +1,4 @@
1
+ import type { ItemTypeEnum } from '../../retailCatalog/enums/ItemTypeEnum';
1
2
  import type { ProductCategoryEnum } from '../../retailCatalog/enums/ProductCategoryEnum';
2
3
  import type { ProductTierEnum } from '../../retailCatalog/enums/ProductTierEnum';
3
4
  import type { TrackingModeEnum } from '../../retailCatalog/enums/TrackingModeEnum';
@@ -12,4 +13,6 @@ export interface WizardLineProduct {
12
13
  tier: ProductTierEnum | null;
13
14
  category: ProductCategoryEnum;
14
15
  trackingMode: TrackingModeEnum;
16
+ /** Producto físico o servicio; ausente en líneas viejas (el consumidor lo toma como PRODUCT). */
17
+ itemType?: ItemTypeEnum;
15
18
  }
@@ -21,6 +21,8 @@ export declare class RetailCommissionInboundLine {
21
21
  brand: string | null;
22
22
  paymentType: LinePaymentTypeEnum;
23
23
  role?: WizardLineRoleEnum;
24
+ /** Teléfonos que trae la línea (kit), congelados al cerrar; ausente en mensajes viejos. */
25
+ phoneCount?: number;
24
26
  }
25
27
  /** Venta cerrada o cancelada que el wizard publica en RetailCommissionInboundQueue. */
26
28
  export declare class RetailCommissionInboundMessage {
@@ -72,6 +72,12 @@ __decorate([
72
72
  (0, class_validator_1.IsEnum)(WizardLineRoleEnum_1.WizardLineRoleEnum),
73
73
  __metadata("design:type", String)
74
74
  ], RetailCommissionInboundLine.prototype, "role", void 0);
75
+ __decorate([
76
+ (0, class_validator_1.IsOptional)(),
77
+ (0, class_validator_1.IsInt)(),
78
+ (0, class_validator_1.Min)(0),
79
+ __metadata("design:type", Number)
80
+ ], RetailCommissionInboundLine.prototype, "phoneCount", void 0);
75
81
  /** Venta cerrada o cancelada que el wizard publica en RetailCommissionInboundQueue. */
76
82
  class RetailCommissionInboundMessage {
77
83
  }
@@ -10,7 +10,8 @@ export interface SaleCompletedV1 {
10
10
  retailerId: string;
11
11
  storeId: string;
12
12
  retailCustomerId: string;
13
- sku: string;
13
+ /** `null` en la venta sin teléfono. */
14
+ sku: string | null;
14
15
  imei: string | null;
15
16
  /**
16
17
  * Lo cobrado en caja al cerrar: enganche + lo no financiado (en contado, el total).
@@ -22,7 +23,7 @@ export interface SaleCompletedV1 {
22
23
  * sin `type` un crédito con $1,300 de enganche era indistinguible de un contado de $1,300.
23
24
  */
24
25
  type?: SaleTypeEnum;
25
- /** Precio de lista del equipo; en contado coincide con `amountCents`. */
26
+ /** Precio de lista del equipo; en contado coincide con `amountCents`. Sin equipo se omite. */
26
27
  equipmentPriceCents?: number;
27
28
  /** Enganche cobrado (solo `type=CREDIT`). */
28
29
  downPaymentCents?: number;
@@ -23,7 +23,8 @@ export interface SaleCompletedV2 {
23
23
  /** Quién vendió — el eje del cálculo de comisión. Es lo que más falta en la V1. */
24
24
  sellerRetailUserId: string;
25
25
  retailCustomerId: string;
26
- sku: string;
26
+ /** `null` en la venta sin teléfono. */
27
+ sku: string | null;
27
28
  /** Marca del equipo, del snapshot congelado al cerrar. `null` si no se pudo resolver. */
28
29
  brand: string | null;
29
30
  imei: string | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fiado/type-kit",
3
- "version": "3.518.0",
3
+ "version": "3.519.0",
4
4
  "description": "",
5
5
  "main": "bin/index.js",
6
6
  "types": "bin/index.d.ts",
@@ -72,7 +72,8 @@ export interface CommissionSaleInput {
72
72
  sellerId: string;
73
73
  retailerId: string;
74
74
  storeId: string;
75
- sku: string;
75
+ /** `null` en la venta sin teléfono: se comisiona solo por accesorios. */
76
+ sku: string | null;
76
77
  brand: string | null;
77
78
  /**
78
79
  * 🔴 **La base de comisión es el PRECIO DEL EQUIPO, no lo cobrado en caja.**
@@ -21,8 +21,9 @@ export interface Sale {
21
21
  wizardSessionId: string;
22
22
  type: SaleTypeEnum;
23
23
  externalProvider: ExternalProviderEnum | null;
24
- sku: string;
25
- /** Líneas vendidas; `sku` e `imei` siguen siendo los del teléfono financiado o de la línea principal. */
24
+ /** SKU del teléfono de la venta; `null` en la venta sin teléfono (en DDB se omite). */
25
+ sku: string | null;
26
+ /** Líneas vendidas; `sku` e `imei` son los del teléfono, o `null` si la venta no lleva teléfono. */
26
27
  lines?: SaleLine[];
27
28
  /** SKUs de líneas y componentes, para filtrar ventas por SKU. */
28
29
  skus?: string[];
@@ -1,3 +1,4 @@
1
+ import type { ItemTypeEnum } from '../../retailCatalog/enums/ItemTypeEnum';
1
2
  import type { ProductCategoryEnum } from '../../retailCatalog/enums/ProductCategoryEnum';
2
3
  import type { ProductTierEnum } from '../../retailCatalog/enums/ProductTierEnum';
3
4
  import type { TrackingModeEnum } from '../../retailCatalog/enums/TrackingModeEnum';
@@ -13,4 +14,6 @@ export interface WizardLineProduct {
13
14
  tier: ProductTierEnum | null;
14
15
  category: ProductCategoryEnum;
15
16
  trackingMode: TrackingModeEnum;
17
+ /** Producto físico o servicio; ausente en líneas viejas (el consumidor lo toma como PRODUCT). */
18
+ itemType?: ItemTypeEnum;
16
19
  }
@@ -62,6 +62,12 @@ export class RetailCommissionInboundLine {
62
62
  @IsOptional()
63
63
  @IsEnum(WizardLineRoleEnum)
64
64
  role?: WizardLineRoleEnum;
65
+
66
+ /** Teléfonos que trae la línea (kit), congelados al cerrar; ausente en mensajes viejos. */
67
+ @IsOptional()
68
+ @IsInt()
69
+ @Min(0)
70
+ phoneCount?: number;
65
71
  }
66
72
 
67
73
  /** Venta cerrada o cancelada que el wizard publica en RetailCommissionInboundQueue. */
@@ -11,7 +11,8 @@ export interface SaleCompletedV1 {
11
11
  retailerId: string;
12
12
  storeId: string;
13
13
  retailCustomerId: string;
14
- sku: string;
14
+ /** `null` en la venta sin teléfono. */
15
+ sku: string | null;
15
16
  imei: string | null;
16
17
  /**
17
18
  * Lo cobrado en caja al cerrar: enganche + lo no financiado (en contado, el total).
@@ -23,7 +24,7 @@ export interface SaleCompletedV1 {
23
24
  * sin `type` un crédito con $1,300 de enganche era indistinguible de un contado de $1,300.
24
25
  */
25
26
  type?: SaleTypeEnum;
26
- /** Precio de lista del equipo; en contado coincide con `amountCents`. */
27
+ /** Precio de lista del equipo; en contado coincide con `amountCents`. Sin equipo se omite. */
27
28
  equipmentPriceCents?: number;
28
29
  /** Enganche cobrado (solo `type=CREDIT`). */
29
30
  downPaymentCents?: number;
@@ -24,7 +24,8 @@ export interface SaleCompletedV2 {
24
24
  /** Quién vendió — el eje del cálculo de comisión. Es lo que más falta en la V1. */
25
25
  sellerRetailUserId: string;
26
26
  retailCustomerId: string;
27
- sku: string;
27
+ /** `null` en la venta sin teléfono. */
28
+ sku: string | null;
28
29
  /** Marca del equipo, del snapshot congelado al cerrar. `null` si no se pudo resolver. */
29
30
  brand: string | null;
30
31
  imei: string | null;