@nubiia/mcp-freematica 0.6.3 → 0.7.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.
Files changed (43) hide show
  1. package/README.md +63 -13
  2. package/dist/clients/freematica-client.d.ts +190 -1
  3. package/dist/clients/freematica-client.js +281 -1
  4. package/dist/clients/freematica-client.js.map +1 -1
  5. package/dist/config.d.ts +22 -1
  6. package/dist/config.js +29 -1
  7. package/dist/config.js.map +1 -1
  8. package/dist/schemas/contratos.d.ts +268 -5
  9. package/dist/schemas/contratos.js +402 -1
  10. package/dist/schemas/contratos.js.map +1 -1
  11. package/dist/server-instructions.js +44 -1
  12. package/dist/server-instructions.js.map +1 -1
  13. package/dist/server.d.ts +6 -0
  14. package/dist/server.js +2 -2
  15. package/dist/server.js.map +1 -1
  16. package/dist/tools/contratos/cabecera.d.ts +7 -0
  17. package/dist/tools/contratos/cabecera.js +128 -0
  18. package/dist/tools/contratos/cabecera.js.map +1 -0
  19. package/dist/tools/contratos/index.d.ts +21 -0
  20. package/dist/tools/contratos/index.js +31 -0
  21. package/dist/tools/contratos/index.js.map +1 -0
  22. package/dist/tools/contratos/materiales.d.ts +4 -0
  23. package/dist/tools/{contratos.js → contratos/materiales.js} +5 -11
  24. package/dist/tools/contratos/materiales.js.map +1 -0
  25. package/dist/tools/contratos/opcionales.d.ts +5 -0
  26. package/dist/tools/contratos/opcionales.js +106 -0
  27. package/dist/tools/contratos/opcionales.js.map +1 -0
  28. package/dist/tools/contratos/servicios.d.ts +5 -0
  29. package/dist/tools/contratos/servicios.js +215 -0
  30. package/dist/tools/contratos/servicios.js.map +1 -0
  31. package/dist/transports/http.d.ts +2 -0
  32. package/dist/transports/http.js +3 -1
  33. package/dist/transports/http.js.map +1 -1
  34. package/dist/transports/stdio.d.ts +2 -0
  35. package/dist/transports/stdio.js +3 -1
  36. package/dist/transports/stdio.js.map +1 -1
  37. package/dist/types/contratos.d.ts +29 -0
  38. package/dist/utils/idreg.d.ts +37 -0
  39. package/dist/utils/idreg.js +52 -0
  40. package/dist/utils/idreg.js.map +1 -0
  41. package/package.json +21 -4
  42. package/dist/tools/contratos.d.ts +0 -10
  43. package/dist/tools/contratos.js.map +0 -1
package/README.md CHANGED
@@ -1,6 +1,12 @@
1
1
  # mcp-freematica
2
2
 
3
- MCP server que expone operaciones del API REST de Freemática para ser consumidas por Claude a través de Nubiia.
3
+ [![npm version](https://img.shields.io/npm/v/@nubiia/mcp-freematica)](https://www.npmjs.com/package/@nubiia/mcp-freematica)
4
+ [![license: MIT](https://img.shields.io/npm/l/@nubiia/mcp-freematica)](./LICENSE)
5
+ [![by Nubiia](https://img.shields.io/badge/by-Nubiia-6C4EE3)](https://nubiia.es)
6
+
7
+ MCP server que expone operaciones del API REST de Freemática (ERP: facturación, cartera, proveedores, contabilidad, personal) para ser consumidas por asistentes de IA como Claude.
8
+
9
+ > Built and maintained by **[Nubiia](https://nubiia.es)** — automatización e integraciones con IA para negocios (MCP, Holded, Pipedrive y más). ¿Quieres algo así para tu empresa? Escríbenos en **[nubiia.es](https://nubiia.es)**.
4
10
 
5
11
  ## Stack
6
12
 
@@ -53,6 +59,29 @@ MCP server que expone operaciones del API REST de Freemática para ser consumida
53
59
  | `freematica_list_albaranes_factura` | `GET /pven/v2/albaranes-facturas` | Lista paginada de vinculaciones albarán↔factura (idReg nativo + FIQL) |
54
60
  | `freematica_get_albaran_factura` | `GET /pven/v2/albaranes-facturas/{idReg}` | Detalle de una vinculación albarán-factura |
55
61
  | `freematica_list_resultados_facturacion` | `GET /pvss/v1/facturacion-resultados` | Resultados del proceso batch de facturación automática de vigilancia (FIQL) |
62
+ | `freematica_list_contratos` | `GET /pvss/v1/contratos` | Lista paginada de cabeceras de contratos (solo filtros nativos: el API ignora FIQL aquí) |
63
+ | `freematica_get_contrato` | `GET /pvss/v1/contratos` (scan) | Busca un contrato por empresa + código natural (paginación interna + filtrado en cliente) |
64
+ | `freematica_list_servicios_contrato` | `GET /pvss/v1/contratos/{idReg}/servicios` | Servicios de un contrato |
65
+ | `freematica_get_servicio_contrato` | `GET /pvss/v2/contratos-servicios/{idReg}` | Detalle de un servicio de contrato |
66
+ | `freematica_list_contratos_opcionales` | `GET /ppre/v2/contratos/opcionales` | Lista paginada de opcionales de contratos |
67
+ | `freematica_get_contrato_opcionales` | `GET /ppre/v2/contratos/opcionales/{idReg}` | Detalle de un registro de opcionales |
68
+
69
+ ### Tools de escritura (requieren `FREEMATICA_ENABLE_WRITES=true`)
70
+
71
+ Por defecto el servidor es de **solo lectura**. Con `FREEMATICA_ENABLE_WRITES=true` se registran además estas tools de creación y actualización. **No existe ninguna tool de borrado.** Todas las escrituras dejan log de auditoría (operación + endpoint + campos a nivel info; body completo a nivel debug).
72
+
73
+ | Tool | Endpoint Freemática | Descripción |
74
+ | ---------------------------------------------- | --------------------------------------------------------------- | ---------------------------------------------------------------- |
75
+ | `freematica_create_contrato` | `POST /pvss/v2/contratos` | Alta de cabecera de contrato |
76
+ | `freematica_update_contrato` | `PUT /pvss/v2/contratos/{idReg}` | Actualización de cabecera de contrato |
77
+ | `freematica_create_servicio_contrato` | `POST /pvss/v2/contratos/{idReg}/servicios` | Alta de servicio (identificación derivada del idReg) |
78
+ | `freematica_update_servicio_fechas` | `PUT /pvss/v2/contratos/{id}/servicio/{id}` | Fechas inicio/fin del servicio (mecanismo de baja) |
79
+ | `freematica_create_servicio_historico_precios` | `POST /pvss/v2/contratos/{id}/servicios-historico-precios/{id}` | Alta de revisión de precios del servicio |
80
+ | `freematica_update_servicio_historico_precios` | `PUT /pvss/v2/contratos/{id}/servicios-historico-precios/{id}` | Actualización de revisión de precios |
81
+ | `freematica_create_servicio_facturacion_txt` | `POST /pvss/v2/contratos/{id}/servicios-facturacion-txt/{id}` | Alta de línea de texto de facturación |
82
+ | `freematica_update_servicio_facturacion` | `PUT /pvss/v2/contratos/{id}/servicios-facturacion/{id}` | Datos de facturación del servicio (precios hora, forma de pago…) |
83
+ | `freematica_create_contrato_opcionales` | `POST /ppre/v2/contratos/opcionales` | Alta de opcionales de contrato |
84
+ | `freematica_update_contrato_opcionales` | `PUT /ppre/v2/contratos/opcionales/{idReg}` | Actualización de opcionales de contrato |
56
85
 
57
86
  ## Filtros tipados (FIQL interno)
58
87
 
@@ -228,18 +257,19 @@ El servidor lee toda su configuración de **variables de entorno** al arrancar.
228
257
 
229
258
  Variables de entorno opcionales para tuning de comportamiento en producción:
230
259
 
231
- | Variable | Default | Descripción |
232
- | --------------------------------------- | ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
233
- | `FREEMATICA_BASE_URL` | `https://api-p01.clientservicepanel.com/restsat/api` | Base URL del API de Freemática. Cambiar solo si Freemática cambia el host o se usa un entorno alternativo. |
234
- | `FREEMATICA_TIMEOUT_MS` | `30000` | Timeout por petición en milisegundos. Si el upstream no responde en este tiempo, el request se cancela con `network_error`. |
235
- | `FREEMATICA_MAX_RETRIES` | `3` | Número máximo de reintentos para errores 5xx y errores de red. Los errores 4xx nunca se reintentan. |
236
- | `FREEMATICA_CIRCUIT_BREAKER_THRESHOLD` | `5` | Número de operaciones lógicas fallidas consecutivas (post-retry-exhaustion) necesarias para abrir el circuit breaker. |
237
- | `FREEMATICA_CIRCUIT_BREAKER_TIMEOUT_MS` | `30000` | Tiempo en ms que el circuit breaker permanece abierto antes de pasar a half-open. |
238
- | `FREEMATICA_MAX_RESPONSE_SIZE_MB` | `10` | Tamaño máximo de respuesta aceptada del upstream en MB. Respuestas mayores se truncan con aviso en el campo `truncated`. |
239
- | `MCP_TRANSPORT` | `stdio` | Transporte: `stdio` (para Claude Desktop/Code) o `http` (para Nubiia). Equivale al flag `--transport=`. |
240
- | `MCP_PORT` | `3000` | Puerto TCP donde el servidor MCP expone `/mcp` y `/health`. Ignorado en modo stdio. |
241
- | `MCP_ALLOWED_ORIGINS` | `*` | CORS: lista de orígenes permitidos separados por coma, o `*` para todos. En producción restringir al dominio de Nubiia. Ignorado en modo stdio. |
242
- | `LOG_LEVEL` | `info` | Nivel de log para pino: `fatal`, `error`, `warn`, `info`, `debug`, `trace`. En producción usar `info`. En desarrollo usar `debug`. |
260
+ | Variable | Default | Descripción |
261
+ | --------------------------------------- | ---------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
262
+ | `FREEMATICA_BASE_URL` | `https://api-p01.clientservicepanel.com/restsat/api` | Base URL del API de Freemática. Cambiar solo si Freemática cambia el host o se usa un entorno alternativo. |
263
+ | `FREEMATICA_TIMEOUT_MS` | `30000` | Timeout por petición en milisegundos. Si el upstream no responde en este tiempo, el request se cancela con `network_error`. |
264
+ | `FREEMATICA_MAX_RETRIES` | `3` | Número máximo de reintentos para errores 5xx y errores de red. Los errores 4xx nunca se reintentan. |
265
+ | `FREEMATICA_CIRCUIT_BREAKER_THRESHOLD` | `5` | Número de operaciones lógicas fallidas consecutivas (post-retry-exhaustion) necesarias para abrir el circuit breaker. |
266
+ | `FREEMATICA_CIRCUIT_BREAKER_TIMEOUT_MS` | `30000` | Tiempo en ms que el circuit breaker permanece abierto antes de pasar a half-open. |
267
+ | `FREEMATICA_MAX_RESPONSE_SIZE_MB` | `10` | Tamaño máximo de respuesta aceptada del upstream en MB. Respuestas mayores se truncan con aviso en el campo `truncated`. |
268
+ | `FREEMATICA_ENABLE_WRITES` | `false` | Registra las tools de escritura (`freematica_create_*` / `freematica_update_*`). Sin ella el servidor es de solo lectura. Valores: `true`/`false`/`1`/`0`. |
269
+ | `MCP_TRANSPORT` | `stdio` | Transporte: `stdio` (para Claude Desktop/Code) o `http` (para Nubiia). Equivale al flag `--transport=`. |
270
+ | `MCP_PORT` | `3000` | Puerto TCP donde el servidor MCP expone `/mcp` y `/health`. Ignorado en modo stdio. |
271
+ | `MCP_ALLOWED_ORIGINS` | `*` | CORS: lista de orígenes permitidos separados por coma, o `*` para todos. En producción restringir al dominio de Nubiia. Ignorado en modo stdio. |
272
+ | `LOG_LEVEL` | `info` | Nivel de log para pino: `fatal`, `error`, `warn`, `info`, `debug`, `trace`. En producción usar `info`. En desarrollo usar `debug`. |
243
273
 
244
274
  ### De dónde salen las credenciales `x-auth-*`
245
275
 
@@ -510,6 +540,26 @@ node dist/index.js
510
540
  - API: `apidocs/Freematica API - Complete Collection.postman_collection.json`
511
541
  - CHANGELOG: `CHANGELOG.md`
512
542
 
543
+ ## About Nubiia
544
+
545
+ This MCP server is built and maintained by **[Nubiia](https://nubiia.es)**.
546
+
547
+ [Nubiia](https://nubiia.es) ayuda a empresas a **automatizar procesos e integrar sus herramientas con IA**: servidores MCP a medida, integraciones con ERPs y CRMs (Freemática, Holded, Pipedrive…), y agentes que conectan tus datos de negocio con asistentes como Claude. Este `@nubiia/mcp-freematica` es un ejemplo open source de lo que hacemos.
548
+
549
+ 👉 ¿Quieres una integración o automatización con IA para tu negocio? **[nubiia.es](https://nubiia.es)** · ✉️ [hola@nubiia.es](mailto:hola@nubiia.es)
550
+
551
+ ## Author
552
+
553
+ Built by **[Nubiia](https://nubiia.es)** — [nubiia.es](https://nubiia.es) · [hola@nubiia.es](mailto:hola@nubiia.es)
554
+
555
+ Maintainer: Samuel Fraga — [GitHub](https://github.com/iamsamuelfraga)
556
+
557
+ ## Links
558
+
559
+ - [Nubiia — AI automation & integrations](https://nubiia.es)
560
+ - [npm: @nubiia/mcp-freematica](https://www.npmjs.com/package/@nubiia/mcp-freematica)
561
+ - [GitHub: nubiia-dev/mcp-freematica](https://github.com/nubiia-dev/mcp-freematica)
562
+
513
563
  ## Licencia
514
564
 
515
565
  MIT — ver `LICENSE`.
@@ -7,7 +7,7 @@ import type { Cliente } from '../types/clientes.js';
7
7
  import type { ContactoCliente } from '../types/contactos-clientes.js';
8
8
  import type { MasterDataItem } from '../types/master-data.js';
9
9
  import type { OportunidadNegocio } from '../types/oportunidades-negocio.js';
10
- import type { VoContratosServMatAsignado } from '../types/contratos.js';
10
+ import type { VoContrato, VoContratoServicio, VoContratosOpcionales, VoContratosServMatAsignado, VoServiciosFac, VoServiciosFacTxt, VoServiciosHistPr } from '../types/contratos.js';
11
11
  export interface ListResult<T> {
12
12
  items: T[];
13
13
  total: number;
@@ -601,6 +601,195 @@ export declare class FreematicaClient extends BaseClient {
601
601
  traspasado?: string;
602
602
  order?: string;
603
603
  }): Promise<ListResult<Record<string, unknown>>>;
604
+ /**
605
+ * Lista paginada de cabeceras de contratos.
606
+ *
607
+ * Endpoint: GET /pvss/v1/contratos
608
+ *
609
+ * IMPORTANTE: este endpoint IGNORA silenciosamente el query param `rQuery`
610
+ * (verificado empíricamente contra el API real el 2026-07-02: con un filtro
611
+ * FIQL válido devuelve el dataset completo sin error). Por eso solo se
612
+ * exponen los filtros nativos `codEmpresa` y `codDelegacion`; cualquier otro
613
+ * filtrado debe hacerse en cliente (ver `getContrato`).
614
+ *
615
+ * @param opts - Paginación + filtros nativos empresa/delegación + order.
616
+ * @returns Lista paginada de cabeceras de contrato (campos CTRT_*).
617
+ */
618
+ listContratos(opts?: ListOptions & {
619
+ empresa?: string;
620
+ delegacion?: string;
621
+ order?: string;
622
+ }): Promise<ListResult<VoContrato>>;
623
+ /**
624
+ * Busca una cabecera de contrato por sus códigos naturales.
625
+ *
626
+ * No existe un endpoint GET singular para contratos pvss y el listado ignora
627
+ * `rQuery`, así que se pagina el listado (items=200, máximo permitido por el
628
+ * API) filtrando en cliente por CTRT_COD hasta encontrar la coincidencia.
629
+ *
630
+ * @param opts - Códigos naturales del contrato.
631
+ * @returns Cabecera del contrato.
632
+ * @throws FreematicaError('not_found') si no existe.
633
+ */
634
+ getContrato(opts: {
635
+ empresa: string;
636
+ delegacion?: string;
637
+ codContrato: string;
638
+ }): Promise<VoContrato>;
639
+ /**
640
+ * Lista paginada de servicios de un contrato.
641
+ *
642
+ * Endpoint: GET /pvss/v1/contratos/{idContrato}/servicios
643
+ *
644
+ * @param idContrato - idReg opaco del contrato (Base64 "EMP__DELEG__COD").
645
+ * @param opts - Paginación + order.
646
+ * @returns Lista paginada de servicios (campos CTRTS_*).
647
+ */
648
+ listServiciosContrato(idContrato: string, opts?: ListOptions & {
649
+ order?: string;
650
+ }): Promise<ListResult<VoContratoServicio>>;
651
+ /**
652
+ * Detalle de un servicio de contrato por `idReg` opaco.
653
+ *
654
+ * Endpoint: GET /pvss/v2/contratos-servicios/{idreg}
655
+ *
656
+ * @param idReg - idReg opaco del servicio (Base64 "EMP__DELEG__CTRT__COD").
657
+ * @returns Servicio completo.
658
+ */
659
+ getServicioContrato(idReg: string): Promise<VoContratoServicio>;
660
+ /**
661
+ * Lista paginada de opcionales de contratos.
662
+ *
663
+ * Endpoint: GET /ppre/v2/contratos/opcionales
664
+ *
665
+ * @param opts - Paginación + order.
666
+ * @returns Lista paginada de opcionales (campos CON2_*).
667
+ */
668
+ listContratosOpcionales(opts?: ListOptions & {
669
+ order?: string;
670
+ }): Promise<ListResult<VoContratosOpcionales>>;
671
+ /**
672
+ * Detalle de un registro de opcionales de contrato por `idReg` opaco.
673
+ *
674
+ * Endpoint: GET /ppre/v2/contratos/opcionales/{idReg}
675
+ */
676
+ getContratoOpcionales(idReg: string): Promise<VoContratosOpcionales>;
677
+ /**
678
+ * Alta de cabecera de contrato.
679
+ *
680
+ * Endpoint: POST /pvss/v2/contratos — body VoContratos.
681
+ * Campos requeridos por el API: CTRT_DELEG, CTRT_DES, CTRT_FECHA, CTRT_COD_CLI.
682
+ *
683
+ * @param body - Campos CTRT_* del contrato.
684
+ * @returns El contrato creado (VoContratos).
685
+ */
686
+ createContrato(body: VoContrato): Promise<VoContrato>;
687
+ /**
688
+ * Actualización de cabecera de contrato.
689
+ *
690
+ * Endpoint: PUT /pvss/v2/contratos/{idReg} — body VoContratos.
691
+ *
692
+ * @param idReg - idReg opaco del contrato.
693
+ * @param body - Campos CTRT_* a actualizar.
694
+ * @returns Respuesta del API.
695
+ */
696
+ updateContrato(idReg: string, body: VoContrato): Promise<VoContrato>;
697
+ /**
698
+ * Alta de un servicio en un contrato.
699
+ *
700
+ * Endpoint: POST /pvss/v2/contratos/{idReg}/servicios — body VoServicios.
701
+ * Campos requeridos por el API: CTRTS_EMP, CTRTS_DELEG, CTRTS_CTRT (los
702
+ * mismos valores codificados en el idReg del contrato).
703
+ *
704
+ * @param idContrato - idReg opaco del contrato.
705
+ * @param body - Campos CTRTS_* del servicio.
706
+ * @returns El servicio creado (VoServicios).
707
+ */
708
+ createServicioContrato(idContrato: string, body: VoContratoServicio): Promise<VoContratoServicio>;
709
+ /**
710
+ * Actualiza fechas de inicio y fin de un servicio.
711
+ *
712
+ * Endpoint: PUT /pvss/v2/contratos/{idContrato}/servicio/{idServicio}.
713
+ * Según el spec, este endpoint solo actualiza fecha inicio y fecha fin.
714
+ *
715
+ * @param idContrato - idReg opaco del contrato.
716
+ * @param idServicio - idReg opaco del servicio.
717
+ * @param body - Campos CTRTS_* (fechas) a actualizar.
718
+ * @returns Respuesta del API.
719
+ */
720
+ updateServicioFechas(idContrato: string, idServicio: string, body: VoContratoServicio): Promise<VoContratoServicio>;
721
+ /**
722
+ * Actualiza los datos de facturación de un servicio.
723
+ *
724
+ * Endpoint: PUT /pvss/v2/contratos/{idContrato}/servicios-facturacion/{idServicio}.
725
+ *
726
+ * @param idContrato - idReg opaco del contrato.
727
+ * @param idServicio - idReg opaco del servicio.
728
+ * @param body - Campos CTRTF_* a actualizar.
729
+ * @returns Respuesta del API.
730
+ */
731
+ updateServicioFacturacion(idContrato: string, idServicio: string, body: VoServiciosFac): Promise<VoServiciosFac>;
732
+ /**
733
+ * Alta de un texto de facturación de un servicio.
734
+ *
735
+ * Endpoint: POST /pvss/v2/contratos/{idContrato}/servicios-facturacion-txt/{idServicio}.
736
+ *
737
+ * @param idContrato - idReg opaco del contrato.
738
+ * @param idServicio - idReg opaco del servicio.
739
+ * @param body - Campos CTRTFL_* del texto.
740
+ * @returns Respuesta del API.
741
+ */
742
+ createServicioFacturacionTxt(idContrato: string, idServicio: string, body: VoServiciosFacTxt): Promise<VoServiciosFacTxt>;
743
+ /**
744
+ * Alta de un registro de histórico de precios de un servicio.
745
+ *
746
+ * Endpoint: POST /pvss/v2/contratos/{idContrato}/servicios-historico-precios/{idServicio}.
747
+ *
748
+ * @param idContrato - idReg opaco del contrato.
749
+ * @param idServicio - idReg opaco del servicio.
750
+ * @param body - Campos SERVHPR_* del histórico.
751
+ * @returns Respuesta del API (VoServiciosHistPr).
752
+ */
753
+ createServicioHistoricoPrecios(idContrato: string, idServicio: string, body: VoServiciosHistPr): Promise<VoServiciosHistPr>;
754
+ /**
755
+ * Actualiza un registro de histórico de precios de un servicio.
756
+ *
757
+ * Endpoint: PUT /pvss/v2/contratos/{idContrato}/servicios-historico-precios/{idServicio}.
758
+ *
759
+ * @param idContrato - idReg opaco del contrato.
760
+ * @param idServicio - idReg opaco del servicio.
761
+ * @param body - Campos SERVHPR_* a actualizar.
762
+ * @returns Respuesta del API.
763
+ */
764
+ updateServicioHistoricoPrecios(idContrato: string, idServicio: string, body: VoServiciosHistPr): Promise<VoServiciosHistPr>;
765
+ /**
766
+ * Alta de un registro de opcionales de contrato.
767
+ *
768
+ * Endpoint: POST /ppre/v2/contratos/opcionales — body VoContratosOpcionales.
769
+ * Campos requeridos por el API: CON2_CODEMP, CON2_DELEG, CON2_TIPOCONT,
770
+ * CON2_NUMCONT, CON2_FCHCONT.
771
+ *
772
+ * @param body - Campos CON2_* del registro.
773
+ * @returns El registro creado.
774
+ */
775
+ createContratoOpcionales(body: VoContratosOpcionales): Promise<VoContratosOpcionales>;
776
+ /**
777
+ * Actualiza un registro de opcionales de contrato.
778
+ *
779
+ * Endpoint: PUT /ppre/v2/contratos/opcionales/{idReg}.
780
+ *
781
+ * @param idReg - idReg opaco del registro de opcionales.
782
+ * @param body - Campos CON2_* a actualizar.
783
+ * @returns Respuesta del API.
784
+ */
785
+ updateContratoOpcionales(idReg: string, body: VoContratosOpcionales): Promise<VoContratosOpcionales>;
786
+ /**
787
+ * Log de auditoría de operaciones de escritura.
788
+ *
789
+ * Nivel info: operación + endpoint + claves del body (sin valores).
790
+ * Nivel debug: body completo, para poder reconstruir la operación.
791
+ */
792
+ private logWrite;
604
793
  private listResource;
605
794
  /**
606
795
  * Helper genérico para endpoints de lista que soportan paginación + filtros FIQL
@@ -1,4 +1,4 @@
1
- import { BaseClient } from './base-client.js';
1
+ import { BaseClient, FreematicaError } from './base-client.js';
2
2
  import { buildFiql, appendRquery } from './fiql-builder.js';
3
3
  import { CATALOG_ENDPOINTS } from '../schemas/master-data.js';
4
4
  import { ESTADO_CARTERA_FIQL_MAP, } from '../schemas/cartera.js';
@@ -1239,6 +1239,286 @@ export class FreematicaClient extends BaseClient {
1239
1239
  return { items: data.items, total: Number(data.total) };
1240
1240
  }
1241
1241
  // ---------------------------------------------------------------------------
1242
+ // Contratos y Servicios (v0.7.0) — lectura
1243
+ // ---------------------------------------------------------------------------
1244
+ /**
1245
+ * Lista paginada de cabeceras de contratos.
1246
+ *
1247
+ * Endpoint: GET /pvss/v1/contratos
1248
+ *
1249
+ * IMPORTANTE: este endpoint IGNORA silenciosamente el query param `rQuery`
1250
+ * (verificado empíricamente contra el API real el 2026-07-02: con un filtro
1251
+ * FIQL válido devuelve el dataset completo sin error). Por eso solo se
1252
+ * exponen los filtros nativos `codEmpresa` y `codDelegacion`; cualquier otro
1253
+ * filtrado debe hacerse en cliente (ver `getContrato`).
1254
+ *
1255
+ * @param opts - Paginación + filtros nativos empresa/delegación + order.
1256
+ * @returns Lista paginada de cabeceras de contrato (campos CTRT_*).
1257
+ */
1258
+ async listContratos(opts = {}) {
1259
+ const url = new URL('https://placeholder/pvss/v1/contratos');
1260
+ if (opts.items !== undefined)
1261
+ url.searchParams.set('items', String(opts.items));
1262
+ if (opts.page !== undefined)
1263
+ url.searchParams.set('page', String(opts.page));
1264
+ if (opts.empresa !== undefined)
1265
+ url.searchParams.set('codEmpresa', opts.empresa);
1266
+ if (opts.delegacion !== undefined)
1267
+ url.searchParams.set('codDelegacion', opts.delegacion);
1268
+ if (opts.order !== undefined)
1269
+ url.searchParams.set('order', opts.order);
1270
+ const path = url.pathname + (url.search ? url.search : '');
1271
+ const data = await this.get(path);
1272
+ return { items: data.items, total: Number(data.total) };
1273
+ }
1274
+ /**
1275
+ * Busca una cabecera de contrato por sus códigos naturales.
1276
+ *
1277
+ * No existe un endpoint GET singular para contratos pvss y el listado ignora
1278
+ * `rQuery`, así que se pagina el listado (items=200, máximo permitido por el
1279
+ * API) filtrando en cliente por CTRT_COD hasta encontrar la coincidencia.
1280
+ *
1281
+ * @param opts - Códigos naturales del contrato.
1282
+ * @returns Cabecera del contrato.
1283
+ * @throws FreematicaError('not_found') si no existe.
1284
+ */
1285
+ async getContrato(opts) {
1286
+ const PAGE_SIZE = 200;
1287
+ const MAX_PAGES = 100; // guardarraíl: 20.000 contratos
1288
+ for (let page = 1; page <= MAX_PAGES; page++) {
1289
+ const { items } = await this.listContratos({
1290
+ empresa: opts.empresa,
1291
+ delegacion: opts.delegacion,
1292
+ page,
1293
+ items: PAGE_SIZE,
1294
+ });
1295
+ const match = items.find((c) => String(c['CTRT_COD']) === opts.codContrato &&
1296
+ String(c['CTRT_EMP']) === opts.empresa &&
1297
+ (opts.delegacion === undefined || String(c['CTRT_DELEG']) === opts.delegacion));
1298
+ if (match)
1299
+ return match;
1300
+ if (items.length < PAGE_SIZE)
1301
+ break;
1302
+ }
1303
+ throw new FreematicaError('not_found', `Contrato no encontrado: empresa=${opts.empresa} delegacion=${opts.delegacion ?? '*'} codContrato=${opts.codContrato}`);
1304
+ }
1305
+ /**
1306
+ * Lista paginada de servicios de un contrato.
1307
+ *
1308
+ * Endpoint: GET /pvss/v1/contratos/{idContrato}/servicios
1309
+ *
1310
+ * @param idContrato - idReg opaco del contrato (Base64 "EMP__DELEG__COD").
1311
+ * @param opts - Paginación + order.
1312
+ * @returns Lista paginada de servicios (campos CTRTS_*).
1313
+ */
1314
+ async listServiciosContrato(idContrato, opts = {}) {
1315
+ const url = new URL(`https://placeholder/pvss/v1/contratos/${encodeURIComponent(idContrato)}/servicios`);
1316
+ if (opts.items !== undefined)
1317
+ url.searchParams.set('items', String(opts.items));
1318
+ if (opts.page !== undefined)
1319
+ url.searchParams.set('page', String(opts.page));
1320
+ if (opts.order !== undefined)
1321
+ url.searchParams.set('order', opts.order);
1322
+ const path = url.pathname + (url.search ? url.search : '');
1323
+ const data = await this.get(path);
1324
+ return { items: data.items, total: Number(data.total) };
1325
+ }
1326
+ /**
1327
+ * Detalle de un servicio de contrato por `idReg` opaco.
1328
+ *
1329
+ * Endpoint: GET /pvss/v2/contratos-servicios/{idreg}
1330
+ *
1331
+ * @param idReg - idReg opaco del servicio (Base64 "EMP__DELEG__CTRT__COD").
1332
+ * @returns Servicio completo.
1333
+ */
1334
+ async getServicioContrato(idReg) {
1335
+ return this.get(`/pvss/v2/contratos-servicios/${encodeURIComponent(idReg)}`);
1336
+ }
1337
+ /**
1338
+ * Lista paginada de opcionales de contratos.
1339
+ *
1340
+ * Endpoint: GET /ppre/v2/contratos/opcionales
1341
+ *
1342
+ * @param opts - Paginación + order.
1343
+ * @returns Lista paginada de opcionales (campos CON2_*).
1344
+ */
1345
+ async listContratosOpcionales(opts = {}) {
1346
+ const url = new URL('https://placeholder/ppre/v2/contratos/opcionales');
1347
+ if (opts.items !== undefined)
1348
+ url.searchParams.set('items', String(opts.items));
1349
+ if (opts.page !== undefined)
1350
+ url.searchParams.set('page', String(opts.page));
1351
+ if (opts.order !== undefined)
1352
+ url.searchParams.set('order', opts.order);
1353
+ const path = url.pathname + (url.search ? url.search : '');
1354
+ const data = await this.get(path);
1355
+ return { items: data.items, total: Number(data.total) };
1356
+ }
1357
+ /**
1358
+ * Detalle de un registro de opcionales de contrato por `idReg` opaco.
1359
+ *
1360
+ * Endpoint: GET /ppre/v2/contratos/opcionales/{idReg}
1361
+ */
1362
+ async getContratoOpcionales(idReg) {
1363
+ return this.get(`/ppre/v2/contratos/opcionales/${encodeURIComponent(idReg)}`);
1364
+ }
1365
+ // ---------------------------------------------------------------------------
1366
+ // Contratos y Servicios (v0.7.0) — escritura (create/update, sin delete)
1367
+ //
1368
+ // Todas las operaciones de escritura se registran en el log (nivel info con
1369
+ // la operación y claves del registro; nivel debug con el body completo) para
1370
+ // soportar auditoría y rollback manual.
1371
+ // ---------------------------------------------------------------------------
1372
+ /**
1373
+ * Alta de cabecera de contrato.
1374
+ *
1375
+ * Endpoint: POST /pvss/v2/contratos — body VoContratos.
1376
+ * Campos requeridos por el API: CTRT_DELEG, CTRT_DES, CTRT_FECHA, CTRT_COD_CLI.
1377
+ *
1378
+ * @param body - Campos CTRT_* del contrato.
1379
+ * @returns El contrato creado (VoContratos).
1380
+ */
1381
+ async createContrato(body) {
1382
+ this.logWrite('createContrato', 'POST /pvss/v2/contratos', body);
1383
+ return this.post('/pvss/v2/contratos', body);
1384
+ }
1385
+ /**
1386
+ * Actualización de cabecera de contrato.
1387
+ *
1388
+ * Endpoint: PUT /pvss/v2/contratos/{idReg} — body VoContratos.
1389
+ *
1390
+ * @param idReg - idReg opaco del contrato.
1391
+ * @param body - Campos CTRT_* a actualizar.
1392
+ * @returns Respuesta del API.
1393
+ */
1394
+ async updateContrato(idReg, body) {
1395
+ this.logWrite('updateContrato', `PUT /pvss/v2/contratos/${idReg}`, body);
1396
+ return this.put(`/pvss/v2/contratos/${encodeURIComponent(idReg)}`, body);
1397
+ }
1398
+ /**
1399
+ * Alta de un servicio en un contrato.
1400
+ *
1401
+ * Endpoint: POST /pvss/v2/contratos/{idReg}/servicios — body VoServicios.
1402
+ * Campos requeridos por el API: CTRTS_EMP, CTRTS_DELEG, CTRTS_CTRT (los
1403
+ * mismos valores codificados en el idReg del contrato).
1404
+ *
1405
+ * @param idContrato - idReg opaco del contrato.
1406
+ * @param body - Campos CTRTS_* del servicio.
1407
+ * @returns El servicio creado (VoServicios).
1408
+ */
1409
+ async createServicioContrato(idContrato, body) {
1410
+ this.logWrite('createServicioContrato', `POST /pvss/v2/contratos/${idContrato}/servicios`, body);
1411
+ return this.post(`/pvss/v2/contratos/${encodeURIComponent(idContrato)}/servicios`, body);
1412
+ }
1413
+ /**
1414
+ * Actualiza fechas de inicio y fin de un servicio.
1415
+ *
1416
+ * Endpoint: PUT /pvss/v2/contratos/{idContrato}/servicio/{idServicio}.
1417
+ * Según el spec, este endpoint solo actualiza fecha inicio y fecha fin.
1418
+ *
1419
+ * @param idContrato - idReg opaco del contrato.
1420
+ * @param idServicio - idReg opaco del servicio.
1421
+ * @param body - Campos CTRTS_* (fechas) a actualizar.
1422
+ * @returns Respuesta del API.
1423
+ */
1424
+ async updateServicioFechas(idContrato, idServicio, body) {
1425
+ this.logWrite('updateServicioFechas', `PUT /pvss/v2/contratos/${idContrato}/servicio/${idServicio}`, body);
1426
+ return this.put(`/pvss/v2/contratos/${encodeURIComponent(idContrato)}/servicio/${encodeURIComponent(idServicio)}`, body);
1427
+ }
1428
+ /**
1429
+ * Actualiza los datos de facturación de un servicio.
1430
+ *
1431
+ * Endpoint: PUT /pvss/v2/contratos/{idContrato}/servicios-facturacion/{idServicio}.
1432
+ *
1433
+ * @param idContrato - idReg opaco del contrato.
1434
+ * @param idServicio - idReg opaco del servicio.
1435
+ * @param body - Campos CTRTF_* a actualizar.
1436
+ * @returns Respuesta del API.
1437
+ */
1438
+ async updateServicioFacturacion(idContrato, idServicio, body) {
1439
+ this.logWrite('updateServicioFacturacion', `PUT /pvss/v2/contratos/${idContrato}/servicios-facturacion/${idServicio}`, body);
1440
+ return this.put(`/pvss/v2/contratos/${encodeURIComponent(idContrato)}/servicios-facturacion/${encodeURIComponent(idServicio)}`, body);
1441
+ }
1442
+ /**
1443
+ * Alta de un texto de facturación de un servicio.
1444
+ *
1445
+ * Endpoint: POST /pvss/v2/contratos/{idContrato}/servicios-facturacion-txt/{idServicio}.
1446
+ *
1447
+ * @param idContrato - idReg opaco del contrato.
1448
+ * @param idServicio - idReg opaco del servicio.
1449
+ * @param body - Campos CTRTFL_* del texto.
1450
+ * @returns Respuesta del API.
1451
+ */
1452
+ async createServicioFacturacionTxt(idContrato, idServicio, body) {
1453
+ this.logWrite('createServicioFacturacionTxt', `POST /pvss/v2/contratos/${idContrato}/servicios-facturacion-txt/${idServicio}`, body);
1454
+ return this.post(`/pvss/v2/contratos/${encodeURIComponent(idContrato)}/servicios-facturacion-txt/${encodeURIComponent(idServicio)}`, body);
1455
+ }
1456
+ /**
1457
+ * Alta de un registro de histórico de precios de un servicio.
1458
+ *
1459
+ * Endpoint: POST /pvss/v2/contratos/{idContrato}/servicios-historico-precios/{idServicio}.
1460
+ *
1461
+ * @param idContrato - idReg opaco del contrato.
1462
+ * @param idServicio - idReg opaco del servicio.
1463
+ * @param body - Campos SERVHPR_* del histórico.
1464
+ * @returns Respuesta del API (VoServiciosHistPr).
1465
+ */
1466
+ async createServicioHistoricoPrecios(idContrato, idServicio, body) {
1467
+ this.logWrite('createServicioHistoricoPrecios', `POST /pvss/v2/contratos/${idContrato}/servicios-historico-precios/${idServicio}`, body);
1468
+ return this.post(`/pvss/v2/contratos/${encodeURIComponent(idContrato)}/servicios-historico-precios/${encodeURIComponent(idServicio)}`, body);
1469
+ }
1470
+ /**
1471
+ * Actualiza un registro de histórico de precios de un servicio.
1472
+ *
1473
+ * Endpoint: PUT /pvss/v2/contratos/{idContrato}/servicios-historico-precios/{idServicio}.
1474
+ *
1475
+ * @param idContrato - idReg opaco del contrato.
1476
+ * @param idServicio - idReg opaco del servicio.
1477
+ * @param body - Campos SERVHPR_* a actualizar.
1478
+ * @returns Respuesta del API.
1479
+ */
1480
+ async updateServicioHistoricoPrecios(idContrato, idServicio, body) {
1481
+ this.logWrite('updateServicioHistoricoPrecios', `PUT /pvss/v2/contratos/${idContrato}/servicios-historico-precios/${idServicio}`, body);
1482
+ return this.put(`/pvss/v2/contratos/${encodeURIComponent(idContrato)}/servicios-historico-precios/${encodeURIComponent(idServicio)}`, body);
1483
+ }
1484
+ /**
1485
+ * Alta de un registro de opcionales de contrato.
1486
+ *
1487
+ * Endpoint: POST /ppre/v2/contratos/opcionales — body VoContratosOpcionales.
1488
+ * Campos requeridos por el API: CON2_CODEMP, CON2_DELEG, CON2_TIPOCONT,
1489
+ * CON2_NUMCONT, CON2_FCHCONT.
1490
+ *
1491
+ * @param body - Campos CON2_* del registro.
1492
+ * @returns El registro creado.
1493
+ */
1494
+ async createContratoOpcionales(body) {
1495
+ this.logWrite('createContratoOpcionales', 'POST /ppre/v2/contratos/opcionales', body);
1496
+ return this.post('/ppre/v2/contratos/opcionales', body);
1497
+ }
1498
+ /**
1499
+ * Actualiza un registro de opcionales de contrato.
1500
+ *
1501
+ * Endpoint: PUT /ppre/v2/contratos/opcionales/{idReg}.
1502
+ *
1503
+ * @param idReg - idReg opaco del registro de opcionales.
1504
+ * @param body - Campos CON2_* a actualizar.
1505
+ * @returns Respuesta del API.
1506
+ */
1507
+ async updateContratoOpcionales(idReg, body) {
1508
+ this.logWrite('updateContratoOpcionales', `PUT /ppre/v2/contratos/opcionales/${idReg}`, body);
1509
+ return this.put(`/ppre/v2/contratos/opcionales/${encodeURIComponent(idReg)}`, body);
1510
+ }
1511
+ /**
1512
+ * Log de auditoría de operaciones de escritura.
1513
+ *
1514
+ * Nivel info: operación + endpoint + claves del body (sin valores).
1515
+ * Nivel debug: body completo, para poder reconstruir la operación.
1516
+ */
1517
+ logWrite(operation, endpoint, body) {
1518
+ logger.info({ operation, endpoint, fields: Object.keys(body) }, 'freematica write');
1519
+ logger.debug({ operation, endpoint, body }, 'freematica write body');
1520
+ }
1521
+ // ---------------------------------------------------------------------------
1242
1522
  // Internal helpers
1243
1523
  // ---------------------------------------------------------------------------
1244
1524
  async listResource(path, opts) {