const NLPProcessor = require('../nlp/processor'); const SSHManager = require('../ssh/manager'); const { TopSappManager } = require('../topsapp'); const FiberhomeManager = require('../fiberhome/manager'); const FiberhomeTL1Manager = require('../fiberhome/manager_tl1'); const { GenieACSManager } = require('../genieacs'); const config = require('../config/config'); const sites = require('../config/sites'); const logger = require('../utils/logger'); const DateProcessor = require('../utils/dateProcessor'); const cgnatParser = require('../cgn_list/parser'); class BotHandler { constructor(bot) { this.bot = bot; this.nlp = new NLPProcessor(); this.ssh = new SSHManager(); this.topsapp = new TopSappManager(); this.fiberhome = new FiberhomeManager(); this.fiberhomeTL1 = new FiberhomeTL1Manager(); this.genieacs = new GenieACSManager(); this.dateProcessor = new DateProcessor(); this.userSessions = new Map(); // Armazena sessões de usuário this.suporteSessions = new Map(); // Armazena sessões de suporte em andamento this.pendingRestarts = new Map(); // Armazena dados de reinicialização pendentes this._restartIdCounter = 0; this.activeSite = new Map(); // chatId -> siteId (cidade/provedor ativo) logger.info('BotHandler inicializado'); console.log('🎯 BotHandler inicializado'); } /** * Verifica se o usuário tem permissão (em pelo menos uma cidade/provedor) */ isAuthorized(userId) { return sites.getUserSiteIds(userId).length > 0; } /** * Cidade/provedor ativo para este chat. Pode ser null se ainda não * foi resolvido (usuário com acesso a mais de um site e que ainda não * escolheu via menu). */ getActiveSiteId(chatId) { return this.activeSite.get(chatId) || null; } /** * Site (objeto completo) ativo para este chat, com fallback seguro * para Guaraí caso ainda não haja site resolvido (mantém o * comportamento histórico do bot, que sempre assumiu Guaraí). */ getActiveSite(chatId) { return sites.getSite(this.getActiveSiteId(chatId)) || sites.getSite('guarai'); } /** * Label de localização usado pelas heurísticas existentes do TopSapp * (substitui a antiga detecção por regex no texto da mensagem). */ getActiveLocation(chatId) { return this.getActiveSite(chatId).topsappLocationLabel; } /** * Garante que o usuário tenha uma cidade/provedor ativa antes de * processar qualquer ação. Se o usuário tiver acesso a mais de um * site e ainda não tiver escolhido, exibe o menu de seleção e * retorna null (o chamador deve interromper o processamento). */ async resolveActiveSite(chatId, userId) { const siteIds = sites.getUserSiteIds(userId); if (siteIds.length === 0) { return null; } if (siteIds.length === 1) { this.activeSite.set(chatId, siteIds[0]); return siteIds[0]; } const current = this.activeSite.get(chatId); if (current && siteIds.includes(current)) { return current; } await this.sendSiteSelectionMenu(chatId, siteIds); return null; } /** * Exibe o menu de seleção de cidade/provedor */ async sendSiteSelectionMenu(chatId, siteIds) { const keyboard = siteIds.map(id => { const site = sites.getSite(id); return [{ text: `📍 ${site.name}`, callback_data: `select_site_${site.id}` }]; }); await this.bot.sendMessage(chatId, `🏙️ *Selecione a cidade/provedor*\n\n` + `Você tem acesso a mais de uma cidade. Escolha qual deseja atender agora:`, { parse_mode: 'Markdown', reply_markup: { inline_keyboard: keyboard } } ); } /** * Processa mensagens recebidas */ async handleMessage(msg) { const chatId = msg.chat.id; const userId = msg.from.id; const text = msg.text; // Log da mensagem if (config.bot.debug) { console.log(`📨 Mensagem de ${msg.from.first_name} (${userId}): ${text}`); } // Verificar autorização if (!this.isAuthorized(userId)) { await this.bot.sendMessage(chatId, `🚫 **Acesso Negado**\n\n` + `❌ Você não tem permissão para usar este bot.\n` + `🆔 Seu ID: \`${userId}\`\n\n` + `📞 Entre em contato com o administrador para solicitar acesso.`, { parse_mode: 'Markdown' } ); console.log(`🚫 Acesso negado para usuário ${msg.from.first_name} (${userId})`); return; } // Resolver cidade/provedor ativo (exibe menu se houver mais de um e ainda não escolhido) const siteId = await this.resolveActiveSite(chatId, userId); if (!siteId) { return; } try { // Verificar se há uma sessão de suporte ativa const processouSuporte = await this.processarMensagemSuporte(msg); if (processouSuporte) { return; // Mensagem foi processada pelo fluxo de suporte } // Verificar se o usuário está em uma sessão esperando input const userSession = this.userSessions.get(userId); if (userSession) { await this.handleUserSessionInput(chatId, text, userId, userSession); return; } // Processar a mensagem com NLP const intent = this.nlp.processMessage(text); if (config.bot.debug) { console.log('🧠 Intent detectado:', intent); } // Executar ação baseada na intenção await this.executeAction(chatId, intent, msg.from); } catch (error) { console.error('❌ Erro ao processar mensagem:', error); await this.bot.sendMessage(chatId, '❌ Ocorreu um erro ao processar sua mensagem. Tente novamente.'); } } /** * Executa ação baseada na intenção detectada */ async executeAction(chatId, intent, user) { const { action, target, confidence, type } = intent; const userId = user.id; // Extrair userId do objeto user // Se a confiança é muito baixa, pedir esclarecimento if (confidence < 0.3 && action) { const suggestions = this.nlp.suggestCorrection(action); let message = '🤔 Não entendi muito bem. '; if (suggestions.length > 0) { message += `Você quis dizer: ${suggestions.join(', ')}?`; } else { message += 'Digite /help para ver os comandos disponíveis.'; } await this.bot.sendMessage(chatId, message); return; } // Executar ação específica try { switch (action) { case 'start': await this.handleStart(chatId, user); break; case 'help': await this.handleHelp(chatId, userId); break; case 'cidade': await this.handleCidade(chatId, userId); break; case 'ssh': await this.handleSSH(chatId, target); break; case 'status': await this.handleStatus(chatId, target); break; case 'lista': case 'equipamentos': await this.handleListEquipments(chatId); break; case 'listar_usuarios': await this.handleListUsers(chatId, target); break; case 'consultar_usuario': console.log(`🔍 Executando handleConsultarUsuario com target: ${target}`); await this.handleConsultarUsuario(chatId, target); break; case 'admin': case 'administracao': 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; case 'meu_id': case 'meu_user_id': await this.handleMyId(chatId, userId); break; case 'ver_sinal': console.log(`🔍 Executando handleVerSinal com target: ${target}`); await this.handleVerSinal(chatId, target); break; case 'remover_onu': console.log(`🗑️ Executando handleRemoverOnu com target: ${target}`); await this.handleRemoverOnu(chatId, target); break; case 'adicionar_onu': console.log(`➕ Executando handleAdicionarOnu com target: ${target}`); await this.handleAdicionarOnu(chatId, target); break; case 'adicionar_onu_vlan': console.log(`➕ Executando handleAdicionarOnuVlan com target: ${target}, vlanId: ${intent.vlanId}`); await this.handleAdicionarOnuVlan(chatId, target, intent.vlanId); break; case 'adicionar_onu_fiberhome': console.log(`➕ Executando handleAdicionarOnuFiberhome com target: ${target}`); await this.handleAdicionarOnuFiberhome(chatId, target, userId); break; case 'remover_onu_fiberhome': console.log(`🗑️ Executando handleRemoverOnuFiberhome com target: ${target}`); await this.handleRemoverOnuFiberhome(chatId, target, userId); break; case 'buscar_cliente': console.log(`🔍 Executando handleBuscarCliente com target: ${target}`); await this.handleBuscarCliente(chatId, target); break; case 'ver_faturas': console.log(`📄 Executando handleVerFaturas com target: ${target}`); await this.handleVerFaturas(chatId, target); break; case 'gerar_boleto': console.log(`💸 Executando handleGerarBoleto com target: ${target}`); await this.handleGerarBoleto(chatId, target); break; case 'bloquear_cliente': console.log(`🔒 Executando handleBloquearCliente com target: ${target}`); await this.handleBloquearCliente(chatId, target); break; case 'liberar_cliente': console.log(`🔓 Executando handleLiberarCliente com target: ${target}`); await this.handleLiberarCliente(chatId, target); break; case 'listar_servicos': console.log(`📋 Executando handleListarServicos com target: ${target}`); await this.handleListarServicos(chatId, target); break; case 'diagnosticar': console.log(`🩺 Executando handleDiagnosticarCliente com target: ${target}`); await this.handleDiagnosticarCliente(chatId, target); break; case 'dados_cliente': case 'dados_cadastrais': console.log(`📋 Executando handleDadosClientePorBusca com target: ${target}`); await this.handleDadosClientePorBusca(chatId, target); break; case 'status_topsapp': console.log(`🏢 Executando handleStatusTopSapp`); await this.handleStatusTopSapp(chatId); break; case 'status_onu': console.log(`📡 Executando handleStatusONU com target: ${target}`); await this.handleStatusONU(chatId, target); break; case 'fiberhome_status': console.log(`🔌 Executando handleFiberhomeStatus`); await this.handleFiberhomeStatus(chatId); break; case 'ver_roteador': console.log(`📡 Executando handleVerRoteador com target: ${target}`); await this.handleVerRoteador(chatId, target); break; case 'configurar_roteador': console.log(`⚙️ Executando handleConfigurarRoteador com target: ${target}`); await this.handleConfigurarRoteador(chatId, userId, target); break; case 'abrir_suporte': case 'adicionar_suporte': case 'criar_suporte': console.log(`🎫 Iniciando processo de abertura de suporte`); await this.iniciarProcessoSuporte(chatId, user.id, target); break; case 'fechar_suporte': console.log(`🔒 Iniciando processo de fechamento de suporte`); await this.iniciarProcessoFecharSuporte(chatId, user.id, target); break; case 'mudar_ipoe': console.log(`🔗 Executando handleMudarIPoE com target: ${target}`); await this.handleMudarIPoE(chatId, target); break; case 'reiniciar_onu': console.log(`🔄 Executando handleReiniciarOnu com target: ${target}`); await this.handleReiniciarOnu(chatId, target); break; case 'ver_cgnat': console.log(`🌐 Executando handleVerCgnat com target: ${target}`); await this.handleVerCgnat(chatId, userId, target); break; case 'conectar': await this.handleConnect(chatId, target); break; case 'desconectar': await this.handleDisconnect(chatId, target); break; case 'desconectar_usuario': await this.handleDisconnectUser(chatId, target); break; case 'reiniciar': await this.handleRestart(chatId, target); break; case 'ping': await this.handlePing(chatId, target); break; default: if (!action) { await this.handleUnknown(chatId, intent.originalText || ''); } else { await this.bot.sendMessage(chatId, `❓ Ação '${action}' não implementada ainda.`); } } } catch (actionError) { console.error(`❌ Erro ao executar ação '${action}':`, actionError); await this.bot.sendMessage(chatId, `❌ Erro ao executar ação: ${actionError.message}`); } } /** * Comando /start */ async handleStart(chatId, user) { const welcomeMessage = ` 🤖 *Bem-vindo ao Bot de Ajuda!* Olá ${user.first_name}! Agora você não precisa mandar mensagem pro Rodrigo ou no grupo da ID pra ficar vendo coisas triviais: 🔐 *Conexões SSH* - Acesso remoto a equipamentos 🌐 *Status de rede* - Verificação de conectividade ⚙️ *Comandos remotos* - Execução de tarefas *Exemplos do que você pode dizer:* • "Conectar no router1" • "Verificar status do switch1" • "Reiniciar equipamento" • "Lista de equipamentos" Digite /help para ver todos os comandos disponíveis! `; await this.bot.sendMessage(chatId, welcomeMessage, { parse_mode: 'Markdown' }); } /** * Comando /cidade - permite trocar a cidade/provedor ativo */ async handleCidade(chatId, userId) { const siteIds = sites.getUserSiteIds(userId); if (siteIds.length <= 1) { const site = sites.getSite(siteIds[0]) || this.getActiveSite(chatId); await this.bot.sendMessage(chatId, `📍 Você só tem acesso a *${site.name}*.`, { parse_mode: 'Markdown' }); return; } await this.sendSiteSelectionMenu(chatId, siteIds); } /** * Comando /help */ async handleHelp(chatId, userId = null) { const siteName = this.getActiveSite(chatId).name; const helpMessage = `🤖 **BOT DE AJUDA TÉCNICA** 📍 Cidade/provedor ativo: *${siteName}* 🔧 **COMANDOS PRINCIPAIS:** • /start - Inicia o bot • /help - Esta ajuda • /equipamentos - Lista equipamentos 🏢 **GESTÃO DE CLIENTES:** • "buscar cliente João Silva" • "dados cliente 123.456.789-10" • "ver faturas cliente" • "abrir suporte" 📡 **OLT/ONU:** • "ver sinal DACM91569513" • "adicionar onu SERIAL" • "adicionar onu fiberhome SERIAL" • "remover onu SERIAL" 🌐 **GENIEACS (CPE/ROTEADOR):** • "ver roteador SERIAL" • "configurar roteador SERIAL" • "configurar roteador João Silva" 👥 **JUNIPER:** • "consultar usuario USER" • "desconectar cliente USER" • "listar usuários conectados" 🔐 **SSH:** • "ssh juniper" • "acessar concentrador" 📋 **Para ver comandos detalhados, use:**`; const keyboard = [ [ { text: '🏢 TopSapp', callback_data: 'help_topsapp' }, { text: '📡 OLT/ONU', callback_data: 'help_olt' } ], [ { text: '🌐 GenieACS', callback_data: 'help_genieacs' }, { text: '👥 Juniper', callback_data: 'help_juniper' } ], [ { text: '🔐 SSH', callback_data: 'help_ssh' }, { text: '🎫 Suporte', callback_data: 'help_suporte' } ], [ { text: '🛡️ Admin', callback_data: 'help_admin' } ] ]; if (userId && sites.getUserSiteIds(userId).length > 1) { keyboard.push([{ text: '🏙️ Trocar cidade', callback_data: 'open_site_menu' }]); } await this.bot.sendMessage(chatId, helpMessage, { parse_mode: 'Markdown', reply_markup: { inline_keyboard: keyboard } }); } /** * Handler para callbacks de help detalhado */ async handleHelpDetailCallback(callbackQuery, data) { const chatId = callbackQuery.message.chat.id; const helpType = data.replace('help_', ''); await this.bot.answerCallbackQuery(callbackQuery.id); let helpMessage = ''; let backButton = { text: '⬅️ Voltar', callback_data: 'help_main' }; switch (helpType) { case 'main': await this.handleHelp(chatId, callbackQuery.from.id); return; case 'topsapp': helpMessage = `🏢 **TOPSAPP - GESTÃO DE CLIENTES** **🔍 BUSCAR CLIENTES:** • "buscar cliente João Silva" • "buscar cliente 123.456.789-10" • "buscar cliente Guarai João" **📋 DADOS CADASTRAIS:** • "dados cliente João Silva" • "dados cadastrais 123.456.789-10" **📄 FATURAS:** • "ver faturas 123.456.789-10" • "gerar boleto fatura123" **⚙️ SERVIÇOS:** • "listar serviços 123.456.789-10" **🔒 BLOQUEIO/LIBERAÇÃO:** • "bloquear 123.456.789-10" • "liberar 123.456.789-10" **📊 STATUS:** • "status topsapp"`; break; case 'olt': helpMessage = `📡 **OLT/ONU - COMANDOS** **📶 VERIFICAR SINAL:** • "ver sinal DACM91569513" • "verificar sinal ABCD12345678" • "sinal SERIAL_NUMBER" **➕ ADICIONAR ONU:** • "adicionar onu DACM91569513" • "cadastrar ABCD12345678" • "add onu SERIAL_NUMBER" **➕ ADICIONAR ONU COM VLAN CUSTOMIZADA:** • "adicionar onu vlan 603 DACM91569513" • "add onu vlan 700 ABCD12345678" • "adicionar vlan 603 SERIAL_NUMBER" **➕ ADICIONAR ONU FIBERHOME VIA TL1:** • "adicionar onu fiberhome FHTT12345678" • "fiberhome onu FHTT12345678" • Processo interativo com: - Nome/alias do usuário - Nome da OLT - Porta PON (ex: 7-1) - Tipo da ONU (AN5506-02-B ou AN5506-01-A) - VLAN (padrão: 1002) • Configura automaticamente: - WAN Services - Login Web (admin/clicknet10) - Reinicia a ONU **🗑️ REMOVER ONU:** • "remover onu DACM91569513" • "deletar ABCD12345678" • "remover SERIAL_NUMBER" **🔄 REINICIAR ONU:** • Após verificar sinal, use botão "🔌 Reiniciar ONU" **⬆️ ATUALIZAR FIRMWARE:** • Após verificar sinal, use botão "⬆️ Atualizar ONU" **🏢 OLTs DISPONÍVEIS:** • OLT FIBERHOME (131.0.192.9) • OLT DITO (192.168.190.7) • OLT COLMEIA (192.168.190.8) • OLT CANAA (192.168.190.9) • OLT CANAA 2 (192.168.190.10) • OLT PEDRO AFONSO 1 (192.168.100.2) • OLT CANAVIEIRAS (192.168.100.4)`; break; case 'juniper': helpMessage = `👥 **JUNIPER - GERENCIAR USUÁRIOS** **� CONSULTAR USUÁRIO:** • "consultar usuario USUARIO" • "ver usuario USUARIO" • "status usuario USUARIO" **�🚫 DESCONECTAR:** • "desconectar cliente USUARIO" • "kick usuário USUARIO" • "remover cliente USUARIO" **📊 LISTAR:** • "listar usuários conectados" **🔧 STATUS:** • "verificar concentrador" • "status do juniper" **🌐 PING:** • "ping 131.0.192.254" **🔄 REINICIAR:** • "reiniciar juniper"`; break; case 'ssh': helpMessage = `🔐 **SSH E CONEXÕES** **📡 CONECTAR:** • /ssh • "ssh juniper" • "acessar concentrador" **📋 LISTAR:** • /equipamentos **🔧 EXEMPLOS:** • "conectar no concentrador" • "acessar juniper" • "ssh olt"`; break; case 'suporte': helpMessage = `🎫 **SISTEMA DE SUPORTE** **🆕 ABRIR SUPORTE:** • "abrir suporte" • "criar suporte para João Silva" • "novo chamado" • "suporte cliente 123.456.789-10" **� FECHAR SUPORTE:** • "fechar suporte" • "finalizar chamado" • "encerrar suporte João Silva" **�🔄 PROCESSO INTERATIVO:** 1️⃣ Busca do cliente (nome ou CPF) 2️⃣ Seleção do serviço 3️⃣ Escolha da categoria do problema 4️⃣ Seleção do técnico responsável 5️⃣ Agendamento de data/hora 6️⃣ Descrição detalhada 7️⃣ Confirmação e protocolo **📅 EXEMPLOS DE DATA/HORA:** • "amanhã às 14h" • "sexta-feira 09:00" • "15/08 às 16h30" • "hoje 18h" • "em 3 dias às 10h" • "segunda-feira 08:30"`; break; case 'genieacs': helpMessage = `🌐 **GENIEACS - CPE / ROTEADOR** **🔍 VER ROTEADOR:** • "ver roteador SERIAL" • "ver roteador TAG" • Exibe: SSID, senha, canais, clientes Wi-Fi, PPPoE, velocidade, IP LAN, DHCP **⚙️ CONFIGURAR ROTEADOR:** • "configurar roteador SERIAL" • "configurar roteador TAG" • "configurar roteador João Silva" (busca no TopSapp) • "configurar roteador 123.456.789-10" (CPF) • Abre menu interativo para alterar: - SSID e senha (2.4 GHz e 5 GHz) - Canal (fixo ou automático) - DNS WAN e DNS LAN - DHCP início/fim - IP LAN e máscara **📊 DIAGNÓSTICO:** • Disponível durante o diagnóstico de serviço • Exibe: taxa de download/upload, tráfego total, uptime PPPoE, estado do link físico **📌 OBSERVAÇÕES:** • Alterações com CPE online: ✅ aplicadas imediatamente • Alterações com CPE offline: ⏳ aguardam próximo contato do equipamento`; break; case 'admin': helpMessage = `🛡️ **COMANDOS ADMINISTRATIVOS** **👥 USUÁRIOS:** • "admin" • "usuarios autorizados" • "meu id" **📊 STATUS:** • "fiberhome status" • "status topsapp" **🔧 FIBERHOME ESPECÍFICOS:** • "status onu FHTT12345678" • "verificar onu FHTT12345678" • "onu status DeviceName" **🔒 SEGURANÇA:** • Bot restrito a usuários autorizados • Para solicitar acesso, forneça seu ID`; break; default: helpMessage = '❌ Seção de ajuda não encontrada.'; } await this.bot.sendMessage(chatId, helpMessage, { parse_mode: 'Markdown', reply_markup: { inline_keyboard: [[backButton]] } }); } /** * Listar equipamentos */ async handleListEquipments(chatId) { const equipments = this.ssh.listEquipments(this.getActiveSite(chatId).id); if (equipments.length === 0) { await this.bot.sendMessage(chatId, '📋 Nenhum equipamento configurado.'); return; } let message = '📋 *Equipamentos Disponíveis:*\n\n'; equipments.forEach((eq, index) => { message += `${index + 1}. *${eq.name}*\n`; message += ` 📍 ${eq.host}:${eq.port}\n`; message += ` 🔑 ID: \`${eq.id}\`\n\n`; }); message += '💡 *Como usar:* Digite "conectar " ou "ssh "'; await this.bot.sendMessage(chatId, message, { parse_mode: 'Markdown' }); } /** * Conexão SSH */ async handleSSH(chatId, target) { if (!target) { await this.bot.sendMessage(chatId, '❓ Especifique o equipamento. Exemplo: "ssh router1"'); return; } await this.bot.sendMessage(chatId, `🔄 Conectando em ${target}...`); try { const result = await this.ssh.connect(target); if (result.success) { await this.bot.sendMessage(chatId, `✅ ${result.message}`); // Oferecer opções de comandos const keyboard = { inline_keyboard: [ [ { text: '📊 Status', callback_data: `cmd_status_${result.connectionId}` }, { text: '🔄 Reiniciar', callback_data: `cmd_reboot_${result.connectionId}` } ], [ { text: '📋 Comando personalizado', callback_data: `cmd_custom_${result.connectionId}` }, { text: '❌ Desconectar', callback_data: `cmd_disconnect_${result.connectionId}` } ] ] }; await this.bot.sendMessage(chatId, '🎛️ O que você gostaria de fazer?', { reply_markup: keyboard }); } else { await this.bot.sendMessage(chatId, `❌ Falha na conexão: ${result.message}`); } } catch (error) { await this.bot.sendMessage(chatId, `❌ Erro: ${error.message}`); } } /** * Verificar status */ async handleStatus(chatId, target) { if (!target) { // Mostrar status geral const connections = this.ssh.getActiveConnections(); if (connections.length === 0) { await this.bot.sendMessage(chatId, '📊 Nenhuma conexão ativa no momento.'); return; } let message = '📊 *Conexões Ativas:*\n\n'; connections.forEach((conn, index) => { message += `${index + 1}. ${conn.equipment} (${conn.host})\n`; message += ` ⏰ Conectado: ${conn.connectedAt.toLocaleString()}\n\n`; }); await this.bot.sendMessage(chatId, message, { parse_mode: 'Markdown' }); return; } await this.bot.sendMessage(chatId, `🔍 Verificando status de ${target}...`); try { const result = await this.ssh.testConnection(target); if (result.success) { await this.bot.sendMessage(chatId, `✅ ${result.message}`); } else { await this.bot.sendMessage(chatId, `❌ ${result.message}`); } } catch (error) { await this.bot.sendMessage(chatId, `❌ Erro ao verificar status: ${error.message}`); } } /** * Conectar a um equipamento */ async handleConnect(chatId, target) { await this.handleSSH(chatId, target); } /** * Desconectar */ async handleDisconnect(chatId, target) { if (!target || target === 'todas' || target === 'all') { // Desconectar todas this.ssh.closeAllConnections(); await this.bot.sendMessage(chatId, '🔌 Todas as conexões foram encerradas.'); return; } // Implementar lógica para desconectar equipamento específico await this.bot.sendMessage(chatId, `🔌 Desconectando ${target}...`); } /** * Reiniciar equipamento */ async handleRestart(chatId, target) { if (!target) { await this.bot.sendMessage(chatId, '❓ Especifique qual equipamento reiniciar.'); return; } await this.bot.sendMessage(chatId, `⚠️ Reiniciar ${target}? Esta ação pode causar indisponibilidade.`, { reply_markup: { inline_keyboard: [ [ { text: '✅ Confirmar', callback_data: `confirm_restart_${target}` }, { text: '❌ Cancelar', callback_data: 'cancel_action' } ] ] } }); } /** * Ping */ async handlePing(chatId, target) { if (!target) { await this.bot.sendMessage(chatId, '❓ Especifique o IP ou hostname para fazer ping.'); return; } await this.bot.sendMessage(chatId, `🏓 Fazendo ping para ${target}...`); // Implementar ping usando comandos do sistema // Por enquanto, simular resposta setTimeout(async () => { await this.bot.sendMessage(chatId, `🏓 Ping para ${target}: 12ms - Conexão OK`); }, 2000); } /** * Desconectar usuário específico no concentrador Juniper */ async handleDisconnectUser(chatId, username) { if (!username) { await this.bot.sendMessage(chatId, '❓ Especifique o usuário a ser desconectado. Exemplo: "desconectar cliente joao123"'); return; } await this.bot.sendMessage(chatId, `🔄 Desconectando usuário ${username} do concentrador...`); try { const juniperId = this.getActiveSite(chatId).juniperEquipmentId; const result = await this.ssh.disconnectJuniperUser(juniperId, username); if (result.success) { await this.bot.sendMessage(chatId, `✅ ${result.message}`); if (result.output && result.output.trim()) { const outputMessage = `📋 *Saída do comando:*\n\`\`\`\n${result.output}\n\`\`\``; await this.bot.sendMessage(chatId, outputMessage, { parse_mode: 'Markdown' }); } } else { await this.bot.sendMessage(chatId, `❌ ${result.message}`); if (result.error && result.error.trim()) { const errorMessage = `⚠️ *Detalhes do erro:*\n\`\`\`\n${result.error}\n\`\`\``; await this.bot.sendMessage(chatId, errorMessage, { parse_mode: 'Markdown' }); } } } catch (error) { console.error('Erro ao desconectar usuário:', error); await this.bot.sendMessage(chatId, `❌ Erro interno: ${error.message}`); } } /** * Listar usuários conectados no concentrador */ async handleListUsers(chatId) { await this.bot.sendMessage(chatId, '🔍 Listando usuários conectados no concentrador...'); try { const juniperId = this.getActiveSite(chatId).juniperEquipmentId; const result = await this.ssh.listJuniperUsers(juniperId); if (result.success) { let message = '📋 *Usuários Conectados:*\n\n'; if (result.output && result.output.trim()) { // Processar saída para extrair informações úteis const lines = result.output.split('\n'); let userCount = 0; for (const line of lines) { if (line.trim() && !line.includes('Total') && !line.includes('---')) { userCount++; message += `${userCount}. \`${line.trim()}\`\n`; } } if (userCount === 0) { message += '🟢 Nenhum usuário conectado no momento.\n'; } else { message += `\n👥 Total: ${userCount} usuário(s) conectado(s)`; } } else { message += '🟢 Nenhum usuário conectado no momento.'; } await this.bot.sendMessage(chatId, message, { parse_mode: 'Markdown' }); // Oferecer opções para desconectar usuários se houver algum conectado if (result.output && result.output.trim() && !result.output.includes('Total: 0')) { const keyboard = { inline_keyboard: [ [ { text: '🔄 Atualizar lista', callback_data: 'refresh_users' }, { text: '❌ Desconectar usuário', callback_data: 'disconnect_user_prompt' } ] ] }; await this.bot.sendMessage(chatId, '🎛️ O que você gostaria de fazer?', { reply_markup: keyboard }); } } else { await this.bot.sendMessage(chatId, `❌ ${result.message}`); } } catch (error) { console.error('Erro ao listar usuários:', error); await this.bot.sendMessage(chatId, `❌ Erro interno: ${error.message}`); } } /** * Consultar status de um assinante específico no concentrador Juniper */ async handleConsultarUsuario(chatId, username) { if (!username) { await this.bot.sendMessage(chatId, '❓ Especifique o login do usuário. Exemplo: "consultar usuario joao@provedor"'); return; } await this.bot.sendMessage(chatId, `🔍 Consultando usuário *${username}* no concentrador...`, { parse_mode: 'Markdown' }); try { const juniperId = this.getActiveSite(chatId).juniperEquipmentId; const result = await this.ssh.getJuniperSubscriberInfo(juniperId, username); if (!result.success) { await this.bot.sendMessage(chatId, `❌ ${result.message}`); return; } if (!result.connected) { await this.bot.sendMessage(chatId, `🔴 *${username}* não está conectado no concentrador.`, { parse_mode: 'Markdown' }); return; } const message = `✅ *Usuário conectado!*\n\n` + `👤 *Login:* \`${result.username}\`\n` + `🌐 *Endereço IP:* \`${result.ip || 'N/A'}\`\n` + `🔌 *Interface PPPoE:* \`${result.interface || 'N/A'}\`\n` + `\n📋 *Linha bruta:*\n\`\`\`\n${result.rawLine}\n\`\`\``; await this.bot.sendMessage(chatId, message, { parse_mode: 'Markdown' }); } catch (error) { console.error('Erro ao consultar usuário:', error); await this.bot.sendMessage(chatId, `❌ Erro interno: ${error.message}`); } } /** * Diagnóstico completo de um cliente: TopSapp + Juniper PPPoE + ONU */ async handleDiagnosticarCliente(chatId, query) { if (!query) { await this.bot.sendMessage(chatId, `🩺 *Diagnóstico de Cliente*\n\n` + `💡 *Como usar:*\n` + `• "diagnosticar João da Silva"\n` + `• "diagnosticar 123.456.789-10"\n\n` + `O diagnóstico verifica: status no TopSapp, sessão PPPoE no concentrador e status da ONU.`, { parse_mode: 'Markdown' } ); return; } try { await this.bot.sendMessage(chatId, '🔍 Buscando cliente...'); let location = this.getActiveLocation(chatId); let searchQuery = query; if (/guarai|colmeia/i.test(query)) { location = 'Guarai/Colmeia'; searchQuery = query.replace(/(guarai|colmeia)/gi, '').trim(); } else if (/pedro afonso/i.test(query)) { location = 'Pedro Afonso'; searchQuery = query.replace(/pedro afonso/gi, '').trim(); } const resultado = await this.topsapp.buscarCliente(searchQuery, location); if (!resultado.success) { await this.bot.sendMessage(chatId, `❌ Erro na busca: ${resultado.error}`); return; } if (!resultado.clientes || resultado.clientes.length === 0) { await this.bot.sendMessage(chatId, `🔍 Nenhum cliente encontrado para: "${searchQuery}"`); return; } const instancia = resultado.instancia || location; if (resultado.clientes.length === 1) { await this._mostrarServicosParaDiagnostico(chatId, resultado.clientes[0].id, instancia); return; } // Múltiplos clientes → exibir teclado de seleção let message = `🔍 *${resultado.clientes.length} clientes encontrados*\n\n`; const keyboard = []; resultado.clientes.slice(0, 8).forEach((cliente, index) => { message += `*${index + 1}.* ${cliente.nome}`; if (cliente.cpf) message += ` — CPF: \`${cliente.cpf}\``; message += `\n`; keyboard.push([{ text: `🩺 Diagnosticar: ${cliente.nome.substring(0, 30)}`, callback_data: `diag_cliente_${cliente.id}_${instancia}` }]); }); await this.bot.sendMessage(chatId, message, { parse_mode: 'Markdown', reply_markup: { inline_keyboard: keyboard } }); } catch (error) { console.error('❌ Erro em handleDiagnosticarCliente:', error); await this.bot.sendMessage(chatId, `❌ Erro: ${error.message}`); } } async _mostrarServicosParaDiagnostico(chatId, idCliente, location) { const servicosResult = await this.topsapp.obterServicosCliente(idCliente, location); if (!servicosResult.success) { await this.bot.sendMessage(chatId, `❌ Erro ao buscar serviços: ${servicosResult.error}`); return; } const servicos = (servicosResult.servicos || []).filter(s => { const st = (s.id_status || s.statusNumerico); return st === 1 || st === '1' || String(s.status).toLowerCase() === 'liberado' || String(s.status).toLowerCase() === 'ativo'; }); if (servicos.length === 0) { await this.bot.sendMessage(chatId, `⚠️ Cliente sem serviços ativos para diagnosticar.`); return; } if (servicos.length === 1) { await this.runDiagnosticoServico(chatId, idCliente, servicos[0].id_servico || servicos[0].id, location, servicos[0]); return; } let message = `🩺 *Selecione o serviço para diagnosticar:*\n\n`; const keyboard = []; servicos.forEach((s, i) => { const nome = s.descricao || s.nome || `Serviço ${i + 1}`; const login = s.usuario || s.pppoe_login || s.login || ''; message += `*${i + 1}.* ${nome}${login ? ` — \`${login}\`` : ''}\n`; keyboard.push([{ text: `🔍 ${nome.substring(0, 35)}`, callback_data: `diag_servico_${idCliente}_${s.id_servico || s.id}_${location}` }]); }); await this.bot.sendMessage(chatId, message, { parse_mode: 'Markdown', reply_markup: { inline_keyboard: keyboard } }); } async handleDiagClienteCallback(callbackQuery, data) { const chatId = callbackQuery.message.chat.id; const parts = data.replace('diag_cliente_', '').split('_'); const idCliente = parts[0]; const location = parts.slice(1).join('_'); await this.bot.answerCallbackQuery(callbackQuery.id); await this._mostrarServicosParaDiagnostico(chatId, idCliente, location); } async handleDiagServicoCallback(callbackQuery, data) { const chatId = callbackQuery.message.chat.id; const parts = data.replace('diag_servico_', '').split('_'); const idCliente = parts[0]; const idServico = parts[1]; const location = parts.slice(2).join('_'); await this.bot.answerCallbackQuery(callbackQuery.id); await this.runDiagnosticoServico(chatId, idCliente, idServico, location, null); } async runDiagnosticoServico(chatId, idCliente, idServico, location, servicoPreCarregado) { try { await this.bot.sendMessage(chatId, '⏳ Executando diagnóstico completo...'); // Buscar serviços caso não tenhamos o objeto do serviço let servico = servicoPreCarregado; if (!servico) { const servicosResult = await this.topsapp.obterServicosCliente(idCliente, location); if (!servicosResult.success) { await this.bot.sendMessage(chatId, `❌ Erro ao buscar serviços: ${servicosResult.error}`); return; } const idNum = parseInt(idServico, 10); servico = (servicosResult.servicos || []).find(s => (s.id_servico || s.id) === idNum || String(s.id_servico || s.id) === String(idServico) ); } if (!servico) { await this.bot.sendMessage(chatId, `❌ Serviço #${idServico} não encontrado.`); return; } const pppoeLogin = servico.usuario || servico.pppoe_login || servico.login || null; const serial = servico.serial || servico.comodato_serial || servico.mac || null; const circuitId = servico.dhcp_agent_circuit_id || null; const statusServico = this.topsapp.getStatusServicoDetalhado ? this.topsapp.getStatusServicoDetalhado(servico.id_status) : (servico.status || 'N/A'); const nomePlano = servico.descricao || servico.nome || 'N/A'; const endereco = servico.instalacao_endereco || servico.end_rua || null; const cidade = servico.instalacao_cidade || null; // Campo base/AP usado para rotear para a OLT correta const baseInfo = servico.base || (servico.id_ap ? `AP ${servico.id_ap}` : null); // Executar consultas em paralelo: Juniper + ONU + GenieACS (por TAG/login) // No concentrador Juniper usa-se só o usuário (antes do @) const activeSite = this.getActiveSite(chatId); const juniperId = activeSite.juniperEquipmentId; const pppoeUser = pppoeLogin ? pppoeLogin.split('@')[0] : null; let [juniperResult, onuResult, genieResult] = await Promise.all([ pppoeUser ? this.ssh.getJuniperSubscriberInfo(juniperId, pppoeUser).catch(e => ({ success: false, error: e.message })) : Promise.resolve({ success: false, skipped: true }), serial ? this._diagnosticarONU(serial, baseInfo, activeSite.id).catch(e => ({ success: false, error: e.message })) : Promise.resolve({ success: false, skipped: true }), pppoeLogin && this.genieacs.isReady() ? this.genieacs.buscarRoteador(pppoeLogin).catch(() => ({ success: false })) : Promise.resolve({ success: false, skipped: true }) ]); // Fallback: se PPPoE não encontrou sessão ativa e há circuit-id (IPoE), buscar pelo circuit-id if (circuitId && (!juniperResult.connected)) { try { const circuitResult = await this.ssh.getJuniperSubscriberInfo(juniperId, circuitId); if (circuitResult.success && circuitResult.connected) { juniperResult = { ...circuitResult, viaCircuitId: true }; } } catch (e) { logger.warn(`Fallback circuit-id Juniper falhou: ${e.message}`); } } // Sincronização TAG GenieACS ↔ PPPoE TopSapp (fire-and-forget, silencioso) // Garante que o login PPPoE do cliente fica registrado como TAG no GenieACS, // permitindo buscas futuras por "ver roteador ". if (serial && pppoeLogin && this.genieacs.isReady()) { this.genieacs.sincronizarTagPPPoE(serial, pppoeLogin) .then(r => { if (r.success && !r.already) { logger.info(`[GenieACS] TAG "${r.tag}" sincronizada em ${r.deviceId}`); console.log(`🏷️ [GenieACS] TAG "${r.tag}" adicionada ao device ${r.deviceId}`); } }) .catch(e => logger.warn(`[GenieACS] Falha ao sincronizar TAG: ${e.message}`)); } // Montar relatório let msg = `🩺 *Diagnóstico do Cliente*\n`; msg += `━━━━━━━━━━━━━━━━━━━━━\n\n`; // Bloco 1: TopSapp msg += `📋 *Serviço TopSapp*\n`; msg += ` 📦 Plano: ${nomePlano}\n`; msg += ` 🔌 Status: ${statusServico}\n`; if (pppoeLogin) msg += ` 👤 Login PPPoE: \`${pppoeLogin}\`\n`; if (servico.dhcp_username) msg += ` 👤 DHCP Username: \`${servico.dhcp_username}\`\n`; if (circuitId) msg += ` 🔗 Circuit\-ID: \`${circuitId}\`\n`; if (endereco) msg += ` 📍 Endereço: ${endereco}${cidade ? `, ${cidade}` : ''}\n`; msg += '\n'; // Bloco 2: Concentrador Juniper msg += `🌐 *Concentrador Juniper*\n`; if (juniperResult.skipped && !circuitId) { msg += ` ⚫ Login PPPoE/Circuit\-ID não informado no cadastro\n`; } else if (!juniperResult.success) { msg += ` ⚠️ Erro ao consultar: ${juniperResult.error || 'falha na consulta'}\n`; } else if (juniperResult.connected) { const modoAuth = juniperResult.viaCircuitId ? 'IPoE/Circuit\-ID' : 'PPPoE'; msg += ` 🟢 *Conectado* (${modoAuth})\n`; msg += ` 🌐 IP: \`${juniperResult.ip || 'N/A'}\`\n`; msg += ` 🔌 Interface: \`${juniperResult.interface || 'N/A'}\`\n`; } else { msg += ` 🔴 *Desconectado* (sem sessão ativa)\n`; } msg += '\n'; // Bloco 3: ONU msg += `📡 *ONU / ONT*\n`; if (onuResult.skipped) { msg += ` ⚫ Serial não informado no cadastro\n`; } else if (!onuResult.success) { msg += ` ❌ ${onuResult.error || 'ONU não encontrada'}\n`; if (serial) msg += ` 📋 Serial buscado: \`${serial}\`\n`; } else { const rxLabel = onuResult.rxPower || 'N/A'; const rxNum = parseFloat(rxLabel); const rxEmoji = isNaN(rxNum) ? '⚫' : rxNum >= -27 ? '🟢' : rxNum >= -30 ? '🟡' : '🔴'; msg += ` 📋 Serial: \`${serial}\`\n`; msg += ` 🏢 OLT: ${onuResult.oltName || onuResult.oltType || 'N/A'}\n`; if (onuResult.slot || onuResult.pon) { msg += ` 📍 Slot/PON: ${onuResult.slot || '?'}/${onuResult.pon || '?'}\n`; } msg += ` 🔌 Estado: ${onuResult.online ? '🟢 Online' : '🔴 Offline'}\n`; if (onuResult.uptime) { msg += ` ⏱️ Uptime: ${onuResult.uptime}\n`; } msg += ` ${rxEmoji} Rx Power: *${rxLabel}*\n`; if (onuResult.portaEthernet) { const ethOk = /forward|up|active|1gbit|100mbit/i.test(onuResult.portaEthernet); msg += ` ${ethOk ? '🟢' : '🔴'} Porta Ethernet: ${onuResult.portaEthernet}\n`; } } await this.bot.sendMessage(chatId, msg, { parse_mode: 'Markdown' }); // Bloco 4: Roteador/CPE via GenieACS (exibido apenas se encontrado pela TAG) if (genieResult && genieResult.success) { const esc = (v) => { if (v === null || v === undefined || v === '') return 'N/A'; return String(v).replace(/[_*`\[]/g, '\\$&'); }; const d = genieResult.device; const tagLabel = d.tags && d.tags.length > 0 ? d.tags.map(esc).join(', ') : null; let msgCPE = `📡 *Roteador / CPE (GenieACS)*\n`; msgCPE += ` 🖥️ Nome: ${esc(d.productClass)}\n`; msgCPE += ` 🏭 Fabricante: ${esc(d.manufacturer)}\n`; msgCPE += ` 🔧 SW: ${esc(d.softwareVersion)}\n`; if (tagLabel) msgCPE += ` 🏷️ TAG: \`${tagLabel}\`\n`; if (d.ssid24) msgCPE += ` 📶 SSID 2.4GHz: ${esc(d.ssid24)} (Canal ${esc(d.channel24)}, Auto: ${esc(d.autoChannel24)})\n`; if (d.senha24) msgCPE += ` 🔑 Senha 2.4GHz: \`${esc(d.senha24)}\`\n`; if (d.clients24 !== null && d.clients24 !== undefined) msgCPE += ` 👥 Clientes 2.4GHz: ${esc(d.clients24)}\n`; if (d.ssid5) msgCPE += ` 📶 SSID 5GHz: ${esc(d.ssid5)} (Canal ${esc(d.channel5)}, Auto: ${esc(d.autoChannel5)})\n`; if (d.senha5) msgCPE += ` 🔑 Senha 5GHz: \`${esc(d.senha5)}\`\n`; if (d.clients5 !== null && d.clients5 !== undefined) msgCPE += ` 👥 Clientes 5GHz: ${esc(d.clients5)}\n`; // PPPoE WAN if (d.pppoeStatus || d.pppoeIP) { msgCPE += `\n 🌐 *PPPoE WAN*\n`; if (d.pppoeStatus) msgCPE += ` ├ Status: ${esc(d.pppoeStatus)}\n`; if (d.pppoeIP) msgCPE += ` ├ IP: \`${esc(d.pppoeIP)}\`\n`; if (d.pppoeUptime) msgCPE += ` ├ Uptime: ${esc(d.pppoeUptime)}\n`; if (d.gatewayWAN) msgCPE += ` ├ Gateway: ${esc(d.gatewayWAN)}\n`; if (d.dnsWAN) msgCPE += ` ├ DNS WAN: ${esc(d.dnsWAN)}\n`; if (d.linkFisicoWAN) msgCPE += ` ├ Link Físico: ${esc(d.linkFisicoWAN)}\n`; if (d.trafegoRecebido) msgCPE += ` ├ Recebido: ${esc(d.trafegoRecebido)}\n`; if (d.trafegoEnviado) msgCPE += ` ├ Enviado: ${esc(d.trafegoEnviado)}\n`; if (d.taxaDownload) msgCPE += ` ├ ↓ Download: ${esc(d.taxaDownload)}\n`; if (d.taxaUpload) msgCPE += ` ├ ↑ Upload: ${esc(d.taxaUpload)}\n`; if (d.pppoeLastError && d.pppoeLastError !== 'ERROR_NONE') msgCPE += ` └ Último Erro: ${esc(d.pppoeLastError)}\n`; } // DHCP LAN if (d.dhcpInicio || d.dhcpFim || d.dnsLAN || d.ipLAN || d.mascaraLAN) { msgCPE += `\n 🏠 *LAN/DHCP*\n`; if (d.ipLAN) msgCPE += ` ├ IP LAN: \`${esc(d.ipLAN)}\`\n`; if (d.mascaraLAN) msgCPE += ` ├ Máscara: ${esc(d.mascaraLAN)}\n`; if (d.dhcpInicio) msgCPE += ` ├ DHCP Início: ${esc(d.dhcpInicio)}\n`; if (d.dhcpFim) msgCPE += ` ├ DHCP Fim: ${esc(d.dhcpFim)}\n`; if (d.dnsLAN) msgCPE += ` └ DNS LAN: ${esc(d.dnsLAN)}\n`; } await this.bot.sendMessage(chatId, msgCPE, { parse_mode: 'Markdown' }); } } catch (error) { console.error('❌ Erro em runDiagnosticoServico:', error); await this.bot.sendMessage(chatId, `❌ Erro no diagnóstico: ${error.message}`); } } /** * Mapeia o campo base/AP do TopSapp para o ID da OLT Datacom (ou 'fiberhome') * As bases DITO/COLMEIA/CANAA só existem em Guaraí; em outro site, ignora a dica. */ _resolverOLTporBase(baseInfo, siteId = 'guarai') { if (!baseInfo) return null; const b = String(baseInfo).toUpperCase(); if (/INTERNET\s+FIBRA\s+OPTICA/i.test(b)) return 'fiberhome'; if (siteId !== 'guarai') return null; if (/COLMEIA/i.test(b)) return 'olt_colmeia'; if (/DITO/i.test(b)) return 'olt_dito'; if (/CANAA\s*2/i.test(b)) return 'olt_canaa2'; if (/CANAA/i.test(b)) return 'olt_canaa'; return null; } /** * Diagnóstico de ONU: detecta tipo (Datacom/FiberHome) e retorna dados estruturados * @param {string} serial - Serial da ONU * @param {string|null} baseInfo - Campo base/AP do TopSapp para rotear para OLT correta * @param {string} siteId - Cidade/provedor ativo, usado para restringir a busca */ async _diagnosticarONU(serial, baseInfo, siteId = 'guarai') { const serialUpper = serial.toUpperCase(); const oltHint = this._resolverOLTporBase(baseInfo, siteId); // Usar base description para decidir se é FiberHome if (oltHint === 'fiberhome' || (!serialUpper.startsWith('DACM') && (serialUpper.startsWith('FHTT') || serialUpper.startsWith('FHGT')))) { return await this._diagnosticarONUFiberhome(serial); } return await this._diagnosticarONUDatacom(serial, oltHint, siteId); } async _diagnosticarONUDatacom(serial, oltIdHint, siteId = 'guarai') { let found = null; // 1. Tentar na OLT indicada pelo campo base/AP do cadastro if (oltIdHint && this.ssh.equipamentos.has(oltIdHint)) { const r = await this.ssh.findOnuBySerial(oltIdHint, serial).catch(() => null); if (r && r.success) { const olt = this.ssh.equipamentos.get(oltIdHint); found = { ...r, oltId: oltIdHint, oltName: olt.name }; } } // 2. Fallback: buscar em todas as OLTs Datacom do site ativo if (!found) { const olts = this.ssh.listOLTs(siteId); if (olts.length === 0) { return { success: false, error: 'Nenhuma OLT Datacom configurada' }; } const searches = olts.map(olt => this.ssh.findOnuBySerial(olt.id, serial) .then(r => ({ ...r, oltId: olt.id, oltName: olt.name })) .catch(() => ({ success: false })) ); const results = await Promise.allSettled(searches); for (const r of results) { if (r.status === 'fulfilled' && r.value.success) { found = r.value; break; } } } if (!found) { return { success: false, error: `ONU ${serial} não encontrada em nenhuma OLT Datacom` }; } const { oltId, oltName, onuInfo } = found; const { slot, onuId } = onuInfo; // Buscar sinal e porta ethernet em paralelo const [signalResult, ethResult] = await Promise.all([ this.ssh.checkOnuSignal(oltId, slot, onuId).catch(e => ({ success: false, error: e.message })), this.ssh.checkOnuEthernetDatacom(oltId, slot, onuId).catch(e => ({ success: false })) ]); // Parsear sinal const rawSignal = signalResult.output || ''; const stateMatch = rawSignal.match(/Operational\s+state\s*:\s*(\S+)/i); const rxMatch = rawSignal.match(/Rx\s+Optical\s+Power\s*\[dBm\]\s*:\s*([-\d.]+)/i); const operationalState = stateMatch ? stateMatch[1] : null; const rxPower = rxMatch ? `${rxMatch[1]} dBm` : null; const online = operationalState ? /up|active|online/i.test(operationalState) : false; // Parsear ethernet let portaEthernet = null; if (ethResult.success) { const ethRaw = ethResult.rawOutput || ''; const ethMatch = ethRaw.match(/\b(Forwarding|Down|Up|Blocking)\b[^\n]*([\d]+(?:Gbit|Mbit)[^\n]*|N\/A)?/i); if (ethMatch) { portaEthernet = ethMatch[0].trim().replace(/\s+/g, ' '); } } return { success: true, type: 'datacom', oltName, slot, pon: slot ? slot.split('/')[2] || slot : null, onuId, online, rxPower, operationalState, portaEthernet }; } async _diagnosticarONUFiberhome(serial) { const resultado = await this.fiberhome.obterStatusONU(serial); if (!resultado.success) { return { success: false, error: resultado.error || 'ONU não encontrada na FiberHome' }; } // Preferir porta com dados reais de velocidade; cair para primeira disponível const portas = resultado.portasEthernet || []; const portaComDados = portas.find(p => p.status !== 'unknown' && p.speed); const portaAtiva = portas.find(p => p.status === 'active'); const portaBase = portaComDados || portaAtiva || (portas.length > 0 ? portas[0] : null); let portaEthernetStr = null; if (portaBase && portaBase.status !== 'unknown') { const statusLabel = portaBase.status === 'active' ? 'up' : 'down'; portaEthernetStr = portaBase.speed ? `${statusLabel} — ${portaBase.speed}` : statusLabel; } // rxPower: retornar null se for 'N/A' para que a UI mostre 'N/A' via fallback const rxPower = resultado.rxPower && resultado.rxPower !== 'N/A' ? resultado.rxPower : null; return { success: true, type: 'fiberhome', oltName: 'FiberHome AN5516', slot: resultado.slot, pon: resultado.pon, onuId: resultado.onuId, online: resultado.online, uptime: resultado.uptime || null, rxPower, txPower: resultado.txPower !== 'N/A' ? resultado.txPower : null, portaEthernet: portaEthernetStr }; } /** * Verificar sinal de ONU */ async handleVerSinal(chatId, serialNumber) { console.log(`🎯 handleVerSinal chamado - chatId: ${chatId}, serialNumber: ${serialNumber}`); if (!serialNumber) { console.log('❌ Serial number não fornecido'); await this.bot.sendMessage(chatId, '❓ Especifique o serial number da ONU. Exemplo: "ver sinal DACM91569513"'); return; } // Obter lista de OLTs disponíveis no site ativo const activeSiteId = this.getActiveSite(chatId).id; const olts = this.ssh.listOLTs(activeSiteId); console.log(`📋 OLTs Datacom encontradas: ${olts.length}`); // Criar menu de seleção de OLT const keyboard = { inline_keyboard: [] }; // Adicionar OLT Fiberhome como primeira opção (existe apenas em Guaraí) if (activeSiteId === 'guarai') { keyboard.inline_keyboard.push([ { text: '🔶 OLT FIBERHOME', callback_data: `check_signal_fiberhome_${serialNumber}` } ]); } // Adicionar separador visual if (olts.length > 0) { keyboard.inline_keyboard.push([ { text: '➖ OLTs DATACOM ➖', callback_data: 'separator' } ]); // Adicionar botões para cada OLT Datacom (2 por linha) for (let i = 0; i < olts.length; i += 2) { const row = []; row.push({ text: olts[i].name, callback_data: `check_signal_${olts[i].id}_${serialNumber}` }); if (i + 1 < olts.length) { row.push({ text: olts[i + 1].name, callback_data: `check_signal_${olts[i + 1].id}_${serialNumber}` }); } keyboard.inline_keyboard.push(row); } } // Adicionar botão de cancelar keyboard.inline_keyboard.push([ { text: '❌ Cancelar', callback_data: 'cancel_action' } ]); console.log(`📤 Enviando mensagem com Fiberhome + ${olts.length} OLTs Datacom para seleção`); await this.bot.sendMessage(chatId, `🔍 *Verificar sinal da ONU*\nSerial: \`${serialNumber}\`\n\nSelecione onde deseja pesquisar:`, { parse_mode: 'Markdown', reply_markup: keyboard }); console.log(`✅ Mensagem de seleção de OLT enviada com sucesso`); } /** * Executa verificação de sinal em uma OLT específica */ async handleCheckSignalOLT(chatId, oltId, serialNumber) { const olt = this.ssh.equipamentos.get(oltId); if (!olt) { await this.bot.sendMessage(chatId, `❌ OLT ${oltId} não encontrada.`); return; } await this.bot.sendMessage(chatId, `🔍 Procurando ONU ${serialNumber} na ${olt.name}...`); try { const result = await this.ssh.checkOnuSignalBySerial(oltId, serialNumber); if (result.success) { // Escapar caracteres especiais do Markdown const oltNameEscaped = olt.name.replace(/_/g, '\\_'); const serialEscaped = serialNumber.replace(/_/g, '\\_'); let message = `📡 *Sinal da ONU ${serialEscaped}*\n\n`; message += `🏢 *OLT:* ${oltNameEscaped} (${olt.host})\n`; message += `📍 *Localização:* Slot ${result.slot}, ONU ${result.onuId}\n\n`; message += `📊 *Informações do Sinal:*\n`; message += `\`\`\`\n${result.signalOutput}\n\`\`\``; await this.bot.sendMessage(chatId, message, { parse_mode: 'Markdown' }); // Oferecer opções adicionais const keyboard = { inline_keyboard: [ [ { text: '🔄 Atualizar sinal', callback_data: `check_signal_${oltId}_${serialNumber}` }, { text: '📋 Ver configuração', callback_data: `show_config_${oltId}_${result.slot}_${result.onuId}` } ], [ { text: '🔌 Reiniciar ONU', callback_data: `reset_onu_${oltId}_${result.slot}_${result.onuId}` }, { text: '⬆️ Atualizar ONU', callback_data: `update_onu_${oltId}_${result.slot}_${result.onuId}` } ], [ { text: '🔍 Pesquisar em outra OLT', callback_data: `ver_sinal_${serialNumber}` } ] ] }; await this.bot.sendMessage(chatId, '🎛️ O que você gostaria de fazer?', { reply_markup: keyboard }); } else { await this.bot.sendMessage(chatId, `❌ ${result.message}`); // Oferecer pesquisar em outras OLTs const keyboard = { inline_keyboard: [ [ { text: '🔍 Pesquisar em outra OLT', callback_data: `ver_sinal_${serialNumber}` }, { text: '❌ Cancelar', callback_data: 'cancel_action' } ] ] }; await this.bot.sendMessage(chatId, 'ONU não encontrada nesta OLT. Deseja pesquisar em outra?', { reply_markup: keyboard }); } } catch (error) { console.error('Erro ao verificar sinal:', error); await this.bot.sendMessage(chatId, `❌ Erro interno: ${error.message}`); } } /** * Comando para remover ONU */ async handleRemoverOnu(chatId, serialNumber) { if (!serialNumber) { await this.bot.sendMessage(chatId, '❌ Por favor, informe o serial da ONU que deseja remover.\n\nExemplo: `remover FHTT12345678`', { parse_mode: 'Markdown' }); return; } // Perguntar em qual OLT buscar (apenas OLTs do site ativo) const oltOptions = this.ssh.listOLTs(this.getActiveSite(chatId).id); if (oltOptions.length === 0) { await this.bot.sendMessage(chatId, '❌ Nenhuma OLT disponível configurada.'); return; } let keyboard = { inline_keyboard: oltOptions.map(olt => [ { text: `🏢 ${olt.name}`, callback_data: `remove_onu_${olt.id}_${serialNumber}` } ]) }; // Adicionar opção de cancelar keyboard.inline_keyboard.push([ { text: '❌ Cancelar', callback_data: 'cancel_action' } ]); // Escapar caracteres especiais do serial const serialEscaped = serialNumber.replace(/_/g, '\\_'); await this.bot.sendMessage(chatId, `🗑️ *Remover ONU ${serialEscaped}*\n\n` + '🏢 Selecione a OLT onde a ONU está configurada:', { reply_markup: keyboard, parse_mode: 'Markdown' } ); } /** * Comando para adicionar ONU */ async handleAdicionarOnu(chatId, serialNumber) { if (!serialNumber) { await this.bot.sendMessage(chatId, '❌ Por favor, informe o serial da ONU que deseja adicionar.\n\nExemplo: `adicionar onu FHTT12345678`', { parse_mode: 'Markdown' }); return; } // Perguntar em qual OLT adicionar (apenas OLTs do site ativo) const oltOptions = this.ssh.listOLTs(this.getActiveSite(chatId).id); if (oltOptions.length === 0) { await this.bot.sendMessage(chatId, '❌ Nenhuma OLT disponível configurada.'); return; } let keyboard = { inline_keyboard: oltOptions.map(olt => [ { text: `🏢 ${olt.name}`, callback_data: `add_onu_${olt.id}_${serialNumber}` } ]) }; // Adicionar opção de cancelar keyboard.inline_keyboard.push([ { text: '❌ Cancelar', callback_data: 'cancel_action' } ]); // Escapar caracteres especiais const serialEscaped = serialNumber.replace(/_/g, '\\_'); await this.bot.sendMessage(chatId, `➕ *Adicionar ONU ${serialEscaped}*\n\n` + '🏢 Selecione a OLT onde deseja adicionar a ONU:', { reply_markup: keyboard, parse_mode: 'Markdown' } ); } /** * Comando para adicionar ONU com VLAN customizada */ async handleAdicionarOnuVlan(chatId, serialNumber, vlanId) { if (!serialNumber) { await this.bot.sendMessage(chatId, '❌ Por favor, informe o serial da ONU.\n\nExemplo: `adicionar onu vlan 603 FHTT12345678`', { parse_mode: 'Markdown' }); return; } if (!vlanId || isNaN(vlanId) || vlanId < 1 || vlanId > 4094) { await this.bot.sendMessage(chatId, '❌ VLAN ID inválida. Deve ser um número entre 1 e 4094.\n\nExemplo: `adicionar onu vlan 603 FHTT12345678`', { parse_mode: 'Markdown' }); return; } // Perguntar em qual OLT adicionar (apenas OLTs do site ativo) const oltOptions = this.ssh.listOLTs(this.getActiveSite(chatId).id); if (oltOptions.length === 0) { await this.bot.sendMessage(chatId, '❌ Nenhuma OLT disponível configurada.'); return; } let keyboard = { inline_keyboard: oltOptions.map(olt => [ { text: `🏢 ${olt.name}`, callback_data: `add_onu_vlan_${olt.id}_${vlanId}_${serialNumber}` } ]) }; // Adicionar opção de cancelar keyboard.inline_keyboard.push([ { text: '❌ Cancelar', callback_data: 'cancel_action' } ]); // Escapar caracteres especiais const serialEscaped = serialNumber.replace(/_/g, '\\_'); await this.bot.sendMessage(chatId, `➕ *Adicionar ONU com VLAN Customizada*\n\n` + `📱 *Serial:* ${serialEscaped}\n` + `🏷️ *VLAN:* ${vlanId}\n\n` + '🏢 Selecione a OLT onde deseja adicionar a ONU:', { reply_markup: keyboard, parse_mode: 'Markdown' } ); } /** * Comando para adicionar ONU Fiberhome via TL1 */ async handleAdicionarOnuFiberhome(chatId, serialNumber, userId) { if (!serialNumber) { await this.bot.sendMessage(chatId, '❌ Por favor, informe o serial da ONU Fiberhome.\n\n' + 'Exemplo: `adicionar onu fiberhome FHTT12345678`', { parse_mode: 'Markdown' } ); return; } console.log(`📝 Criando sessão para usuário ${userId}, serial: ${serialNumber}`); // Criar sessão para coletar informações this.userSessions.set(userId, { action: 'adicionar_onu_fiberhome_step1', serial: serialNumber, timestamp: Date.now() }); console.log(`✅ Sessão criada. Sessões ativas:`, this.userSessions.size); // Escapar caracteres especiais const serialEscaped = serialNumber.replace(/_/g, '\\_'); // Perguntar o alias/nome do usuário await this.bot.sendMessage(chatId, `➕ *Adicionar ONU Fiberhome*\n\n` + `📱 *Serial:* ${serialEscaped}\n` + `🔍 A porta PON será descoberta automaticamente\n\n` + '👤 Digite o *nome/alias* do usuário:', { parse_mode: 'Markdown' } ); } /** * Processar input do usuário durante sessões */ async handleUserSessionInput(chatId, text, userId, session) { try { console.log(`🔄 Processando input de sessão. Action: ${session.action}, Type: ${session.type}, Text: ${text}`); // Verificar se é sessão baseada em action (Fiberhome ONU) if (session.action) { // Sessões de configuração de roteador (ação dinâmica com prefixo) if (session.action.startsWith('router_cfg_wait_')) { await this._processarInputConfigurarRoteador(chatId, userId, session, text); return; } switch (session.action) { case 'adicionar_onu_fiberhome_step1': // Recebeu o nome do usuário, adicionar sufixo e perguntar pelo tipo da ONU const usuarioBase = text.trim(); session.usuario = usuarioBase.includes('@') ? usuarioBase : `${usuarioBase}@clicknetguarai.com.br`; session.action = 'adicionar_onu_fiberhome_step2'; this.userSessions.set(userId, session); const keyboard = { inline_keyboard: [ [{ text: '🔌 AN5506-02-B (2 portas)', callback_data: 'fh_onu_type_AN5506-02-B' }], [{ text: '🔌 AN5506-01-A (1 porta)', callback_data: 'fh_onu_type_AN5506-01-A' }], [{ text: '❌ Cancelar', callback_data: 'cancel_action' }] ] }; await this.bot.sendMessage(chatId, `👤 **Usuário:** ${session.usuario}\n\n` + '🔌 Selecione o **tipo da ONU**:', { reply_markup: keyboard, parse_mode: 'Markdown' } ); return; case 'adicionar_onu_fiberhome_step3': // Recebeu confirmação de VLAN customizada const vlanInput = text.trim(); if (vlanInput && !isNaN(vlanInput)) { const vlan = parseInt(vlanInput); if (vlan >= 1 && vlan <= 4094) { session.vlan = vlan; } else { await this.bot.sendMessage(chatId, '❌ VLAN inválida. Usando VLAN padrão 1002.'); session.vlan = 1002; } } else { session.vlan = 1002; } // Limpar sessão this.userSessions.delete(userId); // Executar adição da ONU await this.executarAdicaoOnuFiberhome(chatId, session, userId); return; case 'topsapp_onu_aguardando_nome': await this.topsappOnuBuscarCliente(chatId, userId, text.trim(), session.onuData); return; } } // Verificar se é sessão baseada em type (outros fluxos) if (session.type) { switch (session.type) { case 'waiting_custom_command': await this.executeCustomCommand(chatId, session.connectionId, text); this.userSessions.delete(userId); return; case 'waiting_username_disconnect': await this.handleDisconnectUser(chatId, text.trim()); this.userSessions.delete(userId); return; case 'cgnat_aguardando_datetime': await this.handleCgnatDatetimeInput(chatId, userId, text.trim(), session); return; } } // Estado desconhecido console.log(`❓ Estado de sessão desconhecido: action=${session.action}, type=${session.type}`); this.userSessions.delete(userId); await this.bot.sendMessage(chatId, '❓ Estado de sessão desconhecido. Tente novamente.'); } catch (error) { console.error('❌ Erro ao processar input da sessão:', error); this.userSessions.delete(userId); await this.bot.sendMessage(chatId, '❌ Erro ao processar sua resposta. Tente novamente.'); } } /** * Executar adição da ONU Fiberhome */ async executarAdicaoOnuFiberhome(chatId, session, userId = null) { try { const { serial, usuario, vlan, onuType } = session; // Escapar caracteres especiais const serialEscaped = serial.replace(/_/g, '\\_'); await this.bot.sendMessage(chatId, `⏳ Adicionando ONU Fiberhome...\n\n` + `🔍 Descobrindo porta PON automaticamente...\n` + `👤 *Usuário:* ${usuario}\n` + `📱 *Serial:* ${serialEscaped}\n` + `🔌 *Tipo:* ${onuType}\n` + `🏷️ *VLAN:* ${vlan}`, { parse_mode: 'Markdown' } ); // Executar comando TL1 via Fiberhome Manager const resultado = await this.fiberhomeTL1.adicionarOnuFiberhome( serial, usuario, vlan, onuType ); if (resultado.success) { // Escapar caracteres especiais do Markdown const oltEscaped = resultado.details.olt.replace(/_/g, '\\_'); const serialEscaped = serial.replace(/_/g, '\\_'); await this.bot.sendMessage(chatId, `✅ *ONU Fiberhome Adicionada com Sucesso!*\n\n` + `👤 *Usuário:* ${usuario}\n` + `🏢 *OLT:* ${oltEscaped}\n` + `📡 *Porta descoberta:* Slot ${resultado.details.slot}, PON ${resultado.details.ponPort}\n` + `📱 *Serial:* ${serialEscaped}\n` + `🔌 *Tipo:* ${onuType}\n` + `🏷️ *VLAN:* ${vlan}\n` + `⚙️ *Portas configuradas:* ${resultado.details.portas}\n\n` + `🔐 Login Web: \`admin / clicknet10\`\n\n` + `⚡ A ONU foi reiniciada e estará disponível em alguns instantes.`, { parse_mode: 'Markdown' } ); // Convidar para registrar no TopSapp if (userId) { this.userSessions.set(userId, { action: 'topsapp_onu_pre_busca', onuData: { tipo: 'fiberhome', location: 'guarai_colmeia', oltHost: resultado.details.olt, serial, portaAlias: `${resultado.details.slot}-${resultado.details.ponPort}` } }); await this.bot.sendMessage(chatId, `📋 *Registrar no TopSapp?*\n\nDeseja vincular esta ONU a um cliente no sistema TopSapp?`, { parse_mode: 'Markdown', reply_markup: { inline_keyboard: [[ { text: '✅ Sim, vincular', callback_data: 'topsapp_onu_sim' }, { text: '❌ Não', callback_data: 'cancel_action' } ]] } } ); } } else { await this.bot.sendMessage(chatId, `❌ *Erro ao adicionar ONU Fiberhome*\n\n` + `⚠️ ${resultado.error}`, { parse_mode: 'Markdown' } ); } } catch (error) { console.error('❌ Erro ao executar adição de ONU Fiberhome:', error); await this.bot.sendMessage(chatId, `❌ *Erro ao adicionar ONU Fiberhome*\n\n` + `⚠️ ${error.message}`, { parse_mode: 'Markdown' } ); } } /** * Handler para remover ONU Fiberhome via TL1 */ async handleRemoverOnuFiberhome(chatId, serialNumber, userId) { try { // Validar serial if (!serialNumber || serialNumber.length !== 12) { await this.bot.sendMessage(chatId, '❌ Serial inválido. O serial deve conter 12 caracteres.\n\n' + 'Exemplo: `FHTT199cf3c8`' ); return; } // Escapar caracteres especiais const serialEscaped = serialNumber.replace(/_/g, '\\_'); // Solicitar confirmação const keyboard = { inline_keyboard: [ [ { text: '✅ Sim, remover', callback_data: `confirm_remove_fh_${serialNumber}` }, { text: '❌ Cancelar', callback_data: 'cancel_action' } ] ] }; await this.bot.sendMessage(chatId, `🗑️ *Remover ONU Fiberhome*\n\n` + `📱 *Serial:* ${serialEscaped}\n\n` + `⚠️ Esta ação irá remover a ONU e todas as suas configurações.\n\n` + `Deseja confirmar a remoção?`, { reply_markup: keyboard, parse_mode: 'Markdown' } ); } catch (error) { console.error('❌ Erro ao preparar remoção de ONU Fiberhome:', error); await this.bot.sendMessage(chatId, `❌ *Erro ao preparar remoção*\n\n` + `⚠️ ${error.message}`, { parse_mode: 'Markdown' } ); } } /** * Comando não reconhecido */ async handleUnknown(chatId, text) { const suggestions = this.nlp.suggestCorrection(text); let message = '❓ Não entendi o comando. '; if (suggestions.length > 0) { message += `Talvez você quis dizer: ${suggestions.join(', ')}?\n\n`; } message += 'Digite /help para ver todos os comandos disponíveis.'; await this.bot.sendMessage(chatId, message); } /** * Processa callbacks de botões inline */ async handleCallbackQuery(callbackQuery) { const chatId = callbackQuery.message.chat.id; const data = callbackQuery.data; const userId = callbackQuery.from.id; // Verificar autorização if (!this.isAuthorized(userId)) { await this.bot.answerCallbackQuery(callbackQuery.id, '🚫 Acesso negado'); console.log(`🚫 Callback negado para usuário (${userId})`); return; } // Seleção/troca de cidade — tratado antes de resolver o site ativo, // pois é a própria ação que define o site ativo. if (data.startsWith('select_site_')) { const siteId = data.replace('select_site_', ''); const site = sites.getSite(siteId); if (!site || !sites.getUserSiteIds(userId).includes(siteId)) { await this.bot.answerCallbackQuery(callbackQuery.id, '🚫 Cidade inválida'); return; } this.activeSite.set(chatId, siteId); await this.bot.answerCallbackQuery(callbackQuery.id, `📍 ${site.name}`); await this.bot.sendMessage(chatId, `📍 Cidade/provedor definido: *${site.name}*`, { parse_mode: 'Markdown' }); await this.handleHelp(chatId, userId); return; } if (data === 'open_site_menu') { await this.bot.answerCallbackQuery(callbackQuery.id); await this.sendSiteSelectionMenu(chatId, sites.getUserSiteIds(userId)); return; } // Garante que haja uma cidade/provedor ativa antes de qualquer outra ação const activeSiteId = await this.resolveActiveSite(chatId, userId); if (!activeSiteId) { await this.bot.answerCallbackQuery(callbackQuery.id); return; } try { console.log(`🔍 Callback recebido: ${data}`); console.log(`🔍 Verifica confirm_remove_: ${data.startsWith('confirm_remove_')}`); if (data.startsWith('cmd_')) { console.log(`🔍 Condição cmd_ atendida`); await this.handleCommandCallback(callbackQuery, data); } else if (data.startsWith('confirm_remove_fh_')) { console.log(`🔍 Chamando handleConfirmRemoveFiberhomeCallback`); try { await this.handleConfirmRemoveFiberhomeCallback(callbackQuery, data); } catch (confirmError) { console.error(`❌ Erro em handleConfirmRemoveFiberhomeCallback:`, confirmError); await this.bot.answerCallbackQuery(callbackQuery.id, 'Erro na confirmação'); } } else if (data.startsWith('confirm_remove_')) { console.log(`🔍 Chamando handleConfirmRemoveCallback`); try { await this.handleConfirmRemoveCallback(callbackQuery, data); } catch (confirmError) { console.error(`❌ Erro em handleConfirmRemoveCallback:`, confirmError); await this.bot.answerCallbackQuery(callbackQuery.id, 'Erro na confirmação'); } } else if (data.startsWith('confirm_add_')) { console.log(`🔍 Chamando handleConfirmAddCallback`); try { await this.handleConfirmAddCallback(callbackQuery, data); } catch (confirmError) { 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('ipoe_cliente_')) { console.log(`🔍 Condição ipoe_cliente_ atendida`); await this.handleIPoEClienteCallback(callbackQuery, data); } else if (data.startsWith('ipoe_servico_')) { console.log(`🔍 Condição ipoe_servico_ atendida`); await this.handleIPoEServicoCallback(callbackQuery, data); } else if (data.startsWith('confirm_ipoe_')) { console.log(`🔍 Condição confirm_ipoe_ atendida`); await this.handleConfirmIPoECallback(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_restart_onu_')) { console.log(`🔍 Condição confirm_restart_onu_ atendida`); await this.handleConfirmRestartOnuCallback(callbackQuery, data); } else if (data.startsWith('confirm_')) { console.log(`🔍 Condição confirm_ atendida`); await this.handleConfirmCallback(callbackQuery, data); } else if (data.startsWith('check_signal_')) { console.log(`🔍 Condição check_signal_ atendida`); await this.handleCheckSignalCallback(callbackQuery, data); } else if (data.startsWith('ver_sinal_')) { console.log(`🔍 Condição ver_sinal_ atendida`); const serialNumber = data.replace('ver_sinal_', ''); await this.bot.answerCallbackQuery(callbackQuery.id); await this.handleVerSinal(chatId, serialNumber); } else if (data.startsWith('show_config_')) { console.log(`🔍 Condição show_config_ atendida`); await this.handleShowConfigCallback(callbackQuery, data); } else if (data.startsWith('reset_onu_')) { console.log(`🔍 Condição reset_onu_ atendida`); await this.handleResetOnuCallback(callbackQuery, data); } else if (data.startsWith('update_onu_')) { console.log(`🔍 Condição update_onu_ atendida`); await this.handleUpdateOnuCallback(callbackQuery, data); } else if (data.startsWith('firmware_')) { console.log(`🔍 Condição firmware_ atendida`); await this.handleFirmwareCallback(callbackQuery, data); } else if (data.startsWith('remove_onu_')) { console.log(`🔍 Chamando handleRemoveOnuCallback`); await this.handleRemoveOnuCallback(callbackQuery, data); } else if (data.startsWith('remove_readd_vlan_')) { console.log(`🔍 Chamando handleRemoveAndReAddCallback (vlan)`); await this.handleRemoveAndReAddCallback(callbackQuery, data); } else if (data.startsWith('remove_readd_')) { console.log(`🔍 Chamando handleRemoveAndReAddCallback`); await this.handleRemoveAndReAddCallback(callbackQuery, data); } else if (data.startsWith('topsapp_onu_')) { console.log(`🔍 Condição topsapp_onu_ atendida`); await this.handleTopSappOnuCallback(callbackQuery, data); } else if (data.startsWith('add_onu_vlan_')) { console.log(`🔍 Chamando handleAddOnuVlanCallback`); await this.handleAddOnuVlanCallback(callbackQuery, data); } else if (data.startsWith('add_onu_')) { console.log(`🔍 Chamando handleAddOnuCallback`); await this.handleAddOnuCallback(callbackQuery, data); } else if (data.startsWith('fh_onu_type_')) { console.log(`🔍 Chamando handleFiberhomeOnuTypeCallback`); await this.handleFiberhomeOnuTypeCallback(callbackQuery, data); } else if (data.startsWith('fh_vlan_')) { console.log(`🔍 Chamando handleFiberhomeVlanCallback`); await this.handleFiberhomeVlanCallback(callbackQuery, data); } else if (data.startsWith('fiberhome_details_')) { console.log(`🔍 Condição fiberhome_details_ atendida`); const serialNumber = data.replace('fiberhome_details_', ''); await this.bot.answerCallbackQuery(callbackQuery.id); await this.handleStatusONU(chatId, serialNumber); } else if (data.startsWith('router_cfg_')) { console.log(`🔍 Condição router_cfg_ atendida`); await this.handleRouterCfgCallback(callbackQuery, data); } else if (data.startsWith('cfg_roteador_cli_')) { console.log(`🔍 Condição cfg_roteador_cli_ atendida`); await this.bot.answerCallbackQuery(callbackQuery.id); const parts = data.replace('cfg_roteador_cli_', '').split('_'); const idCliente = parts[0]; const location = parts.slice(1).join('_'); await this._configurarRoteadorSelecionarServico(chatId, userId, idCliente, location); } else if (data.startsWith('cfg_roteador_srv_')) { console.log(`🔍 Condição cfg_roteador_srv_ atendida`); await this.bot.answerCallbackQuery(callbackQuery.id); // formato: cfg_roteador_srv___ const rest = data.replace('cfg_roteador_srv_', ''); const parts = rest.split('_'); const idCliente = parts[0]; const idServico = parts[1]; const location = parts.slice(2).join('_'); // Recarregar o serviço para ter o objeto completo com pppoeLogin const servicosResult = await this.topsapp.obterServicosCliente(idCliente, location); if (!servicosResult.success) { await this.bot.sendMessage(chatId, `❌ Erro ao buscar serviço: ${servicosResult.error}`); } else { const servico = (servicosResult.servicos || []).find(s => String(s.id_servico || s.id) === String(idServico) ); if (!servico) { await this.bot.sendMessage(chatId, `❌ Serviço #${idServico} não encontrado.`); } else { await this._configurarRoteadorPorServico(chatId, userId, servico, location); } } } else if (data.startsWith('cgnat_ativas_')) { console.log(`🔍 Condição cgnat_ativas_ atendida`); await this.bot.answerCallbackQuery(callbackQuery.id); const privateIP = data.replace('cgnat_ativas_', ''); await this.handleCgnatConsultarAtivas(chatId, privateIP); } else if (data.startsWith('cgnat_pedir_data_')) { console.log(`🔍 Condição cgnat_pedir_data_ atendida`); await this.bot.answerCallbackQuery(callbackQuery.id); const privateIP = data.replace('cgnat_pedir_data_', ''); this.userSessions.set(userId, { type: 'cgnat_aguardando_datetime', privateIP, publicIP: null, porta: null }); await this.bot.sendMessage(chatId, `📅 Digite a data e hora do acesso para \`${privateIP}\`:\n` + `Formato: \`DD/MM/AAAA HH:MM\` ou \`DD/MM/AAAA\`\n\n` + `Ou envie *cancelar* para encerrar.`, { parse_mode: 'Markdown' } ); } else if (data.startsWith('buscar_cgnat_user_')) { console.log(`🔍 Condição buscar_cgnat_user_ atendida`); await this.bot.answerCallbackQuery(callbackQuery.id); const login = data.replace('buscar_cgnat_user_', ''); await this.handleBuscarCliente(chatId, login); } else if (data.startsWith('diag_cliente_')) { console.log(`🔍 Condição diag_cliente_ atendida`); await this.handleDiagClienteCallback(callbackQuery, data); } else if (data.startsWith('restart_onu_cli_')) { console.log(`🔍 Condição restart_onu_cli_ atendida`); await this.handleRestartOnuClienteCallback(callbackQuery, data); } else if (data.startsWith('restart_onu_srv_')) { console.log(`🔍 Condição restart_onu_srv_ atendida`); await this.handleRestartOnuServicoCallback(callbackQuery, data); } else if (data.startsWith('diag_servico_')) { console.log(`🔍 Condição diag_servico_ atendida`); await this.handleDiagServicoCallback(callbackQuery, data); } else if (data.startsWith('dados_cliente_')) { console.log(`🔍 Condição dados_cliente_ atendida`); await this.handleDadosClienteCallback(callbackQuery, data); } else if (data.startsWith('faturas_cliente_')) { console.log(`🔍 Condição faturas_cliente_ atendida`); await this.handleFaturasClienteCallback(callbackQuery, data); } else if (data.startsWith('faturas_servico_')) { console.log(`🔍 Condição faturas_servico_ atendida`); await this.handleFaturasServicoCallback(callbackQuery, data); } else if (data.startsWith('servicos_cliente_')) { console.log(`🔍 Condição servicos_cliente_ atendida`); await this.handleServicosClienteCallback(callbackQuery, data); } else if (data.startsWith('help_')) { console.log(`🔍 Condição help_ atendida`); await this.handleHelpDetailCallback(callbackQuery, data); } else if (data === 'separator') { // Ignorar clique no separador visual await this.bot.answerCallbackQuery(callbackQuery.id); } else if (data === 'cancel_action') { await this.bot.answerCallbackQuery(callbackQuery.id, 'Ação cancelada'); // Limpar sessão do usuário se existir if (this.userSessions.has(userId)) { this.userSessions.delete(userId); } await this.bot.editMessageText('❌ Ação cancelada.', { chat_id: chatId, message_id: callbackQuery.message.message_id }); } else if (data === 'refresh_users') { await this.bot.answerCallbackQuery(callbackQuery.id); await this.handleListUsers(chatId); } else if (data === 'disconnect_user_prompt') { await this.bot.answerCallbackQuery(callbackQuery.id); await this.bot.editMessageText('👤 Digite o nome do usuário que deseja desconectar:', { chat_id: chatId, message_id: callbackQuery.message.message_id }); // Armazenar estado para próxima mensagem this.userSessions.set(callbackQuery.from.id, { type: 'waiting_username_disconnect' }); } else if (data.startsWith('suporte_cliente_')) { console.log(`🔍 Condição suporte_cliente_ atendida`); await this.handleSuporteClienteCallback(callbackQuery, data); } else if (data.startsWith('suporte_servico_')) { console.log(`🔍 Condição suporte_servico_ atendida`); await this.handleSuporteServicoCallback(callbackQuery, data); } else if (data.startsWith('suporte_tecnico_')) { await this.bot.answerCallbackQuery(callbackQuery.id); await this.handleSuporteTecnicoCallback(callbackQuery, data); } else if (data.startsWith('fechar_suporte_cli_')) { await this.bot.answerCallbackQuery(callbackQuery.id); await this.handleFecharSuporteClienteCallback(callbackQuery, data); } else if (data.startsWith('fechar_suporte_id_')) { await this.bot.answerCallbackQuery(callbackQuery.id); await this.handleFecharSuporteIdCallback(callbackQuery, data); } else if (data.startsWith('linha_')) { console.log(`🔍 Condição linha_ atendida`); await this.handleLinhaDigitavelCallback(callbackQuery, data); } else if (data.startsWith('pix_')) { console.log(`🔍 Condição pix_ atendida`); await this.handlePixCallback(callbackQuery, data); } else { console.log(`❓ Callback não reconhecido: ${data}`); await this.bot.answerCallbackQuery(callbackQuery.id, 'Comando não reconhecido'); } } catch (error) { console.error('❌ Erro no callback:', error); await this.bot.answerCallbackQuery(callbackQuery.id, 'Erro interno'); } } /** * Processa callbacks de comandos */ async handleCommandCallback(callbackQuery, data) { const parts = data.split('_'); const action = parts[1]; const connectionId = parts.slice(2).join('_'); await this.bot.answerCallbackQuery(callbackQuery.id); switch (action) { case 'status': await this.executeSSHCommand(callbackQuery, connectionId, 'uptime'); break; case 'reboot': await this.confirmRebootCallback(callbackQuery, connectionId); break; case 'disconnect': await this.disconnectCallback(callbackQuery, connectionId); break; case 'custom': await this.requestCustomCommand(callbackQuery, connectionId); break; } } /** * Executa comando SSH e mostra resultado */ async executeSSHCommand(callbackQuery, connectionId, command) { const chatId = callbackQuery.message.chat.id; try { const result = await this.ssh.executeCommand(connectionId, command); let message = `💻 *Comando:* \`${command}\`\n\n`; if (result.success) { message += `✅ *Resultado:*\n\`\`\`\n${result.output}\`\`\``; } else { message += `❌ *Erro:*\n\`\`\`\n${result.error}\`\`\``; } await this.bot.sendMessage(chatId, message, { parse_mode: 'Markdown' }); } catch (error) { await this.bot.sendMessage(chatId, `❌ Erro ao executar comando: ${error.message}`); } } /** * Confirma reinicialização */ async confirmRebootCallback(callbackQuery, connectionId) { const chatId = callbackQuery.message.chat.id; await this.bot.editMessageText('⚠️ Tem certeza que deseja reiniciar o equipamento?', { chat_id: chatId, message_id: callbackQuery.message.message_id, reply_markup: { inline_keyboard: [ [ { text: '✅ SIM, reiniciar', callback_data: `confirm_reboot_${connectionId}` }, { text: '❌ Cancelar', callback_data: 'cancel_action' } ] ] } }); } /** * Desconecta conexão SSH */ async disconnectCallback(callbackQuery, connectionId) { const chatId = callbackQuery.message.chat.id; const success = this.ssh.disconnect(connectionId); if (success) { await this.bot.editMessageText('🔌 Conexão encerrada com sucesso.', { chat_id: chatId, message_id: callbackQuery.message.message_id }); } else { await this.bot.editMessageText('❌ Conexão não encontrada ou já encerrada.', { chat_id: chatId, message_id: callbackQuery.message.message_id }); } } /** * Solicita comando personalizado */ async requestCustomCommand(callbackQuery, connectionId) { const chatId = callbackQuery.message.chat.id; // Armazenar estado para próxima mensagem this.userSessions.set(callbackQuery.from.id, { type: 'waiting_custom_command', connectionId: connectionId }); await this.bot.editMessageText('💻 Digite o comando que deseja executar:', { chat_id: chatId, message_id: callbackQuery.message.message_id }); } /** * Processa callbacks de confirmação */ async handleConfirmCallback(callbackQuery, data) { const parts = data.split('_'); const action = parts[1]; if (action === 'restart' || action === 'reboot') { const target = parts.slice(2).join('_'); await this.executeRestart(callbackQuery, target); } else if (action === 'reset' && parts[2] === 'onu') { // Formato: confirm_reset_onu_oltid_slot_onuid await this.handleConfirmResetOnu(callbackQuery, data); } else if (action === 'firmware') { // Formato: confirm_firmware_filename_oltid_slot_onuid await this.handleConfirmFirmware(callbackQuery, data); } } /** * Executa reinicialização confirmada */ async executeRestart(callbackQuery, target) { const chatId = callbackQuery.message.chat.id; await this.bot.editMessageText(`🔄 Reiniciando ${target}...`, { chat_id: chatId, message_id: callbackQuery.message.message_id }); // Simular reinicialização (implementar comando real aqui) setTimeout(async () => { await this.bot.sendMessage(chatId, `✅ ${target} foi reiniciado com sucesso.`); }, 3000); } /** * MÉTODO REMOVIDO - handleUserSessionInput duplicado foi mesclado acima */ /** * Executa comando personalizado em uma conexão SSH */ async executeCustomCommand(chatId, connectionId, command) { await this.bot.sendMessage(chatId, `💻 Executando: \`${command}\``, { parse_mode: 'Markdown' }); try { const result = await this.ssh.executeCommand(connectionId, command); let message = `💻 *Comando:* \`${command}\`\n\n`; if (result.success) { message += `✅ *Resultado:*\n\`\`\`\n${result.output || 'Comando executado com sucesso'}\`\`\``; } else { message += `❌ *Erro:*\n\`\`\`\n${result.error || 'Erro desconhecido'}\`\`\``; } await this.bot.sendMessage(chatId, message, { parse_mode: 'Markdown' }); } catch (error) { await this.bot.sendMessage(chatId, `❌ Erro ao executar comando: ${error.message}`); } } /** * Processa callback de verificação de sinal */ async handleCheckSignalCallback(callbackQuery, data) { const parts = data.split('_'); const chatId = callbackQuery.message.chat.id; // Verificar se é Fiberhome (formato: check_signal_fiberhome_SERIAL) if (parts.length === 4 && parts[2] === 'fiberhome') { const serialNumber = parts[3]; await this.bot.answerCallbackQuery(callbackQuery.id); await this.handleCheckSignalFiberhome(chatId, serialNumber); return; } // Formato padrão Datacom: check_signal_olt_dito_SERIAL ou check_signal_olt_pedro_afonso_SERIAL let oltId, serialNumber; if (parts.length === 6 && parts[2] === 'olt' && parts[3] === 'pedro' && parts[4] === 'afonso') { // Caso: check_signal_olt_pedro_afonso_SERIAL oltId = parts[2] + '_' + parts[3] + '_' + parts[4]; // "olt_pedro_afonso" serialNumber = parts[5]; } else if (parts.length === 5) { // Caso: check_signal_olt_dito_SERIAL oltId = parts[2] + '_' + parts[3]; // "olt_dito" serialNumber = parts[4]; } else if (parts.length === 4) { // Caso: check_signal_oltid_SERIAL (se existir formato mais simples) oltId = parts[2]; serialNumber = parts[3]; } else { // Tentar reconstruir dinamicamente oltId = parts.slice(2, -1).join('_'); // Tudo exceto o último (que é o serial) serialNumber = parts[parts.length - 1]; // Último elemento } await this.bot.answerCallbackQuery(callbackQuery.id); await this.handleCheckSignalOLT(chatId, oltId, serialNumber); } /** * Executa verificação de sinal na OLT Fiberhome */ async handleCheckSignalFiberhome(chatId, serialNumber) { // Escapar caracteres especiais const serialEscaped = serialNumber.replace(/_/g, '\\_'); await this.bot.sendMessage(chatId, `🔍 Procurando ONU ${serialEscaped} na OLT Fiberhome...`); try { // Usar a mesma lógica do handleStatusONU let resultado; if (serialNumber.length === 12 && /^[A-Z0-9]+$/.test(serialNumber)) { // É um serial resultado = await this.fiberhome.obterStatusONU(serialNumber); } else { // Pode ser device name resultado = await this.fiberhome.obterStatusONUPorNome(serialNumber); } if (resultado.success) { // Formatar resposta similar às OLTs Datacom let message = `📡 *Sinal da ONU ${serialEscaped}*\n\n`; message += `🏢 *OLT:* Fiberhome UNM2000 (131.0.192.9)\n`; message += `📍 *Localização:* Slot ${resultado.slot}, PON ${resultado.pon}, ONU ${resultado.onuId}\n`; message += `🔌 *Status:* ${resultado.online ? '🟢 Online' : '🔴 Offline'}\n\n`; message += `📊 *Informações do Sinal:*\n`; message += `📡 *RX Power:* ${resultado.rxPower}\n`; message += `📤 *TX Power:* ${resultado.txPower}\n`; message += `📏 *Distância:* ${resultado.distance}\n\n`; // Nota sobre leitura de potência message += `_💡 Valores medidos pela OLT via protocolo TL1_\n\n`; // Status das portas Ethernet (resumido) if (resultado.portasEthernet.length > 0) { const portasAtivas = resultado.portasEthernet.filter(p => p.status === 'active' || p.status === 'up').length; message += `🔗 *Portas Ethernet:* ${portasAtivas}/${resultado.portasEthernet.length} ativas\n`; } // Serviços WAN if (resultado.servicosWAN.length > 0) { const servicosAtivos = resultado.servicosWAN.filter(s => s.status === 'active').length; message += `🌐 *Serviços WAN:* ${servicosAtivos} ativo(s)\n`; } await this.bot.sendMessage(chatId, message, { parse_mode: 'Markdown' }); // Oferecer opções adicionais (similar às OLTs Datacom) const keyboard = { inline_keyboard: [ [ { text: '🔄 Atualizar sinal', callback_data: `check_signal_fiberhome_${serialNumber}` }, { text: '📋 Detalhes completos', callback_data: `fiberhome_details_${serialNumber}` } ], [ { text: '🔍 Pesquisar em OLT Datacom', callback_data: `ver_sinal_${serialNumber}` } ] ] }; await this.bot.sendMessage(chatId, '🎛️ O que você gostaria de fazer?', { reply_markup: keyboard }); } else { await this.bot.sendMessage(chatId, `❌ ONU ${serialNumber} não encontrada na OLT Fiberhome.`); // Oferecer pesquisar em OLTs Datacom const keyboard = { inline_keyboard: [ [ { text: '🔍 Pesquisar em OLT Datacom', callback_data: `ver_sinal_${serialNumber}` }, { text: '❌ Cancelar', callback_data: 'cancel_action' } ] ] }; await this.bot.sendMessage(chatId, 'ONU não encontrada na Fiberhome. Deseja pesquisar nas OLTs Datacom?', { reply_markup: keyboard }); } } catch (error) { console.error('Erro ao verificar sinal Fiberhome:', error); await this.bot.sendMessage(chatId, `❌ Erro interno: ${error.message}`); } } /** * Processa callback de mostrar configuração */ async handleShowConfigCallback(callbackQuery, data) { const parts = data.split('_'); // Formato: show_config_olt_dito_slot_onuid ou show_config_olt_pedro_afonso_slot_onuid let oltId, slot, onuId; if (parts.length === 7 && parts[2] === 'olt' && parts[3] === 'pedro' && parts[4] === 'afonso') { // Caso: show_config_olt_pedro_afonso_slot_onuid oltId = parts[2] + '_' + parts[3] + '_' + parts[4]; // "olt_pedro_afonso" slot = parts[5]; onuId = parts[6]; } else if (parts.length === 6) { // Caso: show_config_olt_dito_slot_onuid oltId = parts[2] + '_' + parts[3]; // "olt_dito" slot = parts[4]; onuId = parts[5]; } else if (parts.length === 5) { // Caso: show_config_oltid_slot_onuid (se existir formato mais simples) oltId = parts[2]; slot = parts[3]; onuId = parts[4]; } else { // Tentar reconstruir dinamicamente // Os dois últimos são sempre slot e onuId onuId = parts[parts.length - 1]; slot = parts[parts.length - 2]; oltId = parts.slice(2, -2).join('_'); // Tudo entre o terceiro e os dois últimos } const chatId = callbackQuery.message.chat.id; await this.bot.answerCallbackQuery(callbackQuery.id); await this.bot.sendMessage(chatId, `📋 Buscando configuração da ONU ${onuId} no slot ${slot}...`); try { const connection = await this.ssh.connect(oltId); if (!connection.success) { await this.bot.sendMessage(chatId, `❌ Erro na conexão: ${connection.message}`); return; } const configCommand = `show running-config interface gpon ${slot} onu ${onuId}`; const spCommand = `show running-config service-port | context-match "gpon ${slot} onu ${onuId}"`; const [configResult, spResult] = await Promise.all([ this.ssh.executeCommand(connection.connectionId, configCommand), this.ssh.executeCommand(connection.connectionId, spCommand) ]); this.ssh.disconnect(connection.connectionId); if (configResult.success) { // Extrair service-ports const servicePorts = spResult.success ? this.ssh.parseServicePorts(spResult.output) : []; let message = `📋 *Configuração da ONU*\n`; message += `📍 Slot: ${slot}, ONU: ${onuId}\n`; if (servicePorts.length > 0) { message += `🔌 *Service-port${servicePorts.length > 1 ? 's' : ''}:* ${servicePorts.join(', ')}\n`; } else { message += `🔌 *Service-port:* não encontrado\n`; } message += `\n\`\`\`\n${configResult.output}\n\`\`\``; if (spResult.success && spResult.output && spResult.output.trim()) { message += `\n\n*Service-port(s):*\n\`\`\`\n${spResult.output.trim()}\n\`\`\``; } await this.bot.sendMessage(chatId, message, { parse_mode: 'Markdown' }); } else { await this.bot.sendMessage(chatId, `❌ Erro ao obter configuração: ${configResult.error}`); } } catch (error) { console.error('Erro ao mostrar configuração:', error); await this.bot.sendMessage(chatId, `❌ Erro interno: ${error.message}`); } } /** * Processa callback de reiniciar ONU */ async handleResetOnuCallback(callbackQuery, data) { console.log(`🔌 handleResetOnuCallback chamado com data: ${data}`); const parts = data.split('_'); console.log(`📝 Parts: ${JSON.stringify(parts)}`); // Formato: reset_onu_olt_dito_slot_onuid ou reset_onu_olt_pedro_afonso_slot_onuid let oltId, slot, onuId; if (parts.length === 7 && parts[2] === 'olt' && parts[3] === 'pedro' && parts[4] === 'afonso') { // Caso: reset_onu_olt_pedro_afonso_slot_onuid oltId = parts[2] + '_' + parts[3] + '_' + parts[4]; // "olt_pedro_afonso" slot = parts[5]; onuId = parts[6]; } else if (parts.length === 6) { // Caso: reset_onu_olt_dito_slot_onuid oltId = parts[2] + '_' + parts[3]; // "olt_dito" slot = parts[4]; onuId = parts[5]; } else if (parts.length === 5) { // Caso: reset_onu_oltid_slot_onuid (se existir formato mais simples) oltId = parts[2]; slot = parts[3]; onuId = parts[4]; } else { // Tentar reconstruir dinamicamente // Os dois últimos são sempre slot e onuId onuId = parts[parts.length - 1]; slot = parts[parts.length - 2]; oltId = parts.slice(2, -2).join('_'); // Tudo entre o terceiro e os dois últimos } console.log(`🎯 Parsed - OLT: ${oltId}, Slot: ${slot}, ONU: ${onuId}`); const chatId = callbackQuery.message.chat.id; // Primeiro mostrar confirmação await this.bot.answerCallbackQuery(callbackQuery.id); await this.bot.editMessageText( `⚠️ **Confirmação de Reinicialização**\n\n` + `Tem certeza que deseja reiniciar a ONU?\n\n` + `📍 **Slot:** ${slot}\n` + `🔌 **ONU ID:** ${onuId}\n` + `🏢 **OLT:** ${oltId.replace('olt_', '').toUpperCase()}\n\n` + `⚠️ Esta ação irá desconectar temporariamente o cliente.`, { chat_id: chatId, message_id: callbackQuery.message.message_id, parse_mode: 'Markdown', reply_markup: { inline_keyboard: [ [ { text: '✅ SIM, Reiniciar', callback_data: `confirm_reset_onu_${oltId}_${slot}_${onuId}` }, { text: '❌ Cancelar', callback_data: 'cancel_action' } ] ] } } ); } /** * Processa confirmação de reinicialização de ONU */ async handleConfirmResetOnu(callbackQuery, data) { console.log(`🔄 handleConfirmResetOnu chamado com data: ${data}`); const parts = data.replace('confirm_reset_onu_', '').split('_'); console.log(`📝 Parts após parsing: ${JSON.stringify(parts)}`); // Formato após remoção do prefixo: olt_dito_slot_onuid let oltId, slot, onuId; if (parts.length === 4) { // Caso: olt_dito_slot_onuid oltId = parts[0] + '_' + parts[1]; // "olt_dito" slot = parts[2]; onuId = parts[3]; } else if (parts.length === 3) { // Caso: oltid_slot_onuid oltId = parts[0]; slot = parts[1]; onuId = parts[2]; } else { // Tentar reconstruir dinamicamente onuId = parts[parts.length - 1]; slot = parts[parts.length - 2]; oltId = parts.slice(0, -2).join('_'); } console.log(`🎯 Parsed - OLT: ${oltId}, Slot: ${slot}, ONU: ${onuId}`); const chatId = callbackQuery.message.chat.id; await this.bot.answerCallbackQuery(callbackQuery.id); await this.bot.editMessageText( `🔄 **Reiniciando ONU...**\n\n` + `📍 Slot: ${slot}\n` + `🔌 ONU ID: ${onuId}\n` + `🏢 OLT: ${oltId.replace('olt_', '').toUpperCase()}\n\n` + `⏳ Aguarde, isso pode levar alguns segundos...`, { chat_id: chatId, message_id: callbackQuery.message.message_id, parse_mode: 'Markdown' } ); try { const result = await this.ssh.resetOnuAlternative(oltId, slot, onuId); if (result.success) { await this.bot.editMessageText( `✅ **ONU Reiniciada com Sucesso!**\n\n` + `📍 Slot: ${slot}\n` + `🔌 ONU ID: ${onuId}\n` + `🏢 OLT: ${oltId.replace('olt_', '').toUpperCase()}\n\n` + `🔄 A ONU deve voltar a funcionar em alguns instantes.\n` + `📊 Recomenda-se verificar o sinal novamente em 1-2 minutos.`, { chat_id: chatId, message_id: callbackQuery.message.message_id, parse_mode: 'Markdown' } ); } else { await this.bot.editMessageText( `❌ **Erro ao Reiniciar ONU**\n\n` + `📍 Slot: ${slot}\n` + `🔌 ONU ID: ${onuId}\n` + `🏢 OLT: ${oltId.replace('olt_', '').toUpperCase()}\n\n` + `❌ Erro: ${result.message}`, { chat_id: chatId, message_id: callbackQuery.message.message_id, parse_mode: 'Markdown' } ); } } catch (error) { console.error('Erro ao reiniciar ONU:', error); await this.bot.editMessageText( `❌ **Erro Interno**\n\n` + `Não foi possível reiniciar a ONU.\n` + `Erro: ${error.message}`, { chat_id: chatId, message_id: callbackQuery.message.message_id, parse_mode: 'Markdown' } ); } } /** * Processa callback de atualização de firmware da ONU */ async handleUpdateOnuCallback(callbackQuery, data) { const parts = data.split('_'); // Formato: update_onu_olt_dito_slot_onuid ou update_onu_olt_pedro_afonso_slot_onuid let oltId, slot, onuId; if (parts.length === 7 && parts[2] === 'olt' && parts[3] === 'pedro' && parts[4] === 'afonso') { // Caso: update_onu_olt_pedro_afonso_slot_onuid oltId = parts[2] + '_' + parts[3] + '_' + parts[4]; // "olt_pedro_afonso" slot = parts[5]; onuId = parts[6]; } else if (parts.length === 6) { // Caso: update_onu_olt_dito_slot_onuid oltId = parts[2] + '_' + parts[3]; // "olt_dito" slot = parts[4]; onuId = parts[5]; } else if (parts.length === 5) { // Caso: update_onu_oltid_slot_onuid (se existir formato mais simples) oltId = parts[2]; slot = parts[3]; onuId = parts[4]; } else { // Tentar reconstruir dinamicamente // Os dois últimos são sempre slot e onuId onuId = parts[parts.length - 1]; slot = parts[parts.length - 2]; oltId = parts.slice(2, -2).join('_'); // Tudo entre o terceiro e os dois últimos } const chatId = callbackQuery.message.chat.id; await this.bot.answerCallbackQuery(callbackQuery.id); await this.bot.sendMessage(chatId, `🔍 Identificando modelo da ONU ${onuId} no slot ${slot}...`); try { // Obter detalhes da ONU para identificar o modelo const details = await this.ssh.getOnuDetails(oltId, slot, onuId); if (!details.success) { await this.bot.sendMessage(chatId, `❌ Erro ao obter detalhes da ONU: ${details.message}`); return; } const equipmentId = details.equipmentId; console.log(`🔍 Equipment ID detectado: ${equipmentId}`); // Determinar opções de firmware baseado no modelo let firmwareOptions = []; if (equipmentId.includes('DM986-100')) { firmwareOptions = [ { text: '📦 FW INTERMEDIÁRIA', callback_data: `firmware_DM986-100-inter.tar_${oltId}_${slot}_${onuId}` }, { text: '📦 FW ATUAL (v56)', callback_data: `firmware_DM986-100-v56.tar_${oltId}_${slot}_${onuId}` }, { text: '📦 FW ROUTER (R300)', callback_data: `firmware_DM986-100-R300.tar_${oltId}_${slot}_${onuId}` } ]; } else if (equipmentId.includes('DM986-414')) { firmwareOptions = [ { text: '📦 FW INTERMEDIÁRIA', callback_data: `firmware_DM986-414-inter.tar_${oltId}_${slot}_${onuId}` }, { text: '📦 FW ATUAL (v70)', callback_data: `firmware_DM986-414-v70.tar_${oltId}_${slot}_${onuId}` } ]; } else { await this.bot.sendMessage(chatId, `❌ **Modelo não suportado**\n\n` + `📍 Slot: ${slot}, ONU: ${onuId}\n` + `🔍 Equipment ID: ${equipmentId}\n\n` + `⚠️ Apenas DM986-100 e DM986-414 são suportados para atualização.` ); return; } // Criar teclado com opções de firmware const keyboard = { inline_keyboard: [ ...firmwareOptions.map(option => [option]), [{ text: '❌ Cancelar', callback_data: 'cancel_action' }] ] }; await this.bot.sendMessage(chatId, `⬆️ **Atualização de Firmware**\n\n` + `📍 **Slot:** ${slot}\n` + `🔌 **ONU ID:** ${onuId}\n` + `🏢 **OLT:** ${oltId.replace('olt_', '').toUpperCase()}\n` + `🔍 **Modelo:** ${equipmentId}\n\n` + `📦 Selecione a versão do firmware:`, { parse_mode: 'Markdown', reply_markup: keyboard } ); } catch (error) { console.error('Erro ao processar atualização de ONU:', error); await this.bot.sendMessage(chatId, `❌ Erro interno: ${error.message}`); } } /** * Processa callback de seleção de firmware */ async handleFirmwareCallback(callbackQuery, data) { // Formato: firmware_DM986-100-v56.tar_olt_dito_slot_onuid const parts = data.replace('firmware_', '').split('_'); let firmwareFile, oltId, slot, onuId; // O arquivo de firmware é sempre o primeiro elemento firmwareFile = parts[0]; // Processar o resto baseado no número de partes if (parts.length === 5) { // Caso: DM986-100-v56.tar_olt_dito_slot_onuid oltId = parts[1] + '_' + parts[2]; // "olt_dito" slot = parts[3]; onuId = parts[4]; } else if (parts.length === 4) { // Caso: DM986-100-v56.tar_oltid_slot_onuid oltId = parts[1]; slot = parts[2]; onuId = parts[3]; } else { // Tentar reconstruir dinamicamente onuId = parts[parts.length - 1]; slot = parts[parts.length - 2]; oltId = parts.slice(1, -2).join('_'); } const chatId = callbackQuery.message.chat.id; // Mostrar confirmação await this.bot.answerCallbackQuery(callbackQuery.id); await this.bot.editMessageText( `⚠️ **Confirmação de Atualização de Firmware**\n\n` + `📦 **Firmware:** ${firmwareFile}\n` + `📍 **Slot:** ${slot}\n` + `🔌 **ONU ID:** ${onuId}\n` + `🏢 **OLT:** ${oltId.replace('olt_', '').toUpperCase()}\n\n` + `⚠️ **ATENÇÃO:**\n` + `• A atualização pode demorar alguns minutos\n` + `• A ONU ficará offline durante o processo\n` + `• Não interrompa a operação\n\n` + `Tem certeza que deseja continuar?`, { chat_id: chatId, message_id: callbackQuery.message.message_id, parse_mode: 'Markdown', reply_markup: { inline_keyboard: [ [ { text: '✅ SIM, Atualizar', callback_data: `confirm_firmware_${firmwareFile}_${oltId}_${slot}_${onuId}` }, { text: '❌ Cancelar', callback_data: 'cancel_action' } ] ] } } ); } /** * Processa confirmação de atualização de firmware */ async handleConfirmFirmware(callbackQuery, data) { // Formato: confirm_firmware_DM986-100-v56.tar_olt_dito_slot_onuid const parts = data.replace('confirm_firmware_', '').split('_'); let firmwareFile, oltId, slot, onuId; firmwareFile = parts[0]; if (parts.length === 5) { oltId = parts[1] + '_' + parts[2]; slot = parts[3]; onuId = parts[4]; } else if (parts.length === 4) { oltId = parts[1]; slot = parts[2]; onuId = parts[3]; } else { onuId = parts[parts.length - 1]; slot = parts[parts.length - 2]; oltId = parts.slice(1, -2).join('_'); } const chatId = callbackQuery.message.chat.id; await this.bot.answerCallbackQuery(callbackQuery.id); await this.bot.editMessageText( `⬆️ **Atualizando Firmware...**\n\n` + `📦 Firmware: ${firmwareFile}\n` + `📍 Slot: ${slot}\n` + `🔌 ONU ID: ${onuId}\n` + `🏢 OLT: ${oltId.replace('olt_', '').toUpperCase()}\n\n` + `⏳ Enviando comando de atualização...`, { chat_id: chatId, message_id: callbackQuery.message.message_id, parse_mode: 'Markdown' } ); try { const result = await this.ssh.updateOnuFirmware(oltId, slot, onuId, firmwareFile); if (result.success) { await this.bot.editMessageText( `✅ **Comando de Atualização Enviado!**\n\n` + `📦 **Firmware:** ${firmwareFile}\n` + `📍 **Slot:** ${slot}\n` + `🔌 **ONU ID:** ${onuId}\n` + `🏢 **OLT:** ${oltId.replace('olt_', '').toUpperCase()}\n\n` + `✅ **Status:** Comando enviado com sucesso\n` + `⏳ **Processamento:** A atualização está sendo processada pela OLT\n` + `🕒 **Tempo estimado:** 5-15 minutos\n` + `📡 **Importante:** Monitore o status da ONU\n\n` + `💡 *A ONU ficará offline durante a atualização*`, { chat_id: chatId, message_id: callbackQuery.message.message_id, parse_mode: 'Markdown' } ); } else { await this.bot.editMessageText( `❌ **Erro ao Enviar Comando**\n\n` + `📦 **Firmware:** ${firmwareFile}\n` + `📍 **Slot:** ${slot}\n` + `🔌 **ONU ID:** ${onuId}\n` + `🏢 **OLT:** ${oltId.replace('olt_', '').toUpperCase()}\n\n` + `❌ **Erro:** ${result.message}\n\n` + `🔄 Tente novamente ou verifique a conexão`, { chat_id: chatId, message_id: callbackQuery.message.message_id, parse_mode: 'Markdown' } ); } } catch (error) { console.error('Erro ao atualizar firmware:', error); await this.bot.editMessageText( `❌ **Erro Interno**\n\n` + `Não foi possível atualizar o firmware.\n` + `Erro: ${error.message}`, { chat_id: chatId, message_id: callbackQuery.message.message_id, parse_mode: 'Markdown' } ); } } /** * Handle callback para iniciar processo de remoção de ONU */ async handleRemoveOnuCallback(callbackQuery, data) { const chatId = callbackQuery.message.chat.id; const parts = data.split('_'); // remove_onu_olt_dito_[serialNumber] ou remove_onu_olt_pedro_afonso_[serialNumber] let oltId, serialNumber; if (parts.length >= 6 && parts[3] === 'pedro' && parts[4] === 'afonso') { // Caso: remove_onu_olt_pedro_afonso_SERIAL oltId = parts[2] + '_' + parts[3] + '_' + parts[4]; // "olt_pedro_afonso" serialNumber = parts.slice(5).join('_'); // Serial pode ter underscores } else if (parts.length >= 5) { // Caso: remove_onu_olt_dito_SERIAL ou outras OLTs de duas palavras oltId = parts[2] + '_' + parts[3]; // "olt_dito" serialNumber = parts.slice(4).join('_'); // Serial pode ter underscores } else { // Fallback - caso não siga o padrão esperado oltId = parts[2]; serialNumber = parts.slice(3).join('_'); } await this.bot.answerCallbackQuery(callbackQuery.id); await this.bot.editMessageText( `🔍 **Buscando ONU ${serialNumber}**\n\n` + `🏢 OLT: ${oltId.replace('olt_', '').replace(/_/g, '\\_').toUpperCase()}\n` + `📱 Serial: ${serialNumber}\n\n` + `⏳ Localizando ONU e service-port...`, { chat_id: chatId, message_id: callbackQuery.message.message_id, parse_mode: 'Markdown' } ); try { const result = await this.ssh.removeOnuBySerial(oltId, serialNumber); if (result.success && result.readyForRemoval) { // ONU encontrada - mostrar informações e pedir confirmação // O service-port NÃO é incluído no callback_data pois removeOnu busca todos automaticamente const keyboard = { inline_keyboard: [ [ { text: '✅ CONFIRMAR REMOÇÃO', callback_data: `confirm_remove_${oltId}_${result.slot.replace(/\//g, '-')}_${result.onuId}_${serialNumber}` } ], [ { text: '❌ Cancelar', callback_data: 'cancel_action' } ] ] }; const servicePortsInfo = result.servicePorts && result.servicePorts.length > 1 ? `🔌 **Service-ports:** ${result.servicePorts.join(', ')} *(${result.servicePorts.length} encontrados — todos serão removidos)*` : `🔌 **Service-port:** ${result.servicePort}`; await this.bot.editMessageText( `⚠️ **CONFIRMAÇÃO DE REMOÇÃO**\n\n` + `📱 **Serial:** ${serialNumber}\n` + `📍 **Localização:** Slot ${result.slot}, ONU ${result.onuId}\n` + `${servicePortsInfo}\n` + `🏢 **OLT:** ${oltId.replace('olt_', '').replace(/_/g, '\\_').toUpperCase()}\n\n` + `⚠️ **ATENÇÃO:** Esta ação não pode ser desfeita!\n` + `A ONU será completamente removida da OLT.\n\n` + `🔴 **Confirme que deseja remover esta ONU:**`, { chat_id: chatId, message_id: callbackQuery.message.message_id, parse_mode: 'Markdown', reply_markup: keyboard } ); } else { await this.bot.editMessageText( `❌ **ONU não encontrada**\n\n` + `📱 **Serial:** ${serialNumber}\n` + `🏢 **OLT:** ${oltId.replace('olt_', '').replace(/_/g, '\\_').toUpperCase()}\n\n` + `❌ **Erro:** ${result.message}\n\n` + `💡 Verifique se o serial está correto ou se a ONU existe nesta OLT`, { chat_id: chatId, message_id: callbackQuery.message.message_id, parse_mode: 'Markdown' } ); } } catch (error) { console.error('Erro ao buscar ONU para remoção:', error); await this.bot.editMessageText( `❌ **Erro interno**\n\n` + `Não foi possível buscar a ONU.\n` + `Erro: ${error.message}`, { chat_id: chatId, message_id: callbackQuery.message.message_id, parse_mode: 'Markdown' } ); } } /** * Handle callback para confirmar remoção de ONU */ async handleConfirmRemoveCallback(callbackQuery, data) { const chatId = callbackQuery.message.chat.id; console.log(`🔍 handleConfirmRemoveCallback chamado com data: ${data}`); // Novo formato (sem servicePort): confirm_remove_olt_dito_[slot]_[onuId]_[serialNumber] const parts = data.split('_'); console.log(`🔍 Parts: ${JSON.stringify(parts)}`); let oltId, slot, onuId, serialNumber; if (parts.length >= 8 && parts[3] === 'pedro' && parts[4] === 'afonso') { // Caso: confirm_remove_olt_pedro_afonso_slot_onuId_serialNumber oltId = parts[2] + '_' + parts[3] + '_' + parts[4]; // "olt_pedro_afonso" slot = parts[5].replace(/-/g, '/'); onuId = parts[6]; serialNumber = parts.slice(7).join('_'); } else if (parts.length >= 7) { // Caso: confirm_remove_olt_dito_slot_onuId_serialNumber oltId = parts[2] + '_' + parts[3]; // "olt_dito" slot = parts[4].replace(/-/g, '/'); onuId = parts[5]; serialNumber = parts.slice(6).join('_'); } else { // Fallback oltId = parts[2]; slot = parts[3].replace(/-/g, '/'); onuId = parts[4]; serialNumber = parts.slice(5).join('_'); } console.log(`🔍 Parsed - oltId: ${oltId}, slot: ${slot}, onuId: ${onuId}, serialNumber: ${serialNumber}`); await this.bot.answerCallbackQuery(callbackQuery.id); await this.bot.editMessageText( `🗑️ **Removendo ONU...**\n\n` + `📱 **Serial:** ${serialNumber}\n` + `📍 **Localização:** Slot ${slot}, ONU ${onuId}\n` + `🏢 **OLT:** ${oltId.replace('olt_', '').replace(/_/g, '\\_').toUpperCase()}\n\n` + `⏳ Buscando e removendo todos os service-ports...`, { chat_id: chatId, message_id: callbackQuery.message.message_id, parse_mode: 'Markdown' } ); try { const result = await this.ssh.removeOnu(oltId, slot, onuId); if (result.success) { const portsRemovidos = result.servicePorts && result.servicePorts.length > 0 ? result.servicePorts.join(', ') : 'N/A'; const portsLabel = result.servicePorts && result.servicePorts.length > 1 ? `• Removidos ${result.servicePorts.length} service-ports (${portsRemovidos})` : `• Removido service-port ${portsRemovidos}`; await this.bot.editMessageText( `✅ **ONU Removida com Sucesso!**\n\n` + `📱 **Serial:** ${serialNumber}\n` + `📍 **Localização:** Slot ${slot}, ONU ${onuId}\n` + `🏢 **OLT:** ${oltId.replace('olt_', '').replace(/_/g, '\\_').toUpperCase()}\n\n` + `✅ **Status:** ONU removida completamente\n` + `🗑️ **Ações realizadas:**\n` + `• Removida interface ONU\n` + `${portsLabel}\n` + `• Configuração commitada\n\n` + `💡 A ONU pode ser reconfigurada quando necessário`, { chat_id: chatId, message_id: callbackQuery.message.message_id, parse_mode: 'Markdown' } ); } else { await this.bot.editMessageText( `❌ **Erro na Remoção**\n\n` + `📱 **Serial:** ${serialNumber}\n` + `📍 **Localização:** Slot ${slot}, ONU ${onuId}\n` + `🏢 **OLT:** ${oltId.replace('olt_', '').replace(/_/g, '\\_').toUpperCase()}\n\n` + `❌ **Erro:** ${result.message}\n\n` + `🔄 Verifique o status da ONU ou tente novamente`, { chat_id: chatId, message_id: callbackQuery.message.message_id, parse_mode: 'Markdown' } ); } } catch (error) { console.error('Erro ao remover ONU:', error); await this.bot.editMessageText( `❌ **Erro Interno**\n\n` + `Não foi possível remover a ONU.\n` + `Erro: ${error.message}`, { chat_id: chatId, message_id: callbackQuery.message.message_id, parse_mode: 'Markdown' } ); } } /** * Handle callback para confirmar remoção de ONU Fiberhome via TL1 */ async handleConfirmRemoveFiberhomeCallback(callbackQuery, data) { const chatId = callbackQuery.message.chat.id; console.log(`🔍 handleConfirmRemoveFiberhomeCallback chamado com data: ${data}`); // Formato: confirm_remove_fh_SERIAL const serialNumber = data.replace('confirm_remove_fh_', '').toUpperCase(); console.log(`🔍 Serial a remover: ${serialNumber}`); await this.bot.answerCallbackQuery(callbackQuery.id); // Escapar caracteres especiais const serialEscaped = serialNumber.replace(/_/g, '\\_'); await this.bot.editMessageText( `🗑️ *Removendo ONU Fiberhome...*\n\n` + `📱 *Serial:* ${serialEscaped}\n\n` + `⏳ Aguarde...`, { chat_id: chatId, message_id: callbackQuery.message.message_id, parse_mode: 'Markdown' } ); try { // Executar comando TL1 de remoção const resultado = await this.fiberhomeTL1.removerOnuFiberhome(serialNumber); if (resultado.success) { await this.bot.editMessageText( `✅ *ONU Fiberhome Removida com Sucesso!*\n\n` + `📱 *Serial:* ${serialEscaped}\n` + `🏢 *OLT:* ${resultado.details.olt.replace(/_/g, '\\_')}\n` + `📡 *Porta:* ${resultado.details.porta}\n\n` + `🔄 A ONU foi removida do sistema.`, { chat_id: chatId, message_id: callbackQuery.message.message_id, parse_mode: 'Markdown' } ); } else { await this.bot.editMessageText( `❌ *Erro na Remoção*\n\n` + `📱 *Serial:* ${serialEscaped}\n\n` + `⚠️ *Erro:* ${resultado.error}\n\n` + `🔄 Verifique o status da ONU ou tente novamente`, { chat_id: chatId, message_id: callbackQuery.message.message_id, parse_mode: 'Markdown' } ); } } catch (error) { console.error('❌ Erro ao remover ONU Fiberhome:', error); await this.bot.editMessageText( `❌ *Erro Interno*\n\n` + `📱 *Serial:* ${serialEscaped}\n\n` + `⚠️ ${error.message}`, { chat_id: chatId, message_id: callbackQuery.message.message_id, parse_mode: 'Markdown' } ); } } /** * Handle callback para iniciar processo de adição de ONU */ /** * Handler: remove ONU já cadastrada e tenta readicioná-la automaticamente. * Formatos de callback_data: * remove_readd____ * remove_readd_vlan_____ */ async handleRemoveAndReAddCallback(callbackQuery, data) { const chatId = callbackQuery.message.chat.id; const hasVlan = data.startsWith('remove_readd_vlan_'); const parts = data.split('_'); let oltId, slot, onuId, vlanId, serialNumber; if (hasVlan) { // remove_readd_vlan_olt_pedro_afonso_slot_onuId_vlanId_serial // remove_readd_vlan_olt_dito_slot_onuId_vlanId_serial // parts[0]=remove [1]=readd [2]=vlan [3]=olt [4]=dito|pedro [5]=afonso|slot ... if (parts[4] === 'pedro' && parts[5] === 'afonso') { oltId = parts[3] + '_' + parts[4] + '_' + parts[5]; slot = parts[6].replace(/-/g, '/'); onuId = parts[7]; vlanId = parts[8]; serialNumber = parts.slice(9).join('_'); } else { oltId = parts[3] + '_' + parts[4]; slot = parts[5].replace(/-/g, '/'); onuId = parts[6]; vlanId = parts[7]; serialNumber = parts.slice(8).join('_'); } } else { // remove_readd_olt_pedro_afonso_slot_onuId_serial // remove_readd_olt_dito_slot_onuId_serial // parts[0]=remove [1]=readd [2]=olt [3]=dito|pedro [4]=afonso|slot ... if (parts[3] === 'pedro' && parts[4] === 'afonso') { oltId = parts[2] + '_' + parts[3] + '_' + parts[4]; slot = parts[5].replace(/-/g, '/'); onuId = parts[6]; serialNumber = parts.slice(7).join('_'); } else { oltId = parts[2] + '_' + parts[3]; slot = parts[4].replace(/-/g, '/'); onuId = parts[5]; serialNumber = parts.slice(6).join('_'); } } console.log(`🗑️➕ remove_readd - oltId: ${oltId}, slot: ${slot}, onuId: ${onuId}, vlanId: ${vlanId || 'auto'}, serial: ${serialNumber}`); await this.bot.answerCallbackQuery(callbackQuery.id); await this.bot.editMessageText( `🗑️ **Removendo ONU cadastrada...**\n\n` + `📱 **Serial:** ${serialNumber}\n` + `📍 **Slot:** ${slot}, ONU ${onuId}\n` + `🏢 **OLT:** ${oltId.replace('olt_', '').replace(/_/g, '\\_').toUpperCase()}\n\n` + `⏳ Aguarde...`, { chat_id: chatId, message_id: callbackQuery.message.message_id, parse_mode: 'Markdown' } ); try { const removeResult = await this.ssh.removeOnu(oltId, slot, onuId); if (!removeResult.success) { await this.bot.editMessageText( `❌ **Erro ao remover ONU**\n\n` + `📱 **Serial:** ${serialNumber}\n` + `🏢 **OLT:** ${oltId.replace('olt_', '').replace(/_/g, '\\_').toUpperCase()}\n\n` + `❌ **Erro:** ${removeResult.message}\n\n` + `🔄 Verifique o status da ONU e tente novamente.`, { chat_id: chatId, message_id: callbackQuery.message.message_id, parse_mode: 'Markdown' } ); return; } // ONU removida — aguardar brevemente e tentar redescobrir await this.bot.editMessageText( `✅ **ONU removida!** Aguardando redescoberta...\n\n` + `📱 **Serial:** ${serialNumber}\n` + `🏢 **OLT:** ${oltId.replace('olt_', '').replace(/_/g, '\\_').toUpperCase()}\n\n` + `⏳ Verificando discovered-onus...`, { chat_id: chatId, message_id: callbackQuery.message.message_id, parse_mode: 'Markdown' } ); // Aguardar 4 segundos para a ONU voltar à lista de discovered await new Promise(resolve => setTimeout(resolve, 4000)); const addResult = await this.ssh.addOnuBySerial(oltId, serialNumber, vlanId || undefined); if (addResult.success && addResult.readyForAddition) { const effectiveVlan = vlanId || addResult.vlanId; const keyboard = { inline_keyboard: [ [ { text: '✅ CONFIRMAR ADIÇÃO', callback_data: `confirm_add_${oltId}_${addResult.slot.replace(/\//g, '-')}_${addResult.onuId}_${effectiveVlan}_${serialNumber}` } ], [ { text: '❌ Cancelar', callback_data: 'cancel_action' } ] ] }; await this.bot.editMessageText( `✅ **ONU pronta para adição!**\n\n` + `📱 **Serial:** ${serialNumber}\n` + `📍 **Slot descoberto:** ${addResult.slot}\n` + `🔢 **ONU ID a usar:** ${addResult.onuId}\n` + `🏷️ **VLAN ID:** ${effectiveVlan}${vlanId ? ' (customizada)' : ''}\n` + `🏢 **OLT:** ${oltId.replace('olt_', '').replace(/_/g, '\\_').toUpperCase()}\n\n` + `📋 **ONU IDs em uso no slot:** ${addResult.usedIds.join(', ')}\n\n` + `✅ **Confirme que deseja adicionar esta ONU:**`, { chat_id: chatId, message_id: callbackQuery.message.message_id, parse_mode: 'Markdown', reply_markup: keyboard } ); } else { // ONU não apareceu em discovered-onus ainda await this.bot.editMessageText( `✅ **ONU removida com sucesso!**\n\n` + `📱 **Serial:** ${serialNumber}\n` + `🏢 **OLT:** ${oltId.replace('olt_', '').replace(/_/g, '\\_').toUpperCase()}\n\n` + `⚠️ A ONU ainda não apareceu como disponível para adição.\n\n` + `💡 Aguarde alguns instantes e tente:\n` + `\`adicionar onu ${serialNumber}\``, { chat_id: chatId, message_id: callbackQuery.message.message_id, parse_mode: 'Markdown' } ); } } catch (error) { console.error('Erro em handleRemoveAndReAddCallback:', error); await this.bot.editMessageText( `❌ **Erro interno**\n\n` + `Não foi possível concluir a operação.\n` + `Erro: ${error.message}`, { chat_id: chatId, message_id: callbackQuery.message.message_id, parse_mode: 'Markdown' } ); } } /** * Handle callback para iniciar processo de adição de ONU */ async handleAddOnuCallback(callbackQuery, data) { const chatId = callbackQuery.message.chat.id; const parts = data.split('_'); // add_onu_olt_dito_[serialNumber] ou add_onu_olt_pedro_afonso_[serialNumber] let oltId, serialNumber; if (parts.length >= 6 && parts[3] === 'pedro' && parts[4] === 'afonso') { // Caso: add_onu_olt_pedro_afonso_SERIAL oltId = parts[2] + '_' + parts[3] + '_' + parts[4]; // "olt_pedro_afonso" serialNumber = parts.slice(5).join('_'); // Serial pode ter underscores } else if (parts.length >= 5) { // Caso: add_onu_olt_dito_SERIAL ou outras OLTs de duas palavras oltId = parts[2] + '_' + parts[3]; // "olt_dito" serialNumber = parts.slice(4).join('_'); // Serial pode ter underscores } else { // Fallback - caso não siga o padrão esperado oltId = parts[2]; serialNumber = parts.slice(3).join('_'); } await this.bot.answerCallbackQuery(callbackQuery.id); await this.bot.editMessageText( `🔍 **Analisando ONU ${serialNumber}**\n\n` + `🏢 OLT: ${oltId.replace('olt_', '').replace(/_/g, '\\_').toUpperCase()}\n` + `📱 Serial: ${serialNumber}\n\n` + `⏳ Descobrindo slot e próximo ONU ID disponível...`, { chat_id: chatId, message_id: callbackQuery.message.message_id, parse_mode: 'Markdown' } ); try { const result = await this.ssh.addOnuBySerial(oltId, serialNumber); if (result.success && result.readyForAddition) { // ONU analisada - mostrar informações e pedir confirmação const keyboard = { inline_keyboard: [ [ { text: '✅ CONFIRMAR ADIÇÃO', callback_data: `confirm_add_${oltId}_${result.slot.replace(/\//g, '-')}_${result.onuId}_${result.vlanId}_${serialNumber}` } ], [ { text: '❌ Cancelar', callback_data: 'cancel_action' } ] ] }; await this.bot.editMessageText( `✅ **CONFIRMAÇÃO DE ADIÇÃO**\n\n` + `📱 **Serial:** ${serialNumber}\n` + `📍 **Slot descoberto:** ${result.slot}\n` + `🔢 **ONU ID a usar:** ${result.onuId}\n` + `🏷️ **VLAN ID:** ${result.vlanId}\n` + `🏢 **OLT:** ${oltId.replace('olt_', '').toUpperCase()}\n\n` + `📋 **ONU IDs em uso no slot:** ${result.usedIds.join(', ')}\n\n` + `⚡ **Configurações que serão aplicadas:**\n` + `• Line-profile: DM986-100\n` + `• Ethernet: porta 1\n` + `• Native VLAN: ${result.vlanId}\n` + `• Service-port: novo (auto)\n\n` + `✅ **Confirme que deseja adicionar esta ONU:**`, { chat_id: chatId, message_id: callbackQuery.message.message_id, parse_mode: 'Markdown', reply_markup: keyboard } ); } else { // ONU não encontrada em discovered-onus — verificar se já está cadastrada no running-config if (result.message && result.message.includes('discovered-onus')) { await this.bot.editMessageText( `🔍 **ONU não disponível para adição**\n\n` + `📱 **Serial:** ${serialNumber}\n` + `🏢 **OLT:** ${oltId.replace('olt_', '').toUpperCase()}\n\n` + `⏳ Verificando se a ONU já está cadastrada...`, { chat_id: chatId, message_id: callbackQuery.message.message_id, parse_mode: 'Markdown' } ); const findResult = await this.ssh.findOnuBySerial(oltId, serialNumber); if (findResult.success && findResult.onuInfo && findResult.onuInfo.found) { const { slot, onuId } = findResult.onuInfo; const keyboard = { inline_keyboard: [ [ { text: '🗑️ Remover e adicionar novamente', callback_data: `remove_readd_${oltId}_${slot.replace(/\//g, '-')}_${onuId}_${serialNumber}` } ], [ { text: '❌ Cancelar', callback_data: 'cancel_action' } ] ] }; await this.bot.editMessageText( `⚠️ **ONU já cadastrada na OLT**\n\n` + `📱 **Serial:** ${serialNumber}\n` + `📍 **Localização atual:** Slot ${slot}, ONU ${onuId}\n` + `🏢 **OLT:** ${oltId.replace('olt_', '').toUpperCase()}\n\n` + `A ONU já está cadastrada e não aparece como disponível para adição.\n\n` + `Deseja **remover** o cadastro atual e adicioná-la novamente?`, { chat_id: chatId, message_id: callbackQuery.message.message_id, parse_mode: 'Markdown', reply_markup: keyboard } ); } else { await this.bot.editMessageText( `❌ **ONU não disponível**\n\n` + `📱 **Serial:** ${serialNumber}\n` + `🏢 **OLT:** ${oltId.replace('olt_', '').toUpperCase()}\n\n` + `A ONU não foi encontrada em discovered-onus nem no running-config.\n\n` + `💡 **Dica:** Verifique se a ONU está ligada e conectada à OLT.\n` + `Use o comando: \`show interface gpon discovered-onus\``, { chat_id: chatId, message_id: callbackQuery.message.message_id, parse_mode: 'Markdown' } ); } } else { let errorMessage = `❌ **Não foi possível adicionar a ONU**\n\n` + `📱 **Serial:** ${serialNumber}\n` + `🏢 **OLT:** ${oltId.replace('olt_', '').toUpperCase()}\n\n`; if (result.message) errorMessage += `❌ **Erro:** ${result.message}\n\n`; if (result.message && result.message.includes('todos os ONU IDs')) { errorMessage += `💡 **Dica:** O slot está lotado. Considere usar outro slot ou remover ONUs inutilizadas.`; } else { errorMessage += `💡 Verifique a conectividade com a OLT ou se a ONU está descoberta.`; } await this.bot.editMessageText(errorMessage, { chat_id: chatId, message_id: callbackQuery.message.message_id, parse_mode: 'Markdown' }); } } } catch (error) { console.error('Erro ao analisar ONU para adição:', error); await this.bot.editMessageText( `❌ **Erro interno**\n\n` + `Não foi possível analisar a ONU.\n` + `Erro: ${error.message}`, { chat_id: chatId, message_id: callbackQuery.message.message_id, parse_mode: 'Markdown' } ); } } /** * Handle callback para iniciar processo de adição de ONU com VLAN customizada */ async handleAddOnuVlanCallback(callbackQuery, data) { const chatId = callbackQuery.message.chat.id; // Formato: add_onu_vlan_olt_dito_603_SERIAL ou add_onu_vlan_olt_pedro_afonso_603_SERIAL // Índices: [0]_[1]_[2]_[3]_[4]_[5]_[6] const parts = data.split('_'); let oltId, vlanId, serialNumber; if (parts.length >= 9 && parts[4] === 'pedro' && parts[5] === 'afonso') { // Caso: add_onu_vlan_olt_pedro_afonso_VLANID_SERIAL oltId = parts[3] + '_' + parts[4] + '_' + parts[5]; // "olt_pedro_afonso" vlanId = parts[6]; serialNumber = parts.slice(7).join('_'); // Serial pode ter underscores } else if (parts.length >= 7) { // Caso: add_onu_vlan_olt_dito_VLANID_SERIAL ou add_onu_vlan_olt_colmeia_VLANID_SERIAL oltId = parts[3] + '_' + parts[4]; // "olt_dito" ou "olt_colmeia" vlanId = parts[5]; serialNumber = parts.slice(6).join('_'); // Serial pode ter underscores } else { // Fallback oltId = parts[3]; vlanId = parts[4]; serialNumber = parts.slice(5).join('_'); } await this.bot.answerCallbackQuery(callbackQuery.id); await this.bot.editMessageText( `🔍 **Analisando ONU ${serialNumber}**\n\n` + `🏢 OLT: ${oltId.replace('olt_', '').replace(/_/g, '\\_').toUpperCase()}\n` + `📱 Serial: ${serialNumber}\n` + `🏷️ VLAN customizada: ${vlanId}\n\n` + `⏳ Descobrindo slot e próximo ONU ID disponível...`, { chat_id: chatId, message_id: callbackQuery.message.message_id, parse_mode: 'Markdown' } ); try { // Usar a mesma função addOnuBySerial mas sem a vlanId (vamos usar a customizada) const result = await this.ssh.addOnuBySerial(oltId, serialNumber); if (result.success && result.readyForAddition) { // ONU analisada - mostrar informações e pedir confirmação const keyboard = { inline_keyboard: [ [ { text: '✅ CONFIRMAR ADIÇÃO', callback_data: `confirm_add_${oltId}_${result.slot.replace(/\//g, '-')}_${result.onuId}_${vlanId}_${serialNumber}` } ], [ { text: '❌ Cancelar', callback_data: 'cancel_action' } ] ] }; await this.bot.editMessageText( `✅ **CONFIRMAÇÃO DE ADIÇÃO COM VLAN CUSTOMIZADA**\n\n` + `📱 **Serial:** ${serialNumber}\n` + `📍 **Slot descoberto:** ${result.slot}\n` + `🔢 **ONU ID a usar:** ${result.onuId}\n` + `🏷️ **VLAN ID customizada:** ${vlanId}\n` + `🏢 **OLT:** ${oltId.replace('olt_', '').replace(/_/g, '\\_').toUpperCase()}\n\n` + `📋 **ONU IDs em uso no slot:** ${result.usedIds.join(', ')}\n\n` + `⚡ **Configurações que serão aplicadas:**\n` + `• Line-profile: DM986-100\n` + `• Ethernet: porta 1\n` + `• Native VLAN: ${vlanId} (customizada)\n` + `• Service-port: novo (auto) com VLAN ${vlanId}\n\n` + `✅ **Confirme que deseja adicionar esta ONU:**`, { chat_id: chatId, message_id: callbackQuery.message.message_id, parse_mode: 'Markdown', reply_markup: keyboard } ); } else { // ONU não encontrada em discovered-onus — verificar se já está cadastrada no running-config if (result.message && result.message.includes('discovered-onus')) { await this.bot.editMessageText( `🔍 **ONU não disponível para adição**\n\n` + `📱 **Serial:** ${serialNumber}\n` + `🏷️ **VLAN:** ${vlanId}\n` + `🏢 **OLT:** ${oltId.replace('olt_', '').replace(/_/g, '\\_').toUpperCase()}\n\n` + `⏳ Verificando se a ONU já está cadastrada...`, { chat_id: chatId, message_id: callbackQuery.message.message_id, parse_mode: 'Markdown' } ); const findResult = await this.ssh.findOnuBySerial(oltId, serialNumber); if (findResult.success && findResult.onuInfo && findResult.onuInfo.found) { const { slot, onuId } = findResult.onuInfo; const keyboard = { inline_keyboard: [ [ { text: '🗑️ Remover e adicionar novamente', callback_data: `remove_readd_vlan_${oltId}_${slot.replace(/\//g, '-')}_${onuId}_${vlanId}_${serialNumber}` } ], [ { text: '❌ Cancelar', callback_data: 'cancel_action' } ] ] }; await this.bot.editMessageText( `⚠️ **ONU já cadastrada na OLT**\n\n` + `📱 **Serial:** ${serialNumber}\n` + `📍 **Localização atual:** Slot ${slot}, ONU ${onuId}\n` + `🏷️ **VLAN a usar:** ${vlanId}\n` + `🏢 **OLT:** ${oltId.replace('olt_', '').replace(/_/g, '\\_').toUpperCase()}\n\n` + `A ONU já está cadastrada e não aparece como disponível para adição.\n\n` + `Deseja **remover** o cadastro atual e adicioná-la novamente com VLAN ${vlanId}?`, { chat_id: chatId, message_id: callbackQuery.message.message_id, parse_mode: 'Markdown', reply_markup: keyboard } ); } else { await this.bot.editMessageText( `❌ **ONU não disponível**\n\n` + `📱 **Serial:** ${serialNumber}\n` + `🏷️ **VLAN:** ${vlanId}\n` + `🏢 **OLT:** ${oltId.replace('olt_', '').replace(/_/g, '\\_').toUpperCase()}\n\n` + `A ONU não foi encontrada em discovered-onus nem no running-config.\n\n` + `💡 **Dica:** Verifique se a ONU está ligada e conectada à OLT.\n` + `Use o comando: \`show interface gpon discovered-onus\``, { chat_id: chatId, message_id: callbackQuery.message.message_id, parse_mode: 'Markdown' } ); } } else { let errorMessage = `❌ **Não foi possível adicionar a ONU**\n\n` + `📱 **Serial:** ${serialNumber}\n` + `🏷️ **VLAN:** ${vlanId}\n` + `🏢 **OLT:** ${oltId.replace('olt_', '').replace(/_/g, '\\_').toUpperCase()}\n\n`; if (result.message) errorMessage += `❌ **Erro:** ${result.message}\n\n`; if (result.message && result.message.includes('todos os ONU IDs')) { errorMessage += `💡 **Dica:** O slot está lotado. Considere usar outro slot ou remover ONUs inutilizadas.`; } else { errorMessage += `💡 Verifique a conectividade com a OLT ou se a ONU está descoberta.`; } await this.bot.editMessageText(errorMessage, { chat_id: chatId, message_id: callbackQuery.message.message_id, parse_mode: 'Markdown' }); } } } catch (error) { console.error('Erro ao analisar ONU para adição com VLAN customizada:', error); await this.bot.editMessageText( `❌ **Erro interno**\n\n` + `Não foi possível analisar a ONU.\n` + `Erro: ${error.message}`, { chat_id: chatId, message_id: callbackQuery.message.message_id, parse_mode: 'Markdown' } ); } } /** * Handle callback para tipo de ONU Fiberhome */ async handleFiberhomeOnuTypeCallback(callbackQuery, data) { const chatId = callbackQuery.message.chat.id; const userId = callbackQuery.from.id; // Formato: fh_onu_type_AN5506-02-B ou fh_onu_type_AN5506-01-A const onuType = data.replace('fh_onu_type_', ''); // Recuperar sessão const session = this.userSessions.get(userId); if (!session || session.action !== 'adicionar_onu_fiberhome_step2') { await this.bot.answerCallbackQuery(callbackQuery.id, '❌ Sessão expirada'); await this.bot.sendMessage(chatId, '❌ Sessão expirada. Por favor, comece novamente.'); this.userSessions.delete(userId); return; } session.onuType = onuType; session.action = 'adicionar_onu_fiberhome_step3'; this.userSessions.set(userId, session); await this.bot.answerCallbackQuery(callbackQuery.id); // Perguntar sobre VLAN const keyboard = { inline_keyboard: [ [{ text: '✅ Usar VLAN 1002 (padrão)', callback_data: 'fh_vlan_1002' }], [{ text: '🔧 Usar outra VLAN', callback_data: 'fh_vlan_custom' }], [{ text: '❌ Cancelar', callback_data: 'cancel_action' }] ] }; await this.bot.editMessageText( `🔌 **Tipo da ONU:** ${onuType}\n\n` + `🏷️ **VLAN:**\n` + `A VLAN padrão é 1002. Deseja usar esta VLAN ou configurar outra?`, { chat_id: chatId, message_id: callbackQuery.message.message_id, reply_markup: keyboard, parse_mode: 'Markdown' } ); } /** * Handle callback para VLAN Fiberhome */ async handleFiberhomeVlanCallback(callbackQuery, data) { const chatId = callbackQuery.message.chat.id; const userId = callbackQuery.from.id; // Recuperar sessão const session = this.userSessions.get(userId); if (!session || session.action !== 'adicionar_onu_fiberhome_step3') { await this.bot.answerCallbackQuery(callbackQuery.id, '❌ Sessão expirada'); await this.bot.sendMessage(chatId, '❌ Sessão expirada. Por favor, comece novamente.'); this.userSessions.delete(userId); return; } await this.bot.answerCallbackQuery(callbackQuery.id); if (data === 'fh_vlan_1002') { // Usar VLAN padrão 1002 session.vlan = 1002; // Limpar sessão this.userSessions.delete(userId); // Executar adição da ONU await this.executarAdicaoOnuFiberhome(chatId, session, userId); } else if (data === 'fh_vlan_custom') { // Pedir VLAN customizada - manter sessão no step3 para receber input do usuário await this.bot.editMessageText( `🏷️ **VLAN Customizada**\n\n` + `Digite o número da VLAN que deseja usar (1-4094):`, { chat_id: chatId, message_id: callbackQuery.message.message_id, parse_mode: 'Markdown' } ); } } /** * Handle callback para confirmar adição de ONU */ async handleConfirmAddCallback(callbackQuery, data) { const chatId = callbackQuery.message.chat.id; const userId = callbackQuery.from.id; console.log(`🔍 handleConfirmAddCallback chamado com data: ${data}`); // Parse do formato: confirm_add_olt_dito_1-1-3_0_603_DACM915E0F76 const parts = data.split('_'); console.log(`🔍 Parts: ${JSON.stringify(parts)}`); let oltId, slot, onuId, vlanId, serialNumber; if (parts.length >= 8 && parts[2] === 'olt' && parts[3] === 'pedro' && parts[4] === 'afonso') { // Caso: confirm_add_olt_pedro_afonso_1-1-3_0_603_DACM915E0F76 oltId = parts[2] + '_' + parts[3] + '_' + parts[4]; // "olt_pedro_afonso" slot = parts[5].replace(/-/g, '/'); // Converter de volta: "1-1-3" -> "1/1/3" onuId = parts[6]; vlanId = parts[7]; serialNumber = parts.slice(8).join('_'); // Serial pode ter underscores } else if (parts.length >= 7) { // Caso padrão: confirm_add_olt_dito_1-1-3_0_603_DACM915E0F76 oltId = parts[2] + '_' + parts[3]; // "olt_dito" slot = parts[4].replace(/-/g, '/'); // Converter de volta: "1-1-3" -> "1/1/3" onuId = parts[5]; vlanId = parts[6]; serialNumber = parts.slice(7).join('_'); // Serial pode ter underscores } else if (parts.length >= 6) { // Fallback para OLTs com nome simples oltId = parts[2]; // "olt" slot = parts[3].replace(/-/g, '/'); // Converter de volta onuId = parts[4]; vlanId = parts[5]; serialNumber = parts.slice(6).join('_'); } else { console.error(`❌ Formato de callback inválido: ${data}`); await this.bot.answerCallbackQuery(callbackQuery.id, { text: 'Erro no formato do callback' }); return; } console.log(`🔍 Parsed - oltId: ${oltId}, slot: ${slot}, onuId: ${onuId}, vlanId: ${vlanId}, serialNumber: ${serialNumber}`); await this.bot.answerCallbackQuery(callbackQuery.id); await this.bot.editMessageText( `➕ **Adicionando ONU...**\n\n` + `📱 **Serial:** ${serialNumber}\n` + `📍 **Localização:** Slot ${slot}, ONU ${onuId}\n` + `🏷️ **VLAN:** ${vlanId}\n` + `🏢 **OLT:** ${oltId.replace('olt_', '').toUpperCase()}\n\n` + `⏳ Executando comandos de configuração...\n` + `⚡ Este processo pode levar até 90 segundos`, { chat_id: chatId, message_id: callbackQuery.message.message_id, parse_mode: 'Markdown' } ); try { const result = await this.ssh.addOnu(oltId, slot, onuId, serialNumber, parseInt(vlanId)); if (result.success) { const servicePortInfo = result.servicePortId ? `🔌 **Service-port:** ${result.servicePortId} (Auto-gerado)` : `🔌 **Service-port:** Criado automaticamente`; await this.bot.editMessageText( `✅ **ONU Adicionada com Sucesso!**\n\n` + `📱 **Serial:** ${serialNumber}\n` + `📍 **Localização:** Slot ${slot}, ONU ${onuId}\n` + `🏷️ **VLAN:** ${vlanId}\n` + `${servicePortInfo}\n` + `🏢 **OLT:** ${oltId.replace('olt_', '').toUpperCase()}\n\n` + `✅ **Configurações aplicadas:**\n` + `• Line-profile: DM986-100\n` + `• Serial number: ${serialNumber}\n` + `• Ethernet: porta 1\n` + `• Native VLAN: ${vlanId}\n` + `• Service-port: ${result.servicePortId || 'auto-gerado'} criado e configurado\n` + `• Configuração commitada\n\n` + `🎉 A ONU está pronta para uso!`, { chat_id: chatId, message_id: callbackQuery.message.message_id, parse_mode: 'Markdown' } ); // Convidar para registrar no TopSapp const tsLocation = oltId.includes('pedro_afonso') ? 'pedro_afonso' : 'guarai_colmeia'; const oltEquipment = this.ssh.equipamentos.get(oltId); this.userSessions.set(userId, { action: 'topsapp_onu_pre_busca', onuData: { tipo: 'datacom', location: tsLocation, oltId, oltHost: oltEquipment ? oltEquipment.host : '', serial: serialNumber, onuId, servicePort: result.servicePortId ? parseInt(result.servicePortId) : null, portaAlias: slot } }); await this.bot.sendMessage(chatId, `📋 *Registrar no TopSapp?*\n\nDeseja vincular esta ONU a um cliente no sistema TopSapp?`, { parse_mode: 'Markdown', reply_markup: { inline_keyboard: [[ { text: '✅ Sim, vincular', callback_data: 'topsapp_onu_sim' }, { text: '❌ Não', callback_data: 'cancel_action' } ]] } } ); } else { await this.bot.editMessageText( `❌ **Erro na Adição**\n\n` + `📱 **Serial:** ${serialNumber}\n` + `📍 **Localização:** Slot ${slot}, ONU ${onuId}\n` + `🏷️ **VLAN:** ${vlanId}\n` + `🏢 **OLT:** ${oltId.replace('olt_', '').toUpperCase()}\n\n` + `❌ **Erro:** ${result.message}\n\n` + `🔄 Verifique a conectividade com a OLT ou tente novamente`, { chat_id: chatId, message_id: callbackQuery.message.message_id, parse_mode: 'Markdown' } ); } } catch (error) { console.error('Erro ao adicionar ONU:', error); await this.bot.editMessageText( `❌ **Erro Interno**\n\n` + `Não foi possível adicionar a ONU.\n` + `Erro: ${error.message}`, { chat_id: chatId, message_id: callbackQuery.message.message_id, parse_mode: 'Markdown' } ); } } /** * Painel administrativo */ async handleAdmin(chatId) { const userId = chatId; // Para chats individuais, chatId = userId // Verificar se é usuário autorizado if (!this.isAuthorized(userId)) { await this.bot.sendMessage(chatId, '🚫 Acesso negado ao painel administrativo.'); return; } await this.bot.sendMessage(chatId, `🛡️ **Painel Administrativo**\n\n` + `👥 **Gestão de Usuários:**\n` + `• \`usuarios autorizados\` - Ver lista de usuários\n` + `• \`meu id\` - Ver seu ID do Telegram\n\n` + `📊 **Sistema:**\n` + `• \`status\` - Status do bot\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` + `• Gerenciar usuários Juniper`, { parse_mode: 'Markdown' } ); } /** * Listar usuários autorizados */ async handleListAuthorizedUsers(chatId) { const userId = chatId; if (!this.isAuthorized(userId)) { await this.bot.sendMessage(chatId, '🚫 Acesso negado.'); return; } const authorizedUsers = config.bot.allowedUsers; if (authorizedUsers.length === 0) { await this.bot.sendMessage(chatId, `👥 **Usuários Autorizados**\n\n` + `⚠️ Nenhuma restrição configurada\n` + `🌐 Bot está aberto para todos os usuários\n\n` + `💡 Configure ALLOWED_USERS no .env para restringir` ); } else { const userList = authorizedUsers.map((id, index) => `${index + 1}. ID: \`${id}\`` ).join('\n'); await this.bot.sendMessage(chatId, `👥 **Usuários Autorizados**\n\n` + `📊 Total: ${authorizedUsers.length} usuário(s)\n\n` + `${userList}\n\n` + `🆔 Seu ID: \`${userId}\``, { parse_mode: 'Markdown' } ); } } /** * Exibir ID do usuário */ async handleMyId(chatId, userId) { await this.bot.sendMessage(chatId, `🆔 **Sua Identificação**\n\n` + `👤 User ID: \`${userId}\`\n` + `💬 Chat ID: \`${chatId}\`\n\n` + `📋 Use estes IDs para configurar permissões`, { parse_mode: 'Markdown' } ); } /** * Status do TopSapp */ async handleStatusTopSapp(chatId) { try { await this.bot.sendMessage(chatId, '🔄 Verificando status do TopSapp...'); const status = this.topsapp.getConfigStatus(); const instances = this.topsapp.getInstances(); let message = `🏢 **STATUS TOPSAPP**\n\n`; if (status.ready) { message += `✅ **Configuração**: OK\n`; message += `📊 **Instâncias configuradas**: ${instances.length}\n\n`; instances.forEach(instance => { message += `🌐 **${instance.location}**\n`; message += `└ ${instance.host}\n`; message += `└ Status: ${status.instances.find(i => i.location === instance.location)?.configured ? '✅ Configurado' : '❌ Não configurado'}\n\n`; }); message += `💡 Use os comandos:\n`; message += `• "buscar cliente João"\n`; message += `• "ver faturas 123.456.789-10"\n`; message += `• "gerar boleto fatura123"\n`; message += `• "bloquear cliente 456"`; } else { message += `❌ **Configuração**: Incompleta\n\n`; message += `⚠️ **Problemas encontrados**:\n`; status.instances.forEach(instance => { if (!instance.configured) { message += `• ${instance.location}: Credenciais não configuradas\n`; } }); message += `\n📝 **Configure no arquivo .env**:\n`; message += `• TOPSAPP_GUARAI_USER\n`; message += `• TOPSAPP_GUARAI_PASS\n`; message += `• TOPSAPP_PEDRO_AFONSO_USER\n`; message += `• TOPSAPP_PEDRO_AFONSO_PASS`; } await this.bot.sendMessage(chatId, message, { parse_mode: 'Markdown' }); } catch (error) { console.error('❌ Erro ao verificar status TopSapp:', error); await this.bot.sendMessage(chatId, `❌ **Erro ao verificar status TopSapp**\n\n` + `⚠️ ${error.message}`, { parse_mode: 'Markdown' } ); } } /** * Buscar cliente no TopSapp */ async handleBuscarCliente(chatId, target) { if (!target) { await this.bot.sendMessage(chatId, `🔍 **Buscar Cliente**\n\n` + `💡 **Como usar:**\n` + `• "buscar cliente João da Silva"\n` + `• "buscar cliente 123.456.789-10"\n` + `• "buscar cliente Guarai João" (específica instância)\n` + `• "buscar cliente Pedro Afonso Maria"` ); return; } try { await this.bot.sendMessage(chatId, '🔍 Buscando cliente...'); // Detectar se especificou uma localização let location = this.getActiveLocation(chatId); let query = target; if (target.toLowerCase().includes('guarai') || target.toLowerCase().includes('colmeia')) { location = 'Guarai/Colmeia'; query = target.replace(/(guarai|colmeia)/gi, '').trim(); } else if (target.toLowerCase().includes('pedro afonso')) { location = 'Pedro Afonso'; query = target.replace(/pedro afonso/gi, '').trim(); } const resultado = await this.topsapp.buscarCliente(query, location); if (!resultado.success) { await this.bot.sendMessage(chatId, `❌ **Erro na busca**\n\n` + `⚠️ ${resultado.error}`, { parse_mode: 'Markdown' } ); return; } if (!resultado.clientes || resultado.clientes.length === 0) { await this.bot.sendMessage(chatId, `🔍 **Nenhum cliente encontrado**\n\n` + `📝 Termo buscado: "${query}"\n` + `🌐 Instância: ${resultado.instancia || 'Todas'}`, { parse_mode: 'Markdown' } ); return; } // Se encontrou mais de um cliente, mostrar lista para seleção if (resultado.clientes.length > 1) { let message = `🔍 **${resultado.clientes.length} clientes encontrados**\n\n`; message += `🌐 **Instância**: ${resultado.instancia}\n\n`; const keyboard = []; resultado.clientes.slice(0, 10).forEach((cliente, index) => { message += `👤 **${index + 1}. ${cliente.nome}**\n`; message += `🆔 ID: \`${cliente.id}\`\n`; if (cliente.cpf) { message += `📄 CPF: \`${cliente.cpf}\`\n`; } message += `📞 ${cliente.telefone || 'Telefone não informado'}\n`; message += `📊 Status: ${cliente.status === 1 ? '✅ Ativo' : '❌ Inativo'}\n\n`; // Adicionar botões para ações keyboard.push([ { text: `📋 Dados ${index + 1}`, callback_data: `dados_cliente_${cliente.id}_${location}` }, { text: `📄 Faturas ${index + 1}`, callback_data: `faturas_cliente_${cliente.id}_${location}` } ]); }); if (resultado.clientes.length > 10) { message += `📋 *Mostrando apenas os primeiros 10 resultados de ${resultado.clientes.length}*\n\n`; } message += `💡 **Selecione uma opção abaixo:**`; await this.bot.sendMessage(chatId, message, { parse_mode: 'Markdown', reply_markup: { inline_keyboard: keyboard } }); return; } // Se encontrou apenas um cliente, mostrar dados detalhados // Se encontrou apenas um cliente, mostrar dados detalhados const cliente = resultado.clientes[0]; let message = `👤 **Cliente Encontrado**\n\n`; message += `🆔 **ID**: \`${cliente.id}\`\n`; message += `👤 **Nome**: ${cliente.nome}\n`; if (cliente.cpf) { message += `📄 **CPF**: \`${cliente.cpf}\`\n`; } message += `📧 **Email**: ${cliente.email || 'Não informado'}\n`; message += `📞 **Telefone**: ${cliente.telefone || 'Não informado'}\n`; message += `📍 **Endereço**: ${cliente.endereco || 'Não informado'}\n`; message += `🏙️ **Cidade**: ${cliente.cidade || 'Não informado'}\n`; message += `📊 **Status**: ${cliente.status === 1 ? '✅ Ativo' : '❌ Inativo'}\n`; message += `🌐 **Instância**: ${resultado.instancia}\n\n`; // Botões de ação const keyboard = [ [ { text: '📋 Dados Cadastrais', callback_data: `dados_cliente_${cliente.id}_${location}` }, { text: '📄 Ver Faturas', callback_data: `faturas_cliente_${cliente.id}_${location}` } ], [ { text: '🔒 Bloquear', callback_data: `confirm_block_${cliente.id}_${location}` }, { text: '🔓 Liberar', callback_data: `confirm_unblock_${cliente.id}_${location}` } ] ]; await this.bot.sendMessage(chatId, message, { parse_mode: 'Markdown', reply_markup: { inline_keyboard: keyboard } }); } catch (error) { console.error('❌ Erro ao buscar cliente:', error); await this.bot.sendMessage(chatId, `❌ **Erro ao buscar cliente**\n\n` + `⚠️ ${error.message}`, { parse_mode: 'Markdown' } ); } } /** * Bloquear cliente */ async handleBloquearCliente(chatId, target) { if (!target) { await this.bot.sendMessage(chatId, `🔒 **Bloquear Cliente**\n\n` + `💡 **Como usar:**\n` + `• "bloquear cliente João da Silva"\n` + `• "bloquear cliente 123.456.789-10"\n` + `• "bloquear Guarai João" (específica instância)` ); return; } try { await this.bot.sendMessage(chatId, '🔍 Buscando cliente...'); // Detectar se especificou uma localização let location = this.getActiveLocation(chatId); let query = target; if (target.toLowerCase().includes('guarai') || target.toLowerCase().includes('colmeia')) { location = 'Guarai/Colmeia'; query = target.replace(/(guarai|colmeia)/gi, '').trim(); } else if (target.toLowerCase().includes('pedro afonso')) { location = 'Pedro Afonso'; query = target.replace(/pedro afonso/gi, '').trim(); } const resultado = await this.topsapp.buscarCliente(query, location); if (!resultado.success) { await this.bot.sendMessage(chatId, `❌ **Erro na busca**\n\n` + `⚠️ ${resultado.error}`, { parse_mode: 'Markdown' } ); return; } if (!resultado.clientes || resultado.clientes.length === 0) { await this.bot.sendMessage(chatId, `🔍 **Nenhum cliente encontrado**\n\n` + `📝 Termo buscado: "${query}"\n` + `🌐 Instância: ${resultado.instancia || 'Todas'}`, { parse_mode: 'Markdown' } ); return; } const cliente = resultado.clientes[0]; // Obter serviços do cliente para bloquear const servicosResult = await this.topsapp.obterServicos(cliente.id, location); if (!servicosResult.success) { await this.bot.sendMessage(chatId, `❌ **Erro ao buscar serviços**\n\n` + `⚠️ ${servicosResult.error}`, { parse_mode: 'Markdown' } ); return; } const servicos = servicosResult.servicos || servicosResult.data?.dados || servicosResult.data?.servicos || []; const servicosAtivos = servicos.filter(s => { // id_status já vem normalizado da API return s.id_status === 1 || (s.status && s.status.toLowerCase() === 'liberado'); }); if (servicosAtivos.length === 0) { await this.bot.sendMessage(chatId, `⚠️ **Nenhum serviço ativo**\n\n` + `O cliente **${cliente.nome}** não possui serviços ativos para bloquear.`, { parse_mode: 'Markdown' } ); return; } let message = `🔒 **Bloquear Cliente**\n\n`; message += `👤 **Cliente**: ${cliente.nome}\n`; message += `🆔 **ID**: ${cliente.id}\n`; message += `📊 **Serviços ativos**: ${servicosAtivos.length}\n\n`; message += `⚠️ **Confirma o bloqueio de todos os serviços?**`; const keyboard = [ [ { text: '✅ Confirmar Bloqueio', callback_data: `confirm_block_all_${cliente.id}_${location}` }, { 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 bloquear cliente:', error); await this.bot.sendMessage(chatId, `❌ **Erro ao bloquear cliente**\n\n` + `⚠️ ${error.message}`, { parse_mode: 'Markdown' } ); } } /** * Liberar cliente */ async handleLiberarCliente(chatId, target) { if (!target) { await this.bot.sendMessage(chatId, `🔓 **Liberar Cliente**\n\n` + `💡 **Como usar:**\n` + `• "liberar cliente João da Silva"\n` + `• "liberar cliente 123.456.789-10"\n` + `• "liberar Guarai João" (específica instância)` ); return; } try { await this.bot.sendMessage(chatId, '🔍 Buscando cliente...'); // Detectar se especificou uma localização let location = this.getActiveLocation(chatId); let query = target; if (target.toLowerCase().includes('guarai') || target.toLowerCase().includes('colmeia')) { location = 'Guarai/Colmeia'; query = target.replace(/(guarai|colmeia)/gi, '').trim(); } else if (target.toLowerCase().includes('pedro afonso')) { location = 'Pedro Afonso'; query = target.replace(/pedro afonso/gi, '').trim(); } const resultado = await this.topsapp.buscarCliente(query, location); if (!resultado.success) { await this.bot.sendMessage(chatId, `❌ **Erro na busca**\n\n` + `⚠️ ${resultado.error}`, { parse_mode: 'Markdown' } ); return; } if (!resultado.clientes || resultado.clientes.length === 0) { await this.bot.sendMessage(chatId, `🔍 **Nenhum cliente encontrado**\n\n` + `📝 Termo buscado: "${query}"\n` + `🌐 Instância: ${resultado.instancia || 'Todas'}`, { parse_mode: 'Markdown' } ); return; } const cliente = resultado.clientes[0]; // Obter serviços do cliente para liberar const servicosResult = await this.topsapp.obterServicos(cliente.id, location); if (!servicosResult.success) { await this.bot.sendMessage(chatId, `❌ **Erro ao buscar serviços**\n\n` + `⚠️ ${servicosResult.error}`, { parse_mode: 'Markdown' } ); return; } const servicos = servicosResult.servicos || servicosResult.data?.dados || servicosResult.data?.servicos || []; // DEBUG: Verificar estrutura dos serviços console.log('🔍 DEBUG - Total de serviços recebidos:', servicos.length); if (servicos.length > 0) { console.log('🔍 DEBUG - Primeiro serviço:', JSON.stringify(servicos[0], null, 2)); } const servicosBloqueados = servicos.filter(s => { // id_status já vem normalizado da API const isBloqueado = s.id_status === 2 || (s.status && s.status.toLowerCase() === 'bloqueado'); console.log(`🔍 DEBUG - Serviço ${s.id_servico || s.id}: id_status=${s.id_status}, status="${s.status}", bloqueado=${isBloqueado}`); return isBloqueado; }); console.log('🔍 DEBUG - Serviços bloqueados encontrados:', servicosBloqueados.length); if (servicosBloqueados.length === 0) { await this.bot.sendMessage(chatId, `⚠️ **Nenhum serviço bloqueado**\n\n` + `O cliente **${cliente.nome}** não possui serviços bloqueados para liberar.`, { parse_mode: 'Markdown' } ); return; } let message = `🔓 **Liberar Cliente**\n\n`; message += `👤 **Cliente**: ${cliente.nome}\n`; message += `🆔 **ID**: ${cliente.id}\n`; message += `📊 **Serviços bloqueados**: ${servicosBloqueados.length}\n\n`; message += `⚠️ **Confirma a liberação de todos os serviços?**`; const keyboard = [ [ { text: '✅ Confirmar Liberação', callback_data: `confirm_unblock_all_${cliente.id}_${location}` }, { 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 liberar cliente:', error); await this.bot.sendMessage(chatId, `❌ **Erro ao liberar cliente**\n\n` + `⚠️ ${error.message}`, { parse_mode: 'Markdown' } ); } } /** * Listar serviços do cliente */ async handleListarServicos(chatId, target) { if (!target) { await this.bot.sendMessage(chatId, `📋 **Listar Serviços**\n\n` + `💡 **Como usar:**\n` + `• "listar serviços João da Silva"\n` + `• "serviços do cliente 123.456.789-10"\n` + `• "serviços Guarai João" (específica instância)` ); return; } try { await this.bot.sendMessage(chatId, '🔍 Buscando serviços...'); // Detectar se especificou uma localização let location = this.getActiveLocation(chatId); let query = target; if (target.toLowerCase().includes('guarai') || target.toLowerCase().includes('colmeia')) { location = 'Guarai/Colmeia'; query = target.replace(/(guarai|colmeia)/gi, '').trim(); } else if (target.toLowerCase().includes('pedro afonso')) { location = 'Pedro Afonso'; query = target.replace(/pedro afonso/gi, '').trim(); } const resultado = await this.topsapp.buscarCliente(query, location); if (!resultado.success || !resultado.clientes || resultado.clientes.length === 0) { await this.bot.sendMessage(chatId, `🔍 **Nenhum cliente encontrado**\n\n` + `📝 Termo buscado: "${query}"`, { parse_mode: 'Markdown' } ); return; } const cliente = resultado.clientes[0]; // Obter serviços do cliente const servicosResult = await this.topsapp.obterServicos(cliente.id, location); if (!servicosResult.success) { await this.bot.sendMessage(chatId, `❌ **Erro ao buscar serviços**\n\n` + `⚠️ ${servicosResult.error}`, { parse_mode: 'Markdown' } ); return; } const servicos = servicosResult.servicos || servicosResult.data?.dados || servicosResult.data?.servicos || []; if (servicos.length === 0) { await this.bot.sendMessage(chatId, `📋 **Nenhum serviço encontrado**\n\n` + `O cliente **${cliente.nome}** não possui serviços cadastrados.`, { parse_mode: 'Markdown' } ); return; } // Separar serviços por status (id_status já vem normalizado da API) const servicosAtivos = servicos.filter(s => { return s.id_status === 1 || (s.status && s.status.toLowerCase() === 'liberado'); }); const servicosBloqueados = servicos.filter(s => { return s.id_status === 2 || (s.status && s.status.toLowerCase() === 'bloqueado'); }); const servicosDesativados = servicos.filter(s => { return s.id_status === 3 || (s.status && s.status.toLowerCase() === 'desativado'); }); let message = `📋 **SERVIÇOS DO CLIENTE**\n\n`; message += `👤 **Cliente**: ${cliente.nome}\n`; message += `🆔 **ID**: ${cliente.id}\n\n`; // Exibir serviços ativos if (servicosAtivos.length > 0) { message += `✅ **SERVIÇOS ATIVOS** (${servicosAtivos.length})\n\n`; servicosAtivos.forEach((servico, index) => { message += `${index + 1}. 🟢 **${servico.descricao || servico.plano || 'Serviço'}**\n`; message += ` 🆔 ID: \`${servico.id_servico || servico.id}\`\n`; if (servico.usuario) { message += ` 👤 Login: \`${servico.usuario}\`\n`; } if (servico.servico_valor || servico.valor) { const valor = parseFloat(servico.servico_valor || servico.valor); message += ` 💰 Valor: R$ ${valor.toFixed(2)}\n`; } if (servico.vencimento) { message += ` 📅 Vencimento: dia ${servico.vencimento}\n`; } message += `\n`; }); } // Exibir serviços bloqueados if (servicosBloqueados.length > 0) { message += `\n🔒 **SERVIÇOS BLOQUEADOS** (${servicosBloqueados.length})\n\n`; servicosBloqueados.forEach((servico, index) => { message += `${index + 1}. 🔴 **${servico.descricao || servico.plano || 'Serviço'}**\n`; message += ` 🆔 ID: \`${servico.id_servico || servico.id}\`\n`; if (servico.usuario) { message += ` 👤 Login: \`${servico.usuario}\`\n`; } if (servico.servico_valor || servico.valor) { const valor = parseFloat(servico.servico_valor || servico.valor); message += ` 💰 Valor: R$ ${valor.toFixed(2)}\n`; } if (servico.data_bloqueio && servico.data_bloqueio !== '0000-00-00') { message += ` 🕒 Bloqueado em: ${servico.data_bloqueio}\n`; } message += `\n`; }); } // Exibir serviços desativados if (servicosDesativados.length > 0) { message += `\n❌ **SERVIÇOS DESATIVADOS** (${servicosDesativados.length})\n\n`; servicosDesativados.forEach((servico, index) => { message += `${index + 1}. ⚫ **${servico.descricao || servico.plano || 'Serviço'}**\n`; message += ` 🆔 ID: \`${servico.id_servico || servico.id}\`\n`; if (servico.data_desativacao && servico.data_desativacao !== '0000-00-00') { message += ` 🕒 Desativado em: ${servico.data_desativacao}\n`; } message += `\n`; }); } // Botões de ação const keyboard = []; if (servicosAtivos.length > 0) { keyboard.push([ { text: '🔒 Bloquear Todos', callback_data: `confirm_block_all_${cliente.id}_${location}` } ]); } if (servicosBloqueados.length > 0) { keyboard.push([ { text: '🔓 Liberar Todos', callback_data: `confirm_unblock_all_${cliente.id}_${location}` } ]); } await this.bot.sendMessage(chatId, message, { parse_mode: 'Markdown', reply_markup: keyboard.length > 0 ? { inline_keyboard: keyboard } : undefined }); } catch (error) { console.error('❌ Erro ao listar serviços:', error); await this.bot.sendMessage(chatId, `❌ **Erro ao listar serviços**\n\n` + `⚠️ ${error.message}`, { parse_mode: 'Markdown' } ); } } /** * Ver faturas do cliente */ async handleVerFaturas(chatId, targetOrId, location = '', idServicoFilter = null) { try { let idCliente = targetOrId; // Se não for um número, buscar cliente primeiro if (isNaN(targetOrId)) { await this.bot.sendMessage(chatId, '🔍 Buscando cliente...'); const resultado = await this.topsapp.buscarCliente(targetOrId, location); if (!resultado.success || !resultado.clientes || resultado.clientes.length === 0) { await this.bot.sendMessage(chatId, `🔍 **Nenhum cliente encontrado**\n\n` + `📝 Termo buscado: "${targetOrId}"`, { parse_mode: 'Markdown' } ); return; } idCliente = resultado.clientes[0].id; location = location || resultado.instancia; } await this.bot.sendMessage(chatId, '📄 Buscando faturas...'); const resultado = await this.topsapp.obterFaturasDetalhadas(idCliente, location); if (!resultado.success) { await this.bot.sendMessage(chatId, `❌ **Erro ao buscar faturas**\n\n` + `⚠️ ${resultado.error}`, { parse_mode: 'Markdown' } ); return; } let faturasPorServico = resultado.faturasPorServico || []; if (faturasPorServico.length === 0) { await this.bot.sendMessage(chatId, `📄 **Nenhuma fatura encontrada**\n\n` + `✅ O cliente não possui faturas cadastradas.`, { parse_mode: 'Markdown' } ); return; } // Se há mais de um serviço com faturas e nenhum filtro foi aplicado, // perguntar qual serviço o usuário quer ver if (faturasPorServico.length > 1 && !idServicoFilter) { let message = `📄 **SELECIONE O SERVIÇO**\n\n`; message += `🆔 **ID Cliente**: ${idCliente}\n`; message += `📊 Este cliente possui **${faturasPorServico.length} serviços** com faturas.\n\n`; faturasPorServico.forEach((servicoInfo, index) => { const s = servicoInfo.servico; const rawStatus = String(s.status || '').toLowerCase(); let statusText; if (rawStatus === 'liberado' || rawStatus === '1' || parseInt(s.status) === 1) { statusText = '🟢 Liberado'; } else if (rawStatus === 'bloqueado' || rawStatus === '2' || parseInt(s.status) === 2) { statusText = '🔴 Bloqueado'; } else if (rawStatus === 'desativado' || rawStatus === '3' || parseInt(s.status) === 3) { statusText = '⚫ Desativado'; } else { statusText = `❓ ${s.status || 'Desconhecido'}`; } message += `*${index + 1}. ${s.nome || 'Serviço'}*\n`; if (s.login) message += ` 👤 Login PPPoE: \`${s.login}\`\n`; message += ` 📊 Status: ${statusText}\n`; if (s.endereco) message += ` 📍 Endereço: ${s.endereco}\n`; message += ` 🧾 ${servicoInfo.faturas.length} fatura(s)\n\n`; }); message += `Escolha qual serviço deseja ver as faturas:`; const keyboard = faturasPorServico.map((servicoInfo, index) => [{ text: `${index + 1}. ${servicoInfo.servico.login || servicoInfo.servico.nome || 'Serviço'} — ${servicoInfo.faturas.length} fatura(s)`, callback_data: `faturas_servico_${idCliente}_${servicoInfo.servico.id}_${location}` }]); await this.bot.sendMessage(chatId, message, { parse_mode: 'Markdown', reply_markup: { inline_keyboard: keyboard } }); return; } // Filtrar pelo serviço selecionado, se houver if (idServicoFilter) { faturasPorServico = faturasPorServico.filter( s => String(s.servico.id) === String(idServicoFilter) ); } // Contar total de faturas let totalFaturas = 0; faturasPorServico.forEach(s => { totalFaturas += s.faturas.length; }); let message = `📄 **FATURAS DO CLIENTE**\n\n`; message += `🆔 **ID Cliente**: ${idCliente}\n`; message += `📊 **Total de faturas**: ${totalFaturas}\n\n`; faturasPorServico.forEach((servicoInfo, sIndex) => { if (servicoInfo.faturas.length > 0) { message += `📋 **Serviço**: ${servicoInfo.servico.nome || servicoInfo.servico.descricao || 'Serviço'}\n`; message += `🆔 **ID**: ${servicoInfo.servico.id}\n`; if (servicoInfo.servico.login) { message += `👤 **Login**: ${servicoInfo.servico.login}\n`; } if (servicoInfo.servico.endereco) { message += `📍 **Endereço**: ${servicoInfo.servico.endereco}\n`; } message += `\n`; servicoInfo.faturas.forEach((fatura, fIndex) => { const isPago = fatura.status === 'Pago'; const statusEmoji = isPago ? '✅' : '🔴'; message += `${fIndex + 1}. ${statusEmoji} **Fatura #${fatura.id}**\n`; if (fatura.valor) { const valor = parseFloat(fatura.valor); message += ` 💰 Valor: R$ ${valor.toFixed(2)}\n`; } if (fatura.vencimento) { message += ` 📅 Vencimento: ${fatura.vencimento}\n`; } message += ` 📊 Status: ${fatura.status}\n`; if (fatura.descricao) { message += ` 📝 Descrição: ${fatura.descricao}\n`; } if (fatura.periodo) { message += ` 📆 Período: ${fatura.periodo}\n`; } message += `\n`; }); message += `\n`; } }); // Criar botões para cada fatura com linha digitável/PIX // Inclui a data de vencimento para melhor identificação const keyboard = []; faturasPorServico.forEach((servicoInfo) => { servicoInfo.faturas.forEach((fatura) => { if (fatura.status !== 'Pago') { const vencLabel = fatura.vencimento ? ` (${fatura.vencimento})` : ''; const row = []; if (fatura.linhaDigitavel) { row.push({ text: `📋 Boleto #${fatura.id}${vencLabel}`, callback_data: `linha_${fatura.id}_${location}` }); } if (fatura.pixCopiaCola) { row.push({ text: `💳 PIX #${fatura.id}${vencLabel}`, callback_data: `pix_${fatura.id}_${location}` }); } if (row.length > 0) { keyboard.push(row); } } }); }); await this.bot.sendMessage(chatId, message, { parse_mode: 'Markdown', reply_markup: keyboard.length > 0 ? { inline_keyboard: keyboard } : undefined }); } catch (error) { console.error('❌ Erro ao buscar faturas:', error); await this.bot.sendMessage(chatId, `❌ **Erro ao buscar faturas**\n\n` + `⚠️ ${error.message}`, { parse_mode: 'Markdown' } ); } } /** * Gerar boleto */ async handleGerarBoleto(chatId, target) { if (!target) { await this.bot.sendMessage(chatId, `💸 **Gerar Boleto**\n\n` + `💡 **Como usar:**\n` + `• "gerar boleto João da Silva"\n` + `• "boleto do cliente 123.456.789-10"` ); return; } try { await this.bot.sendMessage(chatId, '🔍 Buscando cliente...'); let location = this.getActiveLocation(chatId); let query = target; if (target.toLowerCase().includes('guarai') || target.toLowerCase().includes('colmeia')) { location = 'Guarai/Colmeia'; query = target.replace(/(guarai|colmeia)/gi, '').trim(); } else if (target.toLowerCase().includes('pedro afonso')) { location = 'Pedro Afonso'; query = target.replace(/pedro afonso/gi, '').trim(); } const resultado = await this.topsapp.buscarCliente(query, location); if (!resultado.success || !resultado.clientes || resultado.clientes.length === 0) { await this.bot.sendMessage(chatId, `🔍 **Nenhum cliente encontrado**\n\n` + `📝 Termo buscado: "${query}"`, { parse_mode: 'Markdown' } ); return; } const cliente = resultado.clientes[0]; await this.handleVerFaturas(chatId, cliente.id, location); } catch (error) { console.error('❌ Erro ao gerar boleto:', error); await this.bot.sendMessage(chatId, `❌ **Erro ao gerar boleto**\n\n` + `⚠️ ${error.message}`, { parse_mode: 'Markdown' } ); } } /** * Callback para confirmar bloqueio de todos os serviços */ async handleConfirmBlockAllCallback(callbackQuery, data) { const chatId = callbackQuery.message.chat.id; const parts = data.replace('confirm_block_all_', '').split('_'); const idCliente = parts[0]; const location = parts.slice(1).join('_'); try { await this.bot.answerCallbackQuery(callbackQuery.id); await this.bot.editMessageText('⏳ Bloqueando serviços...', { chat_id: chatId, message_id: callbackQuery.message.message_id }); // Obter serviços do cliente const servicosResult = await this.topsapp.obterServicos(idCliente, location); if (!servicosResult.success) { await this.bot.sendMessage(chatId, `❌ **Erro ao buscar serviços**\n\n` + `⚠️ ${servicosResult.error}`, { parse_mode: 'Markdown' } ); return; } const servicos = servicosResult.servicos || servicosResult.data?.dados || servicosResult.data?.servicos || []; const servicosAtivos = servicos.filter(s => { // id_status já vem normalizado da API return s.id_status === 1 || (s.status && s.status.toLowerCase() === 'liberado'); }); let sucesso = 0; let falhas = 0; for (const servico of servicosAtivos) { const resultado = await this.topsapp.bloquearServico(servico.id_servico || servico.id, idCliente, location); if (resultado.success) { sucesso++; } else { falhas++; } } let message = `🔒 **Bloqueio Concluído**\n\n`; message += `✅ **Bloqueados**: ${sucesso}\n`; if (falhas > 0) { message += `❌ **Falhas**: ${falhas}\n`; } await this.bot.sendMessage(chatId, message, { parse_mode: 'Markdown' }); } catch (error) { console.error('❌ Erro ao bloquear serviços:', error); await this.bot.sendMessage(chatId, `❌ **Erro ao bloquear serviços**\n\n` + `⚠️ ${error.message}`, { parse_mode: 'Markdown' } ); } } /** * Callback para confirmar liberação de todos os serviços */ async handleConfirmUnblockAllCallback(callbackQuery, data) { const chatId = callbackQuery.message.chat.id; const parts = data.replace('confirm_unblock_all_', '').split('_'); const idCliente = parts[0]; const location = parts.slice(1).join('_'); try { await this.bot.answerCallbackQuery(callbackQuery.id); await this.bot.editMessageText('⏳ Liberando serviços...', { chat_id: chatId, message_id: callbackQuery.message.message_id }); // Obter serviços do cliente const servicosResult = await this.topsapp.obterServicos(idCliente, location); if (!servicosResult.success) { await this.bot.sendMessage(chatId, `❌ **Erro ao buscar serviços**\n\n` + `⚠️ ${servicosResult.error}`, { parse_mode: 'Markdown' } ); return; } const servicos = servicosResult.servicos || servicosResult.data?.dados || servicosResult.data?.servicos || []; const servicosBloqueados = servicos.filter(s => { // id_status já vem normalizado da API return s.id_status === 2 || (s.status && s.status.toLowerCase() === 'bloqueado'); }); let sucesso = 0; let falhas = 0; for (const servico of servicosBloqueados) { const resultado = await this.topsapp.liberarServico(servico.id_servico || servico.id, idCliente, location); if (resultado.success) { sucesso++; } else { falhas++; } } let message = `🔓 **Liberação Concluída**\n\n`; message += `✅ **Liberados**: ${sucesso}\n`; if (falhas > 0) { message += `❌ **Falhas**: ${falhas}\n`; } await this.bot.sendMessage(chatId, message, { parse_mode: 'Markdown' }); } catch (error) { console.error('❌ Erro ao liberar serviços:', error); await this.bot.sendMessage(chatId, `❌ **Erro ao liberar serviços**\n\n` + `⚠️ ${error.message}`, { parse_mode: 'Markdown' } ); } } /** * Callback para confirmar bloqueio de cliente individual */ async handleConfirmBlockCallback(callbackQuery, data) { await this.handleConfirmBlockAllCallback(callbackQuery, data.replace('confirm_block_', 'confirm_block_all_')); } /** * Callback para confirmar liberação de cliente individual */ async handleConfirmUnblockCallback(callbackQuery, data) { await this.handleConfirmUnblockAllCallback(callbackQuery, data.replace('confirm_unblock_', 'confirm_unblock_all_')); } /** * Callback para confirmar bloqueio de serviço individual */ async handleConfirmBlockServiceCallback(callbackQuery, data) { const chatId = callbackQuery.message.chat.id; // Formato: confirm_block_service_idServico_idCliente_location const parts = data.replace('confirm_block_service_', '').split('_'); const idServico = parts[0]; const idCliente = parts[1]; const location = parts.slice(2).join('_'); try { await this.bot.answerCallbackQuery(callbackQuery.id); await this.bot.editMessageText('⏳ Bloqueando serviço...', { chat_id: chatId, message_id: callbackQuery.message.message_id }); const resultado = await this.topsapp.bloquearServico(idServico, idCliente, location); if (resultado.success) { await this.bot.sendMessage(chatId, `✅ **Serviço bloqueado com sucesso!**\n\n` + `🆔 **ID do Serviço**: ${idServico}`, { parse_mode: 'Markdown' } ); } else { await this.bot.sendMessage(chatId, `❌ **Erro ao bloquear serviço**\n\n` + `⚠️ ${resultado.error}`, { parse_mode: 'Markdown' } ); } } catch (error) { console.error('❌ Erro ao bloquear serviço:', error); await this.bot.sendMessage(chatId, `❌ **Erro ao bloquear serviço**\n\n` + `⚠️ ${error.message}`, { parse_mode: 'Markdown' } ); } } /** * Callback para confirmar liberação de serviço individual */ async handleConfirmUnblockServiceCallback(callbackQuery, data) { const chatId = callbackQuery.message.chat.id; // Formato: confirm_unblock_service_idServico_idCliente_location const parts = data.replace('confirm_unblock_service_', '').split('_'); const idServico = parts[0]; const idCliente = parts[1]; const location = parts.slice(2).join('_'); try { await this.bot.answerCallbackQuery(callbackQuery.id); await this.bot.editMessageText('⏳ Liberando serviço...', { chat_id: chatId, message_id: callbackQuery.message.message_id }); const resultado = await this.topsapp.liberarServico(idServico, idCliente, location); if (resultado.success) { await this.bot.sendMessage(chatId, `✅ **Serviço liberado com sucesso!**\n\n` + `🆔 **ID do Serviço**: ${idServico}`, { parse_mode: 'Markdown' } ); } else { await this.bot.sendMessage(chatId, `❌ **Erro ao liberar serviço**\n\n` + `⚠️ ${resultado.error}`, { parse_mode: 'Markdown' } ); } } catch (error) { console.error('❌ Erro ao liberar serviço:', error); await this.bot.sendMessage(chatId, `❌ **Erro ao liberar serviço**\n\n` + `⚠️ ${error.message}`, { parse_mode: 'Markdown' } ); } } /** * Callback para dados do cliente */ async handleDadosClienteCallback(callbackQuery, data) { const chatId = callbackQuery.message.chat.id; const parts = data.replace('dados_cliente_', '').split('_'); const idCliente = parts[0]; const location = parts.slice(1).join('_'); await this.bot.answerCallbackQuery(callbackQuery.id); await this.handleDadosCliente(chatId, idCliente, location); } /** * Callback para faturas do cliente */ async handleFaturasClienteCallback(callbackQuery, data) { const chatId = callbackQuery.message.chat.id; const parts = data.replace('faturas_cliente_', '').split('_'); const idCliente = parts[0]; const location = parts.slice(1).join('_'); await this.bot.answerCallbackQuery(callbackQuery.id); await this.handleVerFaturas(chatId, idCliente, location); } /** * Callback para selecionar serviço específico ao ver faturas */ async handleFaturasServicoCallback(callbackQuery, data) { const chatId = callbackQuery.message.chat.id; const parts = data.replace('faturas_servico_', '').split('_'); const idCliente = parts[0]; const idServico = parts[1]; const location = parts.slice(2).join('_'); await this.bot.answerCallbackQuery(callbackQuery.id); await this.handleVerFaturas(chatId, idCliente, location, idServico); } /** * Callback para serviços do cliente */ async handleServicosClienteCallback(callbackQuery, data) { const chatId = callbackQuery.message.chat.id; const parts = data.replace('servicos_cliente_', '').split('_'); const idCliente = parts[0]; const location = parts.slice(1).join('_'); await this.bot.answerCallbackQuery(callbackQuery.id); await this.handleListarServicos(chatId, idCliente); } /** * Callback para exibir linha digitável */ async handleLinhaDigitavelCallback(callbackQuery, data) { const chatId = callbackQuery.message.chat.id; const parts = data.replace('linha_', '').split('_'); const idFatura = parts[0]; const location = parts.slice(1).join('_'); try { await this.bot.answerCallbackQuery(callbackQuery.id); await this.bot.sendMessage(chatId, '⏳ Buscando linha digitável...'); // Buscar dados da fatura const resultado = await this.topsapp.obterDadosFatura(idFatura, location); if (!resultado.success) { await this.bot.sendMessage(chatId, `❌ **Erro ao buscar linha digitável**\n\n` + `⚠️ ${resultado.error}`, { parse_mode: 'Markdown' } ); return; } const dadosFatura = resultado.data?.fatura || resultado.data?.dados || resultado.data; if (!dadosFatura || !dadosFatura.linha_digitavel) { await this.bot.sendMessage(chatId, `⚠️ **Linha digitável não disponível**\n\n` + `A fatura #${idFatura} não possui linha digitável.`, { parse_mode: 'Markdown' } ); return; } let message = `📋 **LINHA DIGITÁVEL**\n\n`; message += `🆔 **Fatura**: #${idFatura}\n`; message += `💰 **Valor**: R$ ${parseFloat(dadosFatura.valor || dadosFatura.valor_atualizado || 0).toFixed(2)}\n`; message += `📅 **Vencimento**: ${dadosFatura.vencimento || dadosFatura.vencimento_atualizado || 'N/A'}\n\n`; message += `📋 **Linha Digitável**:\n\`${dadosFatura.linha_digitavel}\`\n\n`; message += `💡 _Copie a linha digitável acima para pagar pelo app do banco_`; await this.bot.sendMessage(chatId, message, { parse_mode: 'Markdown' }); } catch (error) { console.error('❌ Erro ao buscar linha digitável:', error); await this.bot.sendMessage(chatId, `❌ **Erro ao buscar linha digitável**\n\n` + `⚠️ ${error.message}`, { parse_mode: 'Markdown' } ); } } /** * Callback para exibir PIX copia e cola */ async handlePixCallback(callbackQuery, data) { const chatId = callbackQuery.message.chat.id; const parts = data.replace('pix_', '').split('_'); const idFatura = parts[0]; const location = parts.slice(1).join('_'); try { await this.bot.answerCallbackQuery(callbackQuery.id); await this.bot.sendMessage(chatId, '⏳ Buscando código PIX...'); // Buscar dados da fatura const resultado = await this.topsapp.obterDadosFatura(idFatura, location); if (!resultado.success) { await this.bot.sendMessage(chatId, `❌ **Erro ao buscar código PIX**\n\n` + `⚠️ ${resultado.error}`, { parse_mode: 'Markdown' } ); return; } const dadosFatura = resultado.data?.fatura || resultado.data?.dados || resultado.data; const pixCode = dadosFatura?.qrcode || dadosFatura?.pix_copia_cola || dadosFatura?.codigo_pix; if (!dadosFatura || !pixCode) { await this.bot.sendMessage(chatId, `⚠️ **PIX não disponível**\n\n` + `A fatura #${idFatura} não possui código PIX.`, { parse_mode: 'Markdown' } ); return; } let message = `💳 **PIX COPIA E COLA**\n\n`; message += `🆔 **Fatura**: #${idFatura}\n`; message += `💰 **Valor**: R$ ${parseFloat(dadosFatura.valor || dadosFatura.valor_atualizado || 0).toFixed(2)}\n`; message += `📅 **Vencimento**: ${dadosFatura.vencimento || dadosFatura.vencimento_atualizado || 'N/A'}\n\n`; message += `💳 **PIX Copia e Cola**:\n\`${pixCode}\`\n\n`; message += `💡 _Copie o código acima e cole no app do banco na opção PIX Copia e Cola_`; await this.bot.sendMessage(chatId, message, { parse_mode: 'Markdown' }); } catch (error) { console.error('❌ Erro ao buscar código PIX:', error); await this.bot.sendMessage(chatId, `❌ **Erro ao buscar código PIX**\n\n` + `⚠️ ${error.message}`, { parse_mode: 'Markdown' } ); } } /** * Escapar caracteres problemáticos para Markdown do Telegram */ escapeMarkdown(text) { if (!text) return text; // Escapar caracteres especiais que podem causar problemas no Markdown return text.toString() .replace(/\\/g, '\\\\') // Escape backslash .replace(/\*/g, '\\*') // Escape asterisco .replace(/_/g, '\\_') // Escape underscore .replace(/\[/g, '\\[') // Escape colchete .replace(/\]/g, '\\]') // Escape colchete .replace(/\(/g, '\\(') // Escape parêntese .replace(/\)/g, '\\)') // Escape parêntese .replace(/~/g, '\\~') // Escape til .replace(/>/g, '\\>') // Escape maior que .replace(/#/g, '\\#') // Escape hashtag .replace(/\+/g, '\\+') // Escape mais .replace(/-/g, '\\-') // Escape hífen .replace(/=/g, '\\=') // Escape igual .replace(/\|/g, '\\|') // Escape pipe .replace(/\{/g, '\\{') // Escape chaves .replace(/\}/g, '\\}') // Escape chaves .replace(/\./g, '\\.') // Escape ponto .replace(/!/g, '\\!') // Escape exclamação .replace(/@/g, '\\@'); // Escape arroba } /** * Mostrar dados cadastrais completos do cliente */ async handleDadosCliente(chatId, idCliente, location = '') { try { await this.bot.sendMessage(chatId, '📋 Carregando dados cadastrais...'); const resultado = await this.topsapp.obterDadosCadastrais(idCliente, location); if (!resultado.success) { await this.bot.sendMessage(chatId, `❌ Erro ao buscar dados\n\n` + `⚠️ ${resultado.error}` ); return; } const cliente = resultado.cliente; const servicos = resultado.servicos || []; let message = `📋 Dados Cadastrais Completos\n\n`; // Dados do cliente message += `👤 CLIENTE\n`; message += `🆔 ID: ${cliente.id}\n`; message += `👤 Nome: ${cliente.nome || 'Não informado'}\n`; if (cliente.cpf) { message += `📄 CPF: \`${cliente.cpf}\`\n`; } if (cliente.rg) { message += `📄 RG: ${cliente.rg}\n`; } message += `📧 Email: ${cliente.email || 'Não informado'}\n`; if (cliente.telefone && cliente.telefone !== 'Não informado') { message += `📞 Telefone: \`${cliente.telefone}\`\n`; } else { message += `📞 Telefone: Não informado\n`; } if (cliente.celular && cliente.celular !== 'Não informado') { message += `📱 Celular: \`${cliente.celular}\`\n`; } else { message += `📱 Celular: Não informado\n`; } // Endereço if (cliente.endereco) { message += `📍 Endereço: ${cliente.endereco}`; if (cliente.numero) { message += `, ${cliente.numero}`; } message += `\n`; } if (cliente.complemento) { message += `📝 Complemento: ${cliente.complemento}\n`; } message += `🏘️ Bairro: ${cliente.bairro || 'Não informado'}\n`; message += `🏙️ Cidade: ${cliente.cidade || 'Não informado'}`; if (cliente.uf) { message += ` - ${cliente.uf}`; } message += `\n`; message += `📮 CEP: ${cliente.cep || 'Não informado'}\n`; message += `📊 Status Geral: ${cliente.status === 1 ? '✅ Ativo' : '❌ Inativo'}\n`; if (cliente.data_cadastro) { message += `📅 Cadastro: ${this.formatarData(cliente.data_cadastro)}\n`; } if (cliente.observacoes) { message += `📝 Observações: ${cliente.observacoes}\n`; } message += `\n🌐 Instância: ${resultado.instancia}\n\n`; // Dados dos serviços if (servicos.length > 0) { message += `⚙️ **SERVIÇOS CONTRATADOS**\n\n`; servicos.forEach((servico, index) => { message += `📦 Serviço ${index + 1}\n`; message += `🏷️ Plano: ${servico.planoNome}\n`; message += `📊 Status: ${servico.statusFormatado}\n`; if (servico.login) { message += `👤 Login: ${servico.login}\n`; } if (servico.senha) { message += `🔑 Senha: ${servico.senha}\n`; } if (servico.ip) { message += `🌐 IP: ${servico.ip}\n`; } if (servico.vencimento) { message += `📅 Vencimento: Dia ${servico.vencimento}\n`; } if (servico.valor) { message += `💰 Valor: R$ ${parseFloat(servico.valor).toFixed(2)}\n`; } message += `🔧 Tecnologia: ${servico.tecnologia}\n`; if (servico.velocidade_down) { message += `⬇️ Download: ${servico.velocidade_down} Mbps\n`; } if (servico.velocidade_up) { message += `⬆️ Upload: ${servico.velocidade_up} Mbps\n`; } // Dados FTTH - usando campos reais da API if (servico.tecnologia === 'FTTH' || servico.pon || servico.serial) { message += `\n📡 Dados FTTH:\n`; if (servico.base || servico.id_ap) { message += `🔌 Base/AP: ${servico.base || `AP ${servico.id_ap}`}\n`; } if (servico.pon) { message += `🔗 PON: ${servico.pon}\n`; } if (servico.perfil) { message += `⚙️ Perfil: ${servico.perfil}\n`; } if (servico.serial) { message += `📟 Serial ONU: \`${servico.serial}\`\n`; } if (servico.serviceport) { message += `🔌 Service Port: ${servico.serviceport}\n`; } if (servico.ultimo_rx_power) { message += `📶 Último RX: ${servico.ultimo_rx_power} dBm\n`; } if (servico.ultimo_tx_power) { message += `📡 Último TX: ${servico.ultimo_tx_power} dBm\n`; } if (servico.data_ultimo_sinal) { message += `📅 Último sinal: ${this.formatarData(servico.data_ultimo_sinal)} ${servico.hora_ultimo_sinal || ''}\n`; } if (servico.conexao) { const statusConexao = servico.conexao === 'online' ? '🟢 Online' : '🔴 Offline'; message += `🔌 Conexão: ${statusConexao}\n`; } if (servico.ultimo_acesso) { message += `🕐 Último acesso: ${servico.ultimo_acesso}\n`; } if (servico.ultimo_ip) { message += `🌐 Último IP: ${servico.ultimo_ip}\n`; } } // Dados DHCP / IPoE if (servico.dhcp_username || servico.dhcp_agent_circuit_id) { message += `\n🔗 Dados IPoE (DHCP Option 82):\n`; if (servico.dhcp_username) { message += `👤 DHCP Username: \`${servico.dhcp_username}\`\n`; } if (servico.dhcp_agent_circuit_id) { message += `🔌 Agent Circuit ID: \`${servico.dhcp_agent_circuit_id}\`\n`; } } // Dados de instalação if (servico.instalacao_endereco) { message += `\n📍 Endereço de Instalação:\n`; message += `🏠 ${servico.instalacao_endereco}, ${servico.instalacao_numero || 'S/N'}\n`; if (servico.instalacao_complemento) { message += `📝 Complemento: ${servico.instalacao_complemento}\n`; } message += `🏘️ ${servico.instalacao_bairro}, ${servico.instalacao_cidade}/${servico.instalacao_uf}\n`; if (servico.instalacao_cep) { message += `📮 CEP: ${servico.instalacao_cep}\n`; } if (servico.latitude && servico.longitude) { message += `🗺️ Coordenadas: ${servico.latitude}, ${servico.longitude}\n`; } } message += `\n`; }); } else { message += `⚙️ SERVIÇOS: Nenhum serviço encontrado\n\n`; } // Botões de ação const keyboard = [ [ { text: '📄 Ver Faturas', callback_data: `faturas_cliente_${idCliente}_${location}` }, { text: '⚙️ Listar Serviços', callback_data: `servicos_cliente_${idCliente}_${location}` } ], [ { text: '🔒 Bloquear Cliente', callback_data: `confirm_block_${idCliente}_${location}` }, { text: '🔓 Liberar Cliente', callback_data: `confirm_unblock_${idCliente}_${location}` } ] ]; // Adicionar botão para gerar PDF da primeira fatura (se houver) if (resultado.faturasPorServico && resultado.faturasPorServico.length > 0 && resultado.faturasPorServico[0].faturas && resultado.faturasPorServico[0].faturas.length > 0) { const primeiraFatura = resultado.faturasPorServico[0].faturas[0]; keyboard.push([ { text: '📄 Gerar Boleto PDF', callback_data: `boleto_pdf_${primeiraFatura.id}_${location}` } ]); } await this.bot.sendMessage(chatId, message, { parse_mode: 'Markdown', reply_markup: { inline_keyboard: keyboard } }); } catch (error) { console.error('❌ Erro ao buscar faturas detalhadas:', error); await this.bot.sendMessage(chatId, `❌ **Erro ao buscar faturas**\n\n` + `⚠️ ${error.message}`, { parse_mode: 'Markdown' } ); } } /** * Buscar e mostrar dados cadastrais do cliente */ async handleDadosClientePorBusca(chatId, target) { if (!target) { await this.bot.sendMessage(chatId, `📋 **Dados Cadastrais**\n\n` + `💡 **Como usar:**\n` + `• "dados cliente João da Silva"\n` + `• "dados cadastrais 123.456.789-10"\n` + `• "dados cliente 12345" (ID do cliente)\n` + `• "dados cliente Guarai João" (específica instância)` ); return; } try { // Primeiro, buscar o cliente let location = this.getActiveLocation(chatId); let query = target; if (target.toLowerCase().includes('guarai') || target.toLowerCase().includes('colmeia')) { location = 'Guarai/Colmeia'; query = target.replace(/(guarai|colmeia)/gi, '').trim(); } else if (target.toLowerCase().includes('pedro afonso')) { location = 'Pedro Afonso'; query = target.replace(/pedro afonso/gi, '').trim(); } const busca = await this.topsapp.buscarCliente(query, location); if (!busca.success) { await this.bot.sendMessage(chatId, `❌ **Erro na busca**\n\n` + `⚠️ ${busca.error}`, { parse_mode: 'Markdown' } ); return; } if (!busca.clientes || busca.clientes.length === 0) { await this.bot.sendMessage(chatId, `🔍 **Nenhum cliente encontrado**\n\n` + `📝 Termo buscado: "${query}"\n` + `🌐 Instância: ${busca.instancia || 'Todas'}`, { parse_mode: 'Markdown' } ); return; } // Se encontrou mais de um, mostrar lista if (busca.clientes.length > 1) { let message = `📋 **${busca.clientes.length} clientes encontrados**\n\n`; message += `🌐 **Instância**: ${busca.instancia}\n\n`; message += `💡 **Selecione um cliente para ver os dados cadastrais:**\n\n`; const keyboard = []; busca.clientes.slice(0, 10).forEach((cliente, index) => { message += `👤 **${index + 1}. ${cliente.nome}**\n`; message += `🆔 ID: \`${cliente.id}\`\n`; if (cliente.cpf) { message += `📄 CPF: \`${cliente.cpf}\`\n`; } message += `📊 Status: ${cliente.status === 1 ? '✅ Ativo' : '❌ Inativo'}\n\n`; keyboard.push([ { text: `📋 Ver Dados ${index + 1}`, callback_data: `dados_cliente_${cliente.id}_${location}` } ]); }); await this.bot.sendMessage(chatId, message, { parse_mode: 'Markdown', reply_markup: { inline_keyboard: keyboard } }); return; } // Se encontrou apenas um, mostrar dados diretamente const cliente = busca.clientes[0]; await this.handleDadosCliente(chatId, cliente.id, location || busca.instancia); } catch (error) { console.error('❌ Erro ao buscar dados do cliente:', error); await this.bot.sendMessage(chatId, `❌ **Erro ao buscar dados do cliente**\n\n` + `⚠️ ${error.message}`, { parse_mode: 'Markdown' } ); } } /** * Formatardata 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; } /** * Verificar status de ONU Fiberhome */ async handleStatusONU(chatId, target) { if (!target) { await this.bot.sendMessage(chatId, `📡 **Status ONU Fiberhome**\n\n` + `💡 **Como usar:**\n` + `• "status onu FHTT12345678"\n` + `• "verificar onu FHTT12345678"\n` + `• "onu status FHTT12345678"\n\n` + `📝 **Nota:** Informe o serial da ONU (12 caracteres)`, { parse_mode: 'Markdown' } ); return; } try { await this.bot.sendMessage(chatId, '📡 Conectando à OLT Fiberhome...'); logger.botCommand(chatId, 'status_onu', { target }); // Verificar se é serial ou device name let resultado; if (target.length === 12 && /^[A-Z0-9]+$/.test(target)) { // É um serial resultado = await this.fiberhome.obterStatusONU(target); } else { // Pode ser device name resultado = await this.fiberhome.obterStatusONUPorNome(target); } if (resultado.success) { // Formatar informações da ONU let message = `📡 **Status ONU Fiberhome**\n\n`; message += `🆔 **Serial:** ${resultado.serial}\n`; message += `📋 **Device Name:** ${resultado.deviceName}\n`; message += `📍 **Posição:** Slot ${resultado.slot}, PON ${resultado.pon}, ONU ${resultado.onuId}\n`; message += `🔌 **Status:** ${resultado.online ? '🟢 Online' : '🔴 Offline'}\n\n`; // Informações de sinal message += `📶 **Informações de Sinal:**\n`; message += `📡 **RX Power:** ${resultado.rxPower}\n`; message += `📤 **TX Power:** ${resultado.txPower}\n`; message += `📏 **Distância:** ${resultado.distance}\n\n`; // Status das portas Ethernet (resumido) if (resultado.portasEthernet.length > 0) { message += `🔗 **Portas Ethernet:**\n`; resultado.portasEthernet.forEach(porta => { const statusIcon = porta.status === 'up' ? '🟢' : '🔴'; message += ` ${statusIcon} **Porta ${porta.porta}:** ${porta.status}\n`; }); message += '\n'; } // Serviços WAN if (resultado.servicosWAN.length > 0) { message += `🌐 **Serviços WAN:**\n`; resultado.servicosWAN.forEach(servico => { const statusIcon = servico.status === 'active' ? '🟢' : '🔴'; message += ` ${statusIcon} **Serviço ${servico.id}:** ${servico.status}\n`; if (servico.ip !== 'N/A') { message += ` 📍 **IP:** ${servico.ip}\n`; } }); } else { message += `🌐 **Serviços WAN:** Nenhum configurado\n`; } await this.bot.sendMessage(chatId, message, { parse_mode: 'Markdown' }); } else { await this.bot.sendMessage(chatId, `❌ ONU ${target} não encontrada na OLT Fiberhome.`); // Oferecer pesquisar em OLTs Datacom const keyboard = { inline_keyboard: [ [ { text: '🔍 Pesquisar em OLT Datacom', callback_data: `ver_sinal_${target}` }, { text: '❌ Cancelar', callback_data: 'cancel_action' } ] ] }; await this.bot.sendMessage(chatId, 'ONU não encontrada na Fiberhome. Deseja pesquisar nas OLTs Datacom?', { reply_markup: keyboard }); } } catch (error) { logger.botError(chatId, error, 'handleStatusONU'); // Escapar caracteres especiais para evitar problemas no Telegram const errorMsg = error.message ? error.message.replace(/[_*\[\]()~`>#+=|{}.!-]/g, '\\$&') : 'Erro desconhecido'; await this.bot.sendMessage(chatId, `❌ **Erro ao verificar status da ONU**\n\n` + `⚠️ ${errorMsg}`, { parse_mode: 'Markdown' } ); } } /** * Verificar status das conexões Fiberhome */ async handleFiberhomeStatus(chatId) { try { await this.bot.sendMessage(chatId, '🔌 Diagnosticando conexões Fiberhome...'); logger.botCommand(chatId, 'fiberhome_status'); // Fazer diagnóstico completo const diagnostico = await this.fiberhome.diagnosticarConectividade(); let message = `🔌 **Diagnóstico Fiberhome**\n\n`; // Status OLT message += `📡 **OLT (${diagnostico.olt.host}:${diagnostico.olt.port}):**\n`; message += ` 🌐 **Conectividade:** ${diagnostico.olt.conectividade ? '🟢 OK' : '🔴 Falhou'}\n`; message += ` � **Telnet:** ${diagnostico.olt.telnet ? '🟢 Conectado' : '🔴 Falhou'}\n`; if (diagnostico.olt.erro) { message += ` ⚠️ **Erro:** ${diagnostico.olt.erro}\n`; } message += '\n'; // Status UNM2000 message += `�️ **UNM2000 (${diagnostico.unm2000.host}:${diagnostico.unm2000.port}):**\n`; message += ` 🌐 **Conectividade:** ${diagnostico.unm2000.conectividade ? '🟢 OK' : '🔴 Falhou'}\n`; message += ` � **TL1:** ${diagnostico.unm2000.tl1 ? '🟢 Conectado' : '🔴 Falhou'}\n`; if (diagnostico.unm2000.erro) { message += ` ⚠️ **Erro:** ${diagnostico.unm2000.erro}\n`; } message += '\n'; // Configurações message += `⚙️ **Configurações:**\n`; message += ` 📡 **OLT:** GEPON/GEPON\n`; message += ` 🖥️ **UNM2000:** 1/1\n\n`; // Comandos disponíveis message += `💡 **Comandos disponíveis:**\n`; message += `• \`status onu FHTT12345678\` - Status de ONU específica\n`; message += `• \`verificar onu FHTT12345678\` - Verificar ONU\n`; message += `• \`onu status DeviceName\` - Status por nome do dispositivo\n\n`; // Status geral const oltOk = diagnostico.olt.conectividade && diagnostico.olt.telnet; const unm2000Ok = diagnostico.unm2000.conectividade && diagnostico.unm2000.tl1; if (oltOk && unm2000Ok) { message += `✅ **Status:** Sistema funcionando normalmente`; } else if (oltOk || unm2000Ok) { message += `⚠️ **Status:** Funcionamento parcial`; } else { message += `❌ **Status:** Sistema indisponível`; } await this.bot.sendMessage(chatId, message, { parse_mode: 'Markdown' }); } catch (error) { logger.botError(chatId, error, 'handleFiberhomeStatus'); await this.bot.sendMessage(chatId, `❌ **Erro ao diagnosticar Fiberhome**\n\n` + `⚠️ ${error.message}`, { parse_mode: 'Markdown' } ); } } /** * Iniciar processo interativo de abertura de suporte */ async iniciarProcessoSuporte(chatId, userId, target = '') { if (!this.topsapp.isReady()) { await this.bot.sendMessage(chatId, `❌ **TopSapp não configurado**\n\n` + `⚠️ O sistema TopSapp não está configurado. Entre em contato com o administrador.` ); return; } // Criar sessão de suporte const sessionId = `${userId}_${chatId}_${Date.now()}`; const suporteSession = { id: sessionId, chatId, userId, etapa: 'buscar_cliente', dados: {}, iniciado: new Date() }; this.suporteSessions.set(sessionId, suporteSession); await this.bot.sendMessage(chatId, `🎫 **Abertura de Suporte**\n\n` + `🔍 **Etapa 1:** Identificar Cliente\n\n` + `Por favor, digite o **nome** ou **CPF** do cliente para quem deseja abrir o suporte:`, { parse_mode: 'Markdown' } ); // Se foi fornecido um target, usar como busca inicial if (target && target.trim()) { await this.processarEtapaSuporteBuscarCliente(sessionId, target.trim()); } } /** * Processar mensagem dentro do fluxo de suporte */ async processarMensagemSuporte(msg) { const chatId = msg.chat.id; const userId = msg.from.id; const text = msg.text; // Encontrar sessão ativa para este usuário const sessaoAtiva = Array.from(this.suporteSessions.values()).find( session => session.chatId === chatId && session.userId === userId ); if (!sessaoAtiva) { return false; // Não há sessão ativa } try { switch (sessaoAtiva.etapa) { case 'buscar_cliente': await this.processarEtapaSuporteBuscarCliente(sessaoAtiva.id, text); break; case 'selecionar_cliente': await this.processarEtapaSelecionarCliente(sessaoAtiva.id, text); break; case 'selecionar_servico': await this.processarEtapaSelecionarServico(sessaoAtiva.id, text); break; case 'selecionar_categoria': await this.processarEtapaSelecionarCategoria(sessaoAtiva.id, text); break; case 'selecionar_tecnico': await this.processarEtapaSelecionarTecnico(sessaoAtiva.id, text); break; case 'definir_data_hora': await this.processarEtapaDefinirDataHora(sessaoAtiva.id, text); break; case 'adicionar_descricao': await this.processarEtapaAdicionarDescricao(sessaoAtiva.id, text); break; case 'confirmar': await this.finalizarCriacaoSuporte(sessaoAtiva.id, text); break; // Etapas para fechar suporte case 'fechar_buscar_cliente': await this.processarEtapaFecharBuscarCliente(sessaoAtiva.id, text); break; case 'fechar_selecionar_cliente': await this.processarEtapaFecharSelecionarCliente(sessaoAtiva.id, text); break; case 'fechar_selecionar_suporte': await this.processarEtapaFecharSelecionarSuporte(sessaoAtiva.id, text); break; case 'fechar_aguardando_descricao': await this.processarEtapaFecharDescricao(sessaoAtiva.id, text); break; default: await this.bot.sendMessage(chatId, '❌ Etapa desconhecida. Reiniciando processo...'); this.suporteSessions.delete(sessaoAtiva.id); break; } return true; // Mensagem foi processada pelo fluxo de suporte } catch (error) { logger.error('Erro no processamento de suporte:', error); await this.bot.sendMessage(chatId, `❌ **Erro no processamento**\n\n` + `⚠️ ${error.message}\n\n` + `🔄 Reiniciando processo de suporte...` ); this.suporteSessions.delete(sessaoAtiva.id); return true; } } /** * Etapa 1: Buscar cliente */ async processarEtapaSuporteBuscarCliente(sessionId, query) { const session = this.suporteSessions.get(sessionId); if (!session) return; await this.bot.sendMessage(session.chatId, `🔍 Buscando cliente: "${query}"...`); try { const resultado = await this.topsapp.buscarCliente(query); if (!resultado.success) { await this.bot.sendMessage(session.chatId, `❌ **Erro na busca**\n\n` + `⚠️ ${resultado.error}\n\n` + `Por favor, tente novamente com outro nome ou CPF:` ); return; } if (!resultado.clientes || resultado.clientes.length === 0) { await this.bot.sendMessage(session.chatId, `❌ **Nenhum cliente encontrado**\n\n` + `🔍 Não foi encontrado nenhum cliente com: "${query}"\n\n` + `Por favor, tente novamente com outro nome ou CPF:` ); return; } session.dados.clientesEncontrados = resultado.clientes; if (resultado.clientes.length === 1) { // Apenas um cliente encontrado, selecionar automaticamente session.dados.clienteSelecionado = resultado.clientes[0]; session.etapa = 'selecionar_servico'; await this.processarEtapaSelecionarServico(sessionId); } else { // Múltiplos clientes, deixar usuário escolher session.etapa = 'selecionar_cliente'; await this.mostrarOpcoesClientes(session); } } catch (error) { await this.bot.sendMessage(session.chatId, `❌ **Erro na busca**\n\n` + `⚠️ ${error.message}\n\n` + `Por favor, tente novamente:` ); } } /** * Mostrar opções de clientes encontrados */ async mostrarOpcoesClientes(session) { let message = `👥 **Clientes encontrados:**\n\n`; const buttons = []; session.dados.clientesEncontrados.forEach((cliente, index) => { const status = this.topsapp.interpretarStatusCliente ? this.topsapp.interpretarStatusCliente(cliente) : cliente.status; const statusEmoji = status === 1 ? '🟢' : '🔴'; message += `**${index + 1}.** ${statusEmoji} ${cliente.nome}\n`; message += ` 📄 CPF: ${cliente.cpf || 'N/A'}\n`; message += ` 🆔 ID: ${cliente.id}\n\n`; // Adicionar botão para seleção direta buttons.push([{ text: `${index + 1}. ${cliente.nome}`, callback_data: `suporte_cliente_${index}` }]); }); message += `Selecione o cliente clicando no botão ou digite o **número** (1-${session.dados.clientesEncontrados.length}):`; const keyboard = { inline_keyboard: buttons }; await this.bot.sendMessage(session.chatId, message, { parse_mode: 'Markdown', reply_markup: keyboard }); } /** * Etapa 2: Selecionar cliente específico */ async processarEtapaSelecionarCliente(sessionId, input) { const session = this.suporteSessions.get(sessionId); if (!session) return; const numero = parseInt(input.trim()); if (isNaN(numero) || numero < 1 || numero > session.dados.clientesEncontrados.length) { await this.bot.sendMessage(session.chatId, `❌ **Número inválido**\n\n` + `Por favor, digite um número entre 1 e ${session.dados.clientesEncontrados.length}:` ); return; } session.dados.clienteSelecionado = session.dados.clientesEncontrados[numero - 1]; session.etapa = 'selecionar_servico'; await this.processarEtapaSelecionarServico(sessionId); } /** * Etapa 3: Selecionar serviço */ async processarEtapaSelecionarServico(sessionId, input = null) { const session = this.suporteSessions.get(sessionId); if (!session) return; const cliente = session.dados.clienteSelecionado; if (!input) { // Primeira vez nesta etapa - buscar serviços await this.bot.sendMessage(session.chatId, `🔍 Buscando serviços do cliente **${cliente.nome}**...`, { parse_mode: 'Markdown' } ); try { const resultado = await this.topsapp.obterServicosCliente(cliente.id); console.log(`🔍 Handler - Resultado completo:`, { success: resultado.success, servicosCount: resultado.servicos?.length, total: resultado.total, debug: resultado.debug }); if (!resultado.success) { await this.bot.sendMessage(session.chatId, `❌ **Erro ao buscar serviços**\n\n` + `⚠️ ${resultado.error}\n\n` + `🔄 Reiniciando processo...` ); this.suporteSessions.delete(sessionId); return; } if (!resultado.servicos || resultado.servicos.length === 0) { await this.bot.sendMessage(session.chatId, `❌ **Nenhum serviço encontrado**\n\n` + `O cliente não possui serviços cadastrados.\n\n` + `🔄 Reiniciando processo...` ); this.suporteSessions.delete(sessionId); return; } session.dados.servicos = resultado.servicos; await this.mostrarOpcoesServicos(session); } catch (error) { await this.bot.sendMessage(session.chatId, `❌ **Erro ao buscar serviços**\n\n` + `⚠️ ${error.message}\n\n` + `🔄 Reiniciando processo...` ); this.suporteSessions.delete(sessionId); } } else { // Usuário selecionou um serviço const numero = parseInt(input.trim()); if (isNaN(numero) || numero < 1 || numero > session.dados.servicos.length) { await this.bot.sendMessage(session.chatId, `❌ **Número inválido**\n\n` + `Por favor, digite um número entre 1 e ${session.dados.servicos.length}:` ); return; } session.dados.servicoSelecionado = session.dados.servicos[numero - 1]; session.etapa = 'selecionar_categoria'; await this.processarEtapaSelecionarCategoria(sessionId); } } /** * Mostrar opções de serviços */ async mostrarOpcoesServicos(session) { let message = `📋 **Serviços disponíveis:**\n\n`; const buttons = []; session.dados.servicos.forEach((servico, index) => { const statusEmoji = servico.statusNumerico === 1 ? '🟢' : '🔴'; // Extrair usuário sem o domínio const usuarioRaw = servico.usuario || servico.login || servico.nome || ''; const usuario = usuarioRaw.includes('@') ? usuarioRaw.split('@')[0] : usuarioRaw; // Montar endereço de instalação const endParts = [ servico.instalacao_endereco, servico.instalacao_numero ].filter(Boolean); const endereco = endParts.length > 0 ? endParts.join(', ') : null; message += `**${index + 1}.** ${statusEmoji} ${usuario}\n`; if (endereco) message += ` 📍 ${endereco}\n`; message += ` 🆔 ID: ${servico.id}\n`; message += `\n`; // Adicionar botão para seleção direta const labelBtn = endereco ? `${index + 1}. ${usuario} — ${endereco}` : `${index + 1}. ${usuario}`; buttons.push([{ text: labelBtn, callback_data: `suporte_servico_${index}` }]); }); message += `Selecione o serviço clicando no botão ou digite o **número** (1-${session.dados.servicos.length}):`; const keyboard = { inline_keyboard: buttons }; await this.bot.sendMessage(session.chatId, message, { parse_mode: 'Markdown', reply_markup: keyboard }); } /** * Etapa 4: Selecionar categoria */ async processarEtapaSelecionarCategoria(sessionId, input = null) { const session = this.suporteSessions.get(sessionId); if (!session) return; if (!input) { // Primeira vez nesta etapa - buscar categorias await this.bot.sendMessage(session.chatId, `🔍 Buscando categorias de suporte...`); try { const resultado = await this.topsapp.obterSuporteCategorias(); if (!resultado.success) { await this.bot.sendMessage(session.chatId, `❌ **Erro ao buscar categorias**\n\n` + `⚠️ ${resultado.error}\n\n` + `🔄 Reiniciando processo...` ); this.suporteSessions.delete(sessionId); return; } if (!resultado.data?.dados || resultado.data.dados.length === 0) { await this.bot.sendMessage(session.chatId, `❌ **Nenhuma categoria encontrada**\n\n` + `🔄 Reiniciando processo...` ); this.suporteSessions.delete(sessionId); return; } session.dados.categorias = resultado.data.dados; await this.mostrarOpcoesCategorias(session); } catch (error) { await this.bot.sendMessage(session.chatId, `❌ **Erro ao buscar categorias**\n\n` + `⚠️ ${error.message}\n\n` + `🔄 Reiniciando processo...` ); this.suporteSessions.delete(sessionId); } } else { // Usuário selecionou uma categoria const numero = parseInt(input.trim()); if (isNaN(numero) || numero < 1 || numero > session.dados.categorias.length) { await this.bot.sendMessage(session.chatId, `❌ **Número inválido**\n\n` + `Por favor, digite um número entre 1 e ${session.dados.categorias.length}:` ); return; } session.dados.categoriaSelecionada = session.dados.categorias[numero - 1]; session.etapa = 'selecionar_tecnico'; await this.processarEtapaSelecionarTecnico(sessionId); } } /** * Mostrar opções de categorias */ async mostrarOpcoesCategorias(session) { let message = `📁 **Categorias de suporte:**\n\n`; // Verificar se categorias é um array válido if (!Array.isArray(session.dados.categorias)) { console.error(`❌ session.dados.categorias não é um array:`, { type: typeof session.dados.categorias, value: session.dados.categorias }); throw new Error(`Dados de categorias inválidos: ${typeof session.dados.categorias}`); } session.dados.categorias.forEach((categoria, index) => { message += `**${index + 1}.** ${categoria.descricao}\n`; }); message += `\nDigite o **número** da categoria do suporte (1-${session.dados.categorias.length}):`; await this.bot.sendMessage(session.chatId, message, { parse_mode: 'Markdown' }); } /** * Etapa 5: Selecionar técnico */ async processarEtapaSelecionarTecnico(sessionId, input = null) { const session = this.suporteSessions.get(sessionId); if (!session) return; if (!input) { // Primeira vez nesta etapa - buscar equipes/técnicos via operadores await this.bot.sendMessage(session.chatId, `🔍 Buscando técnicos disponíveis...`); try { const location = session.dados.location || ''; // ObterOperadores traz o campo id_tecnico/tecnico que mapeia as equipes cadastradas const resultado = await this.topsapp.obterOperadores(location); if (!resultado.success) { await this.bot.sendMessage(session.chatId, `❌ **Erro ao buscar técnicos**\n\n` + `⚠️ ${resultado.error}\n\n` + `🔄 Reiniciando processo...` ); this.suporteSessions.delete(sessionId); return; } const operadores = resultado.data?.dados || []; // Extrair técnicos únicos pelo id_tecnico (cada operador aponta para uma equipe) const tecnicosMap = new Map(); operadores.forEach(op => { if (op.id_tecnico > 0 && op.tecnico && op.tecnico.trim() !== '') { if (!tecnicosMap.has(op.id_tecnico)) { tecnicosMap.set(op.id_tecnico, { id: op.id_tecnico, nome: op.tecnico }); } } }); const tecnicos = Array.from(tecnicosMap.values()).sort((a, b) => a.nome.localeCompare(b.nome)); if (tecnicos.length === 0) { await this.bot.sendMessage(session.chatId, `❌ **Nenhum técnico encontrado**\n\n` + `🔄 Reiniciando processo...` ); this.suporteSessions.delete(sessionId); return; } session.dados.tecnicos = tecnicos; await this.mostrarOpcoesTecnicos(session); } catch (error) { await this.bot.sendMessage(session.chatId, `❌ **Erro ao buscar técnicos**\n\n` + `⚠️ ${error.message}\n\n` + `🔄 Reiniciando processo...` ); this.suporteSessions.delete(sessionId); } } else { // Usuário selecionou um técnico const numero = parseInt(input.trim()); if (isNaN(numero) || numero < 1 || numero > session.dados.tecnicos.length) { await this.bot.sendMessage(session.chatId, `❌ **Número inválido**\n\n` + `Por favor, digite um número entre 1 e ${session.dados.tecnicos.length}:` ); return; } const tecnicoSelecionado = session.dados.tecnicos[numero - 1]; session.dados.tecnicoSelecionado = tecnicoSelecionado; session.etapa = 'definir_data_hora'; await this.processarEtapaDefinirDataHora(sessionId); } } /** * Mostrar opções de técnicos/tipos de atendimento */ async mostrarOpcoesTecnicos(session) { let message = `👨‍🔧 **Técnicos disponíveis:**\n\n`; // Validar se técnicos é um array if (!Array.isArray(session.dados.tecnicos)) { console.error(`❌ session.dados.tecnicos não é um array:`, { type: typeof session.dados.tecnicos, value: session.dados.tecnicos }); throw new Error(`Dados de técnicos inválidos: ${typeof session.dados.tecnicos}`); } const buttons = []; session.dados.tecnicos.forEach((tecnico, index) => { message += `**${index + 1}.** ${tecnico.nome}\n`; buttons.push([{ text: `${index + 1}. ${tecnico.nome}`, callback_data: `suporte_tecnico_${index}` }]); }); message += `\nSelecione o técnico clicando no botão ou digitando o número (1-${session.dados.tecnicos.length}):`; await this.bot.sendMessage(session.chatId, message, { parse_mode: 'Markdown', reply_markup: { inline_keyboard: buttons } }); } /** * Etapa 6: Definir data e hora */ async processarEtapaDefinirDataHora(sessionId, input = null) { const session = this.suporteSessions.get(sessionId); if (!session) return; if (!input) { // Primeira vez nesta etapa await this.bot.sendMessage(session.chatId, `📅 **Data e horário para visita**\n\n` + `Digite quando você gostaria que o técnico fizesse a visita.\n\n` + `**Exemplos:**\n` + `• "amanhã às 14h"\n` + `• "sexta-feira 09:00"\n` + `• "15/08 às 16h30"\n` + `• "hoje 18h"\n` + `• "em 3 dias às 10h"\n\n` + `📝 Digite a data e horário:`, { parse_mode: 'Markdown' } ); } else { // Processar texto de data/hora const resultado = this.dateProcessor.processarTexto(input); if (!resultado.success) { await this.bot.sendMessage(session.chatId, `❌ **Não consegui entender a data/hora**\n\n` + `⚠️ ${resultado.error}\n\n` + `**Exemplos válidos:**\n` + `• "amanhã às 14h"\n` + `• "sexta-feira 09:00"\n` + `• "15/08 às 16h30"\n\n` + `Por favor, tente novamente:` ); return; } // Verificar se a data não é no passado if (resultado.data < new Date()) { await this.bot.sendMessage(session.chatId, `❌ **Data no passado**\n\n` + `A data especificada (${this.dateProcessor.formatarData(resultado.data)}) já passou.\n\n` + `Por favor, especifique uma data futura:` ); return; } session.dados.dataHora = resultado.data; session.dados.dataHoraTexto = input; session.etapa = 'adicionar_descricao'; await this.bot.sendMessage(session.chatId, `✅ **Data/hora confirmada:**\n${this.dateProcessor.formatarData(resultado.data)}\n\n` + `📝 **Última etapa:** Descrição do problema\n\n` + `Digite uma descrição detalhada do problema ou serviço que precisa ser realizado:`, { parse_mode: 'Markdown' } ); } } /** * Etapa 7: Adicionar descrição */ async processarEtapaAdicionarDescricao(sessionId, descricao) { const session = this.suporteSessions.get(sessionId); if (!session) return; if (!descricao || descricao.trim().length < 10) { await this.bot.sendMessage(session.chatId, `❌ **Descrição muito curta**\n\n` + `Por favor, forneça uma descrição mais detalhada (mínimo 10 caracteres):` ); return; } session.dados.descricao = descricao.trim(); // Mostrar resumo e confirmar await this.mostrarResumoSuporte(session); } /** * Mostrar resumo do suporte antes de criar */ async mostrarResumoSuporte(session) { const dados = session.dados; let resumo = `📋 **Resumo do Suporte**\n\n`; resumo += `👤 **Cliente:** ${dados.clienteSelecionado.nome}\n`; resumo += `🆔 **ID Cliente:** ${dados.clienteSelecionado.id}\n`; resumo += `📋 **Serviço:** ${dados.servicoSelecionado.descricao || dados.servicoSelecionado.plano}\n`; resumo += `🆔 **ID Serviço:** ${dados.servicoSelecionado.id}\n`; resumo += `📁 **Categoria:** ${dados.categoriaSelecionada.descricao}\n`; resumo += `👨‍🔧 **Técnico:** ${dados.tecnicoSelecionado.nome}\n`; resumo += `📅 **Data/Hora:** ${this.dateProcessor.formatarData(dados.dataHora)}\n`; resumo += `📝 **Descrição:** ${dados.descricao}\n\n`; resumo += `Confirma a abertura do suporte? (sim/não)`; await this.bot.sendMessage(session.chatId, resumo, { parse_mode: 'Markdown' }); session.etapa = 'confirmar'; } /** * Finalizar criação do suporte */ async finalizarCriacaoSuporte(sessionId, confirmacao) { const session = this.suporteSessions.get(sessionId); if (!session) return; const confirmar = confirmacao.toLowerCase().trim(); if (!['sim', 's', 'yes', 'y', 'confirmar', 'ok'].includes(confirmar)) { await this.bot.sendMessage(session.chatId, `❌ **Suporte cancelado**\n\n` + `O processo foi cancelado. Use o comando "abrir suporte" para tentar novamente.` ); this.suporteSessions.delete(sessionId); return; } await this.bot.sendMessage(session.chatId, `⏳ Criando suporte...`); try { const dados = session.dados; const location = dados.location || ''; const dadosSuporte = { idCliente: dados.clienteSelecionado.id, idServico: dados.servicoSelecionado.id, idCategoria: dados.categoriaSelecionada.id, descricao: `${dados.descricao}\n\nData/Hora agendada: ${this.dateProcessor.formatarData(dados.dataHora)}\nTécnico: ${dados.tecnicoSelecionado.nome}` }; if (dados.clienteSelecionado?.nome) { dadosSuporte.contato = dados.clienteSelecionado.nome; } if (dados.clienteSelecionado?.telefone) { dadosSuporte.contatoTelefone = dados.clienteSelecionado.telefone; } dadosSuporte.outrasInformacoes = `Criado via Bot Telegram\nData original: ${dados.dataHoraTexto}`; const resultado = await this.topsapp.adicionarSuporte(dadosSuporte, location); if (resultado.success) { const protocolo = resultado.data?.dados?.protocolo || resultado.data?.protocolo; // Encaminhar para o técnico selecionado const idTecnico = dados.tecnicoSelecionado?.id; if (protocolo && idTecnico) { const resEnc = await this.topsapp.encaminharSuporte( protocolo, idTecnico, `Técnico atribuído via Bot Telegram: ${dados.tecnicoSelecionado.nome}`, location ); if (!resEnc.success) { logger.warn('Suporte criado mas falha ao encaminhar técnico:', resEnc.error); } } await this.bot.sendMessage(session.chatId, `✅ **Suporte criado com sucesso!**\n\n` + `🎫 **Protocolo:** ${protocolo || 'N/A'}\n` + `👤 **Cliente:** ${dados.clienteSelecionado.nome}\n` + `📋 **Serviço:** ${dados.servicoSelecionado.descricao || dados.servicoSelecionado.plano}\n` + `📁 **Categoria:** ${dados.categoriaSelecionada.descricao}\n` + `👨‍🔧 **Técnico:** ${dados.tecnicoSelecionado.nome}\n` + `📅 **Data/Hora:** ${this.dateProcessor.formatarData(dados.dataHora)}\n\n` + `🎯 O suporte foi registrado no sistema!`, { parse_mode: 'Markdown' } ); logger.info('Suporte criado via bot', { protocolo, cliente: dados.clienteSelecionado.nome, usuario: session.userId }); } else { await this.bot.sendMessage(session.chatId, `❌ **Erro ao criar suporte**\n\n` + `⚠️ ${resultado.error}\n\n` + `Tente novamente mais tarde.` ); } } catch (error) { logger.error('Erro ao criar suporte:', error); await this.bot.sendMessage(session.chatId, `❌ **Erro ao criar suporte**\n\n` + `⚠️ ${error.message}\n\n` + `Tente novamente mais tarde.` ); } // Limpar sessão this.suporteSessions.delete(sessionId); } // ───────────────────────────────────────────────────────────────────────── // Fechar Suporte // ───────────────────────────────────────────────────────────────────────── /** * Iniciar processo de fechamento de suporte */ async iniciarProcessoFecharSuporte(chatId, userId, target = '') { if (!this.topsapp.isReady()) { await this.bot.sendMessage(chatId, `❌ **TopSapp não configurado**\n\n` + `⚠️ O sistema TopSapp não está configurado. Entre em contato com o administrador.` ); return; } const sessionId = `${userId}_${chatId}_${Date.now()}`; const suporteSession = { id: sessionId, chatId, userId, etapa: 'fechar_buscar_cliente', dados: {}, iniciado: new Date() }; this.suporteSessions.set(sessionId, suporteSession); await this.bot.sendMessage(chatId, `🔒 **Fechamento de Suporte**\n\n` + `🔍 Digite o **nome** ou **CPF** do cliente cujo suporte deseja fechar:`, { parse_mode: 'Markdown' } ); if (target && target.trim()) { await this.processarEtapaFecharBuscarCliente(sessionId, target.trim()); } } /** * Etapa 1 (fechar): Buscar cliente */ async processarEtapaFecharBuscarCliente(sessionId, query) { const session = this.suporteSessions.get(sessionId); if (!session) return; await this.bot.sendMessage(session.chatId, `🔍 Buscando cliente: "${query}"...`); try { const resultado = await this.topsapp.buscarCliente(query); if (!resultado.success || !resultado.clientes || resultado.clientes.length === 0) { await this.bot.sendMessage(session.chatId, `❌ **Nenhum cliente encontrado**\n\n` + `🔍 Não foi encontrado nenhum cliente com: "${query}"\n\n` + `Por favor, tente novamente com outro nome ou CPF:` ); return; } session.dados.clientesEncontrados = resultado.clientes; if (resultado.clientes.length === 1) { session.dados.clienteSelecionado = resultado.clientes[0]; session.dados.location = resultado.instancia || ''; await this.processarEtapaFecharBuscarSuportes(sessionId); } else { session.etapa = 'fechar_selecionar_cliente'; let message = `👥 **Clientes encontrados:**\n\n`; const buttons = []; resultado.clientes.forEach((cliente, index) => { message += `**${index + 1}.** ${cliente.nome} — CPF: ${cliente.cpf || 'N/A'}\n`; buttons.push([{ text: `${index + 1}. ${cliente.nome}`, callback_data: `fechar_suporte_cli_${sessionId}_${index}` }]); }); message += `\nDigite o **número** do cliente:`; await this.bot.sendMessage(session.chatId, message, { parse_mode: 'Markdown', reply_markup: { inline_keyboard: buttons } }); } } catch (error) { await this.bot.sendMessage(session.chatId, `❌ **Erro na busca**\n\n⚠️ ${error.message}\n\nTente novamente:`); } } /** * Etapa 2 (fechar): Selecionar cliente quando há múltiplos */ async processarEtapaFecharSelecionarCliente(sessionId, input) { const session = this.suporteSessions.get(sessionId); if (!session) return; const numero = parseInt(input.trim()); if (isNaN(numero) || numero < 1 || numero > session.dados.clientesEncontrados.length) { await this.bot.sendMessage(session.chatId, `❌ Número inválido. Digite entre 1 e ${session.dados.clientesEncontrados.length}:` ); return; } session.dados.clienteSelecionado = session.dados.clientesEncontrados[numero - 1]; session.dados.location = this.getActiveLocation(session.chatId); await this.processarEtapaFecharBuscarSuportes(sessionId); } /** * Buscar suportes abertos do cliente selecionado */ async processarEtapaFecharBuscarSuportes(sessionId) { const session = this.suporteSessions.get(sessionId); if (!session) return; const cliente = session.dados.clienteSelecionado; await this.bot.sendMessage(session.chatId, `🔍 Buscando suportes abertos de **${cliente.nome}**...`, { parse_mode: 'Markdown' }); try { const resultado = await this.topsapp.obterSuportesAbertos(cliente.id, session.dados.location || ''); const suportes = resultado.success ? (resultado.data?.dados || []) : []; if (suportes.length === 0) { await this.bot.sendMessage(session.chatId, `ℹ️ **Nenhum suporte aberto**\n\n` + `O cliente **${cliente.nome}** não possui suportes abertos no momento.`, { parse_mode: 'Markdown' } ); this.suporteSessions.delete(sessionId); return; } session.dados.suportesAbertos = suportes; if (suportes.length === 1) { session.dados.suporteSelecionado = suportes[0]; session.etapa = 'fechar_aguardando_descricao'; const s = suportes[0]; const enderecoCompleto = [s.endereco, s.numero, s.bairro, s.cidade].filter(Boolean).join(', '); await this.bot.sendMessage(session.chatId, `🎫 **Suporte encontrado:**\n\n` + `🆔 ID: ${s.id}\n` + `📝 Problema: ${s.defeito || 'N/A'}\n` + `📍 Endereço: ${enderecoCompleto || 'N/A'}\n` + `📅 Data: ${s.data || 'N/A'}\n\n` + `✏️ Digite a **descrição do fechamento** do suporte:`, { parse_mode: 'Markdown' } ); } else { session.etapa = 'fechar_selecionar_suporte'; let message = `🎫 **Suportes abertos de ${cliente.nome}:**\n\n`; const buttons = []; suportes.forEach((s, index) => { const enderecoCompleto = [s.endereco, s.numero, s.bairro].filter(Boolean).join(', '); message += `**${index + 1}.** ID #${s.id}\n`; message += ` 📝 ${s.defeito || 'Sem descrição'}\n`; if (enderecoCompleto) message += ` 📍 ${enderecoCompleto}\n`; message += ` 📅 ${s.data || ''}\n\n`; buttons.push([{ text: `${index + 1}. #${s.id} — ${(s.defeito || 'Sem descrição').substring(0, 40)}`, callback_data: `fechar_suporte_id_${sessionId}_${index}` }]); }); message += `Selecione qual suporte deseja fechar:`; await this.bot.sendMessage(session.chatId, message, { parse_mode: 'Markdown', reply_markup: { inline_keyboard: buttons } }); } } catch (error) { await this.bot.sendMessage(session.chatId, `❌ **Erro ao buscar suportes**\n\n⚠️ ${error.message}`); this.suporteSessions.delete(sessionId); } } /** * Etapa 3 (fechar): Selecionar suporte quando há múltiplos */ async processarEtapaFecharSelecionarSuporte(sessionId, input) { const session = this.suporteSessions.get(sessionId); if (!session) return; const numero = parseInt(input.trim()); if (isNaN(numero) || numero < 1 || numero > session.dados.suportesAbertos.length) { await this.bot.sendMessage(session.chatId, `❌ Número inválido. Digite entre 1 e ${session.dados.suportesAbertos.length}:` ); return; } session.dados.suporteSelecionado = session.dados.suportesAbertos[numero - 1]; session.etapa = 'fechar_aguardando_descricao'; const s = session.dados.suporteSelecionado; const enderecoCompleto = [s.endereco, s.numero, s.bairro, s.cidade].filter(Boolean).join(', '); await this.bot.sendMessage(session.chatId, `🎫 **Suporte selecionado:**\n\n` + `🆔 ID: ${s.id}\n` + `📝 Problema: ${s.defeito || 'N/A'}\n` + `📍 Endereço: ${enderecoCompleto || 'N/A'}\n\n` + `✏️ Digite a **descrição do fechamento** do suporte:`, { parse_mode: 'Markdown' } ); } /** * Etapa 4 (fechar): Receber descrição e executar fechamento */ async processarEtapaFecharDescricao(sessionId, descricao) { const session = this.suporteSessions.get(sessionId); if (!session) return; if (!descricao || descricao.trim().length < 3) { await this.bot.sendMessage(session.chatId, `❌ A descrição é obrigatória e deve ter ao menos 3 caracteres.\n\nDigite a descrição do fechamento:` ); return; } const s = session.dados.suporteSelecionado; const cliente = session.dados.clienteSelecionado; await this.bot.sendMessage(session.chatId, `⏳ Finalizando suporte #${s.id}...`); try { const resultado = await this.topsapp.finalizarSuporte(s.id, descricao.trim(), session.dados.location || ''); if (resultado.success) { await this.bot.sendMessage(session.chatId, `✅ **Suporte finalizado com sucesso!**\n\n` + `🎫 ID do Suporte: #${s.id}\n` + `👤 Cliente: ${cliente.nome}\n` + `📝 Problema: ${s.defeito || 'N/A'}\n` + `✏️ Fechamento: ${descricao.trim()}`, { parse_mode: 'Markdown' } ); logger.info('Suporte finalizado via bot', { idSuporte: s.id, cliente: cliente.nome, usuario: session.userId }); } else { await this.bot.sendMessage(session.chatId, `❌ **Erro ao finalizar suporte**\n\n⚠️ ${resultado.error}\n\nTente novamente mais tarde.` ); } } catch (error) { logger.error('Erro ao finalizar suporte:', error); await this.bot.sendMessage(session.chatId, `❌ **Erro ao finalizar suporte**\n\n⚠️ ${error.message}`); } this.suporteSessions.delete(sessionId); } // ───────────────────────────────────────────────────────────────────────── // TopSapp — Vinculação de ONU após adição // ───────────────────────────────────────────────────────────────────────── /** * Roteador de callbacks topsapp_onu_* */ async handleTopSappOnuCallback(callbackQuery, data) { const chatId = callbackQuery.message.chat.id; const userId = callbackQuery.from.id; await this.bot.answerCallbackQuery(callbackQuery.id); const session = this.userSessions.get(userId); if (data === 'topsapp_onu_sim') { if (!session || !session.onuData) { await this.bot.sendMessage(chatId, '❌ Sessão expirada. Tente novamente após adicionar a ONU.'); return; } session.action = 'topsapp_onu_aguardando_nome'; this.userSessions.set(userId, session); await this.bot.sendMessage(chatId, `🔍 *Buscar Cliente no TopSapp*\n\nDigite o nome ou CPF do cliente:`, { parse_mode: 'Markdown' } ); return; } if (data.startsWith('topsapp_onu_cli_')) { const idCliente = data.replace('topsapp_onu_cli_', ''); if (!session || !session.onuData) { await this.bot.sendMessage(chatId, '❌ Sessão expirada. Tente novamente.'); return; } await this.topsappOnuMostrarServicos(chatId, userId, idCliente, session.onuData); return; } if (data.startsWith('topsapp_onu_svc_')) { // formato: topsapp_onu_svc__ const parts = data.replace('topsapp_onu_svc_', '').split('_'); const idServico = parts[0]; const idCliente = parts[1]; if (!session || !session.onuData) { await this.bot.sendMessage(chatId, '❌ Sessão expirada. Tente novamente.'); return; } await this.topsappOnuConfirmarVinculo(chatId, userId, idServico, idCliente, session.onuData); return; } if (data === 'topsapp_onu_ok') { if (!session || !session.ftthPayload) { await this.bot.sendMessage(chatId, '❌ Sessão expirada. Tente novamente.'); return; } await this.topsappOnuExecutarVinculo(chatId, userId, session); return; } await this.bot.sendMessage(chatId, '❓ Ação desconhecida.'); } /** * Busca cliente no TopSapp e exibe lista para seleção */ async topsappOnuBuscarCliente(chatId, userId, query, onuData) { try { await this.bot.sendMessage(chatId, '🔍 Buscando cliente...'); const resultado = await this.topsapp.buscarCliente(query, onuData.location); if (!resultado.success) { await this.bot.sendMessage(chatId, `❌ *Erro na busca*\n\n${resultado.error}`, { parse_mode: 'Markdown' } ); this.userSessions.delete(userId); return; } if (!resultado.clientes || resultado.clientes.length === 0) { await this.bot.sendMessage(chatId, `🔍 *Nenhum cliente encontrado*\n\nTermo: "${query}"\n\nDigite outro nome ou CPF:`, { parse_mode: 'Markdown', reply_markup: { inline_keyboard: [[{ text: '❌ Cancelar', callback_data: 'cancel_action' }]] } } ); // Mantém sessão ativa para nova tentativa return; } const keyboard = []; let msg = `👥 *${resultado.clientes.length > 1 ? resultado.clientes.length + ' clientes encontrados' : 'Cliente encontrado'}*\n\nSelecione o cliente:\n\n`; resultado.clientes.slice(0, 8).forEach((cliente, i) => { msg += `${i + 1}. *${cliente.nome}* (ID: ${cliente.id})\n`; keyboard.push([{ text: `${i + 1}. ${cliente.nome}`, callback_data: `topsapp_onu_cli_${cliente.id}` }]); }); keyboard.push([{ text: '❌ Cancelar', callback_data: 'cancel_action' }]); await this.bot.sendMessage(chatId, msg, { parse_mode: 'Markdown', reply_markup: { inline_keyboard: keyboard } }); } catch (error) { console.error('❌ Erro ao buscar cliente TopSapp:', error); await this.bot.sendMessage(chatId, `❌ Erro ao buscar cliente: ${error.message}`); this.userSessions.delete(userId); } } /** * Lista serviços ativos do cliente para seleção */ async topsappOnuMostrarServicos(chatId, userId, idCliente, onuData) { try { await this.bot.sendMessage(chatId, '📋 Buscando serviços do cliente...'); const servicosResult = await this.topsapp.obterServicosCliente(idCliente, onuData.location); if (!servicosResult.success) { await this.bot.sendMessage(chatId, `❌ Erro ao buscar serviços: ${servicosResult.error}`); this.userSessions.delete(userId); return; } const servicos = servicosResult.servicos || []; const servicosAtivos = servicos.filter(s => s.id_status === 1 || (s.status || '').toLowerCase() === 'liberado' ); if (servicosAtivos.length === 0) { await this.bot.sendMessage(chatId, `⚠️ *Nenhum serviço ativo encontrado* para o cliente ID ${idCliente}.\n\nCancele e verifique o cliente.`, { parse_mode: 'Markdown', reply_markup: { inline_keyboard: [[{ text: '❌ Cancelar', callback_data: 'cancel_action' }]] } } ); return; } const keyboard = []; let msg = `📋 *Serviços ativos do cliente*\n\nSelecione o serviço FTTH:\n\n`; servicosAtivos.slice(0, 8).forEach((servico, i) => { const idSvc = servico.id_servico || servico.id; const nome = servico.descricao || servico.nome || 'Serviço'; const login = servico.usuario ? ` (${servico.usuario})` : ''; msg += `${i + 1}. *${nome}*${login} — ID: ${idSvc}\n`; keyboard.push([{ text: `${i + 1}. ${nome}${login}`, callback_data: `topsapp_onu_svc_${idSvc}_${idCliente}` }]); }); keyboard.push([{ text: '❌ Cancelar', callback_data: 'cancel_action' }]); await this.bot.sendMessage(chatId, msg, { parse_mode: 'Markdown', reply_markup: { inline_keyboard: keyboard } }); } catch (error) { console.error('❌ Erro ao listar serviços TopSapp:', error); await this.bot.sendMessage(chatId, `❌ Erro: ${error.message}`); this.userSessions.delete(userId); } } /** * Faz matching OLT/porta/perfil e mostra tela de confirmação */ async topsappOnuConfirmarVinculo(chatId, userId, idServico, idCliente, onuData) { try { await this.bot.sendMessage(chatId, '🔌 Buscando dados da OLT no TopSapp...'); const oltsResult = await this.topsapp.obterOlts(onuData.location); if (!oltsResult.success) { await this.bot.sendMessage(chatId, `❌ Erro ao buscar OLTs: ${oltsResult.error}`); this.userSessions.delete(userId); return; } const olts = oltsResult.data?.dados?.dados || oltsResult.data?.dados || []; // Mapa manual de keywords por oltId/tipo → para busca no campo ssid e nome do TopSapp // Fonte: valores reais de ssid cadastrados no TopSapp const SSID_KEYWORDS = { 'fiberhome': ['fiberhome', '172.16.10.6'], 'olt_colmeia': ['colmeia'], 'olt_canaa': ['canaa'], 'olt_canaa2': ['canaa 2', 'canaa2'], 'olt_dito': ['dito'], 'olt_pedro_afonso': ['pedro', 'afonso'], 'olt_canavieiras': ['canavieiras'] }; /** * Busca uma OLT na lista pelo campo nome OU ssid usando lista de keywords. * Tenta match com TODOS os tokens primeiro; se falhar, com QUALQUER token. */ const buscarPorKeywords = (keywords) => { const found = olts.find(o => { const haystack = `${o.nome || ''} ${o.ssid || ''}`.toLowerCase(); return keywords.every(k => haystack.includes(k)); }); if (found) return found; return olts.find(o => { const haystack = `${o.nome || ''} ${o.ssid || ''}`.toLowerCase(); return keywords.some(k => haystack.includes(k)); }); }; let oltEncontrada = null; // 1. Match manual pelo tipo (Fiberhome) ou oltId (Datacom) const tipoKey = onuData.tipo === 'fiberhome' ? 'fiberhome' : onuData.oltId; if (tipoKey && SSID_KEYWORDS[tipoKey]) { oltEncontrada = buscarPorKeywords(SSID_KEYWORDS[tipoKey]); if (oltEncontrada) { console.log(`✅ OLT encontrada por mapa manual (${tipoKey}): "${oltEncontrada.nome}" / ssid: "${oltEncontrada.ssid}"`); } } // 2. Fallback: keyword livre do oltId (Datacom sem mapa definido) if (!oltEncontrada && onuData.oltId) { const keyword = onuData.oltId.replace(/^olt_/, '').replace(/_/g, ' ').toLowerCase(); const keywords = keyword.split(' ').filter(k => k.length > 2); oltEncontrada = buscarPorKeywords(keywords); if (oltEncontrada) { console.log(`✅ OLT encontrada por keyword livre (oltId): "${oltEncontrada.nome}" (keyword: "${keyword}")`); } } // 3. Fallback: keyword livre do oltHost (Fiberhome sem mapa definido) if (!oltEncontrada && onuData.oltHost) { const keywords = onuData.oltHost.toLowerCase().split(/[\s_\-]+/).filter(k => k.length > 1); oltEncontrada = buscarPorKeywords(keywords); if (oltEncontrada) { console.log(`✅ OLT encontrada por keyword livre (oltHost): "${oltEncontrada.nome}" (keyword: "${onuData.oltHost}")`); } } // 4. Fallback: primeira OLT disponível if (!oltEncontrada && olts.length > 0) { oltEncontrada = olts[0]; console.log(`⚠️ OLT não identificada — usando a primeira disponível: ${oltEncontrada.nome}`); } if (!oltEncontrada) { await this.bot.sendMessage(chatId, `❌ Nenhuma OLT encontrada no TopSapp para este servidor.`); this.userSessions.delete(userId); return; } // Encontrar porta pelo alias const portas = oltEncontrada.portas || []; let portaEncontrada = portas.find(p => p.alias === onuData.portaAlias); if (!portaEncontrada && portas.length > 0) { portaEncontrada = portas[0]; console.log(`⚠️ Porta não encontrada por alias "${onuData.portaAlias}", usando a primeira: ${portaEncontrada.alias}`); } if (!portaEncontrada) { await this.bot.sendMessage(chatId, `❌ Nenhuma porta encontrada na OLT "${oltEncontrada.nome}".`); this.userSessions.delete(userId); return; } // Encontrar perfil por tipo de ONU const perfis = oltEncontrada.perfis || []; let perfilEncontrado; if (onuData.tipo === 'datacom') { perfilEncontrado = perfis.find(p => p.identificacao && p.identificacao.includes('DATACOM ONU_BRIDGE_')); } else { perfilEncontrado = perfis.find(p => p.identificacao === 'ONU-BRIDGE-AN5506-02-B'); } if (!perfilEncontrado && perfis.length > 0) { perfilEncontrado = perfis[0]; console.log(`⚠️ Perfil não encontrado por tipo, usando o primeiro: ${perfilEncontrado.identificacao}`); } if (!perfilEncontrado) { await this.bot.sendMessage(chatId, `❌ Nenhum perfil encontrado na OLT "${oltEncontrada.nome}".`); this.userSessions.delete(userId); return; } const ftthData = { serial: onuData.serial, idAP: oltEncontrada.id, idPorta: portaEncontrada.id, idPerfil: perfilEncontrado.id }; if (onuData.onuId !== undefined && onuData.onuId !== null) { ftthData.ftth_onuid = parseInt(onuData.onuId); ftthData.onuid = parseInt(onuData.onuId); } if (onuData.servicePort !== null && onuData.servicePort !== undefined) { ftthData.ftth_servport = onuData.servicePort; ftthData.ftth_serviceport = onuData.servicePort; ftthData.serviceport = onuData.servicePort; } console.log('🔧 TopSapp ftthData completo:', JSON.stringify(ftthData, null, 2)); // Salvar payload de confirmação na sessão const session = this.userSessions.get(userId); session.action = 'topsapp_onu_confirmando'; session.ftthPayload = { idCliente, idServico, ftthData, location: onuData.location }; this.userSessions.set(userId, session); const onuIdInfo = onuData.onuId !== undefined && onuData.onuId !== null ? `\n🔢 *ONU ID:* ${onuData.onuId}` : ''; const spInfo = onuData.servicePort !== null && onuData.servicePort !== undefined ? `\n🔌 *Service-port:* ${onuData.servicePort}` : ''; await this.bot.sendMessage(chatId, `🔌 *Confirmar Vinculação no TopSapp*\n\n` + `📱 *Serial:* \`${onuData.serial}\`\n` + `🏢 *OLT:* ${oltEncontrada.nome}\n` + `📡 *Porta:* ${portaEncontrada.alias}` + onuIdInfo + spInfo + `\n` + `⚙️ *Perfil:* ${perfilEncontrado.identificacao}\n` + `🆔 *Serviço ID:* ${idServico}\n` + `👤 *Cliente ID:* ${idCliente}\n\n` + `Confirmar os dados acima?`, { parse_mode: 'Markdown', reply_markup: { inline_keyboard: [[ { text: '✅ Confirmar', callback_data: 'topsapp_onu_ok' }, { text: '❌ Cancelar', callback_data: 'cancel_action' } ]] } } ); } catch (error) { console.error('❌ Erro ao confirmar vínculo TopSapp:', error); await this.bot.sendMessage(chatId, `❌ Erro: ${error.message}`); this.userSessions.delete(userId); } } /** * Executa a atualização FTTH no TopSapp via AlterarServico */ async topsappOnuExecutarVinculo(chatId, userId, session) { try { const { idCliente, idServico, ftthData, location } = session.ftthPayload; await this.bot.sendMessage(chatId, '⏳ Salvando dados no TopSapp...'); const resultado = await this.topsapp.atualizarFTTH(idCliente, idServico, ftthData, location); if (resultado.success) { await this.bot.sendMessage(chatId, `✅ *ONU vinculada com sucesso no TopSapp!*\n\n` + `📱 *Serial:* \`${ftthData.serial}\`\n` + `🆔 *Serviço ID:* ${idServico}\n` + `👤 *Cliente ID:* ${idCliente}`, { parse_mode: 'Markdown' } ); } else { await this.bot.sendMessage(chatId, `❌ *Erro ao vincular no TopSapp*\n\n${resultado.error}`, { parse_mode: 'Markdown' } ); } } catch (error) { console.error('❌ Erro ao executar vínculo TopSapp:', error); await this.bot.sendMessage(chatId, `❌ Erro: ${error.message}`); } finally { this.userSessions.delete(userId); } } /** * Callback: Seleção de cliente via botão inline no fluxo de suporte */ async handleSuporteClienteCallback(callbackQuery, data) { const chatId = callbackQuery.message.chat.id; const userId = callbackQuery.from.id; await this.bot.answerCallbackQuery(callbackQuery.id); const index = parseInt(data.replace('suporte_cliente_', '')); const sessaoAtiva = Array.from(this.suporteSessions.values()).find( session => session.chatId === chatId && session.userId === userId ); if (!sessaoAtiva) { await this.bot.sendMessage(chatId, `❌ **Sessão expirada**\n\nUse o comando "abrir suporte" para iniciar novamente.`, { parse_mode: 'Markdown' } ); return; } const clientes = sessaoAtiva.dados.clientesEncontrados; if (!clientes || index < 0 || index >= clientes.length) { await this.bot.sendMessage(chatId, `❌ **Cliente inválido.** Por favor, tente novamente.`, { parse_mode: 'Markdown' }); return; } sessaoAtiva.dados.clienteSelecionado = clientes[index]; sessaoAtiva.etapa = 'selecionar_servico'; await this.processarEtapaSelecionarServico(sessaoAtiva.id); } /** * Callback: Seleção de serviço via botão inline no fluxo de suporte */ async handleSuporteServicoCallback(callbackQuery, data) { const chatId = callbackQuery.message.chat.id; const userId = callbackQuery.from.id; await this.bot.answerCallbackQuery(callbackQuery.id); const index = parseInt(data.replace('suporte_servico_', '')); const sessaoAtiva = Array.from(this.suporteSessions.values()).find( session => session.chatId === chatId && session.userId === userId ); if (!sessaoAtiva) { await this.bot.sendMessage(chatId, `❌ **Sessão expirada**\n\nUse o comando "abrir suporte" para iniciar novamente.`, { parse_mode: 'Markdown' } ); return; } const servicos = sessaoAtiva.dados.servicos; if (!servicos || index < 0 || index >= servicos.length) { await this.bot.sendMessage(chatId, `❌ **Serviço inválido.** Por favor, tente novamente.`, { parse_mode: 'Markdown' }); return; } sessaoAtiva.dados.servicoSelecionado = servicos[index]; sessaoAtiva.etapa = 'selecionar_categoria'; await this.processarEtapaSelecionarCategoria(sessaoAtiva.id); } /** * Callback: Seleção de técnico via botão inline no fluxo de suporte */ async handleSuporteTecnicoCallback(callbackQuery, data) { const chatId = callbackQuery.message.chat.id; const userId = callbackQuery.from.id; const index = parseInt(data.replace('suporte_tecnico_', '')); const sessaoAtiva = Array.from(this.suporteSessions.values()).find( session => session.chatId === chatId && session.userId === userId ); if (!sessaoAtiva) { await this.bot.sendMessage(chatId, `❌ **Sessão expirada**\n\nUse o comando "abrir suporte" para iniciar novamente.`, { parse_mode: 'Markdown' } ); return; } const tecnicos = sessaoAtiva.dados.tecnicos; if (!tecnicos || index < 0 || index >= tecnicos.length) { await this.bot.sendMessage(chatId, `❌ **Técnico inválido.** Por favor, tente novamente.`, { parse_mode: 'Markdown' }); return; } sessaoAtiva.dados.tecnicoSelecionado = tecnicos[index]; sessaoAtiva.etapa = 'definir_data_hora'; await this.processarEtapaDefinirDataHora(sessaoAtiva.id); } /** * Callback: seleção de cliente no fluxo de fechar suporte * formato: fechar_suporte_cli__ */ async handleFecharSuporteClienteCallback(callbackQuery, data) { const chatId = callbackQuery.message.chat.id; const userId = callbackQuery.from.id; // fechar_suporte_cli__ const withoutPrefix = data.replace('fechar_suporte_cli_', ''); const lastUnder = withoutPrefix.lastIndexOf('_'); const sessionId = withoutPrefix.substring(0, lastUnder); const index = parseInt(withoutPrefix.substring(lastUnder + 1)); const session = this.suporteSessions.get(sessionId); if (!session || session.chatId !== chatId || session.userId !== userId) { await this.bot.sendMessage(chatId, `❌ Sessão expirada. Use "fechar suporte" para iniciar novamente.`); return; } const clientes = session.dados.clientesEncontrados; if (!clientes || index < 0 || index >= clientes.length) { await this.bot.sendMessage(chatId, `❌ Seleção inválida. Tente novamente.`); return; } session.dados.clienteSelecionado = clientes[index]; session.dados.location = this.getActiveLocation(chatId); await this.processarEtapaFecharBuscarSuportes(sessionId); } /** * Callback: seleção de suporte no fluxo de fechar suporte * formato: fechar_suporte_id__ */ async handleFecharSuporteIdCallback(callbackQuery, data) { const chatId = callbackQuery.message.chat.id; const userId = callbackQuery.from.id; // fechar_suporte_id__ const withoutPrefix = data.replace('fechar_suporte_id_', ''); const lastUnder = withoutPrefix.lastIndexOf('_'); const sessionId = withoutPrefix.substring(0, lastUnder); const index = parseInt(withoutPrefix.substring(lastUnder + 1)); const session = this.suporteSessions.get(sessionId); if (!session || session.chatId !== chatId || session.userId !== userId) { await this.bot.sendMessage(chatId, `❌ Sessão expirada. Use "fechar suporte" para iniciar novamente.`); return; } const suportes = session.dados.suportesAbertos; if (!suportes || index < 0 || index >= suportes.length) { await this.bot.sendMessage(chatId, `❌ Seleção inválida. Tente novamente.`); return; } session.dados.suporteSelecionado = suportes[index]; session.etapa = 'fechar_aguardando_descricao'; const s = session.dados.suporteSelecionado; const enderecoCompleto = [s.endereco, s.numero, s.bairro, s.cidade].filter(Boolean).join(', '); await this.bot.sendMessage(session.chatId, `🎫 **Suporte selecionado:**\n\n` + `🆔 ID: ${s.id}\n` + `📝 Problema: ${s.defeito || 'N/A'}\n` + `📍 Endereço: ${enderecoCompleto || 'N/A'}\n\n` + `✏️ Digite a **descrição do fechamento** do suporte:`, { parse_mode: 'Markdown' } ); } /** * Exibe informações do roteador/CPE via GenieACS. * Uso: "ver roteador " ou "ver roteador " */ async handleVerRoteador(chatId, termo) { if (!termo) { await this.bot.sendMessage(chatId, '📡 *Ver Roteador - GenieACS*\n\n' + '❓ Informe o serial ou a TAG (login TopSapp) do dispositivo.\n' + 'Exemplos:\n' + '• `ver roteador ABCD12345678`\n' + '• `ver roteador joao.silva`', { parse_mode: 'Markdown' } ); return; } // Escapa caracteres especiais do Markdown V1 em valores dinâmicos const esc = (v) => { if (v === null || v === undefined || v === '') return 'N/A'; return String(v).replace(/[_*`\[]/g, '\\$&'); }; await this.bot.sendMessage(chatId, `🔍 Consultando dispositivo \`${esc(termo)}\` no GenieACS...`, { parse_mode: 'Markdown' }); try { const resultado = await this.genieacs.buscarRoteador(termo); if (!resultado.success) { await this.bot.sendMessage(chatId, `❌ *Dispositivo não encontrado*\n\n` + `🔍 Busca: \`${esc(termo)}\`\n` + `⚠️ ${esc(resultado.error)}`, { parse_mode: 'Markdown' } ); return; } const d = resultado.device; const tagLabel = d.tags && d.tags.length > 0 ? d.tags.map(esc).join(', ') : 'N/A'; const buscadoPor = resultado.searchedBy === 'tag' ? 'TAG' : 'Serial'; let msg = `📡 *Informações do Roteador*\n` + `📌 Instância: ${esc(resultado.instance)}\n` + `🔎 Encontrado por: ${buscadoPor}\n\n` + `🖥️ *Dispositivo*\n` + `├ Nome: ${esc(d.productClass)}\n` + `├ Fabricante: ${esc(d.manufacturer)}\n` + `├ OUI: ${esc(d.oui)}\n` + `├ Serial: \`${esc(d.serial)}\`\n` + `├ TAG (TopSapp): \`${tagLabel}\`\n` + `├ HW: ${esc(d.hardwareVersion)}\n` + `└ SW: ${esc(d.softwareVersion)}\n\n` + `📶 *Wi-Fi 2.4 GHz*\n` + `├ SSID: ${esc(d.ssid24)}\n` + `├ Senha: \`${esc(d.senha24)}\`\n` + `├ Canal: ${esc(d.channel24)}\n` + `├ Auto-Canal: ${esc(d.autoChannel24)}\n` + `└ Clientes: ${d.clients24 !== null && d.clients24 !== undefined ? esc(d.clients24) : 'N/A'}\n\n` + `📶 *Wi-Fi 5 GHz*\n` + `├ SSID: ${esc(d.ssid5)}\n` + `├ Senha: \`${esc(d.senha5)}\`\n` + `├ Canal: ${esc(d.channel5)}\n` + `├ Auto-Canal: ${esc(d.autoChannel5)}\n` + `└ Clientes: ${d.clients5 !== null && d.clients5 !== undefined ? esc(d.clients5) : 'N/A'}`; // PPPoE WAN msg += `\n\n🌐 *PPPoE WAN*\n`; msg += `├ Username: ${esc(d.pppoeUsername)}\n`; msg += `├ Status: ${esc(d.pppoeStatus)}\n`; msg += `├ IP: \`${esc(d.pppoeIP)}\`\n`; msg += `├ Uptime: ${esc(d.pppoeUptime)}\n`; msg += `├ Gateway: ${esc(d.gatewayWAN)}\n`; msg += `├ DNS WAN: ${esc(d.dnsWAN)}\n`; msg += `├ Link Físico: ${esc(d.linkFisicoWAN)}\n`; msg += `├ Recebido: ${esc(d.trafegoRecebido)}\n`; msg += `├ Enviado: ${esc(d.trafegoEnviado)}\n`; if (d.taxaDownload) msg += `├ ↓ Download: ${esc(d.taxaDownload)}\n`; if (d.taxaUpload) msg += `├ ↑ Upload: ${esc(d.taxaUpload)}\n`; msg += `└ Último Erro: ${esc(d.pppoeLastError)}`; // DHCP LAN msg += `\n\n🏠 *LAN/DHCP*\n`; msg += `├ IP LAN: \`${esc(d.ipLAN)}\`\n`; msg += `├ Máscara: ${esc(d.mascaraLAN)}\n`; msg += `├ Início DHCP: ${esc(d.dhcpInicio)}\n`; msg += `├ Fim DHCP: ${esc(d.dhcpFim)}\n`; msg += `└ DNS LAN: ${esc(d.dnsLAN)}`; await this.bot.sendMessage(chatId, msg, { parse_mode: 'Markdown' }); } catch (error) { logger.error('Erro em handleVerRoteador:', error); await this.bot.sendMessage(chatId, `❌ Erro ao consultar o GenieACS: ${error.message}`); } } // ───────────────────────────────────────────────────────────────────────── // CONFIGURAR ROTEADOR VIA GENIEACS // ───────────────────────────────────────────────────────────────────────── /** * Mapa de campos configuráveis: chave curta → detalhes do parâmetro TR-069. * Centralizado aqui para ser usado tanto no menu quanto nos callbacks. */ _routerParamMap() { return { ssid24: { label: 'SSID 2.4GHz', param: 'InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.SSID', type: 'xsd:string', toggle: false }, ssid5: { label: 'SSID 5GHz', param: 'InternetGatewayDevice.LANDevice.1.WLANConfiguration.2.SSID', type: 'xsd:string', toggle: false }, senha24: { label: 'Senha Wi-Fi 2.4', param: 'InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.PreSharedKey.1.KeyPassphrase', type: 'xsd:string', toggle: false }, senha5: { label: 'Senha Wi-Fi 5', param: 'InternetGatewayDevice.LANDevice.1.WLANConfiguration.2.PreSharedKey.1.KeyPassphrase', type: 'xsd:string', toggle: false }, ch24: { label: 'Canal 2.4GHz', param: 'InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.Channel', type: 'xsd:unsignedInt', toggle: false }, ch5: { label: 'Canal 5GHz', param: 'InternetGatewayDevice.LANDevice.1.WLANConfiguration.2.Channel', type: 'xsd:unsignedInt', toggle: false }, auto24: { label: 'Auto-Canal 2.4', param: 'InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.AutoChannelEnable', type: 'xsd:boolean', toggle: true }, auto5: { label: 'Auto-Canal 5', param: 'InternetGatewayDevice.LANDevice.1.WLANConfiguration.2.AutoChannelEnable', type: 'xsd:boolean', toggle: true }, dnswan: { label: 'DNS WAN', param: 'InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANPPPConnection.1.DNSServers', type: 'xsd:string', toggle: false }, dnslan: { label: 'DNS LAN', param: 'InternetGatewayDevice.LANDevice.1.LANHostConfigManagement.DNSServers', type: 'xsd:string', toggle: false }, dhcpstart: { label: 'DHCP Início', param: 'InternetGatewayDevice.LANDevice.1.LANHostConfigManagement.MinAddress', type: 'xsd:string', toggle: false }, dhcpend: { label: 'DHCP Fim', param: 'InternetGatewayDevice.LANDevice.1.LANHostConfigManagement.MaxAddress', type: 'xsd:string', toggle: false }, iplan: { label: 'IP LAN', param: 'InternetGatewayDevice.LANDevice.1.LANHostConfigManagement.IPInterface.1.IPInterfaceIPAddress', type: 'xsd:string', toggle: false }, mascaralan: { label: 'Máscara LAN', param: 'InternetGatewayDevice.LANDevice.1.LANHostConfigManagement.IPInterface.1.IPInterfaceSubnetMask', type: 'xsd:string', toggle: false }, }; } /** * Comando "configurar roteador ". * * Fluxo: * 1. Se o termo parece serial/tag GenieACS (alphanumérico sem espaços) → busca direto no GenieACS. * 2. Caso contrário, busca no TopSapp por nome/CPF: * a. Seleciona o cliente (se houver múltiplos) * b. Seleciona o serviço com login PPPoE * c. Usa o login PPPoE como TAG para buscar no GenieACS * d. Se não encontrar TAG no GenieACS → informa que não é possível configurar */ async handleConfigurarRoteador(chatId, userId, termo) { if (!termo) { await this.bot.sendMessage(chatId, '⚙️ *Configurar Roteador*\n\n' + 'Informe o serial, TAG, nome do cliente ou CPF.\n' + 'Exemplos:\n' + '• `configurar roteador ABCD12345678`\n' + '• `configurar roteador joao.silva`\n' + '• `configurar roteador João da Silva`\n' + '• `configurar roteador 123.456.789-10`', { parse_mode: 'Markdown' } ); return; } // CPF sem formatação: 11 dígitos numéricos → formata e encaminha para TopSapp if (/^\d{11}$/.test(termo)) { const cpfFormatado = `${termo.slice(0,3)}.${termo.slice(3,6)}.${termo.slice(6,9)}-${termo.slice(9,11)}`; return this._configurarRoteadorBuscarTopSapp(chatId, userId, cpfFormatado); } // Heurística: serial/tag GenieACS não tem espaço e não é CPF formatado const pareceGenieACS = !/\s/.test(termo) && !/^\d{3}\.\d{3}\.\d{3}-\d{2}$/.test(termo); if (pareceGenieACS) { // Caminho direto: buscar no GenieACS por serial ou TAG await this._configurarRoteadorPorGenieACS(chatId, userId, termo); } else { // Caminho via TopSapp: buscar cliente por nome/CPF await this._configurarRoteadorBuscarTopSapp(chatId, userId, termo); } } /** * Busca o dispositivo diretamente no GenieACS e abre o menu de configuração. */ async _configurarRoteadorPorGenieACS(chatId, userId, termo) { await this.bot.sendMessage(chatId, `🔍 Buscando dispositivo \`${termo}\` no GenieACS...`, { parse_mode: 'Markdown' }); try { const resultado = await this.genieacs.buscarRoteador(termo); if (!resultado.success) { await this.bot.sendMessage(chatId, `❌ *Dispositivo não encontrado no GenieACS*\n\n` + `🔍 Busca: \`${termo}\`\n` + `⚠️ ${resultado.error || 'Nenhum resultado'}\n\n` + `_Tente buscar pelo nome ou CPF do cliente._`, { parse_mode: 'Markdown' } ); return; } this.userSessions.set(userId, { action: 'router_cfg', deviceId: resultado.device.deviceId, device: resultado.device, instance: resultado.instance || '', timestamp: Date.now() }); await this._enviarMenuConfigurarRoteador(chatId, resultado.device, resultado.instance || ''); } catch (error) { logger.error('Erro em _configurarRoteadorPorGenieACS:', error); await this.bot.sendMessage(chatId, `❌ Erro ao consultar o GenieACS: ${error.message}`); } } /** * Busca o cliente no TopSapp por nome/CPF e apresenta seleção. */ async _configurarRoteadorBuscarTopSapp(chatId, userId, query) { await this.bot.sendMessage(chatId, `🔍 Buscando cliente "${query}" no TopSapp...`); try { const resultado = await this.topsapp.buscarCliente(query); if (!resultado.success) { await this.bot.sendMessage(chatId, `❌ Erro na busca: ${resultado.error}`); return; } if (!resultado.clientes || resultado.clientes.length === 0) { await this.bot.sendMessage(chatId, `🔍 Nenhum cliente encontrado para: "${query}"`); return; } const instancia = resultado.instancia || ''; if (resultado.clientes.length === 1) { await this._configurarRoteadorSelecionarServico(chatId, userId, resultado.clientes[0].id, instancia); return; } // Múltiplos clientes → seleção let msg = `🔍 *${resultado.clientes.length} clientes encontrados:*\n\n`; const keyboard = []; resultado.clientes.slice(0, 8).forEach((c, i) => { msg += `*${i + 1}.* ${c.nome}`; if (c.cpf) msg += ` — \`${c.cpf}\``; msg += '\n'; keyboard.push([{ text: `⚙️ ${c.nome.substring(0, 35)}`, callback_data: `cfg_roteador_cli_${c.id}_${instancia}` }]); }); await this.bot.sendMessage(chatId, msg, { parse_mode: 'Markdown', reply_markup: { inline_keyboard: keyboard } }); } catch (error) { logger.error('Erro em _configurarRoteadorBuscarTopSapp:', error); await this.bot.sendMessage(chatId, `❌ Erro: ${error.message}`); } } /** * Busca os serviços do cliente e apresenta seleção (ou vai direto se só há 1). */ async _configurarRoteadorSelecionarServico(chatId, userId, idCliente, location) { const servicosResult = await this.topsapp.obterServicosCliente(idCliente, location); if (!servicosResult.success) { await this.bot.sendMessage(chatId, `❌ Erro ao buscar serviços: ${servicosResult.error}`); return; } const servicos = (servicosResult.servicos || []).filter(s => { const st = s.id_status || s.statusNumerico; return st === 1 || st === '1' || String(s.status).toLowerCase() === 'liberado' || String(s.status).toLowerCase() === 'ativo'; }); if (servicos.length === 0) { await this.bot.sendMessage(chatId, `⚠️ Cliente sem serviços ativos.`); return; } if (servicos.length === 1) { await this._configurarRoteadorPorServico(chatId, userId, servicos[0], location); return; } let msg = `⚙️ *Selecione o serviço:*\n\n`; const keyboard = []; servicos.forEach((s, i) => { const nome = s.descricao || s.nome || `Serviço ${i + 1}`; const login = s.usuario || s.pppoe_login || s.login || ''; msg += `*${i + 1}.* ${nome}${login ? ` — \`${login}\`` : ''}\n`; keyboard.push([{ text: `⚙️ ${nome.substring(0, 35)}`, callback_data: `cfg_roteador_srv_${idCliente}_${s.id_servico || s.id}_${location}` }]); }); await this.bot.sendMessage(chatId, msg, { parse_mode: 'Markdown', reply_markup: { inline_keyboard: keyboard } }); } /** * A partir de um serviço TopSapp, extrai o login PPPoE, busca a TAG no GenieACS e abre o menu. */ async _configurarRoteadorPorServico(chatId, userId, servico, location) { const pppoeLogin = servico.usuario || servico.pppoe_login || servico.login || null; if (!pppoeLogin) { await this.bot.sendMessage(chatId, `⚠️ O serviço selecionado não possui login PPPoE cadastrado.\n` + `Não é possível localizar o roteador no GenieACS sem esse dado.` ); return; } await this.bot.sendMessage(chatId, `🔍 Localizando roteador pela TAG \`${pppoeLogin}\` no GenieACS...`, { parse_mode: 'Markdown' }); try { const resultado = await this.genieacs.buscarRoteador(pppoeLogin, location); if (!resultado.success) { await this.bot.sendMessage(chatId, `⚠️ *Roteador não encontrado no GenieACS*\n\n` + `🏷️ TAG pesquisada: \`${pppoeLogin}\`\n\n` + `Não é possível realizar alterações no roteador deste cliente pelo bot.\n` + `_O dispositivo pode não estar cadastrado no GenieACS ou a TAG ainda não foi sincronizada._`, { parse_mode: 'Markdown' } ); return; } this.userSessions.set(userId, { action: 'router_cfg', deviceId: resultado.device.deviceId, device: resultado.device, instance: resultado.instance || '', timestamp: Date.now() }); await this._enviarMenuConfigurarRoteador(chatId, resultado.device, resultado.instance || ''); } catch (error) { logger.error('Erro em _configurarRoteadorPorServico:', error); await this.bot.sendMessage(chatId, `❌ Erro ao consultar o GenieACS: ${error.message}`); } } /** * Envia/atualiza a mensagem de menu de configuração do roteador. */ async _enviarMenuConfigurarRoteador(chatId, device, instance) { const esc = (v) => (v === null || v === undefined || v === '') ? 'N/A' : String(v).replace(/[_*`\[]/g, '\\$&'); const auto24On = device.autoChannel24 === 'Sim'; const auto5On = device.autoChannel5 === 'Sim'; const sumario = `⚙️ *Configurar Roteador*\n` + `📌 ${esc(instance)} — \`${esc(device.serial)}\`\n\n` + `📶 *Wi-Fi 2.4 GHz*\n` + `├ SSID: ${esc(device.ssid24)}\n` + `├ Senha: \`${esc(device.senha24)}\`\n` + `├ Canal: ${esc(device.channel24)} (Auto: ${esc(device.autoChannel24)})\n\n` + `📶 *Wi-Fi 5 GHz*\n` + `├ SSID: ${esc(device.ssid5)}\n` + `├ Senha: \`${esc(device.senha5)}\`\n` + `├ Canal: ${esc(device.channel5)} (Auto: ${esc(device.autoChannel5)})\n\n` + `🌐 *WAN/PPPoE*\n` + `├ DNS WAN: ${esc(device.dnsWAN)}\n\n` + `🏠 *LAN/DHCP*\n` + `├ IP LAN: ${esc(device.ipLAN)}\n` + `├ Máscara: ${esc(device.mascaraLAN)}\n` + `├ DNS LAN: ${esc(device.dnsLAN)}\n` + `├ DHCP Início: ${esc(device.dhcpInicio)}\n` + `└ DHCP Fim: ${esc(device.dhcpFim)}\n\n` + `_Selecione o que deseja alterar:_`; const keyboard = { inline_keyboard: [ [ { text: '📶 SSID 2.4GHz', callback_data: 'router_cfg_ssid24' }, { text: '📶 SSID 5GHz', callback_data: 'router_cfg_ssid5' }, ], [ { text: '🔑 Senha 2.4GHz', callback_data: 'router_cfg_senha24' }, { text: '🔑 Senha 5GHz', callback_data: 'router_cfg_senha5' }, ], [ { text: `${auto24On ? '✅' : '⬜'} Auto-Canal 2.4`, callback_data: 'router_cfg_auto24' }, { text: `${auto5On ? '✅' : '⬜'} Auto-Canal 5`, callback_data: 'router_cfg_auto5' }, ], [ { text: '📡 Canal 2.4GHz', callback_data: 'router_cfg_ch24' }, { text: '📡 Canal 5GHz', callback_data: 'router_cfg_ch5' }, ], [ { text: '🌐 DNS WAN', callback_data: 'router_cfg_dnswan' }, { text: '🏠 DNS LAN', callback_data: 'router_cfg_dnslan' }, ], [ { text: '▶ DHCP Início', callback_data: 'router_cfg_dhcpstart' }, { text: '◀ DHCP Fim', callback_data: 'router_cfg_dhcpend' }, ], [ { text: '🖥️ IP LAN', callback_data: 'router_cfg_iplan' }, { text: '🔢 Máscara LAN', callback_data: 'router_cfg_mascaralan'}, ], [ { text: '🔄 Atualizar leitura', callback_data: 'router_cfg_refresh' }, { text: '❌ Fechar', callback_data: 'cancel_action' }, ], ] }; await this.bot.sendMessage(chatId, sumario, { parse_mode: 'Markdown', reply_markup: keyboard }); } /** * Dispatcher de todos os callbacks com prefixo "router_cfg_". */ async handleRouterCfgCallback(callbackQuery, data) { const chatId = callbackQuery.message.chat.id; const userId = callbackQuery.from.id; const msgId = callbackQuery.message.message_id; await this.bot.answerCallbackQuery(callbackQuery.id); const session = this.userSessions.get(userId); if (!session || session.action !== 'router_cfg') { await this.bot.sendMessage(chatId, '⚠️ Sessão expirada. Use `configurar roteador ` novamente.', { parse_mode: 'Markdown' }); return; } const field = data.replace('router_cfg_', ''); // ex.: "ssid24", "auto24", "ch24_6", "refresh" // ── Atualizar leitura ──────────────────────────────────────────────── if (field === 'refresh') { await this.bot.sendMessage(chatId, '🔄 Relendo dados do dispositivo...'); const fresh = await this.genieacs.buscarRoteadorPorId(session.deviceId, session.instance); if (!fresh.success) { await this.bot.sendMessage(chatId, `❌ Falha ao reler dispositivo: ${fresh.error}`); return; } session.device = fresh.device; this.userSessions.set(userId, session); await this._enviarMenuConfigurarRoteador(chatId, fresh.device, session.instance); return; } // ── Seleção de canal vinda do picker inline (ch24_N ou ch5_N) ─────── const chPickMatch = field.match(/^(ch24|ch5)_(\d+)$/); if (chPickMatch) { const band = chPickMatch[1]; // 'ch24' ou 'ch5' const canal = parseInt(chPickMatch[2], 10); const paramMap = this._routerParamMap(); const meta = paramMap[band]; await this.bot.sendMessage(chatId, `⏳ Aplicando ${meta.label} → canal ${canal}...`); const res = await this.genieacs.configurarRoteador( session.deviceId, [[meta.param, canal, 'xsd:unsignedInt']], session.instance ); if (!res.success) { await this.bot.sendMessage(chatId, `❌ Falha: ${res.error}`); } else { if (band === 'ch24') session.device.channel24 = String(canal); else session.device.channel5 = String(canal); this.userSessions.set(userId, session); const okMsg = res.queued ? `⏳ *${meta.label}* → canal *${canal}* enfileirado. O roteador aplicará na próxima conexão.` : `✅ *${meta.label}* definido para o canal *${canal}*.`; await this.bot.sendMessage(chatId, okMsg, { parse_mode: 'Markdown' }); } await this._enviarMenuConfigurarRoteador(chatId, session.device, session.instance); return; } const paramMap = this._routerParamMap(); // ── Toggle de auto-canal ───────────────────────────────────────────── if (field === 'auto24' || field === 'auto5') { const meta = paramMap[field]; const atualSim = field === 'auto24' ? session.device.autoChannel24 === 'Sim' : session.device.autoChannel5 === 'Sim'; const novoValor = !atualSim; // inverte await this.bot.sendMessage(chatId, `⏳ ${atualSim ? 'Desativando' : 'Ativando'} ${meta.label}...`); const res = await this.genieacs.configurarRoteador( session.deviceId, [[meta.param, novoValor, 'xsd:boolean']], session.instance ); if (!res.success) { await this.bot.sendMessage(chatId, `❌ Falha: ${res.error}`); return; } // Atualiza o device local if (field === 'auto24') session.device.autoChannel24 = novoValor ? 'Sim' : 'Não'; else session.device.autoChannel5 = novoValor ? 'Sim' : 'Não'; this.userSessions.set(userId, session); const autoMsg = res.queued ? `⏳ ${meta.label} ${novoValor ? 'ativação' : 'desativação'} enfileirada. O roteador aplicará na próxima conexão.` : `✅ ${meta.label} ${novoValor ? 'ativado' : 'desativado'} com sucesso.`; await this.bot.sendMessage(chatId, autoMsg); await this._enviarMenuConfigurarRoteador(chatId, session.device, session.instance); return; } // ── Picker de canal com botões inline ──────────────────────────────── if (field === 'ch24' || field === 'ch5') { const meta = paramMap[field]; const canalAtual = field === 'ch24' ? (session.device.channel24 || '') : (session.device.channel5 || ''); const is24 = field === 'ch24'; const canais24 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]; const canais5 = [36, 40, 44, 48, 52, 60, 64, 149, 153, 157, 161]; const canais = is24 ? canais24 : canais5; // Monta rows de 4 botões por linha const rows = []; for (let i = 0; i < canais.length; i += 4) { rows.push( canais.slice(i, i + 4).map(ch => ({ text: String(ch) === String(canalAtual) ? `✅ ${ch}` : String(ch), callback_data: `router_cfg_${field}_${ch}` })) ); } rows.push([{ text: '↩️ Voltar', callback_data: 'router_cfg_refresh' }]); await this.bot.sendMessage(chatId, `📡 *${meta.label}*\nCanal atual: \`${canalAtual || 'N/A'}\`\n\nEscolha o novo canal:`, { parse_mode: 'Markdown', reply_markup: { inline_keyboard: rows } } ); return; } // ── Campos de texto/número: solicita input ─────────────────────────── const meta = paramMap[field]; if (!meta) { await this.bot.sendMessage(chatId, `❓ Campo desconhecido: \`${field}\``, { parse_mode: 'Markdown' }); return; } // Valor atual para mostrar ao usuário const fieldToCurrent = { ssid24: 'ssid24', ssid5: 'ssid5', senha24: 'senha24', senha5: 'senha5', dnswan: 'dnsWAN', dnslan: 'dnsLAN', dhcpstart: 'dhcpInicio', dhcpend: 'dhcpFim', iplan: 'ipLAN', mascaralan: 'mascaraLAN', }; const atualKey = fieldToCurrent[field]; const valorAtual = atualKey ? (session.device[atualKey] || 'N/A') : 'N/A'; let hint = ''; if (field.startsWith('dns')) hint = ' (ex.: 8.8.8.8,8.8.4.4)'; else if (field.startsWith('dhcp')) hint = ' (ex.: 192.168.1.100)'; else if (field === 'iplan') hint = ' (ex.: 192.168.1.1)'; else if (field === 'mascaralan') hint = ' (ex.: 255.255.255.0)'; // Promove o estado de sessão para aguardar o valor session.action = `router_cfg_wait_${field}`; this.userSessions.set(userId, session); await this.bot.sendMessage(chatId, `⚙️ *Alterar ${meta.label}*\n\n` + `Valor atual: \`${valorAtual}\`\n\n` + `Digite o novo valor${hint}:\n` + `_(ou envie /cancelar para abortar)_`, { parse_mode: 'Markdown' } ); } /** * Processa o valor digitado pelo usuário para alterar um parâmetro do roteador. * Chamado por handleUserSessionInput quando session.action começa com "router_cfg_wait_". */ async _processarInputConfigurarRoteador(chatId, userId, session, text) { const field = session.action.replace('router_cfg_wait_', ''); // ex.: "ssid24" const paramMap = this._routerParamMap(); const meta = paramMap[field]; if (!meta) { this.userSessions.delete(userId); await this.bot.sendMessage(chatId, '❌ Campo desconhecido. Operação cancelada.'); return; } // Cancela se o usuário enviou /cancelar if (text.trim().toLowerCase() === '/cancelar') { session.action = 'router_cfg'; this.userSessions.set(userId, session); await this.bot.sendMessage(chatId, '🚫 Alteração cancelada.'); await this._enviarMenuConfigurarRoteador(chatId, session.device, session.instance); return; } // Converte para o tipo correto let valorFinal; if (meta.type === 'xsd:unsignedInt') { const n = parseInt(text.trim(), 10); if (isNaN(n) || n < 0) { await this.bot.sendMessage(chatId, `❌ Valor inválido. Informe um número inteiro positivo.`); return; // mantém sessão aguardando nova tentativa } valorFinal = n; } else { valorFinal = text.trim(); if (!valorFinal) { await this.bot.sendMessage(chatId, `❌ Valor não pode ser vazio.`); return; } } await this.bot.sendMessage(chatId, `⏳ Aplicando ${meta.label}...`); const res = await this.genieacs.configurarRoteador( session.deviceId, [[meta.param, valorFinal, meta.type]], session.instance ); if (!res.success) { await this.bot.sendMessage(chatId, `❌ Falha ao aplicar: ${res.error}`); session.action = 'router_cfg'; this.userSessions.set(userId, session); return; } const statusMsg = res.queued ? `⏳ *${meta.label}* → \`${valorFinal}\`\n_Task enfileirada. O roteador aplicará na próxima conexão com o servidor._` : `✅ *${meta.label}* atualizado para: \`${valorFinal}\``; await this.bot.sendMessage(chatId, statusMsg, { parse_mode: 'Markdown' }); // Atualiza o device local com o novo valor para o menu refletir const fieldToDeviceKey = { ssid24: 'ssid24', ssid5: 'ssid5', senha24: 'senha24', senha5: 'senha5', ch24: 'channel24', ch5: 'channel5', dnswan: 'dnsWAN', dnslan: 'dnsLAN', dhcpstart: 'dhcpInicio', dhcpend: 'dhcpFim', iplan: 'ipLAN', mascaralan: 'mascaraLAN', }; const devKey = fieldToDeviceKey[field]; if (devKey) session.device[devKey] = String(valorFinal); session.action = 'router_cfg'; this.userSessions.set(userId, session); await this._enviarMenuConfigurarRoteador(chatId, session.device, session.instance); } // ========== IPoE ========== /** * Handler para comando "mudar para ipoe" * Busca o cliente e inicia fluxo de seleção de serviço */ async handleMudarIPoE(chatId, target) { if (!target) { await this.bot.sendMessage(chatId, `🔗 *Mudar para IPoE*\n\n` + `💡 *Como usar:*\n` + `• "mudar para ipoe João da Silva"\n` + `• "ipoe 123.456.789-10"\n` + `• "mudar ipoe 12345" (ID do cliente)\n\n` + `O comando gera os dados de DHCP Option 82 para o TopSapp e configura a VLAN 977 (IPoE) na OLT automaticamente.`, { parse_mode: 'Markdown' } ); return; } try { await this.bot.sendMessage(chatId, '🔍 Buscando cliente...'); let location = this.getActiveLocation(chatId); let query = target; if (/guarai|colmeia/i.test(target)) { location = 'Guarai/Colmeia'; query = target.replace(/(guarai|colmeia)/gi, '').trim(); } else if (/pedro afonso/i.test(target)) { location = 'Pedro Afonso'; query = target.replace(/pedro afonso/gi, '').trim(); } const resultado = await this.topsapp.buscarCliente(query, location); if (!resultado.success) { await this.bot.sendMessage(chatId, `❌ Erro na busca: ${resultado.error}`); return; } if (!resultado.clientes || resultado.clientes.length === 0) { await this.bot.sendMessage(chatId, `🔍 Nenhum cliente encontrado para: "${query}"`); return; } const instancia = resultado.instancia || location; if (resultado.clientes.length === 1) { await this._mostrarServicosParaIPoE(chatId, resultado.clientes[0].id, instancia); return; } // Múltiplos clientes → teclado de seleção let message = `🔗 *Mudar para IPoE — Selecione o cliente:*\n\n`; const keyboard = []; resultado.clientes.slice(0, 8).forEach((cliente, index) => { message += `*${index + 1}.* ${cliente.nome}`; if (cliente.cpf) message += ` — CPF: \`${cliente.cpf}\``; message += `\n`; keyboard.push([{ text: `🔗 ${cliente.nome.substring(0, 30)}`, callback_data: `ipoe_cliente_${cliente.id}_${instancia}` }]); }); await this.bot.sendMessage(chatId, message, { parse_mode: 'Markdown', reply_markup: { inline_keyboard: keyboard } }); } catch (error) { console.error('❌ Erro em handleMudarIPoE:', error); await this.bot.sendMessage(chatId, `❌ Erro: ${error.message}`); } } /** * Callback quando o usuário seleciona um cliente para IPoE */ async handleIPoEClienteCallback(callbackQuery, data) { const chatId = callbackQuery.message.chat.id; const parts = data.replace('ipoe_cliente_', '').split('_'); const idCliente = parts[0]; const location = parts.slice(1).join('_'); await this.bot.answerCallbackQuery(callbackQuery.id); await this._mostrarServicosParaIPoE(chatId, idCliente, location); } /** * Exibe serviços ativos do cliente com endereço e login para seleção (IPoE) */ async _mostrarServicosParaIPoE(chatId, idCliente, location) { await this.bot.sendMessage(chatId, '📋 Carregando serviços do cliente...'); const servicosResult = await this.topsapp.obterServicosCliente(idCliente, location); if (!servicosResult.success) { await this.bot.sendMessage(chatId, `❌ Erro ao buscar serviços: ${servicosResult.error}`); return; } const servicos = (servicosResult.servicos || []).filter(s => { const st = s.id_status || s.statusNumerico; return st === 1 || st === '1' || String(s.status).toLowerCase() === 'liberado'; }); if (servicos.length === 0) { await this.bot.sendMessage(chatId, `⚠️ Cliente sem serviços ativos para configurar IPoE.`); return; } let message = `🔗 *Mudar para IPoE — Serviços do cliente:*\n\n`; const keyboard = []; const esc = (v) => String(v).replace(/_/g, '\\_'); servicos.forEach((s, i) => { const nome = s.descricao || s.nome || `Serviço ${i + 1}`; const serial = s.serial || s.mac || 'sem serial'; const login = s.usuario || s.pppoe_login || s.login || 'sem login'; const endereco = s.instalacao_endereco || ''; const numero = s.instalacao_numero || ''; const bairro = s.instalacao_bairro || ''; const cidade = s.instalacao_cidade || ''; message += `📦 *${i + 1}. ${esc(nome)}*\n`; message += ` 👤 Login: \`${login}\`\n`; message += ` 📟 Serial: \`${serial}\`\n`; if (endereco) { message += ` 📍 ${endereco}${numero ? `, ${numero}` : ''}`; if (bairro) message += ` — ${bairro}`; if (cidade) message += `, ${cidade}`; message += `\n`; } message += `\n`; keyboard.push([{ text: `🔗 ${(i + 1)}. ${nome.substring(0, 30)}`, callback_data: `ipoe_servico_${idCliente}_${s.id_servico || s.id}_${location}` }]); }); message += `💡 Selecione o serviço para configurar IPoE:`; await this.bot.sendMessage(chatId, message, { parse_mode: 'Markdown', reply_markup: { inline_keyboard: keyboard } }); } /** * Callback quando o usuário seleciona um serviço para IPoE */ async handleIPoEServicoCallback(callbackQuery, data) { const chatId = callbackQuery.message.chat.id; const parts = data.replace('ipoe_servico_', '').split('_'); const idCliente = parts[0]; const idServico = parts[1]; const location = parts.slice(2).join('_'); await this.bot.answerCallbackQuery(callbackQuery.id); // Recarregar serviço para ter dados atualizados const servicosResult = await this.topsapp.obterServicosCliente(idCliente, location); if (!servicosResult.success) { await this.bot.sendMessage(chatId, `❌ Erro ao buscar serviço: ${servicosResult.error}`); return; } const servico = (servicosResult.servicos || []).find(s => String(s.id_servico || s.id) === String(idServico) ); if (!servico) { await this.bot.sendMessage(chatId, `❌ Serviço #${idServico} não encontrado.`); return; } await this._confirmarMudarIPoE(chatId, idCliente, servico, location); } /** * Mostra preview do que será alterado e pede confirmação */ async _confirmarMudarIPoE(chatId, idCliente, servico, location) { const serial = servico.serial || servico.mac || ''; const baseInfo = servico.base || ''; const prefixo = this.topsapp._resolverPrefixoIPoE ? this.topsapp._resolverPrefixoIPoE(baseInfo) : null; if (!serial) { await this.bot.sendMessage(chatId, `❌ *Serviço sem serial de ONU cadastrado.*\n\n` + `Não é possível gerar o Circuit-ID sem o serial.\n` + `Verifique o cadastro do serviço no TopSapp.`, { parse_mode: 'Markdown' } ); return; } if (!prefixo) { await this.bot.sendMessage(chatId, `❌ *Não foi possível identificar a OLT/base deste serviço.*\n\n` + `Base cadastrada: "${baseInfo || 'vazio'}"\n` + `Verifique o cadastro do serviço no TopSapp.`, { parse_mode: 'Markdown' } ); return; } const circuitId = `${prefixo}:${serial}`; const nome = servico.descricao || servico.nome || 'N/A'; const idServico = servico.id_servico || servico.id; // Escapar underscores para Telegram Markdown const esc = (v) => String(v).replace(/_/g, '\\_'); let message = `🔗 *Confirmar mudança para IPoE*\n\n`; message += `📦 *Serviço:* ${esc(nome)}\n`; message += `🆔 *ID Serviço:* ${idServico}\n`; message += `📟 *Serial ONU:* \`${serial}\`\n`; message += `🏢 *Base/OLT:* ${esc(baseInfo)}\n`; message += `🏷️ *Prefixo:* ${esc(prefixo)}\n\n`; message += `*Será configurado:*\n`; message += `📝 DHCP Username: \`${circuitId}\`\n`; message += `🔗 Agent Circuit ID: \`${circuitId}\`\n`; message += `✅ DHCP Liberar: *Sim*\n`; message += `📡 VLAN OLT: *977* (IPoE)\n\n`; if (prefixo === 'FH_GRI') { message += `🔧 _OLT FiberHome — WAN Service será reconfigurado para VLAN 977_\n`; } else { message += `🔧 _OLT Datacom — Ethernet e Service-Port serão reconfigurados para VLAN 977_\n`; } message += `Deseja confirmar?`; const keyboard = [ [ { text: '✅ Confirmar', callback_data: `confirm_ipoe_${idCliente}_${idServico}_${location}` }, { text: '❌ Cancelar', callback_data: 'cancel_action' } ] ]; await this.bot.sendMessage(chatId, message, { parse_mode: 'Markdown', reply_markup: { inline_keyboard: keyboard } }); } /** * Callback de confirmação: executa a mudança para IPoE */ async handleConfirmIPoECallback(callbackQuery, data) { const chatId = callbackQuery.message.chat.id; const parts = data.replace('confirm_ipoe_', '').split('_'); const idCliente = parts[0]; const idServico = parts[1]; const location = parts.slice(2).join('_'); await this.bot.answerCallbackQuery(callbackQuery.id); await this.bot.sendMessage(chatId, '⏳ Processando mudança para IPoE...'); const resultado = await this.topsapp.mudarParaIPoE(idCliente, idServico, location); if (!resultado.success) { await this.bot.sendMessage(chatId, `❌ *Erro ao gerar configuração IPoE*\n\n⚠️ ${resultado.error}`, { parse_mode: 'Markdown' } ); return; } const esc = (v) => String(v).replace(/_/g, '\\_'); // Mostrar dados do TopSapp para configuração manual let msg = `📋 *Configuração IPoE — Dados para o TopSapp*\n\n`; msg += `📦 *Serviço:* ${esc(resultado.servico)}\n`; msg += `👤 *Login:* \`${resultado.login || 'N/A'}\`\n`; msg += `📟 *Serial:* \`${resultado.serial}\`\n`; msg += `🏢 *OLT:* ${esc(resultado.baseInfo)} (${esc(resultado.prefixo)})\n\n`; msg += `✏️ *Configure no painel TopSapp:*\n`; msg += `▸ DHCP Username: \`${resultado.circuitId}\`\n`; msg += `▸ Agent Circuit ID: \`${resultado.circuitId}\`\n`; msg += `▸ DHCP Liberar: *Sim*\n\n`; msg += `⚠️ _A API não suporta campos DHCP — configure manualmente no painel._\n`; await this.bot.sendMessage(chatId, msg, { parse_mode: 'Markdown' }); // Configuração OLT automática await this.bot.sendMessage(chatId, '📡 Configurando VLAN 977 na OLT...'); try { let oltResult; if (resultado.prefixo === 'FH_GRI') { // FiberHome — SET-WANSERVICE com VLAN 977 oltResult = await this.fiberhomeTL1.configurarIPoEFiberhome(resultado.serial); } else { // Datacom — precisa descobrir slot/onuId pelo serial const prefixoParaOlt = { 'DIT': 'olt_dito', 'CLM01': 'olt_colmeia', 'CAN01': 'olt_canaa', 'CAN02': 'olt_canaa2', 'PA01': 'olt_pedro_afonso', 'PA02': 'olt_canavieiras' }; const oltId = prefixoParaOlt[resultado.prefixo]; if (!oltId) { await this.bot.sendMessage(chatId, `❌ OLT não mapeada para prefixo: ${esc(resultado.prefixo)}`); return; } // Buscar ONU na OLT pelo serial const onuInfo = await this.ssh.findOnuBySerial(oltId, resultado.serial); if (!onuInfo.success || !onuInfo.onuInfo?.found) { await this.bot.sendMessage(chatId, `❌ *ONU não encontrada na OLT*\n\n` + `Serial: \`${resultado.serial}\`\n` + `OLT: ${esc(oltId)}\n\n` + `Verifique se o serial está correto no TopSapp.`, { parse_mode: 'Markdown' } ); return; } const { slot, onuId } = onuInfo.onuInfo; await this.bot.sendMessage(chatId, `🔍 ONU encontrada: Slot ${slot}, ONU ${onuId}`); oltResult = await this.ssh.configIPoEDatacom(oltId, slot, onuId); } if (oltResult.success) { let successMsg = `✅ *VLAN alterada para 977 na OLT!*\n\n`; if (resultado.prefixo === 'FH_GRI') { successMsg += `📡 FiberHome — WAN Service reconfigurado\n`; if (oltResult.details) { successMsg += `🔌 Portas configuradas: ${oltResult.details.portas}\n`; } } else { successMsg += `📡 Datacom — Ethernet + Service-Port reconfigurados\n`; if (oltResult.oldServicePorts) { successMsg += `🔌 Service-ports antigos removidos: ${oltResult.oldServicePorts.join(', ')}\n`; } if (oltResult.newServicePortId) { successMsg += `🆕 Novo service-port: ${oltResult.newServicePortId}\n`; } } successMsg += `\n✅ *IPoE configurado com sucesso!*`; await this.bot.sendMessage(chatId, successMsg, { parse_mode: 'Markdown' }); } else { await this.bot.sendMessage(chatId, `❌ *Erro ao configurar OLT*\n\n⚠️ ${oltResult.message || oltResult.error}`, { parse_mode: 'Markdown' } ); } } catch (error) { console.error('❌ Erro na configuração OLT IPoE:', error); await this.bot.sendMessage(chatId, `❌ *Erro ao configurar OLT*\n\n⚠️ ${error.message}`, { parse_mode: 'Markdown' } ); } } // ====================================================================== // REINICIAR ONU POR CLIENTE (Nome / CPF) // ====================================================================== /** * Reiniciar ONU buscando pelo nome ou CPF do cliente via TopSapp */ async handleReiniciarOnu(chatId, query) { if (!query) { await this.bot.sendMessage(chatId, `🔄 *Reiniciar ONU*\n\n` + `💡 *Como usar:*\n` + `• "reiniciar onu João da Silva"\n` + `• "reiniciar onu 123.456.789-10"\n\n` + `O sistema busca o cliente no TopSapp, detecta a base (Fiberhome ou Datacom) e reinicia a ONU automaticamente.`, { parse_mode: 'Markdown' } ); return; } try { await this.bot.sendMessage(chatId, '🔍 Buscando cliente...'); let location = this.getActiveLocation(chatId); let searchQuery = query; if (/guarai|colmeia/i.test(query)) { location = 'Guarai/Colmeia'; searchQuery = query.replace(/(guarai|colmeia)/gi, '').trim(); } else if (/pedro afonso/i.test(query)) { location = 'Pedro Afonso'; searchQuery = query.replace(/pedro afonso/gi, '').trim(); } const resultado = await this.topsapp.buscarCliente(searchQuery, location); if (!resultado.success) { await this.bot.sendMessage(chatId, `❌ Erro na busca: ${resultado.error}`); return; } if (!resultado.clientes || resultado.clientes.length === 0) { await this.bot.sendMessage(chatId, `🔍 Nenhum cliente encontrado para: "${searchQuery}"`); return; } const instancia = resultado.instancia || location; if (resultado.clientes.length === 1) { await this._mostrarServicosParaReiniciarOnu(chatId, resultado.clientes[0].id, instancia); return; } // Múltiplos clientes → exibir teclado de seleção let message = `🔍 *${resultado.clientes.length} clientes encontrados*\n\n`; const keyboard = []; resultado.clientes.slice(0, 8).forEach((cliente, index) => { message += `*${index + 1}.* ${cliente.nome}`; if (cliente.cpf) message += ` — CPF: \`${cliente.cpf}\``; message += `\n`; keyboard.push([{ text: `🔄 Reiniciar ONU: ${cliente.nome.substring(0, 28)}`, callback_data: `restart_onu_cli_${cliente.id}_${instancia}` }]); }); await this.bot.sendMessage(chatId, message, { parse_mode: 'Markdown', reply_markup: { inline_keyboard: keyboard } }); } catch (error) { console.error('❌ Erro em handleReiniciarOnu:', error); await this.bot.sendMessage(chatId, `❌ Erro: ${error.message}`); } } /** * Mostra serviços do cliente para selecionar qual ONU reiniciar */ async _mostrarServicosParaReiniciarOnu(chatId, idCliente, location) { const servicosResult = await this.topsapp.obterServicosCliente(idCliente, location); if (!servicosResult.success) { await this.bot.sendMessage(chatId, `❌ Erro ao buscar serviços: ${servicosResult.error}`); return; } const servicos = (servicosResult.servicos || []).filter(s => { const st = (s.id_status || s.statusNumerico); return st === 1 || st === '1' || String(s.status).toLowerCase() === 'liberado' || String(s.status).toLowerCase() === 'ativo'; }); if (servicos.length === 0) { await this.bot.sendMessage(chatId, `⚠️ Cliente sem serviços ativos.`); return; } // Se só tem 1 serviço, ir direto para confirmação if (servicos.length === 1) { const s = servicos[0]; await this._confirmarReiniciarOnu(chatId, idCliente, s, location); return; } // Múltiplos serviços → seleção let message = `🔄 *Selecione o serviço para reiniciar a ONU:*\n\n`; const keyboard = []; servicos.forEach((s, i) => { const nome = s.descricao || s.nome || `Serviço ${i + 1}`; const serial = s.serial || s.comodato_serial || s.mac || ''; const endereco = s.instalacao_endereco || ''; message += `*${i + 1}.* ${nome}${serial ? ` — \`${serial}\`` : ''}`; if (endereco) message += `\n 📍 ${endereco}`; message += '\n'; keyboard.push([{ text: `🔄 ${nome.substring(0, 35)}`, callback_data: `restart_onu_srv_${idCliente}_${s.id_servico || s.id}_${location}` }]); }); await this.bot.sendMessage(chatId, message, { parse_mode: 'Markdown', reply_markup: { inline_keyboard: keyboard } }); } /** * Mostra confirmação antes de reiniciar a ONU */ async _confirmarReiniciarOnu(chatId, idCliente, servico, location) { const serial = servico.serial || servico.comodato_serial || servico.mac || ''; const base = servico.base || ''; const nome = servico.descricao || servico.nome || 'Serviço'; if (!serial) { await this.bot.sendMessage(chatId, `❌ Serviço "${nome}" não possui serial de ONU cadastrado.`); return; } const oltHint = this._resolverOLTporBase(base, this.getActiveSite(chatId).id); const tipoOLT = oltHint === 'fiberhome' ? '🔶 Fiberhome (TL1)' : '🔷 Datacom (SSH)'; const serialEscaped = serial.replace(/_/g, '\\_'); // Gerar ID curto para callback_data const restartId = String(++this._restartIdCounter); this.pendingRestarts.set(restartId, { serial, base, location }); await this.bot.sendMessage(chatId, `⚠️ *Confirmar Reinicialização de ONU*\n\n` + `📦 *Serviço:* ${nome}\n` + `📡 *Serial:* \`${serialEscaped}\`\n` + `🏢 *Base:* ${base || 'N/A'}\n` + `🔌 *Tipo:* ${tipoOLT}\n\n` + `⚠️ A ONU será reiniciada e o cliente ficará sem conexão temporariamente.`, { parse_mode: 'Markdown', reply_markup: { inline_keyboard: [ [ { text: '✅ SIM, Reiniciar', callback_data: `confirm_restart_onu_${restartId}` }, { text: '❌ Cancelar', callback_data: 'cancel_action' } ] ] } } ); } /** * Callback: seleção de cliente para reiniciar ONU */ async handleRestartOnuClienteCallback(callbackQuery, data) { const chatId = callbackQuery.message.chat.id; const parts = data.replace('restart_onu_cli_', '').split('_'); const idCliente = parts[0]; const location = parts.slice(1).join('_'); await this.bot.answerCallbackQuery(callbackQuery.id); await this._mostrarServicosParaReiniciarOnu(chatId, idCliente, location); } /** * Callback: seleção de serviço para reiniciar ONU */ async handleRestartOnuServicoCallback(callbackQuery, data) { const chatId = callbackQuery.message.chat.id; const parts = data.replace('restart_onu_srv_', '').split('_'); const idCliente = parts[0]; const idServico = parts[1]; const location = parts.slice(2).join('_'); await this.bot.answerCallbackQuery(callbackQuery.id); // Buscar o serviço completo const servicosResult = await this.topsapp.obterServicosCliente(idCliente, location); if (!servicosResult.success) { await this.bot.sendMessage(chatId, `❌ Erro ao buscar serviço: ${servicosResult.error}`); return; } const servico = (servicosResult.servicos || []).find(s => String(s.id_servico || s.id) === String(idServico) ); if (!servico) { await this.bot.sendMessage(chatId, `❌ Serviço #${idServico} não encontrado.`); return; } await this._confirmarReiniciarOnu(chatId, idCliente, servico, location); } /** * Callback: confirmação de reiniciar ONU — executa a reinicialização */ async handleConfirmRestartOnuCallback(callbackQuery, data) { const chatId = callbackQuery.message.chat.id; await this.bot.answerCallbackQuery(callbackQuery.id); // Formato: confirm_restart_onu_ID const restartId = data.replace('confirm_restart_onu_', ''); const pending = this.pendingRestarts.get(restartId); if (!pending) { await this.bot.sendMessage(chatId, '❌ Dados de reinicialização expirados. Tente novamente.'); return; } this.pendingRestarts.delete(restartId); const { serial, base } = pending; const siteId = this.getActiveSite(chatId).id; const oltHint = this._resolverOLTporBase(base, siteId); const serialEscaped = serial.replace(/_/g, '\\_'); await this.bot.editMessageText( `🔄 *Reiniciando ONU...*\n\n` + `📡 Serial: \`${serialEscaped}\`\n` + `⏳ Aguarde...`, { chat_id: chatId, message_id: callbackQuery.message.message_id, parse_mode: 'Markdown' } ); try { let result; if (oltHint === 'fiberhome') { // Reiniciar via TL1 (Fiberhome) result = await this.fiberhomeTL1.reiniciarOnu(serial); } else if (oltHint) { // Reiniciar via SSH (Datacom) result = await this._reiniciarOnuDatacom(serial, oltHint); } else { // Sem base definida — tentar Fiberhome primeiro, depois Datacom result = await this.fiberhomeTL1.reiniciarOnu(serial); if (!result.success) { result = await this._reiniciarOnuDatacomAutoDetect(serial, siteId); } } if (result.success) { await this.bot.sendMessage(chatId, `✅ *ONU Reiniciada com Sucesso!*\n\n` + `📡 *Serial:* \`${serialEscaped}\`\n` + `${result.details ? `🏢 *OLT:* ${(result.details.olt || result.details.oltName || '').replace(/_/g, '\\_')}\n` : ''}` + `${result.details && result.details.slot ? `📍 *Slot:* ${result.details.slot}\n` : ''}` + `\n⚡ A ONU estará disponível em alguns instantes.`, { parse_mode: 'Markdown' } ); } else { await this.bot.sendMessage(chatId, `❌ *Erro ao reiniciar ONU*\n\n` + `📡 Serial: \`${serialEscaped}\`\n` + `⚠️ ${result.error || 'Erro desconhecido'}`, { parse_mode: 'Markdown' } ); } } catch (error) { console.error('❌ Erro ao reiniciar ONU:', error); await this.bot.sendMessage(chatId, `❌ *Erro ao reiniciar ONU*\n\n⚠️ ${error.message}`, { parse_mode: 'Markdown' } ); } } /** * Reiniciar ONU Datacom via SSH usando serial — busca na OLT indicada */ async _reiniciarOnuDatacom(serial, oltId) { try { const findResult = await this.ssh.findOnuBySerial(oltId, serial); if (!findResult || !findResult.success) { return { success: false, error: `ONU ${serial} não encontrada na OLT ${oltId}` }; } const { slot, onuId } = findResult.onuInfo; const oltEquip = this.ssh.equipamentos.get(oltId); const result = await this.ssh.resetOnuAlternative(oltId, slot, onuId); return { ...result, details: { oltName: oltEquip ? oltEquip.name : oltId, slot, onuId, serial } }; } catch (error) { return { success: false, error: error.message }; } } /** * Consulta CGNAT: dado IP público + porta opcional, informa o IP privado * e opcionalmente busca no TopSapp quem estava usando aquele IP. */ async handleVerCgnat(chatId, userId, target) { if (!target) { await this.bot.sendMessage(chatId, '🌐 *Consulta CGNAT*\n\n' + 'Informe o IP público e, opcionalmente, a porta:\n\n' + '• `cgnat 131.0.193.0 1500` — busca por IP + porta\n' + '• `cgnat 131.0.193.0` — lista todos os IPs privados do IP público', { parse_mode: 'Markdown' } ); return; } // Parsear target: "IP" ou "IP PORTA" const parts = target.trim().split(/\s+/); const publicIp = parts[0]; const porta = parts[1] ? parseInt(parts[1], 10) : null; // Validar IP if (!/^\d+\.\d+\.\d+\.\d+$/.test(publicIp)) { await this.bot.sendMessage(chatId, `❌ IP inválido: \`${publicIp}\``, { parse_mode: 'Markdown' }); return; } if (!cgnatParser.hasPublicIp(publicIp)) { await this.bot.sendMessage(chatId, `❌ IP \`${publicIp}\` não encontrado na tabela CGNAT.`, { parse_mode: 'Markdown' }); return; } if (porta !== null) { // Busca por IP + porta const entry = cgnatParser.lookupByPort(publicIp, porta); if (!entry) { await this.bot.sendMessage(chatId, `❌ Porta *${porta}* não encontrada nos ranges do IP \`${publicIp}\`.`, { parse_mode: 'Markdown' } ); return; } const keyboard = { inline_keyboard: [ [ { text: '🟢 Conexões ativas agora', callback_data: `cgnat_ativas_${entry.privateIP}` }, { text: '📅 Informar data e hora', callback_data: `cgnat_pedir_data_${entry.privateIP}` } ], [{ text: '❌ Cancelar', callback_data: 'cancel_action' }] ] }; await this.bot.sendMessage(chatId, `🌐 *Resultado CGNAT*\n\n` + `📡 IP Público: \`${publicIp}\`\n` + `🔌 Porta: \`${porta}\`\n` + `🖥️ IP Privado: \`${entry.privateIP}\`\n` + `📊 Range de portas: \`${entry.portStart} – ${entry.portEnd}\`\n\n` + `🔍 Deseja consultar quem está/estava usando este IP no TopSapp?`, { parse_mode: 'Markdown', reply_markup: keyboard } ); } else { // Listar todos os IPs privados const entries = cgnatParser.lookupAll(publicIp); if (!entries || entries.length === 0) { await this.bot.sendMessage(chatId, `❌ Nenhum IP privado encontrado para \`${publicIp}\`.`, { parse_mode: 'Markdown' }); return; } let msg = `🌐 *IPs privados do IP público \`${publicIp}\`*\n\n`; for (const e of entries) { msg += `• \`${e.privateIP}\` — portas \`${e.portStart}\` a \`${e.portEnd}\`\n`; } msg += `\n💡 Para identificar o cliente, informe também a porta:\n\`cgnat ${publicIp} PORTA\``; await this.bot.sendMessage(chatId, msg, { parse_mode: 'Markdown' }); } } /** * Recebe a data/hora e consulta o TopSapp por acessos do IP privado */ async handleCgnatDatetimeInput(chatId, userId, text, session) { if (/^cancelar$/i.test(text)) { this.userSessions.delete(userId); await this.bot.sendMessage(chatId, '❌ Consulta cancelada.'); return; } // Aceitar formatos: "DD/MM/AAAA HH:MM" ou "DD/MM/AAAA" const dtMatch = text.match(/^(\d{2}\/\d{2}\/\d{4})(?:\s+(\d{2}:\d{2}))?$/); if (!dtMatch) { await this.bot.sendMessage(chatId, '❌ Formato inválido. Use `DD/MM/AAAA HH:MM` ou `DD/MM/AAAA`.\n' + 'Ou envie *cancelar* para encerrar.', { parse_mode: 'Markdown' } ); return; } this.userSessions.delete(userId); const dataStr = dtMatch[1]; // DD/MM/AAAA const horaStr = dtMatch[2]; // HH:MM ou undefined await this.bot.sendMessage(chatId, `🔍 Consultando acessos de \`${session.privateIP}\` em ${dataStr}${horaStr ? ' ' + horaStr : ''}...`, { parse_mode: 'Markdown' } ); const result = await this.topsapp.buscarSessaoPorHorario( session.privateIP, dataStr, dataStr, horaStr || null ); if (!result.success) { await this.bot.sendMessage(chatId, `❌ Erro ao consultar TopSapp: ${result.error}`, { parse_mode: 'Markdown' } ); return; } if (result.total === 0) { const extra = result.totalGeral > 0 ? `\n\n_${result.totalGeral} registros retornados para ${dataStr} — nenhuma sessão com IP \`${session.privateIP}\` ou ativa às ${horaStr || 'hora informada'}._` : `\n\n_Nenhum registro retornado para ${dataStr}._`; await this.bot.sendMessage(chatId, `ℹ️ Não foi possível identificar o cliente para \`${session.privateIP}\` em ${dataStr}${horaStr ? ' ' + horaStr : ''}.${extra}`, { parse_mode: 'Markdown' } ); return; } if (result.modoFallback) { await this.bot.sendMessage(chatId, `⚠️ _IP não armazenado para sessões encerradas. Mostrando sessões ativas às ${horaStr}._`, { parse_mode: 'Markdown' } ); } const titulo = `Sessão em ${dataStr}${horaStr ? ' ' + horaStr : ''}`; await this._exibirAcessosCgnat(chatId, session.privateIP, result.dados, titulo); } /** * Consulta conexões ATIVAS agora para o IP privado CGNAT, * identifica o cliente e exibe seus dados cadastrais. */ async handleCgnatConsultarAtivas(chatId, privateIP) { await this.bot.sendMessage(chatId, `🔍 Consultando conexão ativa para \`${privateIP}\`...`, { parse_mode: 'Markdown' } ); const result = await this.topsapp.buscarConexaoAtivaPorIp(privateIP); if (!result.success) { await this.bot.sendMessage(chatId, `❌ Erro ao consultar TopSapp: ${result.error}`, { parse_mode: 'Markdown' }); return; } if (result.total === 0) { const extra = result.totalGeral > 0 ? `_(${result.totalGeral} sessões ativas, nenhuma com IP \`${privateIP}\`)_` : `_(nenhuma sessão ativa no momento)_`; await this.bot.sendMessage(chatId, `ℹ️ Nenhuma conexão ativa encontrada para \`${privateIP}\`.\n${extra}`, { parse_mode: 'Markdown' } ); return; } const sessao = result.dados[0]; await this._exibirClienteCgnat(chatId, sessao, privateIP); } /** * Identifica o cliente a partir de um registro de sessão CGNAT e exibe seus dados. */ async _exibirClienteCgnat(chatId, sessao, privateIP) { const { id_cliente, usuario, inicio, termino, nas_ip } = sessao; const inicioFmt = inicio ? inicio.replace(/-\d{2}$/, '').replace('T', ' ') : '—'; const terminoFmt = termino ? termino.replace(/-\d{2}$/, '').replace('T', ' ') : 'ativo'; // Se não tem id_cliente válido, mostra só o que temos if (!id_cliente || id_cliente < 0) { await this.bot.sendMessage(chatId, `🌐 *Sessão CGNAT — \`${privateIP}\`*\n\n` + `👤 Login: \`${usuario || '—'}\`\n` + `🕐 ${inicioFmt} → ${terminoFmt}\n` + `⚠️ _Cliente não identificado (registro sem ID)_`, { parse_mode: 'Markdown' } ); return; } await this.bot.sendMessage(chatId, `🔍 Buscando dados do cliente (ID ${id_cliente})...`, { parse_mode: 'Markdown' }); const dadosResult = await this.topsapp.obterDadosCadastrais(id_cliente); if (!dadosResult.success || !dadosResult.cliente) { // Fallback: mostra só os dados da sessão await this.bot.sendMessage(chatId, `🌐 *Sessão CGNAT — \`${privateIP}\`*\n\n` + `👤 Login: \`${usuario || '—'}\`\n` + `🕐 ${inicioFmt} → ${terminoFmt}\n` + `❌ Erro ao buscar dados do cliente: ${dadosResult.error || 'desconhecido'}`, { parse_mode: 'Markdown' } ); return; } const c = dadosResult.cliente; const servAtivo = (dadosResult.servicos || []).find(s => s.login === usuario) || (dadosResult.servicos || [])[0]; let msg = `🌐 *Cliente identificado via CGNAT*\n`; msg += `📡 IP Privado: \`${privateIP}\`\n\n`; msg += `👤 *${c.nome || '—'}*\n`; if (c.cpf_cnpj) msg += `🪪 CPF/CNPJ: \`${c.cpf_cnpj}\`\n`; if (c.telefone || c.celular) msg += `📞 Tel: ${c.telefone || c.celular}\n`; if (c.email) msg += `📧 ${c.email}\n`; msg += `\n🔌 *Serviço:*\n`; msg += ` Login: \`${usuario}\`\n`; msg += ` 🕐 Sessão: ${inicioFmt} → ${terminoFmt}\n`; if (servAtivo) { if (servAtivo.planoNome) msg += ` 📦 Plano: ${servAtivo.planoNome}\n`; if (servAtivo.statusFormatado) msg += ` 🔄 Status: ${servAtivo.statusFormatado}\n`; } await this.bot.sendMessage(chatId, msg, { parse_mode: 'Markdown' }); // Botões de ação const keyboard = { inline_keyboard: [[ { text: '📋 Diagnóstico completo', callback_data: `diag_cliente_${id_cliente}_guarai_colmeia` }, { text: '❌ Fechar', callback_data: 'cancel_action' } ]] }; await this.bot.sendMessage(chatId, '💡 Deseja ver o diagnóstico completo do cliente?', { reply_markup: keyboard }); } /** * Exibe sessões CGNAT do histórico e busca dados do cliente se identificado */ async _exibirAcessosCgnat(chatId, privateIP, acessos, titulo) { // Se há apenas uma sessão com id_cliente válido, vai direto para o cliente const comCliente = acessos.filter(a => a.id_cliente && a.id_cliente > 0); if (comCliente.length === 1) { await this._exibirClienteCgnat(chatId, comCliente[0], privateIP); return; } // Múltiplas sessões — lista para o usuário escolher let msg = `🌐 *${titulo}*\n\`${privateIP}\`\n\n`; for (const a of acessos.slice(0, 8)) { const inicio = a.inicio ? a.inicio.replace(/-\d{2}$/, '').replace('T', ' ') : '—'; const termino = a.termino ? a.termino.replace(/-\d{2}$/, '').replace('T', ' ') : 'ativo'; msg += `👤 *${a.usuario || '—'}* (ID cliente: ${a.id_cliente || '?'})\n`; msg += ` 🕐 ${inicio} → ${termino}\n`; if (a.ip) msg += ` 🖥️ \`${a.ip}\`\n`; msg += '\n'; } if (acessos.length > 8) msg += `_... e mais ${acessos.length - 8} registro(s)._\n`; await this.bot.sendMessage(chatId, msg, { parse_mode: 'Markdown' }); } /** * Reiniciar ONU Datacom — busca em todas as OLTs */ async _reiniciarOnuDatacomAutoDetect(serial, siteId = 'guarai') { const olts = this.ssh.listOLTs(siteId); for (const olt of olts) { try { const findResult = await this.ssh.findOnuBySerial(olt.id, serial); if (findResult && findResult.success) { const { slot, onuId } = findResult.onuInfo; const result = await this.ssh.resetOnuAlternative(olt.id, slot, onuId); return { ...result, details: { oltName: olt.name, slot, onuId, serial } }; } } catch (e) { // Tentar próxima OLT } } return { success: false, error: `ONU ${serial} não encontrada em nenhuma OLT Datacom` }; } } module.exports = BotHandler;