diff --git a/src/bot/handler.js b/src/bot/handler.js index d7f72a0..451ceb0 100644 --- a/src/bot/handler.js +++ b/src/bot/handler.js @@ -4,6 +4,7 @@ const { TopSappManager } = require('../topsapp'); const FiberhomeManager = require('../fiberhome/manager'); const config = require('../config/config'); const logger = require('../utils/logger'); +const DateProcessor = require('../utils/dateProcessor'); class BotHandler { constructor(bot) { @@ -12,7 +13,9 @@ class BotHandler { this.ssh = new SSHManager(); this.topsapp = new TopSappManager(); this.fiberhome = new FiberhomeManager(); + 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'); @@ -55,6 +58,12 @@ class BotHandler { } 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) { @@ -215,6 +224,13 @@ class BotHandler { 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; @@ -336,6 +352,29 @@ Digite /help para ver todos os comandos disponíveis! • "liberar 123.456.789-10" • "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: • Informações pessoais do cliente • CPF, telefone, endereço completo @@ -1031,6 +1070,12 @@ Digite /help para ver todos os comandos disponíveis! 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 { console.log(`❓ Callback não reconhecido: ${data}`); 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 */ @@ -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; diff --git a/src/nlp/processor.js b/src/nlp/processor.js index 735007f..43a8b04 100644 --- a/src/nlp/processor.js +++ b/src/nlp/processor.js @@ -27,7 +27,10 @@ class NLPProcessor { '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'], '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 @@ -41,6 +44,9 @@ class NLPProcessor { /^(status|verificar)\s+(topsapp)$/i, /^(dados|cadastro|informações|info)\s+(cliente|cadastrais?)\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 /^(status|verificar|check)\s+(onu|ont)\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 if (/bloquear|suspender|cortar/i.test(action)) { 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 const normalizedAction = this.normalizeAction(action); const target = match[match.length - 1]; // Último grupo capturado diff --git a/src/topsapp/api.js b/src/topsapp/api.js index eddf9fc..ca5900a 100644 --- a/src/topsapp/api.js +++ b/src/topsapp/api.js @@ -625,6 +625,86 @@ class TopSappAPI { 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 */ diff --git a/src/topsapp/manager.js b/src/topsapp/manager.js index 82f6a44..b6f053f 100644 --- a/src/topsapp/manager.js +++ b/src/topsapp/manager.js @@ -770,6 +770,16 @@ class TopSappManager { 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 if (servicosDoCliente.length > 50) { 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 */ diff --git a/src/utils/dateProcessor.js b/src/utils/dateProcessor.js new file mode 100644 index 0000000..1e99aaa --- /dev/null +++ b/src/utils/dateProcessor.js @@ -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; diff --git a/test-date-processor.js b/test-date-processor.js new file mode 100644 index 0000000..cdccb07 --- /dev/null +++ b/test-date-processor.js @@ -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!'); diff --git a/test-servicos.js b/test-servicos.js new file mode 100644 index 0000000..83b6966 --- /dev/null +++ b/test-servicos.js @@ -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(); diff --git a/test-suporte-nlp.js b/test-suporte-nlp.js new file mode 100644 index 0000000..68ff371 --- /dev/null +++ b/test-suporte-nlp.js @@ -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!'); diff --git a/{ b/{ new file mode 100644 index 0000000..e69de29