raorsa / sage-middleware-client
Requires
- php: >=8.2
- monolog/monolog: *
- raorsa/rw-file-cache: ^1.0
- symfony/http-client: ^7.0
Requires (Dev)
- php-coveralls/php-coveralls: ^2.7
- phpunit/phpunit: ^11.0
This package is auto-updated.
Last update: 2026-08-19 11:30:51 UTC
README
Cliente PHP para consumir la API REST del middleware Laravel que hace de puente entre el ERP interno (SQL Server, tipo Sage) y aplicaciones externas (portal de cliente, apps de técnicos, etc.).
Este paquete no contiene lógica de negocio: cada clase es un wrapper fino sobre un grupo de endpoints del middleware — construye la URL, gestiona el login/token y el caché local, y decodifica la respuesta JSON. Toda la lógica real (filtros, cálculos, formateo de IDs) vive en el middleware.
- Requisitos: PHP >= 8.2
- Dependencias:
symfony/http-client,raorsa/rw-file-cache,monolog/monolog - Licencia: Apache-2.0
Instalación
composer require raorsa/sage-middleware-client
Uso básico
Cada clase de negocio (Clients, Invoices, DeliveryNotesClients, DeliveryNotesProviders, OrdersClients, OrdersProviders, Jobs, Incentives, Articles, Apparatus, CustomClient) extiende Raorsa\SageMiddlewareClient\components\baseClient y se instancia con el método estático make():
use Raorsa\SageMiddlewareClient\Clients; $clients = Clients::make( url: 'https://middleware.ejemplo.com/api/', user: 'usuario@dominio.com', password: 'secreto', verify: true, // verificar certificado SSL name: 'MiApp', // nombre del token generado en el login del middleware cacheLife: 10, // minutos de vida del caché de respuestas (0 = sin caché) cacheDir: null, // por defecto /tmp/sageCache.<hash del cwd>/ cacheCompress: true, // comprime el caché en disco (gzip) logDir: null, // por defecto ./logs/ logLengthData: 100, // nº de caracteres de payload que se registran en el log ); $listado = $clients->clients(); // GET /clients/list $ficha = $clients->clientInfo('1234'); // GET /clients/info/1234
make() crea una conexión (login/password/name) nueva; el login contra el middleware (POST /login) es perezoso: solo se dispara la primera vez que se necesita un token, y el token obtenido se cachea (ver "Caché y autenticación" más abajo).
Si ya tienes una connexion construida (por ejemplo, en tests, o para compartir conexión entre varias clases de negocio), usa mount():
use Raorsa\SageMiddlewareClient\components\connexion; use Raorsa\SageMiddlewareClient\wrappers\{cache, log}; use Raorsa\SageMiddlewareClient\Invoices; $connexion = connexion::mount('https://middleware.ejemplo.com/api/', 'usuario@dominio.com', 'secreto'); $invoices = Invoices::mount($connexion, new log(), new cache());
Casi todos los métodos aceptan un último parámetro bool $cache = true para omitir el caché de esa llamada concreta ($clients->clients(false)).
Caché y autenticación
- El login se resuelve solo cuando hace falta un token:
connexion::call()intenta primero con el token en memoria/caché; si el middleware responde405, descarta el token y vuelve a autenticar. - El token se guarda en el mismo caché de disco que las respuestas (
wrappers\cache, basado enraorsa/rw-file-cache), con una vida de 6 días (baseClient::TOKEN_LIFE_TIME) — un margen intencionado por debajo de los 7 días de caducidad que el middleware aplica a los tokens Sanctum, para evitar reutilizar uno a punto de expirar. - Las respuestas JSON de cada endpoint se cachean por su URL completa (
cacheLifeminutos, por defecto 10). Si una llamada al servidor falla pero hay una respuesta anterior en caché ("last"),baseClient::call()la devuelve igualmente como fallback. - Los métodos
callJson()devuelvenfalsesi la respuesta no es JSON válido o si la llamada falla (nunca lanzan excepción).
Clases disponibles
Cada tabla indica el método del cliente, el endpoint del middleware al que llama y qué devuelve. La semántica completa de cada endpoint (filtros de negocio, formato de identificadores compuestos, etc.) la define el middleware; aquí solo se documenta el contrato desde el punto de vista del cliente.
Clients — clientes del ERP
| Método | Endpoint | Descripción |
|---|---|---|
clients(bool $cache = true) |
GET /clients/list |
Diccionario {código: nombre} de clientes válidos. |
clientInfo(string $id, bool $cache = true) |
GET /clients/info/{id} |
Ficha completa de un cliente (incluye direcciones). |
companyInfo(string $domain, bool $cache = true) |
GET /clients/team/{domain} |
Cliente asociado a un dominio de email de contacto. |
validDomains(bool $cache = true) |
GET /clients/auth-domains |
Lista de dominios de email válidos para alta/login de usuarios del portal. |
Invoices — facturas de cliente
| Método | Endpoint | Descripción |
|---|---|---|
list(string $team, bool $cache = true) |
GET /invoice/list/{team} |
Facturas de un cliente, ordenadas por fecha descendente. |
info(string $id, bool $cache = true) |
GET /invoice/info/{id} |
Cabecera de una factura. $id es el identificador compuesto Serie-Ejercicio-Numero (p. ej. G-2024-00123). |
lines(string $invoiceNumber, bool $cache = true) |
GET /invoice/lines/{invoiceNumber} |
Líneas de una factura. ⚠️ $invoiceNumber es solo NumeroFactura (no el identificador compuesto de info): si el número se repite entre series o ejercicios distintos, el middleware puede devolver líneas de más de un documento — bug conocido del lado servidor, pendiente de corregir allí. |
download(string $id, bool $cache = true) |
GET /invoice/download/{id} |
PDF original de la factura (string binario). |
img(string $id, bool $cache = true) |
GET /invoice/img/{id} |
Primera página de la factura en JPG. |
DeliveryNotesClients / DeliveryNotesProviders — albaranes
Ambas clases comparten toda la lógica en la clase abstracta DeliveryNotes; solo cambia el prefijo de ruta (delivery para albaranes de cliente, delivery-provider para los de proveedor).
| Método | Endpoint | Descripción |
|---|---|---|
list(string $team, bool $cache = true) |
.../list/{team} |
Albaranes de un cliente/proveedor. |
info(string $id, bool $cache = true) |
.../info/{id} |
Cabecera de un albarán. $id admite identificador compuesto parcial (serie, ejercicio y/o número, en cualquier combinación). |
lines(string $id, bool $cache = true) |
.../lines/{id} |
Líneas de un albarán. |
linesSN(string $sn, bool $cache = true) |
.../lines-sn/{sn} |
Líneas que coinciden con un número de serie de aparato. |
emptyLines(string $year, bool $cache = true) |
.../empty-lines/{year} |
Líneas de un año sin número de serie asignado. |
find(string $query, bool $cache = true) |
.../find/{query} |
Búsqueda flexible (coincidencia parcial) por el mismo identificador compuesto que info. |
download(string $id, bool $cache = true) |
.../download/{id} |
PDF del albarán. |
img(string $id, bool $cache = true) |
.../img/{id} |
JPG de la primera página del albarán. |
OrdersClients / OrdersProviders — pedidos
Ambas clases comparten toda la lógica en la clase abstracta Orders; solo cambia el prefijo de ruta (orders para pedidos de cliente, orders-provider para los de proveedor). A diferencia de los albaranes, un pedido todavía no se ha servido: no hay número de serie de aparato ni PDF asociado, por lo que no existen linesSN, emptyLines, download ni img.
| Método | Endpoint | Descripción |
|---|---|---|
list(string $team, bool $cache = true) |
.../list/{team} |
Pedidos de un cliente/proveedor. |
info(string $id, bool $cache = true) |
.../info/{id} |
Cabecera de un pedido. $id admite identificador compuesto parcial (serie, ejercicio y/o número, en cualquier combinación). |
lines(string $id, bool $cache = true) |
.../lines/{id} |
Líneas de un pedido. |
find(string $query, bool $cache = true) |
.../find/{query} |
Búsqueda flexible (coincidencia parcial) por el mismo identificador compuesto que info. |
Jobs — órdenes de trabajo (OT)
| Método | Endpoint | Descripción |
|---|---|---|
list(bool $cache = true) |
GET /jobs/list |
OT activas, enriquecidas con número de serie del aparato y operaciones. |
listReport(bool $cache = true) |
GET /jobs/list-report |
Todas las OT (pasen o no las validaciones internas) con el detalle de cada comprobación — pensado para auditoría, no para uso normal. |
info(string $id, bool $cache = true) |
GET /jobs/job/{id} |
Una OT concreta. $id es el identificador compuesto Ejercicio-Numero. |
getSN(string $id, bool $cache = true) |
GET /jobs/jobsn/{id} |
Número de serie del aparato asociado a una OT. |
operations(bool $cache = true) |
GET /jobs/operations |
Catálogo {idOperación: nombre}. |
operationsJobs(bool $cache = true) |
GET /jobs/operations-jobs |
Operaciones asignadas a cada OT activa. |
importLog(string $date, bool $cache = true) |
GET /jobs/import/{date} |
Registros de importación de actividades de una fecha concreta. |
Incentives — incentivos SAT
| Método | Endpoint | Descripción |
|---|---|---|
sat(?string $startDate = null, ?string $endDate = null, bool $cache = true) |
GET /incentives/sat/all[/{startDate}[/{endDate}]] |
Puntos de incentivo de todos los técnicos, opcionalmente acotado por fechas. |
userSAT(string $user, bool $cache = true) |
GET /incentives/sat/user/{user} |
Resumen de puntos de un técnico concreto. |
satDetails(string $user, ?string $startDate = null, ?string $endDate = null, bool $cache = true) |
GET /incentives/sat/details/{user}[/{startDate}[/{endDate}]] |
Igual que userSAT pero con el detalle de líneas que justifica el cálculo. |
Articles — artículos
| Método | Endpoint | Descripción |
|---|---|---|
screwTips(bool $cache = true) |
GET /articles/screw-tip/list |
Lista de "punteras completas" disponibles. |
findScrewTips(string $diameter, bool $cache = true) |
GET /articles/screw-tip/find/{diameter} |
Punteras que coinciden con un diámetro. |
searchScrewTips(array $diameters, bool $cache = true) |
GET /articles/screw-tip/search/{diameters} |
Punteras que coinciden con cualquiera de varios diámetros (el array se envía unido por comas). |
Apparatus — aparatos
Estos endpoints no forman parte de la documentación de API compartida por el equipo de middleware; la tabla siguiente refleja únicamente el contrato tal como lo usa este cliente (rutas y parámetros), no las reglas de negocio del servidor.
| Método | Endpoint | Descripción |
|---|---|---|
list(string $client, bool $cache = true) |
GET /apparatus/{client} |
Aparatos asociados a un cliente. |
interventions(string $id, bool $cache = true) |
GET /apparatus/{id}/interventions |
Intervenciones registradas sobre un aparato. |
integrated(string $id, bool $cache = true) |
GET /apparatus/{id}/integrated |
Componentes/elementos integrados en un aparato. |
brandInfo(string $id, bool $cache = true) |
GET /apparatus/brand/{id} |
Ficha de una marca de aparato. |
brands(bool $cache = true) |
GET /apparatus/brands |
Catálogo de marcas. |
types(bool $cache = true) |
GET /apparatus/types |
Catálogo de tipos de aparato. |
warranties(bool $cache = true) |
GET /apparatus/warranties |
Catálogo de tipos de garantía. |
interventionsType(bool $cache = true) |
GET /apparatus/interventions-type |
Catálogo de tipos de intervención. |
CustomClient — llamadas ad-hoc
Para endpoints del middleware que aún no tienen wrapper dedicado, o para prototipar:
use Raorsa\SageMiddlewareClient\CustomClient; $client = CustomClient::make(/* ... */); $raw = $client->get('ruta/relativa', false); // string crudo de la respuesta $json = $client->getJson('ruta/relativa', false); // decodificado como object|array|false
Testing
composer install vendor/bin/phpunit
Los tests no llaman a un middleware real: usan Symfony\Component\HttpClient\MockHttpClient con respuestas fijas en tests/resources/{clase}/{test}.json (ver tests/Traits/baseTest.php). Al añadir un método nuevo a una clase de negocio, el patrón habitual es añadir un test que use Nombre::mount($this->createConnexion(__FUNCTION__), $this->log, $this->cache) y un fixture JSON correspondiente.
Licencia
Apache-2.0