const dotenv = require("dotenv"); dotenv.config(); const axios = require("axios"); const { sleep } = require("./sleepService"); const { normalizePartnerSigla } = require("./partnerSiglaService"); // Configure sua API_KEY e COOKIE aqui ou via variáveis de ambiente const API_URL = process.env.API_URL; const API_KEY = process.env.API_KEY; const COOKIE = process.env.COOKIE; const HEADERS = { "api-key": API_KEY, Cookie: COOKIE, }; // small fetch wrapper for external services (ViaCEP etc.) with basic rate-limiting const BASE_BACKOFF_MS = 10; // backoff inicial para retry const MAX_RETRIES = 5; async function getMinDistance(lat, lon) { // tenta várias vezes com backoff exponencial; trata 429 usando Retry-After se disponível let attempt = 0; while (attempt < MAX_RETRIES) { try { // envia também o raio (em metros) - API espera esse parâmetro em várias rotas const resp = await axios.get(API_URL, { headers: HEADERS, params: { raio: 5000, latitude: lat, longitude: lon, "itens[]": ["caixa"], consultarPasta: "S", }, timeout: 10000, }); const data = resp.data; const registros = data && data.registros ? data.registros : []; // find registros that have a numeric distancia and keep original object for robust extraction const candidates = registros .map((r) => ({ raw: r, distanciaRaw: r && r.distancia })) .map((o) => ({ raw: o.raw, num: o.distanciaRaw !== undefined && o.distanciaRaw !== null && o.distanciaRaw !== "" ? Number(o.distanciaRaw) : null, })) .filter((x) => x.num !== null && !Number.isNaN(x.num)); if (candidates.length) { candidates.sort((a, b) => a.num - b.num); const best = candidates[0]; const r = best.raw || {}; // robust extraction of pasta sigla with fallbacks let pastaSigla = null; try { if (r.pasta) { if (typeof r.pasta === "string" && r.pasta.trim()) pastaSigla = r.pasta.trim(); else if (r.pasta.sigla && String(r.pasta.sigla).trim()) pastaSigla = String(r.pasta.sigla).trim(); else if ( r.pasta.cidade && r.pasta.cidade.sigla && String(r.pasta.cidade.sigla).trim() ) pastaSigla = String(r.pasta.cidade.sigla).trim(); } } catch (e) { pastaSigla = null; } // if closest has no pastaSigla, try find any candidate with non-empty sigla if (!pastaSigla) { for (let j = 0; j < candidates.length; j++) { const rr = candidates[j].raw || {}; try { if (rr.pasta) { if (typeof rr.pasta === "string" && rr.pasta.trim()) { pastaSigla = rr.pasta.trim(); break; } if (rr.pasta.sigla && String(rr.pasta.sigla).trim()) { pastaSigla = String(rr.pasta.sigla).trim(); break; } if ( rr.pasta.cidade && rr.pasta.cidade.sigla && String(rr.pasta.cidade.sigla).trim() ) { pastaSigla = String(rr.pasta.cidade.sigla).trim(); break; } } } catch (e) { // continue } } } if (!pastaSigla) console.warn( `[WARN] Nenhuma pasta.sigla encontrada para coordenadas ${lat},${lon} (closest dist ${best.num})` ); pastaSigla = normalizePartnerSigla(pastaSigla); return { dist: best.num, pastaSigla }; } // sem distancias válidas return null; } catch (err) { attempt += 1; // se for 429, tente respeitar Retry-After quando disponível if (err.response && err.response.status === 429) { const ra = err.response.headers && (err.response.headers["retry-after"] || err.response.headers["Retry-After"]); let waitMs = BASE_BACKOFF_MS * Math.pow(2, attempt - 1); if (ra) { const raSec = parseInt(ra, 10); if (!isNaN(raSec)) waitMs = raSec * 1000; } console.warn( `[WARN] 429 recebido para ${lat},${lon} - aguardando ${waitMs}ms e tentando novamente (attempt ${attempt}/${MAX_RETRIES})` ); await sleep(waitMs); continue; } // para outros erros de rede/timeout, aguarda backoff exponencial e tenta de novo const waitMs = BASE_BACKOFF_MS * Math.pow(2, attempt - 1); console.warn( `[WARN] Erro ao consultar API para ${lat},${lon}: ${err.message} - backoff ${waitMs}ms (attempt ${attempt}/${MAX_RETRIES})` ); await sleep(waitMs); } } // exauriu tentativas console.error(`[ERROR] Exauriu retries para ${lat},${lon}`); return null; } module.exports = { getMinDistance };