Files
rodrigonet/src/bot/handler.js
T
2026-03-29 10:01:41 -03:00

5580 lines
226 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const NLPProcessor = require('../nlp/processor');
const SSHManager = require('../ssh/manager');
const { TopSappManager } = require('../topsapp');
const FiberhomeManager = require('../fiberhome/manager');
const FiberhomeTL1Manager = require('../fiberhome/manager_tl1');
const 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.dateProcessor = new DateProcessor();
this.userSessions = new Map(); // Armazena sessões de usuário
this.suporteSessions = new Map(); // Armazena sessões de suporte em andamento
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 '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 '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 '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 '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"
� **JUNIPER:**
• "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: '👥 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**
**🚫 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"
**🔄 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 '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}`);
}
}
/**
* 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) {
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);
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) {
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' }
);
} 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('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('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('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('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('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} _(uplink)_\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
*/
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 {
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('discovered-onus')) {
errorMessage += `💡 **Dica:** Verifique se a ONU está ligada e descoberta pela OLT.\n` +
`Use o comando: \`show interface gpon discovered-onus\``;
} else 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 {
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('discovered-onus')) {
errorMessage += `💡 **Dica:** Verifique se a ONU está ligada e descoberta pela OLT.\n` +
`Use o comando: \`show interface gpon discovered-onus\``;
} else 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);
} 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;
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'
}
);
} 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 = '') {
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;
}
const 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;
}
// 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.descricao || 'Serviço'}\n`;
message += `🆔 **ID**: ${servicoInfo.servico.id}\n\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
const keyboard = [];
faturasPorServico.forEach((servicoInfo) => {
servicoInfo.faturas.forEach((fatura) => {
if (fatura.status !== 'Pago') {
const row = [];
if (fatura.linhaDigitavel) {
row.push({
text: `📋 Linha Digitável #${fatura.id}`,
callback_data: `linha_${fatura.id}_${location}`
});
}
if (fatura.pixCopiaCola) {
row.push({
text: `💳 PIX #${fatura.id}`,
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 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 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;
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 ? '🟢' : '🔴';
message += `**${index + 1}.** ${statusEmoji} ${servico.nome}\n`;
message += ` 🆔 ID: ${servico.id}\n`;
if (servico.valorMensal) message += ` 💰 Valor: R$ ${servico.valorMensal}\n`;
message += `\n`;
// Adicionar botão para seleção direta
buttons.push([{
text: `${index + 1}. ${servico.nome}`,
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();
console.log(`🔍 Handler - Resultado categorias:`, {
success: resultado.success,
dataType: typeof resultado.data,
dataLength: resultado.data?.dados?.length,
error: resultado.error
});
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 tipos de atendimento
await this.bot.sendMessage(session.chatId, `🔍 Buscando tipos de atendimento disponíveis...`);
try {
// Usar tipos de atendimento ao invés de técnicos
let resultado = await this.topsapp.obterSuporteTiposAtendimento();
if (!resultado.success || !resultado.data?.dados || resultado.data.dados.length === 0) {
// Fallback para técnicos se tipos de atendimento não funcionarem
console.log('🔄 Fallback para técnicos...');
resultado = await this.topsapp.obterTecnicos();
if (!resultado.success) {
await this.bot.sendMessage(session.chatId,
`❌ **Erro ao buscar tipos de atendimento**\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,
`❌ **Nenhum tipo de atendimento encontrado**\n\n` +
`🔄 Reiniciando processo...`
);
this.suporteSessions.delete(sessionId);
return;
}
session.dados.tecnicos = resultado.data.dados;
await this.mostrarOpcoesTecnicos(session);
} catch (error) {
await this.bot.sendMessage(session.chatId,
`❌ **Erro ao buscar tipos de atendimento**\n\n` +
`⚠️ ${error.message}\n\n` +
`🔄 Reiniciando processo...`
);
this.suporteSessions.delete(sessionId);
}
} else {
// Usuário selecionou um técnico
const numero = parseInt(input.trim());
console.log(`🔍 Técnico - Input do usuário: "${input}", Número convertido: ${numero}`);
console.log(`🔍 Técnico - Lista completa de técnicos:`, session.dados.tecnicos);
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];
console.log(`🎯 Técnico selecionado - Índice: ${numero - 1}, Dados:`, tecnicoSelecionado);
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 = `👨‍🔧 **Tipos de atendimento 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 tipos de atendimento inválidos: ${typeof session.dados.tecnicos}`);
}
session.dados.tecnicos.forEach((tecnico, index) => {
// Mostrar nome ou descrição do tipo de atendimento
const nome = tecnico.nome || tecnico.descricao || tecnico.tipo || tecnico.tecnico;
message += `**${index + 1}.** ${nome}\n`;
if (tecnico.email) message += ` 📧 ${tecnico.email}\n`;
message += `\n`;
});
message += `Digite o **número** do tipo de atendimento (1-${session.dados.tecnicos.length}):`;
await this.bot.sendMessage(session.chatId, message, { parse_mode: 'Markdown' });
}
/**
* 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 || dados.tecnicoSelecionado.tecnico}\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 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 || dados.tecnicoSelecionado.tecnico}`
};
// Adicionar campos opcionais se existirem
if (dados.tecnicoSelecionado?.id) {
dadosSuporte.idTecnico = dados.tecnicoSelecionado.id;
}
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}`;
console.log('🎫 Dados finais para criação do suporte:', dadosSuporte);
const resultado = await this.topsapp.adicionarSuporte(dadosSuporte);
if (resultado.success) {
const protocolo = resultado.data?.protocolo || 'N/A';
await this.bot.sendMessage(session.chatId,
`✅ **Suporte criado com sucesso!**\n\n` +
`🎫 **Protocolo:** ${protocolo}\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 || dados.tecnicoSelecionado.tecnico}\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);
}
}
module.exports = BotHandler;