diff --git a/.env b/.env index 256a77c..0e45aa0 100644 --- a/.env +++ b/.env @@ -1,5 +1,5 @@ GOOGLE_API_KEY="AIzaSyCTrRFGKCZSspHRmTWQiclmIEOg-LROgyo" API_URL="https://plutao.geogridmaps.com.br/vale/api/v3/viabilidade/raio" API_KEY="6d717e972ba17c7cf0ab731801b8bbeac2f281e5" -COKIE="PHPSESSID=6d717e972ba17c7cf0ab731801b8bbeac2f281e5" +COOKIE="PHPSESSID=6d717e972ba17c7cf0ab731801b8bbeac2f281e5" PORT="3000" \ No newline at end of file diff --git a/app.js b/app.js index c189414..7898970 100644 --- a/app.js +++ b/app.js @@ -48,8 +48,8 @@ function createApp() { .json({ error: "geocode não encontrado (Google)" }); const lat = Number(geo.lat); const lon = Number(geo.lon); - const result = await getMinDistance(lat, lon); - if (result && result.dist !== undefined) { + const result = await getMinDistance(lat, lon); + if (result && result.dist !== undefined) { // preciso criar 2 campos: Link Dedicado e Link Não Dedicado em que o dedicado é viável até 1000m e o não dedicado até 500m if (result.dist <= 500) { return res.json({ @@ -80,6 +80,16 @@ function createApp() { }); } } + // quando a consulta não retorna um resultado válido, responder como 'Não viável' + // isso evita que a UI fique presa em "Consultando..." esperando uma resposta + return res.json({ + endereco, + latitude: lat, + longitude: lon, + distancia: null, + dedicado: "Não viável", + naoDedicado: "Não viável", + }); } catch (err) { console.error(err); return res.status(500).json({ error: "erro na consulta" }); diff --git a/services/distanceService.js b/services/distanceService.js index 0dde7cc..46e93d0 100644 --- a/services/distanceService.js +++ b/services/distanceService.js @@ -23,126 +23,138 @@ const { normalizePartnerSigla } = require("./partnerSiglaService"); 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 || {}; + const radii = [5000, 10000, 20000]; + for (const raio of radii) { + let attempt = 0; + while (attempt < MAX_RETRIES) { + try { + // observe que aqui enviamos itens[] como string simples (algumas APIs esperam isto) + const resp = await axios.get(API_URL, { + headers: HEADERS, + params: { + raio, + latitude: lat, + longitude: lon, + "itens[]": "caixa", + consultarPasta: "S", + }, + timeout: 10000, + }); + + const data = resp.data || {}; + const registros = data.registros || []; + console.info( + `[INFO] API resp. raio=${raio} lat=${lat} lon=${lon} registros=${registros.length}` + ); + if (registros.length > 0) { + // same extraction logic as before + 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 || {}; + let pastaSigla = null; 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; - } + 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) { - // continue + pastaSigla = null; } + 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 }; } - } - if (!pastaSigla) - console.warn( - `[WARN] Nenhuma pasta.sigla encontrada para coordenadas ${lat},${lon} (closest dist ${best.num})` + } else { + // log mais detalhado para diagnóstico quando vazio + console.debug( + `[DEBUG] resposta vazia para raio=${raio} lat=${lat} lon=${lon} -> primeiroFragmento: ${JSON.stringify( + Array.isArray(data.registros) ? data.registros.slice(0, 3) : data + )}` ); - 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; } + // sem distancias válidas para este raio -> tenta próximo raio + break; + } catch (err) { + attempt += 1; + 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; + } + const waitMs = BASE_BACKOFF_MS * Math.pow(2, attempt - 1); console.warn( - `[WARN] 429 recebido para ${lat},${lon} - aguardando ${waitMs}ms e tentando novamente (attempt ${attempt}/${MAX_RETRIES})` + `[WARN] Erro ao consultar API para ${lat},${lon}: ${err.message} - backoff ${waitMs}ms (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); } + // pequena espera entre mudanças de raio + await sleep(50); } - // exauriu tentativas - console.error(`[ERROR] Exauriu retries para ${lat},${lon}`); + + console.error(`[ERROR] Sem registros válidos para ${lat},${lon} após todos os raios`); return null; }