From 6ab6b209b63b9be6ad488745d59f606a8d21aa13 Mon Sep 17 00:00:00 2001 From: "gabriel.amancio" Date: Fri, 14 Nov 2025 08:16:04 -0300 Subject: [PATCH] =?UTF-8?q?WIP:=20Falta=20acertar=20a=20viabilidade=20no?= =?UTF-8?q?=20Geogrid,=20est=C3=A1=20trazendo=20postes=20e=20n=C3=A3o=20ap?= =?UTF-8?q?enas=20caixas.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 158 ++++++++++++++++++++++++++++++++- package.json | 6 +- src/app.js | 2 + src/config/apiConfig.js | 10 ++- src/controllers/controller.js | 75 +++++++++++++++- src/routes/routes.js | 1 + src/services/cepRestService.js | 21 +++-- src/services/geogridService.js | 42 +++++++++ src/services/googleService.js | 34 +++---- 9 files changed, 318 insertions(+), 31 deletions(-) diff --git a/package-lock.json b/package-lock.json index 772d027..c8b26d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,10 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "express": "^5.1.0" + "axios": "^1.6.0", + "cors": "^2.8.5", + "express": "^5.1.0", + "qs": "^6.11.0" } }, "node_modules/accepts": { @@ -25,6 +28,23 @@ "node": ">= 0.6" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/body-parser": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", @@ -83,6 +103,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/content-disposition": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", @@ -122,6 +154,19 @@ "node": ">=6.6.0" } }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -139,6 +184,15 @@ } } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -207,6 +261,21 @@ "node": ">= 0.4" } }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -281,6 +350,63 @@ "node": ">= 0.8" } }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/form-data/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -369,6 +495,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -505,6 +646,15 @@ "node": ">= 0.6" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", @@ -570,6 +720,12 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/qs": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", diff --git a/package.json b/package.json index 3f3743f..5c1f68d 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,10 @@ "license": "ISC", "description": "", "dependencies": { - "express": "^5.1.0" + "cors": "^2.8.5", + "express": "^5.1.0", + "qs": "^6.11.0", + "axios": "^1.6.0" } } + diff --git a/src/app.js b/src/app.js index 38fb4e1..985364d 100644 --- a/src/app.js +++ b/src/app.js @@ -1,9 +1,11 @@ const express = require('express'); const routes = require('./routes/routes.js'); +const cors = require('cors'); const app = express(); app.use(express.json()); +app.use(cors()); app.use('/api', routes); module.exports = app; \ No newline at end of file diff --git a/src/config/apiConfig.js b/src/config/apiConfig.js index 3a834cb..1c7015f 100644 --- a/src/config/apiConfig.js +++ b/src/config/apiConfig.js @@ -1,8 +1,14 @@ const dotenv = require("dotenv"); dotenv.config(); -const key = process.env.GOOGLE_API_KEY; +const googleApiKey = process.env.GOOGLE_API_KEY; +const geogridApiUrl = process.env.GEOGRID_API_URL; +const geogridApiKey = process.env.GEOGRID_API_KEY; +const geogridApiCookie = process.env.GEOGRID_API_COOKIE; module.exports = { - googleApiKey: key, + googleApiKey: googleApiKey, + geogridApiUrl: geogridApiUrl, + geogridApiKey: geogridApiKey, + geogridApiCookie: geogridApiCookie, }; \ No newline at end of file diff --git a/src/controllers/controller.js b/src/controllers/controller.js index 455329a..efe3686 100644 --- a/src/controllers/controller.js +++ b/src/controllers/controller.js @@ -1,14 +1,83 @@ -exports.handleViabilidadeNegada = (req, res) => { +const geogridService = require("../services/geogridService.js"); +const googleService = require("../services/googleService.js"); +const cepRestService = require("../services/cepRestService.js"); +function handleViabilidadeNegada(req, res) { // Lógica para lidar com a viabilidade negada res.status(200).json({ message: 'Viabilidade negada processada com sucesso.' }); } -exports.handleViabilidadeAceita = (req, res) => { - +function handleViabilidadeAceita(req, res) { console.log('Recebido viabilidade aceita:', req.body); // Lógica para lidar com a viabilidade aceita res.status(200).json({ message: 'Viabilidade aceita' }); } +async function handleViabilidade(req, res) { + const rawCep = req.body.cep; + const rawNumero = req.body.numero; + + try { + if (!rawCep && !rawNumero) return res.status(400).json({ error: 'cep e número são obrigatórios' }); + if (!rawCep) return res.status(400).json({ error: 'cep é obrigatório' }); + if (!rawNumero) return res.status(400).json({ error: 'número é obrigatório' }); + + const address = await cepRestService.getConsultaCep(rawCep, rawNumero); + + if (!address) return res.status(404).json({ error: 'Endereço não encontrado para o CEP fornecido' }); + + const city = address.data.localidade; + const state = address.data.uf; + const numero = address.data.numero || rawNumero; + + const addressString = `${address.data.logradouro}, ${numero}, ${address.data.bairro}, ${city}, ${state}, ${address.data.cep}`; + + const coords = await googleService.geocodeWithGoogle(addressString); + if (!coords) return res.status(500).json({ error: 'Não foi possível obter as coordenadas do endereço' }); + + const viabilidade = await geogridService.consultaViabilidade(coords.lat, coords.lon); + + console.log('Viabilidade retornada:', viabilidade); + + const distancia = viabilidade.data.distancia; + + if (distancia == null) return res.status(500).json({ error: 'Distância não retornada pela consulta de viabilidade' }); + + let naoDedicado; + let dedicado; + + // lógica de decisão: use if/else (ajuste os limites conforme regra de negócio) + if (distancia <= 500) { + naoDedicado = true; + dedicado = true; + } else if (distancia <= 1000) { + naoDedicado = true; + dedicado = false; + } else { + naoDedicado = false; + dedicado = false; + } + + return res.json({ + bairro: address.data.bairro, + cidade: city, + estado: state, + logradouro: address.data.logradouro, + naoDedicado, + dedicado, + }); + + } catch (error) { + console.error("Erro ao processar a viabilidade:", error); + return res.status(500).json({ error: "Erro ao processar a viabilidade" }); + } +} + +module.exports = { + handleViabilidadeNegada, + handleViabilidadeAceita, + handleViabilidade, +}; + + \ No newline at end of file diff --git a/src/routes/routes.js b/src/routes/routes.js index 1f2c60d..fd1fca3 100644 --- a/src/routes/routes.js +++ b/src/routes/routes.js @@ -5,5 +5,6 @@ const controller = require('../controllers/controller.js'); //rota de teste router.post('/viabilidade_negada', controller.handleViabilidadeNegada); router.post('/viabilidade_aceita', controller.handleViabilidadeAceita); +router.post('/viabilidade', controller.handleViabilidade); module.exports = router; \ No newline at end of file diff --git a/src/services/cepRestService.js b/src/services/cepRestService.js index 14dd4c3..1eb77e9 100644 --- a/src/services/cepRestService.js +++ b/src/services/cepRestService.js @@ -1,26 +1,29 @@ const axios = require("axios"); -const getConsultaCep = async (req, res) => { - const { cep: rawCep, numero: rawNumero } = req.query; - if (!rawCep) return res.status(400).json({ error: "cep é obrigatório" }); +const getConsultaCep = async (rawCep, rawNumero) => { + if (!rawCep) { + throw new Error("cep é obrigatório"); + } const cep = String(rawCep).trim().replace(/\D/g, ""); - if (cep.length !== 8) return res.status(400).json({ error: "cep inválido, verifique se foram digitados apenas números" }); + if (cep.length !== 8) { + throw new Error("cep inválido, verifique se foram digitados apenas números"); + } const numero = rawNumero ? String(rawNumero).trim() : ""; try { const cepRestUrl = 'https://api.cep.rest/'; const address = await axios.post(cepRestUrl, { cep }); - + if (address.data && address.data.code === 404) { - return res.status(404).json({ error: "CEP não encontrado" }); + return null; // Controller tratará como 'Endereço não encontrado' } else if (address.data && address.data.code) { - return res.status(500).json({ error: "Erro ao consultar o CEP" }); + throw new Error("Erro ao consultar o CEP na API externa"); } else { if (numero) address.data.numero = numero; - return res.json(address.data); + return address.data; } } catch (error) { console.error("Erro ao consultar o CEP:", error); - return res.status(500).json({ error: "Erro ao consultar o CEP" }); + throw new Error("Erro ao consultar o CEP"); } }; diff --git a/src/services/geogridService.js b/src/services/geogridService.js index a9bda10..71e0f66 100644 --- a/src/services/geogridService.js +++ b/src/services/geogridService.js @@ -1,2 +1,44 @@ const apiConfig = require("../config/apiConfig.js") const axios = require("axios"); +const qs = require("qs"); + + +const consultaViabilidade = async (lat, lon) => { + + const url = apiConfig.geogridApiUrl; + const apiKey = apiConfig.geogridApiKey; + const apiCookie = apiConfig.geogridApiCookie; + + try { + // Monta os parâmetros como arrays para garantir serialização correta + const params = { + raio: 5000, + latitude: lat, + longitude: lon, + itens: ["caixa"], + ordenarCampos: ["distancia"], + ordenarPor: ["asc"] + }; + + const response = await axios.get(url, { + params, + // força a serialização do tipo `itens[]=caixa` + paramsSerializer: p => qs.stringify(p, { arrayFormat: 'brackets' }), + headers: { + 'api-key': apiKey, + Cookie: apiCookie + } + }); + + const registros = response.data && response.data.registros; + const primeiro = Array.isArray(registros) && registros.length ? registros[0] : null; + + // Retorna no formato esperado pelo controller (viabilidade.data.distancia) + return { data: primeiro }; + } catch (error) { + console.error("Erro ao consultar viabilidade:", error && error.message ? error.message : error); + throw new Error("Erro ao consultar viabilidade"); + } +}; + +module.exports = { consultaViabilidade }; diff --git a/src/services/googleService.js b/src/services/googleService.js index ce8bd18..871758e 100644 --- a/src/services/googleService.js +++ b/src/services/googleService.js @@ -7,25 +7,29 @@ async function geocodeWithGoogle(address) { const key = apiConfig.googleApiKey; if (!key) return null; try { - const url = `https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent( - address - )}&key=${encodeURIComponent(key)}`; - const r = await axios.get(url, { timeout: 10000 }); - if ( - r && - r.data && - Array.isArray(r.data.results) && - r.data.results.length > 0 - ) { - const loc = - r.data.results[0].geometry && r.data.results[0].geometry.location; - if (loc && loc.lat !== undefined && loc.lng !== undefined) { - return { lat: Number(loc.lat), lon: Number(loc.lng) }; + const url = 'https://maps.googleapis.com/maps/api/geocode/json'; + const r = await axios.get(url, { + params: { address, key }, + timeout: 10000, + }); + + // ensure HTTP 200 + if (!r || r.status !== 200) { + console.warn(`geocodeWithGoogle unexpected status for '${address}': ${r && r.status}`); + return null; + } + + if (r.data && Array.isArray(r.data.results) && r.data.results.length > 0) { + const loc = r.data.results[0].geometry && r.data.results[0].geometry.location; + const lat = loc && (loc.lat !== undefined ? Number(loc.lat) : NaN); + const lng = loc && (loc.lng !== undefined ? Number(loc.lng) : NaN); + if (!Number.isNaN(lat) && !Number.isNaN(lng)) { + return { lat, lon: lng }; } } return null; } catch (e) { - console.warn(`geocodeWithGoogle error for '${address}': ${e.message}`); + console.warn(`geocodeWithGoogle error for '${address}': ${e && e.message}`, e && e.stack); return null; } }