REFACTOR: Alterada a tratativa de erro de busca de CEP e alterei o modo de busca de cep para a biblioteca cep-promise retirando as outras duas formas de busca pelas APIs.

- Ganho de fallback (a biblioteca em várias fontes diferentes).
- Ganho de performance com menos código.

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
Gabriel Amancio 2026-05-05 17:17:59 -03:00
parent 28b988d2d1
commit b75345bb8c
5 changed files with 126 additions and 169 deletions

154
package-lock.json generated
View File

@ -10,6 +10,7 @@
"license": "ISC",
"dependencies": {
"axios": "^1.6.0",
"cep-promise": "^4.4.1",
"cors": "^2.8.6",
"dotenv": "^17.2.3",
"express": "^5.1.0",
@ -107,14 +108,14 @@
"license": "MIT"
},
"node_modules/axios": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
"integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
"version": "1.16.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.16.0.tgz",
"integrity": "sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.4",
"proxy-from-env": "^1.1.0"
"follow-redirects": "^1.16.0",
"form-data": "^4.0.5",
"proxy-from-env": "^2.1.0"
}
},
"node_modules/balanced-match": {
@ -138,29 +139,33 @@
}
},
"node_modules/body-parser": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
"integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==",
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz",
"integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==",
"license": "MIT",
"dependencies": {
"bytes": "^3.1.2",
"content-type": "^1.0.5",
"debug": "^4.4.0",
"debug": "^4.4.3",
"http-errors": "^2.0.0",
"iconv-lite": "^0.6.3",
"iconv-lite": "^0.7.0",
"on-finished": "^2.4.1",
"qs": "^6.14.0",
"raw-body": "^3.0.0",
"type-is": "^2.0.0"
"qs": "^6.14.1",
"raw-body": "^3.0.1",
"type-is": "^2.0.1"
},
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"version": "1.1.14",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
"integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -219,6 +224,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/cep-promise": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/cep-promise/-/cep-promise-4.4.1.tgz",
"integrity": "sha512-YC4vXorKvGurfx0H008Y5lEo9+9ScqeM25dOTbhXx1sRA3knTHWbvtO7yyqZcH+CWqzP5rCxO8cZP3qVZV++ww==",
"license": "MIT",
"dependencies": {
"node-fetch": "2.6.7",
"unfetch": "4.1.0"
}
},
"node_modules/chokidar": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
@ -634,9 +649,9 @@
"license": "MIT"
},
"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==",
"version": "1.16.0",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
"integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==",
"funding": [
{
"type": "individual",
@ -654,9 +669,9 @@
}
},
"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==",
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
@ -869,15 +884,19 @@
}
},
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
"integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/ignore-by-default": {
@ -1048,9 +1067,9 @@
}
},
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"dev": true,
"license": "ISC",
"dependencies": {
@ -1093,6 +1112,26 @@
"node": ">=6.0.0"
}
},
"node_modules/node-fetch": {
"version": "2.6.7",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
"license": "MIT",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/nodemon": {
"version": "3.1.11",
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.11.tgz",
@ -1212,9 +1251,9 @@
}
},
"node_modules/path-to-regexp": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
"integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==",
"version": "8.4.2",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz",
"integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==",
"license": "MIT",
"funding": {
"type": "opencollective",
@ -1311,9 +1350,9 @@
}
},
"node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
"dev": true,
"license": "MIT",
"engines": {
@ -1376,10 +1415,13 @@
}
},
"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"
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
"integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==",
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/pstree.remy": {
"version": "1.1.8",
@ -1389,9 +1431,9 @@
"license": "MIT"
},
"node_modules/qs": {
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
"integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
"version": "6.15.1",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz",
"integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==",
"license": "BSD-3-Clause",
"dependencies": {
"side-channel": "^1.1.0"
@ -1772,6 +1814,12 @@
"nodetouch": "bin/nodetouch.js"
}
},
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"license": "MIT"
},
"node_modules/triple-beam": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz",
@ -1802,6 +1850,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/unfetch": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.1.0.tgz",
"integrity": "sha512-crP/n3eAPUJxZXM9T80/yv0YhkTEx2K1D3h7D1AJM6fzsWZrxdyRuLN0JH/dkZh1LNH8LxCnBzoPFCPbb2iGpg==",
"license": "MIT"
},
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
@ -1826,6 +1880,22 @@
"node": ">= 0.8"
}
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
"license": "BSD-2-Clause"
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"license": "MIT",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",

View File

@ -19,6 +19,7 @@
"description": "A robust and scalable Node.js project starter based on Clean Architecture principles.",
"dependencies": {
"axios": "^1.6.0",
"cep-promise": "^4.4.1",
"cors": "^2.8.6",
"dotenv": "^17.2.3",
"express": "^5.1.0",

View File

@ -1,10 +1,8 @@
const geogridService = require("../../shared/apis/geogridService.js");
const googleService = require("../../shared/apis/googleService.js");
const cepRestService = require("../../shared/apis/cepRestService.js");
const hubsoftService = require("../../shared/apis/hubsoftService.js");
const logger = require('../../shared/utils/logger.js');
const repository = require('./contratacao.repository.js');
const viaCepService = require("../../shared/apis/viaCepService.js");
const { ViabilidadeModel, ClientModelPf, ClientModelPj, ProspectModel, ViabilidadeReverseGeocodeModel } = require('./contratacao.model');
// utilitário para ler chaves do payload tolerante a ":" e espaços
@ -47,35 +45,28 @@ async function verificarViabilidade(rawViabilidadeData) {
throw new ServiceError('CEP e número são obrigatórios.', 400);
}
// Obtém o endereço completo usando a API de CEP (viaCEP e cepRestService como fallback)
try {
address = await viaCepService.getConsultaCep(rawCep);
if (!address || !address.logradouro) {
throw new Error('Resposta inválida do viaCEP');
}
logger.info('Endereço obtido com sucesso via viaCEP', { address });
const cep = require('cep-promise');
address = await cep(rawCep);
logger.info('Endereço obtido com sucesso via cep-promise', { address });
} catch (err) {
address = await cepRestService.getConsultaCep(rawCep);
if (!address || !address.logradouro) {
throw new Error('Resposta inválida do cepRestService');
}
logger.info('Endereço obtido com sucesso via cepRestService', { address });
}
if (!address) {
throw new ServiceError('Não foi possível obter endereço para o CEP informado.', 502);
logger.error('Erro ao obter endereço com cep-promise', { message: err.message, stack: err.stack, rawCep });
throw new ServiceError('Não foi possível obter endereço para o CEP informado ou o CEP é inválido.', 502);
}
// Obtém as coordenadas geográficas do endereço usando o Google Geocoding API
const { logradouro, bairro, localidade: city, uf: state, cep } = address;
const { street, neighborhood, city, state, cep } = address;
addressString = `${logradouro}, ${rawNumero}, ${bairro}, ${city}, ${state}, ${cep}`;
addressString = `${street}, ${rawNumero}, ${neighborhood}, ${city}, ${state}, ${rawCep}`;
logger.info('Endereço montado para geocodificação', { addressString });
if (!street || !neighborhood || !city || !state) {
logger.error('Dados de endereço incompletos para geocodificação', { address });
throw new ServiceError('Dados de endereço indefinidos:' + addressString, 422);
}
const coords = await googleService.geocodeWithGoogle(addressString);
if (!coords) {
@ -108,7 +99,7 @@ async function verificarViabilidade(rawViabilidadeData) {
const viabilidadeResult = new ViabilidadeModel(rawViabilidadeData.nome, rawViabilidadeData.email, rawViabilidadeData.telefone, logradouro, rawNumero, bairro, city, state, cep, naoDedicado, dedicado, distancia || null);
const viabilidadeResult = new ViabilidadeModel(rawViabilidadeData.nome, rawViabilidadeData.email, rawViabilidadeData.telefone, street, rawNumero, neighborhood, city, state, cep, naoDedicado, dedicado, distancia || null);
if (source) {
return viabilidadeResult;

View File

@ -1,75 +0,0 @@
const axios = require("axios");
const logger = require('../utils/logger.js');
// Consulta o endereço completo usando a API externa de CEP
const getConsultaCep = async (rawCep) => {
logger.info('Iniciando consulta de CEP');
if (!rawCep) {
logger.warn('CEP não fornecido para getConsultaCep');
throw new Error("cep é obrigatório");
}
const cep = String(rawCep).trim().replace(/\D/g, "");
if (cep.length !== 8) {
logger.warn('CEP inválido fornecido para getConsultaCep', { cepBruto: rawCep, cepLimpo: cep });
throw new Error("cep inválido, verifique se foram digitados apenas números");
}
try {
const cepRestUrl = 'https://api.cep.rest/';
const address = await axios.post(cepRestUrl, { cep }, { timeout: 5000 });
console.log(address.data);
// Tratamento de resposta conforme diferentes estruturas possíveis
if (address.data && address.data.code === 404) {
logger.warn('CEP não encontrado na API externa', { cep, response: address.data });
return null; // Controller tratará como 'Endereço não encontrado'
} else if (address.data && address.data.code) {
logger.error('API de CEP retornou um código de erro', { cep, response: address.data });
throw new Error("Erro ao consultar o CEP na API externa");
} else {
// Estrutura 1: { "cep:09662000": { bairro, ... } }
const cepKey = `cep:${cep}`;
if (address.data.data && address.data.data[cepKey]) {
logger.info('CEP encontrado com chave aninhada', { cepKey });
return address.data.data[cepKey];
}
// Estrutura 2: { bairro, cep, ... } (direto em data)
else if (address.data.data && address.data.data.cep) {
return address.data.data;
}
else {
logger.warn('Estrutura de resposta não reconhecida', { response: address.data });
return address.data.data || address.data;
}
}
} catch (error) {
logger.error("Erro na chamada da API de CEP", { message: error.message, stack: error.stack, cep });
throw new Error("Erro ao consultar o CEP");
}
};
module.exports = { getConsultaCep };
/*
DESCRIÇÃO:
Este arquivo define um serviço para consultar endereços a partir de um CEP.
FLUXO:
1. É chamado principalmente pelo `contratacao.service.js`.
2. Recebe um CEP e um número.
3. Formata e valida o CEP.
4. Realiza uma requisição POST para a API externa `https://api.cep.rest/`.
5. Trata a resposta da API:
- Se o CEP não for encontrado (código 404), retorna `null`.
- Se houver outro erro na API, lança uma exceção.
- Se for bem-sucedido, verifica qual estrutura a API retornou:
* Estrutura aninhada: `{ data: { "cep:09662000": { ... } } }` extrai a chave dinâmica
* Estrutura direta: `{ data: { bairro, cep, ... } }` retorna data direto
6. Em caso de erro na comunicação, loga o erro e lança uma exceção genérica.
Este serviço abstrai a complexidade da comunicação com a API de CEP, fornecendo uma interface simples para outras partes da aplicação obterem dados de endereço.
*/

View File

@ -1,30 +0,0 @@
const axios = require("axios");
const logger = require('../utils/logger.js');
const getConsultaCep = async (rawCep) => {
logger.info('Iniciando consulta de CEP');
if (!rawCep) {
logger.warn('CEP não fornecido para getConsultaCep');
throw new Error("cep é obrigatório");
}
const cep = String(rawCep).trim().replace(/\D/g, "");
if (cep.length !== 8) {
logger.warn('CEP inválido fornecido para getConsultaCep', { cepBruto: rawCep, cepLimpo: cep });
throw new Error("cep inválido, verifique se foram digitados apenas números");
}
try {
const viaCepUrl = `https://viacep.com.br/ws/${cep}/json/`;
const response = await axios.get(viaCepUrl, { timeout: 5000 });
if (response.data.erro) {
logger.warn('CEP não encontrado na API ViaCEP', { cep });
return null; // Controller tratará como 'Endereço não encontrado'
} else {
return response.data;
}
} catch (error) {
logger.error("Erro na chamada da API ViaCEP", { message: error.message, stack: error.stack, cep });
throw new Error("Erro ao consultar o CEP");
}
};
module.exports = { getConsultaCep };