sothis-contratacao-api/src/modules/contratacao/contratacao.service.js

204 lines
10 KiB
JavaScript
Raw Normal View History

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");
2025-11-24 16:13:35 -03:00
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) {
super(message);
this.name = 'ServiceError';
this.statusCode = statusCode;
}
}
async function verificarViabilidade(rawCep, rawNumero) {
2025-11-24 16:13:35 -03:00
logger.info('Iniciando verificação de viabilidade', { cep: rawCep, numero: rawNumero });
if (!rawCep || !rawNumero) {
2025-11-24 16:13:35 -03:00
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);
2025-11-24 16:13:35 -03:00
// FIX: Revertendo para a verificação correta da resposta do cepRestService
if (!address || !address.success || !address.data) {
2025-11-24 16:13:35 -03:00
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);
}
2025-11-24 16:13:35 -03:00
logger.info('Endereço obtido com sucesso via CEP', { data: address.data });
2025-11-24 16:13:35 -03:00
const { logradouro, bairro, localidade: city, uf: state, cep } = address.data; // FIX: Usar address.data para desestruturar
const addressString = `${logradouro}, ${rawNumero}, ${bairro}, ${city}, ${state}, ${cep}`;
2025-11-24 16:13:35 -03:00
logger.info('String de endereço montada para geocodificação', { addressString });
const coords = await googleService.geocodeWithGoogle(addressString);
if (!coords) {
2025-11-24 16:13:35 -03:00
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);
}
2025-11-24 16:13:35 -03:00
logger.info('Coordenadas obtidas com sucesso', { coords });
const viabilidade = await geogridService.consultaViabilidade(coords.lat, coords.lon);
2025-11-24 16:13:35 -03:00
logger.info('Resultado da consulta de viabilidade no GeoGrid', { viabilidade });
let naoDedicado = false;
let dedicado = false;
if (viabilidade.data) {
const distancia = viabilidade.data.distancia;
2025-11-24 16:13:35 -03:00
logger.info(`Distância para o ponto de presença: ${distancia}m`);
if (distancia <= 500) {
naoDedicado = true;
dedicado = true;
} else if (distancia <= 1000) {
naoDedicado = true;
dedicado = false;
}
2025-11-24 16:13:35 -03:00
} else {
logger.warn('Resposta do GeoGrid não continha dados de viabilidade.', { cep: rawCep, numero: rawNumero });
}
2025-11-24 16:13:35 -03:00
const resultadoFinal = {
bairro,
cidade: city,
estado: state,
logradouro,
naoDedicado,
dedicado,
};
2025-11-24 16:13:35 -03:00
logger.info('Finalizando verificação de viabilidade com sucesso', { resultadoFinal });
return resultadoFinal;
}
2025-11-24 16:13:35 -03:00
async function criarProspecto(rawProspectData) {
try {
2025-11-24 16:13:35 -03:00
// 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);
2025-11-24 16:13:35 -03:00
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;
2025-11-24 16:13:35 -03:00
} catch (error) {
2025-11-24 16:13:35 -03:00
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);
}
}
module.exports = {
verificarViabilidade,
criarProspecto
};
/*
DESCRIÇÃO:
Este arquivo é a camada de "Serviço" para o módulo de contratação. Ele contém a lógica de negócio principal da aplicação, orquestrando chamadas a diferentes APIs externas e processando os dados para atender às solicitações do `contratacao.controller.js`.
A classe `ServiceError` é uma classe de erro personalizada para permitir que os serviços lancem exceções com `status codes` HTTP específicos, que podem ser capturados e tratados pelo controller.
FUNÇÕES:
- verificarViabilidade(rawCep, rawNumero):
1. É chamado pelo `contratacao.controller.js` com o CEP e o número do endereço.
2. Valida se o CEP e o número foram fornecidos, lançando um `ServiceError` (400) se não forem.
3. Chama o `cepRestService` para obter os dados do endereço a partir do CEP. Se não encontrar, lança um `ServiceError` (404).
4. Monta uma string de endereço completo e a utiliza para obter as coordenadas geográficas (latitude e longitude) através do `googleService`. Lança um erro (500) se não conseguir obter as coordenadas.
5. Com as coordenadas, chama o `geogridService` para consultar a viabilidade do serviço, que retorna a distância de um ponto de presença.
6. Com base na distância retornada pelo GeoGrid, define as flags `naoDedicado` e `dedicado` (ex: se a distância for menor que 500m, ambos são `true`).
7. Retorna um objeto contendo os dados do endereço e as flags de viabilidade para o controller.
- criarProspecto(prospectData):
1. Recebe os dados do prospecto do `contratacao.controller.js`.
2. Chama o `hubsoftService` para criar o prospecto no sistema Hubsoft.
3. Retorna o resultado da criação.
4. Em caso de erro na comunicação com o Hubsoft, lança um `ServiceError` (500).
Este serviço é o cérebro do módulo, conectando múltiplas fontes de dados externas para fornecer uma resposta unificada sobre a viabilidade e para registrar novos clientes.
*/