@nubiia/mcp-freematica 0.6.1
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 +21 -0
- package/README.md +485 -0
- package/dist/clients/base-client.d.ts +31 -0
- package/dist/clients/base-client.js +102 -0
- package/dist/clients/base-client.js.map +1 -0
- package/dist/clients/fiql-builder.d.ts +118 -0
- package/dist/clients/fiql-builder.js +257 -0
- package/dist/clients/fiql-builder.js.map +1 -0
- package/dist/clients/freematica-client.d.ts +659 -0
- package/dist/clients/freematica-client.js +1327 -0
- package/dist/clients/freematica-client.js.map +1 -0
- package/dist/clients/hardened-base-client.d.ts +143 -0
- package/dist/clients/hardened-base-client.js +366 -0
- package/dist/clients/hardened-base-client.js.map +1 -0
- package/dist/config.d.ts +104 -0
- package/dist/config.js +105 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +55 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +71 -0
- package/dist/logger.js +114 -0
- package/dist/logger.js.map +1 -0
- package/dist/schemas/albaranes.d.ts +124 -0
- package/dist/schemas/albaranes.js +250 -0
- package/dist/schemas/albaranes.js.map +1 -0
- package/dist/schemas/cartera.d.ts +70 -0
- package/dist/schemas/cartera.js +104 -0
- package/dist/schemas/cartera.js.map +1 -0
- package/dist/schemas/contratos.d.ts +7 -0
- package/dist/schemas/contratos.js +2 -0
- package/dist/schemas/contratos.js.map +1 -0
- package/dist/schemas/facturas-electronicas.d.ts +60 -0
- package/dist/schemas/facturas-electronicas.js +79 -0
- package/dist/schemas/facturas-electronicas.js.map +1 -0
- package/dist/schemas/facturas-ventas.d.ts +118 -0
- package/dist/schemas/facturas-ventas.js +153 -0
- package/dist/schemas/facturas-ventas.js.map +1 -0
- package/dist/schemas/filters.d.ts +92 -0
- package/dist/schemas/filters.js +76 -0
- package/dist/schemas/filters.js.map +1 -0
- package/dist/schemas/master-data.d.ts +26 -0
- package/dist/schemas/master-data.js +86 -0
- package/dist/schemas/master-data.js.map +1 -0
- package/dist/schemas/pagination.d.ts +14 -0
- package/dist/schemas/pagination.js +27 -0
- package/dist/schemas/pagination.js.map +1 -0
- package/dist/schemas/pedidos-compras.d.ts +92 -0
- package/dist/schemas/pedidos-compras.js +189 -0
- package/dist/schemas/pedidos-compras.js.map +1 -0
- package/dist/server-instructions.d.ts +1 -0
- package/dist/server-instructions.js +83 -0
- package/dist/server-instructions.js.map +1 -0
- package/dist/server.d.ts +6 -0
- package/dist/server.js +41 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/albaranes.d.ts +16 -0
- package/dist/tools/albaranes.js +202 -0
- package/dist/tools/albaranes.js.map +1 -0
- package/dist/tools/calendarios.d.ts +13 -0
- package/dist/tools/calendarios.js +82 -0
- package/dist/tools/calendarios.js.map +1 -0
- package/dist/tools/cartera.d.ts +13 -0
- package/dist/tools/cartera.js +78 -0
- package/dist/tools/cartera.js.map +1 -0
- package/dist/tools/clientes.d.ts +3 -0
- package/dist/tools/clientes.js +48 -0
- package/dist/tools/clientes.js.map +1 -0
- package/dist/tools/contabilidad.d.ts +14 -0
- package/dist/tools/contabilidad.js +259 -0
- package/dist/tools/contabilidad.js.map +1 -0
- package/dist/tools/contactos-clientes.d.ts +3 -0
- package/dist/tools/contactos-clientes.js +25 -0
- package/dist/tools/contactos-clientes.js.map +1 -0
- package/dist/tools/contratos.d.ts +10 -0
- package/dist/tools/contratos.js +35 -0
- package/dist/tools/contratos.js.map +1 -0
- package/dist/tools/facturas-compras.d.ts +13 -0
- package/dist/tools/facturas-compras.js +131 -0
- package/dist/tools/facturas-compras.js.map +1 -0
- package/dist/tools/facturas-electronicas.d.ts +17 -0
- package/dist/tools/facturas-electronicas.js +268 -0
- package/dist/tools/facturas-electronicas.js.map +1 -0
- package/dist/tools/facturas-ventas.d.ts +16 -0
- package/dist/tools/facturas-ventas.js +181 -0
- package/dist/tools/facturas-ventas.js.map +1 -0
- package/dist/tools/helpers.d.ts +17 -0
- package/dist/tools/helpers.js +36 -0
- package/dist/tools/helpers.js.map +1 -0
- package/dist/tools/localizaciones.d.ts +17 -0
- package/dist/tools/localizaciones.js +192 -0
- package/dist/tools/localizaciones.js.map +1 -0
- package/dist/tools/master-data.d.ts +10 -0
- package/dist/tools/master-data.js +70 -0
- package/dist/tools/master-data.js.map +1 -0
- package/dist/tools/oportunidades-negocio.d.ts +3 -0
- package/dist/tools/oportunidades-negocio.js +68 -0
- package/dist/tools/oportunidades-negocio.js.map +1 -0
- package/dist/tools/pedidos-compras.d.ts +14 -0
- package/dist/tools/pedidos-compras.js +105 -0
- package/dist/tools/pedidos-compras.js.map +1 -0
- package/dist/tools/personal.d.ts +13 -0
- package/dist/tools/personal.js +154 -0
- package/dist/tools/personal.js.map +1 -0
- package/dist/tools/prl.d.ts +47 -0
- package/dist/tools/prl.js +242 -0
- package/dist/tools/prl.js.map +1 -0
- package/dist/tools/proveedores.d.ts +14 -0
- package/dist/tools/proveedores.js +122 -0
- package/dist/tools/proveedores.js.map +1 -0
- package/dist/transports/http.d.ts +12 -0
- package/dist/transports/http.js +110 -0
- package/dist/transports/http.js.map +1 -0
- package/dist/transports/stdio.d.ts +12 -0
- package/dist/transports/stdio.js +15 -0
- package/dist/transports/stdio.js.map +1 -0
- package/dist/types/api-envelope.d.ts +27 -0
- package/dist/types/api-envelope.js +2 -0
- package/dist/types/api-envelope.js.map +1 -0
- package/dist/types/clientes.d.ts +9 -0
- package/dist/types/clientes.js +2 -0
- package/dist/types/clientes.js.map +1 -0
- package/dist/types/contactos-clientes.d.ts +7 -0
- package/dist/types/contactos-clientes.js +2 -0
- package/dist/types/contactos-clientes.js.map +1 -0
- package/dist/types/contratos.d.ts +8 -0
- package/dist/types/contratos.js +2 -0
- package/dist/types/contratos.js.map +1 -0
- package/dist/types/master-data.d.ts +9 -0
- package/dist/types/master-data.js +2 -0
- package/dist/types/master-data.js.map +1 -0
- package/dist/types/oportunidades-negocio.d.ts +8 -0
- package/dist/types/oportunidades-negocio.js +2 -0
- package/dist/types/oportunidades-negocio.js.map +1 -0
- package/dist/utils/size-guardrail.d.ts +22 -0
- package/dist/utils/size-guardrail.js +31 -0
- package/dist/utils/size-guardrail.js.map +1 -0
- package/package.json +90 -0
|
@@ -0,0 +1,1327 @@
|
|
|
1
|
+
import { BaseClient } from './base-client.js';
|
|
2
|
+
import { buildFiql, appendRquery } from './fiql-builder.js';
|
|
3
|
+
import { CATALOG_ENDPOINTS } from '../schemas/master-data.js';
|
|
4
|
+
import { ESTADO_CARTERA_FIQL_MAP, } from '../schemas/cartera.js';
|
|
5
|
+
import { buildEstadoPedidoFiql, } from '../schemas/pedidos-compras.js';
|
|
6
|
+
import { logger } from '../logger.js';
|
|
7
|
+
import { loadMaxResponseSizeMb } from '../utils/size-guardrail.js';
|
|
8
|
+
/**
|
|
9
|
+
* Typed client for the Freemática REST API.
|
|
10
|
+
*
|
|
11
|
+
* One method per exposed endpoint (or per family of endpoints). The wrapper
|
|
12
|
+
* `{ errorCode, errorMessage, data }` is unwrapped by `BaseClient.request`,
|
|
13
|
+
* so methods only see `data`.
|
|
14
|
+
*
|
|
15
|
+
* For list endpoints, `data` has shape `{ total, items, rowHeight }`. List
|
|
16
|
+
* methods unwrap that to `{ items, total }` (string total → number; rowHeight
|
|
17
|
+
* discarded).
|
|
18
|
+
*
|
|
19
|
+
* For detail endpoints, `data` is the entity directly.
|
|
20
|
+
*/
|
|
21
|
+
export class FreematicaClient extends BaseClient {
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Existing endpoints (v0.1.0, v0.3.0) — shape adjusted for the unwrap fix
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
/**
|
|
26
|
+
* Obtener lista de material asignado a servicios.
|
|
27
|
+
*
|
|
28
|
+
* Endpoint: GET /pvss/v2/contratos-servicios-material
|
|
29
|
+
*/
|
|
30
|
+
async getMaterialesAsignadosServicios() {
|
|
31
|
+
const data = await this.get('/pvss/v2/contratos-servicios-material');
|
|
32
|
+
return { items: data.items, total: Number(data.total) };
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Obtener un catálogo de datos maestros.
|
|
36
|
+
*/
|
|
37
|
+
async getMasterData(catalog) {
|
|
38
|
+
const endpoint = CATALOG_ENDPOINTS[catalog];
|
|
39
|
+
const data = await this.get(endpoint);
|
|
40
|
+
return { items: data.items, total: Number(data.total) };
|
|
41
|
+
}
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
// Clientes (v0.4.0)
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
/** Lista paginada de clientes. */
|
|
46
|
+
async listClientes(opts = {}) {
|
|
47
|
+
return this.listResource('/pgrl/v2/clientes', opts);
|
|
48
|
+
}
|
|
49
|
+
/** Detalle de un cliente por `idReg` opaco. */
|
|
50
|
+
async getCliente(idReg) {
|
|
51
|
+
return this.get(`/pgrl/v2/clientes/${encodeURIComponent(idReg)}`);
|
|
52
|
+
}
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// Contactos clientes (v0.4.0)
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
/** Lista paginada de contactos de clientes. */
|
|
57
|
+
async listContactosClientes(opts = {}) {
|
|
58
|
+
return this.listResource('/pgrl/v2/contactos-clientes', opts);
|
|
59
|
+
}
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// Oportunidades de negocio (v0.4.0)
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
/** Lista paginada de oportunidades de negocio. */
|
|
64
|
+
async listOportunidadesNegocio(opts = {}) {
|
|
65
|
+
return this.listResource('/pcrm/v2/oportunidades-negocio', opts);
|
|
66
|
+
}
|
|
67
|
+
/** Detalle de una oportunidad por `idReg` opaco. */
|
|
68
|
+
async getOportunidadNegocio(idReg) {
|
|
69
|
+
return this.get(`/pcrm/v2/oportunidades-negocio/${encodeURIComponent(idReg)}`);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Datos ampliados de una oportunidad por `idReg` opaco.
|
|
73
|
+
*
|
|
74
|
+
* Puede devolver `not_found` si la oportunidad no tiene datos ampliados.
|
|
75
|
+
* El caller debe manejar el `FreematicaError` con código `not_found`.
|
|
76
|
+
*/
|
|
77
|
+
async getOportunidadNegocioDatosAmpliados(idReg) {
|
|
78
|
+
return this.get(`/pcrm/v2/oportunidades-negocio/${encodeURIComponent(idReg)}/datos-ampliados`);
|
|
79
|
+
}
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
// PRL — Prevención de Riesgos Laborales (v0.5.0)
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
/**
|
|
84
|
+
* Opciones de filtro para getFichaPrevCliente.
|
|
85
|
+
*
|
|
86
|
+
* Los query params mapean directamente a los parámetros nativos del endpoint
|
|
87
|
+
* GET /pprl/v2/ficha-prev-cliente. NO se usa FIQL en este endpoint.
|
|
88
|
+
*/
|
|
89
|
+
/**
|
|
90
|
+
* Obtiene la ficha de PRL de un cliente.
|
|
91
|
+
*
|
|
92
|
+
* Endpoint: GET /pprl/v2/ficha-prev-cliente
|
|
93
|
+
*
|
|
94
|
+
* Los filtros se envían como query params nativos (codCli, grpCli, locServ,
|
|
95
|
+
* codigo). La validación de "al menos uno" es responsabilidad del caller
|
|
96
|
+
* (la tool lo hace via Zod .refine()).
|
|
97
|
+
*
|
|
98
|
+
* @param opts - Filtros: codCliente, grupoCliente, codLocalizacionServicio, codigoFicha.
|
|
99
|
+
* @returns Lista de fichas PRL que coinciden con los filtros.
|
|
100
|
+
*/
|
|
101
|
+
async getFichaPrevCliente(opts) {
|
|
102
|
+
const url = new URL('placeholder://x/pprl/v2/ficha-prev-cliente');
|
|
103
|
+
if (opts.codCliente !== undefined)
|
|
104
|
+
url.searchParams.set('codCli', opts.codCliente);
|
|
105
|
+
if (opts.grupoCliente !== undefined)
|
|
106
|
+
url.searchParams.set('grpCli', String(opts.grupoCliente));
|
|
107
|
+
if (opts.codLocalizacionServicio !== undefined)
|
|
108
|
+
url.searchParams.set('locServ', String(opts.codLocalizacionServicio));
|
|
109
|
+
if (opts.codigoFicha !== undefined)
|
|
110
|
+
url.searchParams.set('codigo', opts.codigoFicha);
|
|
111
|
+
const qs = url.searchParams.toString();
|
|
112
|
+
const path = qs ? `/pprl/v2/ficha-prev-cliente?${qs}` : '/pprl/v2/ficha-prev-cliente';
|
|
113
|
+
const data = await this.get(path);
|
|
114
|
+
return { items: data.items, total: Number(data.total) };
|
|
115
|
+
}
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
// Contabilidad — cuentas contables (v0.5.0)
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
/**
|
|
120
|
+
* Lista de cuentas del plan contable.
|
|
121
|
+
*
|
|
122
|
+
* Endpoint: GET /pcon/v2/cuentas
|
|
123
|
+
*
|
|
124
|
+
* Los filtros opcionales se mapean a una expresión FIQL y se pasan como
|
|
125
|
+
* query param `rquery`. El prefijoCuenta usa operador `=ge=` / `=lt=` para
|
|
126
|
+
* emular un prefix match sobre COD_CTA (rango léxico).
|
|
127
|
+
*
|
|
128
|
+
* @param opts - Filtros opcionales: codPlan, prefijoCuenta, activa, grupoCuenta.
|
|
129
|
+
*/
|
|
130
|
+
async listCuentasContables(opts = {}) {
|
|
131
|
+
const url = new URL('https://placeholder/pcon/v2/cuentas');
|
|
132
|
+
const filters = {};
|
|
133
|
+
if (opts.codPlan !== undefined)
|
|
134
|
+
filters['COD_PLAN'] = opts.codPlan;
|
|
135
|
+
if (opts.activa !== undefined)
|
|
136
|
+
filters['CTA_ACTIVA'] = opts.activa ? '1' : '0';
|
|
137
|
+
if (opts.grupoCuenta !== undefined)
|
|
138
|
+
filters['COD_GRUPO_CTA'] = opts.grupoCuenta;
|
|
139
|
+
const andGroups = [];
|
|
140
|
+
if (Object.keys(filters).length > 0) {
|
|
141
|
+
andGroups.push(filters);
|
|
142
|
+
}
|
|
143
|
+
// Prefix match sobre COD_CTA: COD_CTA >= prefix AND COD_CTA < prefix_next
|
|
144
|
+
if (opts.prefijoCuenta !== undefined) {
|
|
145
|
+
const prefix = opts.prefijoCuenta;
|
|
146
|
+
const nextPrefix = buildNextPrefix(prefix);
|
|
147
|
+
andGroups.push({ COD_CTA: { op: 'ge', value: prefix } });
|
|
148
|
+
if (nextPrefix !== null) {
|
|
149
|
+
andGroups.push({ COD_CTA: { op: 'lt', value: nextPrefix } });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
const fiql = andGroups.length > 0 ? buildFiql({ and: andGroups }) : '';
|
|
153
|
+
appendRquery(url, fiql);
|
|
154
|
+
const path = url.pathname + (url.search ? url.search : '');
|
|
155
|
+
const data = await this.get(path);
|
|
156
|
+
return { items: data.items, total: Number(data.total) };
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Opciones de filtro para listVigilanciaSalud.
|
|
160
|
+
*/
|
|
161
|
+
/**
|
|
162
|
+
* Lista paginada de registros de Vigilancia de la Salud.
|
|
163
|
+
*
|
|
164
|
+
* Endpoint: GET /pprl/v1/vigilancia-salud
|
|
165
|
+
*
|
|
166
|
+
* `idRegPersona` se envía como query param nativo. El resto de filtros se
|
|
167
|
+
* codifican en FIQL y se envían como `rquery`.
|
|
168
|
+
*
|
|
169
|
+
* @param opts - Opciones de paginación y filtrado.
|
|
170
|
+
* @returns Lista paginada de registros VS.
|
|
171
|
+
*/
|
|
172
|
+
async listVigilanciaSalud(opts) {
|
|
173
|
+
const url = new URL('placeholder://x/pprl/v1/vigilancia-salud');
|
|
174
|
+
if (opts.items !== undefined)
|
|
175
|
+
url.searchParams.set('items', String(opts.items));
|
|
176
|
+
if (opts.page !== undefined)
|
|
177
|
+
url.searchParams.set('page', String(opts.page));
|
|
178
|
+
if (opts.idRegPersona !== undefined)
|
|
179
|
+
url.searchParams.set('idRegPersona', opts.idRegPersona);
|
|
180
|
+
// Filtros FIQL
|
|
181
|
+
const fiqlParts = {};
|
|
182
|
+
if (opts.empresa !== undefined)
|
|
183
|
+
fiqlParts['PERVS_EMP'] = opts.empresa;
|
|
184
|
+
if (opts.delegacion !== undefined)
|
|
185
|
+
fiqlParts['PERVS_DELEG'] = opts.delegacion;
|
|
186
|
+
if (opts.codPersona !== undefined)
|
|
187
|
+
fiqlParts['PERVS_PERSO'] = opts.codPersona;
|
|
188
|
+
if (opts.tipoRevision !== undefined)
|
|
189
|
+
fiqlParts['PERVS_TIPO_REVISION'] = opts.tipoRevision;
|
|
190
|
+
if (opts.resultado !== undefined)
|
|
191
|
+
fiqlParts['PERVS_RESULTADO'] = opts.resultado;
|
|
192
|
+
if (opts.fechaCitaDesde !== undefined)
|
|
193
|
+
fiqlParts['PERVS_FCH_CITA'] = { op: 'ge', value: opts.fechaCitaDesde };
|
|
194
|
+
// If both desde and hasta, we need to combine with AND
|
|
195
|
+
const fiqlParts2 = {};
|
|
196
|
+
if (opts.fechaCitaHasta !== undefined)
|
|
197
|
+
fiqlParts2['PERVS_FCH_CITA'] = { op: 'le', value: opts.fechaCitaHasta };
|
|
198
|
+
const fiql1 = buildFiql(fiqlParts);
|
|
199
|
+
const fiql2 = buildFiql(fiqlParts2);
|
|
200
|
+
const combinedFiql = [fiql1, fiql2].filter(Boolean).join(';');
|
|
201
|
+
appendRquery(url, combinedFiql);
|
|
202
|
+
const qs = url.searchParams.toString();
|
|
203
|
+
const path = qs ? `/pprl/v1/vigilancia-salud?${qs}` : '/pprl/v1/vigilancia-salud';
|
|
204
|
+
const data = await this.get(path);
|
|
205
|
+
return { items: data.items, total: Number(data.total) };
|
|
206
|
+
}
|
|
207
|
+
// ---------------------------------------------------------------------------
|
|
208
|
+
// Contabilidad — cuentas analíticas (v0.5.0)
|
|
209
|
+
// ---------------------------------------------------------------------------
|
|
210
|
+
/**
|
|
211
|
+
* Lista de cuentas analíticas.
|
|
212
|
+
*
|
|
213
|
+
* Endpoint: GET /pcon/v2/cuentas-analiticas
|
|
214
|
+
*
|
|
215
|
+
* @param opts - Filtros opcionales: codPlan, prefijoCuenta, activa, grupoCuenta, area, delegacion.
|
|
216
|
+
*/
|
|
217
|
+
async listCuentasAnaliticas(opts = {}) {
|
|
218
|
+
const url = new URL('https://placeholder/pcon/v2/cuentas-analiticas');
|
|
219
|
+
const filters = {};
|
|
220
|
+
if (opts.codPlan !== undefined)
|
|
221
|
+
filters['COD_PLAN'] = opts.codPlan;
|
|
222
|
+
if (opts.activa !== undefined)
|
|
223
|
+
filters['CTA_ACTIVA_ANL'] = opts.activa ? '1' : '0';
|
|
224
|
+
if (opts.grupoCuenta !== undefined)
|
|
225
|
+
filters['COD_GRUPO_ANL'] = opts.grupoCuenta;
|
|
226
|
+
if (opts.area !== undefined)
|
|
227
|
+
filters['AREA_ANL'] = opts.area;
|
|
228
|
+
if (opts.delegacion !== undefined)
|
|
229
|
+
filters['DELEG'] = opts.delegacion;
|
|
230
|
+
const andGroups = [];
|
|
231
|
+
if (Object.keys(filters).length > 0) {
|
|
232
|
+
andGroups.push(filters);
|
|
233
|
+
}
|
|
234
|
+
// Prefix match sobre COD_CTA_ANL
|
|
235
|
+
if (opts.prefijoCuenta !== undefined) {
|
|
236
|
+
const prefix = opts.prefijoCuenta;
|
|
237
|
+
const nextPrefix = buildNextPrefix(prefix);
|
|
238
|
+
andGroups.push({ COD_CTA_ANL: { op: 'ge', value: prefix } });
|
|
239
|
+
if (nextPrefix !== null) {
|
|
240
|
+
andGroups.push({ COD_CTA_ANL: { op: 'lt', value: nextPrefix } });
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
const fiql = andGroups.length > 0 ? buildFiql({ and: andGroups }) : '';
|
|
244
|
+
appendRquery(url, fiql);
|
|
245
|
+
const path = url.pathname + (url.search ? url.search : '');
|
|
246
|
+
const data = await this.get(path);
|
|
247
|
+
return { items: data.items, total: Number(data.total) };
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Detalle de un registro de Vigilancia de la Salud por `idReg` opaco.
|
|
251
|
+
*
|
|
252
|
+
* Endpoint: GET /pprl/v1/vigilancia-salud/{idreg}
|
|
253
|
+
*
|
|
254
|
+
* @param idReg - Identificador opaco del registro.
|
|
255
|
+
* @returns Objeto con todos los campos del registro VS.
|
|
256
|
+
*/
|
|
257
|
+
async getVigilanciaSalud(idReg) {
|
|
258
|
+
return this.get(`/pprl/v1/vigilancia-salud/${encodeURIComponent(idReg)}`);
|
|
259
|
+
}
|
|
260
|
+
// ---------------------------------------------------------------------------
|
|
261
|
+
// Personal — RRHH (v0.5.0)
|
|
262
|
+
// ---------------------------------------------------------------------------
|
|
263
|
+
/**
|
|
264
|
+
* Lista paginada de personas (empleados) con filtros FIQL opcionales.
|
|
265
|
+
*
|
|
266
|
+
* Endpoint: GET /pers/v2/personal
|
|
267
|
+
*
|
|
268
|
+
* El filtro `activo` (boolean) se traduce a `VSSPER_ACTIVO==S` (true) o
|
|
269
|
+
* `VSSPER_ACTIVO==N` (false). Las búsquedas por `nombre` y `apellido` usan
|
|
270
|
+
* el operador de igualdad estándar FIQL (el servidor aplica LIKE internamente
|
|
271
|
+
* para estos campos).
|
|
272
|
+
*
|
|
273
|
+
* @param opts - Opciones de paginación y filtrado.
|
|
274
|
+
* @returns Lista paginada de personas.
|
|
275
|
+
*/
|
|
276
|
+
async listPersonal(opts) {
|
|
277
|
+
const url = new URL('placeholder://x/pers/v2/personal');
|
|
278
|
+
if (opts.items !== undefined)
|
|
279
|
+
url.searchParams.set('items', String(opts.items));
|
|
280
|
+
if (opts.page !== undefined)
|
|
281
|
+
url.searchParams.set('page', String(opts.page));
|
|
282
|
+
const fiqlGroup = {};
|
|
283
|
+
if (opts.empresa !== undefined)
|
|
284
|
+
fiqlGroup['VSSPER_EMP'] = opts.empresa;
|
|
285
|
+
if (opts.delegacion !== undefined)
|
|
286
|
+
fiqlGroup['VSSPER_DELEG'] = opts.delegacion;
|
|
287
|
+
if (opts.codPersona !== undefined)
|
|
288
|
+
fiqlGroup['VSSPER_COD'] = opts.codPersona;
|
|
289
|
+
if (opts.nombre !== undefined)
|
|
290
|
+
fiqlGroup['VSSPER_NOM'] = opts.nombre;
|
|
291
|
+
if (opts.apellido !== undefined)
|
|
292
|
+
fiqlGroup['VSSPER_APELL1'] = opts.apellido;
|
|
293
|
+
if (opts.nif !== undefined)
|
|
294
|
+
fiqlGroup['VSSPER_NIF'] = opts.nif;
|
|
295
|
+
if (opts.situacion !== undefined)
|
|
296
|
+
fiqlGroup['VSSPER_SIT'] = opts.situacion;
|
|
297
|
+
if (opts.departamento !== undefined)
|
|
298
|
+
fiqlGroup['VSSPER_DPTO'] = opts.departamento;
|
|
299
|
+
if (opts.seccion !== undefined)
|
|
300
|
+
fiqlGroup['VSSPER_SECCION'] = opts.seccion;
|
|
301
|
+
if (opts.activo !== undefined)
|
|
302
|
+
fiqlGroup['VSSPER_ACTIVO'] = opts.activo ? 'S' : 'N';
|
|
303
|
+
appendRquery(url, buildFiql(fiqlGroup));
|
|
304
|
+
const qs = url.searchParams.toString();
|
|
305
|
+
const path = qs ? `/pers/v2/personal?${qs}` : '/pers/v2/personal';
|
|
306
|
+
const data = await this.get(path);
|
|
307
|
+
return { items: data.items, total: Number(data.total) };
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Detalle de una persona por `idReg` opaco.
|
|
311
|
+
*
|
|
312
|
+
* Endpoint: GET /pers/v2/personal/{idreg}
|
|
313
|
+
*
|
|
314
|
+
* @param idReg - Identificador opaco de la persona.
|
|
315
|
+
* @returns Objeto con todos los campos de la persona.
|
|
316
|
+
*/
|
|
317
|
+
async getPersona(idReg) {
|
|
318
|
+
return this.get(`/pers/v2/personal/${encodeURIComponent(idReg)}`);
|
|
319
|
+
}
|
|
320
|
+
// ---------------------------------------------------------------------------
|
|
321
|
+
// Calendarios laborales (v0.5.0)
|
|
322
|
+
// ---------------------------------------------------------------------------
|
|
323
|
+
/**
|
|
324
|
+
* Lista paginada de calendarios laborales.
|
|
325
|
+
*
|
|
326
|
+
* Endpoint: GET /pgrl/v1/calendarios
|
|
327
|
+
*
|
|
328
|
+
* @param opts - Opciones de paginación.
|
|
329
|
+
* @returns Lista paginada de calendarios.
|
|
330
|
+
*/
|
|
331
|
+
async listCalendarios(opts = {}) {
|
|
332
|
+
return this.listResource('/pgrl/v1/calendarios', opts);
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Lista paginada de periodos de un calendario laboral.
|
|
336
|
+
*
|
|
337
|
+
* Endpoint: GET /pgrl/v1/calendarios/{idreg}/periodos
|
|
338
|
+
*
|
|
339
|
+
* @param idCalendario - idReg opaco del calendario.
|
|
340
|
+
* @param opts - Opciones de paginación.
|
|
341
|
+
* @returns Lista paginada de periodos del calendario.
|
|
342
|
+
*/
|
|
343
|
+
async listCalendarioPeriodos(idCalendario, opts = {}) {
|
|
344
|
+
return this.listResource(`/pgrl/v1/calendarios/${encodeURIComponent(idCalendario)}/periodos`, opts);
|
|
345
|
+
}
|
|
346
|
+
// ---------------------------------------------------------------------------
|
|
347
|
+
// Contabilidad — export asientos (v0.5.0)
|
|
348
|
+
// ---------------------------------------------------------------------------
|
|
349
|
+
/**
|
|
350
|
+
* Exporta asientos contables de Freemática.
|
|
351
|
+
*
|
|
352
|
+
* Endpoint: GET /pcon/v2/export-asientos
|
|
353
|
+
*
|
|
354
|
+
* Los parámetros `empresa`, `cal` y `periodo` son query params nativos.
|
|
355
|
+
* Los filtros `fechaDesde`, `fechaHasta`, `diario` y `borrador` se envían
|
|
356
|
+
* como expresión FIQL en el param `rquery`.
|
|
357
|
+
*
|
|
358
|
+
* Protección de tamaño: si el JSON serializado supera
|
|
359
|
+
* `FREEMATICA_MAX_RESPONSE_SIZE_MB` (default 10 MB), los items se truncan
|
|
360
|
+
* y se añade un campo `warning` a la respuesta.
|
|
361
|
+
*
|
|
362
|
+
* @param opts - Opciones de exportación.
|
|
363
|
+
* @returns Resultado con `items`, `total`, y opcionalmente `truncated`+`warning`.
|
|
364
|
+
*/
|
|
365
|
+
async exportAsientos(opts) {
|
|
366
|
+
const maxMb = loadMaxResponseSizeMb();
|
|
367
|
+
const maxBytes = maxMb * 1024 * 1024;
|
|
368
|
+
// Construir URL con params nativos
|
|
369
|
+
const url = new URL('https://placeholder/pcon/v2/export-asientos');
|
|
370
|
+
url.searchParams.set('empresa', opts.empresa);
|
|
371
|
+
url.searchParams.set('cal', opts.cal);
|
|
372
|
+
if (opts.periodo !== undefined)
|
|
373
|
+
url.searchParams.set('periodo', opts.periodo);
|
|
374
|
+
// Construir FIQL para filtros adicionales
|
|
375
|
+
const andGroups = [];
|
|
376
|
+
if (opts.fechaDesde !== undefined) {
|
|
377
|
+
andGroups.push({ ASI_FCHASI: { op: 'ge', value: opts.fechaDesde } });
|
|
378
|
+
}
|
|
379
|
+
if (opts.fechaHasta !== undefined) {
|
|
380
|
+
andGroups.push({ ASI_FCHASI: { op: 'le', value: opts.fechaHasta } });
|
|
381
|
+
}
|
|
382
|
+
if (opts.diario !== undefined) {
|
|
383
|
+
andGroups.push({ ASI_DIARIO: opts.diario });
|
|
384
|
+
}
|
|
385
|
+
if (opts.borrador !== undefined) {
|
|
386
|
+
/**
|
|
387
|
+
* ASUNCIÓN EMPÍRICA — convención para campo nullable en Freemática FIQL
|
|
388
|
+
*
|
|
389
|
+
* La API de Freemática no expone un operador IS NULL nativo en FIQL. La
|
|
390
|
+
* convención observada en otras tablas del sistema es representar el valor
|
|
391
|
+
* nulo/ausente como cadena vacía `''` en las expresiones `=eq=` / `=ne=`.
|
|
392
|
+
*
|
|
393
|
+
* Para `ASI_BORR`:
|
|
394
|
+
* - borrador=true → `ASI_BORR!=''` (campo tiene algún valor → asiento en borrador)
|
|
395
|
+
* - borrador=false → `ASI_BORR==''` (campo vacío / null → asiento definitivo)
|
|
396
|
+
*
|
|
397
|
+
* Esta convención NO ha sido verificada contra la API real de Freemática.
|
|
398
|
+
* Si en pruebas funcionales el filtro no funciona correctamente, puede que
|
|
399
|
+
* el centinela sea el literal `null` en lugar de `''`, o que el campo
|
|
400
|
+
* use un valor boolean/entero como en CTA_ACTIVA.
|
|
401
|
+
*
|
|
402
|
+
* TODO(TD-???): verificar contra API real cuando esté disponible.
|
|
403
|
+
*/
|
|
404
|
+
andGroups.push({ ASI_BORR: { op: opts.borrador ? 'ne' : 'eq', value: '' } });
|
|
405
|
+
}
|
|
406
|
+
const fiql = andGroups.length > 0 ? buildFiql({ and: andGroups }) : '';
|
|
407
|
+
appendRquery(url, fiql);
|
|
408
|
+
const path = url.pathname + url.search;
|
|
409
|
+
const data = await this.get(path);
|
|
410
|
+
const items = data.items;
|
|
411
|
+
const total = Number(data.total);
|
|
412
|
+
// Validación de tamaño máximo
|
|
413
|
+
const serialized = JSON.stringify(items);
|
|
414
|
+
const byteSize = Buffer.byteLength(serialized, 'utf8');
|
|
415
|
+
if (byteSize > maxBytes) {
|
|
416
|
+
logger.warn({
|
|
417
|
+
byteSize,
|
|
418
|
+
maxBytes,
|
|
419
|
+
maxMb,
|
|
420
|
+
totalItems: items.length,
|
|
421
|
+
empresa: opts.empresa,
|
|
422
|
+
cal: opts.cal,
|
|
423
|
+
}, 'export-asientos: respuesta excede MAX_RESPONSE_SIZE_MB, truncando');
|
|
424
|
+
// Truncar items hasta que quepan en el límite
|
|
425
|
+
const truncatedItems = truncateItemsToLimit(items, maxBytes);
|
|
426
|
+
return {
|
|
427
|
+
items: truncatedItems,
|
|
428
|
+
total,
|
|
429
|
+
truncated: true,
|
|
430
|
+
warning: [
|
|
431
|
+
`La respuesta ha sido truncada porque superaba el límite de ${maxMb} MB.`,
|
|
432
|
+
`Items devueltos: ${truncatedItems.length} de ${items.length} totales.`,
|
|
433
|
+
`Usa rangos de fecha cortos (fechaDesde + fechaHasta) para obtener datos completos.`,
|
|
434
|
+
].join(' '),
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
return { items, total, truncated: false };
|
|
438
|
+
}
|
|
439
|
+
// ---------------------------------------------------------------------------
|
|
440
|
+
// Facturas de compras (v0.5.0)
|
|
441
|
+
// ---------------------------------------------------------------------------
|
|
442
|
+
/**
|
|
443
|
+
* Lista paginada de facturas de compras con filtros FIQL + query params nativos.
|
|
444
|
+
*
|
|
445
|
+
* El campo `exportado` es un enum nativo del endpoint (no FIQL): `all` | `not_exported`.
|
|
446
|
+
* El resto de filtros se envían mediante el query param `rquery` (FIQL).
|
|
447
|
+
*
|
|
448
|
+
* Endpoint: GET /pcmp/v2/facturas-compras
|
|
449
|
+
*/
|
|
450
|
+
async listFacturasCompras(opts = {}) {
|
|
451
|
+
const url = new URL('https://placeholder/pcmp/v2/facturas-compras');
|
|
452
|
+
if (opts.items !== undefined)
|
|
453
|
+
url.searchParams.set('items', String(opts.items));
|
|
454
|
+
if (opts.page !== undefined)
|
|
455
|
+
url.searchParams.set('page', String(opts.page));
|
|
456
|
+
if (opts.exportado !== undefined)
|
|
457
|
+
url.searchParams.set('exportado', opts.exportado);
|
|
458
|
+
// Construir grupos AND para el filtro de fecha (ambos usan FCC_FCHFAC con distinto operador).
|
|
459
|
+
// FCC_FCHFAC_HASTA NO existe en el schema Freemática; no se puede usar como campo sintético.
|
|
460
|
+
// La solución correcta es la composición AND: dos expresiones con el mismo campo real.
|
|
461
|
+
const dateGroups = [];
|
|
462
|
+
if (opts.fechaDesde !== undefined) {
|
|
463
|
+
dateGroups.push({ FCC_FCHFAC: { op: 'ge', value: opts.fechaDesde } });
|
|
464
|
+
}
|
|
465
|
+
if (opts.fechaHasta !== undefined) {
|
|
466
|
+
dateGroups.push({ FCC_FCHFAC: { op: 'le', value: opts.fechaHasta } });
|
|
467
|
+
}
|
|
468
|
+
// Resto de filtros escalares en un grupo plano
|
|
469
|
+
const scalarGroup = {
|
|
470
|
+
FCC_CODEMP: opts.empresa,
|
|
471
|
+
FCC_CODPRO: opts.codProveedor,
|
|
472
|
+
FCC_SERIEFRA: opts.serie,
|
|
473
|
+
FCC_NUMFRA: opts.numFactura,
|
|
474
|
+
FCC_FPAGO: opts.formaPago,
|
|
475
|
+
FCC_TRASP_CONTAB: opts.traspasadoContabilidad !== undefined
|
|
476
|
+
? String(opts.traspasadoContabilidad)
|
|
477
|
+
: undefined,
|
|
478
|
+
FCC_DELEG: opts.delegacion,
|
|
479
|
+
FCC_LIN_NEGOCIO: opts.lineaNegocio,
|
|
480
|
+
};
|
|
481
|
+
const fiql = buildFiql({ and: [...dateGroups, scalarGroup] });
|
|
482
|
+
appendRquery(url, fiql);
|
|
483
|
+
const path = url.pathname + (url.search ? url.search : '');
|
|
484
|
+
const data = await this.get(path);
|
|
485
|
+
return { items: data.items, total: Number(data.total) };
|
|
486
|
+
}
|
|
487
|
+
// ---------------------------------------------------------------------------
|
|
488
|
+
// Cartera de clientes (v0.5.0)
|
|
489
|
+
// ---------------------------------------------------------------------------
|
|
490
|
+
/**
|
|
491
|
+
* Lista paginada de documentos de cartera de clientes con filtros FIQL.
|
|
492
|
+
*
|
|
493
|
+
* Endpoint: GET /pcar/v1/cartera-clientes
|
|
494
|
+
*
|
|
495
|
+
* Construye la FIQL a partir de los filtros tipados y la añade como `rquery`
|
|
496
|
+
* usando `appendRquery()` (patrón canónico del foundation TD-117).
|
|
497
|
+
*
|
|
498
|
+
* El filtro `soloImpagados=true` genera `CARCL_FECIMPAG!=null`, donde el
|
|
499
|
+
* valor `'null'` es la convención centinela de Freemática para indicar "sin
|
|
500
|
+
* valor" en campos de fecha opcionales (assumption empírica: observado en
|
|
501
|
+
* datos reales de la API; documentado para futuras implementaciones).
|
|
502
|
+
* El operador `ne` se pasa vía `buildFiql` para garantizar escaping
|
|
503
|
+
* consistente con el resto de filtros.
|
|
504
|
+
*
|
|
505
|
+
* El filtro `estado` mapea a los valores numéricos de `CARCL_SITCAR`
|
|
506
|
+
* (1=pendiente, 2=cancelado, 3=derivado).
|
|
507
|
+
*
|
|
508
|
+
* @param opts - Filtros tipados del schema `ListCarteraFiltersSchema`.
|
|
509
|
+
* @returns Lista paginada de documentos de cartera.
|
|
510
|
+
*/
|
|
511
|
+
async listCarteraClientes(opts = {}) {
|
|
512
|
+
const url = new URL('/pcar/v1/cartera-clientes', 'http://x');
|
|
513
|
+
if (opts.items !== undefined)
|
|
514
|
+
url.searchParams.set('items', String(opts.items));
|
|
515
|
+
if (opts.page !== undefined)
|
|
516
|
+
url.searchParams.set('page', String(opts.page));
|
|
517
|
+
// Build FIQL parts. Each scalar filter is a separate buildFiql call so that
|
|
518
|
+
// two range constraints on the same field (e.g. CARCL_FECDOC =ge= X and
|
|
519
|
+
// CARCL_FECDOC =le= Y) don't overwrite each other in a single object.
|
|
520
|
+
const parts = [];
|
|
521
|
+
// Equality filters (all go into one group → joined with AND)
|
|
522
|
+
const eqGroup = {};
|
|
523
|
+
if (opts.empresa !== undefined)
|
|
524
|
+
eqGroup['CARCL_EMP'] = opts.empresa;
|
|
525
|
+
if (opts.codCliente !== undefined)
|
|
526
|
+
eqGroup['CARCL_CODAUX'] = opts.codCliente;
|
|
527
|
+
if (opts.grupoCliente !== undefined)
|
|
528
|
+
eqGroup['CARCL_GRUPAUX'] = opts.grupoCliente;
|
|
529
|
+
if (opts.representante !== undefined)
|
|
530
|
+
eqGroup['CARCL_CODREP'] = opts.representante;
|
|
531
|
+
if (opts.formaPago !== undefined)
|
|
532
|
+
eqGroup['CARCL_CODFPAG'] = opts.formaPago;
|
|
533
|
+
if (opts.modoPago !== undefined)
|
|
534
|
+
eqGroup['CARCL_CODMPAG'] = opts.modoPago;
|
|
535
|
+
if (opts.estado !== undefined)
|
|
536
|
+
eqGroup['CARCL_SITCAR'] = ESTADO_CARTERA_FIQL_MAP[opts.estado];
|
|
537
|
+
if (opts.referencia !== undefined)
|
|
538
|
+
eqGroup['CARCL_REFCAR'] = opts.referencia;
|
|
539
|
+
const eqFiql = buildFiql(eqGroup);
|
|
540
|
+
if (eqFiql)
|
|
541
|
+
parts.push(eqFiql);
|
|
542
|
+
// Range filters: each gets its own buildFiql call to preserve both endpoints
|
|
543
|
+
if (opts.fechaDocDesde !== undefined)
|
|
544
|
+
parts.push(buildFiql({ CARCL_FECDOC: { op: 'ge', value: opts.fechaDocDesde } }));
|
|
545
|
+
if (opts.fechaDocHasta !== undefined)
|
|
546
|
+
parts.push(buildFiql({ CARCL_FECDOC: { op: 'le', value: opts.fechaDocHasta } }));
|
|
547
|
+
if (opts.fechaVencimientoDesde !== undefined)
|
|
548
|
+
parts.push(buildFiql({ CARCL_FECVCTO: { op: 'ge', value: opts.fechaVencimientoDesde } }));
|
|
549
|
+
if (opts.fechaVencimientoHasta !== undefined)
|
|
550
|
+
parts.push(buildFiql({ CARCL_FECVCTO: { op: 'le', value: opts.fechaVencimientoHasta } }));
|
|
551
|
+
// soloImpagados: 'null' es el centinela de Freemática para fecha sin valor.
|
|
552
|
+
// Se pasa por buildFiql (op 'ne') igual que el resto de filtros para
|
|
553
|
+
// garantizar escaping uniforme y legibilidad del código.
|
|
554
|
+
if (opts.soloImpagados === true) {
|
|
555
|
+
parts.push(buildFiql({ CARCL_FECIMPAG: { op: 'ne', value: 'null' } }));
|
|
556
|
+
}
|
|
557
|
+
appendRquery(url, parts.join(';'));
|
|
558
|
+
const path = url.pathname + (url.search ? url.search : '');
|
|
559
|
+
const data = await this.get(path);
|
|
560
|
+
return { items: data.items, total: Number(data.total) };
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Detalle de una factura de compra por `idReg` opaco.
|
|
564
|
+
*
|
|
565
|
+
* Endpoint: GET /pcmp/v2/facturas-compras/{idReg}
|
|
566
|
+
*/
|
|
567
|
+
async getFacturaCompra(idReg) {
|
|
568
|
+
return this.get(`/pcmp/v2/facturas-compras/${encodeURIComponent(idReg)}`);
|
|
569
|
+
}
|
|
570
|
+
// ---------------------------------------------------------------------------
|
|
571
|
+
// Pedidos de compra (v0.5.1)
|
|
572
|
+
// ---------------------------------------------------------------------------
|
|
573
|
+
/**
|
|
574
|
+
* Lista paginada de pedidos de compra con diseño mixto de filtros.
|
|
575
|
+
*
|
|
576
|
+
* Endpoint: GET /pcmp/v2/pedidos
|
|
577
|
+
*
|
|
578
|
+
* DISEÑO MIXTO: el endpoint combina 4 query params nativos con filtros FIQL
|
|
579
|
+
* adicionales vía `rquery`.
|
|
580
|
+
*
|
|
581
|
+
* Filtros que van como **query params nativos** (NO FIQL):
|
|
582
|
+
* - `empresa` (4c) → `codEmpresa`
|
|
583
|
+
* - `codProveedor` (≤10c) → `codProveedor`
|
|
584
|
+
* - `fechaPedidoDesde` (ISO date) → `desdeFecha`
|
|
585
|
+
* - `fechaPedidoHasta` (ISO date) → `hastaFecha`
|
|
586
|
+
*
|
|
587
|
+
* Filtros que van como **FIQL en `rquery`** (campos `ALCC_*`):
|
|
588
|
+
* - `numPedido` → `ALCC_NUMDOC==N`
|
|
589
|
+
* - `codDocumento` → `ALCC_CODDOC==XXXX`
|
|
590
|
+
* - `delegacion` → `ALCC_DELEG==XXXX`
|
|
591
|
+
* - `formaPago` → `ALCC_FPAGO==XXX`
|
|
592
|
+
* - `tipoIva` → `ALCC_TIPO_IVA==XXXX`
|
|
593
|
+
* - `codCliente` → `ALCC_COD_CLIENTE==X`
|
|
594
|
+
* - `codInstalador` → `ALCC_COD_INSTALADOR==X`
|
|
595
|
+
* - `codMantenedor` → `ALCC_COD_MANTENEDOR==X`
|
|
596
|
+
* - `fechaEntregaDesde` → `ALCC_FCHENTREGA=ge=YYYY-MM-DD`
|
|
597
|
+
* - `fechaEntregaHasta` → `ALCC_FCHENTREGA=le=YYYY-MM-DD`
|
|
598
|
+
* - `referencia` → `ALCC_REFERENCIA==X`
|
|
599
|
+
* - `estado` → expresión compuesta sobre `ALCC_PED_BLOQ` y `ALCC_PED_RECIB`
|
|
600
|
+
* (ver `buildEstadoPedidoFiql` y comentario empírico en `EstadoPedidoEnum`)
|
|
601
|
+
*
|
|
602
|
+
* @param opts - Filtros tipados del schema `ListPedidosCompraFiltersSchema`.
|
|
603
|
+
* @returns Lista paginada de pedidos de compra.
|
|
604
|
+
*/
|
|
605
|
+
async listPedidosCompra(opts = {}) {
|
|
606
|
+
const url = new URL('https://placeholder/pcmp/v2/pedidos');
|
|
607
|
+
// Paginación
|
|
608
|
+
if (opts.items !== undefined)
|
|
609
|
+
url.searchParams.set('items', String(opts.items));
|
|
610
|
+
if (opts.page !== undefined)
|
|
611
|
+
url.searchParams.set('page', String(opts.page));
|
|
612
|
+
// Query params nativos (NO FIQL)
|
|
613
|
+
if (opts.empresa !== undefined)
|
|
614
|
+
url.searchParams.set('codEmpresa', opts.empresa);
|
|
615
|
+
if (opts.codProveedor !== undefined)
|
|
616
|
+
url.searchParams.set('codProveedor', opts.codProveedor);
|
|
617
|
+
if (opts.fechaPedidoDesde !== undefined)
|
|
618
|
+
url.searchParams.set('desdeFecha', opts.fechaPedidoDesde);
|
|
619
|
+
if (opts.fechaPedidoHasta !== undefined)
|
|
620
|
+
url.searchParams.set('hastaFecha', opts.fechaPedidoHasta);
|
|
621
|
+
// Filtros FIQL — cada parte se genera por separado y se une con ";"
|
|
622
|
+
const fiqlParts = [];
|
|
623
|
+
// Scalar equality filters: agrupar en un objeto y construir de una vez
|
|
624
|
+
const eqGroup = {};
|
|
625
|
+
if (opts.codDocumento !== undefined)
|
|
626
|
+
eqGroup['ALCC_CODDOC'] = opts.codDocumento;
|
|
627
|
+
if (opts.delegacion !== undefined)
|
|
628
|
+
eqGroup['ALCC_DELEG'] = opts.delegacion;
|
|
629
|
+
if (opts.formaPago !== undefined)
|
|
630
|
+
eqGroup['ALCC_FPAGO'] = opts.formaPago;
|
|
631
|
+
if (opts.tipoIva !== undefined)
|
|
632
|
+
eqGroup['ALCC_TIPO_IVA'] = opts.tipoIva;
|
|
633
|
+
if (opts.codCliente !== undefined)
|
|
634
|
+
eqGroup['ALCC_COD_CLIENTE'] = opts.codCliente;
|
|
635
|
+
if (opts.codInstalador !== undefined)
|
|
636
|
+
eqGroup['ALCC_COD_INSTALADOR'] = opts.codInstalador;
|
|
637
|
+
if (opts.codMantenedor !== undefined)
|
|
638
|
+
eqGroup['ALCC_COD_MANTENEDOR'] = opts.codMantenedor;
|
|
639
|
+
if (opts.referencia !== undefined)
|
|
640
|
+
eqGroup['ALCC_REFERENCIA'] = opts.referencia;
|
|
641
|
+
// numPedido es number → convertir a string para FIQL
|
|
642
|
+
if (opts.numPedido !== undefined)
|
|
643
|
+
eqGroup['ALCC_NUMDOC'] = String(opts.numPedido);
|
|
644
|
+
const eqFiql = buildFiql(eqGroup);
|
|
645
|
+
if (eqFiql)
|
|
646
|
+
fiqlParts.push(eqFiql);
|
|
647
|
+
// Date range filters for fechaEntrega (same field, two possible operators)
|
|
648
|
+
if (opts.fechaEntregaDesde !== undefined) {
|
|
649
|
+
fiqlParts.push(buildFiql({ ALCC_FCHENTREGA: { op: 'ge', value: opts.fechaEntregaDesde } }));
|
|
650
|
+
}
|
|
651
|
+
if (opts.fechaEntregaHasta !== undefined) {
|
|
652
|
+
fiqlParts.push(buildFiql({ ALCC_FCHENTREGA: { op: 'le', value: opts.fechaEntregaHasta } }));
|
|
653
|
+
}
|
|
654
|
+
// Estado — genera expresión FIQL compuesta sobre ALCC_PED_BLOQ + ALCC_PED_RECIB
|
|
655
|
+
if (opts.estado !== undefined) {
|
|
656
|
+
fiqlParts.push(buildEstadoPedidoFiql(opts.estado));
|
|
657
|
+
}
|
|
658
|
+
appendRquery(url, fiqlParts.join(';'));
|
|
659
|
+
const path = url.pathname + (url.search ? url.search : '');
|
|
660
|
+
const data = await this.get(path);
|
|
661
|
+
return { items: data.items, total: Number(data.total) };
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* Detalle de un pedido de compra por `idReg` opaco.
|
|
665
|
+
*
|
|
666
|
+
* Endpoint: GET /pcmp/v2/pedidos/{idreg}
|
|
667
|
+
*
|
|
668
|
+
* La respuesta es un objeto compuesto `{ VoPedidosCompraCab, cabecera_proveedor, lineas[] }`
|
|
669
|
+
* que se devuelve sin aplanar para conservar la estructura original del API.
|
|
670
|
+
*
|
|
671
|
+
* @param idReg - Identificador opaco (puede contener caracteres como "==" de base64).
|
|
672
|
+
* @returns Objeto compuesto con la cabecera, datos del proveedor y líneas del pedido.
|
|
673
|
+
*/
|
|
674
|
+
async getPedidoCompra(idReg) {
|
|
675
|
+
return this.get(`/pcmp/v2/pedidos/${encodeURIComponent(idReg)}`);
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* Detalle de un documento de cartera de clientes por `idReg` opaco.
|
|
679
|
+
*
|
|
680
|
+
* Endpoint: GET /pcar/v1/cartera-clientes/{idreg}
|
|
681
|
+
*
|
|
682
|
+
* @param idReg - Identificador opaco (base64) del documento.
|
|
683
|
+
* @returns Documento de cartera completo.
|
|
684
|
+
*/
|
|
685
|
+
async getCarteraCliente(idReg) {
|
|
686
|
+
return this.get(`/pcar/v1/cartera-clientes/${encodeURIComponent(idReg)}`);
|
|
687
|
+
}
|
|
688
|
+
// ---------------------------------------------------------------------------
|
|
689
|
+
// Proveedores (v0.5.0)
|
|
690
|
+
// ---------------------------------------------------------------------------
|
|
691
|
+
/**
|
|
692
|
+
* Lista paginada de proveedores con filtros FIQL.
|
|
693
|
+
*
|
|
694
|
+
* El filtro `activo` mapea a FECHA_BAJA:
|
|
695
|
+
* - `true` → FECHA_BAJA es nula (proveedor activo). Se usa el operador `=is-null=true` si el
|
|
696
|
+
* API lo soporta; como fallback se omite el filtro y se filtra post-process.
|
|
697
|
+
* En Freemática el convenio estándar es: activo=true → `FECHA_BAJA==null` (FIQL `==null`).
|
|
698
|
+
* - `false` → FECHA_BAJA tiene valor (proveedor de baja).
|
|
699
|
+
*
|
|
700
|
+
* El filtro `nombre` usa operador FIQL `=lk=` para búsqueda parcial (LIKE).
|
|
701
|
+
*
|
|
702
|
+
* Endpoint: GET /pgrl/v2/proveedores
|
|
703
|
+
*/
|
|
704
|
+
async listProveedores(opts = {}) {
|
|
705
|
+
const url = new URL('https://placeholder/pgrl/v2/proveedores');
|
|
706
|
+
if (opts.items !== undefined)
|
|
707
|
+
url.searchParams.set('items', String(opts.items));
|
|
708
|
+
if (opts.page !== undefined)
|
|
709
|
+
url.searchParams.set('page', String(opts.page));
|
|
710
|
+
// Construir filtros FIQL — activo se traduce a FECHA_BAJA
|
|
711
|
+
const filters = {
|
|
712
|
+
COD_PRO: opts.codProveedor,
|
|
713
|
+
COD_GRUPO_PRO: opts.grupoProveedor,
|
|
714
|
+
NIF: opts.nif,
|
|
715
|
+
CMP_TIPO_IDENT: opts.tipoIdent,
|
|
716
|
+
COD_PROVINCIA: opts.codProvincia,
|
|
717
|
+
COD_PAIS: opts.codPais,
|
|
718
|
+
};
|
|
719
|
+
if (opts.nombre !== undefined) {
|
|
720
|
+
filters['NOMBRE_PRO'] = { op: 'lk', value: opts.nombre };
|
|
721
|
+
}
|
|
722
|
+
if (opts.activo === true) {
|
|
723
|
+
// Activos: FECHA_BAJA nula. FIQL: FECHA_BAJA==null
|
|
724
|
+
filters['FECHA_BAJA'] = 'null';
|
|
725
|
+
}
|
|
726
|
+
else if (opts.activo === false) {
|
|
727
|
+
// Dados de baja: FECHA_BAJA tiene valor (not null). FIQL: FECHA_BAJA!=null
|
|
728
|
+
filters['FECHA_BAJA'] = { op: 'ne', value: 'null' };
|
|
729
|
+
}
|
|
730
|
+
const fiql = buildFiql(filters);
|
|
731
|
+
appendRquery(url, fiql);
|
|
732
|
+
const path = url.pathname + (url.search ? url.search : '');
|
|
733
|
+
const data = await this.get(path);
|
|
734
|
+
return { items: data.items, total: Number(data.total) };
|
|
735
|
+
}
|
|
736
|
+
// ---------------------------------------------------------------------------
|
|
737
|
+
// Facturas de ventas (v0.5.0)
|
|
738
|
+
// ---------------------------------------------------------------------------
|
|
739
|
+
/**
|
|
740
|
+
* Lista paginada de cabeceras de facturas de ventas con filtros FIQL.
|
|
741
|
+
*
|
|
742
|
+
* Endpoint: GET /pven/v1/facturas-cabecera
|
|
743
|
+
*
|
|
744
|
+
* @param opts - Filtros tipados del schema `ListFacturasCabeceraFiltersSchema`.
|
|
745
|
+
* @returns Lista paginada de cabeceras de facturas.
|
|
746
|
+
*/
|
|
747
|
+
async listFacturasCabecera(opts = {}) {
|
|
748
|
+
const url = new URL('/pven/v1/facturas-cabecera', 'http://x');
|
|
749
|
+
if (opts.items !== undefined)
|
|
750
|
+
url.searchParams.set('items', String(opts.items));
|
|
751
|
+
if (opts.page !== undefined)
|
|
752
|
+
url.searchParams.set('page', String(opts.page));
|
|
753
|
+
const fiqlParts = [];
|
|
754
|
+
// Standard equality filters
|
|
755
|
+
const eqGroup = {};
|
|
756
|
+
if (opts.empresa !== undefined)
|
|
757
|
+
eqGroup['FVC_EMP'] = opts.empresa;
|
|
758
|
+
if (opts.codCliente !== undefined)
|
|
759
|
+
eqGroup['FVC_CODAUX'] = opts.codCliente;
|
|
760
|
+
if (opts.representante !== undefined)
|
|
761
|
+
eqGroup['FVC_CODREP'] = opts.representante;
|
|
762
|
+
if (opts.serie !== undefined)
|
|
763
|
+
eqGroup['FVC_SERFAC'] = opts.serie;
|
|
764
|
+
if (opts.numFactura !== undefined)
|
|
765
|
+
eqGroup['FVC_NUMFAC'] = opts.numFactura;
|
|
766
|
+
if (opts.formaPago !== undefined)
|
|
767
|
+
eqGroup['FVC_CODFPAG'] = opts.formaPago;
|
|
768
|
+
if (opts.delegacion !== undefined)
|
|
769
|
+
eqGroup['FVC_DELEG'] = opts.delegacion;
|
|
770
|
+
const eqFiql = buildFiql(eqGroup);
|
|
771
|
+
if (eqFiql)
|
|
772
|
+
fiqlParts.push(eqFiql);
|
|
773
|
+
// Date range filters
|
|
774
|
+
if (opts.fechaFacturaDesde !== undefined)
|
|
775
|
+
fiqlParts.push(buildFiql({ FVC_FECFAC: { op: 'ge', value: opts.fechaFacturaDesde } }));
|
|
776
|
+
if (opts.fechaFacturaHasta !== undefined)
|
|
777
|
+
fiqlParts.push(buildFiql({ FVC_FECFAC: { op: 'le', value: opts.fechaFacturaHasta } }));
|
|
778
|
+
// Boolean: traspasadoContabilidad
|
|
779
|
+
if (opts.traspasadoContabilidad !== undefined) {
|
|
780
|
+
fiqlParts.push(buildFiql({ FVC_TRSCONT: opts.traspasadoContabilidad ? 'S' : 'N' }));
|
|
781
|
+
}
|
|
782
|
+
appendRquery(url, fiqlParts.join(';'));
|
|
783
|
+
const path = url.pathname + (url.search ? url.search : '');
|
|
784
|
+
const data = await this.get(path);
|
|
785
|
+
return { items: data.items, total: Number(data.total) };
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* Detalle de un proveedor por `idReg` opaco.
|
|
789
|
+
*
|
|
790
|
+
* Endpoint: GET /pgrl/v2/proveedores/{idReg}
|
|
791
|
+
*/
|
|
792
|
+
async getProveedor(idReg) {
|
|
793
|
+
return this.get(`/pgrl/v2/proveedores/${encodeURIComponent(idReg)}`);
|
|
794
|
+
}
|
|
795
|
+
// ---------------------------------------------------------------------------
|
|
796
|
+
// Localizaciones (v0.5.0)
|
|
797
|
+
// ---------------------------------------------------------------------------
|
|
798
|
+
/**
|
|
799
|
+
* Lista paginada de localizaciones de cobro de clientes.
|
|
800
|
+
*
|
|
801
|
+
* Filtros soportados: codCliente (COD_CLI), grupoCliente (GRUPO_CLI), formaPago (COD_FORMA_COBRO).
|
|
802
|
+
*
|
|
803
|
+
* Endpoint: GET /pgrl/v2/localizaciones-cobro-clientes
|
|
804
|
+
*/
|
|
805
|
+
async listLocalizacionesCobroClientes(opts = {}) {
|
|
806
|
+
return this.listResourceWithFiql('/pgrl/v2/localizaciones-cobro-clientes', opts, {
|
|
807
|
+
COD_CLI: opts.codCliente,
|
|
808
|
+
GRUPO_CLI: opts.grupoCliente,
|
|
809
|
+
COD_FORMA_COBRO: opts.formaPago,
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* Lista paginada de localizaciones de pago de proveedores.
|
|
814
|
+
*
|
|
815
|
+
* Filtros soportados: codProveedor (COD_PRO), grupoProveedor (COD_GRUPO_PRO), formaPago (COD_FORMA_PAGO).
|
|
816
|
+
*
|
|
817
|
+
* Endpoint: GET /pgrl/v2/localizaciones-pago-proveedores
|
|
818
|
+
*/
|
|
819
|
+
async listLocalizacionesPagoProveedores(opts = {}) {
|
|
820
|
+
return this.listResourceWithFiql('/pgrl/v2/localizaciones-pago-proveedores', opts, {
|
|
821
|
+
COD_PRO: opts.codProveedor,
|
|
822
|
+
COD_GRUPO_PRO: opts.grupoProveedor,
|
|
823
|
+
COD_FORMA_PAGO: opts.formaPago,
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
/**
|
|
827
|
+
* Lista paginada de localizaciones de servicio de clientes.
|
|
828
|
+
*
|
|
829
|
+
* Filtros soportados: codCliente (COD_CLI), grupoCliente (GRUPO_CLI), codPais (COD_PAIS),
|
|
830
|
+
* codProvincia (COD_PROVINCIA), representante (COD_REPRES), activo (FECHA_BAJA nulo/no nulo).
|
|
831
|
+
*
|
|
832
|
+
* Endpoint: GET /pgrl/v2/localizaciones-servicio-clientes
|
|
833
|
+
*/
|
|
834
|
+
async listLocalizacionesServicioClientes(opts = {}) {
|
|
835
|
+
const fiqlFilters = {
|
|
836
|
+
COD_CLI: opts.codCliente,
|
|
837
|
+
GRUPO_CLI: opts.grupoCliente,
|
|
838
|
+
COD_PAIS: opts.codPais,
|
|
839
|
+
COD_PROVINCIA: opts.codProvincia,
|
|
840
|
+
COD_REPRES: opts.representante,
|
|
841
|
+
};
|
|
842
|
+
if (opts.activo === true) {
|
|
843
|
+
fiqlFilters['FECHA_BAJA'] = 'null';
|
|
844
|
+
}
|
|
845
|
+
else if (opts.activo === false) {
|
|
846
|
+
fiqlFilters['FECHA_BAJA'] = { op: 'ne', value: 'null' };
|
|
847
|
+
}
|
|
848
|
+
return this.listResourceWithFiql('/pgrl/v2/localizaciones-servicio-clientes', opts, fiqlFilters);
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* Detalle de una factura de ventas (cabecera) por `idReg` opaco.
|
|
852
|
+
*
|
|
853
|
+
* Endpoint: GET /pven/v1/facturas-cabecera/{idreg}
|
|
854
|
+
*
|
|
855
|
+
* @param idReg - Identificador opaco (base64) de la factura.
|
|
856
|
+
* @returns Factura cabecera completa.
|
|
857
|
+
*/
|
|
858
|
+
async getFacturaCabecera(idReg) {
|
|
859
|
+
return this.get(`/pven/v1/facturas-cabecera/${encodeURIComponent(idReg)}`);
|
|
860
|
+
}
|
|
861
|
+
/**
|
|
862
|
+
* Lista paginada de líneas de una factura de ventas con filtros FIQL.
|
|
863
|
+
*
|
|
864
|
+
* Endpoint: GET /pven/v1/facturas-cabecera/{idreg}/lineas
|
|
865
|
+
*
|
|
866
|
+
* @param idReg - idReg opaco de la factura cabecera.
|
|
867
|
+
* @param opts - Filtros tipados del schema `ListFacturaLineasFiltersSchema`.
|
|
868
|
+
* @returns Lista paginada de líneas de factura.
|
|
869
|
+
*/
|
|
870
|
+
async listFacturaLineas(idReg, opts = {}) {
|
|
871
|
+
const url = new URL(`/pven/v1/facturas-cabecera/${encodeURIComponent(idReg)}/lineas`, 'http://x');
|
|
872
|
+
if (opts.items !== undefined)
|
|
873
|
+
url.searchParams.set('items', String(opts.items));
|
|
874
|
+
if (opts.page !== undefined)
|
|
875
|
+
url.searchParams.set('page', String(opts.page));
|
|
876
|
+
const fiqlGroup = {};
|
|
877
|
+
if (opts.codArticulo !== undefined)
|
|
878
|
+
fiqlGroup['FVL_CODART'] = opts.codArticulo;
|
|
879
|
+
if (opts.codFamilia !== undefined)
|
|
880
|
+
fiqlGroup['FVL_CODFAM'] = opts.codFamilia;
|
|
881
|
+
if (opts.codSubfamilia !== undefined)
|
|
882
|
+
fiqlGroup['FVL_CODSFAM'] = opts.codSubfamilia;
|
|
883
|
+
if (opts.delegacion !== undefined)
|
|
884
|
+
fiqlGroup['FVL_DELEG'] = opts.delegacion;
|
|
885
|
+
appendRquery(url, buildFiql(fiqlGroup));
|
|
886
|
+
const path = url.pathname + (url.search ? url.search : '');
|
|
887
|
+
const data = await this.get(path);
|
|
888
|
+
return { items: data.items, total: Number(data.total) };
|
|
889
|
+
}
|
|
890
|
+
/**
|
|
891
|
+
* Lista paginada de líneas de IVA de una factura de ventas con filtros FIQL.
|
|
892
|
+
*
|
|
893
|
+
* Endpoint: GET /pven/v1/facturas-cabecera/{idreg}/iva
|
|
894
|
+
*
|
|
895
|
+
* @param idReg - idReg opaco de la factura cabecera.
|
|
896
|
+
* @param opts - Filtros tipados del schema `ListFacturaIvaFiltersSchema`.
|
|
897
|
+
* @returns Lista paginada de líneas de IVA.
|
|
898
|
+
*/
|
|
899
|
+
async listFacturaIva(idReg, opts = {}) {
|
|
900
|
+
const url = new URL(`/pven/v1/facturas-cabecera/${encodeURIComponent(idReg)}/iva`, 'http://x');
|
|
901
|
+
if (opts.items !== undefined)
|
|
902
|
+
url.searchParams.set('items', String(opts.items));
|
|
903
|
+
if (opts.page !== undefined)
|
|
904
|
+
url.searchParams.set('page', String(opts.page));
|
|
905
|
+
const fiqlGroup = {};
|
|
906
|
+
if (opts.tipoIva !== undefined)
|
|
907
|
+
fiqlGroup['FVI_TIPIVA'] = opts.tipoIva;
|
|
908
|
+
appendRquery(url, buildFiql(fiqlGroup));
|
|
909
|
+
const path = url.pathname + (url.search ? url.search : '');
|
|
910
|
+
const data = await this.get(path);
|
|
911
|
+
return { items: data.items, total: Number(data.total) };
|
|
912
|
+
}
|
|
913
|
+
/**
|
|
914
|
+
* Lista paginada de vencimientos de una factura de ventas con filtros FIQL.
|
|
915
|
+
*
|
|
916
|
+
* Endpoint: GET /pven/v1/facturas-cabecera/{idreg}/vencimientos
|
|
917
|
+
*
|
|
918
|
+
* @param idReg - idReg opaco de la factura cabecera.
|
|
919
|
+
* @param opts - Filtros tipados del schema `ListFacturaVencimientosFiltersSchema`.
|
|
920
|
+
* @returns Lista paginada de vencimientos.
|
|
921
|
+
*/
|
|
922
|
+
async listFacturaVencimientos(idReg, opts = {}) {
|
|
923
|
+
const url = new URL(`/pven/v1/facturas-cabecera/${encodeURIComponent(idReg)}/vencimientos`, 'http://x');
|
|
924
|
+
if (opts.items !== undefined)
|
|
925
|
+
url.searchParams.set('items', String(opts.items));
|
|
926
|
+
if (opts.page !== undefined)
|
|
927
|
+
url.searchParams.set('page', String(opts.page));
|
|
928
|
+
const fiqlParts = [];
|
|
929
|
+
if (opts.modoPago !== undefined)
|
|
930
|
+
fiqlParts.push(buildFiql({ FVV_CODMPAG: opts.modoPago }));
|
|
931
|
+
if (opts.fechaVencimientoDesde !== undefined)
|
|
932
|
+
fiqlParts.push(buildFiql({ FVV_FECVCTO: { op: 'ge', value: opts.fechaVencimientoDesde } }));
|
|
933
|
+
if (opts.fechaVencimientoHasta !== undefined)
|
|
934
|
+
fiqlParts.push(buildFiql({ FVV_FECVCTO: { op: 'le', value: opts.fechaVencimientoHasta } }));
|
|
935
|
+
appendRquery(url, fiqlParts.join(';'));
|
|
936
|
+
const path = url.pathname + (url.search ? url.search : '');
|
|
937
|
+
const data = await this.get(path);
|
|
938
|
+
return { items: data.items, total: Number(data.total) };
|
|
939
|
+
}
|
|
940
|
+
// ---------------------------------------------------------------------------
|
|
941
|
+
// Facturas electrónicas (Facturae/EDICOM/FACe) (v0.5.1 / TD-154)
|
|
942
|
+
// ---------------------------------------------------------------------------
|
|
943
|
+
/**
|
|
944
|
+
* Lista paginada de facturas electrónicas con filtros nativos (NO FIQL).
|
|
945
|
+
*
|
|
946
|
+
* Endpoint: GET /pven/v1/facturas
|
|
947
|
+
*
|
|
948
|
+
* Diferente de listFacturasCabecera: expone las facturas firmadas/enviadas vía
|
|
949
|
+
* Facturae, EDICOM y/o FACe. Los filtros son query params nativos del API.
|
|
950
|
+
*
|
|
951
|
+
* @param opts - Filtros nativos: empresa, codCliente, fechaDesde/Hasta, estado, leido + paginación.
|
|
952
|
+
* @returns Lista paginada de facturas electrónicas con campos FACED_*.
|
|
953
|
+
*/
|
|
954
|
+
async listFacturasElectronicas(opts) {
|
|
955
|
+
const url = new URL('https://placeholder/pven/v1/facturas');
|
|
956
|
+
if (opts.items !== undefined)
|
|
957
|
+
url.searchParams.set('items', String(opts.items));
|
|
958
|
+
if (opts.page !== undefined)
|
|
959
|
+
url.searchParams.set('page', String(opts.page));
|
|
960
|
+
if (opts.empresa !== undefined)
|
|
961
|
+
url.searchParams.set('empresa', opts.empresa);
|
|
962
|
+
if (opts.codCliente !== undefined)
|
|
963
|
+
url.searchParams.set('cliente', opts.codCliente);
|
|
964
|
+
if (opts.fechaDesde !== undefined)
|
|
965
|
+
url.searchParams.set('fechaIni', opts.fechaDesde);
|
|
966
|
+
if (opts.fechaHasta !== undefined)
|
|
967
|
+
url.searchParams.set('fechaFin', opts.fechaHasta);
|
|
968
|
+
if (opts.estado !== undefined)
|
|
969
|
+
url.searchParams.set('estado', opts.estado);
|
|
970
|
+
if (opts.leido !== undefined)
|
|
971
|
+
url.searchParams.set('leido', opts.leido ? '1' : '0');
|
|
972
|
+
if (opts.order !== undefined)
|
|
973
|
+
url.searchParams.set('order', opts.order);
|
|
974
|
+
const path = url.pathname + (url.search ? url.search : '');
|
|
975
|
+
const data = await this.get(path);
|
|
976
|
+
return { items: data.items, total: Number(data.total) };
|
|
977
|
+
}
|
|
978
|
+
// ---------------------------------------------------------------------------
|
|
979
|
+
// Albaranes de ventas (v0.5.1 / TD-155)
|
|
980
|
+
// ---------------------------------------------------------------------------
|
|
981
|
+
/**
|
|
982
|
+
* Lista paginada de albaranes de ventas con filtros nativos del endpoint.
|
|
983
|
+
*
|
|
984
|
+
* Endpoint: GET /pven/v2/albaranes-ventas
|
|
985
|
+
*
|
|
986
|
+
* El endpoint usa exclusivamente query params nativos para los filtros
|
|
987
|
+
* principales (no FIQL). `codEmpresa` es obligatorio según el spec OpenAPI.
|
|
988
|
+
*/
|
|
989
|
+
async listAlbaranesVentas(opts) {
|
|
990
|
+
const url = new URL('https://placeholder/pven/v2/albaranes-ventas');
|
|
991
|
+
if (opts.items !== undefined)
|
|
992
|
+
url.searchParams.set('items', String(opts.items));
|
|
993
|
+
if (opts.page !== undefined)
|
|
994
|
+
url.searchParams.set('page', String(opts.page));
|
|
995
|
+
url.searchParams.set('codEmpresa', opts.empresa);
|
|
996
|
+
if (opts.delegacion !== undefined)
|
|
997
|
+
url.searchParams.set('codDelegacion', opts.delegacion);
|
|
998
|
+
if (opts.codCliente !== undefined)
|
|
999
|
+
url.searchParams.set('codCliente', opts.codCliente);
|
|
1000
|
+
if (opts.codDocumento !== undefined)
|
|
1001
|
+
url.searchParams.set('codDocumento', opts.codDocumento);
|
|
1002
|
+
if (opts.fechaDesde !== undefined)
|
|
1003
|
+
url.searchParams.set('desdeFecha', opts.fechaDesde);
|
|
1004
|
+
if (opts.fechaHasta !== undefined)
|
|
1005
|
+
url.searchParams.set('hastaFecha', opts.fechaHasta);
|
|
1006
|
+
if (opts.order !== undefined)
|
|
1007
|
+
url.searchParams.set('order', opts.order);
|
|
1008
|
+
const path = url.pathname + (url.search ? url.search : '');
|
|
1009
|
+
const data = await this.get(path);
|
|
1010
|
+
return { items: data.items, total: Number(data.total) };
|
|
1011
|
+
}
|
|
1012
|
+
/**
|
|
1013
|
+
* Detalle de una factura electrónica por `idReg` opaco.
|
|
1014
|
+
*
|
|
1015
|
+
* Endpoint: GET /pven/v1/facturas/{idreg}
|
|
1016
|
+
*
|
|
1017
|
+
* @param idReg - Identificador opaco (base64) de la factura electrónica.
|
|
1018
|
+
* @returns Objeto con los campos de la factura electrónica.
|
|
1019
|
+
*/
|
|
1020
|
+
async getFacturaElectronica(idReg) {
|
|
1021
|
+
return this.get(`/pven/v1/facturas/${encodeURIComponent(idReg)}`);
|
|
1022
|
+
}
|
|
1023
|
+
/**
|
|
1024
|
+
* Recupera el documento (PDF o XML Facturae firmado) de una factura electrónica.
|
|
1025
|
+
*
|
|
1026
|
+
* Endpoint: GET /pven/v1/facturas/{idreg}/documento
|
|
1027
|
+
*
|
|
1028
|
+
* La API devuelve un JSON (VoFacturasDocumento) con los documentos codificados en base64
|
|
1029
|
+
* en los campos FACED_DOCUMENTO_PDF, FACED_DOCUMENTO_XML, etc.
|
|
1030
|
+
*
|
|
1031
|
+
* Si el tamaño del JSON supera FREEMATICA_MAX_RESPONSE_SIZE_MB, la tool aplica el
|
|
1032
|
+
* guardrail de tamaño y devuelve `{ truncated: true, warning, sizeBytes }` sin el cuerpo.
|
|
1033
|
+
*
|
|
1034
|
+
* @param idReg - Identificador opaco de la factura.
|
|
1035
|
+
* @param opts - Opciones: documentType (tipo de documento), actualizaLeido (marcar como leído).
|
|
1036
|
+
* @returns Objeto VoFacturasDocumento con campos FACED_DOCUMENTO_*.
|
|
1037
|
+
*/
|
|
1038
|
+
async getFacturaDocumento(idReg, opts = {}) {
|
|
1039
|
+
const maxMb = loadMaxResponseSizeMb();
|
|
1040
|
+
const maxBytes = maxMb * 1024 * 1024;
|
|
1041
|
+
const url = new URL(`https://placeholder/pven/v1/facturas/${encodeURIComponent(idReg)}/documento`);
|
|
1042
|
+
if (opts.documentType !== undefined)
|
|
1043
|
+
url.searchParams.set('documentType', opts.documentType);
|
|
1044
|
+
if (opts.actualizaLeido !== undefined)
|
|
1045
|
+
url.searchParams.set('actualiza-leido', String(opts.actualizaLeido));
|
|
1046
|
+
const path = url.pathname + (url.search ? url.search : '');
|
|
1047
|
+
const data = await this.get(path);
|
|
1048
|
+
// Validación de tamaño: los documentos base64 pueden ser grandes
|
|
1049
|
+
const serialized = JSON.stringify(data);
|
|
1050
|
+
const sizeBytes = Buffer.byteLength(serialized, 'utf8');
|
|
1051
|
+
if (sizeBytes > maxBytes) {
|
|
1052
|
+
logger.warn({ sizeBytes, maxBytes, maxMb, idReg }, 'getFacturaDocumento: respuesta excede MAX_RESPONSE_SIZE_MB, aplicando guardrail');
|
|
1053
|
+
return {
|
|
1054
|
+
truncated: true,
|
|
1055
|
+
warning: [
|
|
1056
|
+
`El documento supera el límite de ${maxMb} MB (${Math.round(sizeBytes / 1024 / 1024 * 10) / 10} MB).`,
|
|
1057
|
+
`Para descargar el documento, usa la URL del sistema Freemática directamente.`,
|
|
1058
|
+
].join(' '),
|
|
1059
|
+
sizeBytes,
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
1062
|
+
return { truncated: false, ...data };
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* Log de auditoría de una factura electrónica (envío, recepción, aceptación AAPP, etc.).
|
|
1066
|
+
*
|
|
1067
|
+
* Endpoint: GET /pven/v1/facturas/{idreg}/log
|
|
1068
|
+
*
|
|
1069
|
+
* @param idReg - Identificador opaco de la factura.
|
|
1070
|
+
* @param opts - Opciones de paginación.
|
|
1071
|
+
* @returns Lista paginada de eventos de auditoría con campos FVCTRLE_*.
|
|
1072
|
+
*/
|
|
1073
|
+
async getFacturaLog(idReg, opts = {}) {
|
|
1074
|
+
return this.listResource(`/pven/v1/facturas/${encodeURIComponent(idReg)}/log`, opts);
|
|
1075
|
+
}
|
|
1076
|
+
/**
|
|
1077
|
+
* Configuración de la integración EDICOM (empresa, aplicación, URLs, credenciales).
|
|
1078
|
+
*
|
|
1079
|
+
* Endpoint: GET /pven/v1/facturas/edicominfo
|
|
1080
|
+
*
|
|
1081
|
+
* Devuelve una lista de configuraciones EDICOM (campos EDICOM_*).
|
|
1082
|
+
* NOTA: incluye campos sensibles (EDICOM_USER, EDICOM_PASSWORD). Usar con precaución.
|
|
1083
|
+
*
|
|
1084
|
+
* @param opts - Opciones de paginación.
|
|
1085
|
+
* @returns Lista de configuraciones EDICOM con campos EDICOM_*.
|
|
1086
|
+
*/
|
|
1087
|
+
async getEdicomInfo(opts = {}) {
|
|
1088
|
+
return this.listResource('/pven/v1/facturas/edicominfo', opts);
|
|
1089
|
+
}
|
|
1090
|
+
/**
|
|
1091
|
+
* Descarga masiva de documentos de facturas electrónicas (PDF/XML/ERROR).
|
|
1092
|
+
*
|
|
1093
|
+
* Endpoint: GET /pven/v1/facturas/download
|
|
1094
|
+
*
|
|
1095
|
+
* Permite recuperar múltiples documentos filtrando por identificadores y tipo.
|
|
1096
|
+
* Los documentos se devuelven con campos VoEFraDoc (empresa, serie, numFra, documentoPdf, etc.).
|
|
1097
|
+
*
|
|
1098
|
+
* @param opts - Filtros: documents (IDs separados por comas), documentType (PDF/XML/ERROR).
|
|
1099
|
+
* @returns Lista de documentos con campos VoEFraDoc.
|
|
1100
|
+
*/
|
|
1101
|
+
async listFacturasDocumentos(opts = {}) {
|
|
1102
|
+
const url = new URL('https://placeholder/pven/v1/facturas/download');
|
|
1103
|
+
if (opts.documents !== undefined)
|
|
1104
|
+
url.searchParams.set('documents', opts.documents);
|
|
1105
|
+
if (opts.documentType !== undefined)
|
|
1106
|
+
url.searchParams.set('documentType', opts.documentType);
|
|
1107
|
+
const path = url.pathname + (url.search ? url.search : '');
|
|
1108
|
+
const data = await this.get(path);
|
|
1109
|
+
return { items: data.items, total: Number(data.total) };
|
|
1110
|
+
}
|
|
1111
|
+
// ---------------------------------------------------------------------------
|
|
1112
|
+
// Albaranes de ventas — detalle (v0.5.1 / TD-155)
|
|
1113
|
+
// ---------------------------------------------------------------------------
|
|
1114
|
+
/**
|
|
1115
|
+
* Detalle de un albarán de venta por `idReg` opaco.
|
|
1116
|
+
*
|
|
1117
|
+
* Endpoint: GET /pven/v2/albaranes-ventas/{idReg}
|
|
1118
|
+
*
|
|
1119
|
+
* @param idReg - Identificador opaco (base64) del albarán.
|
|
1120
|
+
* @returns Objeto completo del albarán de venta.
|
|
1121
|
+
*/
|
|
1122
|
+
async getAlbaranVenta(idReg) {
|
|
1123
|
+
return this.get(`/pven/v2/albaranes-ventas/${encodeURIComponent(idReg)}`);
|
|
1124
|
+
}
|
|
1125
|
+
// ---------------------------------------------------------------------------
|
|
1126
|
+
// Albaranes-facturas (v0.5.1 / TD-155)
|
|
1127
|
+
// ---------------------------------------------------------------------------
|
|
1128
|
+
/**
|
|
1129
|
+
* Lista paginada de vinculaciones albarán↔factura.
|
|
1130
|
+
*
|
|
1131
|
+
* Endpoint: GET /pven/v2/albaranes-facturas
|
|
1132
|
+
*
|
|
1133
|
+
* El filtro `idReg` es un query param nativo. Los filtros empresa, serie,
|
|
1134
|
+
* numFactura y codCliente se envían como FIQL en el parámetro `rQuery`.
|
|
1135
|
+
*
|
|
1136
|
+
* Mapeo de filtros FIQL:
|
|
1137
|
+
* | opts.empresa | FVCA_CODEMP |
|
|
1138
|
+
* | opts.serie | FVCA_SERIEFRA |
|
|
1139
|
+
* | opts.numFactura | FVCA_NUMFRA |
|
|
1140
|
+
* | opts.codCliente | FVCA_CODCLI |
|
|
1141
|
+
*
|
|
1142
|
+
* @param opts - Opciones de paginación y filtros.
|
|
1143
|
+
* @returns Lista paginada de vinculaciones albarán-factura.
|
|
1144
|
+
*/
|
|
1145
|
+
async listAlbaranesFactura(opts) {
|
|
1146
|
+
const url = new URL('https://placeholder/pven/v2/albaranes-facturas');
|
|
1147
|
+
// Paginación
|
|
1148
|
+
if (opts.items !== undefined)
|
|
1149
|
+
url.searchParams.set('items', String(opts.items));
|
|
1150
|
+
if (opts.page !== undefined)
|
|
1151
|
+
url.searchParams.set('page', String(opts.page));
|
|
1152
|
+
// idReg como query param nativo
|
|
1153
|
+
if (opts.idReg !== undefined)
|
|
1154
|
+
url.searchParams.set('idReg', opts.idReg);
|
|
1155
|
+
// Filtros FIQL
|
|
1156
|
+
const fiqlGroup = {};
|
|
1157
|
+
if (opts.empresa !== undefined)
|
|
1158
|
+
fiqlGroup['FVCA_CODEMP'] = opts.empresa;
|
|
1159
|
+
if (opts.serie !== undefined)
|
|
1160
|
+
fiqlGroup['FVCA_SERIEFRA'] = opts.serie;
|
|
1161
|
+
if (opts.numFactura !== undefined)
|
|
1162
|
+
fiqlGroup['FVCA_NUMFRA'] = opts.numFactura;
|
|
1163
|
+
if (opts.codCliente !== undefined)
|
|
1164
|
+
fiqlGroup['FVCA_CODCLI'] = opts.codCliente;
|
|
1165
|
+
appendRquery(url, buildFiql(fiqlGroup));
|
|
1166
|
+
const path = url.pathname + (url.search ? url.search : '');
|
|
1167
|
+
const data = await this.get(path);
|
|
1168
|
+
return { items: data.items, total: Number(data.total) };
|
|
1169
|
+
}
|
|
1170
|
+
/**
|
|
1171
|
+
* Detalle de una vinculación albarán↔factura por `idReg` opaco.
|
|
1172
|
+
*
|
|
1173
|
+
* Endpoint: GET /pven/v2/albaranes-facturas/{idReg}
|
|
1174
|
+
*
|
|
1175
|
+
* @param idReg - Identificador opaco (base64) del registro de vinculación.
|
|
1176
|
+
* @returns Objeto completo de la vinculación albarán-factura.
|
|
1177
|
+
*/
|
|
1178
|
+
async getAlbaranFactura(idReg) {
|
|
1179
|
+
return this.get(`/pven/v2/albaranes-facturas/${encodeURIComponent(idReg)}`);
|
|
1180
|
+
}
|
|
1181
|
+
// ---------------------------------------------------------------------------
|
|
1182
|
+
// Resultados de facturación — vigilancia (v0.5.1 / TD-155)
|
|
1183
|
+
// ---------------------------------------------------------------------------
|
|
1184
|
+
/**
|
|
1185
|
+
* Lista paginada de resultados del proceso batch de facturación de vigilancia.
|
|
1186
|
+
*
|
|
1187
|
+
* Endpoint: GET /pvss/v1/facturacion-resultados
|
|
1188
|
+
*
|
|
1189
|
+
* Todos los filtros se envían como FIQL en el parámetro `rquery`.
|
|
1190
|
+
* El parámetro `order` es nativo del endpoint.
|
|
1191
|
+
*
|
|
1192
|
+
* Mapeo de filtros FIQL:
|
|
1193
|
+
* | opts.empresa | FACT_EMP |
|
|
1194
|
+
* | opts.delegacion | FACT_DELEG |
|
|
1195
|
+
* | opts.codCliente | FACT_COD_CLI |
|
|
1196
|
+
* | opts.calendario | FACT_CAL |
|
|
1197
|
+
* | opts.mes | FACT_MES |
|
|
1198
|
+
* | opts.contrato | FACT_CTRT |
|
|
1199
|
+
* | opts.servicio | FACT_SERV |
|
|
1200
|
+
* | opts.tipoFac | FACT_TIPFAC |
|
|
1201
|
+
* | opts.traspasado | FACT_TRASP |
|
|
1202
|
+
*
|
|
1203
|
+
* @param opts - Opciones de paginación y filtros FIQL.
|
|
1204
|
+
* @returns Lista paginada de resultados de facturación.
|
|
1205
|
+
*/
|
|
1206
|
+
async listResultadosFacturacion(opts) {
|
|
1207
|
+
const url = new URL('https://placeholder/pvss/v1/facturacion-resultados');
|
|
1208
|
+
// Paginación
|
|
1209
|
+
if (opts.items !== undefined)
|
|
1210
|
+
url.searchParams.set('items', String(opts.items));
|
|
1211
|
+
if (opts.page !== undefined)
|
|
1212
|
+
url.searchParams.set('page', String(opts.page));
|
|
1213
|
+
// order es param nativo
|
|
1214
|
+
if (opts.order !== undefined)
|
|
1215
|
+
url.searchParams.set('order', opts.order);
|
|
1216
|
+
// Filtros FIQL
|
|
1217
|
+
const fiqlGroup = {};
|
|
1218
|
+
if (opts.empresa !== undefined)
|
|
1219
|
+
fiqlGroup['FACT_EMP'] = opts.empresa;
|
|
1220
|
+
if (opts.delegacion !== undefined)
|
|
1221
|
+
fiqlGroup['FACT_DELEG'] = opts.delegacion;
|
|
1222
|
+
if (opts.codCliente !== undefined)
|
|
1223
|
+
fiqlGroup['FACT_COD_CLI'] = opts.codCliente;
|
|
1224
|
+
if (opts.calendario !== undefined)
|
|
1225
|
+
fiqlGroup['FACT_CAL'] = opts.calendario;
|
|
1226
|
+
if (opts.mes !== undefined)
|
|
1227
|
+
fiqlGroup['FACT_MES'] = String(opts.mes);
|
|
1228
|
+
if (opts.contrato !== undefined)
|
|
1229
|
+
fiqlGroup['FACT_CTRT'] = opts.contrato;
|
|
1230
|
+
if (opts.servicio !== undefined)
|
|
1231
|
+
fiqlGroup['FACT_SERV'] = opts.servicio;
|
|
1232
|
+
if (opts.tipoFac !== undefined)
|
|
1233
|
+
fiqlGroup['FACT_TIPFAC'] = opts.tipoFac;
|
|
1234
|
+
if (opts.traspasado !== undefined)
|
|
1235
|
+
fiqlGroup['FACT_TRASP'] = opts.traspasado;
|
|
1236
|
+
appendRquery(url, buildFiql(fiqlGroup));
|
|
1237
|
+
const path = url.pathname + (url.search ? url.search : '');
|
|
1238
|
+
const data = await this.get(path);
|
|
1239
|
+
return { items: data.items, total: Number(data.total) };
|
|
1240
|
+
}
|
|
1241
|
+
// ---------------------------------------------------------------------------
|
|
1242
|
+
// Internal helpers
|
|
1243
|
+
// ---------------------------------------------------------------------------
|
|
1244
|
+
async listResource(path, opts) {
|
|
1245
|
+
const params = new URLSearchParams();
|
|
1246
|
+
if (opts.items !== undefined)
|
|
1247
|
+
params.set('items', String(opts.items));
|
|
1248
|
+
if (opts.page !== undefined)
|
|
1249
|
+
params.set('page', String(opts.page));
|
|
1250
|
+
const query = params.toString();
|
|
1251
|
+
const url = query ? `${path}?${query}` : path;
|
|
1252
|
+
const data = await this.get(url);
|
|
1253
|
+
return { items: data.items, total: Number(data.total) };
|
|
1254
|
+
}
|
|
1255
|
+
/**
|
|
1256
|
+
* Helper genérico para endpoints de lista que soportan paginación + filtros FIQL
|
|
1257
|
+
* vía query param `rquery`.
|
|
1258
|
+
*
|
|
1259
|
+
* @param path - Path base del endpoint (sin query string).
|
|
1260
|
+
* @param opts - Opciones de paginación.
|
|
1261
|
+
* @param fiqlFields - Mapa de nombre de campo FIQL → valor. Los valores `undefined` se omiten.
|
|
1262
|
+
* @returns Lista de items con total.
|
|
1263
|
+
*/
|
|
1264
|
+
async listResourceWithFiql(path, opts, fiqlFields) {
|
|
1265
|
+
const url = new URL(`https://placeholder${path}`);
|
|
1266
|
+
if (opts.items !== undefined)
|
|
1267
|
+
url.searchParams.set('items', String(opts.items));
|
|
1268
|
+
if (opts.page !== undefined)
|
|
1269
|
+
url.searchParams.set('page', String(opts.page));
|
|
1270
|
+
const fiql = buildFiql(fiqlFields);
|
|
1271
|
+
appendRquery(url, fiql);
|
|
1272
|
+
const fullPath = url.pathname + (url.search ? url.search : '');
|
|
1273
|
+
const data = await this.get(fullPath);
|
|
1274
|
+
return { items: data.items, total: Number(data.total) };
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
// ---------------------------------------------------------------------------
|
|
1278
|
+
// Helpers privados del módulo
|
|
1279
|
+
// ---------------------------------------------------------------------------
|
|
1280
|
+
/**
|
|
1281
|
+
* Calcula el siguiente prefijo léxico para emular un prefix match FIQL.
|
|
1282
|
+
*
|
|
1283
|
+
* Para un prefijo como "430", el rango [430, 431) captura todos los valores
|
|
1284
|
+
* que comienzan por "430". Si el último carácter es el carácter máximo
|
|
1285
|
+
* unicode (U+FFFF), no se puede calcular un límite superior, por lo que se
|
|
1286
|
+
* devuelve `null` (sólo se aplica el límite inferior).
|
|
1287
|
+
*
|
|
1288
|
+
* @param prefix - Prefijo a extender.
|
|
1289
|
+
* @returns Siguiente prefijo léxico, o `null` si no aplica.
|
|
1290
|
+
*/
|
|
1291
|
+
function buildNextPrefix(prefix) {
|
|
1292
|
+
if (prefix.length === 0)
|
|
1293
|
+
return null;
|
|
1294
|
+
const last = prefix.charCodeAt(prefix.length - 1);
|
|
1295
|
+
if (last >= 0xfffe)
|
|
1296
|
+
return null;
|
|
1297
|
+
return prefix.slice(0, -1) + String.fromCharCode(last + 1);
|
|
1298
|
+
}
|
|
1299
|
+
/**
|
|
1300
|
+
* Trunca un array de items para que su JSON serializado no supere `maxBytes`.
|
|
1301
|
+
*
|
|
1302
|
+
* Utiliza búsqueda binaria para encontrar eficientemente el número máximo de
|
|
1303
|
+
* items que caben dentro del límite.
|
|
1304
|
+
*
|
|
1305
|
+
* @param items - Items originales.
|
|
1306
|
+
* @param maxBytes - Límite en bytes.
|
|
1307
|
+
* @returns Subarray de items que cabe dentro del límite.
|
|
1308
|
+
*/
|
|
1309
|
+
function truncateItemsToLimit(items, maxBytes) {
|
|
1310
|
+
if (items.length === 0)
|
|
1311
|
+
return [];
|
|
1312
|
+
// Búsqueda binaria: encuentra el máximo n tal que JSON([0..n-1]) <= maxBytes
|
|
1313
|
+
let lo = 0;
|
|
1314
|
+
let hi = items.length;
|
|
1315
|
+
while (lo < hi) {
|
|
1316
|
+
const mid = Math.floor((lo + hi) / 2);
|
|
1317
|
+
const size = Buffer.byteLength(JSON.stringify(items.slice(0, mid + 1)), 'utf8');
|
|
1318
|
+
if (size <= maxBytes) {
|
|
1319
|
+
lo = mid + 1;
|
|
1320
|
+
}
|
|
1321
|
+
else {
|
|
1322
|
+
hi = mid;
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
return items.slice(0, lo);
|
|
1326
|
+
}
|
|
1327
|
+
//# sourceMappingURL=freematica-client.js.map
|