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
| Aspecto | Sync (Backend) | Async (Frontend/Backend) |
|---|---|---|
| Quién hace el polling | El SDK en el backend | El frontend (browser/app) |
| Backend bloqueado | Sí (~300ms a 2 seg) | No — retorna inmediatamente |
| Experiencia de usuario | Espera hasta completar | Ve progreso en tiempo real |
| Seguridad del token | Token completo en backend | Token de solo lectura en frontend |
| Complejidad | Baja — una llamada | Media — requiere coordinar frontend y backend |
| Ideal para | POS, ERP, scripts, CLI | Apps web, móviles, SaaS |
Flujo detallado
POST /api/invoices)POST /ecf/31) — recibe un messageIdmessageId, rnc, encf al cliente — no esperaEcfFrontendClient con token de solo lecturaGET /ecf/{rnc}/{encf})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ático —
EcfFrontendClientmaneja 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.
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 inmediatamenteapp.post("/api/v1/invoices", async (req, res) => {const invoice = await validateAndSave(req.body);// Construir el ECF desde tu modelo de negocioconst 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 trackingawait updateInvoice(invoice.id, { messageId: data.messageId });// Retornar inmediatamente — el frontend hará el pollingres.json({id: invoice.id,messageId: data.messageId,rnc: tenant.rnc,encf: invoice.encf,});});// Endpoint para generar token de solo lectura para el frontendapp.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:
import { createEcfFrontendReactClient } from "@ssddo/ecf-react";// 1. Crear cliente de solo lectura// getToken se llama automáticamente cuando se necesitaconst { $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 backendfunction 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 estadonavigate(`/ecf-status/${rnc}/${encf}`);};return <button onClick={() => handleSubmit(datos)}>Enviar factura</button>;}// 3. Componente que hace polling del estado en tiempo realfunction 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 realreturn (<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:
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 manualasync 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 reintentarawait 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.