Método Async — Frontend / Backend

Patrón de integración donde el backend envía el comprobante y retorna inmediatamente, mientras el frontend consulta el estado directamente contra ECF SSD. Ideal para aplicaciones web, móviles y sistemas de alta concurrencia donde no deseas bloquear el backend.

¿Cuándo usar este método? — Cuando tu aplicación tiene un frontend (web/móvil) y quieres que el usuario vea el progreso en tiempo real sin bloquear tu servidor.

Diferencia con el método Sync

AspectoSync (Backend)Async (Frontend/Backend)
Quién hace el pollingEl SDK en el backendEl frontend (browser/app)
Backend bloqueadoSí (~300ms a 2 seg)No — retorna inmediatamente
Experiencia de usuarioEspera hasta completarVe progreso en tiempo real
Seguridad del tokenToken completo en backendToken de solo lectura en frontend
ComplejidadBaja — una llamadaMedia — requiere coordinar frontend y backend
Ideal paraPOS, ERP, scripts, CLIApps web, móviles, SaaS

Flujo detallado

FEEl cliente envía los datos de factura al backend (POST /api/invoices)
BEEl backend valida, guarda y convierte al formato ECF
BEEnvía el ECF a ECF SSD (POST /ecf/31) — recibe un messageId
BERetorna messageId, rnc, encf al cliente — no espera
El backend queda libre para atender otras solicitudes
FEEl frontend usa EcfFrontendClient con token de solo lectura
Token se obtiene vía getToken() → cache automático → refresh en 401
FEPolling directo contra ECF SSD (GET /ecf/{rnc}/{encf})
Cuando progress === “Finished” → muestra QR, código de seguridad, etc.

Por qué este patrón

  • Descarga el backend — Las consultas de estado (polling) las hace el cliente directamente contra ECF SSD.
  • Seguridad — El token del cliente es de solo lectura y está limitado al RNC. No puede enviar comprobantes ni modificar datos.
  • Tiempo real — El cliente puede ver el progreso sin depender del backend.
  • Token automáticoEcfFrontendClient maneja el ciclo de vida del token — cache, refresh en 401, y reintento.

Implementación Backend (Node.js / TypeScript)

El backend expone dos endpoints: uno para enviar la factura y otro para generar el token de solo lectura del frontend.

typescript
import { EcfClient } from "@ssddo/ecf-sdk";
const ecfClient = new EcfClient({
apiKey: process.env.ECF_BACKEND_TOKEN,
environment: "prod",
});
// Endpoint de facturación — envía y retorna inmediatamente
app.post("/api/v1/invoices", async (req, res) => {
const invoice = await validateAndSave(req.body);
// Construir el ECF desde tu modelo de negocio
const ecf = {
encabezado: {
idDoc: {
tipoeCF: "FacturaDeCreditoFiscalElectronica",
encf: invoice.encf,
},
emisor: {
rncEmisor: tenant.rnc,
razonSocialEmisor: tenant.razonSocial,
direccionEmisor: tenant.direccion,
fechaEmision: new Date().toISOString(),
},
comprador: {
rncComprador: invoice.clienteRnc,
razonSocialComprador: invoice.clienteNombre,
},
totales: {
montoGravadoTotal: invoice.subtotal,
totalITBIS: invoice.itbis,
montoTotal: invoice.total,
},
},
detallesItems: invoice.items.map((item) => ({
nombreItem: item.nombre,
indicadorFacturacion: 3,
cantidadItem: item.cantidad,
precioUnitarioItem: item.precio,
montoItem: item.cantidad * item.precio,
})),
};
// POST directo al API — NO usa sendEcf (que haría polling)
const { data } = await ecfClient.raw.POST("/ecf/31", { body: ecf });
// Guardar el messageId para tracking
await updateInvoice(invoice.id, { messageId: data.messageId });
// Retornar inmediatamente — el frontend hará el polling
res.json({
id: invoice.id,
messageId: data.messageId,
rnc: tenant.rnc,
encf: invoice.encf,
});
});
// Endpoint para generar token de solo lectura para el frontend
app.get("/api/v1/ecf-token", async (req, res) => {
const { data } = await ecfClient.createApiKey({
rnc: tenant.rnc,
});
res.json({ apiKey: data.token });
});

Implementación Frontend (React)

El frontend crea un cliente de solo lectura, envía la factura al backend, y luego consulta el estado directamente contra ECF SSD:

tsx
import { createEcfFrontendReactClient } from "@ssddo/ecf-react";
// 1. Crear cliente de solo lectura
// getToken se llama automáticamente cuando se necesita
const { $api } = createEcfFrontendReactClient({
environment: "prod",
getToken: async () => {
const res = await fetch("/api/v1/ecf-token");
const { apiKey } = await res.json();
return apiKey;
},
});
// 2. Componente para enviar la factura al backend
function EnviarFactura() {
const handleSubmit = async (invoiceData) => {
const res = await fetch("/api/v1/invoices", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(invoiceData),
});
const { rnc, encf } = await res.json();
// No espera — navega a la página de estado
navigate(`/ecf-status/${rnc}/${encf}`);
};
return <button onClick={() => handleSubmit(datos)}>Enviar factura</button>;
}
// 3. Componente que hace polling del estado en tiempo real
function EstadoEcf({ rnc, encf }: { rnc: string; encf: string }) {
const { data } = $api.useQuery("get", "/ecf/{rnc}/{encf}", {
params: { path: { rnc, encf } },
refetchInterval: 3000, // Consulta cada 3 segundos
});
if (!data) return <p>Conectando...</p>;
if (data.progress === "Finished") {
return (
<div>
<h2>Comprobante aceptado</h2>
<p>Código de seguridad: {data.codSec}</p>
<p>Fecha de firma: {data.fechaFirma}</p>
<QRCode value={data.impresionUrl} />
</div>
);
}
if (data.progress === "Error") {
return <p>Error: {data.mensaje}</p>;
}
// Muestra progreso en tiempo real
return (
<div>
<p>Procesando comprobante...</p>
<p>Estado: {data.progress}</p>
{/* Queued → Sending → Polling → Finished */}
</div>
);
}

Frontend sin React (TypeScript puro)

Si no usas React, puedes usar createFrontendClient del SDK de TypeScript directamente:

typescript
import { createFrontendClient } from "@ssddo/ecf-sdk";
const frontend = createFrontendClient({
environment: "prod",
getToken: async () => {
const res = await fetch("/api/v1/ecf-token");
const { apiKey } = await res.json();
return apiKey;
},
});
// Polling manual
async function esperarResultado(rnc: string, encf: string) {
let result;
do {
const { data } = await frontend.raw.GET("/ecf/{rnc}/{encf}", {
params: { path: { rnc, encf } },
});
result = data;
if (result.progress === "Finished") {
console.log("Código seguridad:", result.codSec);
console.log("QR:", result.impresionUrl);
return result;
}
if (result.progress === "Error") {
throw new Error(result.mensaje);
}
// Esperar antes de reintentar
await new Promise((r) => setTimeout(r, 3000));
} while (true);
}

Seguridad del Token Frontend

El EcfFrontendClient maneja automáticamente el ciclo de vida del token:

  • Cache automático — Almacena el token en localStorage (web), Keychain (iOS), o archivo cifrado (Java/C++) para no pedirlo en cada request.
  • Refresh en 401 — Si el API retorna 401 (token expirado), automáticamente llama a getToken() de nuevo, actualiza el cache y reintenta.
  • Solo lectura — El token del frontend solo puede consultar estado de comprobantes (GET). No puede enviar, modificar ni eliminar datos.
  • Scoped al RNC — Cada token está limitado al RNC de la empresa, proporcionando aislamiento entre tenants.

Recomendación: Para la mayoría de aplicaciones web y móviles, el método async es la mejor opción. Tu backend se mantiene ligero y el usuario ve el progreso en tiempo real.

¿Buscas la versión simple? Si tu sistema puede esperar la respuesta, el método sync (solo backend) es más fácil de implementar con una sola llamada.