@fiado/type-kit 3.609.0 → 3.611.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/biometrics/BiometricEventTypeEnum.test.ts +35 -0
- package/_test_/unit/biometrics/BiometricVerificationResponse.test.ts +19 -0
- package/_test_/unit/kyc/KycProgressEnums.test.ts +20 -0
- package/_test_/unit/kyc/KycProofOfAddressCompletedV1.test.ts +24 -0
- package/_test_/unit/kyc/KycStepCompletedV1.test.ts +34 -0
- package/_test_/unit/kyc/KycVerificationChangedV1.test.ts +30 -0
- package/_test_/unit/kyc/KycVerificationProgressV1.test.ts +144 -0
- package/_test_/unit/loanCredit/customerReadDtos.test.ts +22 -0
- package/_test_/unit/retailLoan/contract.test.ts +5 -0
- package/_test_/unit/retailWizard/kycAttemptProgress.test.ts +131 -0
- package/bin/biometrics/dtos/responses/BiometricVerificationResponse.d.ts +2 -0
- package/bin/biometrics/dtos/responses/BiometricVerificationResponse.js +6 -0
- package/bin/biometrics/enums/BiometricEventTypeEnum.d.ts +7 -3
- package/bin/biometrics/enums/BiometricEventTypeEnum.js +6 -2
- package/bin/kyc/enums/KycFlowEnum.d.ts +5 -0
- package/bin/kyc/enums/KycFlowEnum.js +9 -0
- package/bin/kyc/enums/KycProgressStageEnum.d.ts +6 -0
- package/bin/kyc/enums/KycProgressStageEnum.js +10 -0
- package/bin/kyc/events/KycProofOfAddressCompletedV1.d.ts +2 -0
- package/bin/kyc/events/KycProofOfAddressCompletedV1.js +7 -0
- package/bin/kyc/events/KycStepCompletedV1.d.ts +5 -0
- package/bin/kyc/events/KycStepCompletedV1.js +14 -0
- package/bin/kyc/events/KycVerificationChangedV1.d.ts +2 -0
- package/bin/kyc/events/KycVerificationChangedV1.js +7 -0
- package/bin/kyc/events/KycVerificationProgressV1.d.ts +14 -0
- package/bin/kyc/events/KycVerificationProgressV1.js +63 -0
- package/bin/kyc/index.d.ts +3 -0
- package/bin/kyc/index.js +3 -0
- package/bin/loanCredit/dtos/responses/LoanCustomerCreditDocument.d.ts +9 -0
- package/bin/loanCredit/dtos/responses/LoanCustomerCreditDocument.js +2 -0
- package/bin/loanCredit/dtos/responses/LoanCustomerCreditDocumentUrl.d.ts +9 -0
- package/bin/loanCredit/dtos/responses/LoanCustomerCreditDocumentUrl.js +2 -0
- package/bin/loanCredit/index.d.ts +2 -0
- package/bin/loanCredit/index.js +2 -0
- package/bin/retailLoan/dtos/MyDocument.d.ts +2 -1
- package/bin/retailLoan/enums/MyDocumentGroupIdEnum.d.ts +2 -1
- package/bin/retailLoan/enums/MyDocumentGroupIdEnum.js +1 -0
- package/bin/retailLoan/enums/MyDocumentKindEnum.d.ts +2 -1
- package/bin/retailLoan/enums/MyDocumentKindEnum.js +1 -0
- package/bin/retailWizard/dtos/KycAttemptProgress.d.ts +14 -0
- package/bin/retailWizard/dtos/KycAttemptProgress.js +2 -0
- package/bin/retailWizard/dtos/KycStatusProgress.d.ts +7 -0
- package/bin/retailWizard/dtos/KycStatusProgress.js +2 -0
- package/bin/retailWizard/dtos/responses/KycPrepareResponse.d.ts +4 -0
- package/bin/retailWizard/dtos/responses/ProofOfAddressPrepareResponse.d.ts +2 -0
- package/bin/retailWizard/enums/KycAttemptStageEnum.d.ts +8 -0
- package/bin/retailWizard/enums/KycAttemptStageEnum.js +12 -0
- package/bin/retailWizard/index.d.ts +3 -0
- package/bin/retailWizard/index.js +3 -0
- package/package.json +1 -1
- package/src/biometrics/dtos/responses/BiometricVerificationResponse.ts +4 -0
- package/src/biometrics/enums/BiometricEventTypeEnum.ts +8 -2
- package/src/kyc/enums/KycFlowEnum.ts +5 -0
- package/src/kyc/enums/KycProgressStageEnum.ts +6 -0
- package/src/kyc/events/KycProofOfAddressCompletedV1.ts +4 -0
- package/src/kyc/events/KycStepCompletedV1.ts +9 -0
- package/src/kyc/events/KycVerificationChangedV1.ts +4 -0
- package/src/kyc/events/KycVerificationProgressV1.ts +33 -0
- package/src/kyc/index.ts +3 -0
- package/src/loanCredit/dtos/responses/LoanCustomerCreditDocument.ts +10 -0
- package/src/loanCredit/dtos/responses/LoanCustomerCreditDocumentUrl.ts +10 -0
- package/src/loanCredit/index.ts +2 -0
- package/src/retailLoan/dtos/MyDocument.ts +2 -1
- package/src/retailLoan/enums/MyDocumentGroupIdEnum.ts +1 -0
- package/src/retailLoan/enums/MyDocumentKindEnum.ts +1 -0
- package/src/retailWizard/dtos/KycAttemptProgress.ts +15 -0
- package/src/retailWizard/dtos/KycStatusProgress.ts +8 -0
- package/src/retailWizard/dtos/responses/KycPrepareResponse.ts +4 -0
- package/src/retailWizard/dtos/responses/ProofOfAddressPrepareResponse.ts +2 -0
- package/src/retailWizard/enums/KycAttemptStageEnum.ts +8 -0
- package/src/retailWizard/index.ts +3 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { plainToInstance } from 'class-transformer';
|
|
3
|
+
import { validate } from 'class-validator';
|
|
4
|
+
import { BiometricEventTypeEnum, BiometricVerificationChangedV1 } from '../../../src/biometrics/index';
|
|
5
|
+
|
|
6
|
+
const base = {
|
|
7
|
+
eventId: 'evt-1',
|
|
8
|
+
biometricVerificationId: 'bio-1',
|
|
9
|
+
provider: 'METAMAP',
|
|
10
|
+
eventType: 'VERIFICATION_STARTED',
|
|
11
|
+
occurredAt: '2026-10-28T10:05:00.000Z',
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
describe('BiometricEventTypeEnum', () => {
|
|
15
|
+
it('declara los seis eventos que traduce el webhook', () => {
|
|
16
|
+
expect(Object.values(BiometricEventTypeEnum)).toEqual([
|
|
17
|
+
'VERIFICATION_STARTED',
|
|
18
|
+
'STEP_COMPLETED',
|
|
19
|
+
'VERIFICATION_COMPLETED',
|
|
20
|
+
'VERIFICATION_EXPIRED',
|
|
21
|
+
'INPUTS_COMPLETED',
|
|
22
|
+
'UPDATED',
|
|
23
|
+
]);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it.each([BiometricEventTypeEnum.INPUTS_COMPLETED, BiometricEventTypeEnum.UPDATED])(
|
|
27
|
+
'BiometricVerificationChangedV1 acepta el eventType %s',
|
|
28
|
+
async (eventType) => {
|
|
29
|
+
const dto = plainToInstance(BiometricVerificationChangedV1, { ...base, eventType }, { excludeExtraneousValues: true });
|
|
30
|
+
|
|
31
|
+
expect(await validate(dto as object)).toHaveLength(0);
|
|
32
|
+
expect(dto.eventType).toBe(eventType);
|
|
33
|
+
},
|
|
34
|
+
);
|
|
35
|
+
});
|
|
@@ -60,4 +60,23 @@ describe('BiometricVerificationResponse', () => {
|
|
|
60
60
|
expect(instancia(base).referenceImageSource).toBe(BiometricReferenceSourceEnum.KYC_SELFIE);
|
|
61
61
|
});
|
|
62
62
|
});
|
|
63
|
+
|
|
64
|
+
// Cuándo el cliente terminó de subir su información; lo usa el wizard para INPUTS_COMPLETED.
|
|
65
|
+
describe('inputsCompletedAt', () => {
|
|
66
|
+
it('es opcional', async () => {
|
|
67
|
+
expect(await propiedadesConError(base)).toEqual([]);
|
|
68
|
+
expect(instancia(base).inputsCompletedAt).toBeUndefined();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('acepta ISO-8601 UTC y sobrevive al plainToInstance', async () => {
|
|
72
|
+
const raw = { ...base, inputsCompletedAt: '2026-10-28T10:07:00.000Z' };
|
|
73
|
+
|
|
74
|
+
expect(await propiedadesConError(raw)).toEqual([]);
|
|
75
|
+
expect(instancia(raw).inputsCompletedAt).toBe('2026-10-28T10:07:00.000Z');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('rechaza una fecha que no es ISO-8601', async () => {
|
|
79
|
+
expect(await propiedadesConError({ ...base, inputsCompletedAt: '28/10/2026' })).toContain('inputsCompletedAt');
|
|
80
|
+
});
|
|
81
|
+
});
|
|
63
82
|
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { KycFlowEnum, KycProgressStageEnum } from '../../../src/kyc/index';
|
|
3
|
+
import { KycMetadataStepEnum } from '../../../src/retailWizard/index';
|
|
4
|
+
|
|
5
|
+
describe('KycProgressStageEnum', () => {
|
|
6
|
+
it('declara solo las tres etapas que publica el webhook', () => {
|
|
7
|
+
expect(Object.values(KycProgressStageEnum)).toEqual(['STARTED', 'INPUTS_COMPLETED', 'EXPIRED']);
|
|
8
|
+
});
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
describe('KycFlowEnum', () => {
|
|
12
|
+
it('declara los dos flujos de Metamap que salen del wizard', () => {
|
|
13
|
+
expect(Object.values(KycFlowEnum)).toEqual(['IDENTITY', 'PROOF_OF_ADDRESS']);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
// El webhook deriva flow de metadata.kycStep: si los valores divergen, el comprobante se rutea como identidad.
|
|
17
|
+
it('PROOF_OF_ADDRESS coincide con el kycStep de la metadata', () => {
|
|
18
|
+
expect(KycFlowEnum.PROOF_OF_ADDRESS).toBe(KycMetadataStepEnum.PROOF_OF_ADDRESS);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -192,4 +192,28 @@ describe('KycProofOfAddressCompletedV1', () => {
|
|
|
192
192
|
].sort(),
|
|
193
193
|
);
|
|
194
194
|
});
|
|
195
|
+
|
|
196
|
+
// ── Intento (estados MetaMap/QR) ──────────────────────────────────────────
|
|
197
|
+
|
|
198
|
+
describe('attemptId', () => {
|
|
199
|
+
it('acepta y expone attemptId', async () => {
|
|
200
|
+
const dto = instancia({ ...base, attemptId: 'att-1' });
|
|
201
|
+
|
|
202
|
+
expect(await validate(dto as object)).toHaveLength(0);
|
|
203
|
+
expect(dto.attemptId).toBe('att-1');
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('valida sin attemptId (compatibilidad hacia atrás)', async () => {
|
|
207
|
+
const dto = instancia(base);
|
|
208
|
+
|
|
209
|
+
expect(await validate(dto as object)).toHaveLength(0);
|
|
210
|
+
expect(dto.attemptId).toBeUndefined();
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('rechaza attemptId vacío', async () => {
|
|
214
|
+
const errors = await validate(instancia({ ...base, attemptId: '' }) as object);
|
|
215
|
+
|
|
216
|
+
expect(errors.map((e) => e.property)).toContain('attemptId');
|
|
217
|
+
});
|
|
218
|
+
});
|
|
195
219
|
});
|
|
@@ -2,6 +2,7 @@ import 'reflect-metadata';
|
|
|
2
2
|
import { plainToInstance } from 'class-transformer';
|
|
3
3
|
import { validate } from 'class-validator';
|
|
4
4
|
import {
|
|
5
|
+
KycFlowEnum,
|
|
5
6
|
KycStepCompletedV1,
|
|
6
7
|
KycVerificationChangedV1,
|
|
7
8
|
KycSubCheckNameEnum,
|
|
@@ -236,4 +237,37 @@ describe('KycStepCompletedV1', () => {
|
|
|
236
237
|
);
|
|
237
238
|
expect(Object.values(KycWatchlistStatusEnum)).toEqual(['CLEAR', 'HIT']);
|
|
238
239
|
});
|
|
240
|
+
|
|
241
|
+
// ── Intento y flujo (estados MetaMap/QR) ──────────────────────────────────
|
|
242
|
+
|
|
243
|
+
describe('attemptId y flow', () => {
|
|
244
|
+
it('acepta y expone attemptId y flow', async () => {
|
|
245
|
+
const dto = instancia({ ...base, attemptId: 'att-1', flow: 'IDENTITY' });
|
|
246
|
+
|
|
247
|
+
expect(await validate(dto as object)).toHaveLength(0);
|
|
248
|
+
expect(dto.attemptId).toBe('att-1');
|
|
249
|
+
expect(dto.flow).toBe(KycFlowEnum.IDENTITY);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// Mensajes en vuelo y sesiones previas al despliegue no los traen.
|
|
253
|
+
it('valida sin attemptId ni flow', async () => {
|
|
254
|
+
const dto = instancia(base);
|
|
255
|
+
|
|
256
|
+
expect(await validate(dto as object)).toHaveLength(0);
|
|
257
|
+
expect(dto.attemptId).toBeUndefined();
|
|
258
|
+
expect(dto.flow).toBeUndefined();
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('rechaza attemptId vacío', async () => {
|
|
262
|
+
const errors = await validate(instancia({ ...base, attemptId: '' }) as object);
|
|
263
|
+
|
|
264
|
+
expect(errors.map((e) => e.property)).toContain('attemptId');
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it.each(['FACEMATCH', 'identity'])('rechaza el flow %s', async (flow) => {
|
|
268
|
+
const errors = await validate(instancia({ ...base, flow }) as object);
|
|
269
|
+
|
|
270
|
+
expect(errors.map((e) => e.property)).toContain('flow');
|
|
271
|
+
});
|
|
272
|
+
});
|
|
239
273
|
});
|
|
@@ -152,4 +152,34 @@ describe('KycVerificationChangedV1', () => {
|
|
|
152
152
|
|
|
153
153
|
expect(errors.map((e) => e.property)).toContain('updateDate');
|
|
154
154
|
});
|
|
155
|
+
|
|
156
|
+
// Estados MetaMap/QR: el cierre trae el intento para que el wizard descarte los ajenos.
|
|
157
|
+
describe('attemptId', () => {
|
|
158
|
+
const closing = {
|
|
159
|
+
verificationId: 'ver-1', directoryId: 'dir-1', peopleId: 'ppl-1',
|
|
160
|
+
verificationStatus: 'COMPLETED', verificationResult: 'VERIFIED',
|
|
161
|
+
updateDate: '2026-10-28T10:05:00.000Z',
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
it('acepta y expone attemptId', async () => {
|
|
165
|
+
const dto = plainToInstance(KycVerificationChangedV1, { ...closing, attemptId: 'att-1' }, { excludeExtraneousValues: true });
|
|
166
|
+
|
|
167
|
+
expect(await validate(dto as object)).toHaveLength(0);
|
|
168
|
+
expect(dto.attemptId).toBe('att-1');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('valida sin attemptId (compatibilidad hacia atrás)', async () => {
|
|
172
|
+
const dto = plainToInstance(KycVerificationChangedV1, closing, { excludeExtraneousValues: true });
|
|
173
|
+
|
|
174
|
+
expect(await validate(dto as object)).toHaveLength(0);
|
|
175
|
+
expect(dto.attemptId).toBeUndefined();
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('rechaza attemptId vacío', async () => {
|
|
179
|
+
const dto = plainToInstance(KycVerificationChangedV1, { ...closing, attemptId: '' }, { excludeExtraneousValues: true });
|
|
180
|
+
const errors = await validate(dto as object);
|
|
181
|
+
|
|
182
|
+
expect(errors.map((e) => e.property)).toContain('attemptId');
|
|
183
|
+
});
|
|
184
|
+
});
|
|
155
185
|
});
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { plainToInstance } from 'class-transformer';
|
|
3
|
+
import { validate } from 'class-validator';
|
|
4
|
+
import {
|
|
5
|
+
KycFlowEnum,
|
|
6
|
+
KycProgressStageEnum,
|
|
7
|
+
KycProofOfAddressCompletedV1,
|
|
8
|
+
KycStepCompletedV1,
|
|
9
|
+
KycVerificationChangedV1,
|
|
10
|
+
KycVerificationProgressV1,
|
|
11
|
+
} from '../../../src/kyc/index';
|
|
12
|
+
|
|
13
|
+
/** Base válida; cada caso pisa solo el campo que prueba. */
|
|
14
|
+
const base = {
|
|
15
|
+
eventType: 'KycVerificationProgressV1',
|
|
16
|
+
stage: 'STARTED',
|
|
17
|
+
flow: 'IDENTITY',
|
|
18
|
+
verificationId: 'ver-1',
|
|
19
|
+
directoryId: 'dir-1',
|
|
20
|
+
attemptId: 'att-1',
|
|
21
|
+
occurredAt: '2026-10-28T10:05:00.000Z',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const toDto = (raw: Record<string, unknown>): KycVerificationProgressV1 =>
|
|
25
|
+
plainToInstance(KycVerificationProgressV1, raw, { excludeExtraneousValues: true });
|
|
26
|
+
|
|
27
|
+
const errorProps = async (raw: Record<string, unknown>): Promise<string[]> =>
|
|
28
|
+
(await validate(toDto(raw) as object)).map((e) => e.property);
|
|
29
|
+
|
|
30
|
+
describe('KycVerificationProgressV1', () => {
|
|
31
|
+
it('valida el shape que publica el webhook', async () => {
|
|
32
|
+
const dto = toDto(base);
|
|
33
|
+
|
|
34
|
+
expect(await errorProps(base)).toEqual([]);
|
|
35
|
+
expect(dto.stage).toBe(KycProgressStageEnum.STARTED);
|
|
36
|
+
expect(dto.flow).toBe(KycFlowEnum.IDENTITY);
|
|
37
|
+
expect(dto.attemptId).toBe('att-1');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it.each(Object.values(KycProgressStageEnum))('acepta la etapa %s', async (stage) => {
|
|
41
|
+
expect(await errorProps({ ...base, stage })).toEqual([]);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it.each(Object.values(KycFlowEnum))('acepta el flujo %s', async (flow) => {
|
|
45
|
+
expect(await errorProps({ ...base, flow })).toEqual([]);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Sesiones creadas antes del despliegue: la metadata no trae attemptId.
|
|
49
|
+
it('valida sin attemptId', async () => {
|
|
50
|
+
const { attemptId: _omitted, ...withoutAttempt } = base;
|
|
51
|
+
|
|
52
|
+
expect(await errorProps(withoutAttempt)).toEqual([]);
|
|
53
|
+
expect(toDto(withoutAttempt).attemptId).toBeUndefined();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('rechaza attemptId vacío', async () => {
|
|
57
|
+
expect(await errorProps({ ...base, attemptId: '' })).toContain('attemptId');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe('eventType', () => {
|
|
61
|
+
it('rechaza el evento sin eventType', async () => {
|
|
62
|
+
const { eventType: _omitted, ...withoutType } = base;
|
|
63
|
+
|
|
64
|
+
expect(await errorProps(withoutType)).toContain('eventType');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('rechaza el eventType de otro evento', async () => {
|
|
68
|
+
expect(await errorProps({ ...base, eventType: 'KycStepCompletedV1' })).toContain('eventType');
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// AWAITING_START y RESULT son etapas del wizard, nunca llegan por evento.
|
|
73
|
+
it.each(['AWAITING_START', 'RESULT', 'COMPLETED'])('rechaza la etapa %s', async (stage) => {
|
|
74
|
+
expect(await errorProps({ ...base, stage })).toContain('stage');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// FACEMATCH no pasa por este evento: su avance sale del GET de biometrics.
|
|
78
|
+
it('rechaza el flujo FACEMATCH', async () => {
|
|
79
|
+
expect(await errorProps({ ...base, flow: 'FACEMATCH' })).toContain('flow');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it.each(['verificationId', 'directoryId'])('rechaza %s vacío', async (field) => {
|
|
83
|
+
expect(await errorProps({ ...base, [field]: '' })).toContain(field);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it.each(['stage', 'flow', 'verificationId', 'directoryId', 'occurredAt'])('rechaza el evento sin %s', async (field) => {
|
|
87
|
+
const raw: Record<string, unknown> = { ...base };
|
|
88
|
+
delete raw[field];
|
|
89
|
+
|
|
90
|
+
expect(await errorProps(raw)).toContain(field);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe('occurredAt', () => {
|
|
94
|
+
it.each([
|
|
95
|
+
['offset positivo en vez de Z', '2026-10-28T15:05:00+05:00'],
|
|
96
|
+
['solo fecha, sin hora', '2026-10-28'],
|
|
97
|
+
['sin zona horaria', '2026-10-28T10:05:00'],
|
|
98
|
+
['formato local', '28/10/2026'],
|
|
99
|
+
])('rechaza occurredAt %s', async (_label, value) => {
|
|
100
|
+
expect(await errorProps({ ...base, occurredAt: value })).toContain('occurredAt');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it.each([
|
|
104
|
+
['con milisegundos', '2026-10-28T10:05:00.000Z'],
|
|
105
|
+
['sin milisegundos', '2026-10-28T10:05:00Z'],
|
|
106
|
+
])('acepta occurredAt en UTC %s', async (_label, value) => {
|
|
107
|
+
expect(await errorProps({ ...base, occurredAt: value })).toEqual([]);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Comparte RetailKycInboundQueue con otros tres eventos: ninguno se puede hacer pasar por otro.
|
|
112
|
+
describe('convivencia en RetailKycInboundQueue', () => {
|
|
113
|
+
it('un avance NO valida como estación', async () => {
|
|
114
|
+
const errors = await validate(plainToInstance(KycStepCompletedV1, base, { excludeExtraneousValues: true }) as object);
|
|
115
|
+
|
|
116
|
+
expect(errors.map((e) => e.property)).toEqual(expect.arrayContaining(['eventType', 'step', 'status']));
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('un avance NO valida como cierre', async () => {
|
|
120
|
+
const errors = await validate(plainToInstance(KycVerificationChangedV1, base, { excludeExtraneousValues: true }) as object);
|
|
121
|
+
|
|
122
|
+
expect(errors.map((e) => e.property)).toEqual(
|
|
123
|
+
expect.arrayContaining(['peopleId', 'verificationStatus', 'verificationResult', 'updateDate']),
|
|
124
|
+
);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('un avance NO valida como comprobante', async () => {
|
|
128
|
+
const errors = await validate(
|
|
129
|
+
plainToInstance(KycProofOfAddressCompletedV1, base, { excludeExtraneousValues: true }) as object,
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
expect(errors.map((e) => e.property)).toEqual(expect.arrayContaining(['eventType', 'status', 'addressPersisted']));
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('una estación NO valida como avance', async () => {
|
|
136
|
+
const station = {
|
|
137
|
+
eventType: 'KycStepCompletedV1', verificationId: 'ver-1', directoryId: 'dir-1',
|
|
138
|
+
step: 'facematch', status: 'PASS', occurredAt: '2026-10-28T10:05:00.000Z',
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
expect(await errorProps(station)).toEqual(expect.arrayContaining(['eventType', 'stage', 'flow']));
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
});
|
|
@@ -3,6 +3,8 @@ import {
|
|
|
3
3
|
ContractTypeEnum,
|
|
4
4
|
CreditSaleContext,
|
|
5
5
|
LoanContractDownloadUrl,
|
|
6
|
+
LoanCustomerCreditDocument,
|
|
7
|
+
LoanCustomerCreditDocumentUrl,
|
|
6
8
|
LoanPaymentListItem,
|
|
7
9
|
LoanPaymentListResponse,
|
|
8
10
|
} from '../../../src/loanCredit/index';
|
|
@@ -68,4 +70,24 @@ describe('loanCredit — DTOs de lectura de cliente (I1)', () => {
|
|
|
68
70
|
};
|
|
69
71
|
expect(url.type).toBe(ContractTypeEnum.CREDIT);
|
|
70
72
|
});
|
|
73
|
+
|
|
74
|
+
it('un documento del ciclo de vida sin PDF todavía no tiene fecha de generación', () => {
|
|
75
|
+
const pending: LoanCustomerCreditDocument = {
|
|
76
|
+
documentType: ContractTypeEnum.PAYMENT_RECEIPT,
|
|
77
|
+
documentKey: '01K8Z5Q2J6V3W4X5Y6Z7A8B9C0',
|
|
78
|
+
generatedAt: null,
|
|
79
|
+
};
|
|
80
|
+
expect(pending.generatedAt).toBeNull();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('la URL de un documento del ciclo de vida trae su tipo, su llave y su vencimiento', () => {
|
|
84
|
+
const url: LoanCustomerCreditDocumentUrl = {
|
|
85
|
+
creditId: 'CR-1',
|
|
86
|
+
documentType: ContractTypeEnum.ACCOUNT_STATEMENT,
|
|
87
|
+
documentKey: '2026-09',
|
|
88
|
+
url: 'https://s3.example/statement.pdf',
|
|
89
|
+
expiresAt: '2026-10-21T15:00:00.000Z',
|
|
90
|
+
};
|
|
91
|
+
expect(url.documentKey).toBe('2026-09');
|
|
92
|
+
});
|
|
71
93
|
});
|
|
@@ -18,6 +18,11 @@ describe('retailLoan — contrato app ↔ retail-loan-business (I1)', () => {
|
|
|
18
18
|
it('los ids de grupo de documentos son los que espera el módulo de la app', () => {
|
|
19
19
|
expect(MyDocumentGroupIdEnum.CONTRACT).toBe('contract');
|
|
20
20
|
expect(MyDocumentGroupIdEnum.MONTHLY).toBe('monthly');
|
|
21
|
+
expect(MyDocumentGroupIdEnum.PAYMENT_RECEIPTS).toBe('receipts');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('un comprobante de pago es su propio tipo de documento', () => {
|
|
25
|
+
expect(MyDocumentKindEnum.PAYMENT_RECEIPT).toBe('PAYMENT_RECEIPT');
|
|
21
26
|
});
|
|
22
27
|
|
|
23
28
|
it('el detalle del crédito va en pesos y con frecuencia semanal', () => {
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { KycProgressStageEnum } from '../../../src/kyc/index';
|
|
3
|
+
import { KycModalityEnum } from '../../../src/retailCustomer/enums/KycModalityEnum';
|
|
4
|
+
import {
|
|
5
|
+
KycAttemptStageEnum,
|
|
6
|
+
KycMetadataSourceEnum,
|
|
7
|
+
KycMetadataStepEnum,
|
|
8
|
+
type KycAttemptProgress,
|
|
9
|
+
type KycPrepareResponse,
|
|
10
|
+
type KycSdkMetadata,
|
|
11
|
+
type KycStatusProgress,
|
|
12
|
+
type ProofOfAddressPrepareResponse,
|
|
13
|
+
} from '../../../src/retailWizard/index';
|
|
14
|
+
|
|
15
|
+
const qrAttempt: KycAttemptProgress = {
|
|
16
|
+
attemptId: 'att-1',
|
|
17
|
+
modality: KycModalityEnum.CUSTOMER_LINK,
|
|
18
|
+
link: 'https://sk.fiado.test/abc',
|
|
19
|
+
stage: KycAttemptStageEnum.STARTED,
|
|
20
|
+
stageAt: '2026-10-28T10:05:00.000Z',
|
|
21
|
+
attemptStartedAt: '2026-10-28T10:00:00.000Z',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const tabletAttempt: KycAttemptProgress = {
|
|
25
|
+
attemptId: 'att-2',
|
|
26
|
+
modality: KycModalityEnum.STORE_DEVICE,
|
|
27
|
+
stage: KycAttemptStageEnum.AWAITING_START,
|
|
28
|
+
stageAt: '2026-10-28T10:00:00.000Z',
|
|
29
|
+
attemptStartedAt: '2026-10-28T10:00:00.000Z',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
describe('KycAttemptStageEnum', () => {
|
|
33
|
+
it('declara las cinco etapas en orden de rango', () => {
|
|
34
|
+
expect(Object.values(KycAttemptStageEnum)).toEqual([
|
|
35
|
+
'AWAITING_START', 'STARTED', 'INPUTS_COMPLETED', 'EXPIRED', 'RESULT',
|
|
36
|
+
]);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// El wizard copia la etapa del evento al intento por valor.
|
|
40
|
+
it.each(Object.values(KycProgressStageEnum))('incluye la etapa de evento %s', (stage) => {
|
|
41
|
+
expect(Object.values(KycAttemptStageEnum)).toContain(stage);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('KycAttemptProgress', () => {
|
|
46
|
+
it('lleva link en QR y lo omite en tablet', () => {
|
|
47
|
+
expect(qrAttempt.link).toBe('https://sk.fiado.test/abc');
|
|
48
|
+
expect(tabletAttempt.link).toBeUndefined();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('solo admite las modalidades de prepare', () => {
|
|
52
|
+
const manual: KycAttemptProgress = {
|
|
53
|
+
...tabletAttempt,
|
|
54
|
+
// @ts-expect-error MANUAL no es una modalidad de prepare
|
|
55
|
+
modality: KycModalityEnum.MANUAL,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
expect(manual.modality).toBe('MANUAL');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// El tope de 25 min del front se mide contra la fecha del prepare, no contra el montaje.
|
|
62
|
+
it('exige attemptStartedAt', () => {
|
|
63
|
+
const { attemptStartedAt: _omitted, ...rest } = qrAttempt;
|
|
64
|
+
// @ts-expect-error attemptStartedAt es obligatorio
|
|
65
|
+
const withoutStart: KycAttemptProgress = rest;
|
|
66
|
+
|
|
67
|
+
expect(qrAttempt.attemptStartedAt).toBe('2026-10-28T10:00:00.000Z');
|
|
68
|
+
expect(withoutStart.attemptStartedAt).toBeUndefined();
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe('KycStatusProgress', () => {
|
|
73
|
+
it('trae un intento o null por cada flujo', () => {
|
|
74
|
+
const progress: KycStatusProgress = { identity: qrAttempt, facematch: null, proofOfAddress: tabletAttempt };
|
|
75
|
+
|
|
76
|
+
expect(Object.keys(progress).sort()).toEqual(['facematch', 'identity', 'proofOfAddress']);
|
|
77
|
+
expect(progress.facematch).toBeNull();
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe('KycSdkMetadata', () => {
|
|
82
|
+
it('acepta attemptId junto a source y kycStep', () => {
|
|
83
|
+
const metadata: KycSdkMetadata = {
|
|
84
|
+
directoryId: 'dir-1',
|
|
85
|
+
typeOfDirectoryId: 'PHONE',
|
|
86
|
+
peopleId: 'ppl-1',
|
|
87
|
+
countryId: 'MX',
|
|
88
|
+
phoneNumber: '5512345678',
|
|
89
|
+
source: KycMetadataSourceEnum.SUREKEEP_WIZARD,
|
|
90
|
+
kycStep: KycMetadataStepEnum.PROOF_OF_ADDRESS,
|
|
91
|
+
attemptId: 'att-1',
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
expect(metadata.attemptId).toBe('att-1');
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// El front liga el SDK y los callbacks al attemptId que devuelve el prepare.
|
|
99
|
+
describe('attemptId en las respuestas de prepare', () => {
|
|
100
|
+
// Sin intento creado (alreadyVerified o bypass de dev) no hay attemptId.
|
|
101
|
+
it('KycPrepareResponse lleva attemptId al preparar y lo omite sin intento', () => {
|
|
102
|
+
const prepared: KycPrepareResponse = {
|
|
103
|
+
modality: KycModalityEnum.CUSTOMER_LINK,
|
|
104
|
+
shortUrl: 'https://sk.fiado.test/abc',
|
|
105
|
+
attemptId: 'att-1',
|
|
106
|
+
};
|
|
107
|
+
const alreadyVerified: KycPrepareResponse = {
|
|
108
|
+
modality: KycModalityEnum.CUSTOMER_LINK,
|
|
109
|
+
alreadyVerified: true,
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
expect(prepared.attemptId).toBe('att-1');
|
|
113
|
+
expect(alreadyVerified.attemptId).toBeUndefined();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// Con domicilio ya registrado no se crea intento, por eso no hay attemptId.
|
|
117
|
+
it('ProofOfAddressPrepareResponse lleva attemptId al preparar y lo omite con alreadyHasAddress', () => {
|
|
118
|
+
const prepared: ProofOfAddressPrepareResponse = {
|
|
119
|
+
modality: KycModalityEnum.STORE_DEVICE,
|
|
120
|
+
clientId: 'client-1',
|
|
121
|
+
attemptId: 'att-3',
|
|
122
|
+
};
|
|
123
|
+
const alreadyHasAddress: ProofOfAddressPrepareResponse = {
|
|
124
|
+
modality: KycModalityEnum.STORE_DEVICE,
|
|
125
|
+
alreadyHasAddress: true,
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
expect(prepared.attemptId).toBe('att-3');
|
|
129
|
+
expect(alreadyHasAddress.attemptId).toBeUndefined();
|
|
130
|
+
});
|
|
131
|
+
});
|
|
@@ -91,6 +91,8 @@ export declare class BiometricVerificationResponse {
|
|
|
91
91
|
updatedAt: string;
|
|
92
92
|
/** Cuándo vence la entrega. Es lo que el `GET` compara para persistir `EXPIRED` (DEC-015). */
|
|
93
93
|
expiresAt: string;
|
|
94
|
+
/** Cuándo el usuario terminó de subir su información. ISO-8601 UTC con `Z`; ausente si aún no. */
|
|
95
|
+
inputsCompletedAt?: string;
|
|
94
96
|
/** Cuándo alcanzó un estado terminal. Ausente mientras siga viva. */
|
|
95
97
|
completedAt?: string;
|
|
96
98
|
}
|
|
@@ -129,6 +129,12 @@ __decorate([
|
|
|
129
129
|
(0, class_validator_1.IsISO8601)({ strict: true }),
|
|
130
130
|
__metadata("design:type", String)
|
|
131
131
|
], BiometricVerificationResponse.prototype, "expiresAt", void 0);
|
|
132
|
+
__decorate([
|
|
133
|
+
(0, class_transformer_1.Expose)(),
|
|
134
|
+
(0, class_validator_1.IsOptional)(),
|
|
135
|
+
(0, class_validator_1.IsISO8601)({ strict: true }),
|
|
136
|
+
__metadata("design:type", String)
|
|
137
|
+
], BiometricVerificationResponse.prototype, "inputsCompletedAt", void 0);
|
|
132
138
|
__decorate([
|
|
133
139
|
(0, class_transformer_1.Expose)(),
|
|
134
140
|
(0, class_validator_1.IsOptional)(),
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* lo que el consumer usa para decidir la transición de estado.
|
|
4
4
|
*
|
|
5
5
|
* Es el vocabulario NUESTRO, no el de Metamap: `kyc-metamap-webhook` traduce sus `eventName` a
|
|
6
|
-
* estos
|
|
7
|
-
* el día que entre otro proveedor traduce a los mismos
|
|
6
|
+
* estos valores antes de publicar. Así el consumer no aprende el catálogo de un proveedor externo, y
|
|
7
|
+
* el día que entre otro proveedor traduce a los mismos valores.
|
|
8
8
|
*
|
|
9
9
|
* `biometrics-business` — Entrega 1.
|
|
10
10
|
*/
|
|
@@ -24,5 +24,9 @@ export declare enum BiometricEventTypeEnum {
|
|
|
24
24
|
* ⚠️ **No se depende de este evento para expirar.** El `GET` persiste `EXPIRED` comparando
|
|
25
25
|
* contra `expiresAt` (DEC-015), así que una verificación vence aunque este evento nunca llegue.
|
|
26
26
|
*/
|
|
27
|
-
VERIFICATION_EXPIRED = "VERIFICATION_EXPIRED"
|
|
27
|
+
VERIFICATION_EXPIRED = "VERIFICATION_EXPIRED",
|
|
28
|
+
/** El usuario terminó de subir su información. Sigue en `IN_PROGRESS` y graba `inputsCompletedAt`. */
|
|
29
|
+
INPUTS_COMPLETED = "INPUTS_COMPLETED",
|
|
30
|
+
/** El proveedor cambió el veredicto (revisión manual). Se trata igual que `VERIFICATION_COMPLETED`. */
|
|
31
|
+
UPDATED = "UPDATED"
|
|
28
32
|
}
|
|
@@ -6,8 +6,8 @@ exports.BiometricEventTypeEnum = void 0;
|
|
|
6
6
|
* lo que el consumer usa para decidir la transición de estado.
|
|
7
7
|
*
|
|
8
8
|
* Es el vocabulario NUESTRO, no el de Metamap: `kyc-metamap-webhook` traduce sus `eventName` a
|
|
9
|
-
* estos
|
|
10
|
-
* el día que entre otro proveedor traduce a los mismos
|
|
9
|
+
* estos valores antes de publicar. Así el consumer no aprende el catálogo de un proveedor externo, y
|
|
10
|
+
* el día que entre otro proveedor traduce a los mismos valores.
|
|
11
11
|
*
|
|
12
12
|
* `biometrics-business` — Entrega 1.
|
|
13
13
|
*/
|
|
@@ -29,4 +29,8 @@ var BiometricEventTypeEnum;
|
|
|
29
29
|
* contra `expiresAt` (DEC-015), así que una verificación vence aunque este evento nunca llegue.
|
|
30
30
|
*/
|
|
31
31
|
BiometricEventTypeEnum["VERIFICATION_EXPIRED"] = "VERIFICATION_EXPIRED";
|
|
32
|
+
/** El usuario terminó de subir su información. Sigue en `IN_PROGRESS` y graba `inputsCompletedAt`. */
|
|
33
|
+
BiometricEventTypeEnum["INPUTS_COMPLETED"] = "INPUTS_COMPLETED";
|
|
34
|
+
/** El proveedor cambió el veredicto (revisión manual). Se trata igual que `VERIFICATION_COMPLETED`. */
|
|
35
|
+
BiometricEventTypeEnum["UPDATED"] = "UPDATED";
|
|
32
36
|
})(BiometricEventTypeEnum || (exports.BiometricEventTypeEnum = BiometricEventTypeEnum = {}));
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.KycFlowEnum = void 0;
|
|
4
|
+
/** Flujo de Metamap al que pertenece un evento; el webhook lo deriva de `metadata.kycStep`. */
|
|
5
|
+
var KycFlowEnum;
|
|
6
|
+
(function (KycFlowEnum) {
|
|
7
|
+
KycFlowEnum["IDENTITY"] = "IDENTITY";
|
|
8
|
+
KycFlowEnum["PROOF_OF_ADDRESS"] = "PROOF_OF_ADDRESS";
|
|
9
|
+
})(KycFlowEnum || (exports.KycFlowEnum = KycFlowEnum = {}));
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.KycProgressStageEnum = void 0;
|
|
4
|
+
/** Etapa de avance de una verificación de Metamap, tal como la publica `kyc-metamap-webhook`. */
|
|
5
|
+
var KycProgressStageEnum;
|
|
6
|
+
(function (KycProgressStageEnum) {
|
|
7
|
+
KycProgressStageEnum["STARTED"] = "STARTED";
|
|
8
|
+
KycProgressStageEnum["INPUTS_COMPLETED"] = "INPUTS_COMPLETED";
|
|
9
|
+
KycProgressStageEnum["EXPIRED"] = "EXPIRED";
|
|
10
|
+
})(KycProgressStageEnum || (exports.KycProgressStageEnum = KycProgressStageEnum = {}));
|
|
@@ -46,4 +46,6 @@ export declare class KycProofOfAddressCompletedV1 {
|
|
|
46
46
|
* truena. Normalizar a UTC es trabajo del webhook; este candado lo manda al DLQ si deja de hacerlo.
|
|
47
47
|
*/
|
|
48
48
|
occurredAt: string;
|
|
49
|
+
/** Intento del wizard que originó el comprobante; el consumidor descarta los que no son el vigente. */
|
|
50
|
+
attemptId?: string;
|
|
49
51
|
}
|
|
@@ -73,3 +73,10 @@ __decorate([
|
|
|
73
73
|
}),
|
|
74
74
|
__metadata("design:type", String)
|
|
75
75
|
], KycProofOfAddressCompletedV1.prototype, "occurredAt", void 0);
|
|
76
|
+
__decorate([
|
|
77
|
+
(0, class_transformer_1.Expose)(),
|
|
78
|
+
(0, class_validator_1.IsOptional)(),
|
|
79
|
+
(0, class_validator_1.IsString)(),
|
|
80
|
+
(0, class_validator_1.IsNotEmpty)(),
|
|
81
|
+
__metadata("design:type", String)
|
|
82
|
+
], KycProofOfAddressCompletedV1.prototype, "attemptId", void 0);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { KycFlowEnum } from '../enums/KycFlowEnum';
|
|
1
2
|
import { KycSubCheckNameEnum } from '../enums/KycSubCheckNameEnum';
|
|
2
3
|
import { KycSubCheckStatusEnum } from '../enums/KycSubCheckStatusEnum';
|
|
3
4
|
import { KycWatchlistStatusEnum } from '../enums/KycWatchlistStatusEnum';
|
|
@@ -82,4 +83,8 @@ export declare class KycStepCompletedV1 {
|
|
|
82
83
|
occurredAt: string;
|
|
83
84
|
/** El flujo de Metamap que corrió. Solo para diagnóstico — nadie ramifica por él. */
|
|
84
85
|
metamapFlowId?: string;
|
|
86
|
+
/** Intento del wizard que originó la verificación; el consumidor descarta los que no son el vigente. */
|
|
87
|
+
attemptId?: string;
|
|
88
|
+
/** Flujo al que pertenece la estación, derivado de `metadata.kycStep`. */
|
|
89
|
+
flow?: KycFlowEnum;
|
|
85
90
|
}
|