Cliente Node.js / TypeScript
facturo-client.ts es un cliente para aplicaciones JavaScript y TypeScript.
Características y compatibilidad
- Sin dependencias externas: usa las APIs nativas
fetchyAbortController. No requiereaxios,node-fetchni otros paquetes. - Entornos de ejecución:
- Node.js 18+ (18, 20, 22+)
- Bun 1.0+
- Deno 1.30+
- Cloudflare Workers / Vercel Edge / AWS Lambda
- Frameworks: Next.js (App Router / Server Actions), Nuxt, Remix, NestJS, Express, Fastify.
- Tipos estrictos: interfaces completas para peticiones, respuestas y códigos fiscales de la DGII.
Solo del lado del servidor
Usa el cliente únicamente en tu backend (rutas API, Server Actions, workers). Nunca expongas tu API Key en el navegador o en una app móvil.
Inicio rápido
Copia facturo-client.ts (o su versión compilada a JavaScript) en src/lib/ o src/services/:
import { FacturoClient, FacturoAPIError } from './facturo-client';
const client = new FacturoClient({
baseUrl: 'https://ecf.facturord.app/', // Opcional (valor por defecto)
apiKey: process.env.FACTURO_API_KEY!,
defaultCompanyId: process.env.FACTURO_COMPANY_ID,
});
async function run() {
// 1. Anular un e-NCF no utilizado (DGII ANECF v1.0)
try {
const res = await client.cancelSingleSequence(
31, // 31 = Factura de Crédito Fiscal Electrónica
'E310000000001',
{ dgiiEnv: 'testecf' }
);
console.log(`Respuesta DGII: ${res.name} (Código: ${res.code})`);
console.log(`Total anuladas: ${res.total_cancelled}`);
} catch (err) {
if (err instanceof FacturoAPIError) {
console.error(`Error de la API (${err.statusCode}):`, err.message);
} else {
console.error('Error inesperado:', err);
}
}
// 2. Anular un rango continuo
try {
const rangeRes = await client.cancelSequenceRange(31, 'E310000000010', 'E310000000020');
console.log(`Se anularon ${rangeRes.total_cancelled} secuencias.`);
} catch (err) {
console.error('Falló la anulación del rango:', err);
}
// 3. Descargar el XML firmado (string)
try {
const xml = await client.getSignedXml('E310000000001');
console.log('Longitud del XML firmado:', xml.length);
} catch (err) {
console.error('No se pudo obtener el XML firmado:', err);
}
// 4. Consultar el estado por Track ID
try {
const status = await client.getTrackingStatus('trk_ejemplo_12345');
console.log(`Estado DGII: ${status.dgii_status}, Último estado: ${status.last_status}`);
} catch (err) {
console.error('Falló la consulta:', err);
}
}
run();
Opciones del constructor
| Opción | Tipo | Descripción |
|---|---|---|
apiKey | string | Requerida. API Key de integración. |
baseUrl | string | URL base. Por defecto https://ecf.facturord.app/. |
defaultCompanyId | string | UUID de la empresa por defecto. |
timeoutMs | number | Timeout por petición en milisegundos. Por defecto 30000. |
customFetch | typeof fetch | Implementación de fetch personalizada (pruebas o polyfills). |
Cada método acepta además un objeto options con companyId (sobrescribe la empresa por defecto) y signal (AbortSignal para cancelar la petición). Los métodos de anulación y getSignedXml aceptan dgiiEnv; getSignedXml acepta también download.
Integración con NestJS
// facturo.service.ts
import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { FacturoClient } from './facturo-client';
@Injectable()
export class FacturoService {
private readonly logger = new Logger(FacturoService.name);
private readonly client: FacturoClient;
constructor(private readonly config: ConfigService) {
this.client = new FacturoClient({
baseUrl: this.config.get<string>('FACTURO_BASE_URL', 'https://ecf.facturord.app/'),
apiKey: this.config.getOrThrow<string>('FACTURO_API_KEY'),
defaultCompanyId: this.config.get<string>('FACTURO_COMPANY_ID'),
});
}
async voidInvoiceSequence(ecfType: number, encf: string) {
this.logger.log(`Anulando secuencia ${encf}...`);
return this.client.cancelSingleSequence(ecfType, encf);
}
}
Next.js Server Action
// app/actions/cancel-sequence.ts
'use server';
import { FacturoClient, FacturoAPIError } from '@/lib/facturo-client';
const client = new FacturoClient({
apiKey: process.env.FACTURO_API_KEY!,
defaultCompanyId: process.env.FACTURO_COMPANY_ID,
});
export async function cancelFiscalSequenceAction(ecfType: number, encf: string) {
try {
const result = await client.cancelSingleSequence(ecfType, encf);
return { success: true, total: result.total_cancelled, code: result.code };
} catch (err: any) {
if (err instanceof FacturoAPIError) {
return { success: false, error: err.message, status: err.statusCode };
}
return { success: false, error: 'Error interno' };
}
}
Referencia de métodos
| Método | Retorna | Descripción |
|---|---|---|
cancelRanges(details, options?) | CancelRangesResponse | Anulación por lotes ante la DGII (ANECF v1.0) |
cancelSingleSequence(ecfType, encf, options?) | CancelRangesResponse | Anula un único e-NCF |
cancelSequenceRange(ecfType, fromEncf, toEncf, options?) | CancelRangesResponse | Anula un rango continuo |
emitRichECF(ecfPayload, options?) | ECFEmissionResponse | Emite un e-CF completo para firma y envío |
getTrackingStatus(trackId, options?) | InvoiceTrackingStatusResponse | Estado en la DGII por Track ID |
getSignedXml(documentIdOrEncf, options?) | string | XML firmado en bruto |
retryEmission(documentId, options?) | EmissionResult | Reintenta un documento pendiente o fallido |
Errores
Todas las fallas se lanzan como FacturoAPIError con statusCode y payload:
| Caso | statusCode |
|---|---|
| Error HTTP de la API | Código HTTP devuelto |
company_id no proporcionado | 400 |
| Timeout | 408 |
| Fallo de red | 500 |