@dynamicore/jumio-sdk 1.0.0 → 1.0.2
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/README.md +238 -160
- package/dist/client-BwEiGMPy.d.mts +515 -0
- package/dist/client-BwEiGMPy.d.ts +515 -0
- package/dist/core/index.d.mts +94 -4
- package/dist/core/index.d.ts +94 -4
- package/dist/core/index.js +545 -125
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +506 -83
- package/dist/core/index.mjs.map +1 -1
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +747 -126
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +706 -83
- package/dist/index.mjs.map +1 -1
- package/dist/react/index.d.mts +146 -2
- package/dist/react/index.d.ts +146 -2
- package/dist/react/index.js +766 -142
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +766 -143
- package/dist/react/index.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/client-BR1xIZ0X.d.mts +0 -252
- package/dist/client-BR1xIZ0X.d.ts +0 -252
package/README.md
CHANGED
|
@@ -1,25 +1,26 @@
|
|
|
1
1
|
# @dynamicore/jumio-sdk
|
|
2
2
|
|
|
3
|
-
> SDK modular, tipado y agnóstico para la validación de identidad
|
|
3
|
+
> SDK modular, tipado y agnóstico para la validación de identidad mediante **Jumio** en el ecosistema DynamiCore. Soporta tanto el flujo de **Hosted Webflow (Redirección Web)** como el de **Carga Directa de Documentos (INE API)**.
|
|
4
4
|
|
|
5
5
|
[](https://www.typescriptlang.org/)
|
|
6
6
|
[](https://react.dev/)
|
|
7
7
|
[](#)
|
|
8
8
|
[](https://opensource.org/licenses/MIT)
|
|
9
9
|
|
|
10
|
+
> [!IMPORTANT]
|
|
11
|
+
> Para poder utilizar esta librería, la compañía debe tener habilitado el módulo de **Jumio** desde el backend. Si el módulo no está habilitado para la compañía, la integración no estará disponible aunque el SDK esté instalado y configurado correctamente.
|
|
12
|
+
|
|
10
13
|
---
|
|
11
14
|
|
|
12
|
-
## 🌟
|
|
15
|
+
## 🌟 Modalidades de Verificación Soportadas
|
|
13
16
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
- **Tipado Estricto y Errores Específicos**: Jerarquía clara de errores (`JumioTimeoutError`, `JumioValidationError`, `JumioImageProcessingError`, etc.).
|
|
22
|
-
- **Bundle Dual Optimizado**: Salida ESM (`.js`) y CommonJS (`.cjs`) con source maps y definiciones TypeScript (`.d.ts`).
|
|
17
|
+
| Característica | 🌐 1. Hosted Webflow (Redirección) | 📄 2. Carga Directa (INE API) |
|
|
18
|
+
| :--- | :--- | :--- |
|
|
19
|
+
| **Experiencia de Usuario** | El usuario es redirigido a la interfaz alojada oficial de Jumio para captura biométrica y de documentos. | El usuario permanece en tu app; tu interfaz captura/sube las imágenes de la INE. |
|
|
20
|
+
| **Casos de Uso** | Panel web, onboarding sin cámara nativa, verificación biométrica completa + Liveness. | Formularios web/móviles integrados con controles de archivo personalizados. |
|
|
21
|
+
| **Entrada Requerida** | `clientId`, `successUrl`, `errorUrl` | `clientId`, `frontImage`, `backImage` |
|
|
22
|
+
| **Hooks de React** | `useJumioWebflow` | `useJumioVerification` |
|
|
23
|
+
| **Métodos de Core** | `startWebflow`, `getWebflowStatus`, `pollWebflowStatus`, `parseWebflowReturnParams`, `buildRedirectUrl` | `startIneVerification`, `getIneStatus`, `verifyIne`, `pollIneStatus` |
|
|
23
24
|
|
|
24
25
|
---
|
|
25
26
|
|
|
@@ -28,110 +29,229 @@
|
|
|
28
29
|
### Desde npm / registro privado:
|
|
29
30
|
```bash
|
|
30
31
|
npm install @dynamicore/jumio-sdk axios
|
|
31
|
-
# o con pnpm:
|
|
32
|
+
# o con pnpm / yarn:
|
|
32
33
|
pnpm add @dynamicore/jumio-sdk axios
|
|
33
|
-
# o con yarn:
|
|
34
|
-
yarn add @dynamicore/jumio-sdk axios
|
|
35
34
|
```
|
|
36
35
|
|
|
37
|
-
###
|
|
36
|
+
### En proyectos locales (Monorepo o enlace local):
|
|
38
37
|
```bash
|
|
39
38
|
npm install file:../jumio-sdk
|
|
40
39
|
```
|
|
41
40
|
|
|
42
41
|
---
|
|
43
42
|
|
|
44
|
-
## 🚀
|
|
43
|
+
## 🚀 Guías y Ejemplos de Uso
|
|
45
44
|
|
|
46
|
-
|
|
45
|
+
---
|
|
47
46
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
### 🌐 Servicio 1: Hosted Webflow (Redirección Web)
|
|
48
|
+
|
|
49
|
+
En este flujo, la app solicita una sesión webflow, redirige al cliente a Jumio (`href`) y consulta el veredicto al regresar.
|
|
50
|
+
|
|
51
|
+
> [!NOTE]
|
|
52
|
+
> Llegar a `successUrl` solo significa que el usuario **completó los pasos en Jumio**, no que fue aprobado. Llegar a `errorUrl` significa que **abandonó o falló el flujo**. El veredicto real (`PASSED` / `WARNING` / `REJECTED` + `extraction`) solo se obtiene consultando el estado con `accountId` + `workflowId` (`getWebflowStatus` una vez, o `pollWebflowStatus` con sondeo). Por eso ambos suelen apuntar a la misma ruta `/verify/return`.
|
|
51
53
|
|
|
52
|
-
|
|
53
|
-
const [frontFile, setFrontFile] = useState<File | null>(null);
|
|
54
|
-
const [backFile, setBackFile] = useState<File | null>(null);
|
|
54
|
+
#### A. Ejemplo con React / Next.js (`useJumioWebflow`)
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
error,
|
|
65
|
-
reset,
|
|
66
|
-
} = useJumioVerification({
|
|
56
|
+
**Paso 1: Iniciar la verificación (Pantalla de Inicio)**
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
import React from "react";
|
|
60
|
+
import { useJumioWebflow } from "@dynamicore/jumio-sdk/react";
|
|
61
|
+
|
|
62
|
+
export function StartIdentityVerification({ clientId }: { clientId: string }) {
|
|
63
|
+
const { startWebflow, isStarting, error } = useJumioWebflow({
|
|
67
64
|
baseUrl: process.env.NEXT_PUBLIC_API_URL || "https://front.dynamicore.io",
|
|
68
|
-
context: process.env.
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
if (res.valid) {
|
|
72
|
-
alert("¡Tu INE ha sido validada exitosamente!");
|
|
73
|
-
} else {
|
|
74
|
-
alert(`No se pudo validar el INE: ${res.errorMessage || "Documento no legible"}`);
|
|
75
|
-
}
|
|
76
|
-
},
|
|
77
|
-
onStatusError: (err) => {
|
|
78
|
-
console.error("Error en validación en segundo plano:", err);
|
|
79
|
-
},
|
|
65
|
+
context: process.env.NEXT_PUBLIC_DYNAMICORE_CONTEXT,
|
|
66
|
+
// Opcional: URL base para proxy de redirección
|
|
67
|
+
redirectProxyUrl: "https://inllhuznm2.execute-api.us-west-2.amazonaws.com/prod/jumio/redirect/",
|
|
80
68
|
});
|
|
81
69
|
|
|
82
|
-
const
|
|
83
|
-
e.preventDefault();
|
|
84
|
-
if (!frontFile || !backFile) return;
|
|
85
|
-
|
|
70
|
+
const handleStart = async () => {
|
|
86
71
|
try {
|
|
87
|
-
|
|
88
|
-
|
|
72
|
+
const baseUrl = window.location.origin;
|
|
73
|
+
await startWebflow({
|
|
89
74
|
clientId,
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
75
|
+
successUrl: `${baseUrl}/verify/return`,
|
|
76
|
+
errorUrl: `${baseUrl}/verify/return`,
|
|
77
|
+
useRedirectProxy: true, // Codifica las URLs en Base64 URL-safe con proxy
|
|
78
|
+
openInNewTab: true, // Abre Jumio en una nueva pestaña (window.open) sin salir de la app
|
|
79
|
+
// autoRedirect: true, // O redirige en la misma pestaña (window.location.href)
|
|
93
80
|
});
|
|
94
81
|
|
|
95
|
-
if (res.valid) {
|
|
96
|
-
onNext(); // Avanzar al siguiente paso del onboarding
|
|
97
|
-
}
|
|
98
82
|
} catch (err) {
|
|
99
83
|
console.error("Error al iniciar verificación:", err);
|
|
100
84
|
}
|
|
101
85
|
};
|
|
102
86
|
|
|
103
87
|
return (
|
|
104
|
-
<
|
|
88
|
+
<div>
|
|
89
|
+
<h3>Verificación de Identidad</h3>
|
|
90
|
+
{error && <p style={{ color: "red" }}>{error.message}</p>}
|
|
91
|
+
<button onClick={handleStart} disabled={isStarting}>
|
|
92
|
+
{isStarting ? "Cargando..." : "Iniciar Verificación con Jumio"}
|
|
93
|
+
</button>
|
|
94
|
+
</div>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Paso 2: Procesar el retorno (Pantalla `/verify/return`)**
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
import React, { useEffect } from "react";
|
|
103
|
+
import { useJumioWebflow } from "@dynamicore/jumio-sdk/react";
|
|
104
|
+
|
|
105
|
+
export function IdentityReturnPage({ clientId }: { clientId: string }) {
|
|
106
|
+
const { parseReturnParams, checkResult, isChecking, result, isValid, isRejected, error } =
|
|
107
|
+
useJumioWebflow({
|
|
108
|
+
baseUrl: process.env.NEXT_PUBLIC_API_URL,
|
|
109
|
+
context: process.env.NEXT_PUBLIC_DYNAMICORE_CONTEXT,
|
|
110
|
+
onResult: (res) => {
|
|
111
|
+
if (res.valid) {
|
|
112
|
+
console.log("¡Verificación aprobada!", res.extraction);
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
// 1. Extraer accountId y workflowId de los query params de la URL
|
|
119
|
+
const { accountId, workflowId } = parseReturnParams();
|
|
120
|
+
|
|
121
|
+
if (accountId && workflowId) {
|
|
122
|
+
// 2. Sondear el resultado final (acepta { maxAttempts, pollingIntervalMs, signal })
|
|
123
|
+
// Para una sola consulta sin sondeo: await client.getWebflowStatus({ accountId, workflowId, clientId })
|
|
124
|
+
// Retorna null si aún está pendiente.
|
|
125
|
+
checkResult({ accountId, workflowId, clientId });
|
|
126
|
+
}
|
|
127
|
+
return () => cancel(); // Limpia el sondeo si el componente se desmonta (abort resetea isChecking)
|
|
128
|
+
}, []);
|
|
129
|
+
|
|
130
|
+
if (isChecking) {
|
|
131
|
+
return <p>Verificando identidad, por favor espera un momento...</p>;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (isValid) {
|
|
135
|
+
return (
|
|
105
136
|
<div>
|
|
106
|
-
<
|
|
107
|
-
<
|
|
108
|
-
type="file"
|
|
109
|
-
accept="image/*"
|
|
110
|
-
onChange={(e) => setFrontFile(e.target.files?.[0] || null)}
|
|
111
|
-
/>
|
|
137
|
+
<h2>✅ Verificación Completada</h2>
|
|
138
|
+
<p>CURP Extraída: {String(result?.extraction?.curp || "N/A")}</p>
|
|
112
139
|
</div>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
113
142
|
|
|
143
|
+
if (isRejected || error) {
|
|
144
|
+
return (
|
|
114
145
|
<div>
|
|
115
|
-
<
|
|
116
|
-
<
|
|
117
|
-
type="file"
|
|
118
|
-
accept="image/*"
|
|
119
|
-
onChange={(e) => setBackFile(e.target.files?.[0] || null)}
|
|
120
|
-
/>
|
|
146
|
+
<h2>❌ Verificación No Aprobada</h2>
|
|
147
|
+
<p>{error?.message || "El documento fue rechazado."}</p>
|
|
121
148
|
</div>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return <p>Cargando información de la sesión...</p>;
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
#### B. Ejemplo con TypeScript / Node.js (`JumioClient`)
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
import { JumioClient } from "@dynamicore/jumio-sdk";
|
|
162
|
+
|
|
163
|
+
const jumio = new JumioClient({
|
|
164
|
+
baseUrl: "https://front.dynamicore.io",
|
|
165
|
+
context: "MI_CONTEXTO_NEGOCIO",
|
|
166
|
+
authToken: () => getAuthTokenFromSession(),
|
|
167
|
+
redirectProxyUrl: "https://inllhuznm2.execute-api.us-west-2.amazonaws.com/prod/jumio/redirect/",
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// 1. Solicitar la URL de verificación
|
|
171
|
+
async function initVerificationSession(clientId: string) {
|
|
172
|
+
const { href, accountId, workflowId } = await jumio.startWebflow({
|
|
173
|
+
clientId,
|
|
174
|
+
successUrl: "https://myapp.com/identity/callback",
|
|
175
|
+
errorUrl: "https://myapp.com/identity/callback",
|
|
176
|
+
useRedirectProxy: true,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
console.log("Redirigir cliente a:", href);
|
|
180
|
+
return { href, accountId, workflowId };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// 2. Consultar o sonder el resultado al regresar
|
|
184
|
+
async function verifyReturnStatus(returnUrl: string, clientId: string) {
|
|
185
|
+
const { accountId, workflowId } = jumio.parseWebflowReturnParams(returnUrl);
|
|
186
|
+
|
|
187
|
+
if (!accountId || !workflowId) {
|
|
188
|
+
throw new Error("No se encontraron parámetros de seguimiento en la URL.");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const result = await jumio.pollWebflowStatus(
|
|
192
|
+
{ accountId, workflowId, clientId },
|
|
193
|
+
{ maxAttempts: 15, pollingIntervalMs: 5000 }
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
if (result.valid) {
|
|
197
|
+
console.log("Veredicto APROBADO:", result.extraction);
|
|
198
|
+
} else {
|
|
199
|
+
console.warn("Veredicto RECHAZADO:", result.errorMessage);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return result;
|
|
203
|
+
}
|
|
204
|
+
```
|
|
122
205
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
### 📄 Servicio 2: Carga Directa de INE (INE API)
|
|
209
|
+
|
|
210
|
+
En este flujo, la app captura las imágenes de la INE (frente y reverso) en su propia interfaz y las envía directamente al backend.
|
|
211
|
+
|
|
212
|
+
#### A. Ejemplo con React / Next.js (`useJumioVerification`)
|
|
213
|
+
|
|
214
|
+
```tsx
|
|
215
|
+
import React, { useState } from "react";
|
|
216
|
+
import { useJumioVerification } from "@dynamicore/jumio-sdk/react";
|
|
130
217
|
|
|
131
|
-
|
|
218
|
+
export function DirectIneVerificationStep({ clientId, onNext }: { clientId: string; onNext: () => void }) {
|
|
219
|
+
const [frontFile, setFrontFile] = useState<File | null>(null);
|
|
220
|
+
const [backFile, setBackFile] = useState<File | null>(null);
|
|
221
|
+
|
|
222
|
+
const { verify, isSubmitting, isPolling, isLoading, stage, progress, error } =
|
|
223
|
+
useJumioVerification({
|
|
224
|
+
baseUrl: process.env.NEXT_PUBLIC_API_URL,
|
|
225
|
+
context: process.env.NEXT_PUBLIC_DYNAMICORE_CONTEXT,
|
|
226
|
+
onStatusResolved: (res) => {
|
|
227
|
+
if (res.valid) {
|
|
228
|
+
onNext();
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
234
|
+
e.preventDefault();
|
|
235
|
+
if (!frontFile || !backFile) return;
|
|
236
|
+
|
|
237
|
+
await verify({
|
|
238
|
+
clientId,
|
|
239
|
+
frontImage: frontFile,
|
|
240
|
+
backImage: backFile,
|
|
241
|
+
awaitFinalStatus: false, // Sondeo en 2do plano sin bloquear la UI
|
|
242
|
+
});
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
return (
|
|
246
|
+
<form onSubmit={handleSubmit}>
|
|
247
|
+
<input type="file" accept="image/*" onChange={(e) => setFrontFile(e.target.files?.[0] || null)} />
|
|
248
|
+
<input type="file" accept="image/*" onChange={(e) => setBackFile(e.target.files?.[0] || null)} />
|
|
249
|
+
|
|
250
|
+
{isLoading && <p>Estado: {stage} ({progress}%)</p>}
|
|
251
|
+
{error && <p style={{ color: "red" }}>{error.message}</p>}
|
|
132
252
|
|
|
133
253
|
<button type="submit" disabled={isLoading || !frontFile || !backFile}>
|
|
134
|
-
|
|
254
|
+
Continuar
|
|
135
255
|
</button>
|
|
136
256
|
</form>
|
|
137
257
|
);
|
|
@@ -140,41 +260,34 @@ export function IneVerificationStep({ clientId, onNext }: { clientId: string; on
|
|
|
140
260
|
|
|
141
261
|
---
|
|
142
262
|
|
|
143
|
-
|
|
263
|
+
#### B. Ejemplo con TypeScript / Node.js (`JumioClient.verifyIne`)
|
|
144
264
|
|
|
145
265
|
```typescript
|
|
146
266
|
import { JumioClient, isJumioError } from "@dynamicore/jumio-sdk";
|
|
147
267
|
|
|
148
268
|
const jumio = new JumioClient({
|
|
149
269
|
baseUrl: "https://front.dynamicore.io",
|
|
150
|
-
context: "MI_CONTEXTO_NEGOCIO",
|
|
151
|
-
authToken: () => getAuthTokenFromSession(),
|
|
152
|
-
// Opcional: Función para firmar URLs privadas de S3
|
|
153
|
-
s3Signer: async (path, expires) => {
|
|
154
|
-
return await myS3SignerService(path, expires);
|
|
155
|
-
},
|
|
270
|
+
context: "MI_CONTEXTO_NEGOCIO",
|
|
271
|
+
authToken: () => getAuthTokenFromSession(),
|
|
156
272
|
});
|
|
157
273
|
|
|
158
|
-
async function
|
|
274
|
+
async function runDirectVerification() {
|
|
159
275
|
try {
|
|
160
276
|
const result = await jumio.verifyIne({
|
|
161
277
|
clientId: "usr_987654",
|
|
162
278
|
frontImage: "https://my-bucket.s3.amazonaws.com/uploads/ine_front.jpg",
|
|
163
279
|
backImage: "https://my-bucket.s3.amazonaws.com/uploads/ine_back.jpg",
|
|
164
|
-
awaitFinalStatus: true,
|
|
280
|
+
awaitFinalStatus: true,
|
|
165
281
|
});
|
|
166
282
|
|
|
167
283
|
if (result.valid) {
|
|
168
|
-
console.log("
|
|
284
|
+
console.log("INE validada con éxito:", result.data);
|
|
169
285
|
} else {
|
|
170
|
-
console.warn("
|
|
286
|
+
console.warn("INE rechazada:", result.errorMessage);
|
|
171
287
|
}
|
|
172
288
|
} catch (error) {
|
|
173
289
|
if (isJumioError(error)) {
|
|
174
290
|
console.error(`Error Jumio [${error.code}]:`, error.message);
|
|
175
|
-
console.error("Detalle crudo:", error.rawData);
|
|
176
|
-
} else {
|
|
177
|
-
console.error("Error inesperado:", error);
|
|
178
291
|
}
|
|
179
292
|
}
|
|
180
293
|
}
|
|
@@ -188,85 +301,50 @@ async function runVerification() {
|
|
|
188
301
|
| :--- | :--- | :--- | :--- |
|
|
189
302
|
| `baseUrl` | `string` | `"https://front.dynamicore.io"` | URL base de la API de DynamiCore. |
|
|
190
303
|
| `endpoint` | `string` | `"/marketplace/apps/jumio"` | Ruta del endpoint de verificación. |
|
|
191
|
-
| `context` | `string` | `undefined` |
|
|
192
|
-
| `authToken` | `string \|
|
|
193
|
-
| `
|
|
194
|
-
| `
|
|
195
|
-
| `
|
|
196
|
-
| `
|
|
197
|
-
| `
|
|
198
|
-
| `
|
|
199
|
-
| `
|
|
200
|
-
| `
|
|
201
|
-
| `
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
---
|
|
216
|
-
|
|
217
|
-
## 🚦 Estados y Veredicto (`valid` / `decision`)
|
|
218
|
-
|
|
219
|
-
> **Regla de oro:** el veredicto real de la verificación está en los campos `valid` y `decision`, **no** en `workflowStatus`. `workflowStatus: "PROCESSED"` solo significa que el workflow terminó de procesar; un `PROCESSED` + `decision: "NOT_EXECUTED"` es un **rechazo**, no una aprobación.
|
|
220
|
-
|
|
221
|
-
### Estados del workflow (`JumioWorkflowStatus`)
|
|
222
|
-
|
|
223
|
-
- `PENDING` / `INITIATED`: En proceso de análisis por los motores de Jumio.
|
|
224
|
-
- `PROCESSED`: El workflow terminó de procesar. **No implica aprobación**: el veredicto se determina con `valid` / `decision`.
|
|
225
|
-
- `APPROVED_VERIFIED` / `DONE` / `COMPLETED`: Estados inequívocamente aprobados (fallback conservador).
|
|
226
|
-
- `REJECTED` / `FAILED` / `DENIED` / `EXPIRED` / `ABANDONED` / `NOT_READABLE` / `FRAUD` / `UNSUPPORTED_ID_TYPE`: Estados finales no válidos.
|
|
227
|
-
|
|
228
|
-
### Precedencia del veredicto
|
|
229
|
-
|
|
230
|
-
El SDK resuelve la validez del resultado priorizando los campos explícitos:
|
|
231
|
-
|
|
232
|
-
1. **`valid` booleano** (fuente de verdad de la gateway): `true` = aprobado, `false` = rechazado.
|
|
233
|
-
2. **`decision`** presente: `PASSED` = aprobado; cualquier otro valor (`NOT_EXECUTED`, `REJECTED`, `FAILED`, etc.) = rechazado.
|
|
234
|
-
3. **Fallback conservador** por `workflowStatus` (fail-closed): solo `APPROVED_VERIFIED` / `DONE` / `COMPLETED` cuentan como aprobados; si no hay `valid` ni `decision`, un `PROCESSED` se considera rechazado.
|
|
235
|
-
|
|
236
|
-
> Siempre lee `result.valid` en lugar de interpretar el payload crudo de Jumio.
|
|
237
|
-
|
|
238
|
-
---
|
|
239
|
-
|
|
240
|
-
## 🔄 Flujo No Bloqueante y Ciclo de Vida del Polling
|
|
241
|
-
|
|
242
|
-
Con `awaitFinalStatus: false` el SDK retorna de inmediato para no detener al usuario y ejecuta el sondeo (`polling`) en segundo plano consultando el estado del workflow hasta obtener un veredicto final:
|
|
243
|
-
|
|
244
|
-
- El polling en segundo plano está **desacoplado del `signal`** del llamador: continúa aunque el componente que lo inició se desmonte o se aborte la señal original (navegación, cierre de pantalla). El `signal` / `AbortController` solo aplica a la fase inicial síncrona (upload y consulta de arranque).
|
|
245
|
-
- El veredicto final llega a `onStatusResolved` (o a `isSuccess` / `isError` del hook); en caso de agotarse los `maxPollingAttempts` se invoca `onStatusError` con un `JumioPollingTimeoutError`.
|
|
246
|
-
- Si la respuesta inicial ya trae un veredicto definitivo (p. ej. `valid: false` + `workflowStatus: "PROCESSED"`), el SDK lo resuelve de inmediato sin requerir los identificadores de seguimiento.
|
|
247
|
-
|
|
248
|
-
---
|
|
249
|
-
|
|
304
|
+
| `context` | `string` | `undefined` | Header de contexto enviado en las peticiones. |
|
|
305
|
+
| `authToken` | `string \| Provider` | `undefined` | Token Bearer o función proveedora dinámica. |
|
|
306
|
+
| `authTokenPrefix` | `string` | `""` | Prefijo del header `Authorization` (ej. `"Bearer"`). |
|
|
307
|
+
| `redirectProxyUrl` | `string` | `undefined` | URL base del proxy de redirección para Hosted Webflow. |
|
|
308
|
+
| `requestTimeout` | `number` | `180000` (3 min) | Timeout para peticiones POST. |
|
|
309
|
+
| `statusTimeout` | `number` | `120000` (2 min) | Timeout para peticiones GET de estado. |
|
|
310
|
+
| `maxRetries` | `number` | `3` | Número de reintentos ante caídas de red. |
|
|
311
|
+
| `retryDelayMs` | `number` | `800` | Delay base del backoff exponencial entre reintentos. |
|
|
312
|
+
| `pollingIntervalMs` | `number` | `10000` (10s) | Intervalo entre consultas de sondeo. |
|
|
313
|
+
| `maxPollingAttempts` | `number` | `20` | Máximo de consultas de sondeo antes de timeout. |
|
|
314
|
+
| `s3Signer` | `S3SignerFunction` | `undefined` | Función para firmar rutas privadas de S3. |
|
|
315
|
+
| `customHeaders` | `Record<string,string>` | `undefined` | Headers adicionales en cada petición. |
|
|
316
|
+
| `axiosInstance` | `AxiosInstance` | `undefined` | Instancia propia de Axios (opcional). |
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
## 🧰 Helpers y notas del flujo Webflow
|
|
321
|
+
|
|
322
|
+
- `client.buildRedirectUrl(returnUrl, useProxy)` / `buildRedirectUrl(url, proxy)` — construye la URL de retorno, con Base64 URL-safe si usas `redirectProxyUrl`.
|
|
323
|
+
- `client.getWebflowStatus({ accountId, workflowId, clientId })` — una sola consulta `GET ?type=ine&accountId&workflowId&clientId`; retorna `WebflowResult | null` (`null` = pendiente).
|
|
324
|
+
- `client.pollWebflowStatus(params, { maxAttempts, pollingIntervalMs, signal, onAttempt })` — sondeo hasta veredicto o `JumioPollingTimeoutError`.
|
|
325
|
+
- Hooks exponen `cancel()` / `reset()`; un abort (desmontaje, `cancel`, StrictMode) baja `isChecking/isStarting/isLoading` sin marcar error.
|
|
326
|
+
- El backend puede responder con envelope `{ data }` o `{ values }` y datos anidados; el SDK los desempaqueta. `parseWebflowReturnParams` acepta `workflowExecutionId` / `workflowId` y params anidados en `?status=`.
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
250
330
|
## 🧪 Pruebas y Construcción
|
|
251
331
|
|
|
252
|
-
Para compilar y ejecutar la suite de pruebas del paquete:
|
|
253
|
-
|
|
254
332
|
```bash
|
|
255
333
|
# Instalar dependencias
|
|
256
334
|
npm install
|
|
257
335
|
|
|
258
|
-
# Ejecutar pruebas unitarias
|
|
336
|
+
# Ejecutar suite completa de pruebas unitarias (59 tests)
|
|
259
337
|
npm test
|
|
260
338
|
|
|
261
|
-
# Compilar para producción (ESM, CJS y .d.ts)
|
|
262
|
-
npm run build
|
|
263
|
-
|
|
264
339
|
# Verificación de tipos TypeScript
|
|
265
340
|
npm run typecheck
|
|
341
|
+
|
|
342
|
+
# Compilar para producción (ESM, CJS y .d.ts)
|
|
343
|
+
npm run build
|
|
266
344
|
```
|
|
267
345
|
|
|
268
346
|
---
|
|
269
347
|
|
|
270
348
|
## 📄 Licencia
|
|
271
349
|
|
|
272
|
-
MIT © DynamiCore
|
|
350
|
+
MIT © DynamiCore
|