topsapp: adicionado opção de abrir suporte
Signed-off-by: Rodrigo B. de Sousa Martins <rodrigo.sousa.577@gmail.com>
This commit is contained in:
1 parent
1337e1c2d0
commit
3d35fc24cc
9 files changed
+1600
-1
No files matched your search
@@ -4,6 +4,7 @@ const { TopSappManager } = require('../topsapp');
|
|||||||
const FiberhomeManager = require('../fiberhome/manager');
|
const FiberhomeManager = require('../fiberhome/manager');
|
||||||
const config = require('../config/config');
|
const config = require('../config/config');
|
||||||
const logger = require('../utils/logger');
|
const logger = require('../utils/logger');
|
||||||
|
const DateProcessor = require('../utils/dateProcessor');
|
||||||
|
|
||||||
class BotHandler {
|
class BotHandler {
|
||||||
constructor(bot) {
|
constructor(bot) {
|
||||||
@@ -12,7 +13,9 @@ class BotHandler {
|
|||||||
this.ssh = new SSHManager();
|
this.ssh = new SSHManager();
|
||||||
this.topsapp = new TopSappManager();
|
this.topsapp = new TopSappManager();
|
||||||
this.fiberhome = new FiberhomeManager();
|
this.fiberhome = new FiberhomeManager();
|
||||||
|
this.dateProcessor = new DateProcessor();
|
||||||
this.userSessions = new Map(); // Armazena sessões de usuário
|
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');
|
logger.info('BotHandler inicializado');
|
||||||
console.log('🎯 BotHandler inicializado');
|
console.log('🎯 BotHandler inicializado');
|
||||||
@@ -55,6 +58,12 @@ class BotHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
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
|
// Verificar se o usuário está em uma sessão esperando input
|
||||||
const userSession = this.userSessions.get(userId);
|
const userSession = this.userSessions.get(userId);
|
||||||
if (userSession) {
|
if (userSession) {
|
||||||
@@ -215,6 +224,13 @@ class BotHandler {
|
|||||||
await this.handleFiberhomeStatus(chatId);
|
await this.handleFiberhomeStatus(chatId);
|
||||||
break;
|
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':
|
case 'conectar':
|
||||||
await this.handleConnect(chatId, target);
|
await this.handleConnect(chatId, target);
|
||||||
break;
|
break;
|
||||||
@@ -336,6 +352,29 @@ Digite /help para ver todos os comandos disponíveis!
|
|||||||
• "liberar 123.456.789-10"
|
• "liberar 123.456.789-10"
|
||||||
• "status topsapp"
|
• "status topsapp"
|
||||||
|
|
||||||
|
🎫 TOPSAPP - ABERTURA DE SUPORTE:
|
||||||
|
• "abrir suporte"
|
||||||
|
• "criar suporte para João Silva"
|
||||||
|
• "novo chamado"
|
||||||
|
• "suporte cliente 123.456.789-10"
|
||||||
|
|
||||||
|
🔄 PROCESSO INTERATIVO DE SUPORTE:
|
||||||
|
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 para visita
|
||||||
|
6️⃣ Descrição detalhada do problema
|
||||||
|
7️⃣ Confirmação e criação do 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"
|
||||||
|
|
||||||
📋 DADOS CADASTRAIS COMPLETOS:
|
📋 DADOS CADASTRAIS COMPLETOS:
|
||||||
• Informações pessoais do cliente
|
• Informações pessoais do cliente
|
||||||
• CPF, telefone, endereço completo
|
• CPF, telefone, endereço completo
|
||||||
@@ -1031,6 +1070,12 @@ Digite /help para ver todos os comandos disponíveis!
|
|||||||
this.userSessions.set(callbackQuery.from.id, {
|
this.userSessions.set(callbackQuery.from.id, {
|
||||||
type: 'waiting_username_disconnect'
|
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 {
|
} else {
|
||||||
console.log(`❓ Callback não reconhecido: ${data}`);
|
console.log(`❓ Callback não reconhecido: ${data}`);
|
||||||
await this.bot.answerCallbackQuery(callbackQuery.id, 'Comando não reconhecido');
|
await this.bot.answerCallbackQuery(callbackQuery.id, 'Comando não reconhecido');
|
||||||
@@ -4003,6 +4048,132 @@ Digite /help para ver todos os comandos disponíveis!
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback para selecionar cliente para suporte
|
||||||
|
*/
|
||||||
|
async handleSuporteClienteCallback(callbackQuery, data) {
|
||||||
|
await this.bot.answerCallbackQuery(callbackQuery.id);
|
||||||
|
|
||||||
|
const chatId = callbackQuery.message.chat.id;
|
||||||
|
const userId = callbackQuery.from.id;
|
||||||
|
|
||||||
|
const parts = data.split('_');
|
||||||
|
const clienteIndex = parseInt(parts[2]);
|
||||||
|
|
||||||
|
console.log(`🎫 Cliente selecionado para suporte: índice ${clienteIndex}`);
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
await this.bot.editMessageText(
|
||||||
|
'❌ **Sessão expirada**\n\nPor favor, inicie novamente com o comando "abrir suporte".',
|
||||||
|
{
|
||||||
|
chat_id: chatId,
|
||||||
|
message_id: callbackQuery.message.message_id,
|
||||||
|
parse_mode: 'Markdown'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sessaoAtiva.dados || !sessaoAtiva.dados.clientesEncontrados || clienteIndex >= sessaoAtiva.dados.clientesEncontrados.length) {
|
||||||
|
await this.bot.editMessageText(
|
||||||
|
'❌ **Cliente inválido**\n\nPor favor, tente novamente.',
|
||||||
|
{
|
||||||
|
chat_id: chatId,
|
||||||
|
message_id: callbackQuery.message.message_id,
|
||||||
|
parse_mode: 'Markdown'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Selecionar cliente
|
||||||
|
sessaoAtiva.dados.clienteSelecionado = sessaoAtiva.dados.clientesEncontrados[clienteIndex];
|
||||||
|
sessaoAtiva.etapa = 'selecionar_servico';
|
||||||
|
|
||||||
|
console.log(`✅ Cliente selecionado: ${sessaoAtiva.dados.clienteSelecionado.nome}`);
|
||||||
|
|
||||||
|
// Atualizar mensagem para mostrar cliente selecionado
|
||||||
|
await this.bot.editMessageText(
|
||||||
|
`✅ **Cliente selecionado:**\n${sessaoAtiva.dados.clienteSelecionado.nome}\n\n🔍 Buscando serviços...`,
|
||||||
|
{
|
||||||
|
chat_id: chatId,
|
||||||
|
message_id: callbackQuery.message.message_id,
|
||||||
|
parse_mode: 'Markdown'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Continuar para próxima etapa
|
||||||
|
await this.processarEtapaSelecionarServico(sessaoAtiva.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback para selecionar serviço para suporte
|
||||||
|
*/
|
||||||
|
async handleSuporteServicoCallback(callbackQuery, data) {
|
||||||
|
await this.bot.answerCallbackQuery(callbackQuery.id);
|
||||||
|
|
||||||
|
const chatId = callbackQuery.message.chat.id;
|
||||||
|
const userId = callbackQuery.from.id;
|
||||||
|
|
||||||
|
const parts = data.split('_');
|
||||||
|
const servicoIndex = parseInt(parts[2]);
|
||||||
|
|
||||||
|
console.log(`🎫 Serviço selecionado para suporte: índice ${servicoIndex}`);
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
await this.bot.editMessageText(
|
||||||
|
'❌ **Sessão expirada**\n\nPor favor, inicie novamente com o comando "abrir suporte".',
|
||||||
|
{
|
||||||
|
chat_id: chatId,
|
||||||
|
message_id: callbackQuery.message.message_id,
|
||||||
|
parse_mode: 'Markdown'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sessaoAtiva.dados || !sessaoAtiva.dados.servicos || servicoIndex >= sessaoAtiva.dados.servicos.length) {
|
||||||
|
await this.bot.editMessageText(
|
||||||
|
'❌ **Serviço inválido**\n\nPor favor, tente novamente.',
|
||||||
|
{
|
||||||
|
chat_id: chatId,
|
||||||
|
message_id: callbackQuery.message.message_id,
|
||||||
|
parse_mode: 'Markdown'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Selecionar serviço
|
||||||
|
sessaoAtiva.dados.servicoSelecionado = sessaoAtiva.dados.servicos[servicoIndex];
|
||||||
|
sessaoAtiva.etapa = 'selecionar_categoria';
|
||||||
|
|
||||||
|
console.log(`✅ Serviço selecionado: ${sessaoAtiva.dados.servicoSelecionado.nome}`);
|
||||||
|
|
||||||
|
// Atualizar mensagem para mostrar serviço selecionado
|
||||||
|
await this.bot.editMessageText(
|
||||||
|
`✅ **Serviço selecionado:**\n${sessaoAtiva.dados.servicoSelecionado.nome}\n\n🔍 Buscando categorias de suporte...`,
|
||||||
|
{
|
||||||
|
chat_id: chatId,
|
||||||
|
message_id: callbackQuery.message.message_id,
|
||||||
|
parse_mode: 'Markdown'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Continuar para próxima etapa
|
||||||
|
await this.processarEtapaSelecionarCategoria(sessaoAtiva.id);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback para mostrar dados cadastrais do cliente
|
* Callback para mostrar dados cadastrais do cliente
|
||||||
*/
|
*/
|
||||||
@@ -4580,6 +4751,702 @@ Digite /help para ver todos os comandos disponíveis!
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 técnicos
|
||||||
|
await this.bot.sendMessage(session.chatId, `🔍 Buscando técnicos disponíveis...`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Tentar obter técnicos primeiro, se falhar, usar operadores
|
||||||
|
let resultado = await this.topsapp.obterTecnicos();
|
||||||
|
|
||||||
|
if (!resultado.success || !resultado.data || resultado.data.length === 0) {
|
||||||
|
// Fallback para operadores
|
||||||
|
resultado = await this.topsapp.obterOperadores();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!resultado.success) {
|
||||||
|
await this.bot.sendMessage(session.chatId,
|
||||||
|
`❌ **Erro ao buscar técnicos**\n\n` +
|
||||||
|
`⚠️ ${resultado.error}\n\n` +
|
||||||
|
`🔄 Reiniciando processo...`
|
||||||
|
);
|
||||||
|
this.suporteSessions.delete(sessionId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!resultado.data?.dados || resultado.data.dados.length === 0) {
|
||||||
|
await this.bot.sendMessage(session.chatId,
|
||||||
|
`❌ **Nenhum técnico 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 técnicos**\n\n` +
|
||||||
|
`⚠️ ${error.message}\n\n` +
|
||||||
|
`🔄 Reiniciando processo...`
|
||||||
|
);
|
||||||
|
this.suporteSessions.delete(sessionId);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Usuário selecionou um técnico
|
||||||
|
const numero = parseInt(input.trim());
|
||||||
|
|
||||||
|
if (isNaN(numero) || numero < 1 || numero > session.dados.tecnicos.length) {
|
||||||
|
await this.bot.sendMessage(session.chatId,
|
||||||
|
`❌ **Número inválido**\n\n` +
|
||||||
|
`Por favor, digite um número entre 1 e ${session.dados.tecnicos.length}:`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
session.dados.tecnicoSelecionado = session.dados.tecnicos[numero - 1];
|
||||||
|
session.etapa = 'definir_data_hora';
|
||||||
|
await this.processarEtapaDefinirDataHora(sessionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mostrar opções de técnicos
|
||||||
|
*/
|
||||||
|
async mostrarOpcoesTecnicos(session) {
|
||||||
|
let message = `👨🔧 **Técnicos disponíveis:**\n\n`;
|
||||||
|
|
||||||
|
// Validar se técnicos é um array
|
||||||
|
if (!Array.isArray(session.dados.tecnicos)) {
|
||||||
|
console.error(`❌ session.dados.tecnicos não é um array:`, {
|
||||||
|
type: typeof session.dados.tecnicos,
|
||||||
|
value: session.dados.tecnicos
|
||||||
|
});
|
||||||
|
throw new Error(`Dados de técnicos inválidos: ${typeof session.dados.tecnicos}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
session.dados.tecnicos.forEach((tecnico, index) => {
|
||||||
|
message += `**${index + 1}.** ${tecnico.nome || tecnico.tecnico}\n`;
|
||||||
|
if (tecnico.email) message += ` 📧 ${tecnico.email}\n`;
|
||||||
|
message += `\n`;
|
||||||
|
});
|
||||||
|
|
||||||
|
message += `Digite o **número** do técnico responsável (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,
|
||||||
|
contato: dados.clienteSelecionado.nome,
|
||||||
|
contatoTelefone: dados.clienteSelecionado.telefone || '',
|
||||||
|
descricao: `${dados.descricao}\n\nData/Hora agendada: ${this.dateProcessor.formatarData(dados.dataHora)}\nTécnico: ${dados.tecnicoSelecionado.nome || dados.tecnicoSelecionado.tecnico}`,
|
||||||
|
outrasInformacoes: `Criado via Bot Telegram\nData original: ${dados.dataHoraTexto}`
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
module.exports = BotHandler;
|
||||||
+44
-1
@@ -27,7 +27,10 @@ class NLPProcessor {
|
|||||||
'status_onu': ['status onu', 'verificar onu', 'onu status', 'check onu', 'status ont', 'ont status'],
|
'status_onu': ['status onu', 'verificar onu', 'onu status', 'check onu', 'status ont', 'ont status'],
|
||||||
'fiberhome_status': ['fiberhome status', 'status fiberhome', 'conexões fiberhome', 'fiberhome conexoes'],
|
'fiberhome_status': ['fiberhome status', 'status fiberhome', 'conexões fiberhome', 'fiberhome conexoes'],
|
||||||
'dados_cliente': ['dados cliente', 'dados cadastrais', 'cadastro cliente', 'informações cliente', 'info cliente'],
|
'dados_cliente': ['dados cliente', 'dados cadastrais', 'cadastro cliente', 'informações cliente', 'info cliente'],
|
||||||
'listar_servicos': ['listar serviços', 'servicos cliente', 'planos cliente', 'contratos cliente', 'ver servicos']
|
'listar_servicos': ['listar serviços', 'servicos cliente', 'planos cliente', 'contratos cliente', 'ver servicos'],
|
||||||
|
'abrir_suporte': ['abrir suporte', 'criar suporte', 'adicionar suporte', 'novo suporte', 'suporte', 'atendimento', 'chamado', 'ticket'],
|
||||||
|
'adicionar_suporte': ['abrir suporte', 'criar suporte', 'adicionar suporte', 'novo suporte', 'suporte tecnico', 'atendimento tecnico'],
|
||||||
|
'abrir_suporte': ['abrir suporte', 'criar suporte', 'adicionar suporte', 'novo suporte', 'suporte', 'chamado', 'ticket', 'atendimento']
|
||||||
};
|
};
|
||||||
|
|
||||||
// Padrões de comandos
|
// Padrões de comandos
|
||||||
@@ -41,6 +44,9 @@ class NLPProcessor {
|
|||||||
/^(status|verificar)\s+(topsapp)$/i,
|
/^(status|verificar)\s+(topsapp)$/i,
|
||||||
/^(dados|cadastro|informações|info)\s+(cliente|cadastrais?)\s+(.+)$/i,
|
/^(dados|cadastro|informações|info)\s+(cliente|cadastrais?)\s+(.+)$/i,
|
||||||
/^(listar|ver)\s+(serviços?|servicos|planos|contratos)\s+(.+)$/i,
|
/^(listar|ver)\s+(serviços?|servicos|planos|contratos)\s+(.+)$/i,
|
||||||
|
// Padrões específicos para suporte
|
||||||
|
/^(abrir|criar|adicionar|novo)\s+(suporte|chamado|ticket|atendimento)(?:\s+(.+))?$/i,
|
||||||
|
/^(suporte|chamado|ticket|atendimento)(?:\s+(.+))?$/i,
|
||||||
// Padrões específicos para Fiberhome
|
// Padrões específicos para Fiberhome
|
||||||
/^(status|verificar|check)\s+(onu|ont)\s+([A-Z0-9]+)$/i,
|
/^(status|verificar|check)\s+(onu|ont)\s+([A-Z0-9]+)$/i,
|
||||||
/^(onu|ont)\s+(status|verificar|check)\s+([A-Z0-9]+)$/i,
|
/^(onu|ont)\s+(status|verificar|check)\s+([A-Z0-9]+)$/i,
|
||||||
@@ -192,6 +198,19 @@ class NLPProcessor {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Detectar comandos de suporte
|
||||||
|
if (/abrir|criar|adicionar|novo|suporte|chamado|ticket|atendimento/i.test(action) &&
|
||||||
|
/suporte|chamado|ticket|atendimento/i.test(fullMatch)) {
|
||||||
|
let query = match[3] || match[2] || ''; // Nome/CPF do cliente (opcional)
|
||||||
|
|
||||||
|
return {
|
||||||
|
action: 'abrir_suporte',
|
||||||
|
target: query ? query.trim() : null,
|
||||||
|
confidence: 0.95,
|
||||||
|
type: 'suporte_pattern'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Detectar comandos TopSapp - bloquear cliente/serviço
|
// Detectar comandos TopSapp - bloquear cliente/serviço
|
||||||
if (/bloquear|suspender|cortar/i.test(action)) {
|
if (/bloquear|suspender|cortar/i.test(action)) {
|
||||||
let clienteId = match[3] || match[2]; // ID/CPF/Nome do cliente
|
let clienteId = match[3] || match[2]; // ID/CPF/Nome do cliente
|
||||||
@@ -367,6 +386,30 @@ class NLPProcessor {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Detectar comandos de suporte
|
||||||
|
if (/abrir|criar|adicionar|novo/i.test(action) && /suporte|chamado|ticket|atendimento/i.test(fullMatch)) {
|
||||||
|
let clienteQuery = match[3] || null; // Cliente opcional
|
||||||
|
|
||||||
|
return {
|
||||||
|
action: 'abrir_suporte',
|
||||||
|
target: clienteQuery ? clienteQuery.trim() : null,
|
||||||
|
confidence: 0.95,
|
||||||
|
type: 'suporte_pattern'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detectar comando simples de suporte
|
||||||
|
if (/suporte|chamado|ticket|atendimento/i.test(action)) {
|
||||||
|
let clienteQuery = match[2] || null; // Cliente opcional
|
||||||
|
|
||||||
|
return {
|
||||||
|
action: 'abrir_suporte',
|
||||||
|
target: clienteQuery ? clienteQuery.trim() : null,
|
||||||
|
confidence: 0.9,
|
||||||
|
type: 'suporte_simple_pattern'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Padrões gerais
|
// Padrões gerais
|
||||||
const normalizedAction = this.normalizeAction(action);
|
const normalizedAction = this.normalizeAction(action);
|
||||||
const target = match[match.length - 1]; // Último grupo capturado
|
const target = match[match.length - 1]; // Último grupo capturado
|
||||||
|
|||||||
@@ -625,6 +625,86 @@ class TopSappAPI {
|
|||||||
return await this.alterarStatusServico(idServico, idCliente, 1, location, credentials);
|
return await this.alterarStatusServico(idServico, idCliente, 1, location, credentials);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obter técnicos disponíveis
|
||||||
|
*/
|
||||||
|
async obterTecnicos(location = '', credentials) {
|
||||||
|
const instance = this.getInstanceByLocation(location);
|
||||||
|
|
||||||
|
// Obter o idUsuario da sessão atual
|
||||||
|
const session = await this.getValidSession(instance, credentials);
|
||||||
|
|
||||||
|
return await this.makeRequest('ObterTecnicos', {
|
||||||
|
idUsuario: session.idUsuario || 1
|
||||||
|
}, instance, credentials);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obter operadores disponíveis
|
||||||
|
*/
|
||||||
|
async obterOperadores(location = '', credentials) {
|
||||||
|
const instance = this.getInstanceByLocation(location);
|
||||||
|
|
||||||
|
// Obter o idUsuario da sessão atual
|
||||||
|
const session = await this.getValidSession(instance, credentials);
|
||||||
|
|
||||||
|
return await this.makeRequest('ObterOperadores', {
|
||||||
|
idUsuario: session.idUsuario || 1
|
||||||
|
}, instance, credentials);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obter funcionários disponíveis
|
||||||
|
*/
|
||||||
|
async obterFuncionarios(location = '', credentials) {
|
||||||
|
const instance = this.getInstanceByLocation(location);
|
||||||
|
|
||||||
|
// Obter o idUsuario da sessão atual
|
||||||
|
const session = await this.getValidSession(instance, credentials);
|
||||||
|
|
||||||
|
return await this.makeRequest('ObterFuncionarios', {
|
||||||
|
idUsuario: session.idUsuario || 1
|
||||||
|
}, instance, credentials);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obter categorias de suporte
|
||||||
|
*/
|
||||||
|
async obterSuporteCategorias(location = '', credentials) {
|
||||||
|
const instance = this.getInstanceByLocation(location);
|
||||||
|
|
||||||
|
// Obter o idUsuario da sessão atual
|
||||||
|
const session = await this.getValidSession(instance, credentials);
|
||||||
|
|
||||||
|
return await this.makeRequest('ObterSuporteCategorias', {
|
||||||
|
idUsuario: session.idUsuario || 1
|
||||||
|
}, instance, credentials);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adicionar novo suporte
|
||||||
|
*/
|
||||||
|
async adicionarSuporte(dadosSuporte, location = '', credentials) {
|
||||||
|
const instance = this.getInstanceByLocation(location);
|
||||||
|
|
||||||
|
// Obter o idUsuario da sessão atual
|
||||||
|
const session = await this.getValidSession(instance, credentials);
|
||||||
|
|
||||||
|
const dados = {
|
||||||
|
idUsuario: session.idUsuario || 1,
|
||||||
|
idCliente: dadosSuporte.idCliente,
|
||||||
|
idServico: dadosSuporte.idServico,
|
||||||
|
idCategoria: dadosSuporte.idCategoria,
|
||||||
|
contato: dadosSuporte.contato || '',
|
||||||
|
contatoTelefone: dadosSuporte.contatoTelefone || '',
|
||||||
|
descricao: dadosSuporte.descricao,
|
||||||
|
outrasInformacoes: dadosSuporte.outrasInformacoes || '',
|
||||||
|
...dadosSuporte // Permite campos adicionais como idAtendimento
|
||||||
|
};
|
||||||
|
|
||||||
|
return await this.makeRequest('AdicionarSuporte', dados, instance, credentials);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Listar todas as instâncias disponíveis
|
* Listar todas as instâncias disponíveis
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -770,6 +770,16 @@ class TopSappManager {
|
|||||||
|
|
||||||
console.log(`🎯 Serviços filtrados para o cliente ${clienteId}: ${servicosDoCliente.length}`);
|
console.log(`🎯 Serviços filtrados para o cliente ${clienteId}: ${servicosDoCliente.length}`);
|
||||||
|
|
||||||
|
// Debug: mostrar um exemplo de serviço filtrado
|
||||||
|
if (servicosDoCliente.length > 0) {
|
||||||
|
console.log(`📋 Exemplo de serviço encontrado:`, {
|
||||||
|
id: servicosDoCliente[0].id || servicosDoCliente[0].id_servico,
|
||||||
|
descricao: servicosDoCliente[0].descricao,
|
||||||
|
id_cliente: servicosDoCliente[0].id_cliente,
|
||||||
|
status: servicosDoCliente[0].status
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Se ainda assim temos muitos resultados, algo está errado
|
// Se ainda assim temos muitos resultados, algo está errado
|
||||||
if (servicosDoCliente.length > 50) {
|
if (servicosDoCliente.length > 50) {
|
||||||
console.log(`❌ ERRO: Ainda temos ${servicosDoCliente.length} serviços após filtro - possível problema na API`);
|
console.log(`❌ ERRO: Ainda temos ${servicosDoCliente.length} serviços após filtro - possível problema na API`);
|
||||||
@@ -947,6 +957,135 @@ class TopSappManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obter técnicos disponíveis
|
||||||
|
*/
|
||||||
|
async obterTecnicos(location = '') {
|
||||||
|
if (!this.isReady()) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: 'TopSapp não está configurado'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const credentials = this.config.getCredentialsByLocation(location);
|
||||||
|
const result = await this.api.obterTecnicos(location, credentials);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: error.message
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obter operadores disponíveis
|
||||||
|
*/
|
||||||
|
async obterOperadores(location = '') {
|
||||||
|
if (!this.isReady()) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: 'TopSapp não está configurado'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const credentials = this.config.getCredentialsByLocation(location);
|
||||||
|
const result = await this.api.obterOperadores(location, credentials);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: error.message
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obter funcionários disponíveis
|
||||||
|
*/
|
||||||
|
async obterFuncionarios(location = '') {
|
||||||
|
if (!this.isReady()) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: 'TopSapp não está configurado'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const credentials = this.config.getCredentialsByLocation(location);
|
||||||
|
const result = await this.api.obterFuncionarios(location, credentials);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: error.message
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obter categorias de suporte
|
||||||
|
*/
|
||||||
|
async obterSuporteCategorias(location = '') {
|
||||||
|
if (!this.isReady()) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: 'TopSapp não está configurado'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const credentials = this.config.getCredentialsByLocation(location);
|
||||||
|
const result = await this.api.obterSuporteCategorias(location, credentials);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: error.message
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adicionar novo suporte
|
||||||
|
*/
|
||||||
|
async adicionarSuporte(dadosSuporte, location = '') {
|
||||||
|
if (!this.isReady()) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: 'TopSapp não está configurado'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const credentials = this.config.getCredentialsByLocation(location);
|
||||||
|
const result = await this.api.adicionarSuporte(dadosSuporte, location, credentials);
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
console.log(`✅ Suporte criado com sucesso. Protocolo: ${result.data.protocolo || 'N/A'}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: error.message
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obter instâncias disponíveis
|
* Obter instâncias disponíveis
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,378 @@
|
|||||||
|
const logger = require('./logger');
|
||||||
|
|
||||||
|
class DateProcessor {
|
||||||
|
constructor() {
|
||||||
|
// Dicionário de dias da semana
|
||||||
|
this.diasSemana = {
|
||||||
|
'domingo': 0, 'dom': 0,
|
||||||
|
'segunda': 1, 'segunda-feira': 1, 'seg': 1,
|
||||||
|
'terça': 2, 'terca': 2, 'terça-feira': 2, 'terca-feira': 2, 'ter': 2,
|
||||||
|
'quarta': 3, 'quarta-feira': 3, 'qua': 3,
|
||||||
|
'quinta': 4, 'quinta-feira': 4, 'qui': 4,
|
||||||
|
'sexta': 5, 'sexta-feira': 5, 'sex': 5,
|
||||||
|
'sábado': 6, 'sabado': 6, 'sab': 6
|
||||||
|
};
|
||||||
|
|
||||||
|
// Dicionário de meses
|
||||||
|
this.meses = {
|
||||||
|
'janeiro': 0, 'jan': 0,
|
||||||
|
'fevereiro': 1, 'fev': 1,
|
||||||
|
'março': 2, 'mar': 2, 'marco': 2,
|
||||||
|
'abril': 3, 'abr': 3,
|
||||||
|
'maio': 4, 'mai': 4,
|
||||||
|
'junho': 5, 'jun': 5,
|
||||||
|
'julho': 6, 'jul': 6,
|
||||||
|
'agosto': 7, 'ago': 7,
|
||||||
|
'setembro': 8, 'set': 8,
|
||||||
|
'outubro': 9, 'out': 9,
|
||||||
|
'novembro': 10, 'nov': 10,
|
||||||
|
'dezembro': 11, 'dez': 11
|
||||||
|
};
|
||||||
|
|
||||||
|
// Palavras-chave temporais
|
||||||
|
this.palavrasTemporais = {
|
||||||
|
'hoje': 0,
|
||||||
|
'amanhã': 1, 'amanha': 1,
|
||||||
|
'depois de amanhã': 2, 'depois de amanha': 2,
|
||||||
|
'ontem': -1,
|
||||||
|
'anteontem': -2
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processar texto em linguagem natural para extrair data e hora
|
||||||
|
* @param {string} texto - Texto em linguagem natural
|
||||||
|
* @returns {Object} - { success: boolean, data: Date, originalText: string, parsed: Object }
|
||||||
|
*/
|
||||||
|
processarTexto(texto) {
|
||||||
|
if (!texto || typeof texto !== 'string') {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: 'Texto inválido'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const textoNormalizado = texto.toLowerCase().trim();
|
||||||
|
logger.debug('Processando texto de data/hora:', { texto: textoNormalizado });
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Tentar diferentes padrões de reconhecimento
|
||||||
|
let resultado = null;
|
||||||
|
|
||||||
|
// 1. Padrões com dias da semana
|
||||||
|
resultado = this.processarDiaSemana(textoNormalizado);
|
||||||
|
if (resultado.success) return resultado;
|
||||||
|
|
||||||
|
// 2. Padrões com palavras temporais (hoje, amanhã, etc)
|
||||||
|
resultado = this.processarPalavrasTemporal(textoNormalizado);
|
||||||
|
if (resultado.success) return resultado;
|
||||||
|
|
||||||
|
// 3. Padrões de data específica (DD/MM, DD/MM/YYYY)
|
||||||
|
resultado = this.processarDataEspecifica(textoNormalizado);
|
||||||
|
if (resultado.success) return resultado;
|
||||||
|
|
||||||
|
// 4. Padrões relativos (em X dias, daqui a X horas)
|
||||||
|
resultado = this.processarDataRelativa(textoNormalizado);
|
||||||
|
if (resultado.success) return resultado;
|
||||||
|
|
||||||
|
// Se não conseguiu processar
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: 'Não foi possível interpretar a data/hora',
|
||||||
|
originalText: texto
|
||||||
|
};
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Erro ao processar data/hora:', error);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: error.message,
|
||||||
|
originalText: texto
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processar padrões com dias da semana
|
||||||
|
*/
|
||||||
|
processarDiaSemana(texto) {
|
||||||
|
// Padrões: "sexta às 14h", "na segunda 09:00", "sexta-feira 14:30"
|
||||||
|
const padroes = [
|
||||||
|
/(?:na\s+)?(\w+(?:-\w+)?)\s+(?:às?\s*)?(\d{1,2}):?(\d{0,2})(?:h)?/i,
|
||||||
|
/(?:na\s+)?(\w+(?:-\w+)?)\s+(?:às?\s*)?(\d{1,2})(?:h)?(\d{0,2})?/i
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const padrao of padroes) {
|
||||||
|
const match = texto.match(padrao);
|
||||||
|
if (match) {
|
||||||
|
const diaNome = match[1].toLowerCase();
|
||||||
|
const hora = parseInt(match[2]);
|
||||||
|
const minuto = match[3] ? parseInt(match[3]) : 0;
|
||||||
|
|
||||||
|
if (this.diasSemana.hasOwnProperty(diaNome)) {
|
||||||
|
const diaSemanaTarget = this.diasSemana[diaNome];
|
||||||
|
const data = this.obterProximoDiaSemana(diaSemanaTarget);
|
||||||
|
data.setHours(hora, minuto, 0, 0);
|
||||||
|
|
||||||
|
if (this.isValidTime(hora, minuto)) {
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data,
|
||||||
|
originalText: texto,
|
||||||
|
parsed: {
|
||||||
|
type: 'dia_semana',
|
||||||
|
dia: diaNome,
|
||||||
|
hora,
|
||||||
|
minuto
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processar palavras temporais (hoje, amanhã, etc)
|
||||||
|
*/
|
||||||
|
processarPalavrasTemporal(texto) {
|
||||||
|
// Padrões: "amanhã às 17h", "hoje 14:30", "depois de amanhã 09h"
|
||||||
|
const padroes = [
|
||||||
|
/(hoje|amanhã|amanha|depois de amanhã|depois de amanha)\s+(?:às?\s*)?(\d{1,2}):?(\d{0,2})(?:h)?/i,
|
||||||
|
/(hoje|amanhã|amanha|depois de amanhã|depois de amanha)\s+(?:às?\s*)?(\d{1,2})(?:h)?(\d{0,2})?/i
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const padrao of padroes) {
|
||||||
|
const match = texto.match(padrao);
|
||||||
|
if (match) {
|
||||||
|
const palavraTemporal = match[1].toLowerCase();
|
||||||
|
const hora = parseInt(match[2]);
|
||||||
|
const minuto = match[3] ? parseInt(match[3]) : 0;
|
||||||
|
|
||||||
|
if (this.palavrasTemporais.hasOwnProperty(palavraTemporal)) {
|
||||||
|
const diasOffset = this.palavrasTemporais[palavraTemporal];
|
||||||
|
const data = new Date();
|
||||||
|
data.setDate(data.getDate() + diasOffset);
|
||||||
|
data.setHours(hora, minuto, 0, 0);
|
||||||
|
|
||||||
|
if (this.isValidTime(hora, minuto)) {
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data,
|
||||||
|
originalText: texto,
|
||||||
|
parsed: {
|
||||||
|
type: 'palavra_temporal',
|
||||||
|
palavra: palavraTemporal,
|
||||||
|
offset: diasOffset,
|
||||||
|
hora,
|
||||||
|
minuto
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processar datas específicas
|
||||||
|
*/
|
||||||
|
processarDataEspecifica(texto) {
|
||||||
|
// Padrões: "15/08 às 14h", "15/08/2024 14:30", "15 de agosto 09h"
|
||||||
|
const padroes = [
|
||||||
|
/(\d{1,2})\/(\d{1,2})(?:\/(\d{2,4}))?\s+(?:às?\s*)?(\d{1,2}):?(\d{0,2})(?:h)?/i,
|
||||||
|
/(\d{1,2})\s+de\s+(\w+)(?:\s+de\s+(\d{2,4}))?\s+(?:às?\s*)?(\d{1,2}):?(\d{0,2})(?:h)?/i
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const padrao of padroes) {
|
||||||
|
const match = texto.match(padrao);
|
||||||
|
if (match) {
|
||||||
|
let dia, mes, ano, hora, minuto;
|
||||||
|
|
||||||
|
if (match[0].includes('/')) {
|
||||||
|
// Formato DD/MM/YYYY
|
||||||
|
dia = parseInt(match[1]);
|
||||||
|
mes = parseInt(match[2]) - 1; // JavaScript usa 0-11 para meses
|
||||||
|
ano = match[3] ? parseInt(match[3]) : new Date().getFullYear();
|
||||||
|
hora = parseInt(match[4]);
|
||||||
|
minuto = match[5] ? parseInt(match[5]) : 0;
|
||||||
|
} else {
|
||||||
|
// Formato "DD de MMMM"
|
||||||
|
dia = parseInt(match[1]);
|
||||||
|
const mesNome = match[2].toLowerCase();
|
||||||
|
mes = this.meses[mesNome];
|
||||||
|
ano = match[3] ? parseInt(match[3]) : new Date().getFullYear();
|
||||||
|
hora = parseInt(match[4]);
|
||||||
|
minuto = match[5] ? parseInt(match[5]) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ano < 100) ano += 2000; // Converter anos de 2 dígitos
|
||||||
|
|
||||||
|
if (mes !== undefined && this.isValidDate(dia, mes, ano) && this.isValidTime(hora, minuto)) {
|
||||||
|
const data = new Date(ano, mes, dia, hora, minuto, 0, 0);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data,
|
||||||
|
originalText: texto,
|
||||||
|
parsed: {
|
||||||
|
type: 'data_especifica',
|
||||||
|
dia,
|
||||||
|
mes: mes + 1, // Para display humano
|
||||||
|
ano,
|
||||||
|
hora,
|
||||||
|
minuto
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processar datas relativas
|
||||||
|
*/
|
||||||
|
processarDataRelativa(texto) {
|
||||||
|
// Padrões: "em 3 dias às 14h", "daqui a 2 horas", "em 1 semana"
|
||||||
|
const padroes = [
|
||||||
|
/(?:em|daqui\s+a)\s+(\d+)\s+(dias?|semanas?|meses?)\s+(?:às?\s*)?(\d{1,2}):?(\d{0,2})(?:h)?/i,
|
||||||
|
/(?:em|daqui\s+a)\s+(\d+)\s+(horas?|minutos?)/i
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const padrao of padroes) {
|
||||||
|
const match = texto.match(padrao);
|
||||||
|
if (match) {
|
||||||
|
const quantidade = parseInt(match[1]);
|
||||||
|
const unidade = match[2].toLowerCase();
|
||||||
|
|
||||||
|
const data = new Date();
|
||||||
|
|
||||||
|
if (unidade.includes('dia')) {
|
||||||
|
data.setDate(data.getDate() + quantidade);
|
||||||
|
if (match[3] && match[4] !== undefined) {
|
||||||
|
const hora = parseInt(match[3]);
|
||||||
|
const minuto = match[4] ? parseInt(match[4]) : 0;
|
||||||
|
if (this.isValidTime(hora, minuto)) {
|
||||||
|
data.setHours(hora, minuto, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (unidade.includes('semana')) {
|
||||||
|
data.setDate(data.getDate() + (quantidade * 7));
|
||||||
|
if (match[3] && match[4] !== undefined) {
|
||||||
|
const hora = parseInt(match[3]);
|
||||||
|
const minuto = match[4] ? parseInt(match[4]) : 0;
|
||||||
|
if (this.isValidTime(hora, minuto)) {
|
||||||
|
data.setHours(hora, minuto, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (unidade.includes('mes')) {
|
||||||
|
data.setMonth(data.getMonth() + quantidade);
|
||||||
|
if (match[3] && match[4] !== undefined) {
|
||||||
|
const hora = parseInt(match[3]);
|
||||||
|
const minuto = match[4] ? parseInt(match[4]) : 0;
|
||||||
|
if (this.isValidTime(hora, minuto)) {
|
||||||
|
data.setHours(hora, minuto, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (unidade.includes('hora')) {
|
||||||
|
data.setHours(data.getHours() + quantidade);
|
||||||
|
} else if (unidade.includes('minuto')) {
|
||||||
|
data.setMinutes(data.getMinutes() + quantidade);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data,
|
||||||
|
originalText: texto,
|
||||||
|
parsed: {
|
||||||
|
type: 'data_relativa',
|
||||||
|
quantidade,
|
||||||
|
unidade
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obter próximo dia da semana
|
||||||
|
*/
|
||||||
|
obterProximoDiaSemana(diaSemanaTarget) {
|
||||||
|
const hoje = new Date();
|
||||||
|
const diaAtual = hoje.getDay();
|
||||||
|
|
||||||
|
let diasParaAdicionar = diaSemanaTarget - diaAtual;
|
||||||
|
if (diasParaAdicionar <= 0) {
|
||||||
|
diasParaAdicionar += 7; // Próxima semana
|
||||||
|
}
|
||||||
|
|
||||||
|
const proximaData = new Date(hoje);
|
||||||
|
proximaData.setDate(hoje.getDate() + diasParaAdicionar);
|
||||||
|
|
||||||
|
return proximaData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validar se a data é válida
|
||||||
|
*/
|
||||||
|
isValidDate(dia, mes, ano) {
|
||||||
|
if (dia < 1 || dia > 31) return false;
|
||||||
|
if (mes < 0 || mes > 11) return false;
|
||||||
|
if (ano < 1900 || ano > 2100) return false;
|
||||||
|
|
||||||
|
// Verificar dias válidos para o mês
|
||||||
|
const diasNoMes = new Date(ano, mes + 1, 0).getDate();
|
||||||
|
return dia <= diasNoMes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validar se a hora é válida
|
||||||
|
*/
|
||||||
|
isValidTime(hora, minuto) {
|
||||||
|
return hora >= 0 && hora <= 23 && minuto >= 0 && minuto <= 59;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formatar data para string legível
|
||||||
|
*/
|
||||||
|
formatarData(data) {
|
||||||
|
if (!(data instanceof Date)) return 'Data inválida';
|
||||||
|
|
||||||
|
const opcoes = {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
timeZone: 'America/Sao_Paulo'
|
||||||
|
};
|
||||||
|
|
||||||
|
return data.toLocaleDateString('pt-BR', opcoes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formatar data para o formato do sistema (YYYY-MM-DD HH:mm:ss)
|
||||||
|
*/
|
||||||
|
formatarParaSistema(data) {
|
||||||
|
if (!(data instanceof Date)) return null;
|
||||||
|
|
||||||
|
const ano = data.getFullYear();
|
||||||
|
const mes = String(data.getMonth() + 1).padStart(2, '0');
|
||||||
|
const dia = String(data.getDate()).padStart(2, '0');
|
||||||
|
const hora = String(data.getHours()).padStart(2, '0');
|
||||||
|
const minuto = String(data.getMinutes()).padStart(2, '0');
|
||||||
|
const segundo = String(data.getSeconds()).padStart(2, '0');
|
||||||
|
|
||||||
|
return `${ano}-${mes}-${dia} ${hora}:${minuto}:${segundo}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = DateProcessor;
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
const DateProcessor = require('./src/utils/dateProcessor');
|
||||||
|
|
||||||
|
// Teste do processador de datas
|
||||||
|
const dateProcessor = new DateProcessor();
|
||||||
|
|
||||||
|
console.log('🧪 Testando processador de datas...\n');
|
||||||
|
|
||||||
|
const testCases = [
|
||||||
|
'amanhã às 14h',
|
||||||
|
'sexta-feira 09:00',
|
||||||
|
'hoje 18h',
|
||||||
|
'em 3 dias às 10h',
|
||||||
|
'15/08 às 16h30',
|
||||||
|
'segunda-feira 08:30',
|
||||||
|
'depois de amanhã 20h',
|
||||||
|
'quarta 14:30'
|
||||||
|
];
|
||||||
|
|
||||||
|
testCases.forEach((testCase, index) => {
|
||||||
|
console.log(`${index + 1}. Teste: "${testCase}"`);
|
||||||
|
const resultado = dateProcessor.processarTexto(testCase);
|
||||||
|
|
||||||
|
if (resultado.success) {
|
||||||
|
console.log(` ✅ Sucesso: ${dateProcessor.formatarData(resultado.data)}`);
|
||||||
|
console.log(` 📊 Parsed: ${JSON.stringify(resultado.parsed)}`);
|
||||||
|
} else {
|
||||||
|
console.log(` ❌ Erro: ${resultado.error}`);
|
||||||
|
}
|
||||||
|
console.log('');
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('🏁 Teste concluído!');
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
// Teste rápido para verificar a estrutura de dados do TopSapp
|
||||||
|
const { TopSappManager } = require('./src/topsapp');
|
||||||
|
|
||||||
|
async function testarServicos() {
|
||||||
|
const topsapp = new TopSappManager();
|
||||||
|
|
||||||
|
if (!topsapp.isReady()) {
|
||||||
|
console.log('❌ TopSapp não está configurado');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('🔍 Testando obterServicosCliente...');
|
||||||
|
const resultado = await topsapp.obterServicosCliente(2207);
|
||||||
|
|
||||||
|
console.log('📊 Resultado completo:', JSON.stringify(resultado, null, 2));
|
||||||
|
|
||||||
|
if (resultado.success && resultado.servicos) {
|
||||||
|
console.log('\n✅ Serviços encontrados:');
|
||||||
|
resultado.servicos.forEach((servico, index) => {
|
||||||
|
console.log(`${index + 1}. ${servico.nome} (ID: ${servico.id})`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Erro:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
testarServicos();
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
const NLPProcessor = require('./src/nlp/processor');
|
||||||
|
|
||||||
|
// Teste do processador NLP para comandos de suporte
|
||||||
|
const nlp = new NLPProcessor();
|
||||||
|
|
||||||
|
console.log('🧪 Testando detecção de comandos de suporte...\n');
|
||||||
|
|
||||||
|
const testCases = [
|
||||||
|
'abrir suporte',
|
||||||
|
'criar suporte para João Silva',
|
||||||
|
'novo chamado',
|
||||||
|
'suporte cliente 123.456.789-10',
|
||||||
|
'adicionar suporte',
|
||||||
|
'abrir suporte FRANCE CRISTINA DE SOUSA CAMARGO',
|
||||||
|
'ticket',
|
||||||
|
'atendimento'
|
||||||
|
];
|
||||||
|
|
||||||
|
testCases.forEach((testCase, index) => {
|
||||||
|
console.log(`${index + 1}. Teste: "${testCase}"`);
|
||||||
|
const intent = nlp.processMessage(testCase);
|
||||||
|
|
||||||
|
console.log(` 🧠 Action: ${intent.action}`);
|
||||||
|
console.log(` 🎯 Target: ${intent.target}`);
|
||||||
|
console.log(` 📊 Confidence: ${intent.confidence}`);
|
||||||
|
console.log(` 🔧 Type: ${intent.type}`);
|
||||||
|
console.log('');
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('🏁 Teste de NLP concluído!');
|
||||||
Reference in new issue
Block a user