@nubiia/mcp-freematica 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -7
- package/dist/clients/fiql-builder.d.ts +32 -8
- package/dist/clients/fiql-builder.js +42 -14
- package/dist/clients/fiql-builder.js.map +1 -1
- package/dist/clients/freematica-client.d.ts +92 -35
- package/dist/clients/freematica-client.js +251 -98
- package/dist/clients/freematica-client.js.map +1 -1
- package/dist/schemas/cartera.d.ts +7 -5
- package/dist/schemas/cartera.js +13 -11
- package/dist/schemas/cartera.js.map +1 -1
- package/dist/schemas/facturas-ventas.d.ts +20 -16
- package/dist/schemas/facturas-ventas.js +36 -32
- package/dist/schemas/facturas-ventas.js.map +1 -1
- package/dist/schemas/pedidos-compras.d.ts +8 -6
- package/dist/schemas/pedidos-compras.js +8 -6
- package/dist/schemas/pedidos-compras.js.map +1 -1
- package/dist/server-instructions.js +19 -0
- package/dist/server-instructions.js.map +1 -1
- package/dist/server.js +2 -0
- package/dist/server.js.map +1 -1
- package/dist/tools/articulos.d.ts +14 -0
- package/dist/tools/articulos.js +183 -0
- package/dist/tools/articulos.js.map +1 -0
- package/dist/tools/cartera.js +2 -2
- package/dist/tools/cartera.js.map +1 -1
- package/dist/tools/contabilidad.js +7 -7
- package/dist/tools/contabilidad.js.map +1 -1
- package/dist/tools/facturas-ventas.js +12 -12
- package/dist/tools/facturas-ventas.js.map +1 -1
- package/dist/tools/localizaciones.d.ts +1 -1
- package/dist/tools/localizaciones.js +3 -12
- package/dist/tools/localizaciones.js.map +1 -1
- package/dist/tools/personal.js +13 -17
- package/dist/tools/personal.js.map +1 -1
- package/dist/tools/prl.js +15 -49
- package/dist/tools/prl.js.map +1 -1
- package/dist/tools/proveedores.d.ts +2 -2
- package/dist/tools/proveedores.js +8 -6
- package/dist/tools/proveedores.js.map +1 -1
- package/package.json +1 -1
|
@@ -6,6 +6,38 @@ import { buildEstadoPedidoFiql, } from '../schemas/pedidos-compras.js';
|
|
|
6
6
|
import { LOC_FIELD_MAP } from '../schemas/localizaciones.js';
|
|
7
7
|
import { logger } from '../logger.js';
|
|
8
8
|
import { loadMaxResponseSizeMb } from '../utils/size-guardrail.js';
|
|
9
|
+
/**
|
|
10
|
+
* Día siguiente a una fecha ISO (YYYY-MM-DD), en formato ISO.
|
|
11
|
+
*
|
|
12
|
+
* Se usa para emular "hasta X inclusive" como `=lt='X+1'` en los endpoints
|
|
13
|
+
* donde el operador `=le=` responde 400/500 (facturas-cabecera, cartera,
|
|
14
|
+
* export-asientos — verificado contra el API real).
|
|
15
|
+
*/
|
|
16
|
+
function nextDayIso(isoDate) {
|
|
17
|
+
const fmt = (d) => {
|
|
18
|
+
// Formateo manual: toISOString() cambia de formato con años de 5+ dígitos
|
|
19
|
+
// (p.ej. 9999-12-31 + 1 día → '+010000-01-01…').
|
|
20
|
+
const y = String(d.getUTCFullYear()).padStart(4, '0');
|
|
21
|
+
const m = String(d.getUTCMonth() + 1).padStart(2, '0');
|
|
22
|
+
const day = String(d.getUTCDate()).padStart(2, '0');
|
|
23
|
+
return `${y}-${m}-${day}`;
|
|
24
|
+
};
|
|
25
|
+
const d = new Date(`${isoDate}T00:00:00Z`);
|
|
26
|
+
// Round-trip: V8 "arrastra" días fuera de rango (2026-02-30 → 2026-03-02)
|
|
27
|
+
// en vez de fallar; solo el round-trip detecta ambos casos (NaN y arrastre).
|
|
28
|
+
if (Number.isNaN(d.getTime()) || fmt(d) !== isoDate) {
|
|
29
|
+
throw new Error(`Fecha inválida: ${isoDate}. Usa una fecha de calendario real en formato YYYY-MM-DD.`);
|
|
30
|
+
}
|
|
31
|
+
d.setUTCDate(d.getUTCDate() + 1);
|
|
32
|
+
return fmt(d);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Mensaje de error para los endpoints donde el API de Freemática devuelve
|
|
36
|
+
* 0 filas si se combinan dos condiciones de rango sobre el MISMO campo
|
|
37
|
+
* (verificado con ge+lt, ge+le, orden inverso y paréntesis).
|
|
38
|
+
*/
|
|
39
|
+
const RANGO_NO_SOPORTADO = 'El API de Freemática no soporta combinar fecha-desde y fecha-hasta a la vez ' +
|
|
40
|
+
'en este endpoint (devuelve 0 resultados). Usa solo uno de los dos límites por consulta.';
|
|
9
41
|
/**
|
|
10
42
|
* Typed client for the Freemática REST API.
|
|
11
43
|
*
|
|
@@ -32,6 +64,96 @@ export class FreematicaClient extends BaseClient {
|
|
|
32
64
|
const data = await this.get('/pvss/v2/contratos-servicios-material');
|
|
33
65
|
return { items: data.items, total: Number(data.total) };
|
|
34
66
|
}
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
// Artículos — catálogo de inventario (v0.9.0)
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
/**
|
|
71
|
+
* Lista paginada de artículos del catálogo de inventario.
|
|
72
|
+
*
|
|
73
|
+
* Endpoint: GET /part/v1/articulos
|
|
74
|
+
*
|
|
75
|
+
* Todos los filtros son de coincidencia EXACTA (el endpoint no soporta
|
|
76
|
+
* búsqueda parcial: =lk= responde 400 y los wildcards % devuelven 0
|
|
77
|
+
* resultados — verificado contra el API real). OJO: COD_ARTICULO puede
|
|
78
|
+
* llevar espacios iniciales en Freemática (ej. " QQ10615") que forman
|
|
79
|
+
* parte del código.
|
|
80
|
+
*
|
|
81
|
+
* El filtro `activo` mapea a MOTIVO_BAJA (columna de texto — las columnas
|
|
82
|
+
* de fecha no permiten comparar con vacío):
|
|
83
|
+
* - `true` → MOTIVO_BAJA=='' (sin motivo de baja).
|
|
84
|
+
* - `false` → MOTIVO_BAJA!='' (con motivo de baja).
|
|
85
|
+
* Los artículos con MOTIVO_BAJA nulo (≈36 de 6304 en producción) no
|
|
86
|
+
* matchean ninguno de los dos valores.
|
|
87
|
+
*
|
|
88
|
+
* @param opts - Paginación y filtros opcionales.
|
|
89
|
+
* @returns Lista paginada de artículos (cada item incluye `idReg`).
|
|
90
|
+
*/
|
|
91
|
+
async listArticulos(opts) {
|
|
92
|
+
const url = new URL('placeholder://x/part/v1/articulos');
|
|
93
|
+
if (opts.items !== undefined)
|
|
94
|
+
url.searchParams.set('items', String(opts.items));
|
|
95
|
+
if (opts.page !== undefined)
|
|
96
|
+
url.searchParams.set('page', String(opts.page));
|
|
97
|
+
const fiqlGroup = {};
|
|
98
|
+
if (opts.codArticulo !== undefined)
|
|
99
|
+
fiqlGroup['COD_ARTICULO'] = opts.codArticulo;
|
|
100
|
+
if (opts.tipoCodigo !== undefined)
|
|
101
|
+
fiqlGroup['TIPO_CODIGO'] = opts.tipoCodigo;
|
|
102
|
+
if (opts.codProveedor !== undefined)
|
|
103
|
+
fiqlGroup['COD_PROVEEDOR'] = opts.codProveedor;
|
|
104
|
+
if (opts.linea !== undefined)
|
|
105
|
+
fiqlGroup['COD_LIN_ART'] = opts.linea;
|
|
106
|
+
if (opts.familia !== undefined)
|
|
107
|
+
fiqlGroup['COD_FAMILIA'] = opts.familia;
|
|
108
|
+
if (opts.subfamilia !== undefined)
|
|
109
|
+
fiqlGroup['COD_SUBFAM'] = opts.subfamilia;
|
|
110
|
+
if (opts.descripcion !== undefined)
|
|
111
|
+
fiqlGroup['DESC_ART'] = opts.descripcion;
|
|
112
|
+
if (opts.codigoBarras !== undefined)
|
|
113
|
+
fiqlGroup['CODIGO_BARRAS'] = opts.codigoBarras;
|
|
114
|
+
if (opts.activo === true)
|
|
115
|
+
fiqlGroup['MOTIVO_BAJA'] = '';
|
|
116
|
+
if (opts.activo === false)
|
|
117
|
+
fiqlGroup['MOTIVO_BAJA'] = { op: 'ne', value: '' };
|
|
118
|
+
appendRquery(url, buildFiql(fiqlGroup));
|
|
119
|
+
const qs = url.searchParams.toString();
|
|
120
|
+
const path = qs ? `/part/v1/articulos?${qs}` : '/part/v1/articulos';
|
|
121
|
+
const data = await this.get(path);
|
|
122
|
+
return { items: data.items, total: Number(data.total) };
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Detalle de un artículo por `idReg` opaco.
|
|
126
|
+
*
|
|
127
|
+
* Endpoint: GET /part/v1/articulos/{idreg}
|
|
128
|
+
*
|
|
129
|
+
* El API devuelve un envelope de LISTA con un único item (verificado contra
|
|
130
|
+
* el API real), no un objeto detalle; este método lo desenvuelve.
|
|
131
|
+
*
|
|
132
|
+
* @param idReg - Identificador opaco (base64) del artículo.
|
|
133
|
+
* @returns Objeto con todos los campos del artículo.
|
|
134
|
+
*/
|
|
135
|
+
async getArticulo(idReg) {
|
|
136
|
+
const data = await this.get(`/part/v1/articulos/${encodeURIComponent(idReg)}`);
|
|
137
|
+
const item = data.items?.[0];
|
|
138
|
+
if (item === undefined) {
|
|
139
|
+
throw new FreematicaError('not_found', `Artículo no encontrado: ${idReg}`);
|
|
140
|
+
}
|
|
141
|
+
return item;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Precios de venta de un artículo por `idReg` opaco.
|
|
145
|
+
*
|
|
146
|
+
* Endpoint: GET /pgrl/v1/precio-articulo/{idreg}
|
|
147
|
+
*
|
|
148
|
+
* Devuelve un objeto con PRECIO_VENTA, DESCUENTO y FACTURABLE.
|
|
149
|
+
*
|
|
150
|
+
* @param idReg - Identificador opaco (base64) del artículo (el mismo que
|
|
151
|
+
* devuelve freematica_list_articulos).
|
|
152
|
+
* @returns Objeto de precios del artículo.
|
|
153
|
+
*/
|
|
154
|
+
async getPrecioArticulo(idReg) {
|
|
155
|
+
return this.get(`/pgrl/v1/precio-articulo/${encodeURIComponent(idReg)}`);
|
|
156
|
+
}
|
|
35
157
|
/**
|
|
36
158
|
* Obtener un catálogo de datos maestros.
|
|
37
159
|
*/
|
|
@@ -164,13 +286,15 @@ export class FreematicaClient extends BaseClient {
|
|
|
164
286
|
*
|
|
165
287
|
* Endpoint: GET /pprl/v1/vigilancia-salud
|
|
166
288
|
*
|
|
167
|
-
*
|
|
168
|
-
* codifican en FIQL y se envían como `rquery`.
|
|
169
|
-
*
|
|
170
|
-
* @param opts - Opciones de paginación y filtrado.
|
|
289
|
+
* @param opts - Paginación y filtro opcional idRegPersona.
|
|
171
290
|
* @returns Lista paginada de registros VS.
|
|
172
291
|
*/
|
|
173
292
|
async listVigilanciaSalud(opts) {
|
|
293
|
+
// El endpoint IGNORA el parámetro `rquery` por completo: cualquier FIQL
|
|
294
|
+
// (incluso con campos inexistentes) responde 200 con el dataset completo
|
|
295
|
+
// sin filtrar (verificado contra el API real). El único filtro operativo
|
|
296
|
+
// es el query param nativo `idRegPersona` (el idReg de la persona en
|
|
297
|
+
// freematica_list_personal), que sí filtra correctamente.
|
|
174
298
|
const url = new URL('placeholder://x/pprl/v1/vigilancia-salud');
|
|
175
299
|
if (opts.items !== undefined)
|
|
176
300
|
url.searchParams.set('items', String(opts.items));
|
|
@@ -178,28 +302,6 @@ export class FreematicaClient extends BaseClient {
|
|
|
178
302
|
url.searchParams.set('page', String(opts.page));
|
|
179
303
|
if (opts.idRegPersona !== undefined)
|
|
180
304
|
url.searchParams.set('idRegPersona', opts.idRegPersona);
|
|
181
|
-
// Filtros FIQL
|
|
182
|
-
const fiqlParts = {};
|
|
183
|
-
if (opts.empresa !== undefined)
|
|
184
|
-
fiqlParts['PERVS_EMP'] = opts.empresa;
|
|
185
|
-
if (opts.delegacion !== undefined)
|
|
186
|
-
fiqlParts['PERVS_DELEG'] = opts.delegacion;
|
|
187
|
-
if (opts.codPersona !== undefined)
|
|
188
|
-
fiqlParts['PERVS_PERSO'] = opts.codPersona;
|
|
189
|
-
if (opts.tipoRevision !== undefined)
|
|
190
|
-
fiqlParts['PERVS_TIPO_REVISION'] = opts.tipoRevision;
|
|
191
|
-
if (opts.resultado !== undefined)
|
|
192
|
-
fiqlParts['PERVS_RESULTADO'] = opts.resultado;
|
|
193
|
-
if (opts.fechaCitaDesde !== undefined)
|
|
194
|
-
fiqlParts['PERVS_FCH_CITA'] = { op: 'ge', value: opts.fechaCitaDesde };
|
|
195
|
-
// If both desde and hasta, we need to combine with AND
|
|
196
|
-
const fiqlParts2 = {};
|
|
197
|
-
if (opts.fechaCitaHasta !== undefined)
|
|
198
|
-
fiqlParts2['PERVS_FCH_CITA'] = { op: 'le', value: opts.fechaCitaHasta };
|
|
199
|
-
const fiql1 = buildFiql(fiqlParts);
|
|
200
|
-
const fiql2 = buildFiql(fiqlParts2);
|
|
201
|
-
const combinedFiql = [fiql1, fiql2].filter(Boolean).join(';');
|
|
202
|
-
appendRquery(url, combinedFiql);
|
|
203
305
|
const qs = url.searchParams.toString();
|
|
204
306
|
const path = qs ? `/pprl/v1/vigilancia-salud?${qs}` : '/pprl/v1/vigilancia-salud';
|
|
205
307
|
const data = await this.get(path);
|
|
@@ -264,18 +366,23 @@ export class FreematicaClient extends BaseClient {
|
|
|
264
366
|
/**
|
|
265
367
|
* Lista paginada de personas (empleados) con filtros FIQL opcionales.
|
|
266
368
|
*
|
|
267
|
-
* Endpoint: GET /pers/
|
|
369
|
+
* Endpoint: GET /pers/v1/personal
|
|
268
370
|
*
|
|
269
|
-
*
|
|
270
|
-
*
|
|
271
|
-
*
|
|
272
|
-
* para estos campos).
|
|
371
|
+
* Todos los filtros son de coincidencia EXACTA: el endpoint no soporta
|
|
372
|
+
* búsqueda parcial (=lk= responde 400 y los wildcards % / * devuelven 0
|
|
373
|
+
* resultados — verificado contra el API real).
|
|
273
374
|
*
|
|
274
375
|
* @param opts - Opciones de paginación y filtrado.
|
|
275
376
|
* @returns Lista paginada de personas.
|
|
276
377
|
*/
|
|
277
378
|
async listPersonal(opts) {
|
|
278
|
-
|
|
379
|
+
// v1, no v2: /pers/v2/personal es un endpoint de sincronización incremental
|
|
380
|
+
// que exige el parámetro nativo `fchmodificacion` (sin él → 400 "Parámetro
|
|
381
|
+
// [fchmodificacion] obligatorio") y devuelve un subconjunto del dataset.
|
|
382
|
+
// /pers/v1/personal devuelve el dataset completo con idReg. Verificado
|
|
383
|
+
// contra el API real (v1: 5482 personas; v2 con fchmodificacion=1900-01-01:
|
|
384
|
+
// 3984). El campo VSSPER_ACTIVO no existe en v1 (filtrarlo → 400).
|
|
385
|
+
const url = new URL('placeholder://x/pers/v1/personal');
|
|
279
386
|
if (opts.items !== undefined)
|
|
280
387
|
url.searchParams.set('items', String(opts.items));
|
|
281
388
|
if (opts.page !== undefined)
|
|
@@ -299,24 +406,32 @@ export class FreematicaClient extends BaseClient {
|
|
|
299
406
|
fiqlGroup['VSSPER_DPTO'] = opts.departamento;
|
|
300
407
|
if (opts.seccion !== undefined)
|
|
301
408
|
fiqlGroup['VSSPER_SECCION'] = opts.seccion;
|
|
302
|
-
if (opts.activo !== undefined)
|
|
303
|
-
fiqlGroup['VSSPER_ACTIVO'] = opts.activo ? 'S' : 'N';
|
|
304
409
|
appendRquery(url, buildFiql(fiqlGroup));
|
|
305
410
|
const qs = url.searchParams.toString();
|
|
306
|
-
const path = qs ? `/pers/
|
|
411
|
+
const path = qs ? `/pers/v1/personal?${qs}` : '/pers/v1/personal';
|
|
307
412
|
const data = await this.get(path);
|
|
308
413
|
return { items: data.items, total: Number(data.total) };
|
|
309
414
|
}
|
|
310
415
|
/**
|
|
311
|
-
* Detalle de una persona por `idReg` opaco.
|
|
416
|
+
* Detalle de una persona por su `idReg` opaco.
|
|
417
|
+
*
|
|
418
|
+
* Endpoint: GET /pers/v1/personal/{idreg}
|
|
312
419
|
*
|
|
313
|
-
*
|
|
420
|
+
* v1, igual que el listado: garantiza que todo idReg devuelto por
|
|
421
|
+
* freematica_list_personal funciona aquí (la vista v2 es un subconjunto
|
|
422
|
+
* de sincronización incremental). El API devuelve un envelope de LISTA
|
|
423
|
+
* con un único item (verificado contra el API real); se desenvuelve.
|
|
314
424
|
*
|
|
315
|
-
* @param idReg - Identificador opaco de la persona.
|
|
425
|
+
* @param idReg - Identificador opaco (base64) de la persona.
|
|
316
426
|
* @returns Objeto con todos los campos de la persona.
|
|
317
427
|
*/
|
|
318
428
|
async getPersona(idReg) {
|
|
319
|
-
|
|
429
|
+
const data = await this.get(`/pers/v1/personal/${encodeURIComponent(idReg)}`);
|
|
430
|
+
const item = data.items?.[0];
|
|
431
|
+
if (item === undefined) {
|
|
432
|
+
throw new FreematicaError('not_found', `Persona no encontrada: ${idReg}`);
|
|
433
|
+
}
|
|
434
|
+
return item;
|
|
320
435
|
}
|
|
321
436
|
// ---------------------------------------------------------------------------
|
|
322
437
|
// Calendarios laborales (v0.5.0)
|
|
@@ -372,13 +487,19 @@ export class FreematicaClient extends BaseClient {
|
|
|
372
487
|
url.searchParams.set('cal', opts.cal);
|
|
373
488
|
if (opts.periodo !== undefined)
|
|
374
489
|
url.searchParams.set('periodo', opts.periodo);
|
|
375
|
-
// Construir FIQL para filtros adicionales
|
|
490
|
+
// Construir FIQL para filtros adicionales.
|
|
491
|
+
// ASI_FCHASI: `=ge=` funciona; `=le=` responde 400 (se emula con `=lt=`
|
|
492
|
+
// del día siguiente); combinar ambos límites devuelve 0 filas (bug del
|
|
493
|
+
// API verificado en producción) → se rechaza.
|
|
494
|
+
if (opts.fechaDesde !== undefined && opts.fechaHasta !== undefined) {
|
|
495
|
+
throw new Error(RANGO_NO_SOPORTADO);
|
|
496
|
+
}
|
|
376
497
|
const andGroups = [];
|
|
377
498
|
if (opts.fechaDesde !== undefined) {
|
|
378
499
|
andGroups.push({ ASI_FCHASI: { op: 'ge', value: opts.fechaDesde } });
|
|
379
500
|
}
|
|
380
501
|
if (opts.fechaHasta !== undefined) {
|
|
381
|
-
andGroups.push({ ASI_FCHASI: { op: '
|
|
502
|
+
andGroups.push({ ASI_FCHASI: { op: 'lt', value: nextDayIso(opts.fechaHasta) } });
|
|
382
503
|
}
|
|
383
504
|
if (opts.diario !== undefined) {
|
|
384
505
|
andGroups.push({ ASI_DIARIO: opts.diario });
|
|
@@ -496,12 +617,11 @@ export class FreematicaClient extends BaseClient {
|
|
|
496
617
|
* Construye la FIQL a partir de los filtros tipados y la añade como `rquery`
|
|
497
618
|
* usando `appendRquery()` (patrón canónico del foundation TD-117).
|
|
498
619
|
*
|
|
499
|
-
* El filtro `soloImpagados=true` genera `CARCL_FECIMPAG
|
|
500
|
-
*
|
|
501
|
-
*
|
|
502
|
-
*
|
|
503
|
-
*
|
|
504
|
-
* consistente con el resto de filtros.
|
|
620
|
+
* El filtro `soloImpagados=true` genera `CARCL_FECIMPAG=ge='1900-01-01'`
|
|
621
|
+
* (cualquier fecha de impago con valor). El FIQL de Freemática no tiene
|
|
622
|
+
* IS NOT NULL: `!=null` devuelve 0 resultados silenciosamente y `!='null'`
|
|
623
|
+
* responde 500 (verificado contra el API real; con este rango devuelve
|
|
624
|
+
* 382 impagados de 72054 documentos).
|
|
505
625
|
*
|
|
506
626
|
* El filtro `estado` mapea a los valores numéricos de `CARCL_SITCAR`
|
|
507
627
|
* (1=pendiente, 2=cancelado, 3=derivado).
|
|
@@ -540,20 +660,27 @@ export class FreematicaClient extends BaseClient {
|
|
|
540
660
|
const eqFiql = buildFiql(eqGroup);
|
|
541
661
|
if (eqFiql)
|
|
542
662
|
parts.push(eqFiql);
|
|
543
|
-
// Range filters:
|
|
663
|
+
// Range filters. CARCL_FECDOC: `=ge=` funciona; `=le=` responde 500
|
|
664
|
+
// (hasta se emula con `=lt=` del día siguiente); combinar desde+hasta
|
|
665
|
+
// devuelve 0 filas (bug del API verificado en producción) → se rechaza.
|
|
666
|
+
// CARCL_FECVCTO: solo `=ge=` funciona (le/lt se ignoran silenciosamente
|
|
667
|
+
// devolviendo el dataset completo), así que fechaVencimientoHasta no
|
|
668
|
+
// existe como filtro.
|
|
669
|
+
if (opts.fechaDocDesde !== undefined && opts.fechaDocHasta !== undefined) {
|
|
670
|
+
throw new Error(RANGO_NO_SOPORTADO);
|
|
671
|
+
}
|
|
544
672
|
if (opts.fechaDocDesde !== undefined)
|
|
545
673
|
parts.push(buildFiql({ CARCL_FECDOC: { op: 'ge', value: opts.fechaDocDesde } }));
|
|
546
674
|
if (opts.fechaDocHasta !== undefined)
|
|
547
|
-
parts.push(buildFiql({ CARCL_FECDOC: { op: '
|
|
675
|
+
parts.push(buildFiql({ CARCL_FECDOC: { op: 'lt', value: nextDayIso(opts.fechaDocHasta) } }));
|
|
548
676
|
if (opts.fechaVencimientoDesde !== undefined)
|
|
549
677
|
parts.push(buildFiql({ CARCL_FECVCTO: { op: 'ge', value: opts.fechaVencimientoDesde } }));
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
//
|
|
553
|
-
//
|
|
554
|
-
// garantizar escaping uniforme y legibilidad del código.
|
|
678
|
+
// soloImpagados: el FIQL de Freemática no soporta IS NOT NULL (`!=null`
|
|
679
|
+
// devuelve 0 resultados y `!='null'` responde 500 — verificado contra el
|
|
680
|
+
// API real). El rango de fecha equivale: cualquier CARCL_FECIMPAG con
|
|
681
|
+
// valor es >= 1900-01-01.
|
|
555
682
|
if (opts.soloImpagados === true) {
|
|
556
|
-
parts.push(buildFiql({ CARCL_FECIMPAG: { op: '
|
|
683
|
+
parts.push(buildFiql({ CARCL_FECIMPAG: { op: 'ge', value: '1900-01-01' } }));
|
|
557
684
|
}
|
|
558
685
|
appendRquery(url, parts.join(';'));
|
|
559
686
|
const path = url.pathname + (url.search ? url.search : '');
|
|
@@ -692,13 +819,16 @@ export class FreematicaClient extends BaseClient {
|
|
|
692
819
|
/**
|
|
693
820
|
* Lista paginada de proveedores con filtros FIQL.
|
|
694
821
|
*
|
|
695
|
-
* El filtro `activo` mapea a FECHA_BAJA
|
|
696
|
-
*
|
|
697
|
-
*
|
|
698
|
-
*
|
|
699
|
-
* - `
|
|
822
|
+
* El filtro `activo` mapea a FECHA_BAJA. El API no soporta IS NULL en FIQL
|
|
823
|
+
* (`==null` devuelve 0 resultados y `=='null'` responde 500 — verificado
|
|
824
|
+
* contra el API real), así que:
|
|
825
|
+
* - `false` → `FECHA_BAJA=ge='1900-01-01'` (cualquier fecha de baja).
|
|
826
|
+
* - `true` → sin filtro server-side; las bajas se descartan post-process
|
|
827
|
+
* sobre la página recibida (FECHA_BAJA con valor). El `total`
|
|
828
|
+
* devuelto es el del dataset sin filtrar.
|
|
700
829
|
*
|
|
701
|
-
* El filtro `nombre`
|
|
830
|
+
* El filtro `nombre` es coincidencia EXACTA: el operador `=lk=` responde
|
|
831
|
+
* 400 en este endpoint y los wildcards % devuelven 0 resultados.
|
|
702
832
|
*
|
|
703
833
|
* Endpoint: GET /pgrl/v2/proveedores
|
|
704
834
|
*/
|
|
@@ -718,21 +848,25 @@ export class FreematicaClient extends BaseClient {
|
|
|
718
848
|
COD_PAIS: opts.codPais,
|
|
719
849
|
};
|
|
720
850
|
if (opts.nombre !== undefined) {
|
|
721
|
-
filters['NOMBRE_PRO'] =
|
|
722
|
-
}
|
|
723
|
-
if (opts.activo === true) {
|
|
724
|
-
// Activos: FECHA_BAJA nula. FIQL: FECHA_BAJA==null
|
|
725
|
-
filters['FECHA_BAJA'] = 'null';
|
|
851
|
+
filters['NOMBRE_PRO'] = opts.nombre;
|
|
726
852
|
}
|
|
727
|
-
|
|
728
|
-
// Dados de baja: FECHA_BAJA
|
|
729
|
-
|
|
853
|
+
if (opts.activo === false) {
|
|
854
|
+
// Dados de baja: cualquier FECHA_BAJA con valor. IS NOT NULL no existe
|
|
855
|
+
// en el FIQL de Freemática; el rango de fecha equivale.
|
|
856
|
+
filters['FECHA_BAJA'] = { op: 'ge', value: '1900-01-01' };
|
|
730
857
|
}
|
|
731
858
|
const fiql = buildFiql(filters);
|
|
732
859
|
appendRquery(url, fiql);
|
|
733
860
|
const path = url.pathname + (url.search ? url.search : '');
|
|
734
861
|
const data = await this.get(path);
|
|
735
|
-
|
|
862
|
+
// activo=true: descartar bajas de la página (no expresable en FIQL).
|
|
863
|
+
const items = opts.activo === true
|
|
864
|
+
? data.items.filter((p) => {
|
|
865
|
+
const fb = p['FECHA_BAJA'];
|
|
866
|
+
return fb === undefined || fb === null || String(fb).trim() === '';
|
|
867
|
+
})
|
|
868
|
+
: data.items;
|
|
869
|
+
return { items, total: Number(data.total) };
|
|
736
870
|
}
|
|
737
871
|
// ---------------------------------------------------------------------------
|
|
738
872
|
// Facturas de ventas (v0.5.0)
|
|
@@ -752,33 +886,43 @@ export class FreematicaClient extends BaseClient {
|
|
|
752
886
|
if (opts.page !== undefined)
|
|
753
887
|
url.searchParams.set('page', String(opts.page));
|
|
754
888
|
const fiqlParts = [];
|
|
755
|
-
// Standard equality filters
|
|
889
|
+
// Standard equality filters. Nombres de columna verificados contra el API
|
|
890
|
+
// real: la vista de facturas-cabecera expone FVC_CODEMP, FVC_CODCLI,
|
|
891
|
+
// FVC_CODREPRES, FVC_SERIEFRA, FVC_NUMFRA, FVC_FPAGO, FVC_FCHFAC y
|
|
892
|
+
// FVC_TRASP_CONTAB ('1'/'0'). Los nombres anteriores (FVC_EMP, FVC_CODAUX,
|
|
893
|
+
// FVC_CODREP, FVC_SERFAC, FVC_NUMFAC, FVC_CODFPAG, FVC_FECFAC,
|
|
894
|
+
// FVC_TRSCONT) no existen y provocaban 500 en cada consulta con filtro.
|
|
756
895
|
const eqGroup = {};
|
|
757
896
|
if (opts.empresa !== undefined)
|
|
758
|
-
eqGroup['
|
|
897
|
+
eqGroup['FVC_CODEMP'] = opts.empresa;
|
|
759
898
|
if (opts.codCliente !== undefined)
|
|
760
|
-
eqGroup['
|
|
899
|
+
eqGroup['FVC_CODCLI'] = opts.codCliente;
|
|
761
900
|
if (opts.representante !== undefined)
|
|
762
|
-
eqGroup['
|
|
901
|
+
eqGroup['FVC_CODREPRES'] = opts.representante;
|
|
763
902
|
if (opts.serie !== undefined)
|
|
764
|
-
eqGroup['
|
|
903
|
+
eqGroup['FVC_SERIEFRA'] = opts.serie;
|
|
765
904
|
if (opts.numFactura !== undefined)
|
|
766
|
-
eqGroup['
|
|
905
|
+
eqGroup['FVC_NUMFRA'] = opts.numFactura;
|
|
767
906
|
if (opts.formaPago !== undefined)
|
|
768
|
-
eqGroup['
|
|
907
|
+
eqGroup['FVC_FPAGO'] = opts.formaPago;
|
|
769
908
|
if (opts.delegacion !== undefined)
|
|
770
909
|
eqGroup['FVC_DELEG'] = opts.delegacion;
|
|
771
910
|
const eqFiql = buildFiql(eqGroup);
|
|
772
911
|
if (eqFiql)
|
|
773
912
|
fiqlParts.push(eqFiql);
|
|
774
|
-
// Date range filters
|
|
913
|
+
// Date range filters. FVC_FCHFAC: `=ge=` funciona; `=le=`/`=gt=` responden
|
|
914
|
+
// 500 (hasta se emula con `=lt=` del día siguiente); combinar desde+hasta
|
|
915
|
+
// devuelve 0 filas (bug del API verificado en producción) → se rechaza.
|
|
916
|
+
if (opts.fechaFacturaDesde !== undefined && opts.fechaFacturaHasta !== undefined) {
|
|
917
|
+
throw new Error(RANGO_NO_SOPORTADO);
|
|
918
|
+
}
|
|
775
919
|
if (opts.fechaFacturaDesde !== undefined)
|
|
776
|
-
fiqlParts.push(buildFiql({
|
|
920
|
+
fiqlParts.push(buildFiql({ FVC_FCHFAC: { op: 'ge', value: opts.fechaFacturaDesde } }));
|
|
777
921
|
if (opts.fechaFacturaHasta !== undefined)
|
|
778
|
-
fiqlParts.push(buildFiql({
|
|
779
|
-
// Boolean: traspasadoContabilidad
|
|
922
|
+
fiqlParts.push(buildFiql({ FVC_FCHFAC: { op: 'lt', value: nextDayIso(opts.fechaFacturaHasta) } }));
|
|
923
|
+
// Boolean: traspasadoContabilidad — la columna real usa '1'/'0'
|
|
780
924
|
if (opts.traspasadoContabilidad !== undefined) {
|
|
781
|
-
fiqlParts.push(buildFiql({
|
|
925
|
+
fiqlParts.push(buildFiql({ FVC_TRASP_CONTAB: opts.traspasadoContabilidad ? '1' : '0' }));
|
|
782
926
|
}
|
|
783
927
|
appendRquery(url, fiqlParts.join(';'));
|
|
784
928
|
const path = url.pathname + (url.search ? url.search : '');
|
|
@@ -828,7 +972,11 @@ export class FreematicaClient extends BaseClient {
|
|
|
828
972
|
* Lista paginada de localizaciones de servicio de clientes.
|
|
829
973
|
*
|
|
830
974
|
* Filtros soportados: codCliente (COD_CLI), grupoCliente (GRUPO_CLI), codPais (COD_PAIS),
|
|
831
|
-
* codProvincia (COD_PROVINCIA), representante (COD_REPRES)
|
|
975
|
+
* codProvincia (COD_PROVINCIA), representante (COD_REPRES).
|
|
976
|
+
*
|
|
977
|
+
* La vista NO tiene columna FECHA_BAJA (filtrar por ella responde 400
|
|
978
|
+
* "Error en rquery" — verificado contra el API real), así que no existe
|
|
979
|
+
* filtro activo/baja en este recurso.
|
|
832
980
|
*
|
|
833
981
|
* Endpoint: GET /pgrl/v2/localizaciones-servicio-clientes
|
|
834
982
|
*/
|
|
@@ -840,12 +988,6 @@ export class FreematicaClient extends BaseClient {
|
|
|
840
988
|
COD_PROVINCIA: opts.codProvincia,
|
|
841
989
|
COD_REPRES: opts.representante,
|
|
842
990
|
};
|
|
843
|
-
if (opts.activo === true) {
|
|
844
|
-
fiqlFilters['FECHA_BAJA'] = 'null';
|
|
845
|
-
}
|
|
846
|
-
else if (opts.activo === false) {
|
|
847
|
-
fiqlFilters['FECHA_BAJA'] = { op: 'ne', value: 'null' };
|
|
848
|
-
}
|
|
849
991
|
return this.listResourceWithFiql('/pgrl/v2/localizaciones-servicio-clientes', opts, fiqlFilters);
|
|
850
992
|
}
|
|
851
993
|
/**
|
|
@@ -874,13 +1016,15 @@ export class FreematicaClient extends BaseClient {
|
|
|
874
1016
|
url.searchParams.set('items', String(opts.items));
|
|
875
1017
|
if (opts.page !== undefined)
|
|
876
1018
|
url.searchParams.set('page', String(opts.page));
|
|
1019
|
+
// Nombres de columna verificados contra el API real (las líneas exponen
|
|
1020
|
+
// FVL_CODARTIC, FVL_COD_FAMILIA, FVL_COD_SUBFAM y FVL_DELEG).
|
|
877
1021
|
const fiqlGroup = {};
|
|
878
1022
|
if (opts.codArticulo !== undefined)
|
|
879
|
-
fiqlGroup['
|
|
1023
|
+
fiqlGroup['FVL_CODARTIC'] = opts.codArticulo;
|
|
880
1024
|
if (opts.codFamilia !== undefined)
|
|
881
|
-
fiqlGroup['
|
|
1025
|
+
fiqlGroup['FVL_COD_FAMILIA'] = opts.codFamilia;
|
|
882
1026
|
if (opts.codSubfamilia !== undefined)
|
|
883
|
-
fiqlGroup['
|
|
1027
|
+
fiqlGroup['FVL_COD_SUBFAM'] = opts.codSubfamilia;
|
|
884
1028
|
if (opts.delegacion !== undefined)
|
|
885
1029
|
fiqlGroup['FVL_DELEG'] = opts.delegacion;
|
|
886
1030
|
appendRquery(url, buildFiql(fiqlGroup));
|
|
@@ -903,9 +1047,10 @@ export class FreematicaClient extends BaseClient {
|
|
|
903
1047
|
url.searchParams.set('items', String(opts.items));
|
|
904
1048
|
if (opts.page !== undefined)
|
|
905
1049
|
url.searchParams.set('page', String(opts.page));
|
|
1050
|
+
// Columna real: FVI_TIPO_IVA (verificado contra el API; FVI_TIPIVA no existe).
|
|
906
1051
|
const fiqlGroup = {};
|
|
907
1052
|
if (opts.tipoIva !== undefined)
|
|
908
|
-
fiqlGroup['
|
|
1053
|
+
fiqlGroup['FVI_TIPO_IVA'] = opts.tipoIva;
|
|
909
1054
|
appendRquery(url, buildFiql(fiqlGroup));
|
|
910
1055
|
const path = url.pathname + (url.search ? url.search : '');
|
|
911
1056
|
const data = await this.get(path);
|
|
@@ -926,13 +1071,18 @@ export class FreematicaClient extends BaseClient {
|
|
|
926
1071
|
url.searchParams.set('items', String(opts.items));
|
|
927
1072
|
if (opts.page !== undefined)
|
|
928
1073
|
url.searchParams.set('page', String(opts.page));
|
|
1074
|
+
// Columnas reales: FVV_MODOPAGO y FVV_FCH_VTO (verificado contra el API;
|
|
1075
|
+
// FVV_CODMPAG y FVV_FECVCTO no existen). A diferencia de FVC_FCHFAC en la
|
|
1076
|
+
// cabecera, en este sub-recurso `=le=` responde 200 y ge+le combinados
|
|
1077
|
+
// devuelven el registro esperado (verificado en producción con una factura
|
|
1078
|
+
// de 1 vencimiento), así que se mantienen el =le= y el rango.
|
|
929
1079
|
const fiqlParts = [];
|
|
930
1080
|
if (opts.modoPago !== undefined)
|
|
931
|
-
fiqlParts.push(buildFiql({
|
|
1081
|
+
fiqlParts.push(buildFiql({ FVV_MODOPAGO: opts.modoPago }));
|
|
932
1082
|
if (opts.fechaVencimientoDesde !== undefined)
|
|
933
|
-
fiqlParts.push(buildFiql({
|
|
1083
|
+
fiqlParts.push(buildFiql({ FVV_FCH_VTO: { op: 'ge', value: opts.fechaVencimientoDesde } }));
|
|
934
1084
|
if (opts.fechaVencimientoHasta !== undefined)
|
|
935
|
-
fiqlParts.push(buildFiql({
|
|
1085
|
+
fiqlParts.push(buildFiql({ FVV_FCH_VTO: { op: 'le', value: opts.fechaVencimientoHasta } }));
|
|
936
1086
|
appendRquery(url, fiqlParts.join(';'));
|
|
937
1087
|
const path = url.pathname + (url.search ? url.search : '');
|
|
938
1088
|
const data = await this.get(path);
|
|
@@ -1596,10 +1746,13 @@ export class FreematicaClient extends BaseClient {
|
|
|
1596
1746
|
/**
|
|
1597
1747
|
* Lista paginada de localizaciones de factura de clientes.
|
|
1598
1748
|
*
|
|
1599
|
-
* Endpoint: GET /pgrl/
|
|
1749
|
+
* Endpoint: GET /pgrl/v1/localizaciones-factura-clientes
|
|
1750
|
+
* (la lista solo existe en v1; el v2 de este recurso es solo POST/PUT).
|
|
1600
1751
|
*/
|
|
1601
1752
|
async listLocalizacionesFacturaClientes(opts = {}) {
|
|
1602
|
-
|
|
1753
|
+
// v1, no v2: la LISTA solo existe en /pgrl/v1 (v2 responde 404 — solo
|
|
1754
|
+
// tiene POST/PUT). Verificado contra el API real y el Postman.
|
|
1755
|
+
return this.listResourceWithFiql('/pgrl/v1/localizaciones-factura-clientes', opts, {
|
|
1603
1756
|
COD_CLI: opts.codCliente,
|
|
1604
1757
|
COD_GRUPO_CLI: opts.grupoCliente,
|
|
1605
1758
|
});
|