@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.
Files changed (45) hide show
  1. package/_test_/unit/biometrics/BiometricVerificationChangedV1.test.ts +96 -0
  2. package/_test_/unit/biometrics/CreateBiometricVerificationRequest.test.ts +109 -0
  3. package/bin/biometrics/dtos/BiometricDelivery.d.ts +65 -0
  4. package/bin/biometrics/dtos/BiometricDelivery.js +89 -0
  5. package/bin/biometrics/dtos/BiometricSubject.d.ts +43 -0
  6. package/bin/biometrics/dtos/BiometricSubject.js +63 -0
  7. package/bin/biometrics/dtos/requests/CreateBiometricVerificationRequest.d.ts +73 -0
  8. package/bin/biometrics/dtos/requests/CreateBiometricVerificationRequest.js +81 -0
  9. package/bin/biometrics/dtos/responses/BiometricVerificationResponse.d.ts +87 -0
  10. package/bin/biometrics/dtos/responses/BiometricVerificationResponse.js +131 -0
  11. package/bin/biometrics/enums/BiometricDeliveryModeEnum.d.ts +29 -0
  12. package/bin/biometrics/enums/BiometricDeliveryModeEnum.js +33 -0
  13. package/bin/biometrics/enums/BiometricEventTypeEnum.d.ts +28 -0
  14. package/bin/biometrics/enums/BiometricEventTypeEnum.js +32 -0
  15. package/bin/biometrics/enums/BiometricProviderEnum.d.ts +25 -0
  16. package/bin/biometrics/enums/BiometricProviderEnum.js +29 -0
  17. package/bin/biometrics/enums/BiometricResultEnum.d.ts +25 -0
  18. package/bin/biometrics/enums/BiometricResultEnum.js +29 -0
  19. package/bin/biometrics/enums/BiometricTypeEnum.d.ts +20 -0
  20. package/bin/biometrics/enums/BiometricTypeEnum.js +24 -0
  21. package/bin/biometrics/enums/BiometricVerificationStatusEnum.d.ts +33 -0
  22. package/bin/biometrics/enums/BiometricVerificationStatusEnum.js +37 -0
  23. package/bin/biometrics/events/BiometricVerificationChangedV1.d.ts +80 -0
  24. package/bin/biometrics/events/BiometricVerificationChangedV1.js +98 -0
  25. package/bin/biometrics/index.d.ts +11 -0
  26. package/bin/biometrics/index.js +35 -0
  27. package/bin/helpdesk/dtos/HelpdeskCreateTicketRequest.d.ts +6 -0
  28. package/bin/helpdesk/dtos/HelpdeskCreateTicketRequest.js +16 -0
  29. package/bin/index.d.ts +1 -0
  30. package/bin/index.js +4 -3
  31. package/package.json +1 -1
  32. package/src/biometrics/dtos/BiometricDelivery.ts +81 -0
  33. package/src/biometrics/dtos/BiometricSubject.ts +54 -0
  34. package/src/biometrics/dtos/requests/CreateBiometricVerificationRequest.ts +101 -0
  35. package/src/biometrics/dtos/responses/BiometricVerificationResponse.ts +133 -0
  36. package/src/biometrics/enums/BiometricDeliveryModeEnum.ts +31 -0
  37. package/src/biometrics/enums/BiometricEventTypeEnum.ts +31 -0
  38. package/src/biometrics/enums/BiometricProviderEnum.ts +28 -0
  39. package/src/biometrics/enums/BiometricResultEnum.ts +28 -0
  40. package/src/biometrics/enums/BiometricTypeEnum.ts +23 -0
  41. package/src/biometrics/enums/BiometricVerificationStatusEnum.ts +37 -0
  42. package/src/biometrics/events/BiometricVerificationChangedV1.ts +114 -0
  43. package/src/biometrics/index.ts +24 -0
  44. package/src/helpdesk/dtos/HelpdeskCreateTicketRequest.ts +17 -1
  45. 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';