From d1448404e88ef401d10f4f160228a8d9b7fd8393 Mon Sep 17 00:00:00 2001 From: Rafael Lopes Date: Mon, 24 Nov 2025 16:13:35 -0300 Subject: [PATCH] =?UTF-8?q?PERF:=20Adi=C3=A7=C3=A3o=20de=20Logs=20estrutur?= =?UTF-8?q?ados?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app.js | 19 +++ .../contratacao/contratacao.controller.js | 9 +- .../contratacao/contratacao.service.js | 116 +++++++++++++++++- src/shared/apis/cepRestService.js | 11 +- src/shared/apis/geogridService.js | 10 +- src/shared/apis/googleService.js | 18 ++- src/shared/apis/hubsoftService.js | 69 ++++++++--- 7 files changed, 221 insertions(+), 31 deletions(-) diff --git a/src/app.js b/src/app.js index fa9283d..e1af6e2 100644 --- a/src/app.js +++ b/src/app.js @@ -8,12 +8,31 @@ const routes = require('./routes/routes.js'); const logger = require('./shared/utils/logger.js'); const app = express(); +app.set('trust proxy', 1); // Confia no primeiro proxy reverso na frente da aplicação const PORT = process.env.PORT || 3000; app.use(cors()); app.use(express.json()); + +// Logar todas as requisições +app.use((req, res, next) => { + logger.info(`Requisição recebida: ${req.method} ${req.originalUrl} - IP: ${req.ip}`); + next(); +}); + app.use('/api', routes); +// Tratamento de erros +app.use((err, req, res, next) => { + logger.error(`Erro não tratado: ${err.message}`, { + stack: err.stack, + url: req.originalUrl, + method: req.method, + ip: req.ip + }); + res.status(500).send('Ocorreu um erro interno no servidor.'); +}); + app.listen(PORT, () => { logger.info(`🚀 Servidor API rodando na porta ${PORT} em modo ${process.env.NODE_ENV}`); }); diff --git a/src/modules/contratacao/contratacao.controller.js b/src/modules/contratacao/contratacao.controller.js index b0de245..4f53e91 100644 --- a/src/modules/contratacao/contratacao.controller.js +++ b/src/modules/contratacao/contratacao.controller.js @@ -1,14 +1,17 @@ const contratacaoService = require('./contratacao.service.js'); +const logger = require('../../shared/utils/logger.js'); async function handleViabilidade(req, res) { const { cep, numero } = req.body; + logger.info('Iniciando handleViabilidade', { cep, numero }); try { const resultadoViabilidade = await contratacaoService.verificarViabilidade(cep, numero); + logger.info('Viabilidade verificada com sucesso', { cep, numero, resultado: resultadoViabilidade }); return res.json(resultadoViabilidade); } catch (error) { - console.error("Erro no controller ao processar viabilidade:", error.message); + logger.error('Erro no controller ao processar viabilidade', { message: error.message, stack: error.stack, cep, numero }); const statusCode = error.statusCode || 500; return res.status(statusCode).json({ error: error.message || "Erro interno ao processar a viabilidade." }); } @@ -16,13 +19,15 @@ async function handleViabilidade(req, res) { async function handleCriarProspecto(req, res) { const prospectData = req.body; + logger.info('Iniciando handleCriarProspecto', { prospectData }); try { const resultado = await contratacaoService.criarProspecto(prospectData); + logger.info('Prospecto criado com sucesso', { resultado }); return res.json({ message: 'Prospecto criado com sucesso', data: resultado }); } catch (error) { - console.error("Erro no controller ao criar prospecto:", error.message); + logger.error('Erro no controller ao criar prospecto', { message: error.message, stack: error.stack }); const statusCode = error.statusCode || 500; return res.status(statusCode).json({ error: error.message || "Erro interno ao criar o prospecto." }); } diff --git a/src/modules/contratacao/contratacao.service.js b/src/modules/contratacao/contratacao.service.js index da40808..4653d3a 100644 --- a/src/modules/contratacao/contratacao.service.js +++ b/src/modules/contratacao/contratacao.service.js @@ -2,6 +2,17 @@ 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'); + +// mapping de planos para IDs e valores no Hubsoft +const PLANOS_CONFIG = { + '100-mega': { servicoId: '22', servicoValor: '149' }, + '200-mega': { servicoId: '94', servicoValor: '159.9' }, + '300-mega': { servicoId: '95', servicoValor: '239.9' }, + '500-mega': { servicoId: '96', servicoValor: '379.9' }, + '700-mega': { servicoId: '97', servicoValor: '499.9' }, + +}; class ServiceError extends Error { constructor(message, statusCode = 500) { @@ -12,30 +23,41 @@ class ServiceError extends Error { } async function verificarViabilidade(rawCep, rawNumero) { + logger.info('Iniciando verificação de viabilidade', { cep: rawCep, numero: rawNumero }); + if (!rawCep || !rawNumero) { + logger.warn('CEP ou número não fornecidos na verificação de viabilidade.'); throw new ServiceError('CEP e número são obrigatórios.', 400); } const address = await cepRestService.getConsultaCep(rawCep, rawNumero); + // FIX: Revertendo para a verificação correta da resposta do cepRestService if (!address || !address.success || !address.data) { + logger.warn('Endereço não encontrado ou resposta inválida do serviço de CEP', { cep: rawCep, response: address }); throw new ServiceError('Endereço não encontrado para o CEP fornecido.', 404); } + logger.info('Endereço obtido com sucesso via CEP', { data: address.data }); - const { logradouro, bairro, localidade: city, uf: state, cep } = address.data; + const { logradouro, bairro, localidade: city, uf: state, cep } = address.data; // FIX: Usar address.data para desestruturar const addressString = `${logradouro}, ${rawNumero}, ${bairro}, ${city}, ${state}, ${cep}`; + logger.info('String de endereço montada para geocodificação', { addressString }); const coords = await googleService.geocodeWithGoogle(addressString); if (!coords) { + logger.error('Não foi possível obter coordenadas do Google Geocoding', { addressString }); throw new ServiceError('Não foi possível obter as coordenadas do endereço.', 500); } + logger.info('Coordenadas obtidas com sucesso', { coords }); const viabilidade = await geogridService.consultaViabilidade(coords.lat, coords.lon); + logger.info('Resultado da consulta de viabilidade no GeoGrid', { viabilidade }); let naoDedicado = false; let dedicado = false; if (viabilidade.data) { const distancia = viabilidade.data.distancia; + logger.info(`Distância para o ponto de presença: ${distancia}m`); if (distancia <= 500) { naoDedicado = true; dedicado = true; @@ -43,9 +65,11 @@ async function verificarViabilidade(rawCep, rawNumero) { naoDedicado = true; dedicado = false; } + } else { + logger.warn('Resposta do GeoGrid não continha dados de viabilidade.', { cep: rawCep, numero: rawNumero }); } - return { + const resultadoFinal = { bairro, cidade: city, estado: state, @@ -53,14 +77,98 @@ async function verificarViabilidade(rawCep, rawNumero) { naoDedicado, dedicado, }; + logger.info('Finalizando verificação de viabilidade com sucesso', { resultadoFinal }); + return resultadoFinal; } -async function criarProspecto(prospectData) { +async function criarProspecto(rawProspectData) { try { - console.log("Service: Dados recebidos para criação de prospecto:", prospectData); + // Normaliza payload (alguns callers enviam { prospectData: { ... } }) + const payload = (rawProspectData && rawProspectData.prospectData) ? rawProspectData.prospectData : rawProspectData; + logger.info('Payload usado para criação de prospecto', { payload }); + + // helper: busca valor por múltiplas chaves possíveis, normalizando nomes + const normalize = s => String(s || '').toLowerCase().replace(/[^a-z0-9]/g, ''); + const find = (obj, variants) => { + if (!obj) return undefined; + const map = {}; + for (const k of Object.keys(obj)) map[normalize(k)] = obj[k]; + for (const v of variants) { + const val = map[normalize(v)]; + if (val !== undefined) return val; + } + return undefined; + }; + + // --- Camada de Transformação e Validação de Dados --- + const cep = find(payload, ['CEP:', 'CEP', 'cep']); + if (!cep) { + throw new ServiceError('O campo "CEP:" é obrigatório.', 400); + } + + const addressDetails = await cepRestService.getConsultaCep(cep); + if (!addressDetails || !addressDetails.success || !addressDetails.data) { + logger.error('Não foi possível obter detalhes do endereço (bairro) para o CEP fornecido.', { cep }); + throw new ServiceError('Endereço inválido ou não encontrado. Não foi possível determinar o bairro a partir do CEP.', 400); + } + + // localizar plano com mais variações de chave + const planoKey = find(payload, ['Escolha o plano:', 'Escolha o Plano:', 'plano', 'Plano', 'escolha o plano']); + const planoConfig = PLANOS_CONFIG[planoKey]; + if (!planoConfig) { + logger.error(`Plano desconhecido selecionado: ${planoKey}`); + throw new ServiceError(`O plano selecionado '${planoKey}' é inválido.`, 400); + } + + const tipoPlano = find(payload, ['Escolha o tipo de plano:', 'tipo de plano', 'Tipo de plano', 'tipoPlano', 'tipo']); + // tipoPlano esperado: 'plano-residencia' -> pf, else -> pj + const tipoPessoa = (tipoPlano && tipoPlano.toString().toLowerCase().includes('resid')) ? 'pf' : 'pj'; + + const prospectData = { + cep: cep, + servico: { + id_servico: Number(planoConfig.servicoId), + valor: Number(planoConfig.servicoValor) + }, + cpf_cnpj: find(payload, ['CPF/CNPJ:', 'CPF/CNPJ', 'cpf_cnpj', 'cpf']), + telefone: (find(payload, ['Telefone:', 'Telefone', 'telefone']) || '').toString().replace(/\D/g, ''), // só números + nome_razaosocial: find(payload, ['Nome Completo:', 'Nome', 'nome', 'nome_razaosocial']), + tipo_pessoa: tipoPessoa, + bairro: addressDetails.data.bairro, + endereco: addressDetails.data.logradouro || find(payload, ['Endereço:', 'Endereço', 'endereco', 'logradouro']), + numero: Number(find(payload, ['Número:', 'Numero:', 'numero', 'número'])) + }; + + // validações adicionais específicas + if (!prospectData.servico || !prospectData.servico.id_servico || !prospectData.servico.valor) { + throw new ServiceError('É necessário selecionar um plano válido com valor.', 400); + } + const requiredFields = ['cep', 'numero', 'endereco', 'bairro', 'nome_razaosocial', 'telefone', 'tipo_pessoa']; + for (const field of requiredFields) { + if (prospectData[field] === undefined || prospectData[field] === null || prospectData[field] === '') { + const formField = Object.keys(payload).find(key => key.toLowerCase().includes(field)) || field; + throw new ServiceError(`O campo obrigatório '${formField}' está ausente ou é inválido.`, 400); + } + } + // --- Fim da Camada de Transformação --- + + logger.info('Dados do prospecto mapeados. Enviando para o Hubsoft.', { prospectData }); const resultado = await hubsoftService.criarProspectHubsoft(prospectData); + + if (resultado && resultado.status === 'error') { + logger.error('Hubsoft retornou um erro de negócio ao criar prospecto', { resposta: resultado }); + throw new ServiceError(resultado.msg || 'Erro ao criar prospecto no Hubsoft', 400); + } + + logger.info("Prospecto criado com sucesso no Hubsoft", { resultado }); return resultado; + } catch (error) { + if (error instanceof ServiceError) { + logger.error(`Erro de serviço ao criar prospecto: ${error.message}`); + throw error; + } + logger.error("Erro inesperado ao criar prospecto no Hubsoft", { message: error.message, stack: error.stack }); throw new ServiceError(error.message || 'Erro ao comunicar com o serviço de prospectos.', 500); } } diff --git a/src/shared/apis/cepRestService.js b/src/shared/apis/cepRestService.js index 78e69f8..d07a703 100644 --- a/src/shared/apis/cepRestService.js +++ b/src/shared/apis/cepRestService.js @@ -1,13 +1,18 @@ 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, rawNumero) => { + logger.info('Iniciando consulta de CEP', { cep: rawCep, numero: rawNumero }); + 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"); } @@ -16,18 +21,22 @@ const getConsultaCep = async (rawCep, rawNumero) => { const numero = rawNumero ? String(rawNumero).trim() : ""; try { const cepRestUrl = 'https://api.cep.rest/'; + logger.info('Consultando API de CEP', { url: cepRestUrl, cep }); const address = await axios.post(cepRestUrl, { cep }); 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 { if (numero) address.data.numero = numero; + logger.info('Endereço obtido com sucesso da API de CEP', { response: address.data }); return address.data; } } catch (error) { - console.error("Erro ao consultar o CEP:", 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"); } }; diff --git a/src/shared/apis/geogridService.js b/src/shared/apis/geogridService.js index df9091a..7ae6adc 100644 --- a/src/shared/apis/geogridService.js +++ b/src/shared/apis/geogridService.js @@ -1,9 +1,11 @@ const apiConfig = require("../config/apiConfig.js") const axios = require("axios"); const qs = require("qs"); +const logger = require('../utils/logger.js'); const consultaViabilidade = async (lat, lon) => { + logger.info('Iniciando consulta de viabilidade no GeoGrid', { lat, lon }); const url = apiConfig.geogridApiUrl; const apiKey = apiConfig.geogridApiKey; @@ -21,9 +23,8 @@ const consultaViabilidade = async (lat, lon) => { consultarIndividual: "S" }; - // Console log com curl equivalente para depuração - const curlParams = qs.stringify(params, { arrayFormat: 'brackets' }); - console.log(`CURL equivalente: curl -X GET "${url}?${curlParams}" -H "api-key: ${apiKey}" -H "Cookie: ${apiCookie}"`); + const urlParams = qs.stringify(params, { arrayFormat: 'brackets' }); + logger.info(`Chamada à API GeoGrid: ${url}?${urlParams}`); const response = await axios.get(url, { params, @@ -38,10 +39,11 @@ const consultaViabilidade = async (lat, lon) => { const registros = response.data && response.data.registros; const primeiro = Array.isArray(registros) && registros.length ? registros[0] : null; + logger.info('Resposta recebida do GeoGrid', { data: primeiro }); // 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); + logger.error("Erro ao consultar viabilidade no GeoGrid", { message: error.message, stack: error.stack, lat, lon }); throw new Error("Erro ao consultar viabilidade"); } }; diff --git a/src/shared/apis/googleService.js b/src/shared/apis/googleService.js index 277ca89..4d93424 100644 --- a/src/shared/apis/googleService.js +++ b/src/shared/apis/googleService.js @@ -1,14 +1,20 @@ const apiConfig = require("../config/apiConfig.js") const axios = require("axios"); +const logger = require('../utils/logger.js'); // Geocodifica um endereço usando a API do Google Maps async function geocodeWithGoogle(address) { + logger.info('Iniciando geocodificação com Google', { address }); const key = apiConfig.googleApiKey; - if (!key) return null; + if (!key) { + logger.error('Chave da API do Google não configurada.'); + return null; + } try { const url = 'https://maps.googleapis.com/maps/api/geocode/json'; + logger.info('Consultando Google Geocoding API', { url, address }); const r = await axios.get(url, { params: { address, key }, timeout: 10000, @@ -16,7 +22,7 @@ async function geocodeWithGoogle(address) { // Valida a resposta e extrai as coordenadas if (!r || r.status !== 200) { - console.warn(`geocodeWithGoogle unexpected status for '${address}': ${r && r.status}`); + logger.warn(`Resposta inesperada do Google Geocoding API para o endereço '${address}'`, { status: r ? r.status : 'undefined' }); return null; } @@ -27,12 +33,16 @@ async function geocodeWithGoogle(address) { 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 }; + const coords = { lat, lon: lng }; + logger.info('Geocodificação bem-sucedida', { address, coords }); + return coords; } } + + logger.warn('Nenhum resultado de geocodificação válido encontrado para o endereço', { address, responseData: r.data }); return null; } catch (e) { - console.warn(`geocodeWithGoogle error for '${address}': ${e && e.message}`, e && e.stack); + logger.error(`Erro na chamada ao Google Geocoding API para o endereço '${address}'`, { message: e.message, stack: e.stack }); return null; } } diff --git a/src/shared/apis/hubsoftService.js b/src/shared/apis/hubsoftService.js index a6660e3..6e771dc 100644 --- a/src/shared/apis/hubsoftService.js +++ b/src/shared/apis/hubsoftService.js @@ -1,9 +1,11 @@ const apiConfig = require('../config/apiConfig'); const axios = require('axios'); const qs = require('qs'); +const logger = require('../utils/logger.js'); // Função para autenticar e obter o token de acesso do Hubsoft const hubsoftToken = async () => { + logger.info('Iniciando autenticação no Hubsoft'); try { const authBody = { client_id: apiConfig.hubsoftClientId, @@ -18,10 +20,16 @@ const hubsoftToken = async () => { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }); - return response.data && response.data.access_token; + const token = response.data && response.data.access_token; + if (token) { + logger.info('Token de acesso do Hubsoft obtido com sucesso.'); + } else { + logger.warn('A resposta de autenticação do Hubsoft não continha um token de acesso.'); + } + return token; } catch (error) { - console.error('Erro ao autenticar no Hubsoft:', error.response?.data || error.message); + logger.error('Erro ao autenticar no Hubsoft', { message: error.message, response: error.response?.data }); throw new Error('Erro ao autenticar no Hubsoft'); } } @@ -30,36 +38,65 @@ const hubsoftToken = async () => { const criarProspectHubsoft = async (prospectData) => { try { const token = await hubsoftToken(); + if (!token) { + throw new Error('Não foi possível obter o token do Hubsoft para criar o prospecto.'); + } + + // Normaliza leitura dos campos para aceitar snake_case, camelCase ou objeto servico nested + const servId = prospectData?.servico?.id_servico ?? prospectData?.servicoId ?? prospectData?.servico?.idServico ?? prospectData?.servico?.id; + const servValor = prospectData?.servico?.valor ?? prospectData?.servicoValor ?? prospectData?.servico?.valorServico ?? prospectData?.servico?.valor; + const tipoPessoa = prospectData?.tipo_pessoa ?? prospectData?.tipoPessoa; + const nomeRazao = prospectData?.nome_razaosocial ?? prospectData?.nomeRazaoSocial; + const cpfCnpj = prospectData?.cpf_cnpj ?? prospectData?.cpfCnpj; + const email = prospectData?.email ?? prospectData?.e_mail ?? null; + const numero = prospectData?.numero ?? prospectData?.numeroEndereco ?? prospectData?.numeroEnderecoString; + const endereco = prospectData?.endereco; + const bairro = prospectData?.bairro; + const telefone = prospectData?.telefone ?? null; const payload = { cep: prospectData.cep, servico: { - id_servico: prospectData.servicoId, - valor: prospectData.servicoValor + id_servico: servId !== undefined ? Number(servId) : undefined, + valor: servValor !== undefined ? Number(servValor) : undefined }, - numero: prospectData.numero, - endereco: prospectData.endereco, - bairro: prospectData.bairro, - tipo_pessoa: prospectData.tipoPessoa, - nome_razaosocial: prospectData.nomeRazaoSocial, - cpf_cnpj: prospectData.cpfCnpj, - email: prospectData.email, - telefone: prospectData.telefone || null + numero: numero !== undefined ? String(numero) : undefined, + endereco: endereco, + bairro: bairro, + tipo_pessoa: tipoPessoa, + nome_razaosocial: nomeRazao, + cpf_cnpj: cpfCnpj, + email: email, + telefone: telefone }; - // Envia o body JSON corretamente (sem "params" aninhado) - const response = await axios.post(`${apiConfig.hubsoftUrl}/api/v1/integracao/prospecto`, payload, { + // Remover propriedades undefined para evitar envio de campos vazios + Object.keys(payload).forEach(k => { + if (payload[k] === undefined) delete payload[k]; + }); + if (payload.servico) { + Object.keys(payload.servico).forEach(k => { + if (payload.servico[k] === undefined) delete payload.servico[k]; + }); + if (Object.keys(payload.servico).length === 0) delete payload.servico; + } + + const url = `${apiConfig.hubsoftUrl}/api/v1/integracao/prospecto`; + logger.info('Enviando dados para criar prospecto no Hubsoft', { url, payload }); + + + const response = await axios.post(url, payload, { headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } }); - console.log('Resposta do Hubsoft:', response.data); + logger.info('Resposta da criação de prospecto no Hubsoft', { response: response.data }); return response.data; } catch (error) { - console.error('Erro ao criar prospecto no Hubsoft:', error.response?.data || error.message); + logger.error('Erro ao criar prospecto no Hubsoft', { message: error.message, response: error.response?.data }); throw new Error('Erro ao criar prospecto no Hubsoft'); } }