fix TopSapp

This commit is contained in:
rodrigo committed 2025-08-10 13:54:46 -03:00
1 parent 8dc6c594c9
commit bae6adefcb
42 files changed
+43645 -105

No files matched your search

+308 -11
View File
@@ -195,31 +195,163 @@ class TopSappAPI {
// Obter sessão válida para pegar o idUsuario
const session = await this.getValidSession(instance, credentials);
// Tentar buscar por diferentes critérios
console.log(`🔍 Buscando cliente: "${query}" na instância: ${instance.name}`);
const searchParams = {
idUsuario: session.idUsuario || 1 // Usar ID do usuário da sessão
idUsuario: session.idUsuario || 1
};
// Se é número, pode ser CPF ou ID
if (/^\d+$/.test(query)) {
if (query.length === 11 || query.length === 14) {
// Determinar tipo de busca
const queryLimpo = query.trim();
if (/^\d+$/.test(queryLimpo)) {
// Apenas números - pode ser CPF ou ID
if (queryLimpo.length === 11 || queryLimpo.length === 14) {
// CPF (11 dígitos) ou CNPJ (14 dígitos)
searchParams.cpfCnpj = query;
console.log(`📋 Buscando por CPF/CNPJ: ${queryLimpo}`);
searchParams.cpfCnpj = queryLimpo;
} else {
// ID do cliente
searchParams.idCliente = query;
console.log(`🆔 Buscando por ID: ${queryLimpo}`);
searchParams.idCliente = parseInt(queryLimpo, 10);
}
} else {
// CPF formatado ou nome - usar o endpoint de busca geral
searchParams.cpfCnpj = query.replace(/[^\d]/g, ''); // Remove formatação
// Contém letras - é nome ou CPF formatado
const apenasNumeros = queryLimpo.replace(/[^\d]/g, '');
if (apenasNumeros.length === 11 || apenasNumeros.length === 14) {
// CPF/CNPJ formatado
console.log(`📋 Buscando por CPF/CNPJ formatado: ${queryLimpo} -> ${apenasNumeros}`);
searchParams.cpfCnpj = apenasNumeros;
} else {
// Nome do cliente
console.log(`👤 Buscando por nome: ${queryLimpo}`);
searchParams.nome = queryLimpo;
// Fazer busca sem filtros específicos para pegar todos os clientes
// e depois filtrar localmente por nome
delete searchParams.nome; // Remove o parâmetro nome para buscar todos
}
}
const result = await this.makeRequest('ObterClientes', searchParams, instance, credentials);
let result;
// Se é busca por nome, fazer busca geral e filtrar localmente
if (!searchParams.cpfCnpj && !searchParams.idCliente) {
console.log(`🔄 Fazendo busca geral para filtrar por nome: "${queryLimpo}"`);
// Buscar todos os clientes
result = await this.makeRequest('ObterClientes', searchParams, instance, credentials);
if (result.success && result.data.dados) {
const todosClientes = result.data.dados;
console.log(`📊 Total de clientes retornados: ${todosClientes.length}`);
// Filtrar por nome localmente com diferentes estratégias
const nomeQuery = queryLimpo.toLowerCase()
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, ''); // Remove acentos
const clientesFiltrados = todosClientes.filter(cliente => {
const nomeCliente = (cliente.nome || '').toLowerCase()
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, ''); // Remove acentos
// Estratégias de busca:
// 1. Nome completo exato
if (nomeCliente === nomeQuery) return true;
// 2. Nome contém a busca
if (nomeCliente.includes(nomeQuery)) return true;
// 3. Busca contém o nome (para nomes parciais)
if (nomeQuery.includes(nomeCliente) && nomeCliente.length > 3) return true;
// 4. Busca por palavras individuais
const palavrasBusca = nomeQuery.split(/\s+/).filter(p => p.length > 2);
const palavrasNome = nomeCliente.split(/\s+/);
// Verificar se todas as palavras da busca estão no nome
const todasPalavrasEncontradas = palavrasBusca.every(palavraBusca =>
palavrasNome.some(palavraNome =>
palavraNome.includes(palavraBusca) || palavraBusca.includes(palavraNome)
)
);
if (todasPalavrasEncontradas && palavrasBusca.length > 0) return true;
// 5. Busca fuzzy - verificar similaridade
if (this.calcularSimilaridade(nomeCliente, nomeQuery) > 0.8) return true;
return false;
});
console.log(`🎯 Clientes filtrados por nome: ${clientesFiltrados.length}`);
// Ordenar por relevância (nomes mais similares primeiro)
clientesFiltrados.sort((a, b) => {
const nomeA = (a.nome || '').toLowerCase();
const nomeB = (b.nome || '').toLowerCase();
const simA = this.calcularSimilaridade(nomeA, nomeQuery);
const simB = this.calcularSimilaridade(nomeB, nomeQuery);
return simB - simA; // Ordem decrescente de similaridade
});
// Log dos resultados para debug
if (clientesFiltrados.length > 0) {
console.log(`✅ Clientes encontrados:`);
clientesFiltrados.slice(0, 5).forEach((cliente, index) => {
console.log(` ${index + 1}. ${cliente.nome} (ID: ${cliente.id})`);
});
} else {
console.log(`❌ Nenhum cliente encontrado para: "${queryLimpo}"`);
// Log de amostra para debug
console.log(`🔍 Amostra de nomes disponíveis:`);
todosClientes.slice(0, 5).forEach((cliente, index) => {
console.log(` ${index + 1}. ${cliente.nome}`);
});
}
return {
success: true,
clientes: clientesFiltrados.map(cliente => ({
...cliente,
cpf: cliente.cpfCnpj || cliente.cpf || cliente.documento || '',
status: this.interpretarStatusCliente(cliente),
statusOriginal: cliente.status,
id_status: cliente.id_status,
bloqueado: cliente.bloqueado,
ativo: cliente.ativo
})),
instance: result.instance,
debug: {
totalOriginal: todosClientes.length,
totalFiltrado: clientesFiltrados.length,
termoBusca: queryLimpo
}
};
}
} else {
// Busca específica (CPF/ID)
result = await this.makeRequest('ObterClientes', searchParams, instance, credentials);
}
if (result.success) {
// Normalizar dados dos clientes para incluir CPF e status correto
const clientesNormalizados = (result.data.dados || []).map(cliente => ({
...cliente,
cpf: cliente.cpfCnpj || cliente.cpf || cliente.documento || '',
status: this.interpretarStatusCliente(cliente),
statusOriginal: cliente.status,
id_status: cliente.id_status,
bloqueado: cliente.bloqueado,
ativo: cliente.ativo
}));
return {
success: true,
clientes: result.data.dados || [], // Usar 'dados' em vez de 'clientes'
clientes: clientesNormalizados,
instance: result.instance
};
}
@@ -227,6 +359,7 @@ class TopSappAPI {
return result;
} catch (error) {
console.error(`❌ Erro na busca de cliente:`, error);
return {
success: false,
error: error.message,
@@ -235,6 +368,93 @@ class TopSappAPI {
}
}
/**
* Obter dados detalhados do cliente incluindo dados cadastrais completos
* Usa apenas os dados dos serviços que já contêm informações completas do cliente
*/
async obterDadosCompletosCliente(idCliente, location = '', credentials) {
const instance = this.getInstanceByLocation(location);
try {
console.log(`🔍 Obtendo dados completos do cliente ${idCliente} via serviços (evitando erro 400)`);
// Usar apenas dados dos serviços que já contêm informações completas
const servicosData = await this.obterServicosCliente(idCliente, location, credentials);
if (!servicosData.success) {
console.log(`❌ Falha ao obter serviços: ${servicosData.error}`);
return servicosData;
}
const servicos = servicosData.data.dados || servicosData.data.servicos || [];
if (servicos.length === 0) {
return {
success: false,
error: 'Cliente não encontrado ou sem serviços'
};
}
// Usar dados do primeiro serviço para extrair informações do cliente
const primeiroServico = servicos[0];
console.log(`✅ Extraindo dados do cliente dos serviços: ${primeiroServico.instalacao_nome}`);
// Montar dados do cliente baseado nas informações dos serviços
const cliente = {
id: primeiroServico.id_cliente,
nome: primeiroServico.instalacao_nome || 'Nome não informado',
cpf: primeiroServico.cpf || '',
rg: primeiroServico.rg || '',
email: primeiroServico.usuario || '',
telefone: primeiroServico.instalacao_contato1 || primeiroServico.instalacao_contato || '',
celular: primeiroServico.instalacao_contato1 || '',
endereco: primeiroServico.instalacao_endereco || '',
numero: primeiroServico.instalacao_numero || '',
bairro: primeiroServico.instalacao_bairro || '',
cidade: primeiroServico.instalacao_cidade || '',
uf: primeiroServico.instalacao_uf || '',
cep: primeiroServico.instalacao_cep || '',
complemento: primeiroServico.instalacao_complemento || '',
data_cadastro: primeiroServico.data_cadastro || '',
observacoes: primeiroServico.observacoes || '',
// Determinar status baseado nos serviços
status: this.determinarStatusClientePorServicos(servicos),
// Dados de localização
latitude: primeiroServico.latitude || '',
longitude: primeiroServico.longitude || ''
};
console.log(`✅ Dados do cliente extraídos com sucesso`);
return {
success: true,
cliente: cliente,
instance: instance.name,
fonte: 'servicos'
};
} catch (error) {
console.error(`❌ Erro ao obter dados completos: ${error.message}`);
return {
success: false,
error: error.message,
instance: instance.name
};
}
}
/**
* Determinar status do cliente baseado nos serviços
*/
determinarStatusClientePorServicos(servicos) {
// Se tem pelo menos um serviço ativo, considera cliente ativo
const temServicoAtivo = servicos.some(servico =>
servico.id_status === 1 || servico.status === 'liberado'
);
return temServicoAtivo ? 1 : 0;
}
/**
* Obter dados detalhados do cliente
*/
@@ -452,6 +672,83 @@ class TopSappAPI {
}
}
}
/**
* Interpretar status do cliente corretamente
*/
interpretarStatusCliente(cliente) {
// Verificar diferentes campos de status que podem indicar se o cliente está ativo
// Se tem campo 'ativo' explícito
if (typeof cliente.ativo !== 'undefined') {
return cliente.ativo === 1 || cliente.ativo === true ? 1 : 0;
}
// Se tem campo 'bloqueado'
if (typeof cliente.bloqueado !== 'undefined') {
return cliente.bloqueado === 0 || cliente.bloqueado === false ? 1 : 0;
}
// Verificar por id_status (alguns sistemas usam diferentes códigos)
if (cliente.id_status) {
// Status 1 = Ativo, 2 = Bloqueado, 3 = Desativado, etc.
return cliente.id_status === 1 ? 1 : 0;
}
// Se tem data de desativação, está inativo
if (cliente.data_desativacao && cliente.data_desativacao !== '0000-00-00' && cliente.data_desativacao !== null) {
return 0;
}
// Se tem data de bloqueio sem data de desbloqueio, está bloqueado
if (cliente.data_bloqueio && cliente.data_bloqueio !== '0000-00-00' && cliente.data_bloqueio !== null) {
if (!cliente.data_desbloqueio || cliente.data_desbloqueio === '0000-00-00' || cliente.data_desbloqueio === null) {
return 0;
}
}
// Por padrão, usar o status original ou assumir ativo se não há informação de bloqueio/desativação
return cliente.status || 1;
}
/**
* Calcular similaridade entre duas strings (algoritmo de Jaro-Winkler simplificado)
*/
calcularSimilaridade(str1, str2) {
if (str1 === str2) return 1.0;
if (str1.length === 0 || str2.length === 0) return 0.0;
// Normalizar strings
const s1 = str1.toLowerCase().trim();
const s2 = str2.toLowerCase().trim();
if (s1 === s2) return 1.0;
// Calcular distância de Levenshtein simplificada
const maxLen = Math.max(s1.length, s2.length);
let matches = 0;
for (let i = 0; i < Math.min(s1.length, s2.length); i++) {
if (s1[i] === s2[i]) matches++;
}
// Adicionar pontos por palavras em comum
const words1 = s1.split(/\s+/);
const words2 = s2.split(/\s+/);
let wordMatches = 0;
words1.forEach(word1 => {
if (words2.some(word2 => word1.includes(word2) || word2.includes(word1))) {
wordMatches++;
}
});
const charSim = matches / maxLen;
const wordSim = wordMatches / Math.max(words1.length, words2.length);
// Média ponderada: 60% caracteres, 40% palavras
return (charSim * 0.6) + (wordSim * 0.4);
}
}
module.exports = TopSappAPI;