Files
rodrigonet/src/bot/handler.js
T
2026-04-07 13:14:24 -03:00

8892 lines
374 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 logger = require('../utils/logger');
const DateProcessor = require('../utils/dateProcessor');
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;
logger.info('BotHandler inicializado');
console.log('🎯 BotHandler inicializado');
}
/**
* Verifica se o usuário tem permissão
*/
isAuthorized(userId) {
if (config.bot.allowedUsers.length === 0) {
return true; // Se não há lista de usuários, permite todos
}
return config.bot.allowedUsers.includes(userId);
}
/**
* 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;
}
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);
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 '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 /help
*/
async handleHelp(chatId) {
const helpMessage = `🤖 **BOT DE AJUDA TÉCNICA**
🔧 **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' }
]
];
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);
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 <equipamento>
• "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();
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 <ID>" ou "ssh <ID>"';
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 {
// Usar concentrador Juniper como padrão
const result = await this.ssh.disconnectJuniperUser('juniper', 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, target = 'juniper') {
await this.bot.sendMessage(chatId, '🔍 Listando usuários conectados no concentrador...');
try {
const result = await this.ssh.listJuniperUsers(target);
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 result = await this.ssh.getJuniperSubscriberInfo('juniper', 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 = '';
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 pppoeUser = pppoeLogin ? pppoeLogin.split('@')[0] : null;
let [juniperResult, onuResult, genieResult] = await Promise.all([
pppoeUser
? this.ssh.getJuniperSubscriberInfo('juniper', pppoeUser).catch(e => ({ success: false, error: e.message }))
: Promise.resolve({ success: false, skipped: true }),
serial
? this._diagnosticarONU(serial, baseInfo).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('juniper', 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 <login>".
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`;
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')
*/
_resolverOLTporBase(baseInfo) {
if (!baseInfo) return null;
const b = String(baseInfo).toUpperCase();
if (/INTERNET\s+FIBRA\s+OPTICA/i.test(b)) return 'fiberhome';
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
*/
async _diagnosticarONU(serial, baseInfo) {
const serialUpper = serial.toUpperCase();
const oltHint = this._resolverOLTporBase(baseInfo);
// 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);
}
async _diagnosticarONUDatacom(serial, oltIdHint) {
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
if (!found) {
const olts = this.ssh.listOLTs();
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,
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
const olts = this.ssh.listOLTs();
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
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
const oltOptions = this.ssh.listOLTs();
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
const oltOptions = this.ssh.listOLTs();
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
const oltOptions = this.ssh.listOLTs();
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;
}
}
// 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;
}
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_')) {
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_<idCliente>_<idServico>_<location>
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('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('confirm_rst_')) {
console.log(`🔍 Condição confirm_rst_ atendida`);
await this.handleConfirmRestartOnuCallback(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_<oltId>_<slot-com-traço>_<onuId>_<serial>
* remove_readd_vlan_<oltId>_<slot-com-traço>_<onuId>_<vlanId>_<serial>
*/
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 = '';
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 = '';
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 = '';
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 = '';
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 = '';
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 = '';
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 = '';
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_<idServico>_<idCliente>
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_<sessionId>_<index>
*/
async handleFecharSuporteClienteCallback(callbackQuery, data) {
const chatId = callbackQuery.message.chat.id;
const userId = callbackQuery.from.id;
// fechar_suporte_cli_<sessionId>_<index>
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 = '';
await this.processarEtapaFecharBuscarSuportes(sessionId);
}
/**
* Callback: seleção de suporte no fluxo de fechar suporte
* formato: fechar_suporte_id_<sessionId>_<index>
*/
async handleFecharSuporteIdCallback(callbackQuery, data) {
const chatId = callbackQuery.message.chat.id;
const userId = callbackQuery.from.id;
// fechar_suporte_id_<sessionId>_<index>
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 <serial>" ou "ver roteador <tag/login-topsapp>"
*/
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 <termo>".
*
* 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 <serial>` 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 = '';
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 = '';
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);
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 oltHint = this._resolverOLTporBase(base);
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);
}
}
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 };
}
}
/**
* Reiniciar ONU Datacom — busca em todas as OLTs
*/
async _reiniciarOnuDatacomAutoDetect(serial) {
const olts = this.ssh.listOLTs();
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;