Ejemplos de código
Snippets de los flujos más comunes en tres lenguajes. Todos asumen:
export FE_API_KEY_ID="fak_a1b2c3d4e5f6"
export FE_API_SECRET="abcd1234ef56..."
export FE_API_BASE="https://digimart-api-v2.appworkcloud.com/api/v1/fe"1. Cliente completo con caché de JWT
// fe-api-client.js
let cached = { token: null, expiry: 0 };
async function getToken() {
if (cached.token && Date.now() < cached.expiry - 60_000) {
return cached.token;
}
const resp = await fetch(`${process.env.FE_API_BASE}/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
apiKeyId: process.env.FE_API_KEY_ID,
apiSecret: process.env.FE_API_SECRET,
}),
});
if (!resp.ok) throw new Error(`Login failed: ${resp.status}`);
const { accessToken, expiresIn } = await resp.json();
cached = { token: accessToken, expiry: Date.now() + expiresIn * 1000 };
return accessToken;
}
async function authedFetch(path, opts = {}) {
const token = await getToken();
return fetch(`${process.env.FE_API_BASE}${path}`, {
...opts,
headers: {
...(opts.headers || {}),
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
});
}
export { authedFetch };2. Emitir un e-CF y esperar el resultado
import { authedFetch } from './fe-api-client.js';
async function emitInvoice(payload, clientRequestId) {
const resp = await authedFetch('/invoices', {
method: 'POST',
body: JSON.stringify({
format: 'digimart',
clientRequestId,
payload,
}),
});
if (!resp.ok) {
const err = await resp.json();
throw new Error(`POST /invoices failed: ${err.message}`);
}
const { dgiiLogId } = await resp.json();
return dgiiLogId;
}
async function waitForResult(dgiiLogId, opts = {}) {
const { maxAttempts = 15, baseDelayMs = 1000 } = opts;
for (let i = 0; i < maxAttempts; i++) {
const resp = await authedFetch(`/invoices/${dgiiLogId}`);
const data = await resp.json();
if (data.status === 'Aceptado' || data.status === 'Aceptado Condicional') {
return { success: true, ...data };
}
if (data.status === 'Rechazado') {
return { success: false, ...data };
}
if (data.response?.status === 'error') {
throw new Error(`Permanent error: ${data.response.error}`);
}
// queued / En Proceso → backoff exponential
await new Promise(r => setTimeout(r, baseDelayMs * Math.pow(1.5, i)));
}
throw new Error('Timeout esperando estado final');
}
// Uso:
const dgiiLogId = await emitInvoice(myPayload, 'order-12345');
const result = await waitForResult(dgiiLogId);
console.log('Estado:', result.status, 'NCF:', result.response?.encf);3. Dashboard de uso (últimos 30 días)
async function getUsageStats(days = 30) {
const resp = await authedFetch(`/usage?days=${days}`);
const { items } = await resp.json();
const totals = items.reduce((acc, day) => ({
emissionsAccepted: acc.emissionsAccepted + day.emissionsAccepted,
emissionsRejected: acc.emissionsRejected + day.emissionsRejected,
requestsTotal: acc.requestsTotal + day.requestsTotal,
}), { emissionsAccepted: 0, emissionsRejected: 0, requestsTotal: 0 });
const successRate = (totals.emissionsAccepted /
(totals.emissionsAccepted + totals.emissionsRejected) * 100).toFixed(2);
console.log(`Últimos ${days} días:`);
console.log(` ${totals.emissionsAccepted} aceptadas`);
console.log(` ${totals.emissionsRejected} rechazadas (${100 - successRate}%)`);
console.log(` ${totals.requestsTotal} requests totales`);
}4. Monitor de presupuesto
const BUDGET_USD = 100; // alerta si proyección excede este monto
async function checkBudget() {
const resp = await authedFetch('/billing/current-period');
const { period, emissionsAccepted, subtotal, currency } = await resp.json();
console.log(`Período ${period}:`);
console.log(` ${emissionsAccepted} emisiones`);
console.log(` Proyección: ${currency} ${subtotal.toFixed(3)}`);
if (subtotal > BUDGET_USD) {
await notifySlack({
text: `⚠️ FE API consumo este mes ($${subtotal.toFixed(2)}) excedió budget ($${BUDGET_USD})`,
});
}
}
// Correr cada hora en un cron
setInterval(checkBudget, 60 * 60 * 1000);5. Webhook handler de tu lado (post-emisión)
La FE API no emite webhooks todavía. El patrón es polling con
GET /invoices/:dgiiLogId. Si necesitas webhooks, escríbenos.
Patrón recomendado: en tu DB local guarda el dgiiLogId junto a tu
factura interna. Un cron cada 1-5 minutos itera sobre los que están en
pending y actualiza estados:
async function syncPendingEmissions() {
const pending = await db.query(
`SELECT id, dgii_log_id FROM facturas
WHERE estado_dgii IN ('pending', 'En Proceso')
LIMIT 100`
);
for (const row of pending) {
const resp = await authedFetch(`/invoices/${row.dgii_log_id}`);
const data = await resp.json();
if (data.status && data.status !== 'queued' && data.status !== 'En Proceso') {
await db.query(
`UPDATE facturas SET estado_dgii = $1, qr_code = $2, ncf_seguro = $3
WHERE id = $4`,
[data.status, data.qrCodeUrl, data.securityCode, row.id]
);
}
}
}