@fiado/type-kit 3.494.0 → 3.495.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,56 @@
1
+ import 'reflect-metadata';
2
+ import { plainToInstance } from 'class-transformer';
3
+ import { validate } from 'class-validator';
4
+ import { OriginateLoanCreditRequest } from '../../../../src/loanCredit/dtos/requests/OriginateLoanCreditRequest';
5
+
6
+ const BASE = {
7
+ wizardSessionId: 'WS-1',
8
+ borrowerId: 'B-1',
9
+ planId: 'PLAN-1',
10
+ termWeeks: 12,
11
+ equipmentPriceCents: 500000,
12
+ downPaymentCents: 100000,
13
+ storeId: 'ST-1',
14
+ };
15
+
16
+ /** Igual que `validateRequest` del motor: whitelist sin rechazar extras. */
17
+ const validateWhitelisted = (body: object) => {
18
+ const dto = plainToInstance(OriginateLoanCreditRequest, body);
19
+ return validate(dto, { whitelist: true, forbidNonWhitelisted: false }).then((errors) => ({ dto, errors }));
20
+ };
21
+
22
+ describe('OriginateLoanCreditRequest', () => {
23
+ it('acepta el request con tienda y vendedor y conserva los dos', async () => {
24
+ const { dto, errors } = await validateWhitelisted({ ...BASE, sellerId: 'U-1' });
25
+
26
+ expect(errors).toEqual([]);
27
+ expect(dto.storeId).toBe('ST-1');
28
+ expect(dto.sellerId).toBe('U-1');
29
+ });
30
+
31
+ it('rechaza el request sin tienda: sin ella el motor no resuelve la promoción ni la cartera', async () => {
32
+ const { storeId: _omitted, ...withoutStore } = BASE;
33
+ const { errors } = await validateWhitelisted(withoutStore);
34
+
35
+ expect(errors.some((error) => error.property === 'storeId')).toBe(true);
36
+ });
37
+
38
+ it('rechaza la tienda vacía', async () => {
39
+ const { errors } = await validateWhitelisted({ ...BASE, storeId: '' });
40
+
41
+ expect(errors.some((error) => error.property === 'storeId')).toBe(true);
42
+ });
43
+
44
+ it('acepta el request sin vendedor: los créditos viejos los llena el backfill', async () => {
45
+ const { dto, errors } = await validateWhitelisted(BASE);
46
+
47
+ expect(errors).toEqual([]);
48
+ expect(dto.sellerId).toBeUndefined();
49
+ });
50
+
51
+ it('rechaza el vendedor vacío: un sellerId en blanco sería una llave de GSI inválida', async () => {
52
+ const { errors } = await validateWhitelisted({ ...BASE, sellerId: '' });
53
+
54
+ expect(errors.some((error) => error.property === 'sellerId')).toBe(true);
55
+ });
56
+ });
@@ -0,0 +1,119 @@
1
+ import 'reflect-metadata';
2
+ import type { CreditSaleContext } from '../../../src/loanCredit/index';
3
+ import type { WizardSessionLookupEntry } from '../../../src/retailWizard/index';
4
+ import { WizardSessionLookupStatusEnum } from '../../../src/retailWizard/enums/WizardSessionLookupStatusEnum';
5
+ import {
6
+ LoanCreditStatusEnum,
7
+ LoanInstallmentStatusEnum,
8
+ type SellerCreditDetailResponse,
9
+ type SellerCreditListResponse,
10
+ type SellerCreditRow,
11
+ } from '../../../src/loanCredit/index';
12
+
13
+ describe('loanCredit — contrato de la cartera del vendedor', () => {
14
+ it('el contexto de venta publica la venta que cerró la sesión', () => {
15
+ const context: CreditSaleContext = {
16
+ status: WizardSessionLookupStatusEnum.OK,
17
+ retailCustomerId: 'RC-1',
18
+ retailCustomerStatus: WizardSessionLookupStatusEnum.OK,
19
+ customerName: null,
20
+ customerNameStatus: WizardSessionLookupStatusEnum.OK,
21
+ productBrand: 'Samsung',
22
+ productModel: 'A15',
23
+ productCapacity: null,
24
+ retailerId: 'R-1',
25
+ storeId: 'ST-1',
26
+ storeName: null,
27
+ storeNameStatus: WizardSessionLookupStatusEnum.OK,
28
+ saleId: 'S-1',
29
+ };
30
+
31
+ expect(context.saleId).toBe('S-1');
32
+ });
33
+
34
+ it('la entrada del lote del wizard publica al vendedor de la sesión', () => {
35
+ const entry: WizardSessionLookupEntry = {
36
+ wizardSessionId: 'WS-1',
37
+ status: WizardSessionLookupStatusEnum.OK,
38
+ sellerRetailUserId: 'U-1',
39
+ };
40
+
41
+ expect(entry.sellerRetailUserId).toBe('U-1');
42
+ });
43
+
44
+ const saleContext: CreditSaleContext = {
45
+ status: WizardSessionLookupStatusEnum.OK,
46
+ retailCustomerId: 'RC-1',
47
+ retailCustomerStatus: WizardSessionLookupStatusEnum.OK,
48
+ customerName: 'Ana López',
49
+ customerNameStatus: WizardSessionLookupStatusEnum.OK,
50
+ productBrand: 'Samsung',
51
+ productModel: 'A15',
52
+ productCapacity: null,
53
+ retailerId: 'R-1',
54
+ storeId: 'ST-1',
55
+ storeName: null,
56
+ storeNameStatus: WizardSessionLookupStatusEnum.OK,
57
+ saleId: 'S-1',
58
+ };
59
+
60
+ it('la fila de la cartera lleva el resumen del plan y la cuota vigente', () => {
61
+ const row: SellerCreditRow = {
62
+ creditId: 'CR-1',
63
+ status: LoanCreditStatusEnum.ACTIVE,
64
+ termWeeks: 12,
65
+ equipmentPriceCents: 500000,
66
+ imei: '356789012345678',
67
+ createdAt: '2026-09-01T00:00:00.000Z',
68
+ summaryUnavailable: false,
69
+ paidInstallments: 1,
70
+ outstandingBalanceCents: 300000,
71
+ overdueDays: 0,
72
+ overdueAmountCents: 0,
73
+ currentInstallment: {
74
+ creditId: 'CR-1',
75
+ installmentNumber: 2,
76
+ status: LoanInstallmentStatusEnum.PENDING,
77
+ dueDate: '2026-10-05',
78
+ capitalCents: 8000,
79
+ interestCents: 1500,
80
+ ivaCents: 500,
81
+ totalCents: 10000,
82
+ outstandingBalanceCents: 10000,
83
+ },
84
+ saleContext,
85
+ };
86
+ const list: SellerCreditListResponse = { items: [row], count: 1, truncated: false };
87
+
88
+ expect(list.items[0]?.currentInstallment?.installmentNumber).toBe(2);
89
+ });
90
+
91
+ it('la fila sin plan legible declara summaryUnavailable y deja las cifras en null', () => {
92
+ const row: SellerCreditRow = {
93
+ creditId: 'CR-2',
94
+ status: LoanCreditStatusEnum.IN_ARREARS,
95
+ termWeeks: 12,
96
+ equipmentPriceCents: 500000,
97
+ createdAt: '2026-09-01T00:00:00.000Z',
98
+ summaryUnavailable: true,
99
+ paidInstallments: null,
100
+ outstandingBalanceCents: null,
101
+ overdueDays: null,
102
+ overdueAmountCents: null,
103
+ currentInstallment: null,
104
+ saleContext,
105
+ };
106
+
107
+ expect(row.paidInstallments).toBeNull();
108
+ });
109
+
110
+ it('el detalle es el crédito, su plan y sus últimos cobros', () => {
111
+ const detail: SellerCreditDetailResponse = {
112
+ credit: {} as SellerCreditDetailResponse['credit'],
113
+ installments: [],
114
+ recentPayments: [],
115
+ };
116
+
117
+ expect(detail.recentPayments).toEqual([]);
118
+ });
119
+ });
@@ -30,10 +30,14 @@ export declare class OriginateLoanCreditRequest {
30
30
  imei?: string;
31
31
  /**
32
32
  * Tienda donde se vende. El motor la persiste y la reenvía al simulador del catálogo en cada
33
- * cotización — al RE-cotizar el wizard no la vuelve a mandar, así que sin guardarla la venta
34
- * perdería su promoción a mitad del paso 06.
33
+ * cotización. Obligatoria: sin ella el crédito no tiene tienda en su cartera.
35
34
  */
36
- storeId?: string;
35
+ storeId: string;
36
+ /**
37
+ * Vendedor que origina: el `retailUserId` de retail-org que la sesión del wizard guarda como
38
+ * `sellerRetailUserId`. Llave de la cartera del vendedor en loan-credit.
39
+ */
40
+ sellerId?: string;
37
41
  /** SKU del equipo, por el mismo motivo que `storeId`: resuelve promociones acotadas por producto. */
38
42
  productSku?: string;
39
43
  }
@@ -91,10 +91,17 @@ __decorate([
91
91
  ], OriginateLoanCreditRequest.prototype, "imei", void 0);
92
92
  __decorate([
93
93
  (0, class_transformer_1.Expose)(),
94
- (0, class_validator_1.IsOptional)(),
95
94
  (0, class_validator_1.IsString)(),
95
+ (0, class_validator_1.IsNotEmpty)(),
96
96
  __metadata("design:type", String)
97
97
  ], OriginateLoanCreditRequest.prototype, "storeId", void 0);
98
+ __decorate([
99
+ (0, class_transformer_1.Expose)(),
100
+ (0, class_validator_1.IsOptional)(),
101
+ (0, class_validator_1.IsString)(),
102
+ (0, class_validator_1.IsNotEmpty)(),
103
+ __metadata("design:type", String)
104
+ ], OriginateLoanCreditRequest.prototype, "sellerId", void 0);
98
105
  __decorate([
99
106
  (0, class_transformer_1.Expose)(),
100
107
  (0, class_validator_1.IsOptional)(),
@@ -28,4 +28,6 @@ export interface CreditSaleContext {
28
28
  storeName: string | null;
29
29
  storeNameStatus: WizardSessionLookupStatusEnum;
30
30
  storeNameReason?: SaleContextReason;
31
+ /** Venta que cerró la sesión. `null` si no cerró; ausente si el lote al wizard no respondió. */
32
+ saleId?: string | null;
31
33
  }
@@ -0,0 +1,10 @@
1
+ import { LoanCreditDetail } from './LoanCreditDetail';
2
+ import { LoanInstallmentResponse } from './LoanInstallmentResponse';
3
+ import { LoanPaymentListItem } from './LoanPaymentListItem';
4
+ /** Detalle de un crédito propio del vendedor (`GET /seller/credits/{creditId}`): crédito, plan y últimos cobros. */
5
+ export interface SellerCreditDetailResponse {
6
+ credit: LoanCreditDetail;
7
+ installments: LoanInstallmentResponse[];
8
+ /** Los últimos cobros asentados, del más reciente al más viejo (máximo 3). */
9
+ recentPayments: LoanPaymentListItem[];
10
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,7 @@
1
+ import { SellerCreditRow } from './SellerCreditRow';
2
+ /** La cartera viva COMPLETA del vendedor, sin paginar. `truncated` = se alcanzó el tope de lectura. */
3
+ export interface SellerCreditListResponse {
4
+ items: SellerCreditRow[];
5
+ count: number;
6
+ truncated: boolean;
7
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,25 @@
1
+ import { LoanCreditStatusEnum } from '../../enums/LoanCreditStatusEnum';
2
+ import { ClientLevelEnum } from '../../../loanScoring/enums/ClientLevelEnum';
3
+ import { CreditSaleContext } from './CreditSaleContext';
4
+ import { LoanInstallmentResponse } from './LoanInstallmentResponse';
5
+ /**
6
+ * Una fila de la cartera del vendedor (`GET /seller/credits`, loan-credit-business). Montos en
7
+ * cents · instantes ISO-8601. Con `summaryUnavailable` las cifras del plan llegan en `null`, nunca en cero.
8
+ */
9
+ export interface SellerCreditRow {
10
+ creditId: string;
11
+ status: LoanCreditStatusEnum;
12
+ termWeeks: number;
13
+ equipmentPriceCents: number;
14
+ clientLevelAtOrigination?: ClientLevelEnum;
15
+ imei?: string;
16
+ createdAt: string;
17
+ summaryUnavailable: boolean;
18
+ paidInstallments: number | null;
19
+ outstandingBalanceCents: number | null;
20
+ overdueDays: number | null;
21
+ overdueAmountCents: number | null;
22
+ /** Primera cuota que todavía se debe; `null` si ya no debe nada o si el plan no se leyó. */
23
+ currentInstallment: LoanInstallmentResponse | null;
24
+ saleContext: CreditSaleContext;
25
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -40,6 +40,9 @@ export * from './dtos/responses/OriginateLoanCreditResponse';
40
40
  export * from './dtos/responses/QuoteLoanCreditResponse';
41
41
  export * from './dtos/responses/SignLoanCreditResponse';
42
42
  export * from './dtos/responses/ActivationCheckResponse';
43
+ export * from './dtos/responses/SellerCreditRow';
44
+ export * from './dtos/responses/SellerCreditListResponse';
45
+ export * from './dtos/responses/SellerCreditDetailResponse';
43
46
  export * from './enums/LoanDeviceMdmStatusEnum';
44
47
  export * from './enums/LoanDeviceLockReasonEnum';
45
48
  export { MdmOperationStatusEnum } from '../mdm/enums/MdmOperationStatusEnum';
@@ -64,6 +64,10 @@ __exportStar(require("./dtos/responses/OriginateLoanCreditResponse"), exports);
64
64
  __exportStar(require("./dtos/responses/QuoteLoanCreditResponse"), exports);
65
65
  __exportStar(require("./dtos/responses/SignLoanCreditResponse"), exports);
66
66
  __exportStar(require("./dtos/responses/ActivationCheckResponse"), exports);
67
+ // Cartera del vendedor (pantalla Cobro del Portal Vendedor)
68
+ __exportStar(require("./dtos/responses/SellerCreditRow"), exports);
69
+ __exportStar(require("./dtos/responses/SellerCreditListResponse"), exports);
70
+ __exportStar(require("./dtos/responses/SellerCreditDetailResponse"), exports);
67
71
  // Device / MDM — contrato hacia los callers de loan-credit
68
72
  __exportStar(require("./enums/LoanDeviceMdmStatusEnum"), exports);
69
73
  __exportStar(require("./enums/LoanDeviceLockReasonEnum"), exports);
@@ -24,4 +24,6 @@ export interface WizardSessionLookupEntry {
24
24
  /** La venta que cerro esta sesion; `null` si todavia no cerro. */
25
25
  saleId?: string | null;
26
26
  folio?: string | null;
27
+ /** Vendedor de la sesión (`retailUserId` de retail-org). `null` si la sesión no lo tiene. */
28
+ sellerRetailUserId?: string | null;
27
29
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fiado/type-kit",
3
- "version": "3.494.0",
3
+ "version": "3.495.0",
4
4
  "description": "",
5
5
  "main": "bin/index.js",
6
6
  "types": "bin/index.d.ts",
@@ -84,13 +84,22 @@ export class OriginateLoanCreditRequest {
84
84
 
85
85
  /**
86
86
  * Tienda donde se vende. El motor la persiste y la reenvía al simulador del catálogo en cada
87
- * cotización — al RE-cotizar el wizard no la vuelve a mandar, así que sin guardarla la venta
88
- * perdería su promoción a mitad del paso 06.
87
+ * cotización. Obligatoria: sin ella el crédito no tiene tienda en su cartera.
88
+ */
89
+ @Expose()
90
+ @IsString()
91
+ @IsNotEmpty()
92
+ storeId!: string;
93
+
94
+ /**
95
+ * Vendedor que origina: el `retailUserId` de retail-org que la sesión del wizard guarda como
96
+ * `sellerRetailUserId`. Llave de la cartera del vendedor en loan-credit.
89
97
  */
90
98
  @Expose()
91
99
  @IsOptional()
92
100
  @IsString()
93
- storeId?: string;
101
+ @IsNotEmpty()
102
+ sellerId?: string;
94
103
 
95
104
  /** SKU del equipo, por el mismo motivo que `storeId`: resuelve promociones acotadas por producto. */
96
105
  @Expose()
@@ -41,4 +41,6 @@ export interface CreditSaleContext {
41
41
  storeName: string | null;
42
42
  storeNameStatus: WizardSessionLookupStatusEnum;
43
43
  storeNameReason?: SaleContextReason;
44
+ /** Venta que cerró la sesión. `null` si no cerró; ausente si el lote al wizard no respondió. */
45
+ saleId?: string | null;
44
46
  }
@@ -0,0 +1,11 @@
1
+ import { LoanCreditDetail } from './LoanCreditDetail';
2
+ import { LoanInstallmentResponse } from './LoanInstallmentResponse';
3
+ import { LoanPaymentListItem } from './LoanPaymentListItem';
4
+
5
+ /** Detalle de un crédito propio del vendedor (`GET /seller/credits/{creditId}`): crédito, plan y últimos cobros. */
6
+ export interface SellerCreditDetailResponse {
7
+ credit: LoanCreditDetail;
8
+ installments: LoanInstallmentResponse[];
9
+ /** Los últimos cobros asentados, del más reciente al más viejo (máximo 3). */
10
+ recentPayments: LoanPaymentListItem[];
11
+ }
@@ -0,0 +1,8 @@
1
+ import { SellerCreditRow } from './SellerCreditRow';
2
+
3
+ /** La cartera viva COMPLETA del vendedor, sin paginar. `truncated` = se alcanzó el tope de lectura. */
4
+ export interface SellerCreditListResponse {
5
+ items: SellerCreditRow[];
6
+ count: number;
7
+ truncated: boolean;
8
+ }
@@ -0,0 +1,26 @@
1
+ import { LoanCreditStatusEnum } from '../../enums/LoanCreditStatusEnum';
2
+ import { ClientLevelEnum } from '../../../loanScoring/enums/ClientLevelEnum';
3
+ import { CreditSaleContext } from './CreditSaleContext';
4
+ import { LoanInstallmentResponse } from './LoanInstallmentResponse';
5
+
6
+ /**
7
+ * Una fila de la cartera del vendedor (`GET /seller/credits`, loan-credit-business). Montos en
8
+ * cents · instantes ISO-8601. Con `summaryUnavailable` las cifras del plan llegan en `null`, nunca en cero.
9
+ */
10
+ export interface SellerCreditRow {
11
+ creditId: string;
12
+ status: LoanCreditStatusEnum;
13
+ termWeeks: number;
14
+ equipmentPriceCents: number;
15
+ clientLevelAtOrigination?: ClientLevelEnum;
16
+ imei?: string;
17
+ createdAt: string;
18
+ summaryUnavailable: boolean;
19
+ paidInstallments: number | null;
20
+ outstandingBalanceCents: number | null;
21
+ overdueDays: number | null;
22
+ overdueAmountCents: number | null;
23
+ /** Primera cuota que todavía se debe; `null` si ya no debe nada o si el plan no se leyó. */
24
+ currentInstallment: LoanInstallmentResponse | null;
25
+ saleContext: CreditSaleContext;
26
+ }
@@ -49,6 +49,10 @@ export * from './dtos/responses/OriginateLoanCreditResponse';
49
49
  export * from './dtos/responses/QuoteLoanCreditResponse';
50
50
  export * from './dtos/responses/SignLoanCreditResponse';
51
51
  export * from './dtos/responses/ActivationCheckResponse';
52
+ // Cartera del vendedor (pantalla Cobro del Portal Vendedor)
53
+ export * from './dtos/responses/SellerCreditRow';
54
+ export * from './dtos/responses/SellerCreditListResponse';
55
+ export * from './dtos/responses/SellerCreditDetailResponse';
52
56
 
53
57
  // Device / MDM — contrato hacia los callers de loan-credit
54
58
  export * from './enums/LoanDeviceMdmStatusEnum';
@@ -25,4 +25,6 @@ export interface WizardSessionLookupEntry {
25
25
  /** La venta que cerro esta sesion; `null` si todavia no cerro. */
26
26
  saleId?: string | null;
27
27
  folio?: string | null;
28
+ /** Vendedor de la sesión (`retailUserId` de retail-org). `null` si la sesión no lo tiene. */
29
+ sellerRetailUserId?: string | null;
28
30
  }