2025-12-30 09:16:07 -03:00
|
|
|
|
const axios = require('axios');
|
2026-01-15 15:16:28 -03:00
|
|
|
|
const fs = require('fs');
|
|
|
|
|
|
const readline = require('readline');
|
2026-05-04 16:38:37 -03:00
|
|
|
|
const XLSX = require('xlsx');
|
2026-07-21 15:49:29 -03:00
|
|
|
|
const { apiUrlBase } = require('../config/apiConfig');
|
|
|
|
|
|
const {
|
|
|
|
|
|
normalizeHeader,
|
|
|
|
|
|
isExcelFile,
|
|
|
|
|
|
detectDelimiter,
|
|
|
|
|
|
hasCepHeader,
|
|
|
|
|
|
hasAddressOrNumberHeader,
|
|
|
|
|
|
hasGeoHeaders
|
|
|
|
|
|
} = require('./fileFormat');
|
2026-05-04 16:38:37 -03:00
|
|
|
|
|
2026-05-04 16:52:23 -03:00
|
|
|
|
function findHeaderRow(rows) {
|
|
|
|
|
|
return rows.find(row => {
|
2026-07-21 15:49:29 -03:00
|
|
|
|
const hasCepNumero = hasCepHeader(row) && hasAddressOrNumberHeader(row);
|
|
|
|
|
|
const hasGeo = hasGeoHeaders(row);
|
2026-05-04 16:52:23 -03:00
|
|
|
|
return hasCepNumero || hasGeo;
|
|
|
|
|
|
}) || [];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-04 16:38:37 -03:00
|
|
|
|
function readExcelHeaders(filePath) {
|
|
|
|
|
|
const workbook = XLSX.readFile(filePath, { cellDates: false, raw: false });
|
|
|
|
|
|
const firstSheetName = workbook.SheetNames[0];
|
|
|
|
|
|
if (!firstSheetName) return [];
|
|
|
|
|
|
|
|
|
|
|
|
const rows = XLSX.utils.sheet_to_json(workbook.Sheets[firstSheetName], {
|
|
|
|
|
|
header: 1,
|
2026-05-12 17:57:30 -03:00
|
|
|
|
blankrows: true,
|
2026-05-04 16:38:37 -03:00
|
|
|
|
defval: ''
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-05-04 16:52:23 -03:00
|
|
|
|
return findHeaderRow(rows).map(normalizeHeader);
|
2026-05-04 16:38:37 -03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function readDelimitedHeaders(filePath) {
|
|
|
|
|
|
const instream = fs.createReadStream(filePath, { encoding: 'utf8' });
|
|
|
|
|
|
const rl = readline.createInterface({ input: instream, crlfDelay: Infinity });
|
|
|
|
|
|
|
|
|
|
|
|
for await (const rawLine of rl) {
|
2026-07-21 15:49:29 -03:00
|
|
|
|
const line = rawLine.replace(/^/, '').replace(/\r$/, '');
|
2026-05-04 16:38:37 -03:00
|
|
|
|
if (!line.trim()) continue;
|
2026-05-04 16:52:23 -03:00
|
|
|
|
const headers = line.split(detectDelimiter(line)).map(normalizeHeader);
|
2026-07-21 15:49:29 -03:00
|
|
|
|
if ((hasCepHeader(headers) && hasAddressOrNumberHeader(headers)) || hasGeoHeaders(headers)) {
|
2026-05-04 16:52:23 -03:00
|
|
|
|
rl.close();
|
|
|
|
|
|
return headers;
|
|
|
|
|
|
}
|
2026-05-04 16:38:37 -03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
rl.close();
|
|
|
|
|
|
return [];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-21 15:49:29 -03:00
|
|
|
|
// Verifica se os dados vindos são de CEP ou de geolocalização (aceita caminho de arquivo ou objeto).
|
|
|
|
|
|
async function discoverDataType(input) {
|
|
|
|
|
|
if (typeof input === 'string') {
|
|
|
|
|
|
const headers = isExcelFile(input)
|
|
|
|
|
|
? readExcelHeaders(input)
|
|
|
|
|
|
: await readDelimitedHeaders(input);
|
2026-05-04 16:38:37 -03:00
|
|
|
|
|
2026-07-21 15:49:29 -03:00
|
|
|
|
if (hasCepHeader(headers) && hasAddressOrNumberHeader(headers)) return 'cep';
|
|
|
|
|
|
if (hasGeoHeaders(headers)) return 'geolocalizacao';
|
|
|
|
|
|
return 'unknown';
|
|
|
|
|
|
}
|
2025-12-30 09:16:07 -03:00
|
|
|
|
|
2026-07-21 15:49:29 -03:00
|
|
|
|
if (input && typeof input === 'object') {
|
|
|
|
|
|
if (input.cep && input.numero) return 'cep';
|
|
|
|
|
|
if (input.latitude !== undefined && input.longitude !== undefined) return 'geolocalizacao';
|
|
|
|
|
|
return 'unknown';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return 'unknown';
|
2026-05-05 16:05:49 -03:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-21 15:49:29 -03:00
|
|
|
|
// Consulta individual (manual / uma linha). Escolhe o endpoint conforme o tipo dos dados.
|
2025-12-30 09:16:07 -03:00
|
|
|
|
async function consultarViabilidade(data) {
|
2026-07-21 15:49:29 -03:00
|
|
|
|
const dataType = await discoverDataType(data);
|
|
|
|
|
|
const endpoint = apiUrlBase + (dataType === 'geolocalizacao' ? 'viabilidade/lat-long' : 'viabilidade');
|
|
|
|
|
|
|
|
|
|
|
|
const response = await axios.post(endpoint, data, {
|
|
|
|
|
|
// A API agora faz geocode + GeoGrid + trajeto (várias chamadas externas);
|
|
|
|
|
|
// o cep-promise também pode ser lento. 30s evita timeout prematuro.
|
|
|
|
|
|
timeout: 30000,
|
|
|
|
|
|
headers: { 'Content-Type': 'application/json' }
|
|
|
|
|
|
});
|
|
|
|
|
|
return response.data;
|
2025-12-30 09:16:07 -03:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-21 15:49:29 -03:00
|
|
|
|
// Consulta em LOTE: envia um array de itens ({cep,numero} ou {latitude,longitude}) de uma só vez.
|
|
|
|
|
|
// Retorna o array `resultados` da API, alinhado por índice ao array enviado.
|
|
|
|
|
|
async function consultarViabilidadeLote(itens, modo) {
|
|
|
|
|
|
const endpoint = apiUrlBase + 'viabilidade/lote';
|
|
|
|
|
|
const body = { itens };
|
|
|
|
|
|
if (modo) body.modo = modo;
|
2026-01-15 15:16:28 -03:00
|
|
|
|
|
2026-07-21 15:49:29 -03:00
|
|
|
|
const response = await axios.post(endpoint, body, {
|
|
|
|
|
|
timeout: 180000,
|
|
|
|
|
|
headers: { 'Content-Type': 'application/json' }
|
|
|
|
|
|
});
|
2026-01-15 15:16:28 -03:00
|
|
|
|
|
2026-07-21 15:49:29 -03:00
|
|
|
|
const data = response.data || {};
|
|
|
|
|
|
return Array.isArray(data.resultados) ? data.resultados : [];
|
2026-01-15 15:16:28 -03:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-21 15:49:29 -03:00
|
|
|
|
module.exports = { consultarViabilidade, consultarViabilidadeLote, discoverDataType };
|