Llamadas a la API SAGE 200c — FACTSYNC

Este documento recoge todas las llamadas HTTP que el conector sage200c.php realiza contra la API de SAGE 200c: autenticación, lecturas (GET), escrituras (POST/PUT) y descargas de ficheros. Para cada llamada se especifica la URL, los parámetros enviados, la estructura de la respuesta y los campos que el conector lee efectivamente.

Índice

  1. Autenticación OAuth2
  2. Mecanismo común de consulta GET
  3. Mapa de llamadas (directas vs indirectas)
  4. GET /api/clientes — Clientes
  5. GET /api/cadenas — Cadenas
  6. GET /api/domicilios — Direcciones
  7. GET /api/sector — Sectores
  8. GET /api/comisionistas — Empleados
  9. GET /api/familias — Familias y grupos
  10. GET /api/articulos — Artículos
  11. GET /api/imagenes_articulos — Índice de imágenes
  12. GET /api/imagen/{id} — Imagen individual
  13. GET /api/almacenes — Almacenes
  14. GET /api/stock — Stocks
  15. GET /api/divisas — Divisas
  16. GET /api/iva — Tipos de IVA
  17. GET /api/tarifas — Líneas de descuento
  18. GET /api/tarifa_precio — Precios por tarifa
  19. GET /api/articulos_cliente — Precios especiales por cliente
  20. GET /api/condiciones_especiales — Condiciones especiales
  21. GET /api/pedidos — Lista de pedidos
  22. GET /api/pedido/{any}-{serie}-{num} — Detalle de pedido
  23. GET /api/albaranes — Lista de albaranes
  24. GET /api/albaran/{any}-{serie}-{num} — Detalle de albarán
  25. GET /api/facturas — Lista de facturas
  26. GET /api/factura/{any}-{serie}-{num} — Detalle de factura
  27. GET /api/doc/{path} — Fichero adjunto
  28. POST /api/clientes — Crear cliente
  29. PUT /api/clientes/{codi} — Actualizar cliente
  30. GET /api/formas_pago — Formas de pago
  31. GET /api/campos — Campos personalizados
  32. GET /api/idiomas_articulos — Traducciones de artículos
  33. POST /api/pedidos — Exportar pedido
  34. Gestión de errores y reintentos

1. Autenticación OAuth2

POST INDIRECTA oauth/token Obtiene el Bearer token para todas las llamadas posteriores
Disparada por: antes de cualquier llamada autenticada cuando SAGE_TOKEN está vacío, ha caducado, o tras una respuesta HTTP 401.

Cabeceras enviadas

CabeceraValor
AuthorizationBasic {base64(SAGE_USERNAME:SAGE_PASSWORD)}
Content-Typeapplication/x-www-form-urlencoded

Cuerpo de la petición (form-encoded)

grant_type=client_credentials

Campos de la respuesta leídos

CampoTipoUso interno
data.tokenstringBearer token guardado en SAGE_TOKEN
data.expires_ininteger (minutos)Convertido a Unix timestamp: time() + expires_in × 60SAGE_EXPIRES
Caducidad y reintento: Antes de cada llamada, el conector comprueba time() >= SAGE_EXPIRES. Si el token ha caducado se vuelve a solicitar automáticamente. Además, cualquier respuesta HTTP 401 invalida el token y reintenta la llamada original una sola vez con un token nuevo.

2. Mecanismo común de consulta GET

Todas las llamadas GET comparten el mismo sistema de construcción de querystring y la misma estructura de respuesta.

Parámetros de querystring estándar

ParámetroTipoDescripciónEjemplo
fields array JSON Columnas a devolver. Si está vacío, la API devuelve todos los campos. ["CodigoCliente","RazonSocial"]
filters array JSON Expresiones de filtro en formato cadena. Las comillas tipográficas (´) se convierten a comillas simples (') antes de enviar. ["CodigoSubfamilia = '**********'","BajaEmpresaLc = 0"]
order_by array JSON Expresiones de ordenación. ["CodigoCliente ASC"]
next integer Offset de paginación (fila inicial de la página). 100
limit integer Número máximo de filas por página. 50

Estructura de respuesta estándar

{ "data": { "rows": [ array de registros ] // registros de la página actual }, "meta": { "Total": integer, // total de registros sin paginación "nextpage": "next=N&limit=M" // presente si hay página siguiente }, "http_status": integer }
Paginación: El conector lee meta.Total y calcula si hay página siguiente comparando el total de filas recibidas con el total esperado. Si meta.nextpage está presente, se realiza una nueva llamada añadiendo el valor de next incrementado. El comportamiento (paginación en el servidor o en el cliente) se controla con pagination_server en la configuración del método.

Cabeceras enviadas en todas las llamadas

CabeceraValor
AuthorizationBearer {SAGE_TOKEN}
Content-Typeapplication/json

Opciones cURL globales

OpciónValor
CURLOPT_RETURNTRANSFERtrue
CURLOPT_ENCODING'' (auto)
CURLOPT_MAXREDIRS10
CURLOPT_TIMEOUT0 (sin límite)
CURLOPT_FOLLOWLOCATIONtrue
CURLOPT_HTTP_VERSIONCURL_HTTP_VERSION_1_1
CURLOPT_SSL_VERIFYPEERfalse
CURLOPT_SSL_VERIFYHOSTfalse

2.bis Mapa de llamadas (directas vs indirectas)

El conector hace dos tipos de llamadas a la API de SAGE 200c:

La siguiente tabla resume qué método FACTSYNC dispara cada llamada. Cada endpoint individual repite esta información en su cabecera con un badge y la línea «Disparada por».

Endpoint Tipo Disparada por
POST oauth/token INDIRECTA Antes de cualquier llamada autenticada cuando el token está caducado o ante una respuesta 401
GET api/clientes DIRECTA customers GET
GET api/cadenas INDIRECTA Durante customers GET, vía _get_codigocliente_from_cadena() cuando el cliente tiene CodigoCadena_
GET api/domicilios DIRECTA addresses GET
GET api/sector DIRECTA sectors GET
GET api/comisionistas DIRECTA employees GET
GET api/familias DIRECTA product_families GET y product_groups GET (mismo endpoint, distinto filtro)
GET api/articulos DIRECTA products GET
GET api/imagenes_articulos DIRECTA product_images GET
GET api/imagen/{id} INDIRECTA Durante product_images GET, una llamada por imagen vía _get_base64_image()
GET api/almacenes DIRECTA warehouses GET
GET api/stock DIRECTA stocks GET
GET api/divisas DIRECTA currencies GET
GET api/iva INDIRECTA Cache global vía _init_tipus_iva(); usado por products GET, conditions GET y external_documents GET
GET api/tarifas DIRECTA discount_lines GET
GET api/tarifa_precio DIRECTA conditions GET (1ª pasada)
GET api/articulos_cliente INDIRECTA Durante conditions GET (2ª pasada)
GET api/condiciones_especiales INDIRECTA Durante conditions GET (3ª pasada)
GET api/pedidos DIRECTA external_documents GET con {type:"orders"}
GET api/pedido/{any}-{serie}-{num} INDIRECTA Durante external_documents GET {type:orders}, una llamada por pedido
GET api/albaranes DIRECTA external_documents GET con {type:"delivery_notes"}
GET api/albaran/{any}-{serie}-{num} INDIRECTA Durante external_documents GET {type:delivery_notes}, una llamada por albarán
GET api/facturas DIRECTA external_documents GET con {type:"invoices"}
GET api/factura/{any}-{serie}-{num} INDIRECTA Durante external_documents GET {type:invoices}, una llamada por factura
GET api/doc/{path} INDIRECTA Durante external_documents GET (cualquier tipo), una llamada por adjunto
GET api/formas_pago DIRECTA payment_methods GET
GET api/campos DIRECTA custom_fields GET (directa) y resolución de definiciones de características (indirecta)
GET api/idiomas_articulos DIRECTA translations GET
POST api/clientes DIRECTA customers POST (exportación)
PUT api/clientes/{codi} DIRECTA customers PUT (exportación)
POST api/pedidos DIRECTA orders POST (exportación)
Implicación práctica: los métodos cuyas llamadas indirectas dependen del número de registros (imágenes, detalles de documentos, adjuntos) escalan de forma multiplicativa. Por ejemplo, importar 1.000 facturas con un adjunto de media supone 1 + 1.000 + 1.000 = 2.001 llamadas a la API.

3. GET /api/clientes — Clientes

GET DIRECTA api/clientes Importación de clientes (cli_ficha) paginada
Disparada por: método FACTSYNC customers GET.

Campos de la respuesta leídos (data.rows[])

Campo SAGETipoCampo SoftbaseNotas
CodigoClientestringid_extern / __id_extern__Clave externa principal
CodigoCadena_stringid_parentSi difiere de CodigoCliente → cliente hijo de cadena
RazonSocialstringempresa
Nombrestringnom
Email1stringemail
Telefonostringtelefon
Telefono2stringtelefon2
Telefono3stringtelefon_movil
Faxstringfax
CifDnistringnif / num_doc
FechaAltaISO 8601 datetimedata_altaConvertido a Unix timestamp
CodigoContablestringsubcuenta
CodigoDivisastringid_divisaResuelto vía _get_id_divisa()
FormadePagostringid_forma_pagoResuelto vía _get_id_forma_pago()
TarifaPreciostringid_linea_descompteResuelto vía _get_id_linia_descompte()
CodigoRuta_stringid_ruta
CodigoComisionistastringid_treballadorComisionista 1 (principal)
CodigoComisionista2_string__treballadors[]Comisionista 2
CodigoComisionista3_string__treballadors[]Comisionista 3
CodigoComisionista4_string__treballadors[]Comisionista 4
CodigoSector_stringid_sectorFuente seleccionable vía SAGE_SECTORS
CodigoTipoClienteLcstringid_sectorFuente alternativa si SAGE_SECTORS='tipus_client'
CodigoColectivoClienteLcstringid_sectorFuente alternativa si SAGE_SECTORS='colectius'
ObservacionesClientestringobservacions
IBANstringCC_iban
SiglaNacionstringCC_pais
RiesgoMaximofloatcredit
FechaNacimientoISO 8601 datetimedata_naixementConvertido a Unix timestamp
ClaveIVAstringclau_iva
%Descuentofloatdescompte1
BajaEmpresaLcintegerarxivat0 → 'N' · ≠0 → 'Y'
FechaBajaLcISO 8601 datetimedata_baixaConvertido a Unix timestamp
DIRestringcodi_dir3_gestor
DomiciliostringcarrerDirección completa del cliente (en Sage el Domicilio ya contiene la calle entera; enriquecida vía _add_address_to_data())
CodigoPostalstringcp
Municipiostringpoblacio
Provinciastringprovincia
NacionstringpaisNombre del país
CodigoNacionintegerUsado internamente para la resolución de país
Lógica de cadena: Si CodigoCadena_ existe y es diferente de CodigoCliente, el cliente es hijo de una cadena. El conector busca el cliente padre vía _get_codigocliente_from_cadena() y asigna id_parent, enllacar_factures='N' y enllacar_condicions='Y'.

4. GET /api/cadenas — Cadenas

GET INDIRECTA api/cadenas Tabla de resolución cadena → cliente cabecera (caché estática)
Disparada por: durante customers GET, vía _get_codigocliente_from_cadena(), cuando el cliente importado tiene CodigoCadena_. Se cachea en variable estática: una sola llamada por sesión.

Esta llamada se realiza una sola vez por sesión de sync y el resultado se guarda en una variable estática para evitar repetir la consulta. Se utiliza para construir el mapa CodigoCadena_ → CodigoCliente que se usa durante la importación de clientes.

Campos de la respuesta leídos (data.rows[])

Campo SAGETipoUso
CodigoCadena_stringClave del mapa (código de cadena)
CodigoClientestringValor del mapa (código del cliente cabecera)
CodigoEmpresaintegerLeído pero no mapeado a Softbase
Cadena_stringNombre de la cadena (no mapeado)
RazonSocialstringLeído pero no mapeado
NombrestringLeído pero no mapeado
SiglaNacionstringLeído pero no mapeado
CifDnistringLeído pero no mapeado

5. GET /api/domicilios — Direcciones

GET DIRECTA api/domicilios Importación de direcciones secundarias (cli_direccions) paginada
Disparada por: método FACTSYNC addresses GET.

Campos de la respuesta leídos (data.rows[])

Campo SAGETipoCampo SoftbaseNotas
IdDomiciliostring/integer__id_extern__Clave externa
CodigoClientestringid_tablaResuelto a cli_ficha.id vía _get_id_client()
NumeroDomiciliointegerSi = 0, el registro se ignora (es la dirección principal)
RazonSocialstringnomCombinado con TipoDomicilio
TipoDomiciliostringdefecte'F''Y' · otros → 'N'
DomiciliostringcarrerCombinado con Domicilio2
Domicilio2stringcarrerSe añade al campo anterior
Numero1stringnumero
Pisostringpis
Escalerastringescala
Puertastringporta
CodigoPostalstringcp
Municipiostringpoblacio
Provinciastringprovincia
Nacionstringpais
CodigoNacionintegerUsado para búsqueda de país
SiglaNacionstringpais_codi
Telefonostringtelefon
Telefono2stringtelefon_movil

6. GET /api/sector — Sectores

GET DIRECTA api/sector Importación de sectores de cliente (cli_sectors) paginada
Disparada por: método FACTSYNC sectors GET.

Campos de la respuesta leídos (data.rows[])

Campo SAGETipoCampo Softbase
CodigoSector_string__id_extern__
DescripcionSector_stringdescripcio
El endpoint exacto varía en función del valor de SAGE_SECTORS: api/sector (valor sectors), api/tipos_cliente (valor tipus_client) o api/colectivos_clientes (valor colectius). Los campos devueltos difieren: api/tipos_cliente usa CodigoTipoClienteLc / TipoClienteLc; api/colectivos_clientes usa CodigoColectivoClienteLc / ColectivoClienteLc.

7. GET /api/comisionistas — Empleados

GET DIRECTA api/comisionistas Importación de empleados / comerciales (ficha_treballador) paginada
Disparada por: método FACTSYNC employees GET.

Campos de la respuesta leídos (data.rows[])

Campo SAGETipoCampo Softbase
CodigoComisionistastring__id_extern__
Comisionistastringnom
CifDnistringnif
Telefonostringtelefon
Telefono2stringtelefon_movil
EMail1stringemail
Domiciliostringcarrer
CodigoPostalstringcp
Municipiostringpoblacio
Provinciastringprovincia
Nacionstringpais
SiglaNacionstringpais_codi

8. GET /api/familias — Familias y grupos de artículos

El conector llama dos veces al mismo endpoint con filtros opuestos para separar familias de grupos.

GET DIRECTA api/familias Pasada 1 — Familias (art_families) paginada
Disparada por: método FACTSYNC product_families GET.

Filtro aplicado

filters: ["CodigoSubfamilia = '**********'"]

Campos leídos

Campo SAGETipoCampo Softbase
CodigoFamiliastring__id_extern__
Descripcionstringdescripcio
CodigoSubfamiliastringUsado para el filtro, valor siempre **********
GET DIRECTA api/familias Pasada 2 — Grupos / subfamilias (art_articles) paginada
Disparada por: método FACTSYNC product_groups GET.

Filtro aplicado

filters: ["CodigoSubfamilia <> '**********'"]

Campos leídos

Campo SAGETipoCampo Softbase
CodigoSubfamiliastring__id_extern__
Descripcionstringdescripcio
CodigoFamiliastringid_familia (resuelto vía _get_id_familia())

9. GET /api/articulos — Artículos

GET DIRECTA api/articulos Importación de artículos / subartículos (art_subarticles) paginada
Disparada por: método FACTSYNC products GET. La resolución del campo GrupoIva dispara adicionalmente GET api/iva (sección 15) la primera vez.

Campos de la respuesta leídos (data.rows[])

Campo SAGETipoCampo SoftbaseNotas
CodigoArticulostring__id_extern__ / codiClave externa y código interno
DescripcionArticulostringmodel
CodigoFamiliastringid_familiaResuelto vía _get_id_familia()
CodigoSubfamiliastringid_articleResuelto vía _get_id_article()
PrecioComprafloatpreu_cost
PrecioVentafloat__preus[TARIFA_BASE]Precio de venta base
PrecioVentasinIVA1float__preus[LINIA_DESC_1]Precio para línea de descuento 1
PrecioVentasinIVA2float__preus[LINIA_DESC_2]Precio para línea de descuento 2
PrecioVentasinIVA3float__preus[LINIA_DESC_3]Precio para línea de descuento 3
PrecioOfertasinIVAfloat__preus[TARIFA_BASE_OFERTA]Solo si la oferta está activa
FechaInicioOfertaISO 8601 datetime__preus[].data_iniFecha de inicio de la oferta
FechaFinalOfertaISO 8601 datetime__preus[].data_fiFecha fin de la oferta (ajustada a 23:59:59)
GrupoIvastringid_tipus_ivaResuelto vía _get_tipus_iva()
ObsoletoLcintegerarxivat / web≠0 → arxivat='Y', web='N'
FechaAltaISO 8601 datetimedata_altaConvertido a Unix timestamp
CodigoContablestringsubcuenta

10. GET /api/imagenes_articulos — Índice de imágenes

GET DIRECTA api/imagenes_articulos Lista de imágenes disponibles por artículo (d_items_fotos) paginada
Disparada por: método FACTSYNC product_images GET. Cada imagen nueva o modificada dispara adicionalmente GET api/imagen/{ImagenExt} (sección 11).

Campos de la respuesta leídos (data.rows[])

Campo SAGETipoUso
CodigoArticulostringResuelto a id_subarticle vía _get_id_subarticle()
ImagenExtstring (UUID)__id_extern__ — si ha cambiado respecto al guardado, se vuelve a descargar
sysDescripcionBinariostringdescripcio
Para cada registro de este índice donde el ImagenExt difiere del guardado en Softbase, se realiza una llamada adicional a GET /api/imagen/{ImagenExt} (sección 11).

11. GET /api/imagen/{id} — Imagen individual

GET INDIRECTA api/imagen/{ImagenExt} Descarga de la imagen en Base64
Disparada por: durante product_images GET, una vez por imagen nueva o modificada, vía _get_base64_image().

Llamada individual, sin paginación. El parámetro ImagenExt es el UUID devuelto por /api/imagenes_articulos.

Estructura de respuesta (especial — no usa data.rows[])

{ "data": { "CodigoArticulo": string, "ImagenExt": string, // UUID de la imagen "sysDescripcionBinario": string, "IdSysBinario": string, "ImagenBase64": string // contenido de la imagen en Base64 }, "meta": { "SQLQuery": string } }

Campo leído

Campo SAGETipoUso
data.ImagenBase64string (Base64)Decodificado y guardado en files/public/products/prod_{ImagenExt} vía SaveFileBase64()

12. GET /api/almacenes — Almacenes

GET DIRECTA api/almacenes Importación de almacenes (mag_magatzems) paginada
Disparada por: método FACTSYNC warehouses GET.

Campos de la respuesta leídos (data.rows[])

Campo SAGETipoCampo SoftbaseNotas
CodigoAlmacenstring__id_extern__
Almacenstringdescripcio
DomiciliostringadrecaConcatenado con Municipio, CP y Provincia
Municipiostringadreca
CodigoPostalstringadreca
Provinciastringadreca
actiuSiempre 'Y' en importación
webSiempre 'Y' en importación

13. GET /api/stock — Stocks

GET DIRECTA api/stock Importación de stock por artículo y almacén (mag_estocs) paginada
Disparada por: método FACTSYNC stocks GET.

Campos de la respuesta leídos (data.rows[])

Campo SAGETipoCampo Softbase
IdAcumuladoStockstring/integer__id_extern__
CodigoArticulostringid_subarticle (resuelto vía _get_id_subarticle())
CodigoAlmacenstringid_magatzem (resuelto vía _get_id_magatzem())
UnidadSaldofloatestoc
PrecioMediofloatvalor_mig_unitari

14. GET /api/divisas — Divisas

GET DIRECTA api/divisas Importación de monedas (tbl_divisas) paginada
Disparada por: método FACTSYNC currencies GET.

Campos de la respuesta leídos (data.rows[])

Campo SAGETipoCampo Softbase
CodigoDivisastring__id_extern__ / codi
Divisastringnom
SimboloDivisastringsimbol
Decimalesintegerdecimals
FactorCambioEurofloatfactor_conversio

15. GET /api/iva — Tipos de IVA

GET INDIRECTA api/iva Importación de grupos de IVA (art_tipus_iva) paginada
Disparada por: cache global vía _init_tipus_iva(), una sola vez por sesión. Se invoca desde products GET, conditions GET y external_documents GET al resolver el campo GrupoIva. No es un método FACTSYNC: el conector no expone vat_types al usuario.

Campos de la respuesta leídos (data.rows[])

Campo SAGETipoCampo SoftbaseNotas
GrupoIvastring__id_extern__Clave compuesta: S{GrupoIva}-{CodigoIvaConRecargo}
CodigoIvaConRecargostring__id_extern__Parte de la clave compuesta
DescIvaConrecargostringdescripcio
IVAConrecargofloattipusPorcentaje de IVA
RecargoConRecargofloattipus_recPorcentaje de recargo de equivalencia

16. GET /api/tarifas — Líneas de descuento

GET DIRECTA api/tarifas Importación de tarifas / líneas de descuento (art_linees_descompte) paginada
Disparada por: método FACTSYNC discount_lines GET.

Campos de la respuesta leídos (data.rows[])

Campo SAGETipoCampo Softbase
Tarifainteger__id_extern__
DescripcionTarifastringdescripcio (prefijada con Tarifa + " - ")
IndicadorTarifainteger__IndicadorTarifa (0=precio directo, 1=% incremento, 2=valor fijo)
CodigoDivisastringLeído pero no mapeado directamente
CodigoEmpresaintegerLeído pero no mapeado

17. GET /api/tarifa_precio — Precios por tarifa

GET DIRECTA api/tarifa_precio Precios estándar por tramos de volumen (art_linees_descompte condiciones) paginada
Disparada por: método FACTSYNC conditions GET (1ª pasada). Las pasadas 2 y 3 son indirectas: GET api/articulos_cliente (sección 18) y GET api/condiciones_especiales (sección 19).

Ordenación habitual aplicada

order_by: ["Tarifa ASC", "CodigoArticulo ASC", "FechaInicio ASC"]

Campos de la respuesta leídos (data.rows[])

Campo SAGETipoCampo Softbase / Uso
IdTarifaPreciostring/integerBase para __id_extern__ (tramos: {Id}-1{Id}-N; fila base: {Id})
Tarifaintegerid_tabla (resuelto vía _get_id_linia_descompte())
CodigoArticulostringid_tipus_element (resuelto vía _get_id_subarticle())
IndicadorTarifaintegerTipo de cálculo (0/1/2)
FechaInicioISO 8601 datetimedata_ini (convertido a timestamp)
FechaFinalISO 8601 datetimedata_fi (ajustado a 23:59:59)
StatusActivointegerSi ≠ -1 → eliminat = timestamp
HastaUnidades1HastaUnidades10integervolum de cada tramo
Precio1Precio10floatPrecio del tramo; calculado según IndicadorTarifa
Lógica N+1: Cada registro SAGE genera entre 1 y 11 filas en Softbase: una por cada HastaUnidades no vacío (identificada como {IdTarifaPrecio}-{j}) y una fila base final sin límite de volumen (identificada como {IdTarifaPrecio}).

Cálculo de valor según IndicadorTarifa

IndicadorTarifaFórmula
0 — Precio directovalor = Precio
1 — % incrementovalor = preu_base × (1 + Precio / 100)
2 — Valor fijovalor = preu_base + Precio
Donde preu_base es el precio de la tarifa BASE del artículo, obtenido vía _get_preu_base().

18. GET /api/articulos_cliente(s) — Precios especiales por cliente

GET INDIRECTA api/articulos_cliente Precios y descuentos especiales artículo–cliente paginada
Disparada por: conditions GET durante su 2ª pasada, con el filtro Automatico=0 (sólo las condiciones introducidas manualmente; las generadas automáticamente a partir de albaranes o pedidos se ignoran).

Ordenación habitual aplicada

order_by: ["CodigoArticulo ASC", "CodigoCliente ASC", "FechaInicio ASC"]

Campos de la respuesta leídos (data.rows[])

Campo SAGETipoCampo Softbase / Uso
CodigoClientestringid_tabla (resuelto a cli_ficha.id)
CodigoArticulostringid_tipus_element (resuelto a art_subarticles.id)
FechaInicioISO 8601 datetimedata_ini
PrecioOfertafloatSi > 0 → tipus_condicio='preu'
%DescuentofloatDescuento 1 para cascada
%Descuento2floatDescuento 2 para cascada
%Descuento3floatDescuento 3 para cascada

Cálculo del valor final

Condicióntipus_condicioFórmula valor
PrecioOferta > 0'preu'PrecioOferta × (1 - d1/100) × (1 - d2/100) × (1 - d3/100)
%Descuento > 0'desc'% efectivo resultante de la cascada de descuentos
Sin precio ni descuento__import = false → registro ignorado

19. GET /api/condiciones_especiales — Condiciones especiales

GET INDIRECTA api/condiciones_especiales Condiciones especiales de precio/descuento por múltiples ámbitos paginada
Disparada por: durante conditions GET (3ª pasada del método).

Filtro siempre aplicado

filters: ["Automatico = 0"]

Campos de la respuesta leídos (data.rows[])

Campo SAGETipoCampo Softbase / Uso
CodigoClientestringid_tabla si ámbito = cliente concreto
CodigoCadena_stringid_tabla si ámbito = cadena (resuelto al cliente cabecera)
CodigoTipoClienteLcstringSi SAGE_SECTORS='tipus_client': se expande a todos los clientes del tipo
CodigoFamiliastringid_tipus_element si ámbito = familia
CodigoSubfamiliastringid_tipus_element si ámbito = grupo
FechaInicioISO 8601 datetimedata_ini
PreciofloatPrecio base para el cálculo (cuando hay precio directo)
%DescuentofloatDescuento 1
%Descuento2floatDescuento 2
%Descuento3floatDescuento 3
IdDelegacionstringLeído pero no mapeado

20. GET /api/pedidos — Lista de pedidos

GET DIRECTA api/pedidos Lista de cabeceras de pedidos paginada
Disparada por: método FACTSYNC external_documents GET con {extra_params:{type:"orders"}}. Cada fila dispara una llamada indirecta a GET api/pedido/{any}-{serie}-{num} (sección 21) y, si hay PathFichero, a GET api/doc/{path} (sección 26).

Campos de la respuesta leídos (data.rows[])

Campo SAGETipoCampo SoftbaseNotas
IdPedidoClistring__id_extern__Clave externa del pedido
CodigoClientestringid_clientResuelto vía _get_id_client()
EjercicioPedidointegeranyAño de la serie
SeriePedidostringserie
NumeroPedidointegernumero
FechaPedidoISO 8601 datetimedataConvertido a timestamp
ImporteBrutofloattotal_brut
ImporteLiquidofloattotal
FactorCambiofloatfactor_conversio
CodigoDivisastringid_divisaResuelto vía _get_id_divisa()
estadointegerestat0→BLOQUEADO · 1→SERVIDO · 2→PENDIENTE
PathFicherosarrayRuta(s) de los ficheros adjuntos; dispara llamadas a GET /api/doc/{path}
Para cada pedido de la lista, se realiza una llamada adicional a GET /api/pedido/{any}-{serie}-{num} para obtener las líneas de detalle (sección 21).

21. GET /api/pedido/{any}-{serie}-{num} — Detalle de pedido

GET INDIRECTA api/pedido/{EjercicioPedido}-{SeriePedido}-{NumeroPedido} Detalle completo de un pedido (cabecera + líneas)
Disparada por: durante external_documents GET {type:orders}, una vez por cada pedido devuelto en la lista (sección 20).

Ejemplo: api/pedido/2026-A-1

Estructura de respuesta (especial)

{ "data": { "Cabecera": { // campos de cabecera }, "Lineas": [ // array de líneas ] } }

Campos de Cabecera leídos

Campo SAGETipoUso
EstadointegerConfirma el estado para el campo estat

Campos de Lineas leídos

Campo SAGETipoCampo SoftbaseNotas
CodigoArticulostringid_subarticleResuelto vía _get_id_subarticle()
DescripcionArticulostringdescripcio
UnidadesPedidasfloatquantitatCantidad solicitada
Preciofloatpreu
ImporteBrutofloatUsado para calcular descompte
BaseImponiblefloatUsado para calcular descompte: (ImporteBruto - BaseImponible) / ImporteBruto × 100
ImporteNetofloatpreu_net
GrupoIvastringivaResuelto a tipo IVA interno

22. GET /api/albaranes — Lista de albaranes

GET DIRECTA api/albaranes Lista de cabeceras de albaranes / notas de entrega paginada
Disparada por: método FACTSYNC external_documents GET con {extra_params:{type:"delivery_notes"}}. Cada fila dispara GET api/albaran/{any}-{serie}-{num} (sección 23) y, si procede, GET api/doc/{path} (sección 26).

Campos de la respuesta leídos (data.rows[])

Campo SAGETipoCampo SoftbaseNotas
IdAlbaranClistring__id_extern__Clave externa
CodigoClientestringid_clientResuelto vía _get_id_client()
EjercicioAlbaranintegerany
SerieAlbaranstringserie
NumeroAlbaranintegernumero
FechaAlbaranISO 8601 datetimedataConvertido a timestamp
ImporteBrutofloattotal_brut
ImporteLiquidofloattotal
StatusFacturadointegerestat1 → 'FACTURAT' · 0 → 'OBERT'
StatusAbonointegersubestat1 → 'ABONO'
PathFicherosArrayDispara llamadas a GET /api/doc/{path}
Para cada albarán se realiza una llamada adicional a GET /api/albaran/{any}-{serie}-{num} para obtener las líneas (sección 23).

23. GET /api/albaran/{any}-{serie}-{num} — Detalle de albarán

GET INDIRECTA api/albaran/{EjercicioAlbaran}-{SerieAlbaran}-{NumeroAlbaran} Detalle completo de un albarán (cabecera + líneas)
Disparada por: durante external_documents GET {type:delivery_notes}, una vez por cada albarán devuelto en la lista (sección 22).

Ejemplo: api/albaran/2026-A-1. Misma estructura que el detalle de pedido.

Campos de Lineas leídos

Campo SAGETipoCampo Softbase
CodigoArticulostringid_subarticle
Unidadesfloatquantitat
Preciofloatpreu
CodigoIvaintegeriva
ImporteBrutofloattotal_brut de línea
ImporteNetoLineasfloatpreu_net

24. GET /api/facturas — Lista de facturas

GET DIRECTA api/facturas Lista de cabeceras de facturas paginada
Disparada por: método FACTSYNC external_documents GET con {extra_params:{type:"invoices"}}. Cada fila dispara GET api/factura/{any}-{serie}-{num} (sección 25) y, si procede, GET api/doc/{path} (sección 26).

Campos de la respuesta leídos (data.rows[])

Campo SAGETipoCampo SoftbaseNotas
IdFacturaClistring__id_extern__Clave externa
CodigoClientestringid_clientResuelto vía _get_id_client()
EjercicioFacturaintegerany
SerieFacturastringserie
NumeroFacturaintegernumero
FechaFacturaISO 8601 datetimedataConvertido a timestamp
ImporteBrutofloattotal_brut
ImporteLiquidofloattotal
CodigoDivisastringid_divisaResuelto vía _get_id_divisa()
StatusContabilizadointegerestat1 → 'COMPTABILITZAT' · 0 → 'PENDENT'
StatusAbonointegersubestat1 → 'ABONO'
PathFicherostringDispara llamadas a GET /api/doc/{path}
Para cada factura se realiza una llamada adicional a GET /api/factura/{any}-{serie}-{num} para obtener las líneas (sección 25).

25. GET /api/factura/{any}-{serie}-{num} — Detalle de factura

GET INDIRECTA api/factura/{EjercicioFactura}-{SerieFactura}-{NumeroFactura} Detalle completo de una factura (cabecera + líneas)
Disparada por: durante external_documents GET {type:invoices}, una vez por cada factura devuelta en la lista (sección 24).

Ejemplo: api/factura/2026-A-1. Misma estructura que el detalle de pedido y albarán.

Campos de Cabecera leídos

Campo SAGETipoUso
StatusContabilizadointegerConfirma el estado de contabilización
StatusAbonointegerConfirma si es abono

Campos de Lineas leídos

Campo SAGETipoCampo Softbase
CodigoArticulostringid_subarticle
Unidadesfloatquantitat
Preciofloatpreu
CodigoIvaintegeriva
ImporteBrutofloattotal_brut de línea
ImporteNetoLineasfloatpreu_net

26. GET /api/doc/{path} — Fichero adjunto

GET INDIRECTA api/doc/{PathFichero} Descarga de un documento adjunto (PDF, etc.) en Base64
Disparada por: durante external_documents GET (cualquier tipo), una llamada por cada adjunto referenciado en el campo PathFichero de la cabecera del documento.

Llamado para cada valor de PathFichero devuelto en los listados de pedidos, albaranes y facturas. El conector puede recibir múltiples rutas separadas por coma en un solo campo y realiza una llamada por cada una.

Estructura de respuesta (especial)

{ "data": { "name": string, // nombre del fichero "FileBase64": string, // contenido del fichero en Base64 "last_write_time": string // fecha de modificación (ISO 8601) } }

Campos leídos

Campo SAGETipoUso interno
data.namestringNombre del fichero guardado en files/external_docs/
data.FileBase64string (Base64)Decodificado y guardado localmente
data.last_write_timeISO 8601 datetimeGuardado como date_updated_file para detectar cambios futuros

27. GET /api/formas_pago — Formas de pago

GET DIRECTA api/formas_pago Condiciones de pago y plazos
Disparada por: payment_methods GET

Campos de la respuesta leídos

CampoTipoUso interno
IdCondicionstringClave externa
CodigoCondicionesstringCódigo de condición; se prefija como COND_{valor} para el external ID
FormadePagostringDescripción de la forma de pago
NumeroPlazosintegerNúmero de vencimientos
DiasPrimerPlazointegerSolo visualización
DiasEntrePlazosintegerSolo visualización
Remesableinteger-1: remesable (rebuts=Y, comptat=N) · otro: al contado

28. GET /api/campos — Campos personalizados

GET DIRECTA api/campos Definiciones de campos personalizados de SAGE
Disparada por: custom_fields GET (directa) y también de forma indirecta durante products GET para resolver definiciones de características.

Campos de la respuesta leídos

CampoTipoUso interno
sysIdstringClave externa (nombre del campo)
sysRotulostringEtiqueta / descripción del campo
Uso indirecto: La función _fetch_field_definition() también llama a este endpoint con un filtro sysId = '{campo}' para obtener la definición de un campo concreto al importar características de productos.

29. GET /api/idiomas_articulos — Traducciones de artículos

GET DIRECTA api/idiomas_articulos Traducciones de descripción de artículos por idioma
Disparada por: translations GET

Campos de la respuesta leídos

CampoTipoUso interno
IdIdiomastringClave externa; fallback: CodigoArticulo_CodigoIdioma_
CodigoArticulostringResuelto a art_subarticles.id via _get_item_subarticle()
CodigoIdioma_stringCódigo idioma SAGE (CAT, CAS, ING, POR, FRA, ALE, ITA) → código interno (ca, es, en, pt, fr, de, it)
DescripcionArticulostringGuardado como titol
Descripcion2ArticulostringGuardado como subtitol
Filtro de importación: Solo se importan registros donde el artículo existe en Softbase (id_item > 0) y el código de idioma tiene equivalencia en el mapa interno. Los registros que no cumplen se marcan con __import=false.

30. POST /api/clientes — Crear cliente

POST DIRECTA api/clientes Exportación de un cliente nuevo hacia SAGE
Disparada por: método FACTSYNC customers POST (exportación de un cliente sin fsync_external_id). También por el método orders POST con extra_params.export_customers = "Y" cuando el cliente del pedido no existe en SAGE (ver sección 32).

Cuerpo JSON de la petición

[ { "RazonSocial": "string", // empresa "Nombre": "string", // nom + cognom1 + cognom2 (espacios colapsados) "EMail1": "string", // email "Telefono": "string", // telefon "Telefono2": "string", // telefon2 "Telefono3": "string", // telefon_movil "Fax": "string", // fax "CifDni": "string", // nif o, si esta vacio, num_doc (max 13; si supera, bloquea export) "Domicilio": "string", // carrer "Numero1": "string", // numero "Piso": "string", // pis "Escalera": "string", // escala "Puerta": "string", // porta "CodigoPostal": "string", // cp "Municipio": "string", // poblacio "Provincia": "string", // provincia; si esta vacio, descripcion desde id_provincia "Nacion": "string", // pais (truncado a 25 caracteres) "SiglaNacion": "string", // codigo ISO del pais (cfg_paisos.codi desde id_pais) "ObservacionesCliente": "string", // observacions (truncado a 50 caracteres) "CodigoClienteProveedor": "12345", // id real del cliente en Softbase (cli_ficha.id), como string // + campos de extra_params.default_values del metodo customers POST // p.ej. "CodigoTipoClienteLc": "110", "PeriodicidadFacturas": "1", ... } ]
Campos exportados: solo se envían campos 1:1 seguros (contacto, identidad fiscal y dirección) y únicamente si tienen valor. Los campos que en SoftBase son ids internos (id_treballador, id_divisa, id_linea_descompte…) no se exportan porque no se corresponden con ningún código de Sage. CifDni toma nif y, si está vacío, num_doc; el teléfono móvil (telefon_movil) viaja como Telefono3. La dirección sigue la misma correspondencia que la importación (_add_address_to_data): cpCodigoPostal, poblacioMunicipio, paisNacion y SiglaNacion resuelto como código ISO del país (cfg_paisos.codi a partir de id_pais). Provincia toma el literal provincia y, si está vacío, la descripcion resuelta desde id_provincia (cfg_paisos_provincies).
Límites de longitud y validación: Nacion se trunca a 25 caracteres y ObservacionesCliente a 50. El campo CifDni admite un máximo de 13 caracteres: si el NIF/CIF del cliente (nif o num_doc) lo supera, no se exporta — se marca en la previsualización (de clientes y de pedidos) como no exportable con el motivo, y la exportación queda bloqueada (también si se fuerza). El límite es configurable por conector vía la propiedad _export_nif_maxlen (0 = sin límite).
Formato de array: el endpoint de creación (proceso IME asíncrono) espera una colección de clientes, no un objeto suelto: el cuerpo es un array con el objeto cliente dentro ([ { ... } ]).
CodigoClienteProveedor: al crear el cliente se envía siempre con el id real del cliente en Softbase (cli_ficha.id) como string (entre comillas), de forma que SAGE conserve la referencia al cliente de origen.
Valores por defecto: los campos definidos en extra_params.default_values del método customers POST se añaden solo al crear el cliente y no sobreescriben los campos ya mapeados desde la ficha.

Campos de la respuesta leídos

Campo SAGETipoUso
meta.IdProcesoIMEintegerId del proceso de importación. La creación de clientes en SAGE es asíncrona: el POST no devuelve CodigoCliente; la operación se considera correcta si llega este IdProcesoIME.
Código de cliente diferido: como el POST no devuelve CodigoCliente, fsync_external_id_{id_connexio} queda vacío de momento. El código real se resuelve más tarde buscando el cliente en SAGE por NIF (CifDni) o Email (EMail1) — ver sección 32.
Decisión POST vs PUT: Si el registro de cli_ficha ya tiene fsync_external_id_{id_connexio} rellenado, se usa PUT (sección 28); en caso contrario, POST.

31. PUT /api/clientes/{codi} — Actualizar cliente

PUT DIRECTA api/clientes/{CodigoCliente} Exportación de actualización de un cliente existente a SAGE
Disparada por: método FACTSYNC customers PUT (exportación de un cliente que ya tiene fsync_external_id).

El parámetro de ruta {CodigoCliente} se obtiene de fsync_external_id_{id_connexio} en cli_ficha y se aplica URL-encoding.

Cuerpo JSON de la petición (idéntico al POST)

{ "RazonSocial": "string", "Nombre": "string", "Email1": "string", "Telefono": "string", "CifDni": "string", "CodigoCliente": "string" // incluido en el cuerpo a diferencia del POST }

Respuesta

En una actualización correcta, SAGE devuelve HTTP 200. El conector no lee ningún campo específico de la respuesta; comprueba únicamente que no haya error.

32. POST /api/pedidos — Exportar pedido

POST DIRECTA api/pedidos Exportación de un pedido de Softbase hacia SAGE
Disparada por: método FACTSYNC orders POST (exportación de un pedido pendiente).
Exportación automática de clientes (extra_params.export_customers = "Y"): si el cliente del pedido no tiene fsync_external_id, antes de enviar el pedido el conector lo busca en SAGE vía GET /api/clientes filtrando por NIF (CifDni; usa nif o, si está vacío, num_doc — el mismo valor con el que se creó el cliente) y, si no hay resultado, por Email (EMail1). Si existe, lo vincula; si no, lo crea vía POST /api/clientes (sección 30) usando la configuración del método indicado en extra_params.customers_method. Como la creación no es instantánea, el pedido se aplaza 3 minutos marcando fsync_export_status_{id_connexio} con el timestamp del reintento en negativo; el cron lo reprocesa cuando vence.
Estado «a la espera de creación del cliente» en la previsualización: mientras el pedido está aplazado, en el listado de exportación aparece el mensaje de que está esperando la creación del cliente y la hora a la que se podrá reintentar la exportación manual. El botón de exportar permanece deshabilitado hasta esa hora y se reactiva automáticamente al llegar. Si el cliente nunca llega a crearse en SAGE (p.ej. por falta de datos), el pedido quedaría aplazado indefinidamente porque el cliente ya consta como enviado (fsync_export_status > 0); para resolverlo, el botón Reiniciar (acción FSYN_export_reset) pone a 0 el estado de exportación del pedido y del cliente vinculado, forzando que el cliente se vuelva a enviar en el siguiente intento.

Cuerpo JSON de la petición

{ "CodigoDocumento_PortalClient": "string", // valor amigable de la comanda: any-linea_pedido-num_pedido (p.ej. 2026-PDM-2) "Cabecera": { "EjercicioDocumento": integer, // ped_pedidos.any "SerieDocumento": "string", // extra_params.serie del metodo o, en blanco, ped_pedidos.linea_pedido "NumeroDocumento": null, // siempre null; el numero amigable va en CodigoDocumento_PortalClient "FechaDocumento": "ISO8601", // fecha del pedido "CodigoCliente": "string", // fsync_external_id del cliente en SAGE "Estado": 0, "FechaFormalizacion": "ISO8601", "FechaOperacion": "ISO8601", "FechaCreacion": "ISO8601", "IdDelegacion": "string", // SAGE_DELEGACION (si configurado) "RazonSocial": "string", // cli_ficha.empresa "Nombre": "string", // cli_ficha.nom "Domicilio": "string", // cli_ficha.carrer + numero "CodigoPostal": "string", "Municipio": "string", "Provincia": "string", "TelefonoEnvios": "string", "IndicadorIva": "I", "GrupoIva": integer, // resuelto desde clau_iva del cliente "ImporteNeto": float, // ped_pedidos.total "ImporteLiquido": float, // total + total_iva "Comentario": "string" }, "Lineas": [ { "Orden": integer, // número de línea (base 1) "FechaDocumento": "ISO8601", "CodigoArticulo": "string", // fsync_external_id del artículo en SAGE "DescripcionArticulo": "string", "Unidades": float, "Precio": float, // redondeado a 2 decimales "%Descuento": float, // % de descuento de la linea (ped_pedidos_detalle.descompte), redondeado a 2 decimales "Iva": float, // porcentaje de IVA "CodigoAlmacen": "string", // fsync_external_id del almacén en SAGE "GrupoIva": integer, "CodigoIva": integer, "Estado": 0 } ] }

Campos de la respuesta leídos

Campo SAGETipoUso
data.NumeroPedidointeger/stringNúmero de pedido asignado por SAGE, guardado en Softbase
data.IdstringFallback si NumeroPedido no está presente
Ficheros adjuntos: Si el pedido tiene documentos adjuntos, el conector los recupera vía GET /api/doc/{PathFichero} y los incluye serializados en el campo args JSON guardado en Softbase. No se envían a SAGE en el POST del pedido.

33. Gestión de errores y reintentos

Código HTTP / situaciónComportamiento del conector
401 Unauthorized Invalida el token (SAGE_TOKEN = ''), solicita un token nuevo y reintenta la llamada original una sola vez.
4xx (otros) Devuelve mensaje de error: "HTTP status:{codi} - URL not found" con el detalle de la respuesta.
5xx Intenta parsear el JSON de la respuesta buscando error.message o error; si no, devuelve la respuesta en bruto.
Error cURL Devuelve "Communication Error: {curl_error()}".
JSON inválido Devuelve la respuesta en bruto sin procesar.
ID externo no resoluble Las funciones _get_id_*() devuelven 0. El registro se ignora silenciosamente sin detener la sincronización.