8892 lines
374 KiB
JavaScript
8892 lines
374 KiB
JavaScript
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;
|
||
|