Saltar al contenido principal

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 fetch y AbortController. No requiere axios, node-fetch ni 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ónTipoDescripción
apiKeystringRequerida. API Key de integración.
baseUrlstringURL base. Por defecto https://ecf.facturord.app/.
defaultCompanyIdstringUUID de la empresa por defecto.
timeoutMsnumberTimeout por petición en milisegundos. Por defecto 30000.
customFetchtypeof fetchImplementació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étodoRetornaDescripción
cancelRanges(details, options?)CancelRangesResponseAnulación por lotes ante la DGII (ANECF v1.0)
cancelSingleSequence(ecfType, encf, options?)CancelRangesResponseAnula un único e-NCF
cancelSequenceRange(ecfType, fromEncf, toEncf, options?)CancelRangesResponseAnula un rango continuo
emitRichECF(ecfPayload, options?)ECFEmissionResponseEmite un e-CF completo para firma y envío
getTrackingStatus(trackId, options?)InvoiceTrackingStatusResponseEstado en la DGII por Track ID
getSignedXml(documentIdOrEncf, options?)stringXML firmado en bruto
retryEmission(documentId, options?)EmissionResultReintenta un documento pendiente o fallido

Errores​

Todas las fallas se lanzan como FacturoAPIError con statusCode y payload:

CasostatusCode
Error HTTP de la APICódigo HTTP devuelto
company_id no proporcionado400
Timeout408
Fallo de red500