defontana / sdk
SDK PHP no oficial para el API de Defontana (ERP). Cubre Sales, Purchase, Inventory, Accounting, Order, PurchaseOrder y más.
Requires
- php: >=8.1
- ext-curl: *
- ext-json: *
Requires (Dev)
- phpunit/phpunit: ^10.0
- vlucas/phpdotenv: ^5.6
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
SDK no oficial para el API público de Defontana (ERP utilizado por miles de empresas chilenas para facturación electrónica, ventas, compras, inventario y contabilidad).
- 241 endpoints organizados en 14 servicios tipados.
- 222 DTOs tipados para todos los request bodies (con sub-DTOs y arrays de DTOs).
- Autenticación JWT automática (login + refresh + retry en 401).
- Cero dependencias en runtime (sólo
ext-curlyext-json).
Cobertura por servicio:
| Servicio | Endpoints |
|---|---|
auth() |
4 (+ utilidades de sesión) |
accounting() |
38 |
company() |
1 |
dispatch() |
3 |
environment() |
1 |
inventory() |
13 |
order() |
11 |
payment() |
3 |
paySheet() |
5 |
pricing() |
4 |
purchase() |
16 |
purchaseOrder() |
23 |
sale() |
113 |
subscription() |
4 |
user() |
2 |
Requisitos
- PHP 8.1+
- Extensiones:
curl,json - Credenciales del API de Defontana (cliente, empresa, usuario, password)
Instalación
composer require defontana/sdk
Si todavía no está publicado en Packagist, puedes apuntar al repositorio local en tu composer.json:
{
"repositories": [
{ "type": "path", "url": "../defontana-sdk" }
],
"require": {
"defontana/sdk": "*"
}
}
Quickstart
<?php require __DIR__ . '/vendor/autoload.php'; use Defontana\Sdk\Client; use Defontana\Sdk\Config; use Defontana\Sdk\Exception\ApiException; use Defontana\Sdk\Exception\AuthException; $config = new Config( client: getenv('DEFONTANA_CLIENT'), company: getenv('DEFONTANA_COMPANY'), user: getenv('DEFONTANA_USER'), password: getenv('DEFONTANA_PASSWORD'), ); $defontana = new Client($config); try { // Login implícito en la primera llamada autenticada $empresa = $defontana->company()->list(); echo "Empresa: {$empresa['name']}\n"; // Listar clientes (paginado) $clientes = $defontana->sale()->getClients( status: 1, itemsPerPage: 50, pageNumber: 1, ); foreach ($clientes['clients'] ?? [] as $c) { echo "{$c['legalCode']} — {$c['name']}\n"; } } catch (AuthException $e) { fwrite(STDERR, "Error de autenticación: {$e->getMessage()}\n"); } catch (ApiException $e) { fwrite(STDERR, "Error de API ({$e->statusCode}): {$e->getMessage()}\n"); }
Autenticación
El SDK obtiene el token JWT automáticamente la primera vez que llamas un endpoint protegido, y se re-autentica solo cuando el token expira (o si el API devuelve 401).
Login manual
$defontana->auth()->login(); // refresca el token de inmediato $token = $defontana->auth()->getAccessToken(); $exp = $defontana->auth()->getExpiresAt(); // timestamp UNIX
Reutilizar un token entre requests (ej. en un job)
// Primera ejecución $defontana->auth()->login(); file_put_contents('token.json', json_encode([ 'access_token' => $defontana->auth()->getAccessToken(), 'expires_at' => $defontana->auth()->getExpiresAt(), ])); // Siguiente ejecución (otro proceso) $cached = json_decode(file_get_contents('token.json'), true); $ttl = max(0, $cached['expires_at'] - time()); $defontana->auth()->setToken($cached['access_token'], $ttl);
Importante: no persistas tokens en lugares accesibles públicamente. Trátalos como credenciales.
Login por email/password (alternativo)
$resp = $defontana->auth()->emailLogin('lord.neo.gerson@gmail.com', '***'); // devuelve la lista de empresas asociadas + authResult
DTOs tipados (opcional, recomendado)
Todos los endpoints que aceptan un body tienen un DTO tipado en Defontana\Sdk\Model\<Dominio>\<Clase>. Puedes pasar un DTO o un array — el SDK lo serializa solo:
use Defontana\Sdk\Model\Sale\SaleInputData; use Defontana\Sdk\Model\Sale\SaleInputData_DateInput; use Defontana\Sdk\Model\Sale\SaleInputData_DetailInput; $venta = new SaleInputData( documentType: '33', clientFile: '76123456-7', sellerFileId: 'V001', paymentCondition: 'CO', shopId: '001', emissionDate: new SaleInputData_DateInput(day: 16, month: 5, year: 2026), details: [ new SaleInputData_DetailInput(code: 'PROD001', count: 2, price: 50000), new SaleInputData_DetailInput(code: 'PROD002', count: 1, price: 12500), ], ); $resp = $defontana->sale()->saveSale($venta); echo "Folio asignado: {$resp['firstFolio']}\n";
Ventajas frente al array crudo:
- Autocompletado y type-hints en tu IDE.
- Detección temprana de typos en nombres de propiedades.
toArray()omite automáticamente campos null (no contamina el JSON enviado).fromArray()reconstruye DTOs anidados desde respuestas del API.
Los DTOs son mutables: puedes construirlos por partes y modificarlos antes de enviar.
$venta = new SaleInputData(documentType: '33', clientFile: '76123456-7'); $venta->emissionDate = new SaleInputData_DateInput(day: date('j'), month: date('n'), year: date('Y')); foreach ($carrito as $item) { $venta->details[] = new SaleInputData_DetailInput( code: $item->sku, count: $item->qty, price: $item->price, ); } $resp = $defontana->sale()->saveSale($venta);
Ejemplos por caso de uso
Crear una factura electrónica (con DTO)
use Defontana\Sdk\Model\Sale\SaleInputData; use Defontana\Sdk\Model\Sale\SaleInputData_DateInput; use Defontana\Sdk\Model\Sale\SaleInputData_DetailInput; $resp = $defontana->sale()->saveSale(new SaleInputData( documentType: '33', clientFile: '76123456-7', sellerFileId: 'V001', shopId: '001', paymentCondition: 'CO', emissionDate: new SaleInputData_DateInput(day: 16, month: 5, year: 2026), details: [ new SaleInputData_DetailInput(code: 'PROD001', count: 1, price: 50000), ], )); // $resp['firstFolio'] tiene el folio asignado
Equivalente con array crudo (sigue funcionando)
$resp = $defontana->sale()->saveSale([ 'documentType' => '33', 'clientFile' => '76123456-7', 'sellerFileId' => 'V001', 'shopId' => '001', 'paymentCondition' => 'CO', 'emissionDate' => ['day' => 16, 'month' => 5, 'year' => 2026], 'details' => [ ['code' => 'PROD001', 'count' => 1, 'price' => 50000], ], ]);
Listar ventas en un rango de fechas
$ventas = $defontana->sale()->getSalebyDate( initialDate: '2026-05-01', endingDate: '2026-05-16', itemsPerPage: 100, pageNumber: 1, );
Descargar PDF de un documento
$pdf = $defontana->sale()->getStandardPDFDocumentBase64( documentType: '33', folio: 1042, siiUnit: 'XX', ); file_put_contents('factura-1042.pdf', base64_decode($pdf['pdfBase64']));
Crear/actualizar un cliente
use Defontana\Sdk\Model\Sale\ClientInputData; $defontana->sale()->saveClient(new ClientInputData( legalCode: '76123456-7', name: 'Cliente Ejemplo SpA', giro: 'Servicios informáticos', address: 'Av. Providencia 1234', district: 'PROVIDENCIA', state: 'METROPOLITANA', ));
Inventario y stock
$stock = $defontana->sale()->getStorageStock( storageID: 'BOD01', itemsPerPage: 200, pageNumber: 1, );
Comprobante contable
$voucher = $defontana->accounting()->getVoucher( VoucherType: 'TRA', Number: 100, FiscalYear: 2026, );
Manejo de errores
Toda excepción del SDK extiende de Defontana\Sdk\Exception\DefontanaException. Casos específicos:
| Excepción | Cuándo se lanza |
|---|---|
AuthException |
Login fallido (credenciales inválidas, etc.) |
ApiException |
HTTP no-2xx, o success=false en el cuerpo de respuesta |
NetworkException |
Error de cURL (timeout, DNS, TLS, etc.) |
try { $defontana->sale()->saveSale(body: $payload); } catch (ApiException $e) { echo "Status: {$e->statusCode}\n"; echo "Mensaje: {$e->getMessage()}\n"; echo "Respuesta cruda: {$e->rawBody}\n"; print_r($e->response); }
Configuración avanzada
$config = new Config( client: 'CLIENTE', company: 'EMPRESA', user: 'USUARIO', password: '***', baseUrl: 'https://api.defontana.com', // por defecto timeout: 60, // segundos connectTimeout: 15, verifyTls: true, userAgent: 'mi-app/1.0', );
Estructura del proyecto
src/
├── Client.php ← punto de entrada (facade)
├── Config.php
├── Auth/
│ └── AuthManager.php ← maneja JWT y refresh
├── Http/
│ ├── HttpClient.php ← wrapper sobre cURL
│ └── HttpResponse.php
├── Exception/
│ ├── DefontanaException.php, ApiException.php, AuthException.php, NetworkException.php
├── Service/ ← 14 servicios generados desde Swagger + AuthService + AbstractService
│ └── ...
└── Model/ ← 222 DTOs tipados (uno por schema de request body)
├── AbstractModel.php ← toArray / fromArray
├── Accounting/ (13)
├── Dispatch/ (27)
├── Inventory/ (11)
├── Order/ (26)
├── Payment/ (1)
├── Pricing/ (7)
├── Purchase/ (12)
├── PurchaseOrder/ (16)
└── Sale/ (109)
Los servicios y los DTOs se generan a partir del Swagger oficial. Para regenerarlos cuando Defontana publica nuevos endpoints:
# 1. Actualizar el snapshot del swagger curl -sS https://api.defontana.com/swagger/v1/swagger.json -o tools/swagger.json # 2. Regenerar (en orden) node tools/parse_swagger.js # produce tools/endpoints.json node tools/analyze_schemas.js # produce tools/schemas_to_generate.json node tools/generate_dtos.js # reescribe src/Model/** node tools/generate_services.js # reescribe src/Service/*Service.php
Esto sobreescribe los archivos autogenerados (todo src/Model/** y los *Service.php excepto AuthService y AbstractService). Si añadiste métodos personalizados a alguno, revísalo antes de regenerar.
Licencia
MIT.
Disclaimer
Este SDK no es oficial ni está afiliado a Defontana. Se desarrolló a partir de la documentación pública de su Swagger.