From 8dc6c594c96e742e96233a9947f481f4a75ae62f Mon Sep 17 00:00:00 2001 From: "Rodrigo B. de Sousa Martins" Date: Fri, 8 Aug 2025 21:48:26 -0300 Subject: [PATCH] topsapp: estruturar suporte Signed-off-by: Rodrigo B. de Sousa Martins --- package-lock.json | 39 ++ src/bot/handler.js | 986 ++++++++++++++++++++++++++++++++++++++--- src/nlp/processor.js | 30 +- src/topsapp/api.js | 180 ++++++-- src/topsapp/config.js | 8 +- src/topsapp/manager.js | 375 +++++++++++++++- src/utils/logger.js | 173 ++++++++ 7 files changed, 1687 insertions(+), 104 deletions(-) create mode 100644 src/utils/logger.js diff --git a/package-lock.json b/package-lock.json index 3439313..9d9a872 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,9 @@ "version": "1.0.0", "license": "MIT", "dependencies": { + "axios": "^1.6.0", "dotenv": "^16.3.1", + "form-data": "^4.0.0", "node-telegram-bot-api": "^0.64.0", "ssh2": "^1.15.0" }, @@ -227,6 +229,17 @@ "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", "license": "MIT" }, + "node_modules/axios": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz", + "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -781,6 +794,26 @@ "node": ">=8" } }, + "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==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", @@ -1819,6 +1852,12 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "license": "MIT" }, + "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" + }, "node_modules/psl": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", diff --git a/src/bot/handler.js b/src/bot/handler.js index c6dec8d..d234cf8 100644 --- a/src/bot/handler.js +++ b/src/bot/handler.js @@ -2,6 +2,7 @@ const NLPProcessor = require('../nlp/processor'); const SSHManager = require('../ssh/manager'); const { TopSappManager } = require('../topsapp'); const config = require('../config/config'); +const logger = require('../utils/logger'); class BotHandler { constructor(bot) { @@ -11,6 +12,7 @@ class BotHandler { this.topsapp = new TopSappManager(); this.userSessions = new Map(); // Armazena sessões de usuário + logger.info('BotHandler inicializado'); console.log('🎯 BotHandler inicializado'); } @@ -128,6 +130,14 @@ class BotHandler { await this.handleAdmin(chatId); break; + case 'logs': + await this.handleViewLogs(chatId); + break; + + case 'limpar_logs': + await this.handleClearLogs(chatId); + break; + case 'usuarios_autorizados': await this.handleListAuthorizedUsers(chatId); break; @@ -177,6 +187,11 @@ class BotHandler { await this.handleLiberarCliente(chatId, target); break; + case 'listar_servicos': + console.log(`📋 Executando handleListarServicos com target: ${target}`); + await this.handleListarServicos(chatId, target); + break; + case 'status_topsapp': console.log(`🏢 Executando handleStatusTopSapp`); await this.handleStatusTopSapp(chatId); @@ -293,11 +308,17 @@ Digite /help para ver todos os comandos disponíveis! • "buscar cliente 123.456.789-10" • "buscar cliente Guarai João" • "ver faturas 123.456.789-10" +• "listar serviços 123.456.789-10" • "gerar boleto fatura123" -• "bloquear cliente 456" -• "liberar cliente 456" +• "bloquear 123.456.789-10" +• "liberar 123.456.789-10" • "status topsapp" +🔧 SERVIÇOS INDIVIDUAIS: +• Use "listar serviços CLIENTE" para ver todos os serviços +• Clique nos botões para bloquear/liberar serviços específicos +• Controle granular por serviço do cliente + 📊 STATUS E MONITORAMENTO: /status - Verifica status • "verificar concentrador" @@ -866,12 +887,30 @@ Digite /help para ver todos os comandos disponíveis! console.error(`❌ Erro em handleConfirmAddCallback:`, confirmError); await this.bot.answerCallbackQuery(callbackQuery.id, 'Erro na confirmação'); } + } else if (data.startsWith('confirm_block_service_')) { + console.log(`🔍 Chamando handleConfirmBlockServiceCallback`); + await this.handleConfirmBlockServiceCallback(callbackQuery, data); + } else if (data.startsWith('confirm_unblock_service_')) { + console.log(`🔍 Chamando handleConfirmUnblockServiceCallback`); + await this.handleConfirmUnblockServiceCallback(callbackQuery, data); + } else if (data.startsWith('confirm_block_all_')) { + console.log(`🔍 Chamando handleConfirmBlockAllCallback`); + await this.handleConfirmBlockAllCallback(callbackQuery, data); + } else if (data.startsWith('confirm_unblock_all_')) { + console.log(`🔍 Chamando handleConfirmUnblockAllCallback`); + await this.handleConfirmUnblockAllCallback(callbackQuery, data); } else if (data.startsWith('confirm_block_')) { console.log(`🔍 Chamando handleConfirmBlockCallback`); await this.handleConfirmBlockCallback(callbackQuery, data); } else if (data.startsWith('confirm_unblock_')) { console.log(`🔍 Chamando handleConfirmUnblockCallback`); await this.handleConfirmUnblockCallback(callbackQuery, data); + } else if (data.startsWith('block_service_')) { + console.log(`🔍 Chamando handleBlockServiceSelection`); + await this.handleBlockServiceSelection(callbackQuery, data); + } else if (data.startsWith('unblock_service_')) { + console.log(`🔍 Chamando handleUnblockServiceCallback`); + await this.handleUnblockServiceCallback(callbackQuery, data); } else if (data.startsWith('confirm_')) { console.log(`🔍 Condição confirm_ atendida`); await this.handleConfirmCallback(callbackQuery, data); @@ -2038,7 +2077,9 @@ Digite /help para ver todos os comandos disponíveis! `• \`meu id\` - Ver seu ID do Telegram\n\n` + `📊 **Sistema:**\n` + `• \`status\` - Status do bot\n` + - `• \`equipamentos\` - Lista de equipamentos\n\n` + + `• \`equipamentos\` - Lista de equipamentos\n` + + `• \`logs\` - Ver logs recentes\n` + + `• \`limpar logs\` - Limpar logs do sistema\n\n` + `🔧 **Comandos disponíveis:**\n` + `• Ver sinal, adicionar ONU, remover ONU\n` + `• Resetar ONU, atualizar firmware\n` + @@ -2269,9 +2310,26 @@ Digite /help para ver todos os comandos disponíveis! clienteId = target.replace(/pedro afonso/gi, '').trim(); } - // Se é CPF, buscar o cliente primeiro - let idCliente = clienteId; - if (clienteId.includes('.') || clienteId.includes('-')) { + // Se é CPF ou nome, buscar o cliente primeiro + let idCliente = null; + + // Verificar se é número puro (CPF sem formatação) + if (/^\d{11}$/.test(clienteId)) { + // É CPF sem formatação + const busca = await this.topsapp.buscarCliente(clienteId, location); + if (busca.success && busca.clientes && busca.clientes.length > 0) { + idCliente = busca.clientes[0].id; + location = busca.instancia; + } else { + await this.bot.sendMessage(chatId, + `❌ **Cliente não encontrado**\n\n` + + `📝 CPF: "${clienteId}"`, + { parse_mode: 'Markdown' } + ); + return; + } + } else if (clienteId.includes('.') || clienteId.includes('-') || isNaN(clienteId)) { + // É CPF formatado ou nome const busca = await this.topsapp.buscarCliente(clienteId, location); if (busca.success && busca.clientes && busca.clientes.length > 0) { idCliente = busca.clientes[0].id; @@ -2284,6 +2342,9 @@ Digite /help para ver todos os comandos disponíveis! ); return; } + } else { + // É ID numérico + idCliente = clienteId; } const resultado = await this.topsapp.obterBoletos(idCliente, location); @@ -2312,14 +2373,24 @@ Digite /help para ver todos os comandos disponíveis! message += `🌐 **Instância**: ${resultado.instancia}\n\n`; resultado.boletos.slice(0, 10).forEach((boleto, index) => { + message += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`; message += `💸 **${index + 1}. Fatura #${boleto.id}**\n`; - message += `💰 Valor: R$ ${boleto.valor}\n`; - message += `📅 Vencimento: ${boleto.vencimento}\n`; - message += `📊 Status: ${this.getStatusFatura(boleto.status)}\n`; - message += `📝 Descrição: ${boleto.descricao || 'Não informada'}\n`; + message += `💰 **Valor:** R$ ${boleto.valor}\n`; + message += `📅 **Vencimento:** ${this.formatarData(boleto.vencimento)}\n`; + message += `📊 **Status:** ${this.getStatusFatura(boleto.status)}\n`; + if (boleto.descricao) { + message += `📝 **Descrição:** ${boleto.descricao}\n`; + } + if (boleto.periodo) { + message += `📆 **Período:** ${boleto.periodo}\n`; + } if (boleto.linhaDigitavel) { - message += `🔢 Linha digitável: \`${boleto.linhaDigitavel}\`\n`; + message += `\n🔢 **Linha Digitável:**\n\`${boleto.linhaDigitavel}\`\n`; + } + + if (boleto.pixCopiaCola) { + message += `\n💳 **PIX Copia e Cola:**\n\`${boleto.pixCopiaCola}\`\n`; } message += '\n'; @@ -2404,36 +2475,137 @@ Digite /help para ver todos os comandos disponíveis! * Bloquear cliente */ async handleBloquearCliente(chatId, target) { + console.log(`🔍 handleBloquearCliente iniciado com target:`, target, `(tipo: ${typeof target})`); + if (!target) { await this.bot.sendMessage(chatId, `🔒 **Bloquear Cliente**\n\n` + `💡 **Como usar:**\n` + - `• "bloquear cliente 456"\n` + - `• "bloquear 789"` + `• "bloquear 123.456.789-10"\n` + + `• "bloquear cliente João"\n` + + `• "bloquear Guarai 123.456.789-10"` ); return; } try { - // Solicitar confirmação - const keyboard = { - inline_keyboard: [ - [ - { text: '✅ Confirmar Bloqueio', callback_data: `confirm_block_${target}` }, - { text: '❌ Cancelar', callback_data: 'cancel_action' } - ] - ] - }; + await this.bot.sendMessage(chatId, '🔍 Buscando cliente...'); - await this.bot.sendMessage(chatId, - `🔒 **Confirmar Bloqueio**\n\n` + - `⚠️ Deseja bloquear o cliente ID: ${target}?\n\n` + - `📋 Esta ação bloqueará todos os serviços ativos do cliente.`, - { - parse_mode: 'Markdown', - reply_markup: keyboard + // Detectar se especificou uma localização + let location = ''; + let clienteQuery = target; + + if (target.toLowerCase().includes('guarai') || target.toLowerCase().includes('colmeia')) { + location = 'Guarai/Colmeia'; + clienteQuery = target.replace(/(guarai|colmeia)/gi, '').trim(); + } else if (target.toLowerCase().includes('pedro afonso')) { + location = 'Pedro Afonso'; + clienteQuery = target.replace(/pedro afonso/gi, '').trim(); + } + + // Buscar cliente primeiro + let idCliente = null; + let nomeCliente = ''; + + if (/^\d{11}$/.test(clienteQuery) || clienteQuery.includes('.') || clienteQuery.includes('-') || isNaN(clienteQuery)) { + // É CPF ou nome + const busca = await this.topsapp.buscarCliente(clienteQuery, location); + if (busca.success && busca.clientes && busca.clientes.length > 0) { + idCliente = busca.clientes[0].id; + nomeCliente = busca.clientes[0].nome; + location = busca.instancia; + } else { + await this.bot.sendMessage(chatId, + `❌ **Cliente não encontrado**\n\n` + + `📝 CPF/Nome: "${clienteQuery}"`, + { parse_mode: 'Markdown' } + ); + return; } + } else { + // É ID numérico + idCliente = clienteQuery; + } + + // Buscar serviços do cliente para mostrar opções + const servicosResult = await this.topsapp.obterServicosCliente(idCliente, location); + + if (!servicosResult.success) { + await this.bot.sendMessage(chatId, + `❌ **Erro ao buscar serviços**\n\n` + + `⚠️ ${servicosResult.error}`, + { parse_mode: 'Markdown' } + ); + return; + } + + if (!servicosResult.servicos || servicosResult.servicos.length === 0) { + await this.bot.sendMessage(chatId, + `📋 **Nenhum serviço encontrado**\n\n` + + `👤 Cliente: ${nomeCliente}\n` + + `🆔 ID: ${idCliente}`, + { parse_mode: 'Markdown' } + ); + return; + } + + // Verificar se há serviços liberados + const servicosLiberados = servicosResult.servicos.filter(s => + s.status === 'liberado' || s.statusNumerico === 1 ); + + if (servicosLiberados.length === 0) { + await this.bot.sendMessage(chatId, + `🔒 **Nenhum serviço liberado**\n\n` + + `👤 Cliente: ${nomeCliente}\n` + + `🆔 ID: ${idCliente}\n` + + `📊 Todos os ${servicosResult.servicos.length} serviços já estão bloqueados.`, + { parse_mode: 'Markdown' } + ); + return; + } + + // Criar menu de seleção de serviços + let message = `🔒 **Selecionar Serviços para Bloquear**\n\n`; + message += `👤 **Cliente**: ${nomeCliente}\n`; + message += `🆔 **ID**: ${idCliente}\n`; + message += `🌐 **Instância**: ${location}\n\n`; + message += `✅ **Serviços liberados** (${servicosLiberados.length}):\n\n`; + + const keyboard = []; + + servicosLiberados.forEach((servico, index) => { + message += `${index + 1}. **${servico.nome}**\n`; + message += ` 💰 Valor: R$ ${servico.valorMensal}\n`; + message += ` 🆔 ID: ${servico.id}\n\n`; + + // Botão para bloquear este serviço específico + keyboard.push([{ + text: `🔒 Bloquear ${servico.nome}`, + callback_data: `confirm_block_service_${servico.id}_${idCliente}_${location.replace(/\s+/g, '_')}` + }]); + }); + + // Botão para bloquear todos + if (servicosLiberados.length > 1) { + keyboard.push([{ + text: `🔒 Bloquear TODOS os serviços (${servicosLiberados.length})`, + callback_data: `confirm_block_all_${idCliente}_${location.replace(/\s+/g, '_')}` + }]); + } + + // Botão cancelar + keyboard.push([{ + text: '❌ Cancelar', + callback_data: 'cancel_action' + }]); + + await this.bot.sendMessage(chatId, message, { + parse_mode: 'Markdown', + reply_markup: { + inline_keyboard: keyboard + } + }); } catch (error) { console.error('❌ Erro ao preparar bloqueio:', error); @@ -2453,32 +2625,131 @@ Digite /help para ver todos os comandos disponíveis! await this.bot.sendMessage(chatId, `🔓 **Liberar Cliente**\n\n` + `💡 **Como usar:**\n` + - `• "liberar cliente 456"\n` + - `• "liberar 789"` + `• "liberar 123.456.789-10"\n` + + `• "liberar cliente João"\n` + + `• "liberar Guarai 123.456.789-10"` ); return; } try { - // Solicitar confirmação - const keyboard = { - inline_keyboard: [ - [ - { text: '✅ Confirmar Liberação', callback_data: `confirm_unblock_${target}` }, - { text: '❌ Cancelar', callback_data: 'cancel_action' } - ] - ] - }; + await this.bot.sendMessage(chatId, '🔍 Buscando cliente...'); - await this.bot.sendMessage(chatId, - `🔓 **Confirmar Liberação**\n\n` + - `✅ Deseja liberar o cliente ID: ${target}?\n\n` + - `📋 Esta ação liberará todos os serviços bloqueados do cliente.`, - { - parse_mode: 'Markdown', - reply_markup: keyboard + // Detectar se especificou uma localização + let location = ''; + let clienteQuery = target; + + if (target.toLowerCase().includes('guarai') || target.toLowerCase().includes('colmeia')) { + location = 'Guarai/Colmeia'; + clienteQuery = target.replace(/(guarai|colmeia)/gi, '').trim(); + } else if (target.toLowerCase().includes('pedro afonso')) { + location = 'Pedro Afonso'; + clienteQuery = target.replace(/pedro afonso/gi, '').trim(); + } + + // Buscar cliente primeiro + let idCliente = null; + let nomeCliente = ''; + + if (/^\d{11}$/.test(clienteQuery) || clienteQuery.includes('.') || clienteQuery.includes('-') || isNaN(clienteQuery)) { + // É CPF ou nome + const busca = await this.topsapp.buscarCliente(clienteQuery, location); + if (busca.success && busca.clientes && busca.clientes.length > 0) { + idCliente = busca.clientes[0].id; + nomeCliente = busca.clientes[0].nome; + location = busca.instancia; + } else { + await this.bot.sendMessage(chatId, + `❌ **Cliente não encontrado**\n\n` + + `📝 CPF/Nome: "${clienteQuery}"`, + { parse_mode: 'Markdown' } + ); + return; } + } else { + // É ID numérico + idCliente = clienteQuery; + } + + // Buscar serviços do cliente para mostrar opções + const servicosResult = await this.topsapp.obterServicosCliente(idCliente, location); + + if (!servicosResult.success) { + await this.bot.sendMessage(chatId, + `❌ **Erro ao buscar serviços**\n\n` + + `⚠️ ${servicosResult.error}`, + { parse_mode: 'Markdown' } + ); + return; + } + + if (!servicosResult.servicos || servicosResult.servicos.length === 0) { + await this.bot.sendMessage(chatId, + `📋 **Nenhum serviço encontrado**\n\n` + + `👤 Cliente: ${nomeCliente}\n` + + `🆔 ID: ${idCliente}`, + { parse_mode: 'Markdown' } + ); + return; + } + + // Verificar se há serviços bloqueados + const servicosBloqueados = servicosResult.servicos.filter(s => + s.status === 'bloqueado' || s.statusNumerico === 2 ); + + if (servicosBloqueados.length === 0) { + await this.bot.sendMessage(chatId, + `✅ **Nenhum serviço bloqueado**\n\n` + + `👤 Cliente: ${nomeCliente}\n` + + `🆔 ID: ${idCliente}\n` + + `📊 Todos os ${servicosResult.servicos.length} serviços estão liberados.`, + { parse_mode: 'Markdown' } + ); + return; + } + + // Criar menu de seleção de serviços + let message = `🔓 **Selecionar Serviços para Liberar**\n\n`; + message += `👤 **Cliente**: ${nomeCliente}\n`; + message += `🆔 **ID**: ${idCliente}\n`; + message += `🌐 **Instância**: ${location}\n\n`; + message += `🔒 **Serviços bloqueados** (${servicosBloqueados.length}):\n\n`; + + const keyboard = []; + + servicosBloqueados.forEach((servico, index) => { + message += `${index + 1}. **${servico.nome}**\n`; + message += ` 💰 Valor: R$ ${servico.valorMensal}\n`; + message += ` 🆔 ID: ${servico.id}\n\n`; + + // Botão para liberar este serviço específico + keyboard.push([{ + text: `🔓 Liberar ${servico.nome}`, + callback_data: `confirm_unblock_service_${servico.id}_${idCliente}_${location.replace(/\s+/g, '_')}` + }]); + }); + + // Botão para liberar todos + if (servicosBloqueados.length > 1) { + keyboard.push([{ + text: `🔓 Liberar TODOS os serviços (${servicosBloqueados.length})`, + callback_data: `confirm_unblock_all_${idCliente}_${location.replace(/\s+/g, '_')}` + }]); + } + + // Botão cancelar + keyboard.push([{ + text: '❌ Cancelar', + callback_data: 'cancel_action' + }]); + + await this.bot.sendMessage(chatId, message, { + parse_mode: 'Markdown', + reply_markup: { + inline_keyboard: keyboard + } + }); } catch (error) { console.error('❌ Erro ao preparar liberação:', error); @@ -2494,8 +2765,21 @@ Digite /help para ver todos os comandos disponíveis! * Utilitário para obter status da fatura */ getStatusFatura(status) { + // Se já é uma string formatada, retornar diretamente + if (typeof status === 'string') { + switch (status.toLowerCase()) { + case 'pago': return '✅ Pago'; + case 'pendente': return '⏳ Pendente'; + case 'em aberto': return '⏳ Pendente'; + case 'vencida': return '❌ Vencida'; + case 'processando': return '🔄 Em processamento'; + default: return `📊 ${status}`; + } + } + + // Números (legado) switch (status) { - case 1: return '✅ Paga'; + case 1: return '✅ Pago'; case 2: return '⏳ Pendente'; case 3: return '❌ Vencida'; case 4: return '🔄 Em processamento'; @@ -2508,9 +2792,13 @@ Digite /help para ver todos os comandos disponíveis! */ async handleConfirmBlockCallback(callbackQuery, data) { const chatId = callbackQuery.message.chat.id; - const clienteId = data.replace('confirm_block_', ''); - + try { + // Extrair dados: confirm_block_ID_LOCATION + const parts = data.replace('confirm_block_', '').split('_'); + const clienteId = parts[0]; + const location = parts[1] ? parts[1].replace(/_/g, ' ') : ''; + await this.bot.answerCallbackQuery(callbackQuery.id, 'Processando bloqueio...'); await this.bot.editMessageText( @@ -2522,12 +2810,13 @@ Digite /help para ver todos os comandos disponíveis! } ); - const resultado = await this.topsapp.bloquearCliente(clienteId); + const resultado = await this.topsapp.bloquearCliente(clienteId, location); if (!resultado.success) { await this.bot.editMessageText( `❌ **Erro ao bloquear cliente**\n\n` + `🆔 Cliente ID: ${clienteId}\n` + + `🌐 Instância: ${location}\n` + `⚠️ Erro: ${resultado.error}`, { chat_id: chatId, @@ -2576,9 +2865,13 @@ Digite /help para ver todos os comandos disponíveis! */ async handleConfirmUnblockCallback(callbackQuery, data) { const chatId = callbackQuery.message.chat.id; - const clienteId = data.replace('confirm_unblock_', ''); - + try { + // Extrair dados: confirm_unblock_ID_LOCATION + const parts = data.replace('confirm_unblock_', '').split('_'); + const clienteId = parts[0]; + const location = parts[1] ? parts[1].replace(/_/g, ' ') : ''; + await this.bot.answerCallbackQuery(callbackQuery.id, 'Processando liberação...'); await this.bot.editMessageText( @@ -2590,12 +2883,13 @@ Digite /help para ver todos os comandos disponíveis! } ); - const resultado = await this.topsapp.liberarCliente(clienteId); + const resultado = await this.topsapp.liberarCliente(clienteId, location); if (!resultado.success) { await this.bot.editMessageText( `❌ **Erro ao liberar cliente**\n\n` + `🆔 Cliente ID: ${clienteId}\n` + + `🌐 Instância: ${location}\n` + `⚠️ Erro: ${resultado.error}`, { chat_id: chatId, @@ -2638,6 +2932,590 @@ Digite /help para ver todos os comandos disponíveis! ); } } + + /** + * Comando para listar serviços do cliente + */ + async handleListarServicos(chatId, target) { + logger.botCommand(chatId, 'listar_servicos', { target }); + + if (!target) { + await this.bot.sendMessage(chatId, + `📋 **Listar Serviços**\n\n` + + `💡 **Como usar:**\n` + + `• "listar serviços 123.456.789-10"\n` + + `• "ver serviços cliente123"\n` + + `• "mostrar serviços Guarai 123.456.789-10"` + ); + return; + } + + try { + await this.bot.sendMessage(chatId, '📋 Buscando serviços...'); + + // Detectar se especificou uma localização + let location = ''; + let clienteQuery = target; + + logger.debug('Processando busca de serviços', { + originalTarget: target, + chatId + }); + + if (target.toLowerCase().includes('guarai') || target.toLowerCase().includes('colmeia')) { + location = 'Guarai/Colmeia'; + clienteQuery = target.replace(/(guarai|colmeia)/gi, '').trim(); + } else if (target.toLowerCase().includes('pedro afonso')) { + location = 'Pedro Afonso'; + clienteQuery = target.replace(/pedro afonso/gi, '').trim(); + } + + logger.debug('Detectada localização e query', { + location, + clienteQuery, + isNumeric: !isNaN(clienteQuery), + isCPF: /^\d{11}$/.test(clienteQuery) + }); + + // Buscar cliente primeiro + let idCliente = null; + let nomeCliente = ''; + + if (/^\d{11}$/.test(clienteQuery) || clienteQuery.includes('.') || clienteQuery.includes('-') || isNaN(clienteQuery)) { + // É CPF ou nome + logger.debug('Buscando por CPF/Nome', { clienteQuery }); + const busca = await this.topsapp.buscarCliente(clienteQuery, location); + logger.debug('Resultado da busca de cliente', busca); + + if (busca.success && busca.clientes && busca.clientes.length > 0) { + idCliente = busca.clientes[0].id; + nomeCliente = busca.clientes[0].nome; + location = busca.instancia; + logger.debug('Cliente encontrado', { idCliente, nomeCliente, location }); + } else { + logger.warn('Cliente não encontrado', { clienteQuery, location }); + await this.bot.sendMessage(chatId, + `❌ **Cliente não encontrado**\n\n` + + `📝 CPF/Nome: "${clienteQuery}"`, + { parse_mode: 'Markdown' } + ); + return; + } + } else { + // É ID numérico + idCliente = clienteQuery; + logger.debug('Usando ID direto', { idCliente: clienteQuery }); + } + + logger.debug('Chamando obterServicosCliente', { + idCliente, + location, + tipoIdCliente: typeof idCliente + }); + + const resultado = await this.topsapp.obterServicosCliente(idCliente, location); + logger.topsappOperation('obterServicosCliente', idCliente, resultado); + + if (!resultado.success) { + logger.error('Erro ao buscar serviços', resultado.error); + await this.bot.sendMessage(chatId, + `❌ **Erro ao buscar serviços**\n\n` + + `⚠️ ${resultado.error}`, + { parse_mode: 'Markdown' } + ); + return; + return; + } + + // Debug: verificar se houve problema na filtragem + if (resultado.debug && resultado.debug.totalRetornadoAPI > 100) { + await this.bot.sendMessage(chatId, + `⚠️ **DEBUG: Possível problema na API**\n\n` + + `🔍 Total retornado pela API: ${resultado.debug.totalRetornadoAPI}\n` + + `🎯 Total filtrado para cliente ${resultado.debug.clienteId}: ${resultado.debug.totalFiltrado}\n` + + `🌐 Instância: ${resultado.instancia}\n\n` + + `💡 Se este número ainda está alto, há problema na API do TopSapp.`, + { parse_mode: 'Markdown' } + ); + } + + if (!resultado.servicos || resultado.servicos.length === 0) { + await this.bot.sendMessage(chatId, + `📋 **Nenhum serviço encontrado**\n\n` + + `👤 Cliente ID: ${idCliente}\n` + + `🌐 Instância: ${resultado.instancia}`, + { parse_mode: 'Markdown' } + ); + return; + } + + // Criar mensagem com lista de serviços + let message = `📋 **Serviços do Cliente**\n\n`; + if (nomeCliente) { + message += `👤 **Cliente**: ${nomeCliente}\n`; + } + message += `🆔 **ID**: ${idCliente}\n`; + message += `📊 **Total**: ${resultado.total} serviço(s)\n`; + message += `🌐 **Instância**: ${resultado.instancia}\n\n`; + + // Criar botões inline para cada serviço + const keyboard = []; + + resultado.servicos.forEach((servico, index) => { + message += `🔧 **${index + 1}. ${servico.nome}**\n`; + message += `🆔 ID: ${servico.id}\n`; + message += `📊 Status: ${this.getStatusServico(servico.status)}\n`; + message += `💰 Valor: R$ ${servico.valorMensal}\n`; + + if (servico.dataAtivacao) { + message += `📅 Ativado em: ${servico.dataAtivacao}\n`; + } + + message += '\n'; + + // Adicionar botões de ação baseados no status + const row = []; + if (servico.status === 'liberado') { + row.push({ + text: `🔒 Bloquear ${servico.nome}`, + callback_data: `block_service_${servico.id}_${idCliente}_${location.replace(/\s+/g, '_')}` + }); + } else { + row.push({ + text: `🔓 Liberar ${servico.nome}`, + callback_data: `unblock_service_${servico.id}_${idCliente}_${location.replace(/\s+/g, '_')}` + }); + } + keyboard.push(row); + }); + + await this.bot.sendMessage(chatId, message, { + parse_mode: 'Markdown', + reply_markup: { + inline_keyboard: keyboard + } + }); + + } catch (error) { + console.error('❌ Erro ao listar serviços:', error); + await this.bot.sendMessage(chatId, + `❌ **Erro interno ao listar serviços**\n\n` + + `⚠️ ${error.message}`, + { parse_mode: 'Markdown' } + ); + } + } + + /** + * Utilitário para obter status do serviço + */ + getStatusServico(status) { + if (typeof status === 'string') { + switch (status.toLowerCase()) { + case 'liberado': return '✅ Liberado'; + case 'bloqueado': return '🔒 Bloqueado'; + case 'desativado': return '❌ Desativado'; + default: return `📊 ${status}`; + } + } + + switch (status) { + case 1: return '✅ Liberado'; + case 2: return '🔒 Bloqueado'; + case 3: return '❌ Desativado'; + default: return '❓ Status desconhecido'; + } + } + + async handleConfirmBlockServiceCallback(callbackQuery, data) { + try { + // Extrair dados: confirm_block_service_SERVICEID_CLIENTEID_LOCATION + const parts = data.replace('confirm_block_service_', '').split('_'); + const serviceId = parts[0]; + const idCliente = parts[1]; + const location = parts.slice(2).join('_').replace(/_/g, ' '); + + logger.debug('handleConfirmBlockServiceCallback', { + serviceId, + idCliente, + location, + originalData: data + }); + + const chatId = callbackQuery.message.chat.id; + + await this.bot.editMessageText( + '⏳ Bloqueando serviço...', + { + chat_id: chatId, + message_id: callbackQuery.message.message_id + } + ); + + const resultado = await this.topsapp.alterarStatusServico(serviceId, 'bloquear', location); + + let texto; + if (resultado.success) { + texto = '✅ Serviço bloqueado com sucesso!'; + } else { + texto = `❌ Erro ao bloquear serviço: ${resultado.error}`; + } + + await this.bot.editMessageText( + texto, + { + chat_id: chatId, + message_id: callbackQuery.message.message_id + } + ); + + } catch (error) { + logger.botError(callbackQuery.message.chat.id, error, 'handleConfirmBlockServiceCallback'); + console.error('Erro no handleConfirmBlockServiceCallback:', error); + await this.bot.answerCallbackQuery(callbackQuery.id, 'Erro interno do servidor'); + } + } + + async handleConfirmUnblockServiceCallback(callbackQuery, data) { + try { + // Extrair dados: confirm_unblock_service_SERVICEID_CLIENTEID_LOCATION + const parts = data.replace('confirm_unblock_service_', '').split('_'); + const serviceId = parts[0]; + const idCliente = parts[1]; + const location = parts.slice(2).join('_').replace(/_/g, ' '); + + logger.debug('handleConfirmUnblockServiceCallback', { + serviceId, + idCliente, + location, + originalData: data + }); + + const chatId = callbackQuery.message.chat.id; + + await this.bot.editMessageText( + '⏳ Liberando serviço...', + { + chat_id: chatId, + message_id: callbackQuery.message.message_id + } + ); + + const resultado = await this.topsapp.alterarStatusServico(serviceId, 'liberar', location); + + let texto; + if (resultado.success) { + texto = '✅ Serviço liberado com sucesso!'; + } else { + texto = `❌ Erro ao liberar serviço: ${resultado.error}`; + } + + await this.bot.editMessageText( + texto, + { + chat_id: chatId, + message_id: callbackQuery.message.message_id + } + ); + + } catch (error) { + console.error('Erro no handleConfirmUnblockServiceCallback:', error); + await this.bot.answerCallbackQuery(callbackQuery.id, 'Erro interno do servidor'); + } + } + + async handleConfirmBlockAllCallback(callbackQuery, data) { + try { + const clienteId = data.replace('confirm_block_all_', ''); + const chatId = callbackQuery.message.chat.id; + + await this.bot.editMessageText( + '⏳ Bloqueando todos os serviços...', + { + chat_id: chatId, + message_id: callbackQuery.message.message_id + } + ); + + const resultado = await this.topsapp.bloquearCliente(clienteId); + + let texto; + if (resultado.sucesso) { + texto = '✅ Todos os serviços foram bloqueados com sucesso!'; + } else { + texto = `❌ Erro ao bloquear serviços: ${resultado.erro}`; + } + + await this.bot.editMessageText( + texto, + { + chat_id: chatId, + message_id: callbackQuery.message.message_id + } + ); + + } catch (error) { + console.error('Erro no handleConfirmBlockAllCallback:', error); + await this.bot.answerCallbackQuery(callbackQuery.id, 'Erro interno do servidor'); + } + } + + async handleConfirmUnblockAllCallback(callbackQuery, data) { + try { + const clienteId = data.replace('confirm_unblock_all_', ''); + const chatId = callbackQuery.message.chat.id; + + await this.bot.editMessageText( + '⏳ Liberando todos os serviços...', + { + chat_id: chatId, + message_id: callbackQuery.message.message_id + } + ); + + const resultado = await this.topsapp.liberarCliente(clienteId); + + let texto; + if (resultado.sucesso) { + texto = '✅ Todos os serviços foram liberados com sucesso!'; + } else { + texto = `❌ Erro ao liberar serviços: ${resultado.erro}`; + } + + await this.bot.editMessageText( + texto, + { + chat_id: chatId, + message_id: callbackQuery.message.message_id + } + ); + + } catch (error) { + console.error('Erro no handleConfirmUnblockAllCallback:', error); + await this.bot.answerCallbackQuery(callbackQuery.id, 'Erro interno do servidor'); + } + } + + async handleBlockServiceSelection(callbackQuery, data) { + try { + // Extrair dados: block_service_ID_CLIENTE_LOCATION + const parts = data.replace('block_service_', '').split('_'); + const serviceId = parts[0]; + const clienteId = parts[1]; + const location = parts.slice(2).join('_').replace(/_/g, ' '); + + const chatId = callbackQuery.message.chat.id; + + // Criar mensagem de confirmação + const texto = `🔸 **Confirmar Bloqueio de Serviço**\n\n` + + `🆔 **ID do Serviço:** ${serviceId}\n` + + `👤 **Cliente:** ${clienteId}\n` + + `📍 **Localização:** ${location}\n\n` + + `⚠️ Tem certeza que deseja bloquear este serviço?`; + + const keyboard = { + inline_keyboard: [ + [ + { text: '✅ Sim, bloquear', callback_data: `confirm_block_service_${serviceId}_${clienteId}_${location.replace(/\s+/g, '_')}` }, + { text: '❌ Cancelar', callback_data: 'cancel' } + ] + ] + }; + + await this.bot.editMessageText( + texto, + { + chat_id: chatId, + message_id: callbackQuery.message.message_id, + reply_markup: keyboard, + parse_mode: 'Markdown' + } + ); + + } catch (error) { + console.error('❌ Erro ao selecionar serviço para bloqueio:', error); + await this.bot.editMessageText( + `❌ **Erro interno ao selecionar serviço**\n\n` + + `⚠️ ${error.message}`, + { + chat_id: callbackQuery.message.chat.id, + message_id: callbackQuery.message.message_id, + parse_mode: 'Markdown' + } + ); + } + } + + async handleUnblockServiceSelection(callbackQuery, data) { + try { + // Extrair dados: unblock_service_ID_CLIENTE_LOCATION + const parts = data.replace('unblock_service_', '').split('_'); + const serviceId = parts[0]; + const clienteId = parts[1]; + const location = parts.slice(2).join('_').replace(/_/g, ' '); + + const chatId = callbackQuery.message.chat.id; + + // Criar mensagem de confirmação + const texto = `🔸 **Confirmar Liberação de Serviço**\n\n` + + `🆔 **ID do Serviço:** ${serviceId}\n` + + `👤 **Cliente:** ${clienteId}\n` + + `📍 **Localização:** ${location}\n\n` + + `✅ Tem certeza que deseja liberar este serviço?`; + + const keyboard = { + inline_keyboard: [ + [ + { text: '✅ Sim, liberar', callback_data: `confirm_unblock_service_${serviceId}_${clienteId}_${location.replace(/\s+/g, '_')}` }, + { text: '❌ Cancelar', callback_data: 'cancel' } + ] + ] + }; + + await this.bot.editMessageText( + texto, + { + chat_id: chatId, + message_id: callbackQuery.message.message_id, + reply_markup: keyboard, + parse_mode: 'Markdown' + } + ); + + } catch (error) { + console.error('❌ Erro ao selecionar serviço para liberação:', error); + await this.bot.editMessageText( + `❌ **Erro interno ao selecionar serviço**\n\n` + + `⚠️ ${error.message}`, + { + chat_id: callbackQuery.message.chat.id, + message_id: callbackQuery.message.message_id, + parse_mode: 'Markdown' + } + ); + } + } + + /** + * Visualizar logs recentes + */ + async handleViewLogs(chatId) { + const userId = chatId; + + if (!this.isAuthorized(userId)) { + await this.bot.sendMessage(chatId, '🚫 Acesso negado.'); + return; + } + + try { + const recentLogs = logger.getRecentLogs(50); + + if (recentLogs.length === 0) { + await this.bot.sendMessage(chatId, + `📋 **Logs do Sistema**\n\n` + + `ℹ️ Nenhum log encontrado ou modo debug desabilitado.`, + { parse_mode: 'Markdown' } + ); + return; + } + + // Dividir logs em mensagens menores (Telegram tem limite de caracteres) + const maxLength = 4000; + let currentMessage = `📋 **Logs Recentes (${recentLogs.length} linhas)**\n\n\`\`\`\n`; + + for (const line of recentLogs) { + const newLine = line + '\n'; + + if ((currentMessage + newLine + '```').length > maxLength) { + // Enviar mensagem atual e começar nova + currentMessage += '```'; + await this.bot.sendMessage(chatId, currentMessage, { parse_mode: 'Markdown' }); + currentMessage = '```\n' + newLine; + } else { + currentMessage += newLine; + } + } + + // Enviar último bloco + if (currentMessage.length > 10) { + currentMessage += '```'; + await this.bot.sendMessage(chatId, currentMessage, { parse_mode: 'Markdown' }); + } + + } catch (error) { + logger.botError(userId, error, 'handleViewLogs'); + await this.bot.sendMessage(chatId, + `❌ **Erro ao visualizar logs**\n\n` + + `⚠️ ${error.message}`, + { parse_mode: 'Markdown' } + ); + } + } + + /** + * Limpar logs do sistema + */ + async handleClearLogs(chatId) { + const userId = chatId; + + if (!this.isAuthorized(userId)) { + await this.bot.sendMessage(chatId, '🚫 Acesso negado.'); + return; + } + + try { + logger.clearLogs(); + logger.info('Logs limpos pelo usuário administrativo', { userId }); + + await this.bot.sendMessage(chatId, + `🧹 **Logs Limpos**\n\n` + + `✅ Todos os logs foram removidos com sucesso.`, + { parse_mode: 'Markdown' } + ); + + } catch (error) { + logger.botError(userId, error, 'handleClearLogs'); + await this.bot.sendMessage(chatId, + `❌ **Erro ao limpar logs**\n\n` + + `⚠️ ${error.message}`, + { parse_mode: 'Markdown' } + ); + } + } + + /** + * Formatar data para DD/MM/YYYY + */ + formatarData(data) { + if (!data) return 'Data não informada'; + + // Se já está no formato DD/MM/YYYY, retornar como está + if (data.match(/^\d{2}\/\d{2}\/\d{4}$/)) { + return data; + } + + // Se está no formato YYYY-MM-DD, converter + if (data.match(/^\d{4}-\d{2}-\d{2}$/)) { + const [ano, mes, dia] = data.split('-'); + return `${dia}/${mes}/${ano}`; + } + + // Tentar converter outros formatos + try { + const dataObj = new Date(data); + if (!isNaN(dataObj.getTime())) { + const dia = String(dataObj.getDate()).padStart(2, '0'); + const mes = String(dataObj.getMonth() + 1).padStart(2, '0'); + const ano = dataObj.getFullYear(); + return `${dia}/${mes}/${ano}`; + } + } catch (error) { + console.warn('Erro ao formatar data:', data, error); + } + + // Se não conseguiu formatar, retornar original + return data; + } } module.exports = BotHandler; diff --git a/src/nlp/processor.js b/src/nlp/processor.js index ad3ac0f..6e0c9c4 100644 --- a/src/nlp/processor.js +++ b/src/nlp/processor.js @@ -169,9 +169,9 @@ class NLPProcessor { }; } - // Detectar comandos TopSapp - bloquear cliente - if (/bloquear|suspender|cortar/i.test(action) && /cliente|user/i.test(fullMatch)) { - let clienteId = match[3] || match[2]; // ID do cliente + // Detectar comandos TopSapp - bloquear cliente/serviço + if (/bloquear|suspender|cortar/i.test(action)) { + let clienteId = match[3] || match[2]; // ID/CPF/Nome do cliente return { action: 'bloquear_cliente', @@ -181,6 +181,30 @@ class NLPProcessor { }; } + // Detectar comandos TopSapp - listar serviços + if (/listar|ver|mostrar/i.test(action) && /serviços?|produtos?|planos?/i.test(fullMatch)) { + let clienteId = match[3] || match[2]; // ID/CPF/Nome do cliente + + return { + action: 'listar_servicos', + target: clienteId ? clienteId.trim() : null, + confidence: 0.95, + type: 'topsapp_services_pattern' + }; + } + + // Detectar comandos TopSapp - liberar cliente/serviço + if (/liberar|ativar|religar|reconectar/i.test(action)) { + let clienteId = match[3] || match[2]; // ID/CPF/Nome do cliente + + return { + action: 'liberar_cliente', + target: clienteId ? clienteId.trim() : null, + confidence: 0.95, + type: 'topsapp_unblock_pattern' + }; + } + // Detectar comandos TopSapp - liberar cliente if (/liberar|desbloquear|reconectar/i.test(action) && /cliente|user/i.test(fullMatch)) { let clienteId = match[3] || match[2]; // ID do cliente diff --git a/src/topsapp/api.js b/src/topsapp/api.js index 87511be..3720e51 100644 --- a/src/topsapp/api.js +++ b/src/topsapp/api.js @@ -1,5 +1,6 @@ const axios = require('axios'); const https = require('https'); +const logger = require('../utils/logger'); class TopSappAPI { constructor() { @@ -23,10 +24,8 @@ class TopSappAPI { httpsAgent: new https.Agent({ rejectUnauthorized: false }), - timeout: 30000, - headers: { - 'Content-Type': 'multipart/form-data' - } + timeout: 30000 + // Não definir Content-Type, deixar o FormData fazer isso automaticamente }; console.log('🔗 TopSappAPI inicializado'); @@ -53,31 +52,45 @@ class TopSappAPI { async login(instance, credentials) { try { console.log(`🔐 Fazendo login na instância: ${instance.name}`); + console.log(`📍 URL: ${instance.baseURL}/Login`); + console.log(`👤 Usuario: ${credentials.usuario}`); const formData = new FormData(); formData.append('usuario', credentials.usuario); formData.append('senha', credentials.senha); formData.append('identificador', credentials.identificador || 'BotTelegram'); - formData.append('idUsuario', credentials.idUsuario || '1'); const response = await axios.post( `${instance.baseURL}/Login`, formData, - this.axiosConfig + { + ...this.axiosConfig, + timeout: 10000 + } ); - if (response.data && response.data.status === 'success') { + console.log(`📊 Status da resposta: ${response.status}`); + console.log(`📋 Dados da resposta:`, response.data); + + if (response.data && response.data.resultado === true) { const sessionId = response.data.sessao; + const idUsuario = response.data.id_usuario; + this.sessions.set(instance.baseURL, { sessionId, + idUsuario, timestamp: Date.now(), instance }); console.log(`✅ Login realizado com sucesso na instância: ${instance.name}`); - return { success: true, sessionId, instance }; + console.log(`🎫 Sessão: ${sessionId}`); + console.log(`👤 ID do usuário: ${idUsuario}`); + return { success: true, sessionId, idUsuario, instance }; } else { - throw new Error(response.data?.message || 'Login falhou'); + const errorMsg = response.data?.msg || 'Login falhou'; + console.log(`❌ Erro no login: ${errorMsg}`); + throw new Error(errorMsg); } } catch (error) { @@ -127,15 +140,19 @@ class TopSappAPI { const formData = new FormData(); formData.append('sessao', session.sessionId); + formData.append('identificador', credentials.identificador || 'BotTelegram'); // Adicionar parâmetros extras Object.keys(data).forEach(key => { if (data[key] !== undefined && data[key] !== null) { - formData.append(key, data[key]); + // Converter idCliente para inteiro se necessário + const value = key === 'idCliente' ? parseInt(data[key], 10) : data[key]; + formData.append(key, value.toString()); } }); console.log(`📡 Fazendo requisição para: ${instance.baseURL}/${endpoint}`); + console.log(`📊 Parâmetros:`, data); const response = await axios.post( `${instance.baseURL}/${endpoint}`, @@ -143,14 +160,19 @@ class TopSappAPI { this.axiosConfig ); - if (response.data && response.data.status === 'success') { + console.log(`📊 Status da resposta: ${response.status}`); + console.log(`📋 Dados da resposta:`, response.data); + + if (response.data && response.data.resultado === true) { return { success: true, data: response.data, instance: instance.name }; } else { - throw new Error(response.data?.message || 'Requisição falhou'); + const errorMsg = response.data?.msg || 'Requisição falhou'; + console.log(`❌ Erro na API: ${errorMsg}`); + throw new Error(errorMsg); } } catch (error) { @@ -170,19 +192,26 @@ class TopSappAPI { const instance = this.getInstanceByLocation(location); try { + // Obter sessão válida para pegar o idUsuario + const session = await this.getValidSession(instance, credentials); + // Tentar buscar por diferentes critérios - const searchParams = {}; + const searchParams = { + idUsuario: session.idUsuario || 1 // Usar ID do usuário da sessão + }; // Se é número, pode ser CPF ou ID if (/^\d+$/.test(query)) { - if (query.length === 11) { - searchParams.cpf = query; + if (query.length === 11 || query.length === 14) { + // CPF (11 dígitos) ou CNPJ (14 dígitos) + searchParams.cpfCnpj = query; } else { + // ID do cliente searchParams.idCliente = query; } } else { - // Buscar por nome - searchParams.nome = query; + // CPF formatado ou nome - usar o endpoint de busca geral + searchParams.cpfCnpj = query.replace(/[^\d]/g, ''); // Remove formatação } const result = await this.makeRequest('ObterClientes', searchParams, instance, credentials); @@ -190,7 +219,7 @@ class TopSappAPI { if (result.success) { return { success: true, - clientes: result.data.clientes || [], + clientes: result.data.dados || [], // Usar 'dados' em vez de 'clientes' instance: result.instance }; } @@ -221,7 +250,85 @@ class TopSappAPI { async obterServicosCliente(idCliente, location = '', credentials) { const instance = this.getInstanceByLocation(location); - return await this.makeRequest('ObterClientesServicos', { idCliente }, instance, credentials); + // Obter o idUsuario da sessão atual + const session = await this.getValidSession(instance, credentials); + + console.log(`🔍 Obtendo serviços para cliente: ${idCliente} (tipo: ${typeof idCliente})`); + + const clienteIdInt = parseInt(idCliente, 10); + + // Tentar diferentes abordagens para obter serviços do cliente + let response = null; + let tentativas = [ + // Tentativa 1: Endpoint padrão com idCliente + { endpoint: 'ObterClientesServicos', data: { idCliente: clienteIdInt, idUsuario: session.idUsuario || 1 } }, + // Tentativa 2: Endpoint alternativo (se existir) + { endpoint: 'ObterServicosCliente', data: { idCliente: clienteIdInt, idUsuario: session.idUsuario || 1 } }, + // Tentativa 3: Com parâmetros alternativos + { endpoint: 'ObterClientesServicos', data: { cliente_id: clienteIdInt, idUsuario: session.idUsuario || 1 } } + ]; + + logger.debug('Iniciando tentativas de busca de serviços', { + clienteIdInt, + sessionId: session.idUsuario, + tentativasCount: tentativas.length + }); + + for (let i = 0; i < tentativas.length; i++) { + const tentativa = tentativas[i]; + try { + console.log(`📋 Tentativa ${i + 1} - Endpoint: ${tentativa.endpoint}, Dados:`, tentativa.data); + logger.debug(`Tentativa ${i + 1}`, { + endpoint: tentativa.endpoint, + data: tentativa.data + }); + + response = await this.makeRequest(tentativa.endpoint, tentativa.data, instance, credentials); + logger.apiCall(tentativa.endpoint, tentativa.data, response); + + if (response.success && response.data) { + console.log(`✅ Sucesso na tentativa ${i + 1} - Total de itens: ${response.data.dados?.length || response.data.length || 0}`); + logger.debug(`Sucesso na tentativa ${i + 1}`, { + totalItems: response.data.dados?.length || response.data.length || 0, + hasData: !!response.data + }); + + // Verificar se os dados são relevantes para o cliente + const dados = response.data.dados || response.data; + if (Array.isArray(dados) && dados.length > 0) { + // Verificar quantos itens realmente pertencem ao cliente + const servicosDoCliente = dados.filter(item => { + const itemClienteId = parseInt(item.idCliente || item.cliente_id || item.id_cliente || 0, 10); + return itemClienteId === clienteIdInt; + }); + + console.log(`🎯 Serviços filtrados para cliente ${clienteIdInt}: ${servicosDoCliente.length} de ${dados.length} total`); + logger.debug('Filtragem de serviços', { + clienteIdBuscado: clienteIdInt, + servicosEncontrados: servicosDoCliente.length, + totalServicos: dados.length, + primeiroItem: dados[0] ? { + id_cliente: dados[0].id_cliente, + idCliente: dados[0].idCliente, + cliente_id: dados[0].cliente_id + } : null + }); + + // Se encontrou serviços específicos do cliente, usar essa resposta + if (servicosDoCliente.length > 0 || i === tentativas.length - 1) { + break; + } + } + } + } catch (error) { + console.log(`❌ Falha na tentativa ${i + 1}: ${error.message}`); + if (i === tentativas.length - 1) { + throw error; + } + } + } + + return response; } /** @@ -230,19 +337,31 @@ class TopSappAPI { async obterFaturas(idCliente, location = '', credentials) { const instance = this.getInstanceByLocation(location); + // Obter o idUsuario da sessão atual + const session = await this.getValidSession(instance, credentials); + return await this.makeRequest('ObterDocReceber', { idCliente, - situacao: 1 // Faturas em aberto + idUsuario: session.idUsuario || 1, // Usar ID do usuário da sessão + status: 0, // 0 = Faturas abertas + situacaoFatura: 0 // 0 = Faturas ativas }, instance, credentials); } /** * Obter dados para pagamento da fatura (linha digitável) */ - async obterDadosFatura(idFatura, location = '', credentials) { + async obterDadosFatura(idFatura, idCliente, location = '', credentials) { const instance = this.getInstanceByLocation(location); - return await this.makeRequest('ObterDocReceberDadosFatura', { idFatura }, instance, credentials); + // Obter o idUsuario da sessão atual + const session = await this.getValidSession(instance, credentials); + + return await this.makeRequest('ObterDocReceberDadosFatura', { + idFatura, + idCliente, + idUsuario: session.idUsuario || 1 + }, instance, credentials); } /** @@ -257,28 +376,33 @@ class TopSappAPI { /** * Alterar status do serviço (bloquear/liberar) */ - async alterarStatusServico(idServico, status, location = '', credentials) { + async alterarStatusServico(idServico, idCliente, status, location = '', credentials) { const instance = this.getInstanceByLocation(location); + // Obter o idUsuario da sessão atual + const session = await this.getValidSession(instance, credentials); + // Status: 1 = Ativo, 2 = Bloqueado, 3 = Desativado return await this.makeRequest('AlterarServicoStatus', { idServico, - status + idCliente, + idUsuario: session.idUsuario || 1, + idStatus: status }, instance, credentials); } /** * Bloquear serviço do cliente */ - async bloquearServico(idServico, location = '', credentials) { - return await this.alterarStatusServico(idServico, 2, location, credentials); + async bloquearServico(idServico, idCliente, location = '', credentials) { + return await this.alterarStatusServico(idServico, idCliente, 2, location, credentials); } /** * Liberar serviço do cliente */ - async liberarServico(idServico, location = '', credentials) { - return await this.alterarStatusServico(idServico, 1, location, credentials); + async liberarServico(idServico, idCliente, location = '', credentials) { + return await this.alterarStatusServico(idServico, idCliente, 1, location, credentials); } /** diff --git a/src/topsapp/config.js b/src/topsapp/config.js index 0d0f13e..859e2d8 100644 --- a/src/topsapp/config.js +++ b/src/topsapp/config.js @@ -11,10 +11,10 @@ class TopSappConfig { idUsuario: process.env.TOPSAPP_GUARAI_USERID || '1' }, pedro_afonso: { - usuario: process.env.TOPSAPP_PEDRO_USER || '', - senha: process.env.TOPSAPP_PEDRO_PASS || '', - identificador: process.env.TOPSAPP_PEDRO_ID || 'BotTelegram', - idUsuario: process.env.TOPSAPP_PEDRO_USERID || '1' + usuario: process.env.TOPSAPP_PEDRO_AFONSO_USER || '', + senha: process.env.TOPSAPP_PEDRO_AFONSO_PASS || '', + identificador: process.env.TOPSAPP_PEDRO_AFONSO_ID || 'BotTelegram', + idUsuario: process.env.TOPSAPP_PEDRO_AFONSO_USERID || '1' } }; diff --git a/src/topsapp/manager.js b/src/topsapp/manager.js index c7adfb4..c6d591a 100644 --- a/src/topsapp/manager.js +++ b/src/topsapp/manager.js @@ -1,5 +1,6 @@ const TopSappAPI = require('./api'); const TopSappConfig = require('./config'); +const logger = require('../utils/logger'); const fs = require('fs').promises; const path = require('path'); @@ -8,6 +9,8 @@ class TopSappManager { this.api = new TopSappAPI(); this.config = new TopSappConfig(); + logger.info('TopSappManager inicializado'); + // Limpar sessões expiradas a cada 10 minutos setInterval(() => { this.api.clearExpiredSessions(); @@ -149,21 +152,29 @@ class TopSappManager { const boletos = []; - for (const fatura of faturas.data.faturas || []) { + for (const fatura of faturas.data.dados || []) { // Obter dados da fatura (linha digitável) - const dadosFatura = await this.api.obterDadosFatura(fatura.id, location, credentials); + const dadosFatura = await this.api.obterDadosFatura(fatura.id, fatura.codigo_cliente, location, credentials); const boleto = { id: fatura.id, valor: fatura.valor, vencimento: fatura.vencimento, - status: fatura.status, - descricao: fatura.descricao, - linhaDigitavel: null + status: fatura.pago === 'SIM' ? 'Pago' : 'Pendente', + descricao: fatura.historico, + periodo: this.formatarPeriodo(fatura.periodo_inicio, fatura.periodo_fim), + linhaDigitavel: null, + pixCopiaCola: null }; - if (dadosFatura.success && dadosFatura.data.linhaDigitavel) { - boleto.linhaDigitavel = dadosFatura.data.linhaDigitavel; + // Adicionar linha digitável e PIX copia e cola se obtida com sucesso + if (dadosFatura.success && dadosFatura.data.fatura) { + if (dadosFatura.data.fatura.linha_digitavel) { + boleto.linhaDigitavel = dadosFatura.data.fatura.linha_digitavel; + } + if (dadosFatura.data.fatura.qrcode) { + boleto.pixCopiaCola = dadosFatura.data.fatura.qrcode; + } } boletos.push(boleto); @@ -285,12 +296,12 @@ class TopSappManager { const resultados = []; // Bloquear todos os serviços ativos - for (const servico of servicos.data.servicos || []) { - if (servico.status === 1) { // Apenas serviços ativos - const resultado = await this.api.bloquearServico(servico.id, location, credentials); + for (const servico of servicos.data.dados || []) { + if (servico.status === 1 || servico.estado === 'liberado') { // Apenas serviços ativos + const resultado = await this.api.bloquearServico(servico.id, idCliente, location, credentials); resultados.push({ servicoId: servico.id, - descricao: servico.descricao, + descricao: servico.descricao || servico.nome, resultado }); } @@ -335,12 +346,12 @@ class TopSappManager { const resultados = []; // Liberar todos os serviços bloqueados - for (const servico of servicos.data.servicos || []) { - if (servico.status === 2) { // Apenas serviços bloqueados - const resultado = await this.api.liberarServico(servico.id, location, credentials); + for (const servico of servicos.data.dados || []) { + if (servico.status === 2 || servico.estado === 'bloqueado') { // Apenas serviços bloqueados + const resultado = await this.api.liberarServico(servico.id, idCliente, location, credentials); resultados.push({ servicoId: servico.id, - descricao: servico.descricao, + descricao: servico.descricao || servico.nome, resultado }); } @@ -361,12 +372,346 @@ class TopSappManager { } } + /** + * Obter lista de serviços do cliente + */ + async obterServicosCliente(idCliente, location = '') { + logger.debug('obterServicosCliente chamado', { + idCliente, + location, + tipoIdCliente: typeof idCliente + }); + + if (!this.isReady()) { + logger.warn('TopSapp não está configurado'); + return { + success: false, + error: 'TopSapp não está configurado' + }; + } + + try { + const credentials = this.config.getCredentialsByLocation(location); + logger.debug('Credenciais obtidas', { location, hasCredentials: !!credentials }); + + const servicos = await this.api.obterServicosCliente(idCliente, location, credentials); + logger.debug('Resposta da API obterServicosCliente', { + success: servicos.success, + dataLength: servicos.data?.dados?.length, + error: servicos.error + }); + + if (!servicos.success) { + logger.warn('API retornou erro', servicos.error); + return servicos; + } + + // Converter idCliente para número para comparação + console.log(`🔍 Recebido idCliente:`, idCliente, `(tipo: ${typeof idCliente})`); + logger.debug('Processando idCliente', { + originalValue: idCliente, + originalType: typeof idCliente + }); + + const clienteId = parseInt(idCliente, 10); + logger.debug('idCliente convertido', { + clienteId, + isValid: !isNaN(clienteId) + }); + console.log(`🔍 Convertido para clienteId:`, clienteId, `(tipo: ${typeof clienteId})`); + + // Verificar se a conversão foi bem-sucedida + if (isNaN(clienteId)) { + console.log(`❌ ERRO: idCliente inválido. Valor original:`, idCliente); + return { + success: false, + error: `ID do cliente inválido: ${idCliente}` + }; + } + + console.log(`🔍 Manager - Filtrando serviços para cliente ID: ${clienteId}`); + + // Acessar os dados da resposta + let dadosServicos = servicos.data?.dados || servicos.data || []; + + // Se não é array, tentar converter + if (!Array.isArray(dadosServicos)) { + console.log(`⚠️ Dados retornados não são um array:`, typeof dadosServicos); + dadosServicos = []; + } + + console.log(`📊 Total de registros retornados pela API: ${dadosServicos.length}`); + + if (dadosServicos.length > 100) { + console.log(`⚠️ ALERTA: API retornou ${dadosServicos.length} registros - muito mais que o esperado para um cliente`); + + // Log dos primeiros registros para debug + const amostra = dadosServicos.slice(0, 5).map(item => ({ + id: item.id, + idCliente: item.idCliente || item.cliente_id || item.id_cliente, + nome: item.nome || item.descricao + })); + console.log(`🔍 Amostra dos primeiros registros:`, amostra); + } + + // Filtrar RIGOROSAMENTE apenas os serviços do cliente específico + const servicosDoCliente = dadosServicos.filter(servico => { + // Tentar diferentes campos para o ID do cliente + const possiveisIds = [ + servico.idCliente, + servico.cliente_id, + servico.id_cliente, + servico.clienteId, + servico.cod_cliente, + servico.codigo_cliente + ]; + + // Verificar se algum dos IDs corresponde ao cliente + for (const id of possiveisIds) { + if (id !== undefined && id !== null) { + const servicoClienteId = parseInt(id, 10); + if (servicoClienteId === clienteId) { + return true; + } + } + } + + return false; + }); + + console.log(`🎯 Serviços filtrados para o cliente ${clienteId}: ${servicosDoCliente.length}`); + + // Se ainda assim temos muitos resultados, algo está errado + if (servicosDoCliente.length > 50) { + console.log(`❌ ERRO: Ainda temos ${servicosDoCliente.length} serviços após filtro - possível problema na API`); + + // Log detalhado para investigação + const idsEncontrados = servicosDoCliente.map(s => ({ + id: s.id, + idCliente: s.idCliente || s.cliente_id || s.id_cliente, + nome: s.nome || s.descricao + })).slice(0, 10); + + console.log(`🔍 IDs encontrados (primeiros 10):`, idsEncontrados); + } + + const servicosFormatados = servicosDoCliente.map(servico => ({ + id: servico.id || servico.id_servico, + nome: servico.nome || servico.descricao || 'Serviço sem nome', + status: servico.estado || (servico.status === 1 ? 'liberado' : 'bloqueado'), + statusNumerico: servico.status, + valorMensal: servico.valor_mensal || '0.00', + tipoServico: servico.tipo_servico || 'Não informado', + dataAtivacao: servico.data_ativacao, + observacoes: servico.observacoes, + clienteId: servico.idCliente || servico.cliente_id || servico.id_cliente + })); + + return { + success: true, + servicos: servicosFormatados, + total: servicosFormatados.length, + instancia: servicos.instance, + debug: { + totalRetornadoAPI: dadosServicos.length, + totalFiltrado: servicosFormatados.length, + clienteId: clienteId + } + }; + + } catch (error) { + console.error(`❌ Erro ao obter serviços do cliente ${idCliente}:`, error); + return { + success: false, + error: error.message + }; + } + } + + /** + * Bloquear serviço específico + */ + async bloquearServicoEspecifico(idCliente, idServico, location = '') { + if (!this.isReady()) { + return { + success: false, + error: 'TopSapp não está configurado' + }; + } + + try { + const credentials = this.config.getCredentialsByLocation(location); + const resultado = await this.api.bloquearServico(idServico, idCliente, location, credentials); + + return { + ...resultado, + instancia: location + }; + + } catch (error) { + return { + success: false, + error: error.message + }; + } + } + + /** + * Liberar serviço específico + */ + async liberarServicoEspecifico(idCliente, idServico, location = '') { + if (!this.isReady()) { + return { + success: false, + error: 'TopSapp não está configurado' + }; + } + + try { + const credentials = this.config.getCredentialsByLocation(location); + const resultado = await this.api.liberarServico(idServico, idCliente, location, credentials); + + return { + ...resultado, + instancia: location + }; + + } catch (error) { + return { + success: false, + error: error.message + }; + } + } + + /** + * Alterar status de um serviço específico + */ + async alterarStatusServico(idServico, acao, location = '') { + if (!this.isReady()) { + return { + success: false, + error: 'TopSapp não está configurado' + }; + } + + try { + const credentials = this.config.getCredentialsByLocation(location); + + // Determinar o status numérico baseado na ação + let status; + if (acao === 'bloquear') { + status = 2; // Bloqueado + } else if (acao === 'liberar') { + status = 1; // Ativo + } else { + return { + success: false, + error: 'Ação inválida. Use "bloquear" ou "liberar"' + }; + } + + // Primeiro, obter informações do serviço para saber o idCliente + const servicos = await this.api.obterServicosCliente(null, location, credentials); + + if (!servicos.success) { + return { + success: false, + error: 'Erro ao obter informações do serviço' + }; + } + + // Procurar o serviço específico + const servicoEncontrado = (servicos.data.dados || []).find(s => + s.id_servico == idServico || s.id == idServico + ); + + if (!servicoEncontrado) { + return { + success: false, + error: 'Serviço não encontrado' + }; + } + + const idCliente = servicoEncontrado.id_cliente; + + logger.debug('Alterando status do serviço', { + idServico, + idCliente, + status, + acao + }); + + const resultado = await this.api.alterarStatusServico(idServico, idCliente, status, location, credentials); + + return { + ...resultado, + instancia: location + }; + + } catch (error) { + logger.error('Erro ao alterar status do serviço', error); + return { + success: false, + error: error.message + }; + } + } + /** * Obter instâncias disponíveis */ getInstances() { return this.api.getInstances(); } + + /** + * Formatar período de datas para DD/MM/YYYY + */ + formatarPeriodo(dataInicio, dataFim) { + const formatarData = (data) => { + if (!data) return ''; + + // Se já está no formato DD/MM/YYYY, retornar como está + if (data.match(/^\d{2}\/\d{2}\/\d{4}$/)) { + return data; + } + + // Se está no formato YYYY-MM-DD, converter + if (data.match(/^\d{4}-\d{2}-\d{2}$/)) { + const [ano, mes, dia] = data.split('-'); + return `${dia}/${mes}/${ano}`; + } + + // Tentar converter outros formatos + try { + const dataObj = new Date(data); + if (!isNaN(dataObj.getTime())) { + const dia = String(dataObj.getDate()).padStart(2, '0'); + const mes = String(dataObj.getMonth() + 1).padStart(2, '0'); + const ano = dataObj.getFullYear(); + return `${dia}/${mes}/${ano}`; + } + } catch (error) { + console.warn('Erro ao formatar data:', data, error); + } + + return data; + }; + + const inicio = formatarData(dataInicio); + const fim = formatarData(dataFim); + + if (inicio && fim) { + return `${inicio} a ${fim}`; + } else if (inicio) { + return inicio; + } else if (fim) { + return fim; + } else { + return 'Período não informado'; + } + } } module.exports = TopSappManager; diff --git a/src/utils/logger.js b/src/utils/logger.js new file mode 100644 index 0000000..e42117b --- /dev/null +++ b/src/utils/logger.js @@ -0,0 +1,173 @@ +const fs = require('fs'); +const path = require('path'); + +class Logger { + constructor() { + this.logDir = path.join(__dirname, '../../logs'); + this.logFile = path.join(this.logDir, 'bot-debug.log'); + this.maxLogSize = 10 * 1024 * 1024; // 10MB + this.maxBackups = 5; + + // Criar diretório de logs se não existir + this.ensureLogDir(); + + // Só ativa logs se estiver em modo debug + this.isDebug = process.env.NODE_ENV === 'development' || process.env.DEBUG === 'true'; + } + + ensureLogDir() { + if (!fs.existsSync(this.logDir)) { + fs.mkdirSync(this.logDir, { recursive: true }); + } + } + + formatMessage(level, message, data = null) { + const timestamp = new Date().toISOString(); + let logLine = `[${timestamp}] [${level}] ${message}`; + + if (data) { + if (typeof data === 'object') { + logLine += '\n' + JSON.stringify(data, null, 2); + } else { + logLine += ` - ${data}`; + } + } + + return logLine + '\n'; + } + + rotateLogIfNeeded() { + if (!fs.existsSync(this.logFile)) { + return; + } + + const stats = fs.statSync(this.logFile); + if (stats.size > this.maxLogSize) { + // Fazer backup dos logs existentes + for (let i = this.maxBackups - 1; i >= 0; i--) { + const oldFile = i === 0 ? this.logFile : `${this.logFile}.${i}`; + const newFile = `${this.logFile}.${i + 1}`; + + if (fs.existsSync(oldFile)) { + if (i === this.maxBackups - 1) { + fs.unlinkSync(oldFile); // Deletar o mais antigo + } else { + fs.renameSync(oldFile, newFile); + } + } + } + } + } + + writeLog(level, message, data = null) { + if (!this.isDebug) { + return; // Só loga em modo debug + } + + try { + this.rotateLogIfNeeded(); + const logMessage = this.formatMessage(level, message, data); + + // Escrever no arquivo de log + fs.appendFileSync(this.logFile, logMessage); + + // Também mostrar no console se for erro ou warning + if (level === 'ERROR' || level === 'WARN') { + console.log(logMessage.trim()); + } + } catch (error) { + console.error('Erro ao escrever log:', error); + } + } + + debug(message, data = null) { + this.writeLog('DEBUG', message, data); + } + + info(message, data = null) { + this.writeLog('INFO', message, data); + } + + warn(message, data = null) { + this.writeLog('WARN', message, data); + } + + error(message, data = null) { + this.writeLog('ERROR', message, data); + } + + // Métodos específicos para o bot + botCommand(userId, command, data = null) { + this.debug(`Comando recebido - User: ${userId}, Command: ${command}`, data); + } + + apiCall(endpoint, params = null, response = null) { + this.debug(`API Call - Endpoint: ${endpoint}`, { + params, + responseStatus: response?.status, + responseDataLength: response?.data?.length + }); + } + + botError(userId, error, context = null) { + this.error(`Bot Error - User: ${userId}`, { + error: error.message, + stack: error.stack, + context + }); + } + + topsappOperation(operation, clientId, result = null) { + this.debug(`TopSapp Operation - ${operation} - Cliente: ${clientId}`, { + success: result?.success, + dataCount: result?.data?.length || result?.servicos?.length || result?.clientes?.length, + error: result?.error + }); + } + + // Limpar logs antigos (método utilitário) + clearLogs() { + if (!this.isDebug) { + return; + } + + try { + if (fs.existsSync(this.logFile)) { + fs.unlinkSync(this.logFile); + } + + // Limpar backups também + for (let i = 1; i <= this.maxBackups; i++) { + const backupFile = `${this.logFile}.${i}`; + if (fs.existsSync(backupFile)) { + fs.unlinkSync(backupFile); + } + } + + this.info('Logs limpos com sucesso'); + } catch (error) { + console.error('Erro ao limpar logs:', error); + } + } + + // Obter últimas linhas do log + getRecentLogs(lines = 100) { + if (!this.isDebug || !fs.existsSync(this.logFile)) { + return []; + } + + try { + const content = fs.readFileSync(this.logFile, 'utf8'); + const allLines = content.split('\n').filter(line => line.trim()); + return allLines.slice(-lines); + } catch (error) { + console.error('Erro ao ler logs:', error); + return []; + } + } +} + +// Singleton instance +const logger = new Logger(); + +module.exports = logger;