2025-11-24 10:59:46 -03:00
|
|
|
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');
|
2025-12-02 17:29:20 -03:00
|
|
|
const {ViabilidadeModel, ClientModelPf, ClientModelPj, ProspectModel} = require('./contratacao.model');
|
|
|
|
|
|
|
|
|
|
// utilitário para ler chaves do payload tolerante a ":" e espaços
|
|
|
|
|
const getPayloadValue = (obj, key) => {
|
|
|
|
|
if (!obj) return undefined;
|
|
|
|
|
// variações diretas
|
|
|
|
|
const variants = [key, `${key}:`, `${key}: `];
|
|
|
|
|
for (const v of variants) if (Object.prototype.hasOwnProperty.call(obj, v)) return obj[v];
|
|
|
|
|
// tentativa de correspondência normalizada (ignora ":" e case)
|
|
|
|
|
const nk = String(key).trim().toLowerCase();
|
|
|
|
|
for (const k of Object.keys(obj)) {
|
|
|
|
|
const kn = String(k).trim().replace(/:$/, '').toLowerCase();
|
|
|
|
|
if (kn === nk) return obj[k];
|
|
|
|
|
}
|
|
|
|
|
return undefined;
|
|
|
|
|
};
|
2025-11-24 16:13:35 -03:00
|
|
|
|
|
|
|
|
// mapping de planos para IDs e valores no Hubsoft
|
2025-11-24 10:59:46 -03:00
|
|
|
|
|
|
|
|
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 });
|
|
|
|
|
|
2025-11-24 10:59:46 -03:00
|
|
|
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.');
|
2025-11-24 10:59:46 -03:00
|
|
|
throw new ServiceError('CEP e número são obrigatórios.', 400);
|
|
|
|
|
}
|
2025-12-02 17:29:20 -03:00
|
|
|
|
2025-11-24 10:59:46 -03:00
|
|
|
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
|
2025-12-02 17:29:20 -03:00
|
|
|
if (!address) {
|
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 });
|
2025-11-24 10:59:46 -03:00
|
|
|
throw new ServiceError('Endereço não encontrado para o CEP fornecido.', 404);
|
|
|
|
|
}
|
2025-12-02 17:29:20 -03:00
|
|
|
logger.info('Endereço obtido com sucesso via CEP', { data: address });
|
2025-11-24 10:59:46 -03:00
|
|
|
|
2025-12-02 17:29:20 -03:00
|
|
|
const { logradouro, bairro, localidade: city, uf: state, cep } = address; // FIX: Usar address.data para desestruturar
|
2025-11-24 10:59:46 -03:00
|
|
|
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 });
|
2025-11-24 10:59:46 -03:00
|
|
|
|
|
|
|
|
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 });
|
2025-11-24 10:59:46 -03:00
|
|
|
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 });
|
2025-11-24 10:59:46 -03:00
|
|
|
|
|
|
|
|
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 });
|
2025-11-24 10:59:46 -03:00
|
|
|
|
|
|
|
|
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`);
|
2025-11-24 10:59:46 -03:00
|
|
|
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 10:59:46 -03:00
|
|
|
}
|
|
|
|
|
|
2025-12-02 17:29:20 -03:00
|
|
|
const viabilidadeResult = new ViabilidadeModel(cep, rawNumero, bairro, city, state, logradouro, naoDedicado, dedicado);
|
|
|
|
|
|
|
|
|
|
logger.info('Finalizando verificação de viabilidade com sucesso', { viabilidadeResult });
|
|
|
|
|
return viabilidadeResult;
|
2025-11-24 10:59:46 -03:00
|
|
|
}
|
|
|
|
|
|
2025-11-24 16:13:35 -03:00
|
|
|
async function criarProspecto(rawProspectData) {
|
2025-11-24 10:59:46 -03:00
|
|
|
try {
|
2025-11-24 16:13:35 -03:00
|
|
|
const payload = (rawProspectData && rawProspectData.prospectData) ? rawProspectData.prospectData : rawProspectData;
|
|
|
|
|
|
2025-12-02 17:29:20 -03:00
|
|
|
const planos = {
|
|
|
|
|
'100 Mega + Super WiFi': { id_servico: '22'},
|
|
|
|
|
'200 Mega + Super WiFi': { id_servico: '94'},
|
|
|
|
|
'500 Mega + Super WiFi': { id_servico: '96'},
|
|
|
|
|
'700 Mega + Super WiFi': { id_servico: '97'},
|
|
|
|
|
};
|
2025-11-24 16:13:35 -03:00
|
|
|
|
2025-12-02 17:29:20 -03:00
|
|
|
const planoKey = getPayloadValue(payload, 'Descrição');
|
|
|
|
|
const servico = planos[planoKey];
|
|
|
|
|
const celular = Number(String(getPayloadValue(payload, 'Celular')).replace(/\D/g, ''));
|
2025-11-24 16:13:35 -03:00
|
|
|
|
2025-12-02 17:29:20 -03:00
|
|
|
if (!servico) {
|
|
|
|
|
logger.warn('Plano não encontrado no mapeamento', { planoKey, disponíveis: Object.keys(planos) });
|
|
|
|
|
throw new ServiceError('Plano inválido ou não encontrado.', 400);
|
2025-11-24 16:13:35 -03:00
|
|
|
}
|
|
|
|
|
|
2025-12-02 17:29:20 -03:00
|
|
|
// parse do valor mensal de forma robusta (aceita "R$ 119,90", "119,90", etc)
|
|
|
|
|
const rawValor = getPayloadValue(payload, 'Valor (mensal)') || getPayloadValue(payload, 'Valor (mensal):') || getPayloadValue(payload, 'Valor');
|
|
|
|
|
if (rawValor) {
|
|
|
|
|
const sanitized = String(rawValor).replace(/[^\d,.-]/g, '').replace(/\.(?=\d{3})/g, '').replace(',', '.');
|
|
|
|
|
const valorNum = Number(sanitized);
|
|
|
|
|
if (!Number.isNaN(valorNum)) servico.valor = valorNum;
|
2025-11-24 16:13:35 -03:00
|
|
|
}
|
2025-12-02 17:29:20 -03:00
|
|
|
servico.id_servico = Number(servico.id_servico);
|
|
|
|
|
console.log(servico);
|
|
|
|
|
if (!servico) {
|
|
|
|
|
logger.warn('Plano não encontrado no mapeamento', { planoKey, disponíveis: Object.keys(planos) });
|
|
|
|
|
throw new ServiceError('Plano inválido ou não encontrado.', 400);
|
2025-11-24 16:13:35 -03:00
|
|
|
}
|
|
|
|
|
|
2025-12-02 17:29:20 -03:00
|
|
|
if (payload["CPF:"] === "") {
|
|
|
|
|
// Pessoa Jurídica
|
|
|
|
|
const clientData = new ClientModelPj(
|
|
|
|
|
payload["Razão Social:"],
|
|
|
|
|
payload["E-mail:"],
|
|
|
|
|
payload["CEP:"],
|
|
|
|
|
payload["Número:"],
|
|
|
|
|
payload["Logradouro:"],
|
|
|
|
|
payload["Bairro:"],
|
|
|
|
|
payload["Cidade:"],
|
|
|
|
|
payload["Estado:"],
|
|
|
|
|
servico,
|
|
|
|
|
payload["Telefone:"],
|
|
|
|
|
"pj",
|
|
|
|
|
payload["Endereço de cobrança"],
|
|
|
|
|
payload["CNPJ:"],
|
|
|
|
|
payload["Selecione o dia de vencimento:"],
|
|
|
|
|
payload["Escolha a forma de pagamento:"],
|
|
|
|
|
payload["Banco:"],
|
|
|
|
|
`${payload["Agência:"]}-${payload["Dígito (agência):"]}`,
|
|
|
|
|
payload["Conta:"],
|
|
|
|
|
payload["E-mail de cobrança:"],
|
|
|
|
|
);
|
|
|
|
|
logger.info('Criando prospecto PJ no Hubsoft', { clientData });
|
|
|
|
|
const resultado = await hubsoftService.criarProspectoHubsoft(clientData);
|
|
|
|
|
logger.info('Prospecto PJ criado com sucesso no Hubsoft', { resultado });
|
|
|
|
|
return resultado;
|
|
|
|
|
} else {
|
|
|
|
|
// Pessoa Física
|
|
|
|
|
const clientData = new ClientModelPf(
|
|
|
|
|
payload["Nome completo:"],
|
|
|
|
|
payload["E-mail:"],
|
|
|
|
|
payload["CEP:"],
|
|
|
|
|
payload["Número:"],
|
|
|
|
|
payload["Logradouro:"],
|
|
|
|
|
payload["Bairro:"],
|
|
|
|
|
payload["Cidade:"],
|
|
|
|
|
payload["Estado:"],
|
|
|
|
|
servico,
|
|
|
|
|
payload["Plano:"],
|
|
|
|
|
payload["CPF:"],
|
|
|
|
|
celular,
|
|
|
|
|
"pf",
|
|
|
|
|
payload["Endereço de cobrança"],
|
|
|
|
|
payload["Nome da mãe:"],
|
|
|
|
|
payload["Data de nascimento:"],
|
|
|
|
|
payload["Selecione o dia de vencimento:"],
|
|
|
|
|
payload["Escolha a forma de pagamento:"],
|
|
|
|
|
payload["Banco:"],
|
|
|
|
|
`${payload["Agência:"]}-${payload["Dígito (agência):"]}`,
|
|
|
|
|
payload["Conta:"],
|
|
|
|
|
payload["E-mail de cobrança:"],
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const prospectData = new ProspectModel(
|
|
|
|
|
getPayloadValue(payload, 'CEP'),
|
|
|
|
|
servico, // passa o objeto { id_servico, valor }
|
|
|
|
|
getPayloadValue(payload, 'Nome completo'),
|
|
|
|
|
getPayloadValue(payload, 'CPF'),
|
|
|
|
|
celular,
|
|
|
|
|
getPayloadValue(payload, 'E-mail'),
|
|
|
|
|
"pf",
|
|
|
|
|
getPayloadValue(payload, 'Bairro'),
|
|
|
|
|
getPayloadValue(payload, 'Logradouro'),
|
|
|
|
|
getPayloadValue(payload, 'Número'),
|
|
|
|
|
getPayloadValue(payload, 'Nome da mãe') || ""
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// serializa para JSON plano conforme toJSON()
|
|
|
|
|
const prospectPayload = (typeof prospectData.toJSON === 'function')
|
|
|
|
|
? prospectData.toJSON()
|
|
|
|
|
: JSON.parse(JSON.stringify(prospectData));
|
|
|
|
|
|
|
|
|
|
// envia objeto plano ao hubsoft (o hubsoftService já monta { prospectData })
|
|
|
|
|
await hubsoftService.criarProspectHubsoft(prospectPayload);
|
|
|
|
|
|
|
|
|
|
logger.info('Criando prospecto PF', { prospectPayload });
|
|
|
|
|
return prospectPayload;
|
2025-11-24 16:13:35 -03:00
|
|
|
}
|
2025-12-02 17:29:20 -03:00
|
|
|
} catch (error) {
|
|
|
|
|
logger.error('Erro ao criar prospecto', { message: error.message, stack: error.stack });
|
|
|
|
|
throw new ServiceError('Erro ao criar prospecto.', 500);
|
2025-11-24 10:59:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-02 17:29:20 -03:00
|
|
|
|
2025-11-24 10:59:46 -03:00
|
|
|
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.
|
|
|
|
|
*/
|