@fiado/type-kit 3.252.0 → 3.254.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/BiometricVerificationChangedV1.test.ts +96 -0
- package/_test_/unit/biometrics/CreateBiometricVerificationRequest.test.ts +109 -0
- package/bin/biometrics/dtos/BiometricDelivery.d.ts +65 -0
- package/bin/biometrics/dtos/BiometricDelivery.js +89 -0
- package/bin/biometrics/dtos/BiometricSubject.d.ts +43 -0
- package/bin/biometrics/dtos/BiometricSubject.js +63 -0
- package/bin/biometrics/dtos/requests/CreateBiometricVerificationRequest.d.ts +73 -0
- package/bin/biometrics/dtos/requests/CreateBiometricVerificationRequest.js +81 -0
- package/bin/biometrics/dtos/responses/BiometricVerificationResponse.d.ts +87 -0
- package/bin/biometrics/dtos/responses/BiometricVerificationResponse.js +131 -0
- package/bin/biometrics/enums/BiometricDeliveryModeEnum.d.ts +29 -0
- package/bin/biometrics/enums/BiometricDeliveryModeEnum.js +33 -0
- package/bin/biometrics/enums/BiometricEventTypeEnum.d.ts +28 -0
- package/bin/biometrics/enums/BiometricEventTypeEnum.js +32 -0
- package/bin/biometrics/enums/BiometricProviderEnum.d.ts +25 -0
- package/bin/biometrics/enums/BiometricProviderEnum.js +29 -0
- package/bin/biometrics/enums/BiometricResultEnum.d.ts +25 -0
- package/bin/biometrics/enums/BiometricResultEnum.js +29 -0
- package/bin/biometrics/enums/BiometricTypeEnum.d.ts +20 -0
- package/bin/biometrics/enums/BiometricTypeEnum.js +24 -0
- package/bin/biometrics/enums/BiometricVerificationStatusEnum.d.ts +33 -0
- package/bin/biometrics/enums/BiometricVerificationStatusEnum.js +37 -0
- package/bin/biometrics/events/BiometricVerificationChangedV1.d.ts +80 -0
- package/bin/biometrics/events/BiometricVerificationChangedV1.js +98 -0
- package/bin/biometrics/index.d.ts +11 -0
- package/bin/biometrics/index.js +35 -0
- package/bin/helpdesk/dtos/HelpdeskCreateTicketRequest.d.ts +6 -0
- package/bin/helpdesk/dtos/HelpdeskCreateTicketRequest.js +16 -0
- package/bin/index.d.ts +1 -0
- package/bin/index.js +4 -3
- package/package.json +1 -1
- package/src/biometrics/dtos/BiometricDelivery.ts +81 -0
- package/src/biometrics/dtos/BiometricSubject.ts +54 -0
- package/src/biometrics/dtos/requests/CreateBiometricVerificationRequest.ts +101 -0
- package/src/biometrics/dtos/responses/BiometricVerificationResponse.ts +133 -0
- package/src/biometrics/enums/BiometricDeliveryModeEnum.ts +31 -0
- package/src/biometrics/enums/BiometricEventTypeEnum.ts +31 -0
- package/src/biometrics/enums/BiometricProviderEnum.ts +28 -0
- package/src/biometrics/enums/BiometricResultEnum.ts +28 -0
- package/src/biometrics/enums/BiometricTypeEnum.ts +23 -0
- package/src/biometrics/enums/BiometricVerificationStatusEnum.ts +37 -0
- package/src/biometrics/events/BiometricVerificationChangedV1.ts +114 -0
- package/src/biometrics/index.ts +24 -0
- package/src/helpdesk/dtos/HelpdeskCreateTicketRequest.ts +17 -1
- package/src/index.ts +1 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { Expose } from 'class-transformer';
|
|
2
|
+
import {
|
|
3
|
+
IsEnum,
|
|
4
|
+
IsISO8601,
|
|
5
|
+
IsNotEmpty,
|
|
6
|
+
IsNumber,
|
|
7
|
+
IsOptional,
|
|
8
|
+
IsString,
|
|
9
|
+
Max,
|
|
10
|
+
MaxLength,
|
|
11
|
+
Min,
|
|
12
|
+
} from 'class-validator';
|
|
13
|
+
import { BiometricDeliveryModeEnum } from '../../enums/BiometricDeliveryModeEnum';
|
|
14
|
+
import { BiometricProviderEnum } from '../../enums/BiometricProviderEnum';
|
|
15
|
+
import { BiometricResultEnum } from '../../enums/BiometricResultEnum';
|
|
16
|
+
import { BiometricTypeEnum } from '../../enums/BiometricTypeEnum';
|
|
17
|
+
import { BiometricVerificationStatusEnum } from '../../enums/BiometricVerificationStatusEnum';
|
|
18
|
+
import type { BiometricDelivery } from '../BiometricDelivery';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* El estado completo de una verificación biométrica.
|
|
22
|
+
*
|
|
23
|
+
* 🔴 **El MISMO tipo lo devuelven el `POST` y el `GET`** (DEC-012). Es una excepción consciente a
|
|
24
|
+
* "1 DTO por endpoint": el `POST` devuelve el estado inicial del MISMO recurso que el `GET` expone
|
|
25
|
+
* después. Duplicar el tipo garantizaría drift entre los dos — la clase de bug donde el `GET` gana
|
|
26
|
+
* un campo y el `POST` no, y el caller descubre la diferencia en producción. Queda registrado acá
|
|
27
|
+
* para que no haya que re-discutirlo en cada PR.
|
|
28
|
+
*
|
|
29
|
+
* `biometrics-business` — Entrega 1.
|
|
30
|
+
*/
|
|
31
|
+
export class BiometricVerificationResponse {
|
|
32
|
+
/**
|
|
33
|
+
* La llave de todo. Se genera al crear, viaja dentro de la metadata del proveedor, y vuelve
|
|
34
|
+
* ECOADA en cada webhook — **por eso la correlación es un `get` por PK y no una búsqueda**.
|
|
35
|
+
*/
|
|
36
|
+
@Expose() @IsString() @IsNotEmpty()
|
|
37
|
+
biometricVerificationId!: string;
|
|
38
|
+
|
|
39
|
+
/** Eco de lo que pediste. */
|
|
40
|
+
@Expose() @IsEnum(BiometricTypeEnum)
|
|
41
|
+
type!: BiometricTypeEnum;
|
|
42
|
+
|
|
43
|
+
/** Eco de lo que pediste. */
|
|
44
|
+
@Expose() @IsEnum(BiometricProviderEnum)
|
|
45
|
+
provider!: BiometricProviderEnum;
|
|
46
|
+
|
|
47
|
+
/** Eco de lo que pediste. */
|
|
48
|
+
@Expose() @IsString() @IsNotEmpty()
|
|
49
|
+
directoryId!: string;
|
|
50
|
+
|
|
51
|
+
/** Dónde va el PROCESO. Ver `BiometricVerificationStatusEnum`. */
|
|
52
|
+
@Expose() @IsEnum(BiometricVerificationStatusEnum)
|
|
53
|
+
status!: BiometricVerificationStatusEnum;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Qué dijo el BIOMÉTRICO. Ver `BiometricResultEnum`.
|
|
57
|
+
*
|
|
58
|
+
* ⚠️ Puede venir poblado con `status` todavía en `IN_PROGRESS`: el veredicto del facematch llega
|
|
59
|
+
* en el `step_completed`, antes del cierre de la verificación. Eso no es una inconsistencia, es
|
|
60
|
+
* exactamente la razón de que `status` y `result` sean campos distintos.
|
|
61
|
+
*/
|
|
62
|
+
@Expose() @IsEnum(BiometricResultEnum)
|
|
63
|
+
result!: BiometricResultEnum;
|
|
64
|
+
|
|
65
|
+
/** Discriminador de `delivery`. Ver `BiometricDeliveryModeEnum`. */
|
|
66
|
+
@Expose() @IsEnum(BiometricDeliveryModeEnum)
|
|
67
|
+
deliveryMode!: BiometricDeliveryModeEnum;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Lo que necesitas para ejecutar el biométrico. Su forma depende de `deliveryMode` — haz el
|
|
71
|
+
* `switch`, no leas `link` a ciegas.
|
|
72
|
+
*
|
|
73
|
+
* Sin decorador de validación a propósito: es una unión discriminada y `class-validator` no
|
|
74
|
+
* sabe validar uniones sin un `@Type()` con discriminador que acá agregaría más ceremonia que
|
|
75
|
+
* seguridad. El contrato lo garantiza el productor, que es un solo lambda.
|
|
76
|
+
*/
|
|
77
|
+
@Expose()
|
|
78
|
+
delivery!: BiometricDelivery;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Similitud reportada por el proveedor, 0–100, cuando la expone. Rekognition da score; Metamap
|
|
82
|
+
* responde pass/fail y esto viene ausente.
|
|
83
|
+
*
|
|
84
|
+
* ⚠️ **No lo uses para decidir tú el umbral.** El proveedor ya aplicó el suyo y eso es lo que
|
|
85
|
+
* dice `result`. Esto es para diagnóstico y para calibrar, no para re-juzgar.
|
|
86
|
+
*/
|
|
87
|
+
@Expose() @IsOptional() @IsNumber() @Min(0) @Max(100)
|
|
88
|
+
score?: number;
|
|
89
|
+
|
|
90
|
+
/** Umbral que aplicó el proveedor, cuando lo expone. Contexto para leer el `score`. */
|
|
91
|
+
@Expose() @IsOptional() @IsNumber() @Min(0) @Max(100)
|
|
92
|
+
threshold?: number;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* El id de la verificación del lado del proveedor (en Metamap, su `verificationId`). Para
|
|
96
|
+
* soporte y reconciliación: es lo que se busca en el panel del proveedor.
|
|
97
|
+
*/
|
|
98
|
+
@Expose() @IsOptional() @IsString() @IsNotEmpty()
|
|
99
|
+
providerRef?: string;
|
|
100
|
+
|
|
101
|
+
/** Tu correlación, tal cual la mandaste. */
|
|
102
|
+
@Expose() @IsOptional() @IsString() @MaxLength(256)
|
|
103
|
+
callerContext?: string;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Por qué falló, solo con `status = FAILED`. Texto OPERACIONAL, sin PII y sin nada del
|
|
107
|
+
* proveedor: describe qué se rompió de nuestro lado (DEC-013).
|
|
108
|
+
*/
|
|
109
|
+
@Expose() @IsOptional() @IsString()
|
|
110
|
+
failureReason?: string;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Todas las marcas de tiempo son **`string` ISO-8601 UTC con `Z`**.
|
|
114
|
+
*
|
|
115
|
+
* ⚠️ En la tabla se persisten como `Number` (dynamoose serializa `Date` a number, y un GSI
|
|
116
|
+
* declarado `S` sobre un timestamp se rompe en silencio). El mapeo Row → DTO es explícito. Si
|
|
117
|
+
* copias este tipo para el `Row`, rompes el índice.
|
|
118
|
+
*/
|
|
119
|
+
@Expose() @IsISO8601({ strict: true })
|
|
120
|
+
createdAt!: string;
|
|
121
|
+
|
|
122
|
+
/** Última vez que cambió algo. ISO-8601 UTC con `Z`. */
|
|
123
|
+
@Expose() @IsISO8601({ strict: true })
|
|
124
|
+
updatedAt!: string;
|
|
125
|
+
|
|
126
|
+
/** Cuándo vence la entrega. Es lo que el `GET` compara para persistir `EXPIRED` (DEC-015). */
|
|
127
|
+
@Expose() @IsISO8601({ strict: true })
|
|
128
|
+
expiresAt!: string;
|
|
129
|
+
|
|
130
|
+
/** Cuándo alcanzó un estado terminal. Ausente mientras siga viva. */
|
|
131
|
+
@Expose() @IsOptional() @IsISO8601({ strict: true })
|
|
132
|
+
completedAt?: string;
|
|
133
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CÓMO se le entrega al caller lo que necesita para ejecutar el biométrico. **Es el discriminador
|
|
3
|
+
* de `BiometricVerificationResponse.delivery`.**
|
|
4
|
+
*
|
|
5
|
+
* 🔴 **Esta es la pieza que hace genérico al contrato, y la razón de que exista este servicio.**
|
|
6
|
+
* Los proveedores no se parecen en nada:
|
|
7
|
+
*
|
|
8
|
+
* | Proveedor | Qué entrega | Quién ejecuta | Cómo vuelve el resultado |
|
|
9
|
+
* |---|---|---|---|
|
|
10
|
+
* | Metamap | un link hospedado | el navegador del usuario | webhook async |
|
|
11
|
+
* | Apple/Google | un challenge + nonce | el dispositivo, on-device | attestation que devuelve el cliente |
|
|
12
|
+
* | Amazon | nada (se mandan imágenes) | nuestro servidor | síncrono, con score |
|
|
13
|
+
*
|
|
14
|
+
* Si la respuesta fuera `{ link }` a secas, **solo Metamap entraría** y el día que llegue Apple
|
|
15
|
+
* habría que romper el contrato con todos los callers ya escritos. Discriminar por el MODO —y no
|
|
16
|
+
* por el proveedor— deja que entre cualquiera de los tres sin tocar a nadie.
|
|
17
|
+
*
|
|
18
|
+
* El `GET` de polling NO cambia entre modos: devuelve estado, no mecanismo.
|
|
19
|
+
*
|
|
20
|
+
* `biometrics-business` — Entrega 1 (solo `REDIRECT` produce valores reales).
|
|
21
|
+
*/
|
|
22
|
+
export enum BiometricDeliveryModeEnum {
|
|
23
|
+
/** `delivery` trae `{ link, expiresAt }`. El usuario abre el link y ejecuta ahí. */
|
|
24
|
+
REDIRECT = 'REDIRECT',
|
|
25
|
+
|
|
26
|
+
/** `delivery` trae `{ challenge, nonce, expiresAt }`. El dispositivo firma y devuelve. */
|
|
27
|
+
CHALLENGE = 'CHALLENGE',
|
|
28
|
+
|
|
29
|
+
/** `delivery` viene vacío: el biométrico ya corrió y `result` viene poblado en la misma respuesta. */
|
|
30
|
+
IMMEDIATE = 'IMMEDIATE',
|
|
31
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Qué le pasó a la verificación, según el proveedor. Viaja en `BiometricVerificationChangedV1` y es
|
|
3
|
+
* lo que el consumer usa para decidir la transición de estado.
|
|
4
|
+
*
|
|
5
|
+
* Es el vocabulario NUESTRO, no el de Metamap: `kyc-metamap-webhook` traduce sus `eventName` a
|
|
6
|
+
* estos cuatro 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 cuatro.
|
|
8
|
+
*
|
|
9
|
+
* `biometrics-business` — Entrega 1.
|
|
10
|
+
*/
|
|
11
|
+
export enum BiometricEventTypeEnum {
|
|
12
|
+
/** El usuario arrancó la verificación. → `IN_PROGRESS`. */
|
|
13
|
+
VERIFICATION_STARTED = 'VERIFICATION_STARTED',
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Cerró una estación con resultado. Es el que trae el veredicto del facematch, ANTES del cierre:
|
|
17
|
+
* por eso `result` puede quedar poblado con `status` todavía en `IN_PROGRESS`.
|
|
18
|
+
*/
|
|
19
|
+
STEP_COMPLETED = 'STEP_COMPLETED',
|
|
20
|
+
|
|
21
|
+
/** El proveedor cerró la verificación. → `COMPLETED`. */
|
|
22
|
+
VERIFICATION_COMPLETED = 'VERIFICATION_COMPLETED',
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* El proveedor la dio por vencida. → `EXPIRED`.
|
|
26
|
+
*
|
|
27
|
+
* ⚠️ **No se depende de este evento para expirar.** El `GET` persiste `EXPIRED` comparando
|
|
28
|
+
* contra `expiresAt` (DEC-015), así que una verificación vence aunque este evento nunca llegue.
|
|
29
|
+
*/
|
|
30
|
+
VERIFICATION_EXPIRED = 'VERIFICATION_EXPIRED',
|
|
31
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QUIÉN ejecuta el biométrico. Ortogonal a `BiometricTypeEnum`: el mismo `FACEMATCH` lo puede
|
|
3
|
+
* resolver Metamap con un link o Amazon con una llamada síncrona.
|
|
4
|
+
*
|
|
5
|
+
* 🔴 **Es OBLIGATORIO en el request** (DEC-004, decisión de Andres 2026-07-30). El caller nombra al
|
|
6
|
+
* proveedor en vez de dejar que `biometrics-business` resuelva un default.
|
|
7
|
+
*
|
|
8
|
+
* **Consecuencia registrada (TD-001):** cambiar de proveedor obliga a tocar y redesplegar a TODOS
|
|
9
|
+
* los callers. Se aceptó a cambio de que la elección sea explícita y auditable en cada request.
|
|
10
|
+
*
|
|
11
|
+
* Solo `METAMAP` está implementado. Los otros tres cambian el MODO DE ENTREGA, no solo el
|
|
12
|
+
* proveedor — por eso existe `BiometricDeliveryModeEnum`.
|
|
13
|
+
*
|
|
14
|
+
* `biometrics-business` — Entrega 1.
|
|
15
|
+
*/
|
|
16
|
+
export enum BiometricProviderEnum {
|
|
17
|
+
/** El único implementado. Entrega por link hospedado + webhook async → `REDIRECT`. */
|
|
18
|
+
METAMAP = 'METAMAP',
|
|
19
|
+
|
|
20
|
+
/** Face ID, on-device. Entregaría un challenge y recibiría una attestation → `CHALLENGE`. */
|
|
21
|
+
APPLE = 'APPLE',
|
|
22
|
+
|
|
23
|
+
/** Biometría de Android, on-device. Mismo modo que Apple → `CHALLENGE`. */
|
|
24
|
+
GOOGLE = 'GOOGLE',
|
|
25
|
+
|
|
26
|
+
/** Rekognition: comparación en servidor, con score. Respuesta síncrona → `IMMEDIATE`. */
|
|
27
|
+
AWS = 'AWS',
|
|
28
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Qué dijo el BIOMÉTRICO. Ortogonal a `BiometricVerificationStatusEnum`, que dice dónde va el
|
|
3
|
+
* proceso — ver el comentario de ese enum para por qué van separados.
|
|
4
|
+
*
|
|
5
|
+
* `biometrics-business` — Entrega 1.
|
|
6
|
+
*/
|
|
7
|
+
export enum BiometricResultEnum {
|
|
8
|
+
/**
|
|
9
|
+
* Todavía no hay veredicto. Es el valor inicial y también el de los terminales que cierran sin
|
|
10
|
+
* resultado (`EXPIRED`, `FAILED`).
|
|
11
|
+
*
|
|
12
|
+
* ⚠️ `UNKNOWN` **no es** `NO_MATCH`. Tratarlos igual convierte "el usuario nunca abrió el link"
|
|
13
|
+
* en "el usuario no es quien dice ser" — que es una acusación, no un dato.
|
|
14
|
+
*/
|
|
15
|
+
UNKNOWN = 'UNKNOWN',
|
|
16
|
+
|
|
17
|
+
/** Coincide con la referencia. En Metamap: el step `facematch-service-validation` sin `error`. */
|
|
18
|
+
MATCH = 'MATCH',
|
|
19
|
+
|
|
20
|
+
/** No coincide. Es un veredicto real del proveedor, no una falla de proceso. */
|
|
21
|
+
NO_MATCH = 'NO_MATCH',
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* El biométrico corrió pero no concluye (mala iluminación, foto de referencia pobre, score en la
|
|
25
|
+
* zona gris). Distinto de `UNKNOWN`: acá sí hubo intento.
|
|
26
|
+
*/
|
|
27
|
+
INCONCLUSIVE = 'INCONCLUSIVE',
|
|
28
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QUÉ biométrico se pide. Es el discriminador del caso de uso, independiente de quién lo ejecute
|
|
3
|
+
* (eso lo dice `BiometricProviderEnum`).
|
|
4
|
+
*
|
|
5
|
+
* 🔴 **Solo `FACEMATCH` está implementado.** Los demás están declarados a propósito: el contrato
|
|
6
|
+
* nace genérico para que agregar un biométrico no obligue a romperlo. Pedir uno no implementado
|
|
7
|
+
* devuelve `BIOMETRIC_NOT_SUPPORTED` (422) — un error explícito y accionable, nunca un 500.
|
|
8
|
+
*
|
|
9
|
+
* `biometrics-business` — Entrega 1.
|
|
10
|
+
*/
|
|
11
|
+
export enum BiometricTypeEnum {
|
|
12
|
+
/** Comparar la selfie del momento contra una foto de referencia ya registrada. */
|
|
13
|
+
FACEMATCH = 'FACEMATCH',
|
|
14
|
+
|
|
15
|
+
/** ¿Hay una persona viva frente a la cámara? Declarado, sin implementar. */
|
|
16
|
+
LIVENESS = 'LIVENESS',
|
|
17
|
+
|
|
18
|
+
/** Verificación por voz. Declarado, sin implementar. */
|
|
19
|
+
VOICE = 'VOICE',
|
|
20
|
+
|
|
21
|
+
/** Comparar la cara contra la foto impresa en el documento. Declarado, sin implementar. */
|
|
22
|
+
DOCUMENT_MATCH = 'DOCUMENT_MATCH',
|
|
23
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dónde va el PROCESO de la verificación. **No dice cómo salió el biométrico** — eso es
|
|
3
|
+
* `BiometricResultEnum`.
|
|
4
|
+
*
|
|
5
|
+
* 🔴 **Están separados a propósito.** Aplastar los dos en un solo campo hace ambiguo un caso real y
|
|
6
|
+
* frecuente: el facematch ya dio `MATCH` (llegó el `step_completed`) pero la verificación todavía no
|
|
7
|
+
* cerró (falta el `verification_completed`). Con un campo único no hay forma de decir eso sin
|
|
8
|
+
* mentir en alguna dirección.
|
|
9
|
+
*
|
|
10
|
+
* **Los terminales no retroceden.** `COMPLETED`, `EXPIRED` y `FAILED` no vuelven a `IN_PROGRESS`
|
|
11
|
+
* aunque llegue un evento posterior — SQS no garantiza orden y la cola es at-least-once.
|
|
12
|
+
*
|
|
13
|
+
* `biometrics-business` — Entrega 1.
|
|
14
|
+
*/
|
|
15
|
+
export enum BiometricVerificationStatusEnum {
|
|
16
|
+
/** Se emitió la entrega (link/challenge) y nadie la ha ejecutado todavía. */
|
|
17
|
+
PENDING = 'PENDING',
|
|
18
|
+
|
|
19
|
+
/** El usuario arrancó. Puede haber resultados parciales de estaciones. */
|
|
20
|
+
IN_PROGRESS = 'IN_PROGRESS',
|
|
21
|
+
|
|
22
|
+
/** El proveedor cerró la verificación. `result` tiene el veredicto final. */
|
|
23
|
+
COMPLETED = 'COMPLETED',
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Venció sin completarse. Lo escribe el proveedor (`verification_expired`) **o** el propio `GET`
|
|
27
|
+
* al detectar `now > expiresAt` — y en ese caso lo PERSISTE (DEC-015). Si se calculara sin
|
|
28
|
+
* persistir, un cierre tardío escribiría `COMPLETED` sobre algo que el caller ya vio `EXPIRED`.
|
|
29
|
+
*/
|
|
30
|
+
EXPIRED = 'EXPIRED',
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Falla interna procesando la verificación. **El proveedor NO lo produce**: el catálogo de
|
|
34
|
+
* eventos de Metamap no tiene ninguno de error (DEC-013). `failureReason` es nuestro.
|
|
35
|
+
*/
|
|
36
|
+
FAILED = 'FAILED',
|
|
37
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { Expose } from 'class-transformer';
|
|
2
|
+
import {
|
|
3
|
+
IsEnum,
|
|
4
|
+
IsISO8601,
|
|
5
|
+
IsIn,
|
|
6
|
+
IsNotEmpty,
|
|
7
|
+
IsNumber,
|
|
8
|
+
IsOptional,
|
|
9
|
+
IsString,
|
|
10
|
+
Matches,
|
|
11
|
+
Max,
|
|
12
|
+
Min,
|
|
13
|
+
} from 'class-validator';
|
|
14
|
+
import { BiometricEventTypeEnum } from '../enums/BiometricEventTypeEnum';
|
|
15
|
+
import { BiometricProviderEnum } from '../enums/BiometricProviderEnum';
|
|
16
|
+
import { BiometricResultEnum } from '../enums/BiometricResultEnum';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Lo que le pasó a una verificación biométrica, según el proveedor.
|
|
20
|
+
*
|
|
21
|
+
* **El recorrido:** Metamap → `kyc-metamap-webhook` (que valida la firma HMAC y traduce) →
|
|
22
|
+
* `BiometricsInboundQueue` → el consumer de `biometrics-business`, que aplica la transición.
|
|
23
|
+
*
|
|
24
|
+
* **Por qué el webhook no llama directo:** ese lambda tiene una disciplina explícita — *un fallo de
|
|
25
|
+
* publish NUNCA puede tumbar el webhook*, porque si lanza, Metamap reintenta el webhook completo y
|
|
26
|
+
* se duplican las actualizaciones de identidad. Con esa regla, una llamada síncrona que falle se
|
|
27
|
+
* traga el evento en silencio y la verificación queda `IN_PROGRESS` para siempre, con el caller
|
|
28
|
+
* poleando al vacío y sin alarma. La cola da durabilidad, DLQ y alarma sin tocar esa disciplina.
|
|
29
|
+
*
|
|
30
|
+
* 🔴 **Cola propia, NO `RetailKycInboundQueue`.** SQS no hace broadcast: dos lambdas sobre la misma
|
|
31
|
+
* cola son consumidores en competencia y cada mensaje le llega a UNO solo. Reusar la de SureKeep
|
|
32
|
+
* partiría sus KYC a la mitad, en producción, al azar y sin un solo error en los logs.
|
|
33
|
+
*
|
|
34
|
+
* 🔴 **Sin PII.** Solo identificadores operacionales y el veredicto.
|
|
35
|
+
*
|
|
36
|
+
* `biometrics-business` — Entrega 1.
|
|
37
|
+
*/
|
|
38
|
+
export class BiometricVerificationChangedV1 {
|
|
39
|
+
/**
|
|
40
|
+
* Discriminador del tipo de mensaje. Fijo, para que el consumer rutee por él y no por adivinar
|
|
41
|
+
* la forma del payload.
|
|
42
|
+
*/
|
|
43
|
+
@Expose() @IsOptional() @IsIn(['BiometricVerificationChangedV1'])
|
|
44
|
+
schema?: string;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 🔴 **Identificador ÚNICO de este evento. La clave de deduplicación.**
|
|
48
|
+
*
|
|
49
|
+
* SQS es *at-least-once*: los duplicados son la norma, no la excepción. Y **el candado temporal
|
|
50
|
+
* por `occurredAt` NO alcanza para esto**: dos entregas del mismo evento traen exactamente el
|
|
51
|
+
* mismo `occurredAt`, así que una regla "descartar si es más viejo" las deja pasar a las dos.
|
|
52
|
+
*
|
|
53
|
+
* El consumer guarda los `eventId` ya aplicados y descarta los repetidos.
|
|
54
|
+
*
|
|
55
|
+
* ⚠️ **No deduplicar por el `MessageId` de SQS**: cambia en cada reintento de entrega, así que
|
|
56
|
+
* no deduplica nada. Es el error clásico y por eso queda escrito acá y no en la cabeza de cada
|
|
57
|
+
* consumidor.
|
|
58
|
+
*/
|
|
59
|
+
@Expose() @IsString() @IsNotEmpty()
|
|
60
|
+
eventId!: string;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* A qué verificación se refiere. Sale del `metadata` que el proveedor ecoa tal cual → el
|
|
64
|
+
* consumer hace un `get` por PK, sin búsqueda.
|
|
65
|
+
*/
|
|
66
|
+
@Expose() @IsString() @IsNotEmpty()
|
|
67
|
+
biometricVerificationId!: string;
|
|
68
|
+
|
|
69
|
+
/** Quién ejecutó el biométrico. */
|
|
70
|
+
@Expose() @IsEnum(BiometricProviderEnum)
|
|
71
|
+
provider!: BiometricProviderEnum;
|
|
72
|
+
|
|
73
|
+
/** El id del lado del proveedor (el `verificationId` de Metamap). Para soporte y reconciliación. */
|
|
74
|
+
@Expose() @IsOptional() @IsString() @IsNotEmpty()
|
|
75
|
+
providerRef?: string;
|
|
76
|
+
|
|
77
|
+
/** Qué pasó. Es lo que decide la transición de estado. */
|
|
78
|
+
@Expose() @IsEnum(BiometricEventTypeEnum)
|
|
79
|
+
eventType!: BiometricEventTypeEnum;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* El veredicto, cuando el evento lo trae. Llega en el `STEP_COMPLETED`, **antes** del cierre —
|
|
83
|
+
* por eso el consumer puede poblar `result` con `status` todavía en `IN_PROGRESS`.
|
|
84
|
+
*
|
|
85
|
+
* Ausente en `VERIFICATION_STARTED` y `VERIFICATION_EXPIRED`.
|
|
86
|
+
*/
|
|
87
|
+
@Expose() @IsOptional() @IsEnum(BiometricResultEnum)
|
|
88
|
+
result?: BiometricResultEnum;
|
|
89
|
+
|
|
90
|
+
/** Similitud reportada, 0–100, cuando el proveedor la expone. */
|
|
91
|
+
@Expose() @IsOptional() @IsNumber() @Min(0) @Max(100)
|
|
92
|
+
score?: number;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Cuándo ocurrió, según el proveedor. Obligatorio: es el candado de orden.
|
|
96
|
+
*
|
|
97
|
+
* ⚠️ **UTC estricto, con `Z` y sin offset** — por eso el `@Matches` además del `@IsISO8601()`.
|
|
98
|
+
* `@IsISO8601()` solo acepta `2026-07-26` (sin hora), `2026-W30-1` y, lo que hace daño de
|
|
99
|
+
* verdad, `2026-07-26T15:05:00+05:00`.
|
|
100
|
+
*
|
|
101
|
+
* 🔴 **El candado del consumer es LÉXICO.** Ese `+05:00` son las 10:05 UTC, pero comparado como
|
|
102
|
+
* texto es MAYOR que un `T10:06:00Z` posterior: el consumer lo leería como más nuevo, descartaría
|
|
103
|
+
* el evento real, y la transición no se aplicaría nunca — sin un solo error.
|
|
104
|
+
*
|
|
105
|
+
* Normalizar a UTC es trabajo del traductor del proveedor (`kyc-metamap-webhook`). Este candado
|
|
106
|
+
* existe para que si algún día deja de hacerlo, el mensaje caiga al DLQ en el boundary en vez de
|
|
107
|
+
* corromper el orden en silencio.
|
|
108
|
+
*/
|
|
109
|
+
@Expose() @IsString() @IsISO8601({ strict: true })
|
|
110
|
+
@Matches(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/, {
|
|
111
|
+
message: 'occurredAt debe ser ISO-8601 en UTC con Z (ej. 2026-07-26T10:05:00.000Z): un offset rompe el orden lexico',
|
|
112
|
+
})
|
|
113
|
+
occurredAt!: string;
|
|
114
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Dominio `biometrics` — el contrato de `biometrics-business`, el master de la lógica biométrica
|
|
2
|
+
// de Fiado. El caller dice QUÉ necesita y PARA QUIÉN; el proveedor (Metamap hoy; Apple, Google y
|
|
3
|
+
// Amazon después) queda detrás de un port y no aparece en el contrato más que como un enum.
|
|
4
|
+
|
|
5
|
+
// Enums
|
|
6
|
+
export * from './enums/BiometricTypeEnum';
|
|
7
|
+
export * from './enums/BiometricProviderEnum';
|
|
8
|
+
export * from './enums/BiometricDeliveryModeEnum';
|
|
9
|
+
export * from './enums/BiometricVerificationStatusEnum';
|
|
10
|
+
export * from './enums/BiometricResultEnum';
|
|
11
|
+
export * from './enums/BiometricEventTypeEnum';
|
|
12
|
+
|
|
13
|
+
// DTOs compartidos
|
|
14
|
+
export * from './dtos/BiometricSubject';
|
|
15
|
+
export * from './dtos/BiometricDelivery';
|
|
16
|
+
|
|
17
|
+
// Request DTOs
|
|
18
|
+
export * from './dtos/requests/CreateBiometricVerificationRequest';
|
|
19
|
+
|
|
20
|
+
// Response DTOs
|
|
21
|
+
export * from './dtos/responses/BiometricVerificationResponse';
|
|
22
|
+
|
|
23
|
+
// Events (BiometricsInboundQueue)
|
|
24
|
+
export * from './events/BiometricVerificationChangedV1';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { IsNotEmpty, IsString } from "class-validator";
|
|
1
|
+
import { IsArray, IsBoolean, IsNotEmpty, IsNumber, IsOptional, IsString } from "class-validator";
|
|
2
2
|
|
|
3
3
|
export class HelpdeskCreateTicketRequest {
|
|
4
4
|
|
|
@@ -27,4 +27,20 @@ export class HelpdeskCreateTicketRequest {
|
|
|
27
27
|
@IsString()
|
|
28
28
|
peopleId: string;
|
|
29
29
|
|
|
30
|
+
/** true → la nota se crea como comentario interno (solo agentes). Default: false. */
|
|
31
|
+
@IsBoolean()
|
|
32
|
+
@IsOptional()
|
|
33
|
+
internal?: boolean;
|
|
34
|
+
|
|
35
|
+
/** Tags del ticket (p. ej. ["kyc-listas", "interno"]) para vistas y triggers. */
|
|
36
|
+
@IsArray()
|
|
37
|
+
@IsString({ each: true })
|
|
38
|
+
@IsOptional()
|
|
39
|
+
tags?: string[];
|
|
40
|
+
|
|
41
|
+
/** GID del grupo de Zendesk al que se asigna (p. ej. Legal). */
|
|
42
|
+
@IsNumber()
|
|
43
|
+
@IsOptional()
|
|
44
|
+
groupId?: number;
|
|
45
|
+
|
|
30
46
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * as Crypto from './crypto';
|
|
2
2
|
export * as Account from './account';
|
|
3
3
|
export * as MetamapConnector from './metamapConnector';
|
|
4
|
+
export * as Biometrics from './biometrics';
|
|
4
5
|
export * as Activity from './activity';
|
|
5
6
|
export * as Beneficiary from './beneficiary';
|
|
6
7
|
export * as Address from './address';
|