@fiado/type-kit 3.218.0 → 3.220.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/kyc/IsKycStepStatusByStep.test.ts +86 -0
- package/_test_/unit/kyc/KycStepCompletedV1.test.ts +239 -0
- package/_test_/unit/kyc/applyKycStep.test.ts +75 -0
- package/bin/benefitCenter/enums/BenefitFlowEnum.d.ts +11 -0
- package/bin/benefitCenter/enums/BenefitFlowEnum.js +15 -0
- package/bin/kyc/enums/KycSubCheckNameEnum.d.ts +32 -0
- package/bin/kyc/enums/KycSubCheckNameEnum.js +39 -0
- package/bin/kyc/events/KycStepCompletedV1.d.ts +85 -0
- package/bin/kyc/events/KycStepCompletedV1.js +93 -0
- package/bin/kyc/helpers/applyKycStep.d.ts +26 -0
- package/bin/kyc/helpers/applyKycStep.js +30 -0
- package/bin/kyc/index.d.ts +4 -0
- package/bin/kyc/index.js +6 -0
- package/bin/kyc/validators/IsKycStepStatusByStep.d.ts +26 -0
- package/bin/kyc/validators/IsKycStepStatusByStep.js +64 -0
- package/bin/loanConfig/enums/ModifiableByRoleEnum.d.ts +11 -0
- package/bin/loanConfig/enums/ModifiableByRoleEnum.js +15 -0
- package/bin/places/dtos/CashInFeeDto.d.ts +17 -0
- package/bin/places/dtos/CashInFeeDto.js +12 -0
- package/bin/platformRbac/dtos/ResendOtpRequest.d.ts +22 -0
- package/bin/platformRbac/dtos/ResendOtpRequest.js +36 -0
- package/bin/platformRbac/dtos/ResendSelfRegisterOtpRequest.d.ts +11 -0
- package/bin/platformRbac/dtos/ResendSelfRegisterOtpRequest.js +36 -0
- package/bin/retailWizard/dtos/responses/KycPrepareResponse.d.ts +6 -0
- package/package.json +1 -1
- package/src/kyc/enums/KycSubCheckNameEnum.ts +36 -0
- package/src/kyc/events/KycStepCompletedV1.ts +105 -0
- package/src/kyc/helpers/applyKycStep.ts +36 -0
- package/src/kyc/index.ts +8 -0
- package/src/kyc/validators/IsKycStepStatusByStep.ts +57 -0
- package/src/retailWizard/dtos/responses/KycPrepareResponse.ts +6 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.KycStepCompletedV1 = void 0;
|
|
13
|
+
const class_transformer_1 = require("class-transformer");
|
|
14
|
+
const class_validator_1 = require("class-validator");
|
|
15
|
+
const KycSubCheckNameEnum_1 = require("../enums/KycSubCheckNameEnum");
|
|
16
|
+
const IsKycStepStatusByStep_1 = require("../validators/IsKycStepStatusByStep");
|
|
17
|
+
/**
|
|
18
|
+
* El resultado de UNA estación del KYC, publicado por `kyc-metamap-webhook` en cuanto Metamap la
|
|
19
|
+
* cierra (`step_completed`) — no al final de la verificación.
|
|
20
|
+
*
|
|
21
|
+
* **Por qué existe:** `KycVerificationChangedV1` llega UNA vez, cuando todo terminó. Con eso la
|
|
22
|
+
* pantalla del vendedor solo puede mostrar una ruedita hasta el veredicto. Metamap emite un webhook
|
|
23
|
+
* por estación y el webhook ya los recibía; lo único que faltaba era reenviarlos. Este evento es ese
|
|
24
|
+
* reenvío: deja que SureKeep pinte el avance estación por estación mientras el cliente espera.
|
|
25
|
+
*
|
|
26
|
+
* **Convive con `KycVerificationChangedV1` en la MISMA cola** (`RetailKycInboundQueue`). El
|
|
27
|
+
* discriminador es la presencia de `step`: si lo trae, es una estación; si no, es el cierre. Por eso
|
|
28
|
+
* `KycVerificationChangedV1` no se tocó — un consumidor viejo que no conoce este evento lo descarta
|
|
29
|
+
* en su propio parseo sin romperse.
|
|
30
|
+
*
|
|
31
|
+
* 🔴 **NO es un veredicto.** Un `FAIL` acá no rechaza nada ni frena la venta: el cliente reintenta
|
|
32
|
+
* dentro del mismo SDK, en el mismo dispositivo, sin salir de la sesión. La decisión sigue viniendo
|
|
33
|
+
* del estado consolidado (`RetailKycStatusEnum`) que llega en el cierre. Esto es progreso visible,
|
|
34
|
+
* no política.
|
|
35
|
+
*
|
|
36
|
+
* 🔴 **Sin PII, a propósito.** No lleva `person`, ni `address`, ni `peopleId`: una estación solo
|
|
37
|
+
* necesita decir cuál es y cómo salió. Lo que sí lleva son identificadores operacionales, que es lo
|
|
38
|
+
* único que se puede loguear.
|
|
39
|
+
*
|
|
40
|
+
* **Idempotencia:** la clave es `(verificationId, step)` — NO viaja `eventId` a propósito. Una
|
|
41
|
+
* estación cierra una sola vez por verificación, y asentar su resultado sobre el progreso
|
|
42
|
+
* (`applyKycStep`) es idempotente por diseño: reprocesar el mismo evento escribe el mismo valor. Que
|
|
43
|
+
* quede dicho acá y no en la cabeza de cada consumidor: deducirlo mal lleva a deduplicar por el
|
|
44
|
+
* `MessageId` de SQS, que cambia en cada reintento de entrega y por lo tanto no deduplica nada.
|
|
45
|
+
*
|
|
46
|
+
* SureKeep Fase 2 — progreso del KYC estación por estación.
|
|
47
|
+
*/
|
|
48
|
+
class KycStepCompletedV1 {
|
|
49
|
+
}
|
|
50
|
+
exports.KycStepCompletedV1 = KycStepCompletedV1;
|
|
51
|
+
__decorate([
|
|
52
|
+
(0, class_transformer_1.Expose)(),
|
|
53
|
+
(0, class_validator_1.IsOptional)(),
|
|
54
|
+
(0, class_validator_1.IsIn)(['KycStepCompletedV1']),
|
|
55
|
+
__metadata("design:type", String)
|
|
56
|
+
], KycStepCompletedV1.prototype, "eventType", void 0);
|
|
57
|
+
__decorate([
|
|
58
|
+
(0, class_transformer_1.Expose)(),
|
|
59
|
+
(0, class_validator_1.IsString)(),
|
|
60
|
+
(0, class_validator_1.IsNotEmpty)(),
|
|
61
|
+
__metadata("design:type", String)
|
|
62
|
+
], KycStepCompletedV1.prototype, "verificationId", void 0);
|
|
63
|
+
__decorate([
|
|
64
|
+
(0, class_transformer_1.Expose)(),
|
|
65
|
+
(0, class_validator_1.IsString)(),
|
|
66
|
+
(0, class_validator_1.IsNotEmpty)(),
|
|
67
|
+
__metadata("design:type", String)
|
|
68
|
+
], KycStepCompletedV1.prototype, "directoryId", void 0);
|
|
69
|
+
__decorate([
|
|
70
|
+
(0, class_transformer_1.Expose)(),
|
|
71
|
+
(0, class_validator_1.IsEnum)(KycSubCheckNameEnum_1.KycSubCheckNameEnum),
|
|
72
|
+
__metadata("design:type", String)
|
|
73
|
+
], KycStepCompletedV1.prototype, "step", void 0);
|
|
74
|
+
__decorate([
|
|
75
|
+
(0, class_transformer_1.Expose)(),
|
|
76
|
+
(0, class_validator_1.Validate)(IsKycStepStatusByStep_1.IsKycStepStatusByStep),
|
|
77
|
+
__metadata("design:type", String)
|
|
78
|
+
], KycStepCompletedV1.prototype, "status", void 0);
|
|
79
|
+
__decorate([
|
|
80
|
+
(0, class_transformer_1.Expose)(),
|
|
81
|
+
(0, class_validator_1.IsString)(),
|
|
82
|
+
(0, class_validator_1.IsISO8601)({ strict: true }),
|
|
83
|
+
(0, class_validator_1.Matches)(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/, {
|
|
84
|
+
message: 'occurredAt debe ser ISO-8601 en UTC con Z (ej. 2026-07-26T10:05:00.000Z): un offset rompe el orden léxico',
|
|
85
|
+
}),
|
|
86
|
+
__metadata("design:type", String)
|
|
87
|
+
], KycStepCompletedV1.prototype, "occurredAt", void 0);
|
|
88
|
+
__decorate([
|
|
89
|
+
(0, class_transformer_1.Expose)(),
|
|
90
|
+
(0, class_validator_1.IsOptional)(),
|
|
91
|
+
(0, class_validator_1.IsString)(),
|
|
92
|
+
__metadata("design:type", String)
|
|
93
|
+
], KycStepCompletedV1.prototype, "metamapFlowId", void 0);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { KycSubChecks } from '../dtos/KycSubChecks';
|
|
2
|
+
import { KycSubCheckNameEnum } from '../enums/KycSubCheckNameEnum';
|
|
3
|
+
import { KycSubCheckStatusEnum } from '../enums/KycSubCheckStatusEnum';
|
|
4
|
+
import { KycWatchlistStatusEnum } from '../enums/KycWatchlistStatusEnum';
|
|
5
|
+
/**
|
|
6
|
+
* Asienta el resultado de UNA estación (`KycStepCompletedV1`) sobre el progreso acumulado de la
|
|
7
|
+
* verificación, y devuelve un objeto NUEVO — no muta el que recibe.
|
|
8
|
+
*
|
|
9
|
+
* **Por qué esto vive en la lib y no en cada consumidor:** la asignación obvia
|
|
10
|
+
* `progreso[evento.step] = evento.status` **no compila**. Todos los campos de `KycSubChecks` son
|
|
11
|
+
* opcionales, así que al escribir a través de una llave-unión TypeScript exige el tipo intersección
|
|
12
|
+
* de todas las propiedades, que colapsa a `undefined` (TS2322). El consumidor que se topa con eso
|
|
13
|
+
* lo resuelve con `(progreso as Record<string, string>)[step] = status`, y ese cast desactiva
|
|
14
|
+
* justamente el cruce estación ↔ vocabulario que `IsKycStepStatusByStep` acaba de defender: un
|
|
15
|
+
* `watchlists: 'FAIL'` armado en memoria ya no lo detiene nadie.
|
|
16
|
+
*
|
|
17
|
+
* Concentrar el cast acá lo deja en UN solo lugar auditado, en vez de replicado en cada repo que
|
|
18
|
+
* consuma el evento. La seguridad real viene de haber validado el evento en el boundary; este helper
|
|
19
|
+
* solo evita que cada consumidor reinvente el atajo inseguro.
|
|
20
|
+
*
|
|
21
|
+
* ⚠️ NO valida la coherencia estación ↔ status: eso ya lo hizo el DTO en el boundary. Si se le pasa
|
|
22
|
+
* un par incoherente a mano, lo escribe — igual que lo haría el cast que reemplaza.
|
|
23
|
+
*
|
|
24
|
+
* SureKeep Fase 2 — progreso del KYC estación por estación.
|
|
25
|
+
*/
|
|
26
|
+
export declare function applyKycStep(progreso: KycSubChecks | undefined, step: KycSubCheckNameEnum, status: KycSubCheckStatusEnum | KycWatchlistStatusEnum): KycSubChecks;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.applyKycStep = applyKycStep;
|
|
4
|
+
/**
|
|
5
|
+
* Asienta el resultado de UNA estación (`KycStepCompletedV1`) sobre el progreso acumulado de la
|
|
6
|
+
* verificación, y devuelve un objeto NUEVO — no muta el que recibe.
|
|
7
|
+
*
|
|
8
|
+
* **Por qué esto vive en la lib y no en cada consumidor:** la asignación obvia
|
|
9
|
+
* `progreso[evento.step] = evento.status` **no compila**. Todos los campos de `KycSubChecks` son
|
|
10
|
+
* opcionales, así que al escribir a través de una llave-unión TypeScript exige el tipo intersección
|
|
11
|
+
* de todas las propiedades, que colapsa a `undefined` (TS2322). El consumidor que se topa con eso
|
|
12
|
+
* lo resuelve con `(progreso as Record<string, string>)[step] = status`, y ese cast desactiva
|
|
13
|
+
* justamente el cruce estación ↔ vocabulario que `IsKycStepStatusByStep` acaba de defender: un
|
|
14
|
+
* `watchlists: 'FAIL'` armado en memoria ya no lo detiene nadie.
|
|
15
|
+
*
|
|
16
|
+
* Concentrar el cast acá lo deja en UN solo lugar auditado, en vez de replicado en cada repo que
|
|
17
|
+
* consuma el evento. La seguridad real viene de haber validado el evento en el boundary; este helper
|
|
18
|
+
* solo evita que cada consumidor reinvente el atajo inseguro.
|
|
19
|
+
*
|
|
20
|
+
* ⚠️ NO valida la coherencia estación ↔ status: eso ya lo hizo el DTO en el boundary. Si se le pasa
|
|
21
|
+
* un par incoherente a mano, lo escribe — igual que lo haría el cast que reemplaza.
|
|
22
|
+
*
|
|
23
|
+
* SureKeep Fase 2 — progreso del KYC estación por estación.
|
|
24
|
+
*/
|
|
25
|
+
function applyKycStep(progreso, step, status) {
|
|
26
|
+
// El único cast del ecosistema para escribir por llave dinámica sobre `KycSubChecks`.
|
|
27
|
+
const siguiente = { ...(progreso ?? {}) };
|
|
28
|
+
siguiente[step] = status;
|
|
29
|
+
return siguiente;
|
|
30
|
+
}
|
package/bin/kyc/index.d.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
export * from './enums/KycVerificationStatusEnum';
|
|
2
2
|
export * from './enums/KycSubCheckStatusEnum';
|
|
3
3
|
export * from './enums/KycWatchlistStatusEnum';
|
|
4
|
+
export * from './enums/KycSubCheckNameEnum';
|
|
4
5
|
export * from './dtos/KycSubChecks';
|
|
6
|
+
export * from './validators/IsKycStepStatusByStep';
|
|
7
|
+
export * from './helpers/applyKycStep';
|
|
5
8
|
export * from './dtos/KycAddress';
|
|
6
9
|
export * from './dtos/responses/KycPersonData';
|
|
7
10
|
export * from './dtos/responses/KycByDirectoryResponse';
|
|
8
11
|
export * from './events/KycVerificationChangedV1';
|
|
12
|
+
export * from './events/KycStepCompletedV1';
|
package/bin/kyc/index.js
CHANGED
|
@@ -19,8 +19,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
19
19
|
__exportStar(require("./enums/KycVerificationStatusEnum"), exports);
|
|
20
20
|
__exportStar(require("./enums/KycSubCheckStatusEnum"), exports);
|
|
21
21
|
__exportStar(require("./enums/KycWatchlistStatusEnum"), exports);
|
|
22
|
+
__exportStar(require("./enums/KycSubCheckNameEnum"), exports);
|
|
22
23
|
// Sub-pasos descompuestos (SureKeep F2)
|
|
23
24
|
__exportStar(require("./dtos/KycSubChecks"), exports);
|
|
25
|
+
// Validators
|
|
26
|
+
__exportStar(require("./validators/IsKycStepStatusByStep"), exports);
|
|
27
|
+
// Helpers
|
|
28
|
+
__exportStar(require("./helpers/applyKycStep"), exports);
|
|
24
29
|
// Domicilio del INE normalizado por places (SureKeep F2)
|
|
25
30
|
__exportStar(require("./dtos/KycAddress"), exports);
|
|
26
31
|
// Response DTOs
|
|
@@ -28,3 +33,4 @@ __exportStar(require("./dtos/responses/KycPersonData"), exports);
|
|
|
28
33
|
__exportStar(require("./dtos/responses/KycByDirectoryResponse"), exports);
|
|
29
34
|
// Events
|
|
30
35
|
__exportStar(require("./events/KycVerificationChangedV1"), exports);
|
|
36
|
+
__exportStar(require("./events/KycStepCompletedV1"), exports);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ValidatorConstraintInterface, ValidationArguments } from 'class-validator';
|
|
2
|
+
/**
|
|
3
|
+
* Valida que el `status` de un `KycStepCompletedV1` pertenezca al vocabulario de SU estación:
|
|
4
|
+
* las de lista hablan `CLEAR`/`HIT`, las binarias `PASS`/`FAIL`.
|
|
5
|
+
*
|
|
6
|
+
* **Por qué un validador y no un `@IsIn` con los cuatro valores:** un `@IsIn(['PASS','FAIL','CLEAR','HIT'])`
|
|
7
|
+
* aceptaría `{ step: 'watchlists', status: 'FAIL' }`. Eso rompe el matiz que `KycWatchlistStatusEnum`
|
|
8
|
+
* existe para proteger — un HIT **no es un fallo**, es "que lo mire cumplimiento". Con ese evento
|
|
9
|
+
* colado, el consumidor lo escribiría en el progreso y la pantalla del vendedor marcaría como falla
|
|
10
|
+
* algo que solo requiere revisión. El candado del anidado (`KycSubChecks`) tiene que valer igual
|
|
11
|
+
* cuando la estación viaja suelta.
|
|
12
|
+
*
|
|
13
|
+
* Es la misma razón por la que `KycSubChecks` tipa cada campo a su enum en vez de a un string.
|
|
14
|
+
*
|
|
15
|
+
* Uso:
|
|
16
|
+
* ```ts
|
|
17
|
+
* @Validate(IsKycStepStatusByStep)
|
|
18
|
+
* status!: KycSubCheckStatusEnum | KycWatchlistStatusEnum;
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* SureKeep Fase 2 — progreso del KYC estación por estación.
|
|
22
|
+
*/
|
|
23
|
+
export declare class IsKycStepStatusByStep implements ValidatorConstraintInterface {
|
|
24
|
+
validate(value: unknown, args: ValidationArguments): boolean;
|
|
25
|
+
defaultMessage(args: ValidationArguments): string;
|
|
26
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.IsKycStepStatusByStep = void 0;
|
|
10
|
+
const class_validator_1 = require("class-validator");
|
|
11
|
+
const KycSubCheckNameEnum_1 = require("../enums/KycSubCheckNameEnum");
|
|
12
|
+
const KycSubCheckStatusEnum_1 = require("../enums/KycSubCheckStatusEnum");
|
|
13
|
+
const KycWatchlistStatusEnum_1 = require("../enums/KycWatchlistStatusEnum");
|
|
14
|
+
const VALORES_LISTA = Object.values(KycWatchlistStatusEnum_1.KycWatchlistStatusEnum);
|
|
15
|
+
const VALORES_BINARIO = Object.values(KycSubCheckStatusEnum_1.KycSubCheckStatusEnum);
|
|
16
|
+
/**
|
|
17
|
+
* Valida que el `status` de un `KycStepCompletedV1` pertenezca al vocabulario de SU estación:
|
|
18
|
+
* las de lista hablan `CLEAR`/`HIT`, las binarias `PASS`/`FAIL`.
|
|
19
|
+
*
|
|
20
|
+
* **Por qué un validador y no un `@IsIn` con los cuatro valores:** un `@IsIn(['PASS','FAIL','CLEAR','HIT'])`
|
|
21
|
+
* aceptaría `{ step: 'watchlists', status: 'FAIL' }`. Eso rompe el matiz que `KycWatchlistStatusEnum`
|
|
22
|
+
* existe para proteger — un HIT **no es un fallo**, es "que lo mire cumplimiento". Con ese evento
|
|
23
|
+
* colado, el consumidor lo escribiría en el progreso y la pantalla del vendedor marcaría como falla
|
|
24
|
+
* algo que solo requiere revisión. El candado del anidado (`KycSubChecks`) tiene que valer igual
|
|
25
|
+
* cuando la estación viaja suelta.
|
|
26
|
+
*
|
|
27
|
+
* Es la misma razón por la que `KycSubChecks` tipa cada campo a su enum en vez de a un string.
|
|
28
|
+
*
|
|
29
|
+
* Uso:
|
|
30
|
+
* ```ts
|
|
31
|
+
* @Validate(IsKycStepStatusByStep)
|
|
32
|
+
* status!: KycSubCheckStatusEnum | KycWatchlistStatusEnum;
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* SureKeep Fase 2 — progreso del KYC estación por estación.
|
|
36
|
+
*/
|
|
37
|
+
let IsKycStepStatusByStep = class IsKycStepStatusByStep {
|
|
38
|
+
validate(value, args) {
|
|
39
|
+
if (typeof value !== 'string')
|
|
40
|
+
return false;
|
|
41
|
+
const step = args.object.step;
|
|
42
|
+
// Un `step` fuera del enum ya falla por su propio `@IsEnum`. Acá se rechaza igual en vez de
|
|
43
|
+
// dejar pasar el status: sin estación conocida no hay vocabulario contra el cual validar.
|
|
44
|
+
if (!Object.values(KycSubCheckNameEnum_1.KycSubCheckNameEnum).includes(step))
|
|
45
|
+
return false;
|
|
46
|
+
return KycSubCheckNameEnum_1.KYC_WATCHLIST_STEPS.includes(step)
|
|
47
|
+
? VALORES_LISTA.includes(value)
|
|
48
|
+
: VALORES_BINARIO.includes(value);
|
|
49
|
+
}
|
|
50
|
+
defaultMessage(args) {
|
|
51
|
+
const step = args.object.step;
|
|
52
|
+
if (KycSubCheckNameEnum_1.KYC_WATCHLIST_STEPS.includes(step)) {
|
|
53
|
+
return `status de la estación ${String(step)} debe ser ${VALORES_LISTA.join(' o ')} (un HIT no es un fallo)`;
|
|
54
|
+
}
|
|
55
|
+
if (Object.values(KycSubCheckNameEnum_1.KycSubCheckNameEnum).includes(step)) {
|
|
56
|
+
return `status de la estación ${String(step)} debe ser ${VALORES_BINARIO.join(' o ')}`;
|
|
57
|
+
}
|
|
58
|
+
return 'status no se puede validar sin un step reconocido';
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
exports.IsKycStepStatusByStep = IsKycStepStatusByStep;
|
|
62
|
+
exports.IsKycStepStatusByStep = IsKycStepStatusByStep = __decorate([
|
|
63
|
+
(0, class_validator_1.ValidatorConstraint)({ name: 'IsKycStepStatusByStep', async: false })
|
|
64
|
+
], IsKycStepStatusByStep);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rol RBAC mínimo que puede modificar un parámetro (RBAC a nivel de parámetro, modelo-datos §8).
|
|
3
|
+
* `retailer_admin` = "Admin VentasLuga" de M6 §8. Valores = identificadores de rol de RBAC F0.
|
|
4
|
+
* TD-004: confirmar mapeo super_admin ↔ platform_super_admin con el naming real de F0.
|
|
5
|
+
* @enum {string}
|
|
6
|
+
*/
|
|
7
|
+
export declare enum ModifiableByRoleEnum {
|
|
8
|
+
SUPER_ADMIN = "super_admin",
|
|
9
|
+
SOFOM_ADMIN = "sofom_admin",
|
|
10
|
+
RETAILER_ADMIN = "retailer_admin"
|
|
11
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ModifiableByRoleEnum = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Rol RBAC mínimo que puede modificar un parámetro (RBAC a nivel de parámetro, modelo-datos §8).
|
|
6
|
+
* `retailer_admin` = "Admin VentasLuga" de M6 §8. Valores = identificadores de rol de RBAC F0.
|
|
7
|
+
* TD-004: confirmar mapeo super_admin ↔ platform_super_admin con el naming real de F0.
|
|
8
|
+
* @enum {string}
|
|
9
|
+
*/
|
|
10
|
+
var ModifiableByRoleEnum;
|
|
11
|
+
(function (ModifiableByRoleEnum) {
|
|
12
|
+
ModifiableByRoleEnum["SUPER_ADMIN"] = "super_admin";
|
|
13
|
+
ModifiableByRoleEnum["SOFOM_ADMIN"] = "sofom_admin";
|
|
14
|
+
ModifiableByRoleEnum["RETAILER_ADMIN"] = "retailer_admin";
|
|
15
|
+
})(ModifiableByRoleEnum || (exports.ModifiableByRoleEnum = ModifiableByRoleEnum = {}));
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { CurrencyId } from '../../currency/enums/CurrencyId';
|
|
2
|
+
/**
|
|
3
|
+
* Comisión que cobra un punto de cash-in (lo que el lambda MUESTRA, no cobra).
|
|
4
|
+
* - GreenDot (US): representativo por cadena → `fixed` + `cap` (cada tienda cobra hasta el tope).
|
|
5
|
+
* - Passport (MX): por red → `fixed` + `percentage`.
|
|
6
|
+
* Response DTO — sin decoradores de validación.
|
|
7
|
+
*/
|
|
8
|
+
export declare class CashInFeeDto {
|
|
9
|
+
/** Comisión fija, en la moneda del país. */
|
|
10
|
+
fixed?: number;
|
|
11
|
+
/** Porcentaje del monto depositado (0–100). */
|
|
12
|
+
percentage?: number;
|
|
13
|
+
/** Tope máximo de comisión (GreenDot: el asociado cobra hasta este cap). */
|
|
14
|
+
cap?: number;
|
|
15
|
+
/** Moneda del fee: USD (GreenDot) | MXN (Passport). */
|
|
16
|
+
currency: CurrencyId;
|
|
17
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CashInFeeDto = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Comisión que cobra un punto de cash-in (lo que el lambda MUESTRA, no cobra).
|
|
6
|
+
* - GreenDot (US): representativo por cadena → `fixed` + `cap` (cada tienda cobra hasta el tope).
|
|
7
|
+
* - Passport (MX): por red → `fixed` + `percentage`.
|
|
8
|
+
* Response DTO — sin decoradores de validación.
|
|
9
|
+
*/
|
|
10
|
+
class CashInFeeDto {
|
|
11
|
+
}
|
|
12
|
+
exports.CashInFeeDto = CashInFeeDto;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { MfaMethodEnum } from '../enums/MfaMethodEnum';
|
|
2
|
+
/**
|
|
3
|
+
* Body del POST /auth/resend-otp (público, anónimo). DEC-RBAC-054.
|
|
4
|
+
* Reenvía el OTP del login re-disparando el challenge real CUSTOM_AUTH (EMAIL_OTP) para la
|
|
5
|
+
* identidad SIN password. `tenantId` obligatorio (DEC-064 — el picker ya lo resolvió, NO "solo email").
|
|
6
|
+
* El email se normaliza lowercase server-side. Postura anti-enumeración: respuesta 200 genérica
|
|
7
|
+
* siempre, sin filtrar existencia (ver AuthLoginManager.resendChallengeOtp).
|
|
8
|
+
*/
|
|
9
|
+
export declare class ResendOtpRequest {
|
|
10
|
+
email: string;
|
|
11
|
+
tenantId: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Respuesta del resend-otp. `session`/`mfaMethod` frescos del nuevo challenge CUSTOM_AUTH.
|
|
15
|
+
* Plain sin validators (no validamos lo que mandamos al cliente — fiado-validation-and-dtos § 7).
|
|
16
|
+
* Ambos opcionales: en los caminos de rechazo silencioso (anti-enumeración) o ramas sin CUSTOM_AUTH
|
|
17
|
+
* el server responde 200 genérico sin session ni método.
|
|
18
|
+
*/
|
|
19
|
+
export interface ResendOtpResponse {
|
|
20
|
+
session?: string;
|
|
21
|
+
mfaMethod?: MfaMethodEnum;
|
|
22
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.ResendOtpRequest = void 0;
|
|
13
|
+
const class_transformer_1 = require("class-transformer");
|
|
14
|
+
const class_validator_1 = require("class-validator");
|
|
15
|
+
/**
|
|
16
|
+
* Body del POST /auth/resend-otp (público, anónimo). DEC-RBAC-054.
|
|
17
|
+
* Reenvía el OTP del login re-disparando el challenge real CUSTOM_AUTH (EMAIL_OTP) para la
|
|
18
|
+
* identidad SIN password. `tenantId` obligatorio (DEC-064 — el picker ya lo resolvió, NO "solo email").
|
|
19
|
+
* El email se normaliza lowercase server-side. Postura anti-enumeración: respuesta 200 genérica
|
|
20
|
+
* siempre, sin filtrar existencia (ver AuthLoginManager.resendChallengeOtp).
|
|
21
|
+
*/
|
|
22
|
+
class ResendOtpRequest {
|
|
23
|
+
}
|
|
24
|
+
exports.ResendOtpRequest = ResendOtpRequest;
|
|
25
|
+
__decorate([
|
|
26
|
+
(0, class_transformer_1.Expose)(),
|
|
27
|
+
(0, class_validator_1.IsEmail)(),
|
|
28
|
+
(0, class_validator_1.IsNotEmpty)(),
|
|
29
|
+
__metadata("design:type", String)
|
|
30
|
+
], ResendOtpRequest.prototype, "email", void 0);
|
|
31
|
+
__decorate([
|
|
32
|
+
(0, class_transformer_1.Expose)(),
|
|
33
|
+
(0, class_validator_1.IsString)(),
|
|
34
|
+
(0, class_validator_1.IsNotEmpty)(),
|
|
35
|
+
__metadata("design:type", String)
|
|
36
|
+
], ResendOtpRequest.prototype, "tenantId", void 0);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Body del POST /self-register/resend-otp (público, anónimo). DEC-RBAC-054.
|
|
3
|
+
* Re-envía el OTP del autoregistro (mecanismo messages-business, NO Cognito) tras validar un
|
|
4
|
+
* `pending` existente. Misma postura anti-enumeración del start. El email se normaliza lowercase
|
|
5
|
+
* server-side. DTO propio por endpoint (NO reusa SelfRegisterStartRequest, que exige roleId/scope/
|
|
6
|
+
* scopeRef, ni SelfRegisterVerifyOtpRequest, que exige otp).
|
|
7
|
+
*/
|
|
8
|
+
export declare class ResendSelfRegisterOtpRequest {
|
|
9
|
+
tenantId: string;
|
|
10
|
+
email: string;
|
|
11
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.ResendSelfRegisterOtpRequest = void 0;
|
|
13
|
+
const class_transformer_1 = require("class-transformer");
|
|
14
|
+
const class_validator_1 = require("class-validator");
|
|
15
|
+
/**
|
|
16
|
+
* Body del POST /self-register/resend-otp (público, anónimo). DEC-RBAC-054.
|
|
17
|
+
* Re-envía el OTP del autoregistro (mecanismo messages-business, NO Cognito) tras validar un
|
|
18
|
+
* `pending` existente. Misma postura anti-enumeración del start. El email se normaliza lowercase
|
|
19
|
+
* server-side. DTO propio por endpoint (NO reusa SelfRegisterStartRequest, que exige roleId/scope/
|
|
20
|
+
* scopeRef, ni SelfRegisterVerifyOtpRequest, que exige otp).
|
|
21
|
+
*/
|
|
22
|
+
class ResendSelfRegisterOtpRequest {
|
|
23
|
+
}
|
|
24
|
+
exports.ResendSelfRegisterOtpRequest = ResendSelfRegisterOtpRequest;
|
|
25
|
+
__decorate([
|
|
26
|
+
(0, class_transformer_1.Expose)(),
|
|
27
|
+
(0, class_validator_1.IsString)(),
|
|
28
|
+
(0, class_validator_1.IsNotEmpty)(),
|
|
29
|
+
__metadata("design:type", String)
|
|
30
|
+
], ResendSelfRegisterOtpRequest.prototype, "tenantId", void 0);
|
|
31
|
+
__decorate([
|
|
32
|
+
(0, class_transformer_1.Expose)(),
|
|
33
|
+
(0, class_validator_1.IsEmail)(),
|
|
34
|
+
(0, class_validator_1.IsNotEmpty)(),
|
|
35
|
+
__metadata("design:type", String)
|
|
36
|
+
], ResendSelfRegisterOtpRequest.prototype, "email", void 0);
|
|
@@ -22,6 +22,12 @@ export interface KycSdkMetadata {
|
|
|
22
22
|
phoneNumber: string;
|
|
23
23
|
typeOfDocumentId?: string;
|
|
24
24
|
isPartial?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Idioma con el que Metamap fija el flujo del widget. El wizard lo manda como `'es'` para que
|
|
27
|
+
* la verificación salga en español por defecto (si no viaja, Metamap arranca en su default, inglés).
|
|
28
|
+
* Va opcional en el tipo porque es un ajuste de presentación del SDK, no parte de la correlación.
|
|
29
|
+
*/
|
|
30
|
+
fixedLanguage?: string;
|
|
25
31
|
customInput?: boolean;
|
|
26
32
|
userPhotoLink?: string;
|
|
27
33
|
userPhotoLinkHash?: string;
|
package/package.json
CHANGED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nombre de la estación del KYC que reporta un `KycStepCompletedV1`.
|
|
3
|
+
*
|
|
4
|
+
* ⚠️ Los VALORES son exactamente las llaves de `KycSubChecks`. No es coincidencia ni estética: el
|
|
5
|
+
* consumidor arma el progreso de la verificación asentando cada estación sobre el acumulado, sin
|
|
6
|
+
* tabla de traducción. Si un valor dejara de coincidir con su llave, el progreso quedaría con un
|
|
7
|
+
* campo huérfano y la estación real nunca se marcaría — una falla muda. Ese acumulado se arma con
|
|
8
|
+
* `applyKycStep`, NO indexando a mano: la asignación directa no compila (los campos de `KycSubChecks`
|
|
9
|
+
* son opcionales) y el cast que la destraba desactiva el candado del vocabulario.
|
|
10
|
+
*
|
|
11
|
+
* Son las 8 del contrato, no las 13 que Metamap puede emitir: las otras (ip-validation, selfie,
|
|
12
|
+
* voice, duplicate-user-detection, curp/ine validation) no forman parte de `KycSubChecks` y por lo
|
|
13
|
+
* tanto no viajan. Ampliar la lista es un cambio de contrato en los dos lados, no un valor más aquí.
|
|
14
|
+
*
|
|
15
|
+
* SureKeep Fase 2 — progreso del KYC estación por estación.
|
|
16
|
+
*/
|
|
17
|
+
export enum KycSubCheckNameEnum {
|
|
18
|
+
DOCUMENT_READING = 'documentReading',
|
|
19
|
+
TEMPLATE_MATCHING = 'templateMatching',
|
|
20
|
+
ALTERATION_DETECTION = 'alterationDetection',
|
|
21
|
+
FACEMATCH = 'facematch',
|
|
22
|
+
LIVENESS = 'liveness',
|
|
23
|
+
AGE_CHECK = 'ageCheck',
|
|
24
|
+
WATCHLISTS = 'watchlists',
|
|
25
|
+
PREMIUM_AML_WATCHLISTS = 'premiumAmlWatchlists',
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Las estaciones de LISTA — las únicas cuyo resultado se expresa en `CLEAR`/`HIT`. El resto son
|
|
30
|
+
* binarias (`PASS`/`FAIL`). Se exporta porque el validador del evento y los consumidores necesitan
|
|
31
|
+
* la MISMA partición: duplicarla en cada repo es drift garantizado.
|
|
32
|
+
*/
|
|
33
|
+
export const KYC_WATCHLIST_STEPS: readonly KycSubCheckNameEnum[] = [
|
|
34
|
+
KycSubCheckNameEnum.WATCHLISTS,
|
|
35
|
+
KycSubCheckNameEnum.PREMIUM_AML_WATCHLISTS,
|
|
36
|
+
];
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { Expose } from 'class-transformer';
|
|
2
|
+
import { IsEnum, IsIn, IsISO8601, IsNotEmpty, IsOptional, IsString, Matches, Validate } from 'class-validator';
|
|
3
|
+
import { KycSubCheckNameEnum } from '../enums/KycSubCheckNameEnum';
|
|
4
|
+
import { KycSubCheckStatusEnum } from '../enums/KycSubCheckStatusEnum';
|
|
5
|
+
import { KycWatchlistStatusEnum } from '../enums/KycWatchlistStatusEnum';
|
|
6
|
+
import { IsKycStepStatusByStep } from '../validators/IsKycStepStatusByStep';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* El resultado de UNA estación del KYC, publicado por `kyc-metamap-webhook` en cuanto Metamap la
|
|
10
|
+
* cierra (`step_completed`) — no al final de la verificación.
|
|
11
|
+
*
|
|
12
|
+
* **Por qué existe:** `KycVerificationChangedV1` llega UNA vez, cuando todo terminó. Con eso la
|
|
13
|
+
* pantalla del vendedor solo puede mostrar una ruedita hasta el veredicto. Metamap emite un webhook
|
|
14
|
+
* por estación y el webhook ya los recibía; lo único que faltaba era reenviarlos. Este evento es ese
|
|
15
|
+
* reenvío: deja que SureKeep pinte el avance estación por estación mientras el cliente espera.
|
|
16
|
+
*
|
|
17
|
+
* **Convive con `KycVerificationChangedV1` en la MISMA cola** (`RetailKycInboundQueue`). El
|
|
18
|
+
* discriminador es la presencia de `step`: si lo trae, es una estación; si no, es el cierre. Por eso
|
|
19
|
+
* `KycVerificationChangedV1` no se tocó — un consumidor viejo que no conoce este evento lo descarta
|
|
20
|
+
* en su propio parseo sin romperse.
|
|
21
|
+
*
|
|
22
|
+
* 🔴 **NO es un veredicto.** Un `FAIL` acá no rechaza nada ni frena la venta: el cliente reintenta
|
|
23
|
+
* dentro del mismo SDK, en el mismo dispositivo, sin salir de la sesión. La decisión sigue viniendo
|
|
24
|
+
* del estado consolidado (`RetailKycStatusEnum`) que llega en el cierre. Esto es progreso visible,
|
|
25
|
+
* no política.
|
|
26
|
+
*
|
|
27
|
+
* 🔴 **Sin PII, a propósito.** No lleva `person`, ni `address`, ni `peopleId`: una estación solo
|
|
28
|
+
* necesita decir cuál es y cómo salió. Lo que sí lleva son identificadores operacionales, que es lo
|
|
29
|
+
* único que se puede loguear.
|
|
30
|
+
*
|
|
31
|
+
* **Idempotencia:** la clave es `(verificationId, step)` — NO viaja `eventId` a propósito. Una
|
|
32
|
+
* estación cierra una sola vez por verificación, y asentar su resultado sobre el progreso
|
|
33
|
+
* (`applyKycStep`) es idempotente por diseño: reprocesar el mismo evento escribe el mismo valor. Que
|
|
34
|
+
* quede dicho acá y no en la cabeza de cada consumidor: deducirlo mal lleva a deduplicar por el
|
|
35
|
+
* `MessageId` de SQS, que cambia en cada reintento de entrega y por lo tanto no deduplica nada.
|
|
36
|
+
*
|
|
37
|
+
* SureKeep Fase 2 — progreso del KYC estación por estación.
|
|
38
|
+
*/
|
|
39
|
+
export class KycStepCompletedV1 {
|
|
40
|
+
/**
|
|
41
|
+
* Discriminador EXPLÍCITO del tipo de evento. Opcional hoy, y esa es toda la gracia: agregarlo
|
|
42
|
+
* ahora es gratis; agregarlo después de que el webhook publique en Dev ya sería breaking.
|
|
43
|
+
*
|
|
44
|
+
* Sin él, el ruteo del consumidor depende de la presencia de `step`. Eso funciona con los dos
|
|
45
|
+
* eventos de hoy, pero Metamap también emite `step_started`: el día que alguien publique un
|
|
46
|
+
* `KycStepStartedV1` traería `step` sin `status`, el consumidor lo rutearía a esta rama y el
|
|
47
|
+
* mensaje caería al DLQ etiquetado como "corrupto" en vez de "no soportado" — se diagnostica
|
|
48
|
+
* como bug del webhook cuando en realidad es un evento nuevo legítimo.
|
|
49
|
+
*
|
|
50
|
+
* Regla de ruteo hacia adelante: si `eventType` viene y matchea → esta rama; si no viene
|
|
51
|
+
* ninguno → el cierre legacy (`KycVerificationChangedV1`).
|
|
52
|
+
*/
|
|
53
|
+
@Expose() @IsOptional() @IsIn(['KycStepCompletedV1'])
|
|
54
|
+
eventType?: string;
|
|
55
|
+
|
|
56
|
+
/** Correlaciona la estación con su verificación. */
|
|
57
|
+
@Expose() @IsString() @IsNotEmpty()
|
|
58
|
+
verificationId!: string;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Con esto el consumidor encuentra la sesión (el gate de pertenencia). DEC-KYC-015: `@IsNotEmpty()`
|
|
62
|
+
* y no solo `@IsString()` — un `''` colado dejaría el gate sin nada contra qué buscar y el evento
|
|
63
|
+
* fallaría tres pasos río abajo en vez de en el boundary.
|
|
64
|
+
*/
|
|
65
|
+
@Expose() @IsString() @IsNotEmpty()
|
|
66
|
+
directoryId!: string;
|
|
67
|
+
|
|
68
|
+
/** Cuál estación cerró. El valor coincide con su llave en `KycSubChecks`. */
|
|
69
|
+
@Expose() @IsEnum(KycSubCheckNameEnum)
|
|
70
|
+
step!: KycSubCheckNameEnum;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Cómo salió, en el vocabulario de SU estación: `CLEAR`/`HIT` en las de lista, `PASS`/`FAIL` en
|
|
74
|
+
* las binarias. El cruce lo valida `IsKycStepStatusByStep` — un `@IsIn` con los cuatro valores
|
|
75
|
+
* aceptaría `watchlists: FAIL` y perdería el matiz de que un HIT no es un fallo.
|
|
76
|
+
*/
|
|
77
|
+
@Expose() @Validate(IsKycStepStatusByStep)
|
|
78
|
+
status!: KycSubCheckStatusEnum | KycWatchlistStatusEnum;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Cuándo cerró la estación, según Metamap. **Obligatorio, y no es decorativo:** la cola es SQS
|
|
82
|
+
* estándar, que no garantiza orden. Sin una marca de tiempo por evento, una estación reordenada
|
|
83
|
+
* puede pisar el cierre de la verificación y "des-cerrar" la lista ya completa en pantalla. El
|
|
84
|
+
* consumidor descarta las que llegan más viejas que lo que ya escribió.
|
|
85
|
+
*
|
|
86
|
+
* ⚠️ **UTC estricto, con `Z` y sin offset** — por eso el `@Matches` además del `@IsISO8601()`.
|
|
87
|
+
* `@IsISO8601()` solo NO alcanza: acepta `2026-07-26` (sin hora), `2026-W30-1` (fecha de semana)
|
|
88
|
+
* y, sobre todo, `2026-07-26T15:05:00+05:00`. Ese último es el que hace daño. Si una estación
|
|
89
|
+
* llega con offset `+05:00` y la siguiente en `Z`, la comparación léxica que hace el consumidor
|
|
90
|
+
* concluye que la más nueva es más vieja y la DESCARTA: la estación nunca se pinta y nada truena.
|
|
91
|
+
*
|
|
92
|
+
* Normalizar a UTC es trabajo del webhook — es el traductor del proveedor, y este candado existe
|
|
93
|
+
* para que si algún día deja de hacerlo, el evento caiga al DLQ en el boundary en vez de
|
|
94
|
+
* corromper el orden en silencio.
|
|
95
|
+
*/
|
|
96
|
+
@Expose() @IsString() @IsISO8601({ strict: true })
|
|
97
|
+
@Matches(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/, {
|
|
98
|
+
message: 'occurredAt debe ser ISO-8601 en UTC con Z (ej. 2026-07-26T10:05:00.000Z): un offset rompe el orden léxico',
|
|
99
|
+
})
|
|
100
|
+
occurredAt!: string;
|
|
101
|
+
|
|
102
|
+
/** El flujo de Metamap que corrió. Solo para diagnóstico — nadie ramifica por él. */
|
|
103
|
+
@Expose() @IsOptional() @IsString()
|
|
104
|
+
metamapFlowId?: string;
|
|
105
|
+
}
|