Determina a viabilidade para "Link Dedicado" (<= 1000m) e "Link Não Dedicado" (<= 500m) com base na distância retornada.Estrutura e estiliza página de inserção com alguns lorem's enquanto não há texto definido. Insere lógica de inserção de classes para retorno de resultados visualmente.
This commit is contained in:
parent
001a007b68
commit
2098fb2bb2
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"liveServer.settings.port": 5501
|
||||
}
|
||||
BIN
public/assets/Wpp.png
Normal file
BIN
public/assets/Wpp.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
@ -6,7 +6,7 @@
|
||||
<title>Validação Viabilidade</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<link rel="shortcut icon" href="https://sothis.com.br/wp-content/uploads/2024/11/cropped-Icon-Sothis-2020-01.png" type="image/x-icon">
|
||||
<link rel="shortcut icon" href="https://sothis.com.br/wp-content/uploads/2024/11/cropped-Icon-Sothis-2020-01.png" type="image/x-icon">
|
||||
</head>
|
||||
<body class="p-4">
|
||||
<div class="header__container">
|
||||
@ -20,35 +20,84 @@
|
||||
|
||||
<div class="container">
|
||||
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<h5>Upload de CSV</h5>
|
||||
<form id="uploadForm">
|
||||
<div class="mb-3">
|
||||
<input class="form-control" type="file" id="csvfile" accept=".csv" required />
|
||||
</div>
|
||||
<button class="btn btn-primary" type="submit">Enviar CSV</button>
|
||||
</form>
|
||||
<div id="uploadResult" class="mt-3"></div>
|
||||
<div class="progress mt-2" style="height:24px; display:none;" id="progressWrap">
|
||||
<div id="progressBar" class="progress-bar" role="progressbar" style="width:0%">0%</div>
|
||||
|
||||
</div>
|
||||
<div class="description__container">
|
||||
<div class="description-title">
|
||||
<h4>Consulta por CEP</h4>
|
||||
</div>
|
||||
<div class="description-content">
|
||||
<p>
|
||||
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Harum reprehenderit modi, rerum minima vitae earum delectus repudiandae expedita, architecto voluptatum atque cumque autem repellendus. Maiores aut adipisci repellendus facilis repellat!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5>Consulta Manual por CEP</h5>
|
||||
<div class="row g-2">
|
||||
<div class="col-md-4"><input id="cep" class="form-control" placeholder="CEP (somente números)"></div>
|
||||
<div class="col-md-4"><input id="numero" class="form-control" placeholder="Número"></div>
|
||||
|
||||
<div class="row g-2 card-body-input__container">
|
||||
<div class="col-md-4">
|
||||
<label>Digite o CEP:</label>
|
||||
<input id="cep" class="form-control" placeholder="CEP (somente números)">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label>Digite o número:</label>
|
||||
<input id="numero" class="form-control" placeholder="Número">
|
||||
</div>
|
||||
<div class="col-md-4"><button id="btnConsultaCep" class="btn btn-secondary">Consultar CEP</button></div>
|
||||
</div>
|
||||
<div id="consultaResult" class="mt-3"></div>
|
||||
<div id="consultaResultAddress" class="mt-3"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-results__container" id="consultaResultViabilidade">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<div class="card-content-label">
|
||||
<span>Para link não dedicado (Banda Larga):</span>
|
||||
</div>
|
||||
<div class="card-content-result">
|
||||
<span id="link-nao-dedicado">
|
||||
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<div class="card-content-label">
|
||||
<span>Para link dedicado:</span>
|
||||
</div>
|
||||
<div class="card-content-result">
|
||||
<span id="link-dedicado">
|
||||
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="call__container">
|
||||
<div class="call-text">
|
||||
<p>
|
||||
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Similique, odio iste. Beatae soluta a quas unde iste quia voluptatum, recusandae illo aperiam, est quibusdam? Officia deserunt temporibus at impedit repellat!
|
||||
</p>
|
||||
</div>
|
||||
<a href="https://wa.me/5508000201337" target="_blank">
|
||||
<div class="call-button__container">
|
||||
<div class="call-button-image">
|
||||
<img src="assets/Wpp.png" width="30" alt="Ícone Whatsapp">
|
||||
</div>
|
||||
<div class="call-button-phone">
|
||||
<span>
|
||||
0800 020 1337
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script src="main.js"></script>
|
||||
|
||||
@ -1,69 +1,37 @@
|
||||
document.getElementById('uploadForm').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const fileEl = document.getElementById('csvfile');
|
||||
if (!fileEl.files.length) return;
|
||||
const fd = new FormData();
|
||||
fd.append('csvfile', fileEl.files[0]);
|
||||
const resEl = document.getElementById('uploadResult');
|
||||
resEl.innerText = 'Enviando...';
|
||||
try {
|
||||
const resp = await fetch('/upload', { method: 'POST', body: fd });
|
||||
const data = await resp.json();
|
||||
if (data.jobId) {
|
||||
// show progress bar
|
||||
document.getElementById('progressWrap').style.display = 'block';
|
||||
pollJob(data.jobId);
|
||||
resEl.innerText = `Consultando viabilidade...`;
|
||||
} else if (data.error) {
|
||||
resEl.innerText = 'Erro: ' + data.error;
|
||||
}
|
||||
} catch (e) {
|
||||
resEl.innerText = 'Erro no upload';
|
||||
}
|
||||
});
|
||||
|
||||
async function pollJob(jobId) {
|
||||
const resEl = document.getElementById('uploadResult');
|
||||
const bar = document.getElementById('progressBar');
|
||||
try {
|
||||
const resp = await fetch(`/status/${jobId}`);
|
||||
const j = await resp.json();
|
||||
if (j.total && j.total > 0) {
|
||||
const pct = Math.round((j.processed / j.total) * 100);
|
||||
bar.style.width = pct + '%';
|
||||
bar.innerText = `${pct}%`;
|
||||
}
|
||||
if (j.status === 'done') {
|
||||
bar.style.width = '100%';
|
||||
bar.innerText = '100%';
|
||||
resEl.innerHTML = `Concluído. <a href="${j.download}">Baixar CSV processado</a>`;
|
||||
return;
|
||||
}
|
||||
if (j.status === 'error') {
|
||||
resEl.innerText = 'Erro no processamento: ' + j.error;
|
||||
return;
|
||||
}
|
||||
// ainda processando
|
||||
setTimeout(() => pollJob(jobId), 1000);
|
||||
} catch (e) {
|
||||
resEl.innerText = 'Erro ao consultar status do job';
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('btnConsultaCep').addEventListener('click', async () => {
|
||||
const cep = document.getElementById('cep').value;
|
||||
const numero = document.getElementById('numero').value;
|
||||
const el = document.getElementById('consultaResult');
|
||||
el.innerText = 'Consultando...';
|
||||
const endereco = document.getElementById('consultaResultAddress');
|
||||
const resultados = document.getElementById('consultaResultViabilidade');
|
||||
const dedicado = document.getElementById('link-dedicado');
|
||||
const naoDedicado = document.getElementById('link-nao-dedicado');
|
||||
endereco.innerText = 'Consultando...';
|
||||
try {
|
||||
const resp = await fetch(`/consulta-cep?cep=${encodeURIComponent(cep)}&numero=${encodeURIComponent(numero)}`);
|
||||
const data = await resp.json();
|
||||
if (data.distancia) {
|
||||
el.innerText = `Endereço: ${data.endereco}\nLat: ${data.latitude} Lon: ${data.longitude}\nDistância: ${data.distancia}`;
|
||||
// colocar o card-results__container (resultados) com display block
|
||||
endereco.innerText = `Endereço: ${data.endereco}.`;
|
||||
resultados.style.display = 'block';
|
||||
|
||||
// insere nos spans link-dedicado e link-nao-dedicado os textos de viabilidade e se for viavel adicionar classe "viavel" e se for inviavel adicionar classe "inviavel"
|
||||
dedicado.innerText = data.dedicado;
|
||||
naoDedicado.innerText = data.naoDedicado;
|
||||
if (data.dedicado === 'Viável') {
|
||||
dedicado.classList.add('viavel');
|
||||
} else if (data.dedicado === 'Não viável') {
|
||||
dedicado.classList.add('inviavel');
|
||||
}
|
||||
|
||||
if (data.naoDedicado === 'Viável') {
|
||||
naoDedicado.classList.add('viavel');
|
||||
} else if (data.naoDedicado === 'Não viável') {
|
||||
naoDedicado.classList.add('inviavel');
|
||||
}
|
||||
} else if (data.error) {
|
||||
el.innerText = 'Erro: ' + data.error;
|
||||
endereco.innerText = 'Erro: ' + data.error;
|
||||
}
|
||||
} catch (e) {
|
||||
el.innerText = 'Erro na consulta';
|
||||
endereco.innerText = 'Erro na consulta';
|
||||
}
|
||||
});
|
||||
|
||||
135
public/style.css
135
public/style.css
@ -21,6 +21,71 @@
|
||||
padding: 0px !important;
|
||||
}
|
||||
|
||||
.description__container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.description-title {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.description-content > p {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.card-results__container{
|
||||
margin-top: 50px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.card-results__container .card{
|
||||
margin-top: 5px;
|
||||
min-height: 80px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.card-content-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-right: 1px solid gray;
|
||||
padding: 1rem;
|
||||
width: 60%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.card-content-result {
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 40%;
|
||||
height: 100%;
|
||||
|
||||
}
|
||||
|
||||
.viavel {
|
||||
font-weight: bold;
|
||||
color: #234164;
|
||||
}
|
||||
|
||||
.inviavel {
|
||||
font-weight: bold;
|
||||
color: #d40000;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: #31a3b5 !important;
|
||||
border: none !important;
|
||||
@ -38,6 +103,43 @@ button:hover {
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.call__container {
|
||||
margin-top: 50px;
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.call-text > p {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.call-button__container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0.5rem 1rem;
|
||||
background-color: #25D366;
|
||||
border-radius: 8px;
|
||||
box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.call-button-phone {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.call-button-phone > span {
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.call__container {
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.header__container {
|
||||
@ -53,3 +155,36 @@ button:hover {
|
||||
width: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
body > div.container > div.card > div > div.row.g-2.card-body-input__container > div:nth-child(3){
|
||||
display: flex;
|
||||
align-items: flex-end ;
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
.description__container {
|
||||
align-items: flex-start;
|
||||
padding-left: 0px;
|
||||
}
|
||||
|
||||
.description-content {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.description-content > p {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.call-text {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.card-results__container .card {
|
||||
min-height: fit-content;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
height: fit-content;
|
||||
}
|
||||
}
|
||||
256
server.js
256
server.js
@ -153,252 +153,6 @@ async function getMinDistance(lat, lon) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// upload CSV endpoint
|
||||
const jobs = {}; // jobId -> { status, total, processed, download, error }
|
||||
|
||||
app.post('/upload', upload.single('csvfile'), (req, res) => {
|
||||
if (!req.file) return res.status(400).json({ error: 'Nenhum arquivo enviado' });
|
||||
const filePath = req.file.path;
|
||||
const jobId = `${Date.now()}-${Math.random().toString(36).slice(2,8)}`;
|
||||
jobs[jobId] = { status: 'queued', total: 0, processed: 0, download: null, error: null };
|
||||
|
||||
(async () => {
|
||||
jobs[jobId].status = 'processing';
|
||||
try {
|
||||
const rows = [];
|
||||
await new Promise((resolve, reject) => {
|
||||
fs.createReadStream(filePath)
|
||||
.pipe(csv({ separator: ';' }))
|
||||
.on('data', (data) => rows.push(data))
|
||||
.on('end', resolve)
|
||||
.on('error', reject);
|
||||
});
|
||||
|
||||
jobs[jobId].total = rows.length;
|
||||
|
||||
const coordCache = new Map();
|
||||
|
||||
const outRows = [];
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
const row = rows[i];
|
||||
// normalize keys to avoid duplicates caused by different headers
|
||||
const norm = {};
|
||||
Object.keys(row).forEach(k => {
|
||||
// normalize header: lowercase, remove diacritics and non-alphanumeric
|
||||
const kn = k.trim().toLowerCase().normalize('NFKD').replace(/[\u0300-\u036f]/g, '').replace(/[^a-z0-9]/g, '');
|
||||
norm[kn] = row[k];
|
||||
});
|
||||
|
||||
// Input columns (from normalized map)
|
||||
const rawCep = norm['cep'] ? String(norm['cep']).replace(/\D/g, '') : '';
|
||||
const rawNumero = norm['numero'] ? String(norm['numero']).trim() : '';
|
||||
// prefer lat/lon from normalized input if available
|
||||
const rawLat = norm['latitude'] || norm['lat'] || null;
|
||||
const rawLon = norm['longitude'] || norm['lon'] || norm['long'] || null;
|
||||
|
||||
// Prefer existing lat/lon if provided from normalized fields
|
||||
let lat = null, lon = null;
|
||||
if (rawLat && rawLon) {
|
||||
lat = Number(String(rawLat).replace(',', '.'));
|
||||
lon = Number(String(rawLon).replace(',', '.'));
|
||||
}
|
||||
|
||||
let builtAddress = '';
|
||||
|
||||
// If no coords, try ViaCEP -> Google
|
||||
if (!Number.isFinite(lat) || !Number.isFinite(lon)) {
|
||||
if (rawCep) {
|
||||
const cep8 = rawCep.padStart(8, '0');
|
||||
const viaCepData = await fetchJson(`https://viacep.com.br/ws/${cep8}/json/`);
|
||||
if (viaCepData && !viaCepData.erro) {
|
||||
const logradouro = viaCepData.logradouro || '';
|
||||
const bairro = viaCepData.bairro || '';
|
||||
const cidade = viaCepData.localidade || '';
|
||||
const uf = viaCepData.uf || '';
|
||||
if (logradouro) {
|
||||
builtAddress = `${logradouro}, ${rawNumero}, ${bairro}, ${cidade} - ${uf}`.replace(/, ,/g, ',').replace(/^,\s*/, '');
|
||||
} else {
|
||||
// fallback: use neighborhood/city
|
||||
builtAddress = `${bairro || ''} ${cidade ? ', ' + cidade : ''} ${uf ? '- ' + uf : ''}`.trim();
|
||||
}
|
||||
|
||||
// build addressToUse (builtAddress already assembled above)
|
||||
if (!process.env.GOOGLE_API_KEY) {
|
||||
console.error('[ERROR] GOOGLE_API_KEY não definida. Não será possível geocodificar. Defina a chave no .env ou em process.env');
|
||||
} else {
|
||||
const addressToUse = builtAddress || `${cidade} ${uf} ${cep8}`;
|
||||
const geo = await geocodeWithGoogle(addressToUse);
|
||||
if (geo) { lat = geo.lat; lon = geo.lon; }
|
||||
else console.warn(`Google Geocoding não retornou resultado para '${addressToUse}' (CEP ${cep8}, row ${i+1})`);
|
||||
}
|
||||
} else {
|
||||
console.warn(`ViaCEP erro for CEP ${rawCep} (row ${i+1})`);
|
||||
}
|
||||
} else {
|
||||
console.log(`Row ${i+1}: missing/invalid CEP -> '${rawCep}'`);
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare explicit output row to avoid extra columns
|
||||
const out = {
|
||||
'CEP': rawCep || '',
|
||||
'Número': rawNumero || '',
|
||||
'Endereço': builtAddress || '',
|
||||
// write lat/lon as strings with dot decimal and fixed precision to avoid locale swaps
|
||||
'Latitude': Number.isFinite(lat) ? Number(lat).toFixed(6) : '',
|
||||
'Longitude': Number.isFinite(lon) ? Number(lon).toFixed(6) : '',
|
||||
'Não dedicado': '',
|
||||
'Dedicado': '',
|
||||
'Distancia': '',
|
||||
'Parceiro/Sothis': ''
|
||||
};
|
||||
|
||||
if (Number.isFinite(lat) && Number.isFinite(lon)) {
|
||||
|
||||
const coordKey = `${lat.toFixed(6)},${lon.toFixed(6)}`;
|
||||
if (coordCache.has(coordKey)) {
|
||||
const cached = coordCache.get(coordKey); // cached is either null or { dist, pastaSigla }
|
||||
if (cached !== null) {
|
||||
const d = cached.dist;
|
||||
const di = Math.round(Number(d));
|
||||
out['Não dedicado'] = di <= 500 ? 'viável' : 'Não viável';
|
||||
out['Dedicado'] = di <= 1000 ? 'viável' : 'Não viável';
|
||||
out['Distancia'] = `${di}M`;
|
||||
out['Parceiro/Sothis'] = normalizePartnerSigla(cached.pastaSigla) || '';
|
||||
} else {
|
||||
out['Não dedicado'] = 'Não viável';
|
||||
out['Dedicado'] = 'Não viável';
|
||||
out['Distancia'] = '5km +';
|
||||
out['Parceiro/Sothis'] = '';
|
||||
}
|
||||
} else {
|
||||
const minResult = await getMinDistance(lat, lon); // { dist, pastaSigla } or null
|
||||
coordCache.set(coordKey, minResult);
|
||||
if (minResult !== null) {
|
||||
const di = Math.round(Number(minResult.dist));
|
||||
out['Não dedicado'] = di <= 500 ? 'viável' : 'Não viável';
|
||||
out['Dedicado'] = di <= 1000 ? 'viável' : 'Não viável';
|
||||
out['Distancia'] = `${di}M`;
|
||||
out['Parceiro/Sothis'] = normalizePartnerSigla(minResult.pastaSigla) || '';
|
||||
} else {
|
||||
out['Não dedicado'] = 'Não viável';
|
||||
out['Dedicado'] = 'Não viável';
|
||||
out['Distancia'] = '5km +';
|
||||
out['Parceiro/Sothis'] = '';
|
||||
}
|
||||
await sleep(REQUEST_DELAY_MS);
|
||||
}
|
||||
} else {
|
||||
// no coords available -> keep defaults
|
||||
}
|
||||
|
||||
outRows.push(out);
|
||||
jobs[jobId].processed = i + 1;
|
||||
}
|
||||
|
||||
// write output csv - use explicit outRows and fixed header order
|
||||
const outPath = path.join(__dirname, 'outputs');
|
||||
if (!fs.existsSync(outPath)) fs.mkdirSync(outPath);
|
||||
const originalName = (req.file && req.file.originalname) ? req.file.originalname : `upload_${Date.now()}.csv`;
|
||||
const parsed = path.parse(originalName);
|
||||
let outBase = `${parsed.name}_output`;
|
||||
let outFile = path.join(outPath, `${outBase}.csv`);
|
||||
if (fs.existsSync(outFile)) {
|
||||
outFile = path.join(outPath, `${outBase}_${Date.now()}.csv`);
|
||||
}
|
||||
const headers = ['CEP','Número','Endereço','Latitude','Longitude','Não dedicado','Dedicado','Distancia','Parceiro/Sothis'];
|
||||
await new Promise((resolve, reject) => {
|
||||
const ws = fs.createWriteStream(outFile);
|
||||
ws.write('\uFEFF');
|
||||
fastCsv.write(outRows, { headers: headers, delimiter: ';' }).pipe(ws).on('finish', resolve).on('error', reject);
|
||||
});
|
||||
|
||||
try { fs.unlinkSync(filePath); } catch (e) {}
|
||||
|
||||
jobs[jobId].status = 'done';
|
||||
jobs[jobId].download = `/download/${path.basename(outFile)}`;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
jobs[jobId].status = 'error';
|
||||
jobs[jobId].error = String(err.message || err);
|
||||
}
|
||||
})();
|
||||
|
||||
return res.json({ jobId });
|
||||
});
|
||||
|
||||
// download endpoint
|
||||
app.get('/download/:name', (req, res) => {
|
||||
const name = req.params.name;
|
||||
const p = path.join(__dirname, 'outputs', name);
|
||||
if (!fs.existsSync(p)) return res.status(404).send('Arquivo não encontrado');
|
||||
res.download(p);
|
||||
});
|
||||
|
||||
// job status endpoint
|
||||
app.get('/status/:jobId', (req, res) => {
|
||||
const job = jobs[req.params.jobId];
|
||||
if (!job) return res.status(404).json({ error: 'job não encontrado' });
|
||||
return res.json(job);
|
||||
});
|
||||
|
||||
// manual query endpoint
|
||||
// /consulta now accepts either latitude+longitude OR cep+numero. If cep is provided we resolve ViaCEP -> Google -> Geogrid
|
||||
app.get('/consulta', async (req, res) => {
|
||||
const { latitude: rawLat, longitude: rawLon, cep: rawCep, numero: rawNumero } = req.query;
|
||||
|
||||
// If cep provided, use ViaCEP -> Google geocoding -> Geogrid
|
||||
if (rawCep) {
|
||||
const cep = String(rawCep).replace(/\D/g, '');
|
||||
const numero = rawNumero ? String(rawNumero).trim() : '';
|
||||
try {
|
||||
const viaCepData = await fetchJson(`https://viacep.com.br/ws/${cep}/json/`);
|
||||
if (!viaCepData || viaCepData.erro) return res.status(404).json({ error: 'CEP não encontrado' });
|
||||
const logradouro = viaCepData.logradouro || '';
|
||||
const bairro = viaCepData.bairro || '';
|
||||
const cidade = viaCepData.localidade || '';
|
||||
const uf = viaCepData.uf || '';
|
||||
const endereco = `${logradouro}, ${numero}, ${bairro}, ${cidade} - ${uf}`.replace(/, ,/g, ',').replace(/^,\s*/, '');
|
||||
|
||||
if (!process.env.GOOGLE_API_KEY) return res.status(500).json({ error: 'GOOGLE_API_KEY não definida no servidor' });
|
||||
const geo = await geocodeWithGoogle(endereco || `${cidade} ${uf} ${cep}`);
|
||||
if (!geo) return res.status(404).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) {
|
||||
return res.json({ endereco, latitude: lat, longitude: lon, distancia: result.dist, parceiro: result.pastaSigla || '' });
|
||||
}
|
||||
return res.json({ endereco, latitude: lat, longitude: lon, distancia: '5km +' });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return res.status(500).json({ error: 'Erro na consulta' });
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise require latitude+longitude
|
||||
if (!rawLat || !rawLon) return res.status(400).json({ error: 'latitude e longitude são obrigatórios (ou forneça cep)' });
|
||||
const latitude = Number(String(rawLat).replace(',', '.'));
|
||||
const longitude = Number(String(rawLon).replace(',', '.'));
|
||||
if (!Number.isFinite(latitude) || !Number.isFinite(longitude)) {
|
||||
console.warn(`Consulta manual com parâmetros inválidos: lat='${rawLat}' lon='${rawLon}'`);
|
||||
return res.status(400).json({ error: 'latitude ou longitude inválidos' });
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(`Consulta manual: lat=${latitude} lon=${longitude}`);
|
||||
const result = await getMinDistance(latitude, longitude);
|
||||
console.log(`Resultado consulta manual: ${JSON.stringify(result)}`);
|
||||
if (result && result.dist !== undefined) {
|
||||
return res.json({ distancia: result.dist, parceiro: result.pastaSigla || '' });
|
||||
}
|
||||
return res.json({ distancia: '5km +' });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return res.status(500).json({ error: 'Erro na consulta' });
|
||||
}
|
||||
});
|
||||
|
||||
// manual CEP+Numero query: resolves ViaCEP -> Nominatim -> Geogrid
|
||||
app.get('/consulta-cep', async (req, res) => {
|
||||
const { cep: rawCep, numero: rawNumero } = req.query;
|
||||
@ -422,9 +176,15 @@ app.get('/consulta-cep', async (req, res) => {
|
||||
const lon = Number(geo.lon);
|
||||
const result = await getMinDistance(lat, lon);
|
||||
if (result && result.dist !== undefined) {
|
||||
return res.json({ endereco, latitude: lat, longitude: lon, distancia: result.dist, parceiro: result.pastaSigla || '' });
|
||||
// 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({ endereco, latitude: lat, longitude: lon, distancia: result.dist, dedicado: 'Viável', naoDedicado: 'Viável' });
|
||||
} else if (result.dist <= 1000) {
|
||||
return res.json({ endereco, latitude: lat, longitude: lon, distancia: result.dist, dedicado: 'Viável', naoDedicado: 'Não viável' });
|
||||
} else {
|
||||
return res.json({ endereco, latitude: lat, longitude: lon, distancia: result.dist, dedicado: 'Não viável', naoDedicado: 'Não viável' });
|
||||
}
|
||||
}
|
||||
return res.json({ endereco, latitude: lat, longitude: lon, distancia: '5km +' });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return res.status(500).json({ error: 'erro na consulta' });
|
||||
|
||||
Loading…
Reference in New Issue
Block a user