const geogridService = require('../../shared/apis/geogridService.js'); const apiConfig = require('../../shared/config/apiConfig.js'); const logger = require('../../shared/utils/logger.js'); const { mapWithConcurrency } = require('../../shared/utils/concurrency.js'); // Regra de negócio PURA: dada uma distância (em metros), define quais serviços // estão disponíveis. É a mesma regra de sempre — o que muda é a distância que // alimenta ela (agora a real de trajeto). Isolada e testável. function classificarPorDistancia(distancia) { if (distancia == null || typeof distancia !== 'number' || Number.isNaN(distancia)) { return { dedicado: false, naoDedicado: false }; } if (distancia <= 500) return { dedicado: true, naoDedicado: true }; if (distancia <= 1000) return { dedicado: true, naoDedicado: false }; return { dedicado: false, naoDedicado: false }; // 1001–5000m: caixa existe, mas fora do alcance } // Núcleo de orquestração: a partir de coordenadas de origem (endereço), // 1) acha as N caixas mais próximas em linha reta (GeoGrid), // 2) calcula a distância REAL de trajeto até cada uma e pega a menor, // 3) classifica a viabilidade sobre a distância real (com fallback p/ reta). // // Retorna um resultado de domínio NEUTRO (sem formato de resposta HTTP): // { distancia, distanciaReta, distanciaReal, dedicado, naoDedicado, provedor } // - `distancia` é a EFETIVA usada na regra: real quando disponível, senão reta. async function avaliarPorCoordenadas(origem, { modo = apiConfig.geogridTrajetoModo, topN, misturarProvedores = false } = {}) { const { provedor, caixas } = await geogridService.consultarCaixasProximas(origem.lat, origem.lon, { topN, misturarProvedores }); // Sem caixa no raio: mantém o comportamento histórico ("5KM+", nada viável). if (!caixas || caixas.length === 0) { return { distancia: "5KM+", distanciaReta: null, distanciaReal: null, dedicado: false, naoDedicado: false, provedor, caixas: [] }; } // Para cada caixa (na ordem do GeoGrid = por distância em linha reta), // calcula a distância real de trajeto e classifica individualmente. const detalhes = await mapWithConcurrency(caixas, apiConfig.geogridConcurrency, async (caixa) => { let distanciaReal = null; if (caixa.lat != null && caixa.lon != null) { try { const trajeto = await geogridService.consultaTrajeto(origem, caixa, modo); const d = Number(trajeto && trajeto.distancia); distanciaReal = Number.isFinite(d) ? d : null; } catch (err) { logger.warn('Trajeto falhou para uma caixa, usando linha reta nesta candidata', { message: err.message, status: err.response?.status, data: err.response?.data, // corpo do erro do GeoGrid (motivo do 400) url: err.config?.url, caixa: { lat: caixa.lat, lon: caixa.lon } }); } } const distancia = distanciaReal ?? caixa.distanciaReta; // efetiva por caixa (real, senão reta) return { provedor: caixa.provedor, sigla: caixa.sigla, lat: caixa.lat, lon: caixa.lon, distanciaReta: caixa.distanciaReta, distanciaReal, distancia, ...classificarPorDistancia(distancia) }; }); // Agregado (compatibilidade WordPress): menor distância efetiva entre as caixas. const distanciaReta = detalhes[0].distanciaReta ?? null; // caixa mais próxima em linha reta const reais = detalhes.map(d => d.distanciaReal).filter(d => d != null); const distanciaReal = reais.length ? Math.min(...reais) : null; if (distanciaReal == null) { logger.warn('Nenhum trajeto disponível; usando distância em linha reta como fallback', { origem, distanciaReta }); } const distanciaEfetiva = distanciaReal ?? distanciaReta; return { distancia: distanciaEfetiva, distanciaReta, distanciaReal, ...classificarPorDistancia(distanciaEfetiva), provedor, caixas: detalhes // detalhe por caixa (mais próxima em linha reta primeiro) }; } module.exports = { classificarPorDistancia, avaliarPorCoordenadas };