@veriko-mx/sdk 0.4.3
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/CHANGELOG.md +186 -0
- package/LICENSE +21 -0
- package/README.md +566 -0
- package/dist/cjs/client.d.ts +128 -0
- package/dist/cjs/client.d.ts.map +1 -0
- package/dist/cjs/client.js +145 -0
- package/dist/cjs/client.js.map +1 -0
- package/dist/cjs/errors.d.ts +110 -0
- package/dist/cjs/errors.d.ts.map +1 -0
- package/dist/cjs/errors.js +133 -0
- package/dist/cjs/errors.js.map +1 -0
- package/dist/cjs/generated/openapi.d.ts +8404 -0
- package/dist/cjs/generated/openapi.d.ts.map +1 -0
- package/dist/cjs/generated/openapi.js +7 -0
- package/dist/cjs/generated/openapi.js.map +1 -0
- package/dist/cjs/http.d.ts +75 -0
- package/dist/cjs/http.d.ts.map +1 -0
- package/dist/cjs/http.js +237 -0
- package/dist/cjs/http.js.map +1 -0
- package/dist/cjs/index.d.ts +35 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +91 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/internal.d.ts +40 -0
- package/dist/cjs/internal.d.ts.map +1 -0
- package/dist/cjs/internal.js +92 -0
- package/dist/cjs/internal.js.map +1 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/pagination.d.ts +41 -0
- package/dist/cjs/pagination.d.ts.map +1 -0
- package/dist/cjs/pagination.js +66 -0
- package/dist/cjs/pagination.js.map +1 -0
- package/dist/cjs/resources.d.ts +522 -0
- package/dist/cjs/resources.d.ts.map +1 -0
- package/dist/cjs/resources.js +1361 -0
- package/dist/cjs/resources.js.map +1 -0
- package/dist/cjs/types.d.ts +559 -0
- package/dist/cjs/types.d.ts.map +1 -0
- package/dist/cjs/types.js +117 -0
- package/dist/cjs/types.js.map +1 -0
- package/dist/cjs/webhooks.d.ts +54 -0
- package/dist/cjs/webhooks.d.ts.map +1 -0
- package/dist/cjs/webhooks.js +116 -0
- package/dist/cjs/webhooks.js.map +1 -0
- package/dist/esm/client.d.ts +128 -0
- package/dist/esm/client.d.ts.map +1 -0
- package/dist/esm/client.js +141 -0
- package/dist/esm/client.js.map +1 -0
- package/dist/esm/errors.d.ts +110 -0
- package/dist/esm/errors.d.ts.map +1 -0
- package/dist/esm/errors.js +117 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/generated/openapi.d.ts +8404 -0
- package/dist/esm/generated/openapi.d.ts.map +1 -0
- package/dist/esm/generated/openapi.js +6 -0
- package/dist/esm/generated/openapi.js.map +1 -0
- package/dist/esm/http.d.ts +75 -0
- package/dist/esm/http.d.ts.map +1 -0
- package/dist/esm/http.js +229 -0
- package/dist/esm/http.js.map +1 -0
- package/dist/esm/index.d.ts +35 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +33 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/internal.d.ts +40 -0
- package/dist/esm/internal.d.ts.map +1 -0
- package/dist/esm/internal.js +82 -0
- package/dist/esm/internal.js.map +1 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/pagination.d.ts +41 -0
- package/dist/esm/pagination.d.ts.map +1 -0
- package/dist/esm/pagination.js +62 -0
- package/dist/esm/pagination.js.map +1 -0
- package/dist/esm/resources.d.ts +522 -0
- package/dist/esm/resources.d.ts.map +1 -0
- package/dist/esm/resources.js +1346 -0
- package/dist/esm/resources.js.map +1 -0
- package/dist/esm/types.d.ts +559 -0
- package/dist/esm/types.d.ts.map +1 -0
- package/dist/esm/types.js +109 -0
- package/dist/esm/types.js.map +1 -0
- package/dist/esm/webhooks.d.ts +54 -0
- package/dist/esm/webhooks.d.ts.map +1 -0
- package/dist/esm/webhooks.js +109 -0
- package/dist/esm/webhooks.js.map +1 -0
- package/package.json +78 -0
|
@@ -0,0 +1,1361 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Las operaciones de la API, agrupadas por familia.
|
|
4
|
+
*
|
|
5
|
+
* Cada familia es una propiedad del cliente: `client.validations`,
|
|
6
|
+
* `client.webhooks` y `client.catalog`. Los tres métodos que estrenó la versión
|
|
7
|
+
* 0.1.0 siguen en la raíz del cliente, porque son el camino corto del caso de
|
|
8
|
+
* uso principal.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.Billing = exports.Finance = exports.Insights = exports.Plans = exports.Dashboard = exports.Account = exports.Usage = exports.Beneficiaries = exports.Catalog = exports.Webhooks = exports.Validations = void 0;
|
|
12
|
+
exports.filtersToQuery = filtersToQuery;
|
|
13
|
+
const promises_1 = require("node:fs/promises");
|
|
14
|
+
const node_path_1 = require("node:path");
|
|
15
|
+
const node_perf_hooks_1 = require("node:perf_hooks");
|
|
16
|
+
const errors_js_1 = require("./errors.js");
|
|
17
|
+
const http_js_1 = require("./http.js");
|
|
18
|
+
const internal_js_1 = require("./internal.js");
|
|
19
|
+
const pagination_js_1 = require("./pagination.js");
|
|
20
|
+
const types_js_1 = require("./types.js");
|
|
21
|
+
const DEFAULT_WAIT_TIMEOUT_MS = 300_000;
|
|
22
|
+
const DEFAULT_POLL_INTERVAL_MS = 5_000;
|
|
23
|
+
const EXPORT_CONTENT_TYPES = {
|
|
24
|
+
csv: 'text/csv',
|
|
25
|
+
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
26
|
+
};
|
|
27
|
+
const IMAGE_ACCEPT = 'image/png, image/jpeg, image/webp, application/octet-stream, application/json';
|
|
28
|
+
const IMAGE_EXTENSIONS = {
|
|
29
|
+
'image/png': '.png',
|
|
30
|
+
'image/jpeg': '.jpg',
|
|
31
|
+
'image/webp': '.webp',
|
|
32
|
+
};
|
|
33
|
+
// ── Ayudantes ───────────────────────────────────────────────────────────────
|
|
34
|
+
function flag(value) {
|
|
35
|
+
if (value === undefined)
|
|
36
|
+
return undefined;
|
|
37
|
+
return value ? '1' : '0';
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Los filtros de una consulta, con el nombre que la API espera.
|
|
41
|
+
*
|
|
42
|
+
* `test/spec.test.ts` compara las claves de este resultado con los parámetros de
|
|
43
|
+
* `GET /v1/validations`, de modo que un filtro nuevo del spec no pase inadvertido.
|
|
44
|
+
*/
|
|
45
|
+
function filtersToQuery(filters) {
|
|
46
|
+
return {
|
|
47
|
+
status: (0, internal_js_1.joinList)(filters.status),
|
|
48
|
+
type: filters.type,
|
|
49
|
+
from: filters.from,
|
|
50
|
+
to: filters.to,
|
|
51
|
+
search: filters.search,
|
|
52
|
+
playground: filters.playground ? '1' : undefined,
|
|
53
|
+
with_deleted: flag(filters.withDeleted),
|
|
54
|
+
batch_id: filters.batchId,
|
|
55
|
+
bank: filters.bank,
|
|
56
|
+
amount_min: filters.amountMin,
|
|
57
|
+
amount_max: filters.amountMax,
|
|
58
|
+
retry_state: filters.retryState,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function idempotencyHeader(key) {
|
|
62
|
+
return { 'idempotency-key': key };
|
|
63
|
+
}
|
|
64
|
+
/** El cuerpo de una validación por campos, con su comprobación previa. */
|
|
65
|
+
function directBody(params) {
|
|
66
|
+
if (!params.claveRastreo && !params.referenciaNumerica) {
|
|
67
|
+
throw new errors_js_1.InvalidRequestError('Hace falta claveRastreo o referenciaNumerica para buscar la transferencia en el CEP', { status: 422, code: 'clave_or_ref_required' });
|
|
68
|
+
}
|
|
69
|
+
const body = {
|
|
70
|
+
fecha: params.fecha,
|
|
71
|
+
monto: typeof params.monto === 'string' ? Number(params.monto) : params.monto,
|
|
72
|
+
};
|
|
73
|
+
if (params.claveRastreo !== undefined)
|
|
74
|
+
body.clave_rastreo = params.claveRastreo;
|
|
75
|
+
if (params.referenciaNumerica !== undefined) {
|
|
76
|
+
body.referencia_numerica = params.referenciaNumerica;
|
|
77
|
+
}
|
|
78
|
+
if (params.cuentaBeneficiaria !== undefined) {
|
|
79
|
+
body.cuenta_beneficiaria = params.cuentaBeneficiaria;
|
|
80
|
+
}
|
|
81
|
+
if (params.emisor !== undefined)
|
|
82
|
+
body.emisor = params.emisor;
|
|
83
|
+
if (params.receptor !== undefined)
|
|
84
|
+
body.receptor = params.receptor;
|
|
85
|
+
if (params.receptorParticipante !== undefined) {
|
|
86
|
+
body.receptor_participante = params.receptorParticipante;
|
|
87
|
+
}
|
|
88
|
+
if (params.retryPolicy !== undefined)
|
|
89
|
+
body.retry_policy = params.retryPolicy;
|
|
90
|
+
return body;
|
|
91
|
+
}
|
|
92
|
+
/** Devuelve la imagen en base64, leyéndola del disco cuando es una ruta. */
|
|
93
|
+
async function encodeImage(image) {
|
|
94
|
+
if (typeof image !== 'string')
|
|
95
|
+
return Buffer.from(image).toString('base64');
|
|
96
|
+
let content;
|
|
97
|
+
try {
|
|
98
|
+
content = await (0, promises_1.readFile)(image);
|
|
99
|
+
}
|
|
100
|
+
catch (cause) {
|
|
101
|
+
const missing = typeof cause === 'object' && cause !== null && 'code' in cause && cause.code === 'ENOENT';
|
|
102
|
+
throw new errors_js_1.ConfigurationError(missing
|
|
103
|
+
? `No existe el archivo de imagen: ${image}`
|
|
104
|
+
: `No se pudo leer el archivo de imagen ${image}: ${String(cause)}`);
|
|
105
|
+
}
|
|
106
|
+
return content.toString('base64');
|
|
107
|
+
}
|
|
108
|
+
/** El cuerpo de una validación por imagen, con su comprobación previa. */
|
|
109
|
+
async function ocrBody(params) {
|
|
110
|
+
if (params.image === undefined && !params.imageUrl) {
|
|
111
|
+
throw new errors_js_1.InvalidRequestError('Hace falta la imagen del comprobante: pasa `image` o `imageUrl`', { status: 422, code: 'image_required' });
|
|
112
|
+
}
|
|
113
|
+
const body = {};
|
|
114
|
+
if (params.image !== undefined)
|
|
115
|
+
body.image = await encodeImage(params.image);
|
|
116
|
+
if (params.imageUrl)
|
|
117
|
+
body.image_url = params.imageUrl;
|
|
118
|
+
if (params.cuentaBeneficiaria)
|
|
119
|
+
body.cuenta_beneficiaria = params.cuentaBeneficiaria;
|
|
120
|
+
if (params.retryPolicy !== undefined)
|
|
121
|
+
body.retry_policy = params.retryPolicy;
|
|
122
|
+
return body;
|
|
123
|
+
}
|
|
124
|
+
function readQueued(response) {
|
|
125
|
+
const document = (0, internal_js_1.readDocument)(response);
|
|
126
|
+
const data = document['data'];
|
|
127
|
+
if (!data?.id) {
|
|
128
|
+
throw new errors_js_1.ApiError('La respuesta 202 no trae el identificador de la validación', {
|
|
129
|
+
status: response.status,
|
|
130
|
+
headers: response.headers,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
const poll = (0, internal_js_1.readMeta)(document)['next_poll_after_seconds'];
|
|
134
|
+
return {
|
|
135
|
+
id: data.id,
|
|
136
|
+
status: data.attributes?.status ?? 'queued',
|
|
137
|
+
etag: response.headers['etag'],
|
|
138
|
+
location: response.headers['location'],
|
|
139
|
+
nextPollAfterSeconds: typeof poll === 'number' ? poll : undefined,
|
|
140
|
+
data,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function toFile(response, filename, contentType) {
|
|
144
|
+
return {
|
|
145
|
+
content: response.body,
|
|
146
|
+
contentType,
|
|
147
|
+
filename: (0, http_js_1.filenameFromContentDisposition)(response.headers['content-disposition'], filename),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
function notModified(what, response) {
|
|
151
|
+
return new errors_js_1.ApiError(`${what} no cambió desde el ETag indicado`, {
|
|
152
|
+
status: 304,
|
|
153
|
+
headers: response.headers,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
/** Descarga una exportación en CSV o en XLSX. */
|
|
157
|
+
async function exportFile(transport, path, format, query, fallbackName, extraAccept) {
|
|
158
|
+
if (!types_js_1.EXPORT_FORMATS.includes(format)) {
|
|
159
|
+
throw new errors_js_1.ConfigurationError(`El formato de exportación es 'csv' o 'xlsx'; llegó ${JSON.stringify(format)}`);
|
|
160
|
+
}
|
|
161
|
+
const accept = EXPORT_CONTENT_TYPES[format];
|
|
162
|
+
const response = await transport.request({
|
|
163
|
+
method: 'GET',
|
|
164
|
+
path,
|
|
165
|
+
query: { ...query, format },
|
|
166
|
+
accept: [accept, extraAccept, 'application/json'].filter(Boolean).join(', '),
|
|
167
|
+
});
|
|
168
|
+
return toFile(response, `${fallbackName}.${format}`, response.headers['content-type'] ?? accept);
|
|
169
|
+
}
|
|
170
|
+
// ── Validaciones ────────────────────────────────────────────────────────────
|
|
171
|
+
/** Validaciones SPEI: crearlas, seguirlas y descargar lo que producen. */
|
|
172
|
+
class Validations {
|
|
173
|
+
transport;
|
|
174
|
+
constructor(transport) {
|
|
175
|
+
this.transport = transport;
|
|
176
|
+
}
|
|
177
|
+
// Crear
|
|
178
|
+
/**
|
|
179
|
+
* Valida una transferencia SPEI contra el CEP de Banxico.
|
|
180
|
+
*
|
|
181
|
+
* `POST /v1/validate`. Devuelve el veredicto en `attributes.status`. Para
|
|
182
|
+
* volumen está `enqueue()`, que acepta la petición y deja el veredicto para
|
|
183
|
+
* después.
|
|
184
|
+
*/
|
|
185
|
+
async validate(params) {
|
|
186
|
+
const response = await this.post('/validate', directBody(params), params.idempotencyKey, false);
|
|
187
|
+
return (0, internal_js_1.readData)(response, 'de la validación');
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Valida una transferencia a partir de la imagen del comprobante.
|
|
191
|
+
*
|
|
192
|
+
* `POST /v1/validate-ocr`. La imagen se lee del disco cuando `image` es una
|
|
193
|
+
* ruta, y se codifica en base64 aquí. Formatos: JPEG, PNG o WebP.
|
|
194
|
+
*
|
|
195
|
+
* `imageUrl` sirve para una imagen ya publicada en HTTPS. Si se envían las
|
|
196
|
+
* dos, la API sólo considera `image`.
|
|
197
|
+
*/
|
|
198
|
+
async validateOcr(params) {
|
|
199
|
+
const response = await this.post('/validate-ocr', await ocrBody(params), params.idempotencyKey, false);
|
|
200
|
+
return (0, internal_js_1.readData)(response, 'de la validación');
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Encola una validación por campos y devuelve el acuse.
|
|
204
|
+
*
|
|
205
|
+
* `POST /v1/validate?async=1`. Acepta los mismos argumentos que `validate()`.
|
|
206
|
+
* La API responde `202` con el identificador; el veredicto se recoge con
|
|
207
|
+
* `waitFor()`, o llega por webhook.
|
|
208
|
+
*
|
|
209
|
+
* @see https://docs.veriko.mx/es/concepts/async-validations
|
|
210
|
+
*/
|
|
211
|
+
async enqueue(params) {
|
|
212
|
+
const response = await this.post('/validate', directBody(params), params.idempotencyKey, true);
|
|
213
|
+
return readQueued(response);
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Encola una validación por imagen y devuelve el acuse.
|
|
217
|
+
*
|
|
218
|
+
* `POST /v1/validate-ocr?async=1`. Acepta los mismos argumentos que
|
|
219
|
+
* `validateOcr()`.
|
|
220
|
+
*/
|
|
221
|
+
async enqueueOcr(params) {
|
|
222
|
+
const response = await this.post('/validate-ocr', await ocrBody(params), params.idempotencyKey, true);
|
|
223
|
+
return readQueued(response);
|
|
224
|
+
}
|
|
225
|
+
post(path, body, idempotencyKey, async) {
|
|
226
|
+
return this.transport.request({
|
|
227
|
+
method: 'POST',
|
|
228
|
+
path,
|
|
229
|
+
body,
|
|
230
|
+
query: async ? { async: 1 } : undefined,
|
|
231
|
+
headers: idempotencyHeader(idempotencyKey ?? (0, internal_js_1.newIdempotencyKey)()),
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
// Consultar
|
|
235
|
+
/**
|
|
236
|
+
* Lee una validación por su identificador.
|
|
237
|
+
*
|
|
238
|
+
* `GET /v1/validations/{id}`. Con `ifNoneMatch` la API responde `304` cuando
|
|
239
|
+
* nada cambió desde ese `ETag`, y el SDK lo lanza como `ApiError` con
|
|
240
|
+
* `status` 304. El `ETag` de cada lectura queda en `etag`.
|
|
241
|
+
*/
|
|
242
|
+
async get(validationId, options = {}) {
|
|
243
|
+
const result = await this.read(validationId, options.ifNoneMatch);
|
|
244
|
+
if (result.notModified)
|
|
245
|
+
throw notModified('La validación', result.response);
|
|
246
|
+
return result.validation;
|
|
247
|
+
}
|
|
248
|
+
async read(validationId, ifNoneMatch) {
|
|
249
|
+
const response = await this.transport.request({
|
|
250
|
+
method: 'GET',
|
|
251
|
+
path: `/validations/${(0, internal_js_1.pathSegment)(validationId)}`,
|
|
252
|
+
headers: { 'if-none-match': ifNoneMatch },
|
|
253
|
+
});
|
|
254
|
+
if (response.status === 304)
|
|
255
|
+
return { notModified: true, response };
|
|
256
|
+
const validation = (0, internal_js_1.readData)(response, 'de la validación');
|
|
257
|
+
const etag = response.headers['etag'];
|
|
258
|
+
if (etag)
|
|
259
|
+
validation.etag = etag;
|
|
260
|
+
return { notModified: false, validation };
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Lista las validaciones de la cuenta, con filtros y paginación.
|
|
264
|
+
*
|
|
265
|
+
* `GET /v1/validations`. Un listado trae menos campos que `get()`.
|
|
266
|
+
*/
|
|
267
|
+
async list(params = {}) {
|
|
268
|
+
const response = await this.transport.request({
|
|
269
|
+
method: 'GET',
|
|
270
|
+
path: '/validations',
|
|
271
|
+
query: { page: params.page, per_page: params.perPage, ...filtersToQuery(params) },
|
|
272
|
+
});
|
|
273
|
+
return (0, pagination_js_1.parsePage)(response);
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Recorre todas las validaciones que casan con los filtros.
|
|
277
|
+
*
|
|
278
|
+
* Pide cada página cuando la anterior se agota. `maxPages` acota el recorrido,
|
|
279
|
+
* y `page` fija la página de partida.
|
|
280
|
+
*
|
|
281
|
+
* ```ts
|
|
282
|
+
* for await (const validation of client.validations.iter({ status: 'valid' })) {
|
|
283
|
+
* console.log(validation.id);
|
|
284
|
+
* }
|
|
285
|
+
* ```
|
|
286
|
+
*/
|
|
287
|
+
iter(params = {}) {
|
|
288
|
+
const { maxPages, page, ...filters } = params;
|
|
289
|
+
return (0, pagination_js_1.iteratePages)((number) => this.list({ ...filters, page: number }), {
|
|
290
|
+
startPage: page ?? 1,
|
|
291
|
+
maxPages,
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Los totales de las validaciones de la cuenta, con los mismos filtros.
|
|
296
|
+
*
|
|
297
|
+
* `GET /v1/validations/stats`.
|
|
298
|
+
*/
|
|
299
|
+
async stats(filters = {}) {
|
|
300
|
+
const response = await this.transport.request({
|
|
301
|
+
method: 'GET',
|
|
302
|
+
path: '/validations/stats',
|
|
303
|
+
query: filtersToQuery(filters),
|
|
304
|
+
});
|
|
305
|
+
return (0, internal_js_1.readData)(response, 'de las estadísticas');
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Los intentos del ciclo de reintentos de una validación.
|
|
309
|
+
*
|
|
310
|
+
* `GET /v1/validations/{id}/retry-attempts`.
|
|
311
|
+
*/
|
|
312
|
+
async retryAttempts(validationId) {
|
|
313
|
+
const response = await this.transport.request({
|
|
314
|
+
method: 'GET',
|
|
315
|
+
path: `/validations/${(0, internal_js_1.pathSegment)(validationId)}/retry-attempts`,
|
|
316
|
+
});
|
|
317
|
+
return (0, internal_js_1.readList)(response, 'de los intentos');
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Sondea una validación hasta que su veredicto queda firme.
|
|
321
|
+
*
|
|
322
|
+
* Es la pareja de `enqueue()`. Manda `If-None-Match` con el `ETag` de la
|
|
323
|
+
* respuesta anterior, así que un sondeo que no encuentra cambios no descarga
|
|
324
|
+
* otra vez el mismo cuerpo.
|
|
325
|
+
*
|
|
326
|
+
* Espera a `isSettled()`, no a `isTerminal()`: una validación con reintentos
|
|
327
|
+
* en marcha llega a `not_found` y sigue cambiando después.
|
|
328
|
+
*
|
|
329
|
+
* @throws {TimeoutError} Si se agota `timeoutMs` sin veredicto firme.
|
|
330
|
+
*/
|
|
331
|
+
async waitFor(validationId, options = {}) {
|
|
332
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_WAIT_TIMEOUT_MS;
|
|
333
|
+
const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
334
|
+
const sleep = options.sleep ?? http_js_1.defaultSleep;
|
|
335
|
+
const deadline = node_perf_hooks_1.performance.now() + timeoutMs;
|
|
336
|
+
let etag;
|
|
337
|
+
let latest;
|
|
338
|
+
for (;;) {
|
|
339
|
+
const result = await this.read(validationId, etag);
|
|
340
|
+
if (!result.notModified) {
|
|
341
|
+
latest = result.validation;
|
|
342
|
+
etag = result.validation.etag;
|
|
343
|
+
}
|
|
344
|
+
if (latest && (0, types_js_1.isSettled)(latest))
|
|
345
|
+
return latest;
|
|
346
|
+
if (node_perf_hooks_1.performance.now() >= deadline) {
|
|
347
|
+
throw new errors_js_1.TimeoutError(`La validación ${validationId} no alcanzó un veredicto firme en ${String(timeoutMs)} ms`, validationId, timeoutMs);
|
|
348
|
+
}
|
|
349
|
+
await sleep(pollIntervalMs);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
// Archivos
|
|
353
|
+
/**
|
|
354
|
+
* Descarga el CEP oficial de una validación en XML o en PDF.
|
|
355
|
+
*
|
|
356
|
+
* `GET /v1/validations/{id}/cep`. Devuelve el archivo: el XML que emitió
|
|
357
|
+
* Banxico, con su sello digital y su cadena original, o el PDF equivalente.
|
|
358
|
+
*
|
|
359
|
+
* Existe cuando la validación tiene comprobante (`hasCep()`). Cuando no, la
|
|
360
|
+
* API responde `404` con `cep_not_available` y el SDK lo lanza como
|
|
361
|
+
* `NotFoundError`.
|
|
362
|
+
*/
|
|
363
|
+
async cep(validationId, options = {}) {
|
|
364
|
+
const format = options.format ?? 'xml';
|
|
365
|
+
if (!types_js_1.CEP_FORMATS.includes(format)) {
|
|
366
|
+
throw new errors_js_1.ConfigurationError(`El formato del CEP es 'xml' o 'pdf'; llegó ${JSON.stringify(format)}`);
|
|
367
|
+
}
|
|
368
|
+
const accept = format === 'xml' ? 'application/xml' : 'application/pdf';
|
|
369
|
+
const response = await this.transport.request({
|
|
370
|
+
method: 'GET',
|
|
371
|
+
path: `/validations/${(0, internal_js_1.pathSegment)(validationId)}/cep`,
|
|
372
|
+
query: { format },
|
|
373
|
+
accept: `${accept}, application/json`,
|
|
374
|
+
});
|
|
375
|
+
return {
|
|
376
|
+
validationId,
|
|
377
|
+
format,
|
|
378
|
+
...toFile(response, `CEP-${validationId}.${format}`, response.headers['content-type'] ?? accept),
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Descarga la imagen del comprobante de una validación por OCR.
|
|
383
|
+
*
|
|
384
|
+
* `GET /v1/validations/{id}/image`.
|
|
385
|
+
*/
|
|
386
|
+
async image(validationId) {
|
|
387
|
+
const response = await this.transport.request({
|
|
388
|
+
method: 'GET',
|
|
389
|
+
path: `/validations/${(0, internal_js_1.pathSegment)(validationId)}/image`,
|
|
390
|
+
accept: IMAGE_ACCEPT,
|
|
391
|
+
});
|
|
392
|
+
const contentType = response.headers['content-type'] ?? 'image/png';
|
|
393
|
+
const extension = IMAGE_EXTENSIONS[contentType.split(';')[0]?.trim() ?? ''] ?? '';
|
|
394
|
+
return toFile(response, `comprobante-${validationId}${extension}`, contentType);
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Exporta el historial de validaciones en CSV o en XLSX.
|
|
398
|
+
*
|
|
399
|
+
* `GET /v1/validations/export`. Admite los mismos filtros que `list()`.
|
|
400
|
+
*/
|
|
401
|
+
export(params = {}) {
|
|
402
|
+
return exportFile(this.transport, '/validations/export', params.format ?? 'csv', { ...filtersToQuery(params), limit: params.limit }, 'validaciones');
|
|
403
|
+
}
|
|
404
|
+
// Cambiar
|
|
405
|
+
/**
|
|
406
|
+
* Cambia la política de reintentos de una validación concreta.
|
|
407
|
+
*
|
|
408
|
+
* `PUT /v1/validations/{id}/retry-policy`. Devuelve el estado del ciclo de
|
|
409
|
+
* reintentos, no la validación completa.
|
|
410
|
+
*/
|
|
411
|
+
async setRetryPolicy(validationId, policy, options = {}) {
|
|
412
|
+
const response = await this.transport.request({
|
|
413
|
+
method: 'PUT',
|
|
414
|
+
path: `/validations/${(0, internal_js_1.pathSegment)(validationId)}/retry-policy`,
|
|
415
|
+
body: { retry_policy: policy },
|
|
416
|
+
headers: idempotencyHeader(options.idempotencyKey),
|
|
417
|
+
});
|
|
418
|
+
return (0, internal_js_1.readData)(response, 'del estado de reintentos');
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Detiene el ciclo de reintentos pendientes de una validación.
|
|
422
|
+
*
|
|
423
|
+
* `POST /v1/validations/{id}/cancel-retries`. Si el ciclo ya no está activo,
|
|
424
|
+
* la API responde `422` con `retry_not_active`.
|
|
425
|
+
*/
|
|
426
|
+
async cancelRetries(validationId, options = {}) {
|
|
427
|
+
const response = await this.transport.request({
|
|
428
|
+
method: 'POST',
|
|
429
|
+
path: `/validations/${(0, internal_js_1.pathSegment)(validationId)}/cancel-retries`,
|
|
430
|
+
headers: idempotencyHeader(options.idempotencyKey),
|
|
431
|
+
});
|
|
432
|
+
return (0, internal_js_1.readData)(response, 'del estado de reintentos');
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Retira una validación del historial.
|
|
436
|
+
*
|
|
437
|
+
* `DELETE /v1/validations/{id}`. La API responde `204` y no devuelve cuerpo.
|
|
438
|
+
*/
|
|
439
|
+
async delete(validationId) {
|
|
440
|
+
await this.transport.request({
|
|
441
|
+
method: 'DELETE',
|
|
442
|
+
path: `/validations/${(0, internal_js_1.pathSegment)(validationId)}`,
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Envía el comprobante al chat de Telegram vinculado a la cuenta.
|
|
447
|
+
*
|
|
448
|
+
* `POST /v1/validations/{id}/cep/send-telegram`. La API acusa con `202`: el
|
|
449
|
+
* envío ocurre después.
|
|
450
|
+
*/
|
|
451
|
+
async sendCepToTelegram(validationId) {
|
|
452
|
+
const response = await this.transport.request({
|
|
453
|
+
method: 'POST',
|
|
454
|
+
path: `/validations/${(0, internal_js_1.pathSegment)(validationId)}/cep/send-telegram`,
|
|
455
|
+
});
|
|
456
|
+
return (0, internal_js_1.readData)(response, 'del envío');
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
exports.Validations = Validations;
|
|
460
|
+
// ── Webhooks ────────────────────────────────────────────────────────────────
|
|
461
|
+
/**
|
|
462
|
+
* Los eventos de un endpoint, con su comprobación previa.
|
|
463
|
+
*
|
|
464
|
+
* Recibe el tipo ancho porque quien llama desde JavaScript puede omitirlos, y el
|
|
465
|
+
* tipo de `create()` los declara obligatorios.
|
|
466
|
+
*/
|
|
467
|
+
function subscribedEvents(events) {
|
|
468
|
+
if (!events?.length) {
|
|
469
|
+
throw new errors_js_1.InvalidRequestError('Un endpoint se suscribe al menos a un evento', {
|
|
470
|
+
status: 422,
|
|
471
|
+
code: 'events_required',
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
return [...events];
|
|
475
|
+
}
|
|
476
|
+
/** Endpoints de webhook y su historial de entregas. */
|
|
477
|
+
class Webhooks {
|
|
478
|
+
transport;
|
|
479
|
+
constructor(transport) {
|
|
480
|
+
this.transport = transport;
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Registra un endpoint y devuelve su secreto de firma.
|
|
484
|
+
*
|
|
485
|
+
* `POST /v1/webhooks`. El secreto viaja **una sola vez**, en
|
|
486
|
+
* `attributes.secret` de esta respuesta: guárdalo al recibirlo. Si se pierde,
|
|
487
|
+
* se rota con `regenerateSecret()`.
|
|
488
|
+
*/
|
|
489
|
+
async create(params) {
|
|
490
|
+
const body = {
|
|
491
|
+
url: params.url,
|
|
492
|
+
events: subscribedEvents(params.events),
|
|
493
|
+
};
|
|
494
|
+
if (params.description !== undefined)
|
|
495
|
+
body.description = params.description;
|
|
496
|
+
const response = await this.transport.request({ method: 'POST', path: '/webhooks', body });
|
|
497
|
+
return (0, internal_js_1.readData)(response, 'del endpoint');
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Lista los endpoints registrados, sin sus secretos.
|
|
501
|
+
*
|
|
502
|
+
* `GET /v1/webhooks`.
|
|
503
|
+
*/
|
|
504
|
+
async list() {
|
|
505
|
+
const response = await this.transport.request({ method: 'GET', path: '/webhooks' });
|
|
506
|
+
return (0, internal_js_1.readList)(response, 'de los endpoints');
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Cambia la URL, los eventos suscritos, la etiqueta o el estado de un endpoint.
|
|
510
|
+
*
|
|
511
|
+
* `PUT /v1/webhooks/{id}`. Un endpoint que el sistema apagó se reactiva con
|
|
512
|
+
* `status: 'active'`; `auto_disabled` no se puede asignar desde la API.
|
|
513
|
+
*/
|
|
514
|
+
async update(webhookId, params) {
|
|
515
|
+
const body = {};
|
|
516
|
+
if (params.url !== undefined)
|
|
517
|
+
body.url = params.url;
|
|
518
|
+
if (params.events !== undefined)
|
|
519
|
+
body.events = subscribedEvents(params.events);
|
|
520
|
+
if (params.description !== undefined)
|
|
521
|
+
body.description = params.description;
|
|
522
|
+
if (params.status !== undefined)
|
|
523
|
+
body.status = params.status;
|
|
524
|
+
if (Object.keys(body).length === 0) {
|
|
525
|
+
throw new errors_js_1.InvalidRequestError('No hay nada que cambiar: pasa url, events, description o status', { status: 422, code: 'empty_update' });
|
|
526
|
+
}
|
|
527
|
+
const response = await this.transport.request({
|
|
528
|
+
method: 'PUT',
|
|
529
|
+
path: `/webhooks/${(0, internal_js_1.pathSegment)(webhookId)}`,
|
|
530
|
+
body,
|
|
531
|
+
});
|
|
532
|
+
return (0, internal_js_1.readData)(response, 'del endpoint');
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Retira un endpoint.
|
|
536
|
+
*
|
|
537
|
+
* `DELETE /v1/webhooks/{id}`. La API responde `204`.
|
|
538
|
+
*/
|
|
539
|
+
async delete(webhookId) {
|
|
540
|
+
await this.transport.request({
|
|
541
|
+
method: 'DELETE',
|
|
542
|
+
path: `/webhooks/${(0, internal_js_1.pathSegment)(webhookId)}`,
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Envía un evento de prueba al endpoint.
|
|
547
|
+
*
|
|
548
|
+
* `POST /v1/webhooks/{id}/test`. Las entregas de prueba no cuentan para el
|
|
549
|
+
* contador de fallos consecutivos.
|
|
550
|
+
*/
|
|
551
|
+
async test(webhookId) {
|
|
552
|
+
const response = await this.transport.request({
|
|
553
|
+
method: 'POST',
|
|
554
|
+
path: `/webhooks/${(0, internal_js_1.pathSegment)(webhookId)}/test`,
|
|
555
|
+
});
|
|
556
|
+
return (0, internal_js_1.readData)(response, 'de la prueba');
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Rota el secreto de firma de un endpoint.
|
|
560
|
+
*
|
|
561
|
+
* `POST /v1/webhooks/{id}/regenerate-secret`. El secreto nuevo viene en
|
|
562
|
+
* `attributes.secret`, y es la última vez que la API lo entrega.
|
|
563
|
+
*/
|
|
564
|
+
async regenerateSecret(webhookId) {
|
|
565
|
+
const response = await this.transport.request({
|
|
566
|
+
method: 'POST',
|
|
567
|
+
path: `/webhooks/${(0, internal_js_1.pathSegment)(webhookId)}/regenerate-secret`,
|
|
568
|
+
});
|
|
569
|
+
return (0, internal_js_1.readData)(response, 'del endpoint');
|
|
570
|
+
}
|
|
571
|
+
async deliveries(first, second) {
|
|
572
|
+
const webhookId = typeof first === 'string' ? first : undefined;
|
|
573
|
+
const params = (typeof first === 'string' ? second : first) ?? {};
|
|
574
|
+
const filtered = params.status !== undefined || params.eventType !== undefined;
|
|
575
|
+
const paging = { page: params.page, per_page: params.perPage };
|
|
576
|
+
const response = webhookId !== undefined && !filtered
|
|
577
|
+
? await this.transport.request({
|
|
578
|
+
method: 'GET',
|
|
579
|
+
path: `/webhooks/${(0, internal_js_1.pathSegment)(webhookId)}/deliveries`,
|
|
580
|
+
query: paging,
|
|
581
|
+
})
|
|
582
|
+
: await this.transport.request({
|
|
583
|
+
method: 'GET',
|
|
584
|
+
path: '/webhooks/deliveries',
|
|
585
|
+
query: {
|
|
586
|
+
...paging,
|
|
587
|
+
endpoint_id: webhookId === undefined ? undefined : (0, internal_js_1.checkedId)(webhookId),
|
|
588
|
+
status: params.status,
|
|
589
|
+
event_type: params.eventType,
|
|
590
|
+
},
|
|
591
|
+
});
|
|
592
|
+
return (0, pagination_js_1.parsePage)(response);
|
|
593
|
+
}
|
|
594
|
+
iterDeliveries(first, second) {
|
|
595
|
+
const webhookId = typeof first === 'string' ? first : undefined;
|
|
596
|
+
const options = (typeof first === 'string' ? second : first) ?? {};
|
|
597
|
+
const { maxPages, page, ...filters } = options;
|
|
598
|
+
return (0, pagination_js_1.iteratePages)((number) => webhookId === undefined
|
|
599
|
+
? this.deliveries({ ...filters, page: number })
|
|
600
|
+
: this.deliveries(webhookId, { ...filters, page: number }), { startPage: page ?? 1, maxPages });
|
|
601
|
+
}
|
|
602
|
+
exportDeliveries(first, second) {
|
|
603
|
+
const webhookId = typeof first === 'string' ? first : undefined;
|
|
604
|
+
const params = (typeof first === 'string' ? second : first) ?? {};
|
|
605
|
+
const filtered = params.status !== undefined || params.eventType !== undefined;
|
|
606
|
+
const format = params.format ?? 'csv';
|
|
607
|
+
if (webhookId !== undefined && !filtered) {
|
|
608
|
+
return exportFile(this.transport, `/webhooks/${(0, internal_js_1.pathSegment)(webhookId)}/deliveries/export`, format, { limit: params.limit }, `entregas-${webhookId}`);
|
|
609
|
+
}
|
|
610
|
+
return exportFile(this.transport, '/webhooks/deliveries/export', format, {
|
|
611
|
+
limit: params.limit,
|
|
612
|
+
endpoint_id: webhookId === undefined ? undefined : (0, internal_js_1.checkedId)(webhookId),
|
|
613
|
+
status: params.status,
|
|
614
|
+
event_type: params.eventType,
|
|
615
|
+
}, webhookId === undefined ? 'entregas' : `entregas-${webhookId}`);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
exports.Webhooks = Webhooks;
|
|
619
|
+
// ── Catálogo ────────────────────────────────────────────────────────────────
|
|
620
|
+
/** Datos abiertos: el catálogo de bancos y el estado del servicio. */
|
|
621
|
+
class Catalog {
|
|
622
|
+
transport;
|
|
623
|
+
constructor(transport) {
|
|
624
|
+
this.transport = transport;
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Lista las instituciones participantes en SPEI con su código.
|
|
628
|
+
*
|
|
629
|
+
* `GET /v1/public/banks`. Con `ifNoneMatch` la API responde `304` cuando el
|
|
630
|
+
* catálogo no cambió, y el SDK lo lanza como `ApiError` con `status` 304. El
|
|
631
|
+
* `ETag` de cada lectura queda en `etag`.
|
|
632
|
+
*/
|
|
633
|
+
async banks(options = {}) {
|
|
634
|
+
const response = await this.transport.request({
|
|
635
|
+
method: 'GET',
|
|
636
|
+
path: '/public/banks',
|
|
637
|
+
headers: { 'if-none-match': options.ifNoneMatch },
|
|
638
|
+
});
|
|
639
|
+
if (response.status === 304)
|
|
640
|
+
throw notModified('El catálogo', response);
|
|
641
|
+
const banks = (0, internal_js_1.readList)(response, 'de los bancos');
|
|
642
|
+
const etag = response.headers['etag'];
|
|
643
|
+
if (etag)
|
|
644
|
+
banks.etag = etag;
|
|
645
|
+
return banks;
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Resuelve el banco emisor de una tarjeta por sus primeros dígitos.
|
|
649
|
+
*
|
|
650
|
+
* `GET /v1/public/bin-lookup/{bin}`.
|
|
651
|
+
*/
|
|
652
|
+
async binLookup(bin) {
|
|
653
|
+
const response = await this.transport.request({
|
|
654
|
+
method: 'GET',
|
|
655
|
+
path: `/public/bin-lookup/${(0, internal_js_1.pathSegment)(bin)}`,
|
|
656
|
+
});
|
|
657
|
+
return (0, internal_js_1.readData)(response, 'del BIN');
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* Estado del servicio de consulta de Banxico.
|
|
661
|
+
*
|
|
662
|
+
* `GET /v1/status/banxico`. Sirve para distinguir un `cep_unavailable` propio de
|
|
663
|
+
* la transferencia de una caída del servicio.
|
|
664
|
+
*/
|
|
665
|
+
async banxicoStatus() {
|
|
666
|
+
const response = await this.transport.request({ method: 'GET', path: '/status/banxico' });
|
|
667
|
+
return (0, internal_js_1.readData)(response, 'del estado');
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* Serie temporal de la salud del servicio de Banxico.
|
|
671
|
+
*
|
|
672
|
+
* `GET /v1/status/banxico/timeseries`.
|
|
673
|
+
*/
|
|
674
|
+
async banxicoTimeseries(params = {}) {
|
|
675
|
+
const response = await this.transport.request({
|
|
676
|
+
method: 'GET',
|
|
677
|
+
path: '/status/banxico/timeseries',
|
|
678
|
+
query: { metric: params.metric, window: params.window },
|
|
679
|
+
});
|
|
680
|
+
return (0, internal_js_1.readData)(response, 'de la serie');
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
exports.Catalog = Catalog;
|
|
684
|
+
// ── Beneficiarios ───────────────────────────────────────────────────────────
|
|
685
|
+
const IMPORT_CONTENT_TYPES = {
|
|
686
|
+
'.csv': 'text/csv',
|
|
687
|
+
'.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
688
|
+
'.xls': 'application/vnd.ms-excel',
|
|
689
|
+
'.txt': 'text/plain',
|
|
690
|
+
'.pdf': 'application/pdf',
|
|
691
|
+
};
|
|
692
|
+
const TEMPLATE_CONTENT_TYPES = {
|
|
693
|
+
csv: 'text/csv',
|
|
694
|
+
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
695
|
+
xls: 'application/vnd.ms-excel',
|
|
696
|
+
txt: 'text/plain',
|
|
697
|
+
json: 'application/json',
|
|
698
|
+
};
|
|
699
|
+
const DEFAULT_IMPORT_POLL_INTERVAL_MS = 2_000;
|
|
700
|
+
/** El contenido de un archivo de importación, su nombre y su tipo. */
|
|
701
|
+
async function importFile(file, filename) {
|
|
702
|
+
let content;
|
|
703
|
+
let name;
|
|
704
|
+
if (typeof file === 'string') {
|
|
705
|
+
try {
|
|
706
|
+
content = await (0, promises_1.readFile)(file);
|
|
707
|
+
}
|
|
708
|
+
catch {
|
|
709
|
+
throw new errors_js_1.ConfigurationError(`No existe el archivo: ${file}`);
|
|
710
|
+
}
|
|
711
|
+
name = filename ?? (0, node_path_1.basename)(file);
|
|
712
|
+
}
|
|
713
|
+
else {
|
|
714
|
+
content = file;
|
|
715
|
+
name = filename ?? 'beneficiarios.csv';
|
|
716
|
+
}
|
|
717
|
+
return {
|
|
718
|
+
content,
|
|
719
|
+
filename: name,
|
|
720
|
+
contentType: IMPORT_CONTENT_TYPES[(0, node_path_1.extname)(name).toLowerCase()] ?? 'application/octet-stream',
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
function noValidFields(what) {
|
|
724
|
+
return new errors_js_1.InvalidRequestError(`No hay nada que cambiar: pasa ${what}`, {
|
|
725
|
+
status: 422,
|
|
726
|
+
code: 'no_valid_fields',
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
/** Cuentas beneficiarias guardadas y su importación masiva. */
|
|
730
|
+
class Beneficiaries {
|
|
731
|
+
transport;
|
|
732
|
+
constructor(transport) {
|
|
733
|
+
this.transport = transport;
|
|
734
|
+
}
|
|
735
|
+
// La lista blanca
|
|
736
|
+
/**
|
|
737
|
+
* Registra una cuenta beneficiaria.
|
|
738
|
+
*
|
|
739
|
+
* `POST /v1/beneficiaries`. El tipo se detecta por longitud: CLABE (18 dígitos),
|
|
740
|
+
* tarjeta (16) o celular DiMo (10). Para un celular, `bankCode` es obligatorio;
|
|
741
|
+
* en CLABE y tarjeta se deriva del número. Un alta de una cuenta archivada la
|
|
742
|
+
* reactiva.
|
|
743
|
+
*/
|
|
744
|
+
async create(params) {
|
|
745
|
+
// Quien llama desde JavaScript puede omitirlo; el tipo lo declara obligatorio.
|
|
746
|
+
const accountNumber = params.accountNumber;
|
|
747
|
+
if (!accountNumber) {
|
|
748
|
+
throw new errors_js_1.InvalidRequestError('Hace falta accountNumber para registrar el beneficiario', {
|
|
749
|
+
status: 422,
|
|
750
|
+
code: 'account_number_required',
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
const body = { account_number: accountNumber };
|
|
754
|
+
if (params.bankCode !== undefined)
|
|
755
|
+
body.bank_code = params.bankCode;
|
|
756
|
+
if (params.label !== undefined)
|
|
757
|
+
body.label = params.label;
|
|
758
|
+
const response = await this.transport.request({ method: 'POST', path: '/beneficiaries', body });
|
|
759
|
+
return (0, internal_js_1.readData)(response, 'del beneficiario');
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
762
|
+
* Lista las cuentas beneficiarias guardadas.
|
|
763
|
+
*
|
|
764
|
+
* `GET /v1/beneficiaries`. Sin paginar: devuelve la lista completa.
|
|
765
|
+
* `withArchived: true` trae sólo las archivadas y `false` sólo las activas.
|
|
766
|
+
*/
|
|
767
|
+
async list(params = {}) {
|
|
768
|
+
const response = await this.transport.request({
|
|
769
|
+
method: 'GET',
|
|
770
|
+
path: '/beneficiaries',
|
|
771
|
+
query: { with_archived: flag(params.withArchived) },
|
|
772
|
+
});
|
|
773
|
+
return (0, internal_js_1.readList)(response, 'de los beneficiarios');
|
|
774
|
+
}
|
|
775
|
+
/**
|
|
776
|
+
* Cambia la etiqueta, la cuenta o el banco de un beneficiario.
|
|
777
|
+
*
|
|
778
|
+
* `PUT /v1/beneficiaries/{id}`. Un `accountNumber` nuevo vuelve a derivar el
|
|
779
|
+
* tipo y el banco; `bankCode` sólo se aplica sobre cuentas de tipo celular.
|
|
780
|
+
*/
|
|
781
|
+
async update(beneficiaryId, params) {
|
|
782
|
+
const body = {};
|
|
783
|
+
if (params.label !== undefined)
|
|
784
|
+
body.label = params.label;
|
|
785
|
+
if (params.accountNumber !== undefined)
|
|
786
|
+
body.account_number = params.accountNumber;
|
|
787
|
+
if (params.bankCode !== undefined)
|
|
788
|
+
body.bank_code = params.bankCode;
|
|
789
|
+
if (Object.keys(body).length === 0)
|
|
790
|
+
throw noValidFields('label, accountNumber o bankCode');
|
|
791
|
+
const response = await this.transport.request({
|
|
792
|
+
method: 'PUT',
|
|
793
|
+
path: `/beneficiaries/${(0, internal_js_1.pathSegment)(String(beneficiaryId))}`,
|
|
794
|
+
body,
|
|
795
|
+
});
|
|
796
|
+
return (0, internal_js_1.readData)(response, 'del beneficiario');
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* Archiva un beneficiario.
|
|
800
|
+
*
|
|
801
|
+
* `DELETE /v1/beneficiaries/{id}`. El registro no se borra: sale de la lista
|
|
802
|
+
* activa y se consulta con `withArchived: true`. Un alta posterior con la misma
|
|
803
|
+
* cuenta lo reactiva. La API responde `204`.
|
|
804
|
+
*/
|
|
805
|
+
async delete(beneficiaryId) {
|
|
806
|
+
await this.transport.request({
|
|
807
|
+
method: 'DELETE',
|
|
808
|
+
path: `/beneficiaries/${(0, internal_js_1.pathSegment)(String(beneficiaryId))}`,
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
/**
|
|
812
|
+
* Resuelve una cuenta concreta dentro de la lista propia.
|
|
813
|
+
*
|
|
814
|
+
* `GET /v1/beneficiaries/lookup`. Devuelve los datos del banco cuando la cuenta
|
|
815
|
+
* está entre las guardadas; si no, la API responde `404`.
|
|
816
|
+
*/
|
|
817
|
+
async lookup(account) {
|
|
818
|
+
const response = await this.transport.request({
|
|
819
|
+
method: 'GET',
|
|
820
|
+
path: '/beneficiaries/lookup',
|
|
821
|
+
query: { account },
|
|
822
|
+
});
|
|
823
|
+
return (0, internal_js_1.readData)(response, 'de la cuenta');
|
|
824
|
+
}
|
|
825
|
+
/**
|
|
826
|
+
* Exporta la lista de beneficiarios en CSV o en XLSX.
|
|
827
|
+
*
|
|
828
|
+
* `GET /v1/beneficiaries/export`. Los números salen enmascarados salvo la CLABE;
|
|
829
|
+
* `limit` sólo baja el tope de 100 000 filas.
|
|
830
|
+
*/
|
|
831
|
+
export(params = {}) {
|
|
832
|
+
return exportFile(this.transport, '/beneficiaries/export', params.format ?? 'csv', { with_archived: flag(params.withArchived), limit: params.limit }, 'beneficiarios', 'application/octet-stream');
|
|
833
|
+
}
|
|
834
|
+
// Importación masiva
|
|
835
|
+
/**
|
|
836
|
+
* Descarga la plantilla para la importación masiva.
|
|
837
|
+
*
|
|
838
|
+
* `GET /v1/beneficiaries/imports/template`. Es el punto de partida del ciclo:
|
|
839
|
+
* descargar, rellenar, subir, revisar y confirmar.
|
|
840
|
+
*/
|
|
841
|
+
async importTemplate(options = {}) {
|
|
842
|
+
const format = options.format ?? 'csv';
|
|
843
|
+
if (!types_js_1.IMPORT_TEMPLATE_FORMATS.includes(format)) {
|
|
844
|
+
throw new errors_js_1.ConfigurationError(`El formato de plantilla es 'csv', 'xlsx', 'xls', 'txt' o 'json'; llegó ${JSON.stringify(format)}`);
|
|
845
|
+
}
|
|
846
|
+
const accept = TEMPLATE_CONTENT_TYPES[format];
|
|
847
|
+
const response = await this.transport.request({
|
|
848
|
+
method: 'GET',
|
|
849
|
+
path: '/beneficiaries/imports/template',
|
|
850
|
+
query: { format },
|
|
851
|
+
accept: `${accept}, application/octet-stream, application/json`,
|
|
852
|
+
});
|
|
853
|
+
return toFile(response, `beneficiarios-plantilla.${format}`, response.headers['content-type'] ?? accept);
|
|
854
|
+
}
|
|
855
|
+
/**
|
|
856
|
+
* Sube un archivo y abre un trabajo de importación.
|
|
857
|
+
*
|
|
858
|
+
* `POST /v1/beneficiaries/imports`. `file` son los bytes del archivo o su ruta,
|
|
859
|
+
* que el SDK lee del disco. `parseMode` es `template` (encabezados canónicos) o
|
|
860
|
+
* `free` (formato libre). La respuesta es un `202` con el trabajo en estado
|
|
861
|
+
* `pending`; nada se persiste hasta `importCommit()`.
|
|
862
|
+
*/
|
|
863
|
+
async importStart(file, options = {}) {
|
|
864
|
+
const upload = await importFile(file, options.filename);
|
|
865
|
+
const form = new FormData();
|
|
866
|
+
form.append('parse_mode', options.parseMode ?? 'template');
|
|
867
|
+
form.append('file', new Blob([upload.content], { type: upload.contentType }), upload.filename);
|
|
868
|
+
const response = await this.transport.request({
|
|
869
|
+
method: 'POST',
|
|
870
|
+
path: '/beneficiaries/imports',
|
|
871
|
+
body: form,
|
|
872
|
+
});
|
|
873
|
+
return (0, internal_js_1.readData)(response, 'de la importación');
|
|
874
|
+
}
|
|
875
|
+
/**
|
|
876
|
+
* Lee el estado de un trabajo de importación y sus contadores.
|
|
877
|
+
*
|
|
878
|
+
* `GET /v1/beneficiaries/imports/{id}`.
|
|
879
|
+
*/
|
|
880
|
+
async importStatus(importId) {
|
|
881
|
+
const response = await this.transport.request({
|
|
882
|
+
method: 'GET',
|
|
883
|
+
path: `/beneficiaries/imports/${(0, internal_js_1.pathSegment)(String(importId))}`,
|
|
884
|
+
});
|
|
885
|
+
return (0, internal_js_1.readData)(response, 'de la importación');
|
|
886
|
+
}
|
|
887
|
+
/**
|
|
888
|
+
* Cancela una importación que todavía no se confirmó.
|
|
889
|
+
*
|
|
890
|
+
* `DELETE /v1/beneficiaries/imports/{id}`. Admite los estados `pending`,
|
|
891
|
+
* `parsing` y `preview_ready`; con uno terminal o en `committing`, la API
|
|
892
|
+
* responde `404`. Las cuentas que una confirmación ya persistió se archivan una
|
|
893
|
+
* a una con `delete()`. La API responde `204`.
|
|
894
|
+
*/
|
|
895
|
+
async importCancel(importId) {
|
|
896
|
+
await this.transport.request({
|
|
897
|
+
method: 'DELETE',
|
|
898
|
+
path: `/beneficiaries/imports/${(0, internal_js_1.pathSegment)(String(importId))}`,
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
902
|
+
* Lista las filas extraídas de la importación, paginadas.
|
|
903
|
+
*
|
|
904
|
+
* `GET /v1/beneficiaries/imports/{id}/preview`. Disponible en `preview_ready` o
|
|
905
|
+
* después. `buckets` filtra por grupo (`valid`, `correctable`, `fatal`,
|
|
906
|
+
* `duplicate_account`, `duplicate_alias`).
|
|
907
|
+
*/
|
|
908
|
+
async importPreview(importId, params = {}) {
|
|
909
|
+
const response = await this.transport.request({
|
|
910
|
+
method: 'GET',
|
|
911
|
+
path: `/beneficiaries/imports/${(0, internal_js_1.pathSegment)(String(importId))}/preview`,
|
|
912
|
+
query: { page: params.page, per_page: params.perPage, buckets: params.buckets },
|
|
913
|
+
});
|
|
914
|
+
return (0, pagination_js_1.parsePage)(response);
|
|
915
|
+
}
|
|
916
|
+
/**
|
|
917
|
+
* Recorre todas las filas de la vista previa, página a página.
|
|
918
|
+
*
|
|
919
|
+
* Acepta los mismos argumentos que `importPreview()`, más `maxPages`.
|
|
920
|
+
*/
|
|
921
|
+
iterImportPreview(importId, params = {}) {
|
|
922
|
+
const { maxPages, page, ...filters } = params;
|
|
923
|
+
return (0, pagination_js_1.iteratePages)((number) => this.importPreview(importId, { ...filters, page: number }), {
|
|
924
|
+
startPage: page ?? 1,
|
|
925
|
+
maxPages,
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
/**
|
|
929
|
+
* Corrige una fila de la vista previa antes de confirmar.
|
|
930
|
+
*
|
|
931
|
+
* `PATCH /v1/beneficiaries/imports/{id}/rows/{row_id}`. Sólo los campos
|
|
932
|
+
* presentes se sobrescriben; la fila se reprocesa y su grupo puede cambiar con
|
|
933
|
+
* la corrección.
|
|
934
|
+
*/
|
|
935
|
+
async importEditRow(importId, rowId, params) {
|
|
936
|
+
const body = {};
|
|
937
|
+
if (params.parsedAccount !== undefined)
|
|
938
|
+
body.parsed_account = params.parsedAccount;
|
|
939
|
+
if (params.parsedLabel !== undefined)
|
|
940
|
+
body.parsed_label = params.parsedLabel;
|
|
941
|
+
if (params.parsedAccountType !== undefined) {
|
|
942
|
+
body.parsed_account_type = params.parsedAccountType;
|
|
943
|
+
}
|
|
944
|
+
if (params.parsedBankCode !== undefined)
|
|
945
|
+
body.parsed_bank_code = params.parsedBankCode;
|
|
946
|
+
if (params.parsedBankName !== undefined)
|
|
947
|
+
body.parsed_bank_name = params.parsedBankName;
|
|
948
|
+
if (Object.keys(body).length === 0)
|
|
949
|
+
throw noValidFields('alguno de los campos parsed*');
|
|
950
|
+
const response = await this.transport.request({
|
|
951
|
+
method: 'PATCH',
|
|
952
|
+
path: `/beneficiaries/imports/${(0, internal_js_1.pathSegment)(String(importId))}/rows/${(0, internal_js_1.pathSegment)(String(rowId))}`,
|
|
953
|
+
body,
|
|
954
|
+
});
|
|
955
|
+
return (0, internal_js_1.readData)(response, 'de la fila');
|
|
956
|
+
}
|
|
957
|
+
/**
|
|
958
|
+
* Quita una fila de la vista previa.
|
|
959
|
+
*
|
|
960
|
+
* `DELETE /v1/beneficiaries/imports/{id}/rows/{row_id}`. La API responde `204`.
|
|
961
|
+
*/
|
|
962
|
+
async importRemoveRow(importId, rowId) {
|
|
963
|
+
await this.transport.request({
|
|
964
|
+
method: 'DELETE',
|
|
965
|
+
path: `/beneficiaries/imports/${(0, internal_js_1.pathSegment)(String(importId))}/rows/${(0, internal_js_1.pathSegment)(String(rowId))}`,
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
/**
|
|
969
|
+
* Confirma la importación y dispara la persistencia de sus filas.
|
|
970
|
+
*
|
|
971
|
+
* `POST /v1/beneficiaries/imports/{id}/commit`. La operación es asíncrona: la
|
|
972
|
+
* respuesta es un `202` con el trabajo en `committing`, y el resultado se sigue
|
|
973
|
+
* con `importWait()`.
|
|
974
|
+
*/
|
|
975
|
+
async importCommit(importId) {
|
|
976
|
+
const response = await this.transport.request({
|
|
977
|
+
method: 'POST',
|
|
978
|
+
path: `/beneficiaries/imports/${(0, internal_js_1.pathSegment)(String(importId))}/commit`,
|
|
979
|
+
});
|
|
980
|
+
return (0, internal_js_1.readData)(response, 'de la importación');
|
|
981
|
+
}
|
|
982
|
+
/**
|
|
983
|
+
* Sondea una importación hasta que su avance se detiene.
|
|
984
|
+
*
|
|
985
|
+
* Espera a `isImportSettled()`: `preview_ready` o un estado final. A diferencia
|
|
986
|
+
* de `validations.waitFor()`, aquí no hay `ETag` que reutilizar: el endpoint de
|
|
987
|
+
* estado no lo expone, así que cada sondeo descarga el cuerpo entero.
|
|
988
|
+
*
|
|
989
|
+
* @throws {TimeoutError} Si se agota `timeoutMs` sin llegar a un estado firme.
|
|
990
|
+
*/
|
|
991
|
+
async importWait(importId, options = {}) {
|
|
992
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_WAIT_TIMEOUT_MS;
|
|
993
|
+
const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_IMPORT_POLL_INTERVAL_MS;
|
|
994
|
+
const sleep = options.sleep ?? http_js_1.defaultSleep;
|
|
995
|
+
const deadline = node_perf_hooks_1.performance.now() + timeoutMs;
|
|
996
|
+
for (;;) {
|
|
997
|
+
const job = await this.importStatus(importId);
|
|
998
|
+
if ((0, types_js_1.isImportSettled)(job))
|
|
999
|
+
return job;
|
|
1000
|
+
if (node_perf_hooks_1.performance.now() >= deadline) {
|
|
1001
|
+
throw new errors_js_1.TimeoutError(`La importación ${String(importId)} no llegó a preview_ready ni a un estado final en ${String(timeoutMs)} ms`, String(importId), timeoutMs);
|
|
1002
|
+
}
|
|
1003
|
+
await sleep(pollIntervalMs);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
exports.Beneficiaries = Beneficiaries;
|
|
1008
|
+
// ── Consumo ─────────────────────────────────────────────────────────────────
|
|
1009
|
+
/** Consumo y límites: la cuota del plan y el registro de actividad. */
|
|
1010
|
+
class Usage {
|
|
1011
|
+
transport;
|
|
1012
|
+
constructor(transport) {
|
|
1013
|
+
this.transport = transport;
|
|
1014
|
+
}
|
|
1015
|
+
/**
|
|
1016
|
+
* Devuelve la cuota de validaciones del plan en curso.
|
|
1017
|
+
*
|
|
1018
|
+
* `GET /v1/usage/summary`. Trae el límite, lo consumido, lo restante y el nivel
|
|
1019
|
+
* de aviso en `attributes.tone`.
|
|
1020
|
+
*/
|
|
1021
|
+
async summary() {
|
|
1022
|
+
const response = await this.transport.request({ method: 'GET', path: '/usage/summary' });
|
|
1023
|
+
return (0, internal_js_1.readData)(response, 'del consumo');
|
|
1024
|
+
}
|
|
1025
|
+
/**
|
|
1026
|
+
* Devuelve el consumo mensual de los últimos `months` meses.
|
|
1027
|
+
*
|
|
1028
|
+
* `GET /v1/usage/history`. De más reciente a más antiguo. El límite que
|
|
1029
|
+
* acompaña a cada fila es el de hoy, no el que regía aquel mes.
|
|
1030
|
+
*/
|
|
1031
|
+
async history(params = {}) {
|
|
1032
|
+
const response = await this.transport.request({
|
|
1033
|
+
method: 'GET',
|
|
1034
|
+
path: '/usage/history',
|
|
1035
|
+
query: { months: params.months },
|
|
1036
|
+
});
|
|
1037
|
+
return (0, internal_js_1.readData)(response, 'del historial');
|
|
1038
|
+
}
|
|
1039
|
+
/**
|
|
1040
|
+
* Desglosa el consumo por tipo de operación contabilizada.
|
|
1041
|
+
*
|
|
1042
|
+
* `GET /v1/usage/breakdown`. `period` es `current` para el mes en curso o una
|
|
1043
|
+
* cadena `YYYY-MM`.
|
|
1044
|
+
*/
|
|
1045
|
+
async breakdown(params = {}) {
|
|
1046
|
+
const response = await this.transport.request({
|
|
1047
|
+
method: 'GET',
|
|
1048
|
+
path: '/usage/breakdown',
|
|
1049
|
+
query: { period: params.period },
|
|
1050
|
+
});
|
|
1051
|
+
return (0, internal_js_1.readData)(response, 'del desglose');
|
|
1052
|
+
}
|
|
1053
|
+
/**
|
|
1054
|
+
* Devuelve los límites de tasa aplicables, por contexto.
|
|
1055
|
+
*
|
|
1056
|
+
* `GET /v1/usage/limits`. Sólo la configuración vigente, sin contadores en
|
|
1057
|
+
* vivo; son ajenos a la cuota mensual de `summary()`.
|
|
1058
|
+
*/
|
|
1059
|
+
async limits() {
|
|
1060
|
+
const response = await this.transport.request({ method: 'GET', path: '/usage/limits' });
|
|
1061
|
+
return (0, internal_js_1.readData)(response, 'de los límites');
|
|
1062
|
+
}
|
|
1063
|
+
/**
|
|
1064
|
+
* Devuelve las validaciones agrupadas por día y hora.
|
|
1065
|
+
*
|
|
1066
|
+
* `GET /v1/usage/heatmap`. Cubre los últimos `days` días (máximo 90) y sólo trae
|
|
1067
|
+
* las celdas con al menos una validación.
|
|
1068
|
+
*/
|
|
1069
|
+
async heatmap(params = {}) {
|
|
1070
|
+
const response = await this.transport.request({
|
|
1071
|
+
method: 'GET',
|
|
1072
|
+
path: '/usage/heatmap',
|
|
1073
|
+
query: { days: params.days },
|
|
1074
|
+
});
|
|
1075
|
+
return (0, internal_js_1.readData)(response, 'del mapa de calor');
|
|
1076
|
+
}
|
|
1077
|
+
/**
|
|
1078
|
+
* Devuelve las métricas de uso de la API de la cuenta.
|
|
1079
|
+
*
|
|
1080
|
+
* `GET /v1/api/usage`. Reúne las peticiones de hoy y del mes, la cuota del plan,
|
|
1081
|
+
* las últimas peticiones y el estado del servicio de Banxico.
|
|
1082
|
+
*/
|
|
1083
|
+
async apiUsage() {
|
|
1084
|
+
const response = await this.transport.request({ method: 'GET', path: '/api/usage' });
|
|
1085
|
+
return (0, internal_js_1.readData)(response, 'de las métricas');
|
|
1086
|
+
}
|
|
1087
|
+
/**
|
|
1088
|
+
* Exporta el registro de actividad en CSV o en XLSX.
|
|
1089
|
+
*
|
|
1090
|
+
* `GET /v1/api/usage/export`. `from` y `to` acotan el rango, las dos inclusive;
|
|
1091
|
+
* `limit` sólo baja el tope de 100 000 filas.
|
|
1092
|
+
*/
|
|
1093
|
+
export(params = {}) {
|
|
1094
|
+
return exportFile(this.transport, '/api/usage/export', params.format ?? 'csv', { from: params.from, to: params.to, limit: params.limit }, 'actividad-api');
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
exports.Usage = Usage;
|
|
1098
|
+
// ── Cuenta, panel, planes, insights, finanzas y facturación ────────────────
|
|
1099
|
+
/** Perfil y preferencias de la cuenta autenticada. */
|
|
1100
|
+
class Account {
|
|
1101
|
+
transport;
|
|
1102
|
+
constructor(transport) {
|
|
1103
|
+
this.transport = transport;
|
|
1104
|
+
}
|
|
1105
|
+
/** `GET /v1/users/me`. */
|
|
1106
|
+
async myProfile() {
|
|
1107
|
+
const response = await this.transport.request({ method: 'GET', path: '/users/me' });
|
|
1108
|
+
return (0, internal_js_1.readData)(response, 'del perfil');
|
|
1109
|
+
}
|
|
1110
|
+
profile() {
|
|
1111
|
+
return this.myProfile();
|
|
1112
|
+
}
|
|
1113
|
+
/** `GET /v1/users/me/retry-policy`. */
|
|
1114
|
+
async getMyRetryPolicy() {
|
|
1115
|
+
const response = await this.transport.request({
|
|
1116
|
+
method: 'GET',
|
|
1117
|
+
path: '/users/me/retry-policy',
|
|
1118
|
+
});
|
|
1119
|
+
return (0, internal_js_1.readData)(response, 'de la política de reintentos');
|
|
1120
|
+
}
|
|
1121
|
+
retryPolicy() {
|
|
1122
|
+
return this.getMyRetryPolicy();
|
|
1123
|
+
}
|
|
1124
|
+
/** `PUT /v1/users/me/retry-policy`. */
|
|
1125
|
+
async updateMyRetryPolicy(policy, options = {}) {
|
|
1126
|
+
const body = { retry_policy: policy };
|
|
1127
|
+
const response = await this.transport.request({
|
|
1128
|
+
method: 'PUT',
|
|
1129
|
+
path: '/users/me/retry-policy',
|
|
1130
|
+
body,
|
|
1131
|
+
headers: idempotencyHeader(options.idempotencyKey),
|
|
1132
|
+
});
|
|
1133
|
+
return (0, internal_js_1.readData)(response, 'de la política de reintentos');
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
exports.Account = Account;
|
|
1137
|
+
/** Resumen del panel de la cuenta. */
|
|
1138
|
+
class Dashboard {
|
|
1139
|
+
transport;
|
|
1140
|
+
constructor(transport) {
|
|
1141
|
+
this.transport = transport;
|
|
1142
|
+
}
|
|
1143
|
+
/** `GET /v1/summary`. */
|
|
1144
|
+
async getSummary(params = {}) {
|
|
1145
|
+
const response = await this.transport.request({
|
|
1146
|
+
method: 'GET',
|
|
1147
|
+
path: '/summary',
|
|
1148
|
+
query: { limit: params.limit },
|
|
1149
|
+
});
|
|
1150
|
+
return (0, internal_js_1.readData)(response, 'del resumen del panel');
|
|
1151
|
+
}
|
|
1152
|
+
summary(params = {}) {
|
|
1153
|
+
return this.getSummary(params);
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
exports.Dashboard = Dashboard;
|
|
1157
|
+
/** Catálogo de planes que no requiere clave de API. */
|
|
1158
|
+
class Plans {
|
|
1159
|
+
transport;
|
|
1160
|
+
constructor(transport) {
|
|
1161
|
+
this.transport = transport;
|
|
1162
|
+
}
|
|
1163
|
+
/** `GET /v1/plans/public`. */
|
|
1164
|
+
async listPublic() {
|
|
1165
|
+
const response = await this.transport.request({
|
|
1166
|
+
method: 'GET',
|
|
1167
|
+
path: '/plans/public',
|
|
1168
|
+
authenticated: false,
|
|
1169
|
+
});
|
|
1170
|
+
return (0, internal_js_1.readData)(response, 'de los planes públicos');
|
|
1171
|
+
}
|
|
1172
|
+
list() {
|
|
1173
|
+
return this.listPublic();
|
|
1174
|
+
}
|
|
1175
|
+
/** `GET /v1/plans/public/comparison`. */
|
|
1176
|
+
async getPublicPlanComparison() {
|
|
1177
|
+
const response = await this.transport.request({
|
|
1178
|
+
method: 'GET',
|
|
1179
|
+
path: '/plans/public/comparison',
|
|
1180
|
+
authenticated: false,
|
|
1181
|
+
});
|
|
1182
|
+
return (0, internal_js_1.readData)(response, 'de la comparación de planes');
|
|
1183
|
+
}
|
|
1184
|
+
comparison() {
|
|
1185
|
+
return this.getPublicPlanComparison();
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
exports.Plans = Plans;
|
|
1189
|
+
/** Métricas agregadas de la cuenta autenticada. */
|
|
1190
|
+
class Insights {
|
|
1191
|
+
transport;
|
|
1192
|
+
constructor(transport) {
|
|
1193
|
+
this.transport = transport;
|
|
1194
|
+
}
|
|
1195
|
+
async getOverview() {
|
|
1196
|
+
const response = await this.transport.request({ method: 'GET', path: '/insights/overview' });
|
|
1197
|
+
return (0, internal_js_1.readData)(response, 'de insights');
|
|
1198
|
+
}
|
|
1199
|
+
overview() {
|
|
1200
|
+
return this.getOverview();
|
|
1201
|
+
}
|
|
1202
|
+
async getTrends(params = {}) {
|
|
1203
|
+
const response = await this.transport.request({
|
|
1204
|
+
method: 'GET',
|
|
1205
|
+
path: '/insights/trends',
|
|
1206
|
+
query: { range: params.range, metric: params.metric },
|
|
1207
|
+
});
|
|
1208
|
+
return (0, internal_js_1.readData)(response, 'de las tendencias');
|
|
1209
|
+
}
|
|
1210
|
+
trends(params = {}) {
|
|
1211
|
+
return this.getTrends(params);
|
|
1212
|
+
}
|
|
1213
|
+
async getTopBanks(params = {}) {
|
|
1214
|
+
const response = await this.transport.request({
|
|
1215
|
+
method: 'GET',
|
|
1216
|
+
path: '/insights/top-banks',
|
|
1217
|
+
query: { metric: params.metric, limit: params.limit },
|
|
1218
|
+
});
|
|
1219
|
+
return (0, internal_js_1.readData)(response, 'de los bancos principales');
|
|
1220
|
+
}
|
|
1221
|
+
topBanks(params = {}) {
|
|
1222
|
+
return this.getTopBanks(params);
|
|
1223
|
+
}
|
|
1224
|
+
async getTopBeneficiaries(params = {}) {
|
|
1225
|
+
const response = await this.transport.request({
|
|
1226
|
+
method: 'GET',
|
|
1227
|
+
path: '/insights/top-beneficiaries',
|
|
1228
|
+
query: { limit: params.limit },
|
|
1229
|
+
});
|
|
1230
|
+
return (0, internal_js_1.readData)(response, 'de los beneficiarios principales');
|
|
1231
|
+
}
|
|
1232
|
+
topBeneficiaries(params = {}) {
|
|
1233
|
+
return this.getTopBeneficiaries(params);
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
exports.Insights = Insights;
|
|
1237
|
+
const FINANCE_CONTENT_TYPES = {
|
|
1238
|
+
pdf: 'application/pdf',
|
|
1239
|
+
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
1240
|
+
csv: 'text/csv',
|
|
1241
|
+
html: 'text/html',
|
|
1242
|
+
zip: 'application/zip',
|
|
1243
|
+
};
|
|
1244
|
+
async function financeFile(transport, path, query, format, fallback, includeFormat = true) {
|
|
1245
|
+
const contentType = FINANCE_CONTENT_TYPES[format] ?? 'application/octet-stream';
|
|
1246
|
+
const response = await transport.request({
|
|
1247
|
+
method: 'GET',
|
|
1248
|
+
path,
|
|
1249
|
+
query: includeFormat ? { ...query, format } : query,
|
|
1250
|
+
accept: `${contentType}, application/json`,
|
|
1251
|
+
});
|
|
1252
|
+
return toFile(response, `${fallback}.${format}`, response.headers['content-type'] ?? contentType);
|
|
1253
|
+
}
|
|
1254
|
+
/** Resúmenes y descargas financieras. */
|
|
1255
|
+
class Finance {
|
|
1256
|
+
transport;
|
|
1257
|
+
constructor(transport) {
|
|
1258
|
+
this.transport = transport;
|
|
1259
|
+
}
|
|
1260
|
+
async getSummary(params) {
|
|
1261
|
+
const response = await this.transport.request({
|
|
1262
|
+
method: 'GET',
|
|
1263
|
+
path: '/finance/summary',
|
|
1264
|
+
query: { month: params.month, user_id: params.userId },
|
|
1265
|
+
});
|
|
1266
|
+
return (0, internal_js_1.readData)(response, 'del resumen financiero');
|
|
1267
|
+
}
|
|
1268
|
+
summary(params) {
|
|
1269
|
+
return this.getSummary(params);
|
|
1270
|
+
}
|
|
1271
|
+
getStatement(params) {
|
|
1272
|
+
return financeFile(this.transport, '/finance/statement', { month: params.month, user_id: params.userId }, params.format ?? 'pdf', 'estado-de-cuenta');
|
|
1273
|
+
}
|
|
1274
|
+
statement(params) {
|
|
1275
|
+
return this.getStatement(params);
|
|
1276
|
+
}
|
|
1277
|
+
preview(path, params, fallback) {
|
|
1278
|
+
const format = params.format ?? 'csv';
|
|
1279
|
+
if (format !== 'preview') {
|
|
1280
|
+
return financeFile(this.transport, path, { month: params.month, user_id: params.userId, limit: params.limit }, format, fallback);
|
|
1281
|
+
}
|
|
1282
|
+
return this.transport
|
|
1283
|
+
.request({
|
|
1284
|
+
method: 'GET',
|
|
1285
|
+
path,
|
|
1286
|
+
query: { month: params.month, user_id: params.userId, limit: params.limit, format },
|
|
1287
|
+
})
|
|
1288
|
+
.then((response) => (0, internal_js_1.readData)(response, `de ${fallback}`));
|
|
1289
|
+
}
|
|
1290
|
+
getMonthly(params) {
|
|
1291
|
+
return this.preview('/finance/monthly', params, 'finanzas-mensuales');
|
|
1292
|
+
}
|
|
1293
|
+
monthly(params) {
|
|
1294
|
+
return this.getMonthly(params);
|
|
1295
|
+
}
|
|
1296
|
+
getCounterparties(params) {
|
|
1297
|
+
return this.preview('/finance/counterparties', params, 'contrapartes');
|
|
1298
|
+
}
|
|
1299
|
+
counterparties(params) {
|
|
1300
|
+
return this.getCounterparties(params);
|
|
1301
|
+
}
|
|
1302
|
+
getByBank(params) {
|
|
1303
|
+
return this.preview('/finance/by-bank', params, 'finanzas-por-banco');
|
|
1304
|
+
}
|
|
1305
|
+
byBank(params) {
|
|
1306
|
+
return this.getByBank(params);
|
|
1307
|
+
}
|
|
1308
|
+
getAccounting(params) {
|
|
1309
|
+
const format = params.format ?? 'csv';
|
|
1310
|
+
if (format !== 'preview') {
|
|
1311
|
+
return financeFile(this.transport, '/finance/accounting', {
|
|
1312
|
+
month: params.month,
|
|
1313
|
+
user_id: params.userId,
|
|
1314
|
+
limit: params.limit,
|
|
1315
|
+
decimal: params.decimal,
|
|
1316
|
+
}, format, 'contabilidad');
|
|
1317
|
+
}
|
|
1318
|
+
return this.transport
|
|
1319
|
+
.request({
|
|
1320
|
+
method: 'GET',
|
|
1321
|
+
path: '/finance/accounting',
|
|
1322
|
+
query: {
|
|
1323
|
+
month: params.month,
|
|
1324
|
+
user_id: params.userId,
|
|
1325
|
+
limit: params.limit,
|
|
1326
|
+
decimal: params.decimal,
|
|
1327
|
+
format,
|
|
1328
|
+
},
|
|
1329
|
+
})
|
|
1330
|
+
.then((response) => (0, internal_js_1.readData)(response, 'de contabilidad'));
|
|
1331
|
+
}
|
|
1332
|
+
accounting(params) {
|
|
1333
|
+
return this.getAccounting(params);
|
|
1334
|
+
}
|
|
1335
|
+
getCeps(params) {
|
|
1336
|
+
return financeFile(this.transport, '/finance/ceps', { from: params.from, to: params.to, user_id: params.userId }, 'zip', 'ceps', false);
|
|
1337
|
+
}
|
|
1338
|
+
ceps(params) {
|
|
1339
|
+
return this.getCeps(params);
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
exports.Finance = Finance;
|
|
1343
|
+
/** Suscripción activa de la cuenta autenticada. */
|
|
1344
|
+
class Billing {
|
|
1345
|
+
transport;
|
|
1346
|
+
constructor(transport) {
|
|
1347
|
+
this.transport = transport;
|
|
1348
|
+
}
|
|
1349
|
+
async getSubscription() {
|
|
1350
|
+
const response = await this.transport.request({
|
|
1351
|
+
method: 'GET',
|
|
1352
|
+
path: '/billing/subscription',
|
|
1353
|
+
});
|
|
1354
|
+
return (0, internal_js_1.readData)(response, 'de la suscripción');
|
|
1355
|
+
}
|
|
1356
|
+
subscription() {
|
|
1357
|
+
return this.getSubscription();
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
exports.Billing = Billing;
|
|
1361
|
+
//# sourceMappingURL=resources.js.map
|