extraer-datos-ine 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Extraer Datos de INE
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,160 @@
1
+ # extraer-datos-ine
2
+
3
+ SDK oficial en JavaScript / TypeScript para la **[API de OCR para INE de Extraer Datos de INE](https://extraerdatosdeine.com)** — extrae CURP, nombre, clave de elector, dirección y todos los campos de una **credencial para votar mexicana (INE/IFE)** a partir de una imagen, en segundos.
4
+
5
+ - 🇲🇽 Diseñado para credenciales **INE e IFE** (todos los modelos).
6
+ - 🧩 **Cero dependencias.** Usa el `fetch`/`FormData` nativos de Node 18+ (y navegadores).
7
+ - 🔠 **TypeScript de primera clase**: tipos para los 20 campos y para cada código de error.
8
+ - 📦 Doble build **ESM + CommonJS**.
9
+
10
+ 📚 Documentación de la API: **https://extraerdatosdeine.com/docs**
11
+ 🔑 Consigue una API key con **20 extracciones gratis**: **https://extraerdatosdeine.com/register**
12
+
13
+ ---
14
+
15
+ ## Instalación
16
+
17
+ ```bash
18
+ npm install extraer-datos-ine
19
+ ```
20
+
21
+ Requiere **Node.js 18 o superior** (por `fetch`, `FormData` y `Blob` globales).
22
+
23
+ ## Uso rápido
24
+
25
+ ```ts
26
+ import { readFile } from 'node:fs/promises'
27
+ import { IneExtractorClient } from 'extraer-datos-ine'
28
+
29
+ const client = new IneExtractorClient({ apiKey: process.env.INE_API_KEY! })
30
+
31
+ const front = await readFile('./ine_frente.jpg')
32
+ const back = await readFile('./ine_reverso.jpg') // opcional, para CIC/OCR del reverso
33
+
34
+ const { data, tokensRemaining } = await client.extract({ front, back })
35
+
36
+ console.log(data.curp) // 'PEGJ850101HDFRRL09'
37
+ console.log(data.claveElector) // 'PRGRJN85010109H100'
38
+ console.log(tokensRemaining) // 19
39
+ ```
40
+
41
+ ### Consultar saldo de tokens
42
+
43
+ ```ts
44
+ const balance = await client.getBalance()
45
+ console.log(`Tokens disponibles: ${balance}`)
46
+ ```
47
+
48
+ ## Formas de enviar la imagen
49
+
50
+ El SDK elige el método de envío automáticamente según el tipo de `front`/`back`.
51
+
52
+ | Tipo de entrada | Ejemplo | Método HTTP usado |
53
+ |---|---|---|
54
+ | Binario | `Buffer` / `Uint8Array` / `Blob` | `multipart/form-data` |
55
+ | Base64 | `{ base64: '...' }` o `'data:image/jpeg;base64,...'` | JSON base64 |
56
+ | URL | `{ url: 'https://...' }` | JSON URL (la API descarga la imagen) |
57
+
58
+ ```ts
59
+ // Binario (recomendado en backend)
60
+ await client.extract({ front: buffer, frontMimeType: 'image/png' })
61
+
62
+ // Base64
63
+ await client.extract({ front: { base64 }, back: { base64: back64 } })
64
+
65
+ // URL HTTPS
66
+ await client.extract({ front: { url }, back: { url: backUrl } })
67
+ ```
68
+
69
+ > `front` y `back` deben ser del **mismo tipo**: la API no mezcla métodos en una sola petición.
70
+
71
+ ## Manejo de errores
72
+
73
+ Toda respuesta no exitosa lanza un `IneExtractorError` con un `code` legible por máquina.
74
+
75
+ ```ts
76
+ import { IneExtractorError } from 'extraer-datos-ine'
77
+
78
+ try {
79
+ const { data } = await client.extract({ front })
80
+ } catch (err) {
81
+ if (err instanceof IneExtractorError) {
82
+ switch (err.code) {
83
+ case 'INSUFFICIENT_TOKENS':
84
+ console.error('Sin tokens. Recarga en:', err.enrollUrl)
85
+ break
86
+ case 'LOW_IMAGE_QUALITY':
87
+ console.error('Imagen ilegible. Campos faltantes:', err.missingFields)
88
+ break
89
+ case 'IMAGE_TOO_LARGE':
90
+ console.error('La imagen excede 10 MB.')
91
+ break
92
+ default:
93
+ console.error(`[${err.code}] ${err.message}`)
94
+ }
95
+ } else {
96
+ throw err
97
+ }
98
+ }
99
+ ```
100
+
101
+ ### Códigos de error
102
+
103
+ | `code` | HTTP | Significado |
104
+ |---|---|---|
105
+ | `MISSING_API_KEY` | 401 | No se envió la API key |
106
+ | `INVALID_API_KEY` | 401 | API key inválida o revocada |
107
+ | `MISSING_IMAGE` | 400 | Falta la imagen frontal |
108
+ | `INVALID_IMAGE_FORMAT` | 400 | Formato no soportado (usa JPEG, PNG o WebP) |
109
+ | `IMAGE_TOO_LARGE` | 400 | La imagen excede 10 MB |
110
+ | `INVALID_BASE64` | 400 | Error al decodificar base64 |
111
+ | `INVALID_MULTIPART` | 400 | Error al procesar el formulario multipart |
112
+ | `URL_FETCH_FAILED` | 400 | No se pudo descargar la imagen de la URL |
113
+ | `URL_INVALID` | 400 | URL inválida (debe ser HTTPS) |
114
+ | `URL_BLOCKED` | 400 | URL bloqueada por seguridad |
115
+ | `URL_TIMEOUT` | 400 | Timeout al descargar la imagen (10 s) |
116
+ | `UNSUPPORTED_CONTENT_TYPE` | 415 | Content-Type no soportado |
117
+ | `INSUFFICIENT_TOKENS` | 402 | Saldo insuficiente (ver `err.enrollUrl`) |
118
+ | `LOW_IMAGE_QUALITY` | 422 | Imagen ilegible (ver `err.missingFields`) |
119
+ | `EXTRACTION_FAILED` | 500 | Falló la extracción (el token se reembolsa) |
120
+ | `PROCESSING_ERROR` | 500 | Error procesando la imagen (token reembolsado) |
121
+ | `INTERNAL_ERROR` | 500 | Error interno del servidor |
122
+ | `NETWORK_ERROR` | — | Fallo de red (lado del cliente) |
123
+ | `TIMEOUT` | — | Se superó el `timeoutMs` del cliente |
124
+
125
+ ## Campos extraídos (`IneData`)
126
+
127
+ `nombre`, `apellidoPaterno`, `apellidoMaterno`, `domicilio`, `calle`, `colonia`, `codigoPostal`, `municipio`, `estado`, `seccion`, `curp`, `claveElector`, `anioRegistro`, `fechaNacimiento`, `sexo`, `vigencia`, `numeroVertical`, `ocr`, `cic`, `emision`.
128
+
129
+ Los cuatro últimos (`numeroVertical`, `ocr`, `cic`, `emision`) viven en el **reverso**: envía también `back` para obtenerlos. ¿Dudas sobre CIC vs OCR vs clave de elector? Lee la [guía](https://extraerdatosdeine.com/blog/cic-ocr-clave-elector-ine).
130
+
131
+ ## Configuración del cliente
132
+
133
+ ```ts
134
+ new IneExtractorClient({
135
+ apiKey: 'ine_...', // requerido
136
+ baseUrl: 'https://extraerdatosdeine.com/api/v1', // default
137
+ timeoutMs: 60_000, // default
138
+ fetch: customFetch, // opcional (tests / polyfills)
139
+ })
140
+ ```
141
+
142
+ ## Desarrollo
143
+
144
+ ```bash
145
+ npm install
146
+ npm run build # compila ESM + CJS + .d.ts con tsup
147
+ npm test # build + node --test
148
+ npm run typecheck
149
+ ```
150
+
151
+ ## Enlaces
152
+
153
+ - 🌐 Sitio: https://extraerdatosdeine.com
154
+ - 📚 Documentación de la API: https://extraerdatosdeine.com/docs
155
+ - 🔑 Crear cuenta (20 extracciones gratis): https://extraerdatosdeine.com/register
156
+ - 📝 Blog: https://extraerdatosdeine.com/blog
157
+
158
+ ## Licencia
159
+
160
+ [MIT](./LICENSE) © Extraer Datos de INE
package/dist/index.cjs ADDED
@@ -0,0 +1,221 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ IneExtractorClient: () => IneExtractorClient,
24
+ IneExtractorError: () => IneExtractorError
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
27
+
28
+ // src/errors.ts
29
+ var IneExtractorError = class _IneExtractorError extends Error {
30
+ /** Código de error legible por máquina. */
31
+ code;
32
+ /** Status HTTP de la respuesta (ausente en errores de red). */
33
+ status;
34
+ /** ID de la extracción, cuando aplica (LOW_IMAGE_QUALITY / EXTRACTION_FAILED). */
35
+ extractionId;
36
+ /** Campos que no se pudieron leer (LOW_IMAGE_QUALITY). */
37
+ missingFields;
38
+ /** URL para configurar auto-recarga (algunos INSUFFICIENT_TOKENS). */
39
+ enrollUrl;
40
+ /** Motivo de bloqueo (algunos INSUFFICIENT_TOKENS). */
41
+ blockReason;
42
+ /** Cuerpo crudo de la respuesta de error, por si necesitas más detalle. */
43
+ response;
44
+ constructor(message, code, init = {}) {
45
+ super(message, init.cause !== void 0 ? { cause: init.cause } : void 0);
46
+ this.name = "IneExtractorError";
47
+ this.code = code;
48
+ this.status = init.status;
49
+ this.extractionId = init.extractionId;
50
+ this.missingFields = init.missingFields;
51
+ this.enrollUrl = init.enrollUrl;
52
+ this.blockReason = init.blockReason;
53
+ this.response = init.response;
54
+ Object.setPrototypeOf(this, _IneExtractorError.prototype);
55
+ }
56
+ /** Construye el error a partir de una respuesta de la API. */
57
+ static fromResponse(status, body) {
58
+ const b = body ?? {};
59
+ const code = typeof b.code === "string" ? b.code : "INTERNAL_ERROR";
60
+ const message = typeof b.error === "string" && b.error || `La API respondi\xF3 con status ${status}`;
61
+ return new _IneExtractorError(message, code, {
62
+ status,
63
+ extractionId: typeof b.extraction_id === "string" ? b.extraction_id : void 0,
64
+ missingFields: Array.isArray(b.missing_fields) ? b.missing_fields : void 0,
65
+ enrollUrl: typeof b.enroll_url === "string" ? b.enroll_url : void 0,
66
+ blockReason: typeof b.block_reason === "string" ? b.block_reason : void 0,
67
+ response: body
68
+ });
69
+ }
70
+ };
71
+
72
+ // src/client.ts
73
+ var DEFAULT_BASE_URL = "https://extraerdatosdeine.com/api/v1";
74
+ var DEFAULT_TIMEOUT_MS = 6e4;
75
+ var IneExtractorClient = class {
76
+ apiKey;
77
+ baseUrl;
78
+ timeoutMs;
79
+ fetchImpl;
80
+ constructor(options) {
81
+ if (!options || !options.apiKey) {
82
+ throw new IneExtractorError("Falta `apiKey` en las opciones del cliente.", "INVALID_INPUT");
83
+ }
84
+ const fetchImpl = options.fetch ?? globalThis.fetch;
85
+ if (typeof fetchImpl !== "function") {
86
+ throw new IneExtractorError(
87
+ "No hay `fetch` disponible. Usa Node 18+ o pasa `fetch` en las opciones.",
88
+ "INVALID_INPUT"
89
+ );
90
+ }
91
+ this.apiKey = options.apiKey;
92
+ this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
93
+ this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
94
+ this.fetchImpl = fetchImpl;
95
+ }
96
+ /**
97
+ * Consulta el saldo de tokens de tu cuenta.
98
+ * @returns número de tokens disponibles.
99
+ */
100
+ async getBalance() {
101
+ const body = await this.request("/balance", { method: "GET" });
102
+ return Number(body.balance);
103
+ }
104
+ /**
105
+ * Extrae los datos de una credencial INE/IFE.
106
+ *
107
+ * El método de envío se elige automáticamente según el tipo de `front`:
108
+ * binario → multipart; `{ base64 }` o string → JSON base64; `{ url }` → JSON URL.
109
+ * `front` y `back` deben ser del mismo tipo (la API no mezcla métodos).
110
+ *
111
+ * @throws {IneExtractorError} ante saldo insuficiente, imagen ilegible,
112
+ * formato inválido, error de red, timeout, etc. Revisa `error.code`.
113
+ */
114
+ async extract(input) {
115
+ if (!input || input.front === void 0 || input.front === null) {
116
+ throw new IneExtractorError("Falta la imagen frontal (`front`).", "MISSING_IMAGE");
117
+ }
118
+ const frontKind = kindOf(input.front);
119
+ if (input.back !== void 0 && kindOf(input.back) !== frontKind) {
120
+ throw new IneExtractorError(
121
+ "`front` y `back` deben ser del mismo tipo (ambos binarios, base64 o url).",
122
+ "INVALID_INPUT"
123
+ );
124
+ }
125
+ let init;
126
+ if (frontKind === "url") {
127
+ const payload = {
128
+ image_front_url: input.front.url
129
+ };
130
+ if (input.back) payload.image_back_url = input.back.url;
131
+ init = jsonRequest(payload);
132
+ } else if (frontKind === "base64") {
133
+ const payload = { image_front: asBase64(input.front) };
134
+ if (input.back) payload.image_back = asBase64(input.back);
135
+ init = jsonRequest(payload);
136
+ } else {
137
+ const form = new FormData();
138
+ form.append("image_front", toBlob(input.front, input.frontMimeType));
139
+ if (input.back) {
140
+ form.append("image_back", toBlob(input.back, input.backMimeType));
141
+ }
142
+ init = { method: "POST", body: form };
143
+ }
144
+ const body = await this.request("/extract", init);
145
+ return {
146
+ extractionId: body.extraction_id,
147
+ data: body.data,
148
+ tokensRemaining: body.tokens_remaining,
149
+ uploadMethod: body.upload_method
150
+ };
151
+ }
152
+ async request(path, init) {
153
+ const controller = new AbortController();
154
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
155
+ let res;
156
+ try {
157
+ res = await this.fetchImpl(this.baseUrl + path, {
158
+ ...init,
159
+ signal: controller.signal,
160
+ headers: { "X-API-Key": this.apiKey, ...init.headers ?? {} }
161
+ });
162
+ } catch (err) {
163
+ if (err instanceof Error && err.name === "AbortError") {
164
+ throw new IneExtractorError(
165
+ `La petici\xF3n excedi\xF3 el timeout de ${this.timeoutMs} ms.`,
166
+ "TIMEOUT",
167
+ { cause: err }
168
+ );
169
+ }
170
+ throw new IneExtractorError(
171
+ err instanceof Error ? err.message : "Error de red",
172
+ "NETWORK_ERROR",
173
+ { cause: err }
174
+ );
175
+ } finally {
176
+ clearTimeout(timer);
177
+ }
178
+ const body = await res.json().catch(() => ({}));
179
+ if (!res.ok || body?.success === false) {
180
+ throw IneExtractorError.fromResponse(res.status, body);
181
+ }
182
+ return body;
183
+ }
184
+ };
185
+ function kindOf(src) {
186
+ if (typeof src === "string") return "base64";
187
+ if (src instanceof Uint8Array || src instanceof ArrayBuffer) return "binary";
188
+ if (src instanceof Blob) return "binary";
189
+ if (typeof src === "object" && src !== null) {
190
+ if ("url" in src) return "url";
191
+ if ("base64" in src) return "base64";
192
+ }
193
+ throw new IneExtractorError(
194
+ "Fuente de imagen no soportada. Usa Uint8Array, ArrayBuffer, Blob, { base64 } o { url }.",
195
+ "INVALID_INPUT"
196
+ );
197
+ }
198
+ function asBase64(src) {
199
+ if (typeof src === "string") return src;
200
+ if (typeof src === "object" && src !== null && "base64" in src) return src.base64;
201
+ throw new IneExtractorError("Se esperaba una cadena base64.", "INVALID_INPUT");
202
+ }
203
+ function toBlob(src, mimeType = "image/jpeg") {
204
+ if (src instanceof Blob) return src;
205
+ if (src instanceof ArrayBuffer) return new Blob([src], { type: mimeType });
206
+ const bytes = src.buffer.slice(src.byteOffset, src.byteOffset + src.byteLength);
207
+ return new Blob([bytes], { type: mimeType });
208
+ }
209
+ function jsonRequest(payload) {
210
+ return {
211
+ method: "POST",
212
+ headers: { "Content-Type": "application/json" },
213
+ body: JSON.stringify(payload)
214
+ };
215
+ }
216
+ // Annotate the CommonJS export names for ESM import in node:
217
+ 0 && (module.exports = {
218
+ IneExtractorClient,
219
+ IneExtractorError
220
+ });
221
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/client.ts"],"sourcesContent":["/**\n * SDK oficial de Extraer Datos de INE.\n * API de OCR para credenciales INE/IFE mexicanas.\n *\n * @see https://extraerdatosdeine.com/docs\n * @packageDocumentation\n */\nexport { IneExtractorClient } from './client.js'\nexport { IneExtractorError } from './errors.js'\nexport type { IneErrorCode } from './errors.js'\nexport type {\n IneData,\n ExtractResult,\n ExtractInput,\n ImageSource,\n BinaryImage,\n IneExtractorOptions,\n} from './types.js'\n","/**\n * Códigos de error que devuelve la API de Extraer Datos de INE, más algunos\n * códigos propios del SDK (`NETWORK_ERROR`, `TIMEOUT`).\n *\n * @see https://extraerdatosdeine.com/docs\n */\nexport type IneErrorCode =\n // 401\n | 'MISSING_API_KEY'\n | 'INVALID_API_KEY'\n // 400\n | 'MISSING_IMAGE'\n | 'INVALID_IMAGE_FORMAT'\n | 'IMAGE_TOO_LARGE'\n | 'INVALID_BASE64'\n | 'INVALID_MULTIPART'\n | 'URL_FETCH_FAILED'\n | 'URL_INVALID'\n | 'URL_BLOCKED'\n | 'URL_TIMEOUT'\n // 415\n | 'UNSUPPORTED_CONTENT_TYPE'\n // 402\n | 'INSUFFICIENT_TOKENS'\n // 422\n | 'LOW_IMAGE_QUALITY'\n // 500\n | 'EXTRACTION_FAILED'\n | 'PROCESSING_ERROR'\n | 'INTERNAL_ERROR'\n // SDK-only\n | 'NETWORK_ERROR'\n | 'TIMEOUT'\n | 'INVALID_INPUT'\n // forward-compatible: unknown server codes\n | (string & {})\n\n/**\n * Error lanzado por el SDK ante cualquier respuesta no exitosa de la API\n * o un fallo de red/timeout. Inspecciona `code` para reaccionar de forma\n * programática.\n *\n * @example\n * try {\n * await client.extract({ front })\n * } catch (err) {\n * if (err instanceof IneExtractorError && err.code === 'INSUFFICIENT_TOKENS') {\n * console.log('Recarga tokens en', err.enrollUrl)\n * }\n * }\n */\nexport class IneExtractorError extends Error {\n /** Código de error legible por máquina. */\n readonly code: IneErrorCode\n /** Status HTTP de la respuesta (ausente en errores de red). */\n readonly status?: number\n /** ID de la extracción, cuando aplica (LOW_IMAGE_QUALITY / EXTRACTION_FAILED). */\n readonly extractionId?: string\n /** Campos que no se pudieron leer (LOW_IMAGE_QUALITY). */\n readonly missingFields?: string[]\n /** URL para configurar auto-recarga (algunos INSUFFICIENT_TOKENS). */\n readonly enrollUrl?: string\n /** Motivo de bloqueo (algunos INSUFFICIENT_TOKENS). */\n readonly blockReason?: string\n /** Cuerpo crudo de la respuesta de error, por si necesitas más detalle. */\n readonly response?: unknown\n\n constructor(\n message: string,\n code: IneErrorCode,\n init: {\n status?: number\n extractionId?: string\n missingFields?: string[]\n enrollUrl?: string\n blockReason?: string\n response?: unknown\n cause?: unknown\n } = {}\n ) {\n super(message, init.cause !== undefined ? { cause: init.cause } : undefined)\n this.name = 'IneExtractorError'\n this.code = code\n this.status = init.status\n this.extractionId = init.extractionId\n this.missingFields = init.missingFields\n this.enrollUrl = init.enrollUrl\n this.blockReason = init.blockReason\n this.response = init.response\n // Restore prototype chain for instanceof across transpile targets.\n Object.setPrototypeOf(this, IneExtractorError.prototype)\n }\n\n /** Construye el error a partir de una respuesta de la API. */\n static fromResponse(status: number, body: unknown): IneExtractorError {\n const b = (body ?? {}) as Record<string, unknown>\n const code = (typeof b.code === 'string' ? b.code : 'INTERNAL_ERROR') as IneErrorCode\n const message =\n (typeof b.error === 'string' && b.error) ||\n `La API respondió con status ${status}`\n return new IneExtractorError(message, code, {\n status,\n extractionId: typeof b.extraction_id === 'string' ? b.extraction_id : undefined,\n missingFields: Array.isArray(b.missing_fields)\n ? (b.missing_fields as string[])\n : undefined,\n enrollUrl: typeof b.enroll_url === 'string' ? b.enroll_url : undefined,\n blockReason: typeof b.block_reason === 'string' ? b.block_reason : undefined,\n response: body,\n })\n }\n}\n","import { IneExtractorError } from './errors.js'\nimport type {\n BinaryImage,\n ExtractInput,\n ExtractResult,\n ImageSource,\n IneExtractorOptions,\n} from './types.js'\n\nconst DEFAULT_BASE_URL = 'https://extraerdatosdeine.com/api/v1'\nconst DEFAULT_TIMEOUT_MS = 60_000\n\n/**\n * Cliente para la API de Extraer Datos de INE.\n *\n * @example\n * import { readFile } from 'node:fs/promises'\n * import { IneExtractorClient } from 'extraer-datos-ine'\n *\n * const client = new IneExtractorClient({ apiKey: process.env.INE_API_KEY! })\n * const front = await readFile('./ine_frente.jpg')\n * const back = await readFile('./ine_reverso.jpg')\n * const { data } = await client.extract({ front, back })\n * console.log(data.curp, data.claveElector)\n *\n * @see https://extraerdatosdeine.com/docs\n */\nexport class IneExtractorClient {\n private readonly apiKey: string\n private readonly baseUrl: string\n private readonly timeoutMs: number\n private readonly fetchImpl: typeof fetch\n\n constructor(options: IneExtractorOptions) {\n if (!options || !options.apiKey) {\n throw new IneExtractorError('Falta `apiKey` en las opciones del cliente.', 'INVALID_INPUT')\n }\n const fetchImpl = options.fetch ?? globalThis.fetch\n if (typeof fetchImpl !== 'function') {\n throw new IneExtractorError(\n 'No hay `fetch` disponible. Usa Node 18+ o pasa `fetch` en las opciones.',\n 'INVALID_INPUT'\n )\n }\n this.apiKey = options.apiKey\n this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/$/, '')\n this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS\n this.fetchImpl = fetchImpl\n }\n\n /**\n * Consulta el saldo de tokens de tu cuenta.\n * @returns número de tokens disponibles.\n */\n async getBalance(): Promise<number> {\n const body = await this.request('/balance', { method: 'GET' })\n return Number((body as { balance: number }).balance)\n }\n\n /**\n * Extrae los datos de una credencial INE/IFE.\n *\n * El método de envío se elige automáticamente según el tipo de `front`:\n * binario → multipart; `{ base64 }` o string → JSON base64; `{ url }` → JSON URL.\n * `front` y `back` deben ser del mismo tipo (la API no mezcla métodos).\n *\n * @throws {IneExtractorError} ante saldo insuficiente, imagen ilegible,\n * formato inválido, error de red, timeout, etc. Revisa `error.code`.\n */\n async extract(input: ExtractInput): Promise<ExtractResult> {\n if (!input || input.front === undefined || input.front === null) {\n throw new IneExtractorError('Falta la imagen frontal (`front`).', 'MISSING_IMAGE')\n }\n\n const frontKind = kindOf(input.front)\n if (input.back !== undefined && kindOf(input.back) !== frontKind) {\n throw new IneExtractorError(\n '`front` y `back` deben ser del mismo tipo (ambos binarios, base64 o url).',\n 'INVALID_INPUT'\n )\n }\n\n let init: RequestInit\n if (frontKind === 'url') {\n const payload: Record<string, string> = {\n image_front_url: (input.front as { url: string }).url,\n }\n if (input.back) payload.image_back_url = (input.back as { url: string }).url\n init = jsonRequest(payload)\n } else if (frontKind === 'base64') {\n const payload: Record<string, string> = { image_front: asBase64(input.front) }\n if (input.back) payload.image_back = asBase64(input.back)\n init = jsonRequest(payload)\n } else {\n const form = new FormData()\n form.append('image_front', toBlob(input.front as BinaryImage, input.frontMimeType))\n if (input.back) {\n form.append('image_back', toBlob(input.back as BinaryImage, input.backMimeType))\n }\n init = { method: 'POST', body: form }\n }\n\n const body = (await this.request('/extract', init)) as {\n extraction_id: string\n data: ExtractResult['data']\n tokens_remaining: number\n upload_method: string\n }\n\n return {\n extractionId: body.extraction_id,\n data: body.data,\n tokensRemaining: body.tokens_remaining,\n uploadMethod: body.upload_method,\n }\n }\n\n private async request(path: string, init: RequestInit): Promise<unknown> {\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), this.timeoutMs)\n\n let res: Response\n try {\n res = await this.fetchImpl(this.baseUrl + path, {\n ...init,\n signal: controller.signal,\n headers: { 'X-API-Key': this.apiKey, ...(init.headers ?? {}) },\n })\n } catch (err) {\n if (err instanceof Error && err.name === 'AbortError') {\n throw new IneExtractorError(\n `La petición excedió el timeout de ${this.timeoutMs} ms.`,\n 'TIMEOUT',\n { cause: err }\n )\n }\n throw new IneExtractorError(\n err instanceof Error ? err.message : 'Error de red',\n 'NETWORK_ERROR',\n { cause: err }\n )\n } finally {\n clearTimeout(timer)\n }\n\n const body = await res.json().catch(() => ({}))\n if (!res.ok || (body as { success?: boolean })?.success === false) {\n throw IneExtractorError.fromResponse(res.status, body)\n }\n return body\n }\n}\n\ntype SourceKind = 'binary' | 'base64' | 'url'\n\nfunction kindOf(src: ImageSource): SourceKind {\n if (typeof src === 'string') return 'base64'\n if (src instanceof Uint8Array || src instanceof ArrayBuffer) return 'binary'\n if (src instanceof Blob) return 'binary'\n if (typeof src === 'object' && src !== null) {\n if ('url' in src) return 'url'\n if ('base64' in src) return 'base64'\n }\n throw new IneExtractorError(\n 'Fuente de imagen no soportada. Usa Uint8Array, ArrayBuffer, Blob, { base64 } o { url }.',\n 'INVALID_INPUT'\n )\n}\n\nfunction asBase64(src: ImageSource): string {\n if (typeof src === 'string') return src\n if (typeof src === 'object' && src !== null && 'base64' in src) return src.base64\n throw new IneExtractorError('Se esperaba una cadena base64.', 'INVALID_INPUT')\n}\n\nfunction toBlob(src: BinaryImage, mimeType = 'image/jpeg'): Blob {\n if (src instanceof Blob) return src\n if (src instanceof ArrayBuffer) return new Blob([src], { type: mimeType })\n // Uint8Array: copy exactly its bytes into a standalone ArrayBuffer so the\n // result is a valid BlobPart regardless of the backing buffer kind.\n const bytes = src.buffer.slice(src.byteOffset, src.byteOffset + src.byteLength)\n return new Blob([bytes as ArrayBuffer], { type: mimeType })\n}\n\nfunction jsonRequest(payload: Record<string, string>): RequestInit {\n return {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Datos extraídos de una credencial INE/IFE.
3
+ *
4
+ * Todos los campos son strings. Los del reverso (`numeroVertical`, `ocr`,
5
+ * `cic`, `emision`) solo se obtienen si envías también la imagen trasera.
6
+ * Un campo puede venir vacío si no es legible en la credencial.
7
+ */
8
+ interface IneData {
9
+ /** Nombre(s) del titular */
10
+ nombre: string;
11
+ /** Apellido paterno */
12
+ apellidoPaterno: string;
13
+ /** Apellido materno */
14
+ apellidoMaterno: string;
15
+ /** Domicilio completo */
16
+ domicilio: string;
17
+ /** Calle con número */
18
+ calle: string;
19
+ /** Colonia */
20
+ colonia: string;
21
+ /** Código postal (5 dígitos) */
22
+ codigoPostal: string;
23
+ /** Municipio o alcaldía */
24
+ municipio: string;
25
+ /** Estado (abreviatura) */
26
+ estado: string;
27
+ /** Sección electoral (4 dígitos) */
28
+ seccion: string;
29
+ /** CURP (18 caracteres) */
30
+ curp: string;
31
+ /** Clave de elector (18 caracteres) */
32
+ claveElector: string;
33
+ /** Año de registro (4 dígitos) */
34
+ anioRegistro: string;
35
+ /** Fecha de nacimiento (DD/MM/AAAA) */
36
+ fechaNacimiento: string;
37
+ /** Sexo: H (hombre) o M (mujer) */
38
+ sexo: string;
39
+ /** Vigencia (año, 4 dígitos) */
40
+ vigencia: string;
41
+ /** Número vertical (reverso) */
42
+ numeroVertical: string;
43
+ /** Código OCR / MRZ (13 dígitos, reverso) */
44
+ ocr: string;
45
+ /** CIC — folio del plástico (9 dígitos, reverso) */
46
+ cic: string;
47
+ /** Número de emisión (2 dígitos, reverso) */
48
+ emision: string;
49
+ }
50
+ /** Resultado de una extracción exitosa. */
51
+ interface ExtractResult {
52
+ /** Identificador de la extracción en el sistema. */
53
+ extractionId: string;
54
+ /** Datos extraídos de la credencial. */
55
+ data: IneData;
56
+ /** Tokens restantes en tu cuenta tras esta extracción. */
57
+ tokensRemaining: number;
58
+ /** Método de upload detectado por la API. */
59
+ uploadMethod: string;
60
+ }
61
+ /** Imagen binaria aceptada por el SDK (Node 18+ o navegador). */
62
+ type BinaryImage = Uint8Array | ArrayBuffer | Blob;
63
+ /**
64
+ * Fuente de una imagen de credencial. Puede ser:
65
+ * - binaria (`Uint8Array` / `ArrayBuffer` / `Blob`) → se envía como multipart;
66
+ * - `{ base64 }` (con o sin prefijo `data:`) → se envía como JSON base64;
67
+ * - `{ url }` (HTTPS) → la API descarga la imagen.
68
+ * - `string` → atajo equivalente a `{ base64: string }`.
69
+ */
70
+ type ImageSource = BinaryImage | {
71
+ base64: string;
72
+ } | {
73
+ url: string;
74
+ } | string;
75
+ /** Parámetros de `client.extract()`. */
76
+ interface ExtractInput {
77
+ /** Imagen frontal (requerida). */
78
+ front: ImageSource;
79
+ /** Imagen trasera (opcional; necesaria para CIC, OCR y número vertical). */
80
+ back?: ImageSource;
81
+ /** MIME de la imagen frontal cuando es binaria. Default `image/jpeg`. */
82
+ frontMimeType?: string;
83
+ /** MIME de la imagen trasera cuando es binaria. Default `image/jpeg`. */
84
+ backMimeType?: string;
85
+ }
86
+ /** Opciones del constructor del cliente. */
87
+ interface IneExtractorOptions {
88
+ /** Tu API key (empieza con `ine_`). Requerida. */
89
+ apiKey: string;
90
+ /** URL base de la API. Default `https://extraerdatosdeine.com/api/v1`. */
91
+ baseUrl?: string;
92
+ /** Timeout por petición en ms. Default `60000`. */
93
+ timeoutMs?: number;
94
+ /** Implementación de `fetch` a usar (para tests o entornos sin fetch global). */
95
+ fetch?: typeof fetch;
96
+ }
97
+
98
+ /**
99
+ * Cliente para la API de Extraer Datos de INE.
100
+ *
101
+ * @example
102
+ * import { readFile } from 'node:fs/promises'
103
+ * import { IneExtractorClient } from 'extraer-datos-ine'
104
+ *
105
+ * const client = new IneExtractorClient({ apiKey: process.env.INE_API_KEY! })
106
+ * const front = await readFile('./ine_frente.jpg')
107
+ * const back = await readFile('./ine_reverso.jpg')
108
+ * const { data } = await client.extract({ front, back })
109
+ * console.log(data.curp, data.claveElector)
110
+ *
111
+ * @see https://extraerdatosdeine.com/docs
112
+ */
113
+ declare class IneExtractorClient {
114
+ private readonly apiKey;
115
+ private readonly baseUrl;
116
+ private readonly timeoutMs;
117
+ private readonly fetchImpl;
118
+ constructor(options: IneExtractorOptions);
119
+ /**
120
+ * Consulta el saldo de tokens de tu cuenta.
121
+ * @returns número de tokens disponibles.
122
+ */
123
+ getBalance(): Promise<number>;
124
+ /**
125
+ * Extrae los datos de una credencial INE/IFE.
126
+ *
127
+ * El método de envío se elige automáticamente según el tipo de `front`:
128
+ * binario → multipart; `{ base64 }` o string → JSON base64; `{ url }` → JSON URL.
129
+ * `front` y `back` deben ser del mismo tipo (la API no mezcla métodos).
130
+ *
131
+ * @throws {IneExtractorError} ante saldo insuficiente, imagen ilegible,
132
+ * formato inválido, error de red, timeout, etc. Revisa `error.code`.
133
+ */
134
+ extract(input: ExtractInput): Promise<ExtractResult>;
135
+ private request;
136
+ }
137
+
138
+ /**
139
+ * Códigos de error que devuelve la API de Extraer Datos de INE, más algunos
140
+ * códigos propios del SDK (`NETWORK_ERROR`, `TIMEOUT`).
141
+ *
142
+ * @see https://extraerdatosdeine.com/docs
143
+ */
144
+ type IneErrorCode = 'MISSING_API_KEY' | 'INVALID_API_KEY' | 'MISSING_IMAGE' | 'INVALID_IMAGE_FORMAT' | 'IMAGE_TOO_LARGE' | 'INVALID_BASE64' | 'INVALID_MULTIPART' | 'URL_FETCH_FAILED' | 'URL_INVALID' | 'URL_BLOCKED' | 'URL_TIMEOUT' | 'UNSUPPORTED_CONTENT_TYPE' | 'INSUFFICIENT_TOKENS' | 'LOW_IMAGE_QUALITY' | 'EXTRACTION_FAILED' | 'PROCESSING_ERROR' | 'INTERNAL_ERROR' | 'NETWORK_ERROR' | 'TIMEOUT' | 'INVALID_INPUT' | (string & {});
145
+ /**
146
+ * Error lanzado por el SDK ante cualquier respuesta no exitosa de la API
147
+ * o un fallo de red/timeout. Inspecciona `code` para reaccionar de forma
148
+ * programática.
149
+ *
150
+ * @example
151
+ * try {
152
+ * await client.extract({ front })
153
+ * } catch (err) {
154
+ * if (err instanceof IneExtractorError && err.code === 'INSUFFICIENT_TOKENS') {
155
+ * console.log('Recarga tokens en', err.enrollUrl)
156
+ * }
157
+ * }
158
+ */
159
+ declare class IneExtractorError extends Error {
160
+ /** Código de error legible por máquina. */
161
+ readonly code: IneErrorCode;
162
+ /** Status HTTP de la respuesta (ausente en errores de red). */
163
+ readonly status?: number;
164
+ /** ID de la extracción, cuando aplica (LOW_IMAGE_QUALITY / EXTRACTION_FAILED). */
165
+ readonly extractionId?: string;
166
+ /** Campos que no se pudieron leer (LOW_IMAGE_QUALITY). */
167
+ readonly missingFields?: string[];
168
+ /** URL para configurar auto-recarga (algunos INSUFFICIENT_TOKENS). */
169
+ readonly enrollUrl?: string;
170
+ /** Motivo de bloqueo (algunos INSUFFICIENT_TOKENS). */
171
+ readonly blockReason?: string;
172
+ /** Cuerpo crudo de la respuesta de error, por si necesitas más detalle. */
173
+ readonly response?: unknown;
174
+ constructor(message: string, code: IneErrorCode, init?: {
175
+ status?: number;
176
+ extractionId?: string;
177
+ missingFields?: string[];
178
+ enrollUrl?: string;
179
+ blockReason?: string;
180
+ response?: unknown;
181
+ cause?: unknown;
182
+ });
183
+ /** Construye el error a partir de una respuesta de la API. */
184
+ static fromResponse(status: number, body: unknown): IneExtractorError;
185
+ }
186
+
187
+ export { type BinaryImage, type ExtractInput, type ExtractResult, type ImageSource, type IneData, type IneErrorCode, IneExtractorClient, IneExtractorError, type IneExtractorOptions };
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Datos extraídos de una credencial INE/IFE.
3
+ *
4
+ * Todos los campos son strings. Los del reverso (`numeroVertical`, `ocr`,
5
+ * `cic`, `emision`) solo se obtienen si envías también la imagen trasera.
6
+ * Un campo puede venir vacío si no es legible en la credencial.
7
+ */
8
+ interface IneData {
9
+ /** Nombre(s) del titular */
10
+ nombre: string;
11
+ /** Apellido paterno */
12
+ apellidoPaterno: string;
13
+ /** Apellido materno */
14
+ apellidoMaterno: string;
15
+ /** Domicilio completo */
16
+ domicilio: string;
17
+ /** Calle con número */
18
+ calle: string;
19
+ /** Colonia */
20
+ colonia: string;
21
+ /** Código postal (5 dígitos) */
22
+ codigoPostal: string;
23
+ /** Municipio o alcaldía */
24
+ municipio: string;
25
+ /** Estado (abreviatura) */
26
+ estado: string;
27
+ /** Sección electoral (4 dígitos) */
28
+ seccion: string;
29
+ /** CURP (18 caracteres) */
30
+ curp: string;
31
+ /** Clave de elector (18 caracteres) */
32
+ claveElector: string;
33
+ /** Año de registro (4 dígitos) */
34
+ anioRegistro: string;
35
+ /** Fecha de nacimiento (DD/MM/AAAA) */
36
+ fechaNacimiento: string;
37
+ /** Sexo: H (hombre) o M (mujer) */
38
+ sexo: string;
39
+ /** Vigencia (año, 4 dígitos) */
40
+ vigencia: string;
41
+ /** Número vertical (reverso) */
42
+ numeroVertical: string;
43
+ /** Código OCR / MRZ (13 dígitos, reverso) */
44
+ ocr: string;
45
+ /** CIC — folio del plástico (9 dígitos, reverso) */
46
+ cic: string;
47
+ /** Número de emisión (2 dígitos, reverso) */
48
+ emision: string;
49
+ }
50
+ /** Resultado de una extracción exitosa. */
51
+ interface ExtractResult {
52
+ /** Identificador de la extracción en el sistema. */
53
+ extractionId: string;
54
+ /** Datos extraídos de la credencial. */
55
+ data: IneData;
56
+ /** Tokens restantes en tu cuenta tras esta extracción. */
57
+ tokensRemaining: number;
58
+ /** Método de upload detectado por la API. */
59
+ uploadMethod: string;
60
+ }
61
+ /** Imagen binaria aceptada por el SDK (Node 18+ o navegador). */
62
+ type BinaryImage = Uint8Array | ArrayBuffer | Blob;
63
+ /**
64
+ * Fuente de una imagen de credencial. Puede ser:
65
+ * - binaria (`Uint8Array` / `ArrayBuffer` / `Blob`) → se envía como multipart;
66
+ * - `{ base64 }` (con o sin prefijo `data:`) → se envía como JSON base64;
67
+ * - `{ url }` (HTTPS) → la API descarga la imagen.
68
+ * - `string` → atajo equivalente a `{ base64: string }`.
69
+ */
70
+ type ImageSource = BinaryImage | {
71
+ base64: string;
72
+ } | {
73
+ url: string;
74
+ } | string;
75
+ /** Parámetros de `client.extract()`. */
76
+ interface ExtractInput {
77
+ /** Imagen frontal (requerida). */
78
+ front: ImageSource;
79
+ /** Imagen trasera (opcional; necesaria para CIC, OCR y número vertical). */
80
+ back?: ImageSource;
81
+ /** MIME de la imagen frontal cuando es binaria. Default `image/jpeg`. */
82
+ frontMimeType?: string;
83
+ /** MIME de la imagen trasera cuando es binaria. Default `image/jpeg`. */
84
+ backMimeType?: string;
85
+ }
86
+ /** Opciones del constructor del cliente. */
87
+ interface IneExtractorOptions {
88
+ /** Tu API key (empieza con `ine_`). Requerida. */
89
+ apiKey: string;
90
+ /** URL base de la API. Default `https://extraerdatosdeine.com/api/v1`. */
91
+ baseUrl?: string;
92
+ /** Timeout por petición en ms. Default `60000`. */
93
+ timeoutMs?: number;
94
+ /** Implementación de `fetch` a usar (para tests o entornos sin fetch global). */
95
+ fetch?: typeof fetch;
96
+ }
97
+
98
+ /**
99
+ * Cliente para la API de Extraer Datos de INE.
100
+ *
101
+ * @example
102
+ * import { readFile } from 'node:fs/promises'
103
+ * import { IneExtractorClient } from 'extraer-datos-ine'
104
+ *
105
+ * const client = new IneExtractorClient({ apiKey: process.env.INE_API_KEY! })
106
+ * const front = await readFile('./ine_frente.jpg')
107
+ * const back = await readFile('./ine_reverso.jpg')
108
+ * const { data } = await client.extract({ front, back })
109
+ * console.log(data.curp, data.claveElector)
110
+ *
111
+ * @see https://extraerdatosdeine.com/docs
112
+ */
113
+ declare class IneExtractorClient {
114
+ private readonly apiKey;
115
+ private readonly baseUrl;
116
+ private readonly timeoutMs;
117
+ private readonly fetchImpl;
118
+ constructor(options: IneExtractorOptions);
119
+ /**
120
+ * Consulta el saldo de tokens de tu cuenta.
121
+ * @returns número de tokens disponibles.
122
+ */
123
+ getBalance(): Promise<number>;
124
+ /**
125
+ * Extrae los datos de una credencial INE/IFE.
126
+ *
127
+ * El método de envío se elige automáticamente según el tipo de `front`:
128
+ * binario → multipart; `{ base64 }` o string → JSON base64; `{ url }` → JSON URL.
129
+ * `front` y `back` deben ser del mismo tipo (la API no mezcla métodos).
130
+ *
131
+ * @throws {IneExtractorError} ante saldo insuficiente, imagen ilegible,
132
+ * formato inválido, error de red, timeout, etc. Revisa `error.code`.
133
+ */
134
+ extract(input: ExtractInput): Promise<ExtractResult>;
135
+ private request;
136
+ }
137
+
138
+ /**
139
+ * Códigos de error que devuelve la API de Extraer Datos de INE, más algunos
140
+ * códigos propios del SDK (`NETWORK_ERROR`, `TIMEOUT`).
141
+ *
142
+ * @see https://extraerdatosdeine.com/docs
143
+ */
144
+ type IneErrorCode = 'MISSING_API_KEY' | 'INVALID_API_KEY' | 'MISSING_IMAGE' | 'INVALID_IMAGE_FORMAT' | 'IMAGE_TOO_LARGE' | 'INVALID_BASE64' | 'INVALID_MULTIPART' | 'URL_FETCH_FAILED' | 'URL_INVALID' | 'URL_BLOCKED' | 'URL_TIMEOUT' | 'UNSUPPORTED_CONTENT_TYPE' | 'INSUFFICIENT_TOKENS' | 'LOW_IMAGE_QUALITY' | 'EXTRACTION_FAILED' | 'PROCESSING_ERROR' | 'INTERNAL_ERROR' | 'NETWORK_ERROR' | 'TIMEOUT' | 'INVALID_INPUT' | (string & {});
145
+ /**
146
+ * Error lanzado por el SDK ante cualquier respuesta no exitosa de la API
147
+ * o un fallo de red/timeout. Inspecciona `code` para reaccionar de forma
148
+ * programática.
149
+ *
150
+ * @example
151
+ * try {
152
+ * await client.extract({ front })
153
+ * } catch (err) {
154
+ * if (err instanceof IneExtractorError && err.code === 'INSUFFICIENT_TOKENS') {
155
+ * console.log('Recarga tokens en', err.enrollUrl)
156
+ * }
157
+ * }
158
+ */
159
+ declare class IneExtractorError extends Error {
160
+ /** Código de error legible por máquina. */
161
+ readonly code: IneErrorCode;
162
+ /** Status HTTP de la respuesta (ausente en errores de red). */
163
+ readonly status?: number;
164
+ /** ID de la extracción, cuando aplica (LOW_IMAGE_QUALITY / EXTRACTION_FAILED). */
165
+ readonly extractionId?: string;
166
+ /** Campos que no se pudieron leer (LOW_IMAGE_QUALITY). */
167
+ readonly missingFields?: string[];
168
+ /** URL para configurar auto-recarga (algunos INSUFFICIENT_TOKENS). */
169
+ readonly enrollUrl?: string;
170
+ /** Motivo de bloqueo (algunos INSUFFICIENT_TOKENS). */
171
+ readonly blockReason?: string;
172
+ /** Cuerpo crudo de la respuesta de error, por si necesitas más detalle. */
173
+ readonly response?: unknown;
174
+ constructor(message: string, code: IneErrorCode, init?: {
175
+ status?: number;
176
+ extractionId?: string;
177
+ missingFields?: string[];
178
+ enrollUrl?: string;
179
+ blockReason?: string;
180
+ response?: unknown;
181
+ cause?: unknown;
182
+ });
183
+ /** Construye el error a partir de una respuesta de la API. */
184
+ static fromResponse(status: number, body: unknown): IneExtractorError;
185
+ }
186
+
187
+ export { type BinaryImage, type ExtractInput, type ExtractResult, type ImageSource, type IneData, type IneErrorCode, IneExtractorClient, IneExtractorError, type IneExtractorOptions };
package/dist/index.js ADDED
@@ -0,0 +1,193 @@
1
+ // src/errors.ts
2
+ var IneExtractorError = class _IneExtractorError extends Error {
3
+ /** Código de error legible por máquina. */
4
+ code;
5
+ /** Status HTTP de la respuesta (ausente en errores de red). */
6
+ status;
7
+ /** ID de la extracción, cuando aplica (LOW_IMAGE_QUALITY / EXTRACTION_FAILED). */
8
+ extractionId;
9
+ /** Campos que no se pudieron leer (LOW_IMAGE_QUALITY). */
10
+ missingFields;
11
+ /** URL para configurar auto-recarga (algunos INSUFFICIENT_TOKENS). */
12
+ enrollUrl;
13
+ /** Motivo de bloqueo (algunos INSUFFICIENT_TOKENS). */
14
+ blockReason;
15
+ /** Cuerpo crudo de la respuesta de error, por si necesitas más detalle. */
16
+ response;
17
+ constructor(message, code, init = {}) {
18
+ super(message, init.cause !== void 0 ? { cause: init.cause } : void 0);
19
+ this.name = "IneExtractorError";
20
+ this.code = code;
21
+ this.status = init.status;
22
+ this.extractionId = init.extractionId;
23
+ this.missingFields = init.missingFields;
24
+ this.enrollUrl = init.enrollUrl;
25
+ this.blockReason = init.blockReason;
26
+ this.response = init.response;
27
+ Object.setPrototypeOf(this, _IneExtractorError.prototype);
28
+ }
29
+ /** Construye el error a partir de una respuesta de la API. */
30
+ static fromResponse(status, body) {
31
+ const b = body ?? {};
32
+ const code = typeof b.code === "string" ? b.code : "INTERNAL_ERROR";
33
+ const message = typeof b.error === "string" && b.error || `La API respondi\xF3 con status ${status}`;
34
+ return new _IneExtractorError(message, code, {
35
+ status,
36
+ extractionId: typeof b.extraction_id === "string" ? b.extraction_id : void 0,
37
+ missingFields: Array.isArray(b.missing_fields) ? b.missing_fields : void 0,
38
+ enrollUrl: typeof b.enroll_url === "string" ? b.enroll_url : void 0,
39
+ blockReason: typeof b.block_reason === "string" ? b.block_reason : void 0,
40
+ response: body
41
+ });
42
+ }
43
+ };
44
+
45
+ // src/client.ts
46
+ var DEFAULT_BASE_URL = "https://extraerdatosdeine.com/api/v1";
47
+ var DEFAULT_TIMEOUT_MS = 6e4;
48
+ var IneExtractorClient = class {
49
+ apiKey;
50
+ baseUrl;
51
+ timeoutMs;
52
+ fetchImpl;
53
+ constructor(options) {
54
+ if (!options || !options.apiKey) {
55
+ throw new IneExtractorError("Falta `apiKey` en las opciones del cliente.", "INVALID_INPUT");
56
+ }
57
+ const fetchImpl = options.fetch ?? globalThis.fetch;
58
+ if (typeof fetchImpl !== "function") {
59
+ throw new IneExtractorError(
60
+ "No hay `fetch` disponible. Usa Node 18+ o pasa `fetch` en las opciones.",
61
+ "INVALID_INPUT"
62
+ );
63
+ }
64
+ this.apiKey = options.apiKey;
65
+ this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
66
+ this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
67
+ this.fetchImpl = fetchImpl;
68
+ }
69
+ /**
70
+ * Consulta el saldo de tokens de tu cuenta.
71
+ * @returns número de tokens disponibles.
72
+ */
73
+ async getBalance() {
74
+ const body = await this.request("/balance", { method: "GET" });
75
+ return Number(body.balance);
76
+ }
77
+ /**
78
+ * Extrae los datos de una credencial INE/IFE.
79
+ *
80
+ * El método de envío se elige automáticamente según el tipo de `front`:
81
+ * binario → multipart; `{ base64 }` o string → JSON base64; `{ url }` → JSON URL.
82
+ * `front` y `back` deben ser del mismo tipo (la API no mezcla métodos).
83
+ *
84
+ * @throws {IneExtractorError} ante saldo insuficiente, imagen ilegible,
85
+ * formato inválido, error de red, timeout, etc. Revisa `error.code`.
86
+ */
87
+ async extract(input) {
88
+ if (!input || input.front === void 0 || input.front === null) {
89
+ throw new IneExtractorError("Falta la imagen frontal (`front`).", "MISSING_IMAGE");
90
+ }
91
+ const frontKind = kindOf(input.front);
92
+ if (input.back !== void 0 && kindOf(input.back) !== frontKind) {
93
+ throw new IneExtractorError(
94
+ "`front` y `back` deben ser del mismo tipo (ambos binarios, base64 o url).",
95
+ "INVALID_INPUT"
96
+ );
97
+ }
98
+ let init;
99
+ if (frontKind === "url") {
100
+ const payload = {
101
+ image_front_url: input.front.url
102
+ };
103
+ if (input.back) payload.image_back_url = input.back.url;
104
+ init = jsonRequest(payload);
105
+ } else if (frontKind === "base64") {
106
+ const payload = { image_front: asBase64(input.front) };
107
+ if (input.back) payload.image_back = asBase64(input.back);
108
+ init = jsonRequest(payload);
109
+ } else {
110
+ const form = new FormData();
111
+ form.append("image_front", toBlob(input.front, input.frontMimeType));
112
+ if (input.back) {
113
+ form.append("image_back", toBlob(input.back, input.backMimeType));
114
+ }
115
+ init = { method: "POST", body: form };
116
+ }
117
+ const body = await this.request("/extract", init);
118
+ return {
119
+ extractionId: body.extraction_id,
120
+ data: body.data,
121
+ tokensRemaining: body.tokens_remaining,
122
+ uploadMethod: body.upload_method
123
+ };
124
+ }
125
+ async request(path, init) {
126
+ const controller = new AbortController();
127
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
128
+ let res;
129
+ try {
130
+ res = await this.fetchImpl(this.baseUrl + path, {
131
+ ...init,
132
+ signal: controller.signal,
133
+ headers: { "X-API-Key": this.apiKey, ...init.headers ?? {} }
134
+ });
135
+ } catch (err) {
136
+ if (err instanceof Error && err.name === "AbortError") {
137
+ throw new IneExtractorError(
138
+ `La petici\xF3n excedi\xF3 el timeout de ${this.timeoutMs} ms.`,
139
+ "TIMEOUT",
140
+ { cause: err }
141
+ );
142
+ }
143
+ throw new IneExtractorError(
144
+ err instanceof Error ? err.message : "Error de red",
145
+ "NETWORK_ERROR",
146
+ { cause: err }
147
+ );
148
+ } finally {
149
+ clearTimeout(timer);
150
+ }
151
+ const body = await res.json().catch(() => ({}));
152
+ if (!res.ok || body?.success === false) {
153
+ throw IneExtractorError.fromResponse(res.status, body);
154
+ }
155
+ return body;
156
+ }
157
+ };
158
+ function kindOf(src) {
159
+ if (typeof src === "string") return "base64";
160
+ if (src instanceof Uint8Array || src instanceof ArrayBuffer) return "binary";
161
+ if (src instanceof Blob) return "binary";
162
+ if (typeof src === "object" && src !== null) {
163
+ if ("url" in src) return "url";
164
+ if ("base64" in src) return "base64";
165
+ }
166
+ throw new IneExtractorError(
167
+ "Fuente de imagen no soportada. Usa Uint8Array, ArrayBuffer, Blob, { base64 } o { url }.",
168
+ "INVALID_INPUT"
169
+ );
170
+ }
171
+ function asBase64(src) {
172
+ if (typeof src === "string") return src;
173
+ if (typeof src === "object" && src !== null && "base64" in src) return src.base64;
174
+ throw new IneExtractorError("Se esperaba una cadena base64.", "INVALID_INPUT");
175
+ }
176
+ function toBlob(src, mimeType = "image/jpeg") {
177
+ if (src instanceof Blob) return src;
178
+ if (src instanceof ArrayBuffer) return new Blob([src], { type: mimeType });
179
+ const bytes = src.buffer.slice(src.byteOffset, src.byteOffset + src.byteLength);
180
+ return new Blob([bytes], { type: mimeType });
181
+ }
182
+ function jsonRequest(payload) {
183
+ return {
184
+ method: "POST",
185
+ headers: { "Content-Type": "application/json" },
186
+ body: JSON.stringify(payload)
187
+ };
188
+ }
189
+ export {
190
+ IneExtractorClient,
191
+ IneExtractorError
192
+ };
193
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/client.ts"],"sourcesContent":["/**\n * Códigos de error que devuelve la API de Extraer Datos de INE, más algunos\n * códigos propios del SDK (`NETWORK_ERROR`, `TIMEOUT`).\n *\n * @see https://extraerdatosdeine.com/docs\n */\nexport type IneErrorCode =\n // 401\n | 'MISSING_API_KEY'\n | 'INVALID_API_KEY'\n // 400\n | 'MISSING_IMAGE'\n | 'INVALID_IMAGE_FORMAT'\n | 'IMAGE_TOO_LARGE'\n | 'INVALID_BASE64'\n | 'INVALID_MULTIPART'\n | 'URL_FETCH_FAILED'\n | 'URL_INVALID'\n | 'URL_BLOCKED'\n | 'URL_TIMEOUT'\n // 415\n | 'UNSUPPORTED_CONTENT_TYPE'\n // 402\n | 'INSUFFICIENT_TOKENS'\n // 422\n | 'LOW_IMAGE_QUALITY'\n // 500\n | 'EXTRACTION_FAILED'\n | 'PROCESSING_ERROR'\n | 'INTERNAL_ERROR'\n // SDK-only\n | 'NETWORK_ERROR'\n | 'TIMEOUT'\n | 'INVALID_INPUT'\n // forward-compatible: unknown server codes\n | (string & {})\n\n/**\n * Error lanzado por el SDK ante cualquier respuesta no exitosa de la API\n * o un fallo de red/timeout. Inspecciona `code` para reaccionar de forma\n * programática.\n *\n * @example\n * try {\n * await client.extract({ front })\n * } catch (err) {\n * if (err instanceof IneExtractorError && err.code === 'INSUFFICIENT_TOKENS') {\n * console.log('Recarga tokens en', err.enrollUrl)\n * }\n * }\n */\nexport class IneExtractorError extends Error {\n /** Código de error legible por máquina. */\n readonly code: IneErrorCode\n /** Status HTTP de la respuesta (ausente en errores de red). */\n readonly status?: number\n /** ID de la extracción, cuando aplica (LOW_IMAGE_QUALITY / EXTRACTION_FAILED). */\n readonly extractionId?: string\n /** Campos que no se pudieron leer (LOW_IMAGE_QUALITY). */\n readonly missingFields?: string[]\n /** URL para configurar auto-recarga (algunos INSUFFICIENT_TOKENS). */\n readonly enrollUrl?: string\n /** Motivo de bloqueo (algunos INSUFFICIENT_TOKENS). */\n readonly blockReason?: string\n /** Cuerpo crudo de la respuesta de error, por si necesitas más detalle. */\n readonly response?: unknown\n\n constructor(\n message: string,\n code: IneErrorCode,\n init: {\n status?: number\n extractionId?: string\n missingFields?: string[]\n enrollUrl?: string\n blockReason?: string\n response?: unknown\n cause?: unknown\n } = {}\n ) {\n super(message, init.cause !== undefined ? { cause: init.cause } : undefined)\n this.name = 'IneExtractorError'\n this.code = code\n this.status = init.status\n this.extractionId = init.extractionId\n this.missingFields = init.missingFields\n this.enrollUrl = init.enrollUrl\n this.blockReason = init.blockReason\n this.response = init.response\n // Restore prototype chain for instanceof across transpile targets.\n Object.setPrototypeOf(this, IneExtractorError.prototype)\n }\n\n /** Construye el error a partir de una respuesta de la API. */\n static fromResponse(status: number, body: unknown): IneExtractorError {\n const b = (body ?? {}) as Record<string, unknown>\n const code = (typeof b.code === 'string' ? b.code : 'INTERNAL_ERROR') as IneErrorCode\n const message =\n (typeof b.error === 'string' && b.error) ||\n `La API respondió con status ${status}`\n return new IneExtractorError(message, code, {\n status,\n extractionId: typeof b.extraction_id === 'string' ? b.extraction_id : undefined,\n missingFields: Array.isArray(b.missing_fields)\n ? (b.missing_fields as string[])\n : undefined,\n enrollUrl: typeof b.enroll_url === 'string' ? b.enroll_url : undefined,\n blockReason: typeof b.block_reason === 'string' ? b.block_reason : undefined,\n response: body,\n })\n }\n}\n","import { IneExtractorError } from './errors.js'\nimport type {\n BinaryImage,\n ExtractInput,\n ExtractResult,\n ImageSource,\n IneExtractorOptions,\n} from './types.js'\n\nconst DEFAULT_BASE_URL = 'https://extraerdatosdeine.com/api/v1'\nconst DEFAULT_TIMEOUT_MS = 60_000\n\n/**\n * Cliente para la API de Extraer Datos de INE.\n *\n * @example\n * import { readFile } from 'node:fs/promises'\n * import { IneExtractorClient } from 'extraer-datos-ine'\n *\n * const client = new IneExtractorClient({ apiKey: process.env.INE_API_KEY! })\n * const front = await readFile('./ine_frente.jpg')\n * const back = await readFile('./ine_reverso.jpg')\n * const { data } = await client.extract({ front, back })\n * console.log(data.curp, data.claveElector)\n *\n * @see https://extraerdatosdeine.com/docs\n */\nexport class IneExtractorClient {\n private readonly apiKey: string\n private readonly baseUrl: string\n private readonly timeoutMs: number\n private readonly fetchImpl: typeof fetch\n\n constructor(options: IneExtractorOptions) {\n if (!options || !options.apiKey) {\n throw new IneExtractorError('Falta `apiKey` en las opciones del cliente.', 'INVALID_INPUT')\n }\n const fetchImpl = options.fetch ?? globalThis.fetch\n if (typeof fetchImpl !== 'function') {\n throw new IneExtractorError(\n 'No hay `fetch` disponible. Usa Node 18+ o pasa `fetch` en las opciones.',\n 'INVALID_INPUT'\n )\n }\n this.apiKey = options.apiKey\n this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/$/, '')\n this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS\n this.fetchImpl = fetchImpl\n }\n\n /**\n * Consulta el saldo de tokens de tu cuenta.\n * @returns número de tokens disponibles.\n */\n async getBalance(): Promise<number> {\n const body = await this.request('/balance', { method: 'GET' })\n return Number((body as { balance: number }).balance)\n }\n\n /**\n * Extrae los datos de una credencial INE/IFE.\n *\n * El método de envío se elige automáticamente según el tipo de `front`:\n * binario → multipart; `{ base64 }` o string → JSON base64; `{ url }` → JSON URL.\n * `front` y `back` deben ser del mismo tipo (la API no mezcla métodos).\n *\n * @throws {IneExtractorError} ante saldo insuficiente, imagen ilegible,\n * formato inválido, error de red, timeout, etc. Revisa `error.code`.\n */\n async extract(input: ExtractInput): Promise<ExtractResult> {\n if (!input || input.front === undefined || input.front === null) {\n throw new IneExtractorError('Falta la imagen frontal (`front`).', 'MISSING_IMAGE')\n }\n\n const frontKind = kindOf(input.front)\n if (input.back !== undefined && kindOf(input.back) !== frontKind) {\n throw new IneExtractorError(\n '`front` y `back` deben ser del mismo tipo (ambos binarios, base64 o url).',\n 'INVALID_INPUT'\n )\n }\n\n let init: RequestInit\n if (frontKind === 'url') {\n const payload: Record<string, string> = {\n image_front_url: (input.front as { url: string }).url,\n }\n if (input.back) payload.image_back_url = (input.back as { url: string }).url\n init = jsonRequest(payload)\n } else if (frontKind === 'base64') {\n const payload: Record<string, string> = { image_front: asBase64(input.front) }\n if (input.back) payload.image_back = asBase64(input.back)\n init = jsonRequest(payload)\n } else {\n const form = new FormData()\n form.append('image_front', toBlob(input.front as BinaryImage, input.frontMimeType))\n if (input.back) {\n form.append('image_back', toBlob(input.back as BinaryImage, input.backMimeType))\n }\n init = { method: 'POST', body: form }\n }\n\n const body = (await this.request('/extract', init)) as {\n extraction_id: string\n data: ExtractResult['data']\n tokens_remaining: number\n upload_method: string\n }\n\n return {\n extractionId: body.extraction_id,\n data: body.data,\n tokensRemaining: body.tokens_remaining,\n uploadMethod: body.upload_method,\n }\n }\n\n private async request(path: string, init: RequestInit): Promise<unknown> {\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), this.timeoutMs)\n\n let res: Response\n try {\n res = await this.fetchImpl(this.baseUrl + path, {\n ...init,\n signal: controller.signal,\n headers: { 'X-API-Key': this.apiKey, ...(init.headers ?? {}) },\n })\n } catch (err) {\n if (err instanceof Error && err.name === 'AbortError') {\n throw new IneExtractorError(\n `La petición excedió el timeout de ${this.timeoutMs} ms.`,\n 'TIMEOUT',\n { cause: err }\n )\n }\n throw new IneExtractorError(\n err instanceof Error ? err.message : 'Error de red',\n 'NETWORK_ERROR',\n { cause: err }\n )\n } finally {\n clearTimeout(timer)\n }\n\n const body = await res.json().catch(() => ({}))\n if (!res.ok || (body as { success?: boolean })?.success === false) {\n throw IneExtractorError.fromResponse(res.status, body)\n }\n return body\n }\n}\n\ntype SourceKind = 'binary' | 'base64' | 'url'\n\nfunction kindOf(src: ImageSource): SourceKind {\n if (typeof src === 'string') return 'base64'\n if (src instanceof Uint8Array || src instanceof ArrayBuffer) return 'binary'\n if (src instanceof Blob) return 'binary'\n if (typeof src === 'object' && src !== null) {\n if ('url' in src) return 'url'\n if ('base64' in src) return 'base64'\n }\n throw new IneExtractorError(\n 'Fuente de imagen no soportada. Usa Uint8Array, ArrayBuffer, Blob, { base64 } o { url }.',\n 'INVALID_INPUT'\n )\n}\n\nfunction asBase64(src: ImageSource): string {\n if (typeof src === 'string') return src\n if (typeof src === 'object' && src !== null && 'base64' in src) return src.base64\n throw new IneExtractorError('Se esperaba una cadena base64.', 'INVALID_INPUT')\n}\n\nfunction toBlob(src: BinaryImage, mimeType = 'image/jpeg'): Blob {\n if (src instanceof Blob) return src\n if (src instanceof ArrayBuffer) return new Blob([src], { type: mimeType })\n // Uint8Array: copy exactly its bytes into a standalone ArrayBuffer so the\n // result is a valid BlobPart regardless of the backing buffer kind.\n const bytes = src.buffer.slice(src.byteOffset, src.byteOffset + src.byteLength)\n return new Blob([bytes as ArrayBuffer], { type: mimeType })\n}\n\nfunction jsonRequest(payload: Record<string, string>): RequestInit {\n return {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n }\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "extraer-datos-ine",
3
+ "version": "1.0.0",
4
+ "description": "SDK oficial de Extraer Datos de INE — API de OCR para credenciales INE/IFE mexicanas (CURP, clave de elector, dirección y más).",
5
+ "keywords": [
6
+ "INE",
7
+ "IFE",
8
+ "OCR",
9
+ "CURP",
10
+ "clave de elector",
11
+ "credencial para votar",
12
+ "Mexico",
13
+ "KYC",
14
+ "extraccion de datos",
15
+ "API"
16
+ ],
17
+ "homepage": "https://extraerdatosdeine.com/docs",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/rcfrias/ine-extractor-js.git"
21
+ },
22
+ "bugs": {
23
+ "url": "https://github.com/rcfrias/ine-extractor-js/issues"
24
+ },
25
+ "license": "MIT",
26
+ "author": "Extraer Datos de INE (https://extraerdatosdeine.com)",
27
+ "type": "module",
28
+ "main": "./dist/index.cjs",
29
+ "module": "./dist/index.js",
30
+ "types": "./dist/index.d.ts",
31
+ "exports": {
32
+ ".": {
33
+ "types": "./dist/index.d.ts",
34
+ "import": "./dist/index.js",
35
+ "require": "./dist/index.cjs"
36
+ }
37
+ },
38
+ "files": [
39
+ "dist",
40
+ "README.md",
41
+ "LICENSE"
42
+ ],
43
+ "engines": {
44
+ "node": ">=18"
45
+ },
46
+ "scripts": {
47
+ "build": "tsup",
48
+ "typecheck": "tsc --noEmit",
49
+ "test": "npm run build && node --test",
50
+ "prepublishOnly": "npm run build"
51
+ },
52
+ "devDependencies": {
53
+ "@types/node": "^22.10.0",
54
+ "tsup": "^8.3.5",
55
+ "typescript": "^5.7.2"
56
+ }
57
+ }