const BASE = `${process.env.DATAVERSE_URL}/api/data/v9.2`; const MAX_RETRIES = 3; function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } // Reintenta 429 (throttling de Dataverse) respetando Retry-After — ver migration.md §8. export async function dv(path, accessToken, attempt = 0) { const r = await fetch(`${BASE}/${path}`, { headers: { Authorization: `Bearer ${accessToken}`, Accept: "application/json", "OData-MaxVersion": "4.0", "OData-Version": "4.0", Prefer: 'odata.include-annotations="OData.Community.Display.V1.FormattedValue"', }, cache: "no-store", // decisión: sin caché, datos en vivo }); if (r.status === 401) throw new Error("TOKEN_EXPIRED"); if (r.status === 429 && attempt < MAX_RETRIES) { const retryAfter = Number(r.headers.get("Retry-After")) || 2 ** attempt; await sleep(retryAfter * 1000); return dv(path, accessToken, attempt + 1); } if (!r.ok) throw new Error(`DATAVERSE_${r.status}: ${(await r.text()).slice(0, 300)}`); return r.json(); } // Etiqueta legible de un option set, entregada por el header Prefer de arriba. export function fv(o, key) { return o[`${key}@OData.Community.Display.V1.FormattedValue`] || null; } export function odataString(s) { return String(s).replace(/'/g, "''"); }