@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 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 e identificación oficial (INE) mediante Jumio en el ecosistema DynamiCore.
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
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.7+-blue.svg)](https://www.typescriptlang.org/)
6
6
  [![React](https://img.shields.io/badge/React-18%20%7C%2019-61dafb.svg)](https://react.dev/)
7
7
  [![Dual ESM/CJS](https://img.shields.io/badge/Module-ESM%20%26%20CJS-green.svg)](#)
8
8
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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
- ## 🌟 Características
15
+ ## 🌟 Modalidades de Verificación Soportadas
13
16
 
14
- - **Agnóstico y Universal**: Funciona en cualquier entorno JavaScript/TypeScript (Next.js App/Pages router, React, React Native, Vite, Node.js, Express, Fastify).
15
- - **Manejo Flexible de Imágenes**: Acepta `File`, `Blob`, `ArrayBuffer`, `Uint8Array`, strings en `Base64`, URLs públicas, URLs prefirmadas de S3 o rutas S3 con firmador personalizado.
16
- - **Soporte de Flujos Asíncrono y Síncrono**:
17
- - *No bloqueante* (`awaitFinalStatus: false`): Inicia la validación, responde de inmediato para no detener al usuario y ejecuta el sondeo (polling) en segundo plano notificando mediante callbacks.
18
- - *Bloqueante* (`awaitFinalStatus: true`): Aguarda activamente hasta que Jumio resuelva la validación (`APPROVED_VERIFIED`, `REJECTED`, etc.).
19
- - **Tolerancia a Fallos y Reintentos Automáticos**: Backoff exponencial configurable ante timeouts o caídas transitorias de red.
20
- - **React Hook de Primera Clase**: `useJumioVerification` provee estados reactivos (`isLoading`, `isSubmitting`, `isPolling`, `progress`, `stage`, `result`, `error`) y cancelación con `AbortController`.
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
- ### O en proyectos locales (Monorepo o Enlace local):
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
- ## 🚀 Guía de Uso
43
+ ## 🚀 Guías y Ejemplos de Uso
45
44
 
46
- ### 1. Uso con React / Next.js (`useJumioVerification`)
45
+ ---
47
46
 
48
- ```tsx
49
- import React, { useState } from "react";
50
- import { useJumioVerification } from "@dynamicore/jumio-sdk/react";
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
- export function IneVerificationStep({ clientId, onNext }: { clientId: string; onNext: () => void }) {
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
- const {
57
- verify,
58
- isSubmitting,
59
- isPolling,
60
- isLoading,
61
- stage,
62
- progress,
63
- result,
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.NEXT_PUBLIC_DYNAMICORE_MORAL_CONTEXT,
69
- // Callback cuando el sondeo en segundo plano resuelve el resultado final:
70
- onStatusResolved: (res) => {
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 handleSubmit = async (e: React.FormEvent) => {
83
- e.preventDefault();
84
- if (!frontFile || !backFile) return;
85
-
70
+ const handleStart = async () => {
86
71
  try {
87
- // awaitFinalStatus: false permite avanzar en el flujo sin esperar 2 minutos
88
- const res = await verify({
72
+ const baseUrl = window.location.origin;
73
+ await startWebflow({
89
74
  clientId,
90
- frontImage: frontFile,
91
- backImage: backFile,
92
- awaitFinalStatus: false,
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
- <form onSubmit={handleSubmit} className="space-y-4">
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
- <label>Frente de INE:</label>
107
- <input
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
- <label>Reverso de INE:</label>
116
- <input
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
- {isLoading && (
124
- <div>
125
- <p>Estado: {stage} ({progress}%)</p>
126
- {isSubmitting && <p>Subiendo y procesando imágenes...</p>}
127
- {isPolling && <p>Verificando autenticidad en segundo plano...</p>}
128
- </div>
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
- {error && <p className="text-red-500">{error.message}</p>}
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
- {isLoading ? "Validando..." : "Continuar"}
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
- ### 2. Uso con TypeScript / Node.js / Core (`JumioClient`)
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", // Contexto/tenant de tu aplicación en DynamiCore
151
- authToken: () => getAuthTokenFromSession(), // Token estático o función async
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 runVerification() {
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, // Espera resolución completa
280
+ awaitFinalStatus: true,
165
281
  });
166
282
 
167
283
  if (result.valid) {
168
- console.log("Validación completada con éxito:", result.data);
284
+ console.log("INE validada con éxito:", result.data);
169
285
  } else {
170
- console.warn("Documento rechazado:", result.errorMessage);
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` | Contexto moral / de aplicación en header `context`. |
192
- | `authToken` | `string \| (() => string \| Promise<string>)` | `undefined` | Token Bearer o proveedor dinámico. |
193
- | `requestTimeout` | `number` | `180000` (3 min) | Timeout para el POST inicial de imágenes. |
194
- | `statusTimeout` | `number` | `120000` (2 min) | Timeout para cada consulta de estatus. |
195
- | `maxRetries` | `number` | `3` | Número de reintentos ante caídas transitorias. |
196
- | `retryDelayMs` | `number` | `800` | Delay base de backoff para reintentos. |
197
- | `pollingIntervalMs` | `number` | `10000` (10s) | Intervalo entre consultas sucesivas de estado. |
198
- | `maxPollingAttempts` | `number` | `20` | Cantidad máxima de consultas antes de timeout. |
199
- | `s3Signer` | `S3SignerFunction` | `undefined` | Función para firmar rutas privadas de S3. |
200
- | `customHeaders` | `Record<string, string>` | `{}` | Headers HTTP personalizados. |
201
- | `axiosInstance` | `AxiosInstance` | `undefined` | Instancia personalizada de Axios. |
202
-
203
- ---
204
-
205
- ## 🖼️ Formatos de Imagen Soportados
206
-
207
- La librería detecta y convierte automáticamente cualquier formato a Base64 puro sin encabezados `data:image/...`:
208
-
209
- 1. **`File` o `Blob`**: Obtenidos desde `<input type="file" />` o drag & drop.
210
- 2. **`Base64` / `Data URL`**: Strings directos como `"data:image/jpeg;base64,..."` o Base64 crudo.
211
- 3. **`URL Remota`**: URLs `https://...` públicas o con token de firma.
212
- 4. **`Ruta S3`**: Rutas relativas como `"company/123/docs/ine.png"` (utiliza `s3Signer` para resolver la URL firmada).
213
- 5. **`ArrayBuffer` / `Uint8Array`**: Datos binarios en memoria.
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