@fiado/type-kit 3.550.0 → 3.551.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.
- package/_test_/unit/retailCommission/commissionPayments.test.ts +168 -0
- package/_test_/unit/retailCommission/commissionRules.test.ts +53 -0
- package/_test_/unit/retailOrg/dtos/RetailUser.test.ts +47 -0
- package/_test_/unit/retailOrg/dtos/requests/retailUserWalletRequests.test.ts +41 -0
- package/_test_/unit/retailOrg/dtos/responses/retailUserWalletResponses.test.ts +30 -0
- package/_test_/unit/retailOrg/enums/RetailUserWalletLinkMethodEnum.test.ts +12 -0
- package/bin/retailCommission/dtos/CommissionApi.d.ts +80 -1
- package/bin/retailCommission/dtos/CommissionLedger.d.ts +96 -0
- package/bin/retailCommission/dtos/CommissionLedger.js +2 -0
- package/bin/retailCommission/dtos/CommissionRule.d.ts +14 -1
- package/bin/retailCommission/enums/CommissionBillingPeriodEnum.d.ts +10 -0
- package/bin/retailCommission/enums/CommissionBillingPeriodEnum.js +14 -0
- package/bin/retailCommission/enums/CommissionLedgerMovementKindEnum.d.ts +14 -0
- package/bin/retailCommission/enums/CommissionLedgerMovementKindEnum.js +18 -0
- package/bin/retailCommission/enums/CommissionPayTimingEnum.d.ts +13 -0
- package/bin/retailCommission/enums/CommissionPayTimingEnum.js +17 -0
- package/bin/retailCommission/enums/CommissionPayTriggerEnum.d.ts +18 -0
- package/bin/retailCommission/enums/CommissionPayTriggerEnum.js +22 -0
- package/bin/retailCommission/enums/CommissionPaymentAttentionEnum.d.ts +14 -0
- package/bin/retailCommission/enums/CommissionPaymentAttentionEnum.js +18 -0
- package/bin/retailCommission/enums/CommissionPaymentStatusEnum.d.ts +14 -0
- package/bin/retailCommission/enums/CommissionPaymentStatusEnum.js +18 -0
- package/bin/retailCommission/index.d.ts +7 -0
- package/bin/retailCommission/index.js +7 -0
- package/bin/retailOrg/dtos/RetailUser.d.ts +13 -0
- package/bin/retailOrg/dtos/RetailUser.js +39 -0
- package/bin/retailOrg/dtos/requests/LinkRetailUserWalletRequest.d.ts +10 -0
- package/bin/retailOrg/dtos/requests/LinkRetailUserWalletRequest.js +37 -0
- package/bin/retailOrg/dtos/requests/LookupRetailUserWalletRequest.d.ts +10 -0
- package/bin/retailOrg/dtos/requests/LookupRetailUserWalletRequest.js +37 -0
- package/bin/retailOrg/dtos/responses/LookupRetailUserWalletResponse.d.ts +10 -0
- package/bin/retailOrg/dtos/responses/LookupRetailUserWalletResponse.js +2 -0
- package/bin/retailOrg/dtos/responses/PrivateRetailUserWalletResponse.d.ts +8 -0
- package/bin/retailOrg/dtos/responses/PrivateRetailUserWalletResponse.js +2 -0
- package/bin/retailOrg/enums/RetailUserWalletLinkMethodEnum.d.ts +11 -0
- package/bin/retailOrg/enums/RetailUserWalletLinkMethodEnum.js +15 -0
- package/bin/retailOrg/index.d.ts +6 -0
- package/bin/retailOrg/index.js +7 -0
- package/bin/retailOrg/validators/IsSingleWalletKeyConstraint.d.ts +6 -0
- package/bin/retailOrg/validators/IsSingleWalletKeyConstraint.js +24 -0
- package/package.json +1 -1
- package/src/retailCommission/dtos/CommissionApi.ts +97 -1
- package/src/retailCommission/dtos/CommissionLedger.ts +101 -0
- package/src/retailCommission/dtos/CommissionRule.ts +15 -1
- package/src/retailCommission/enums/CommissionBillingPeriodEnum.ts +10 -0
- package/src/retailCommission/enums/CommissionLedgerMovementKindEnum.ts +14 -0
- package/src/retailCommission/enums/CommissionPayTimingEnum.ts +13 -0
- package/src/retailCommission/enums/CommissionPayTriggerEnum.ts +18 -0
- package/src/retailCommission/enums/CommissionPaymentAttentionEnum.ts +14 -0
- package/src/retailCommission/enums/CommissionPaymentStatusEnum.ts +14 -0
- package/src/retailCommission/index.ts +7 -0
- package/src/retailOrg/dtos/RetailUser.ts +39 -0
- package/src/retailOrg/dtos/requests/LinkRetailUserWalletRequest.ts +24 -0
- package/src/retailOrg/dtos/requests/LookupRetailUserWalletRequest.ts +24 -0
- package/src/retailOrg/dtos/responses/LookupRetailUserWalletResponse.ts +11 -0
- package/src/retailOrg/dtos/responses/PrivateRetailUserWalletResponse.ts +8 -0
- package/src/retailOrg/enums/RetailUserWalletLinkMethodEnum.ts +11 -0
- package/src/retailOrg/index.ts +8 -0
- package/src/retailOrg/validators/IsSingleWalletKeyConstraint.ts +18 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import * as RetailCommission from '../../../src/retailCommission';
|
|
3
|
+
|
|
4
|
+
describe('retailCommission — enums de los pagos', () => {
|
|
5
|
+
it('expone los estados de un depósito', () => {
|
|
6
|
+
expect(Object.values(RetailCommission.CommissionPaymentStatusEnum)).toEqual([
|
|
7
|
+
'PENDING', 'ACCREDITED', 'FAILED', 'UNVERIFIED',
|
|
8
|
+
]);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('expone por qué un saldo necesita atención', () => {
|
|
12
|
+
expect(Object.values(RetailCommission.CommissionPaymentAttentionEnum)).toEqual(['NO_WALLET', 'REJECTED', 'PAUSED', 'DEBT']);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('expone los tipos de movimiento del libro', () => {
|
|
16
|
+
expect(Object.values(RetailCommission.CommissionLedgerMovementKindEnum)).toEqual([
|
|
17
|
+
'EARN', 'DEDUCT', 'RELEASE', 'PAID_OUTSIDE',
|
|
18
|
+
]);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('expone qué pide pagarle a un vendedor', () => {
|
|
22
|
+
expect(Object.values(RetailCommission.CommissionPayTriggerEnum)).toEqual([
|
|
23
|
+
'SALE', 'CUT', 'RELEASE', 'ADJUSTMENT', 'WALLET_LINKED', 'RETRY',
|
|
24
|
+
]);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Ana después del cierre del 12 de octubre: ganó $300, le descontaron $100 de una venta cancelada,
|
|
29
|
+
// se le depositaron $180 y le quedan $20 de renta retenidos hasta el 1 de noviembre.
|
|
30
|
+
const balance: RetailCommission.CommissionSellerBalance = {
|
|
31
|
+
retailerId: 'RET-1',
|
|
32
|
+
sellerId: 'V-01',
|
|
33
|
+
retainedCents: 2_000,
|
|
34
|
+
payableCents: 0,
|
|
35
|
+
inTransitCents: 0,
|
|
36
|
+
earnedCents: 30_000,
|
|
37
|
+
deductedCents: 10_000,
|
|
38
|
+
accreditedCents: 18_000,
|
|
39
|
+
paidOutsideCents: 0,
|
|
40
|
+
attention: null,
|
|
41
|
+
attentionCode: null,
|
|
42
|
+
attentionSince: null,
|
|
43
|
+
updatedAt: '2026-10-12T07:00:00.000Z',
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const cut: RetailCommission.CommissionLedgerMovement = {
|
|
47
|
+
movementKey: 'MOV#2026-10-05#CUT',
|
|
48
|
+
kind: RetailCommission.CommissionLedgerMovementKindEnum.EARN,
|
|
49
|
+
periodId: '2026-10-05',
|
|
50
|
+
retainedDeltaCents: 2_000,
|
|
51
|
+
payableDeltaCents: 8_000,
|
|
52
|
+
byRule: [
|
|
53
|
+
{ ruleId: 'RC-BASE', payTiming: RetailCommission.CommissionPayTimingEnum.ON_CUT, cents: 8_000 },
|
|
54
|
+
{ ruleId: 'RC-RENTA', payTiming: RetailCommission.CommissionPayTimingEnum.MONTHLY, cents: 2_000 },
|
|
55
|
+
],
|
|
56
|
+
saleId: null,
|
|
57
|
+
adjustmentId: null,
|
|
58
|
+
releaseMonth: null,
|
|
59
|
+
reason: null,
|
|
60
|
+
createdAt: '2026-10-12T06:00:00.000Z',
|
|
61
|
+
createdBy: 'system',
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const accredited: RetailCommission.CommissionPayment = {
|
|
65
|
+
paymentId: '01JPAY0000000000000000001',
|
|
66
|
+
retailerId: 'RET-1',
|
|
67
|
+
sellerId: 'V-01',
|
|
68
|
+
amountCents: 10_000,
|
|
69
|
+
status: RetailCommission.CommissionPaymentStatusEnum.ACCREDITED,
|
|
70
|
+
directoryId: 'DIR-1',
|
|
71
|
+
transactionId: 'TX-1',
|
|
72
|
+
errorCode: null,
|
|
73
|
+
concept: 'Comisiones VentasLuga · 5 oct 2026',
|
|
74
|
+
requestedAt: '2026-10-05T18:00:00.000Z',
|
|
75
|
+
resolvedAt: '2026-10-05T18:00:02.000Z',
|
|
76
|
+
resolvedBy: null,
|
|
77
|
+
note: null,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const statement: RetailCommission.CommissionChainStatement = {
|
|
81
|
+
statementId: '01JSTMT00000000000000001',
|
|
82
|
+
retailerId: 'RET-1',
|
|
83
|
+
period: RetailCommission.CommissionBillingPeriodEnum.WEEKLY,
|
|
84
|
+
startDate: '2026-10-05',
|
|
85
|
+
endDate: '2026-10-11',
|
|
86
|
+
accreditedCents: 10_000,
|
|
87
|
+
paymentsCount: 1,
|
|
88
|
+
sellersCount: 1,
|
|
89
|
+
generatedAt: '2026-10-12T07:00:00.000Z',
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
describe('retailCommission — libro de pagos', () => {
|
|
93
|
+
it('lo que queda en las tres bolsas es lo ganado menos lo descontado, lo depositado y lo pagado por fuera', () => {
|
|
94
|
+
const inPockets = balance.retainedCents + balance.payableCents + balance.inTransitCents;
|
|
95
|
+
expect(inPockets).toBe(balance.earnedCents - balance.deductedCents - balance.accreditedCents - balance.paidOutsideCents);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('el movimiento del cierre reparte por regla entre retenido y exigible', () => {
|
|
99
|
+
const total = cut.byRule.reduce((sum, r) => sum + r.cents, 0);
|
|
100
|
+
expect(total).toBe(cut.retainedDeltaCents + cut.payableDeltaCents);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('un depósito confirmado trae folio y uno rechazado su código', () => {
|
|
104
|
+
const failed: RetailCommission.CommissionPayment = {
|
|
105
|
+
...accredited,
|
|
106
|
+
paymentId: '01JPAY0000000000000000002',
|
|
107
|
+
status: RetailCommission.CommissionPaymentStatusEnum.FAILED,
|
|
108
|
+
transactionId: null,
|
|
109
|
+
errorCode: 'USER_NOT_ACTIVE',
|
|
110
|
+
};
|
|
111
|
+
expect([failed.transactionId, failed.errorCode]).toEqual([null, 'USER_NOT_ACTIVE']);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('el estado de cuenta cubre un periodo de cobro', () => {
|
|
115
|
+
expect([statement.period, statement.startDate, statement.endDate]).toEqual(['WEEKLY', '2026-10-05', '2026-10-11']);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe('retailCommission — contratos de los pagos', () => {
|
|
120
|
+
it('"Por pagar" trae pendientes, depósitos sin confirmar y deudas', () => {
|
|
121
|
+
const unverified: RetailCommission.CommissionPayment = {
|
|
122
|
+
...accredited,
|
|
123
|
+
status: RetailCommission.CommissionPaymentStatusEnum.UNVERIFIED,
|
|
124
|
+
transactionId: null,
|
|
125
|
+
resolvedAt: null,
|
|
126
|
+
};
|
|
127
|
+
const debt: RetailCommission.CommissionSellerBalance = {
|
|
128
|
+
...balance,
|
|
129
|
+
payableCents: -5_000,
|
|
130
|
+
attention: RetailCommission.CommissionPaymentAttentionEnum.DEBT,
|
|
131
|
+
attentionSince: '2026-10-09T18:00:00.000Z',
|
|
132
|
+
};
|
|
133
|
+
const report: RetailCommission.GetPaymentAttentionResponse = { pending: [], unverified: [unverified], debts: [debt] };
|
|
134
|
+
expect([report.unverified[0]?.status, report.debts[0]?.attention]).toEqual(['UNVERIFIED', 'DEBT']);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('pagado por fuera lleva motivo; la revisión manda folio solo si sí se depositó', () => {
|
|
138
|
+
const outside: RetailCommission.MarkPaidOutsideRequest = { amountCents: 8_000, reason: 'Pagado en efectivo en tienda' };
|
|
139
|
+
const yes: RetailCommission.ResolvePaymentRequest = { accredited: true, transactionId: 'TX-9', note: 'Folio confirmado' };
|
|
140
|
+
const no: RetailCommission.ResolvePaymentRequest = { accredited: false, note: 'No llegó a la wallet' };
|
|
141
|
+
expect([outside.amountCents, yes.transactionId, no.transactionId]).toEqual([8_000, 'TX-9', undefined]);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('el historial del vendedor y los estados de cuenta de la cadena', () => {
|
|
145
|
+
const ledger: RetailCommission.GetSellerLedgerResponse = { balance, movements: [cut], payments: [accredited] };
|
|
146
|
+
const list: RetailCommission.ListStatementsResponse = { statements: [statement] };
|
|
147
|
+
const detail: RetailCommission.GetStatementResponse = { statement, payments: [accredited] };
|
|
148
|
+
expect([ledger.movements.length, list.statements.length, detail.payments.length]).toEqual([1, 1, 1]);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('el vendedor sin nada en el libro ve su saldo en null', () => {
|
|
152
|
+
const mine: RetailCommission.GetMyPaymentsResponse = { balance: null, payments: [] };
|
|
153
|
+
expect(mine.balance).toBeNull();
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('las respuestas de las acciones de "Por pagar" y del aviso privado de wallet vinculada', () => {
|
|
157
|
+
const retry: RetailCommission.RetrySellerPaymentView = { sellerId: 'V-01', triggerId: '01JRETRY0000000000000000001' };
|
|
158
|
+
const outside: RetailCommission.MarkPaidOutsideView = { balance, movement: cut };
|
|
159
|
+
const resolved: RetailCommission.ResolvePaymentView = { payment: accredited };
|
|
160
|
+
const trigger: RetailCommission.PrivateTriggerSellerPaymentResponse = { enqueued: false };
|
|
161
|
+
expect([retry.sellerId, outside.movement.movementKey, resolved.payment.status, trigger.enqueued]).toEqual([
|
|
162
|
+
'V-01',
|
|
163
|
+
'MOV#2026-10-05#CUT',
|
|
164
|
+
'ACCREDITED',
|
|
165
|
+
false,
|
|
166
|
+
]);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
@@ -28,6 +28,14 @@ describe('retailCommission — enums de las reglas', () => {
|
|
|
28
28
|
expect(Object.values(RetailCommission.CommissionCountByEnum)).toEqual(['PIECE', 'SALE']);
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
+
it('una regla se paga al momento, al cierre de la semana o al cierre del mes', () => {
|
|
32
|
+
expect(Object.values(RetailCommission.CommissionPayTimingEnum)).toEqual(['ON_SALE', 'ON_CUT', 'MONTHLY']);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('a la cadena se le cobra cada semana o cada mes', () => {
|
|
36
|
+
expect(Object.values(RetailCommission.CommissionBillingPeriodEnum)).toEqual(['WEEKLY', 'MONTHLY']);
|
|
37
|
+
});
|
|
38
|
+
|
|
31
39
|
it('expone las ventanas del corte', () => {
|
|
32
40
|
expect(Object.values(RetailCommission.CommissionWindowEnum)).toEqual(['SEMANA_OPERATIVA', 'ROLLING_7D', 'MES']);
|
|
33
41
|
});
|
|
@@ -99,6 +107,12 @@ describe('retailCommission — regla, políticas y snapshot', () => {
|
|
|
99
107
|
expect(npi.condition?.minSample).toBe(5);
|
|
100
108
|
});
|
|
101
109
|
|
|
110
|
+
it('la regla dice cuándo se paga, y una sin el campo sigue siendo válida', () => {
|
|
111
|
+
const monthly: RetailCommission.CommissionRule = { ...base, payTiming: RetailCommission.CommissionPayTimingEnum.MONTHLY };
|
|
112
|
+
expect(base.payTiming).toBeUndefined();
|
|
113
|
+
expect(monthly.payTiming).toBe('MONTHLY');
|
|
114
|
+
});
|
|
115
|
+
|
|
102
116
|
it('el snapshot congela las reglas publicadas y las políticas', () => {
|
|
103
117
|
const snapshot: RetailCommission.CommissionRuleSetSnapshot = {
|
|
104
118
|
retailerId: 'RET-1',
|
|
@@ -124,6 +138,17 @@ describe('retailCommission — regla, políticas y snapshot', () => {
|
|
|
124
138
|
const policies: RetailCommission.CommissionPolicies = { clawback: null, commissions: null };
|
|
125
139
|
expect(policies.clawback).toBeNull();
|
|
126
140
|
});
|
|
141
|
+
|
|
142
|
+
it('la política de pagos es opcional y viaja con las demás', () => {
|
|
143
|
+
const payouts: RetailCommission.CommissionPayoutPolicy = {
|
|
144
|
+
walletDeposits: false,
|
|
145
|
+
chainBillingPeriod: RetailCommission.CommissionBillingPeriodEnum.MONTHLY,
|
|
146
|
+
};
|
|
147
|
+
const legacy: RetailCommission.CommissionPolicies = { clawback: null, commissions: null };
|
|
148
|
+
const withPayouts: RetailCommission.CommissionPolicies = { ...legacy, payouts };
|
|
149
|
+
expect(legacy.payouts).toBeUndefined();
|
|
150
|
+
expect(withPayouts.payouts?.chainBillingPeriod).toBe('MONTHLY');
|
|
151
|
+
});
|
|
127
152
|
});
|
|
128
153
|
|
|
129
154
|
describe('retailCommission — devengo con reglas', () => {
|
|
@@ -234,6 +259,23 @@ describe('retailCommission — contratos de reglas y políticas', () => {
|
|
|
234
259
|
expect([create.name, update.expectedVersion, action.expectedVersion]).toEqual(['Bono Accesorios', 2, 3]);
|
|
235
260
|
});
|
|
236
261
|
|
|
262
|
+
it('el editor manda cuándo se paga y el vendedor lo ve', () => {
|
|
263
|
+
const create: RetailCommission.CreateRuleRequest = { ...input, payTiming: RetailCommission.CommissionPayTimingEnum.ON_SALE };
|
|
264
|
+
const view: RetailCommission.CommissionRuleView = {
|
|
265
|
+
ruleId: 'RC-01',
|
|
266
|
+
name: create.name,
|
|
267
|
+
description: create.description,
|
|
268
|
+
levers: create.levers,
|
|
269
|
+
scope: create.scope,
|
|
270
|
+
condition: create.condition,
|
|
271
|
+
effectiveFrom: create.effectiveFrom,
|
|
272
|
+
effectiveTo: create.effectiveTo,
|
|
273
|
+
payTiming: create.payTiming,
|
|
274
|
+
};
|
|
275
|
+
expect(input.payTiming).toBeUndefined();
|
|
276
|
+
expect(view.payTiming).toBe('ON_SALE');
|
|
277
|
+
});
|
|
278
|
+
|
|
237
279
|
it('las políticas se guardan juntas', () => {
|
|
238
280
|
const request: RetailCommission.UpdatePoliciesRequest = {
|
|
239
281
|
clawback: { cancelacionDias: 7, devolucionDias: 7, npiM30Dias: 30 },
|
|
@@ -242,6 +284,17 @@ describe('retailCommission — contratos de reglas y políticas', () => {
|
|
|
242
284
|
expect(request.commissions.multiplicadorMaxBps).toBe(25000);
|
|
243
285
|
});
|
|
244
286
|
|
|
287
|
+
it('la política de pagos se guarda con las demás, y un editor que no la manda sigue siendo válido', () => {
|
|
288
|
+
const request: RetailCommission.UpdatePoliciesRequest = {
|
|
289
|
+
clawback: { cancelacionDias: 7, devolucionDias: 7, npiM30Dias: 30 },
|
|
290
|
+
commissions: { multiplicadorMaxBps: 25000, ventanaDefault: CommissionWindowEnum.SEMANA_OPERATIVA },
|
|
291
|
+
payouts: { walletDeposits: true, chainBillingPeriod: RetailCommission.CommissionBillingPeriodEnum.WEEKLY },
|
|
292
|
+
};
|
|
293
|
+
const legacy: RetailCommission.UpdatePoliciesRequest = { clawback: request.clawback, commissions: request.commissions };
|
|
294
|
+
expect(request.payouts?.walletDeposits).toBe(true);
|
|
295
|
+
expect(legacy.payouts).toBeUndefined();
|
|
296
|
+
});
|
|
297
|
+
|
|
245
298
|
it('el plan vigente con reglas puede no existir todavía', () => {
|
|
246
299
|
const response: RetailCommission.GetRuleSetResponse = { active: null };
|
|
247
300
|
const history: RetailCommission.RuleHistoryResponse = {
|
|
@@ -5,6 +5,7 @@ import { RetailUser } from '../../../../src/retailOrg/dtos/RetailUser';
|
|
|
5
5
|
import { EmploymentTypeEnum } from '../../../../src/retailOrg/enums/EmploymentTypeEnum';
|
|
6
6
|
import { RetailUserStatusEnum } from '../../../../src/retailOrg/enums/RetailUserStatusEnum';
|
|
7
7
|
import { CollectorSeniorityEnum } from '../../../../src/retailOrg/enums/CollectorSeniorityEnum';
|
|
8
|
+
import { RetailUserWalletLinkMethodEnum } from '../../../../src/retailOrg/enums/RetailUserWalletLinkMethodEnum';
|
|
8
9
|
|
|
9
10
|
const valid = {
|
|
10
11
|
retailUserId: 'ru_001',
|
|
@@ -82,3 +83,49 @@ describe('RetailUser', () => {
|
|
|
82
83
|
expect(errors.some(e => e.property === 'cognitoSub')).toBe(true);
|
|
83
84
|
});
|
|
84
85
|
});
|
|
86
|
+
|
|
87
|
+
describe('RetailUser — wallet PCF', () => {
|
|
88
|
+
const linked = {
|
|
89
|
+
...valid,
|
|
90
|
+
fiadoDirectoryId: 'dir_001',
|
|
91
|
+
pcfAccountId: 'pcf_001',
|
|
92
|
+
walletLinkMethod: RetailUserWalletLinkMethodEnum.CLABE,
|
|
93
|
+
walletClabeLast4: '9012',
|
|
94
|
+
walletLinkedAt: '2026-10-20T10:00:00.000Z',
|
|
95
|
+
walletLinkedBy: 'admin_001',
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
it('valida con la wallet vinculada', async () => {
|
|
99
|
+
const errors = await validate(plainToInstance(RetailUser, linked));
|
|
100
|
+
expect(errors).toEqual([]);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('valida con la wallet desvinculada (campos en null)', async () => {
|
|
104
|
+
const unlinked = {
|
|
105
|
+
...valid,
|
|
106
|
+
fiadoDirectoryId: null,
|
|
107
|
+
pcfAccountId: null,
|
|
108
|
+
walletLinkMethod: null,
|
|
109
|
+
walletClabeLast4: null,
|
|
110
|
+
walletLinkedAt: null,
|
|
111
|
+
walletLinkedBy: null,
|
|
112
|
+
};
|
|
113
|
+
const errors = await validate(plainToInstance(RetailUser, unlinked));
|
|
114
|
+
expect(errors).toEqual([]);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('falla si walletLinkMethod está fuera del enum', async () => {
|
|
118
|
+
const errors = await validate(plainToInstance(RetailUser, { ...linked, walletLinkMethod: 'EMAIL' }));
|
|
119
|
+
expect(errors.some(e => e.property === 'walletLinkMethod')).toBe(true);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('falla si walletClabeLast4 trae la CLABE completa', async () => {
|
|
123
|
+
const errors = await validate(plainToInstance(RetailUser, { ...linked, walletClabeLast4: '646180123456789012' }));
|
|
124
|
+
expect(errors.some(e => e.property === 'walletClabeLast4')).toBe(true);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('falla si walletLinkedAt no es ISO 8601', async () => {
|
|
128
|
+
const errors = await validate(plainToInstance(RetailUser, { ...linked, walletLinkedAt: '20/10/2026' }));
|
|
129
|
+
expect(errors.some(e => e.property === 'walletLinkedAt')).toBe(true);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { plainToInstance, type ClassConstructor } from 'class-transformer';
|
|
3
|
+
import { validateSync, type ValidationError } from 'class-validator';
|
|
4
|
+
import { LinkRetailUserWalletRequest, LookupRetailUserWalletRequest } from '../../../../../src/retailOrg';
|
|
5
|
+
|
|
6
|
+
const CLABE = '646180123456789012';
|
|
7
|
+
const PHONE = '+525512345678';
|
|
8
|
+
|
|
9
|
+
function validate(cls: ClassConstructor<object>, payload: object): ValidationError[] {
|
|
10
|
+
return validateSync(plainToInstance(cls, payload, { excludeExtraneousValues: true }));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function errorsOf(cls: ClassConstructor<object>, payload: object): string[] {
|
|
14
|
+
return validate(cls, payload).map((e) => e.property);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function clabeConstraints(cls: ClassConstructor<object>, payload: object): string[] {
|
|
18
|
+
return Object.keys(validate(cls, payload).find((e) => e.property === 'clabe')?.constraints ?? {});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const requests: Array<[string, ClassConstructor<object>]> = [
|
|
22
|
+
['LookupRetailUserWalletRequest', LookupRetailUserWalletRequest],
|
|
23
|
+
['LinkRetailUserWalletRequest', LinkRetailUserWalletRequest],
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
describe.each(requests)('%s', (_name, cls) => {
|
|
27
|
+
it('acepta solo la CLABE', () => expect(errorsOf(cls, { clabe: CLABE })).toEqual([]));
|
|
28
|
+
it('acepta solo el teléfono', () => expect(errorsOf(cls, { phone: PHONE })).toEqual([]));
|
|
29
|
+
it('rechaza la CLABE y el teléfono juntos', () => expect(errorsOf(cls, { clabe: CLABE, phone: PHONE })).toContain('clabe'));
|
|
30
|
+
it('rechaza un body sin ninguno', () => expect(clabeConstraints(cls, {})).toContain('isSingleWalletKey'));
|
|
31
|
+
it('rechaza un teléfono en null sin CLABE', () =>
|
|
32
|
+
expect(clabeConstraints(cls, { phone: null })).toContain('isSingleWalletKey'));
|
|
33
|
+
it('acepta la CLABE en null con teléfono', () => expect(errorsOf(cls, { clabe: null, phone: '5512345678' })).toEqual([]));
|
|
34
|
+
it('rechaza la CLABE vacía con teléfono', () => expect(errorsOf(cls, { clabe: '', phone: '5512345678' })).toContain('clabe'));
|
|
35
|
+
it('rechaza una CLABE que no son 18 dígitos', () => expect(errorsOf(cls, { clabe: '64618012345678901' })).toContain('clabe'));
|
|
36
|
+
it('acepta el teléfono en 10 dígitos, sin +52', () => expect(errorsOf(cls, { phone: '5512345678' })).toEqual([]));
|
|
37
|
+
it('rechaza un teléfono que no son 10 dígitos o que trae otra lada', () => {
|
|
38
|
+
expect(errorsOf(cls, { phone: '551234567' })).toContain('phone');
|
|
39
|
+
expect(errorsOf(cls, { phone: '+15512345678' })).toContain('phone');
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import {
|
|
3
|
+
RetailUserWalletLinkMethodEnum,
|
|
4
|
+
type LookupRetailUserWalletResponse,
|
|
5
|
+
type PrivateRetailUserWalletResponse,
|
|
6
|
+
} from '../../../../../src/retailOrg';
|
|
7
|
+
|
|
8
|
+
describe('respuestas de la wallet del vendedor', () => {
|
|
9
|
+
it('la búsqueda dice si la encontró, si está activa y a nombre de quién', () => {
|
|
10
|
+
const found: LookupRetailUserWalletResponse = {
|
|
11
|
+
found: true,
|
|
12
|
+
active: true,
|
|
13
|
+
holderName: 'Ana López',
|
|
14
|
+
method: RetailUserWalletLinkMethodEnum.CLABE,
|
|
15
|
+
};
|
|
16
|
+
const missing: LookupRetailUserWalletResponse = {
|
|
17
|
+
found: false,
|
|
18
|
+
active: false,
|
|
19
|
+
holderName: null,
|
|
20
|
+
method: RetailUserWalletLinkMethodEnum.PHONE,
|
|
21
|
+
};
|
|
22
|
+
expect([found.holderName, missing.holderName]).toEqual(['Ana López', null]);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('el privado devuelve la wallet o null si no tiene', () => {
|
|
26
|
+
const linked: PrivateRetailUserWalletResponse = { directoryId: 'dir_001', pcfAccountId: 'pcf_001' };
|
|
27
|
+
const none: PrivateRetailUserWalletResponse = { directoryId: null, pcfAccountId: null };
|
|
28
|
+
expect([linked.directoryId, none.directoryId]).toEqual(['dir_001', null]);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { RetailUserWalletLinkMethodEnum } from '../../../../src/retailOrg/enums/RetailUserWalletLinkMethodEnum';
|
|
2
|
+
|
|
3
|
+
describe('RetailUserWalletLinkMethodEnum', () => {
|
|
4
|
+
it('contiene exactamente los métodos del contrato con value === key', () => {
|
|
5
|
+
expect(RetailUserWalletLinkMethodEnum.CLABE).toBe('CLABE');
|
|
6
|
+
expect(RetailUserWalletLinkMethodEnum.PHONE).toBe('PHONE');
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('no tiene valores de más', () => {
|
|
10
|
+
expect(Object.values(RetailUserWalletLinkMethodEnum).sort()).toEqual(['CLABE', 'PHONE']);
|
|
11
|
+
});
|
|
12
|
+
});
|
|
@@ -4,8 +4,11 @@ import type { CommissionAddOn, CommissionCategory, CommissionPlanSnapshot, Commi
|
|
|
4
4
|
import type { CommissionAccrual, CommissionAppliedBonus, CommissionSaleInput, CommissionSaleLine } from './CommissionAccrual';
|
|
5
5
|
import type { CommissionAdjustment, CommissionAppliedMultiplier, CommissionAppliedThreshold, CommissionPayout, CommissionPayoutSeller, CommissionSellerBreakdown } from './CommissionPayout';
|
|
6
6
|
import type { CommissionPolicies, CommissionRule, CommissionRuleCondition, CommissionRuleLevers, CommissionRuleScope, CommissionRuleSetSnapshot } from './CommissionRule';
|
|
7
|
+
import type { CommissionChainStatement, CommissionLedgerMovement, CommissionPayment, CommissionSellerBalance } from './CommissionLedger';
|
|
7
8
|
import type { CommissionWindowEnum } from '../enums/CommissionWindowEnum';
|
|
9
|
+
import type { CommissionBillingPeriodEnum } from '../enums/CommissionBillingPeriodEnum';
|
|
8
10
|
import type { CommissionCountByEnum } from '../enums/CommissionCountByEnum';
|
|
11
|
+
import type { CommissionPayTimingEnum } from '../enums/CommissionPayTimingEnum';
|
|
9
12
|
import type { CommissionAggregateByEnum } from '../enums/CommissionAggregateByEnum';
|
|
10
13
|
import type { CommissionSeriesByEnum } from '../enums/CommissionSeriesByEnum';
|
|
11
14
|
import type { CommissionAccrualStatusEnum } from '../enums/CommissionAccrualStatusEnum';
|
|
@@ -197,6 +200,8 @@ export interface CommissionRuleInput {
|
|
|
197
200
|
replacesOthers: boolean;
|
|
198
201
|
/** Si la regla cuenta piezas o ventas. Ausente = `PIECE`. */
|
|
199
202
|
countBy?: CommissionCountByEnum;
|
|
203
|
+
/** Cuándo se paga lo que gana. Ausente = `ON_CUT`. */
|
|
204
|
+
payTiming?: CommissionPayTimingEnum;
|
|
200
205
|
}
|
|
201
206
|
/** `POST /backoffice/commissions/rules` — la regla nace en borrador. */
|
|
202
207
|
export type CreateRuleRequest = CommissionRuleInput;
|
|
@@ -231,7 +236,7 @@ export interface RuleHistoryResponse {
|
|
|
231
236
|
export interface GetPoliciesResponse {
|
|
232
237
|
policies: CommissionPolicies;
|
|
233
238
|
}
|
|
234
|
-
/** `PUT /backoffice/commissions/policies` — las
|
|
239
|
+
/** `PUT /backoffice/commissions/policies` — las políticas se guardan juntas. */
|
|
235
240
|
export interface UpdatePoliciesRequest {
|
|
236
241
|
clawback: {
|
|
237
242
|
cancelacionDias: number;
|
|
@@ -242,6 +247,11 @@ export interface UpdatePoliciesRequest {
|
|
|
242
247
|
multiplicadorMaxBps: number;
|
|
243
248
|
ventanaDefault: CommissionWindowEnum;
|
|
244
249
|
};
|
|
250
|
+
/** Opcional para que un editor que todavía no la manda siga funcionando. */
|
|
251
|
+
payouts?: {
|
|
252
|
+
walletDeposits: boolean;
|
|
253
|
+
chainBillingPeriod: CommissionBillingPeriodEnum;
|
|
254
|
+
};
|
|
245
255
|
}
|
|
246
256
|
export interface UpdatePoliciesResponse {
|
|
247
257
|
policies: CommissionPolicies;
|
|
@@ -375,6 +385,8 @@ export interface CommissionRuleView {
|
|
|
375
385
|
effectiveTo: string | null;
|
|
376
386
|
/** Si la regla cuenta piezas o ventas. Ausente = `PIECE`. */
|
|
377
387
|
countBy?: CommissionCountByEnum;
|
|
388
|
+
/** Cuándo se paga lo que gana. Ausente = `ON_CUT`. */
|
|
389
|
+
payTiming?: CommissionPayTimingEnum;
|
|
378
390
|
}
|
|
379
391
|
/** Lo pagado en varios cortes. */
|
|
380
392
|
export interface MyCommissionsTotals {
|
|
@@ -411,3 +423,70 @@ export interface GetMyCommissionsResponse {
|
|
|
411
423
|
/** Tope del multiplicador de la política; `null` sin política. */
|
|
412
424
|
multiplierCapBps: number | null;
|
|
413
425
|
}
|
|
426
|
+
/** `GET /backoffice/commissions/payments/attention` — el reporte "Por pagar" de la cadena. */
|
|
427
|
+
export interface GetPaymentAttentionResponse {
|
|
428
|
+
/** Saldos con exigible sin depositar, con su motivo. */
|
|
429
|
+
pending: CommissionSellerBalance[];
|
|
430
|
+
/** Depósitos sin confirmar: "Requiere revisión". */
|
|
431
|
+
unverified: CommissionPayment[];
|
|
432
|
+
/** Saldos con exigible negativo. */
|
|
433
|
+
debts: CommissionSellerBalance[];
|
|
434
|
+
}
|
|
435
|
+
/** `POST /backoffice/commissions/payments/sellers/{sellerId}/paid-outside` */
|
|
436
|
+
export interface MarkPaidOutsideRequest {
|
|
437
|
+
amountCents: number;
|
|
438
|
+
/** Obligatorio: dinero que sale sin depósito tiene que poder auditarse. */
|
|
439
|
+
reason: string;
|
|
440
|
+
}
|
|
441
|
+
/** `POST /backoffice/commissions/payments/{paymentId}/resolve` — revisión de un depósito sin confirmar. */
|
|
442
|
+
export interface ResolvePaymentRequest {
|
|
443
|
+
/** `true` = sí se depositó; `false` = no, y el monto vuelve al exigible. */
|
|
444
|
+
accredited: boolean;
|
|
445
|
+
/** El folio, cuando sí se depositó. */
|
|
446
|
+
transactionId?: string;
|
|
447
|
+
note: string;
|
|
448
|
+
}
|
|
449
|
+
/** `GET /backoffice/commissions/sellers/{sellerId}/ledger` — saldo e historial de un vendedor. */
|
|
450
|
+
export interface GetSellerLedgerResponse {
|
|
451
|
+
balance: CommissionSellerBalance;
|
|
452
|
+
movements: CommissionLedgerMovement[];
|
|
453
|
+
payments: CommissionPayment[];
|
|
454
|
+
}
|
|
455
|
+
/** `GET /backoffice/commissions/statements` */
|
|
456
|
+
export interface ListStatementsResponse {
|
|
457
|
+
statements: CommissionChainStatement[];
|
|
458
|
+
}
|
|
459
|
+
/** `GET /backoffice/commissions/statements/{statementId}` — el estado de cuenta y sus depósitos. */
|
|
460
|
+
export interface GetStatementResponse {
|
|
461
|
+
statement: CommissionChainStatement;
|
|
462
|
+
payments: CommissionPayment[];
|
|
463
|
+
}
|
|
464
|
+
/** `GET /commissions/me/payments` — los pagos del vendedor que llama. */
|
|
465
|
+
export interface GetMyPaymentsResponse {
|
|
466
|
+
/** `null` mientras no tiene nada en el libro. */
|
|
467
|
+
balance: CommissionSellerBalance | null;
|
|
468
|
+
payments: CommissionPayment[];
|
|
469
|
+
}
|
|
470
|
+
/** `POST /backoffice/commissions/payments/sellers/{sellerId}/retry` (202) — el pago quedó en la cola. */
|
|
471
|
+
export interface RetrySellerPaymentView {
|
|
472
|
+
sellerId: string;
|
|
473
|
+
/** Id del disparo `RETRY` con que se encoló. */
|
|
474
|
+
triggerId: string;
|
|
475
|
+
}
|
|
476
|
+
/** `POST /backoffice/commissions/payments/sellers/{sellerId}/paid-outside` (201) — el saldo nuevo y el movimiento escrito. */
|
|
477
|
+
export interface MarkPaidOutsideView {
|
|
478
|
+
balance: CommissionSellerBalance;
|
|
479
|
+
movement: CommissionLedgerMovement;
|
|
480
|
+
}
|
|
481
|
+
/** `POST /backoffice/commissions/payments/{paymentId}/resolve` — el depósito ya resuelto. */
|
|
482
|
+
export interface ResolvePaymentView {
|
|
483
|
+
payment: CommissionPayment;
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* `POST /private/retailers/{retailerId}/sellers/{sellerId}/payments/trigger?tenantId=` (202) — retail-org
|
|
487
|
+
* avisa que el vendedor vinculó su wallet. El cuerpo va vacío (`{}`).
|
|
488
|
+
*/
|
|
489
|
+
export interface PrivateTriggerSellerPaymentResponse {
|
|
490
|
+
/** `false` si la cola no lo recibió: el saldo sigue pendiente y entra en el siguiente pago. */
|
|
491
|
+
enqueued: boolean;
|
|
492
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { CommissionBillingPeriodEnum } from '../enums/CommissionBillingPeriodEnum';
|
|
2
|
+
import type { CommissionLedgerMovementKindEnum } from '../enums/CommissionLedgerMovementKindEnum';
|
|
3
|
+
import type { CommissionPayTimingEnum } from '../enums/CommissionPayTimingEnum';
|
|
4
|
+
import type { CommissionPaymentAttentionEnum } from '../enums/CommissionPaymentAttentionEnum';
|
|
5
|
+
import type { CommissionPaymentStatusEnum } from '../enums/CommissionPaymentStatusEnum';
|
|
6
|
+
/**
|
|
7
|
+
* Saldo de un vendedor en una cadena. Es parte del libro de pagos: cada peso que se le acredita, se
|
|
8
|
+
* le descuenta o se le deposita en su wallet. Montos en centavos; fechas en ISO.
|
|
9
|
+
*/
|
|
10
|
+
export interface CommissionSellerBalance {
|
|
11
|
+
retailerId: string;
|
|
12
|
+
sellerId: string;
|
|
13
|
+
/** Ganado que todavía no toca pagar (reglas al cierre del mes). */
|
|
14
|
+
retainedCents: number;
|
|
15
|
+
/** Lo que se puede depositar ya. Negativo = el vendedor debe. */
|
|
16
|
+
payableCents: number;
|
|
17
|
+
/** Apartado para un depósito en curso o sin confirmar. */
|
|
18
|
+
inTransitCents: number;
|
|
19
|
+
/** Acumulado de todo lo ganado. */
|
|
20
|
+
earnedCents: number;
|
|
21
|
+
/** Acumulado de todo lo descontado. */
|
|
22
|
+
deductedCents: number;
|
|
23
|
+
/** Acumulado de lo depositado y confirmado. */
|
|
24
|
+
accreditedCents: number;
|
|
25
|
+
/** Acumulado de lo pagado por fuera. */
|
|
26
|
+
paidOutsideCents: number;
|
|
27
|
+
/** `null` si no necesita atención. */
|
|
28
|
+
attention: CommissionPaymentAttentionEnum | null;
|
|
29
|
+
/** Código del último rechazo, p. ej. `USER_NOT_ACTIVE`. */
|
|
30
|
+
attentionCode: string | null;
|
|
31
|
+
/** Desde cuándo tiene esa atención. */
|
|
32
|
+
attentionSince: string | null;
|
|
33
|
+
updatedAt: string;
|
|
34
|
+
}
|
|
35
|
+
/** Lo que una regla aporta a un movimiento. */
|
|
36
|
+
export interface CommissionLedgerMovementRule {
|
|
37
|
+
ruleId: string;
|
|
38
|
+
payTiming: CommissionPayTimingEnum;
|
|
39
|
+
cents: number;
|
|
40
|
+
}
|
|
41
|
+
/** Un movimiento del libro. Nunca cambia. */
|
|
42
|
+
export interface CommissionLedgerMovement {
|
|
43
|
+
/** Determinista por origen: una reentrega no duplica el movimiento. */
|
|
44
|
+
movementKey: string;
|
|
45
|
+
kind: CommissionLedgerMovementKindEnum;
|
|
46
|
+
periodId: string;
|
|
47
|
+
retainedDeltaCents: number;
|
|
48
|
+
payableDeltaCents: number;
|
|
49
|
+
byRule: CommissionLedgerMovementRule[];
|
|
50
|
+
saleId: string | null;
|
|
51
|
+
adjustmentId: string | null;
|
|
52
|
+
/** `YYYY-MM` de la liberación mensual. */
|
|
53
|
+
releaseMonth: string | null;
|
|
54
|
+
/** Obligatorio en lo pagado por fuera. */
|
|
55
|
+
reason: string | null;
|
|
56
|
+
createdAt: string;
|
|
57
|
+
createdBy: string;
|
|
58
|
+
}
|
|
59
|
+
/** Un depósito a la wallet del vendedor. Solo cambia su estado. */
|
|
60
|
+
export interface CommissionPayment {
|
|
61
|
+
/** También es la llave de idempotencia del depósito. */
|
|
62
|
+
paymentId: string;
|
|
63
|
+
retailerId: string;
|
|
64
|
+
sellerId: string;
|
|
65
|
+
amountCents: number;
|
|
66
|
+
status: CommissionPaymentStatusEnum;
|
|
67
|
+
/** La wallet a la que se depositó. */
|
|
68
|
+
directoryId: string;
|
|
69
|
+
/** Folio del depósito; `null` mientras no se confirma. */
|
|
70
|
+
transactionId: string | null;
|
|
71
|
+
errorCode: string | null;
|
|
72
|
+
/** Lo que ve el vendedor en su wallet; máximo 120 caracteres. */
|
|
73
|
+
concept: string;
|
|
74
|
+
requestedAt: string;
|
|
75
|
+
resolvedAt: string | null;
|
|
76
|
+
resolvedBy: string | null;
|
|
77
|
+
/** Nota de quien lo revisó a mano. */
|
|
78
|
+
note: string | null;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Estado de cuenta para cobrarle a la cadena: los depósitos confirmados del periodo. Lo pagado por
|
|
82
|
+
* fuera no entra. Se genera una vez y no cambia.
|
|
83
|
+
*/
|
|
84
|
+
export interface CommissionChainStatement {
|
|
85
|
+
statementId: string;
|
|
86
|
+
retailerId: string;
|
|
87
|
+
period: CommissionBillingPeriodEnum;
|
|
88
|
+
/** `YYYY-MM-DD`, inclusive. */
|
|
89
|
+
startDate: string;
|
|
90
|
+
/** `YYYY-MM-DD`, inclusive. */
|
|
91
|
+
endDate: string;
|
|
92
|
+
accreditedCents: number;
|
|
93
|
+
paymentsCount: number;
|
|
94
|
+
sellersCount: number;
|
|
95
|
+
generatedAt: string;
|
|
96
|
+
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { ProductCategoryEnum } from '../../retailCatalog/enums/ProductCategoryEnum';
|
|
2
2
|
import type { ProductTierEnum } from '../../retailCatalog/enums/ProductTierEnum';
|
|
3
|
+
import type { CommissionBillingPeriodEnum } from '../enums/CommissionBillingPeriodEnum';
|
|
3
4
|
import type { CommissionBonusModeEnum } from '../enums/CommissionBonusModeEnum';
|
|
4
5
|
import type { CommissionCountByEnum } from '../enums/CommissionCountByEnum';
|
|
5
6
|
import type { CommissionMetricEnum } from '../enums/CommissionMetricEnum';
|
|
6
7
|
import type { CommissionOperatorEnum } from '../enums/CommissionOperatorEnum';
|
|
8
|
+
import type { CommissionPayTimingEnum } from '../enums/CommissionPayTimingEnum';
|
|
7
9
|
import type { CommissionRuleStatusEnum } from '../enums/CommissionRuleStatusEnum';
|
|
8
10
|
import type { CommissionWindowEnum } from '../enums/CommissionWindowEnum';
|
|
9
11
|
/**
|
|
@@ -75,6 +77,8 @@ export interface CommissionRule {
|
|
|
75
77
|
replacesOthers: boolean;
|
|
76
78
|
/** Si la regla cuenta piezas o ventas. Ausente = `PIECE`. */
|
|
77
79
|
countBy?: CommissionCountByEnum;
|
|
80
|
+
/** Cuándo se paga lo que gana. Ausente = `ON_CUT`. */
|
|
81
|
+
payTiming?: CommissionPayTimingEnum;
|
|
78
82
|
/** Sube en cada guardado. */
|
|
79
83
|
version: number;
|
|
80
84
|
/** La versión que está en el plan vigente. `null` si nunca se publicó. */
|
|
@@ -102,10 +106,19 @@ export interface CommissionPolicyCommissions {
|
|
|
102
106
|
editedBy: string;
|
|
103
107
|
editedAt: string;
|
|
104
108
|
}
|
|
105
|
-
/**
|
|
109
|
+
/** Política de pagos a la wallet de los vendedores. */
|
|
110
|
+
export interface CommissionPayoutPolicy {
|
|
111
|
+
/** Depositar las comisiones a la wallet. Apagado, todo se calcula y queda pendiente. */
|
|
112
|
+
walletDeposits: boolean;
|
|
113
|
+
/** Cada cuánto se le cobra a la cadena. */
|
|
114
|
+
chainBillingPeriod: CommissionBillingPeriodEnum;
|
|
115
|
+
}
|
|
116
|
+
/** Políticas de la cadena. `clawback` y `commissions` son `null` mientras la cadena no las guarda. */
|
|
106
117
|
export interface CommissionPolicies {
|
|
107
118
|
clawback: CommissionPolicyClawback | null;
|
|
108
119
|
commissions: CommissionPolicyCommissions | null;
|
|
120
|
+
/** Ausente = depósitos encendidos y cobro semanal. */
|
|
121
|
+
payouts?: CommissionPayoutPolicy;
|
|
109
122
|
}
|
|
110
123
|
/**
|
|
111
124
|
* El plan vigente: las reglas publicadas y las políticas, congeladas en un snapshot inmutable.
|