#!/usr/bin/env node /** * scripts/sigma_robot_worker.js * Worker de comunicación e integración entre Help Desk y sigma-robot. * * Funciones: * 1. Lee mensajes pendientes del chat de sigma-robot para Agente Help Desk (ID 90). * 2. Procesa solicitudes de creación de tickets y las registra en Help Desk. * 3. Responde por el chat de sigma-robot confirmando la creación con link directo. * 4. Si la tarea requiere intervención humana o está en proceso, avisa en el chat. */ require('dotenv').config(); const Ticket = require('../models/Ticket'); const Company = require('../models/Company'); const Department = require('../models/Department'); const User = require('../models/User'); const SIGMA_BOT_URL = process.env.SIGMA_BOT_URL || 'https://sigma-bot.telcotronics.com'; const TOKEN = process.env.SIGMA_AGENTE_TOKEN; const AGENTE_ID = 90; // Agente Help Desk if (!TOKEN) { console.error('❌ Error: SIGMA_AGENTE_TOKEN no configurado en .env'); process.exit(1); } /** * Consulta mensajes pendientes en sigma-robot */ async function leerMensajesPendientes() { const res = await fetch(`${SIGMA_BOT_URL}/ecosistema/v1/mensajes/pendientes`, { headers: { 'Authorization': `Bearer ${TOKEN}` } }); if (!res.ok) { throw new Error(`Error ${res.status} leyendo pendientes: ${await res.text()}`); } const data = await res.json(); return data.mensajes || []; } /** * Envía un mensaje a sigma-robot */ async function enviarMensaje({ para, agente_id, mensaje }) { const body = { para, mensaje }; if (para === 'agente' && agente_id) { body.agente_id = parseInt(agente_id); } const res = await fetch(`${SIGMA_BOT_URL}/ecosistema/v1/mensajes`, { method: 'POST', headers: { 'Authorization': `Bearer ${TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); if (!res.ok) { throw new Error(`Error enviando mensaje: ${await res.text()}`); } return await res.json(); } /** * Parsea el texto del mensaje para extraer parámetros de ticket si existen */ function parsearSolicitudTicket(texto) { const raw = String(texto || '').trim(); const lower = raw.toLowerCase(); const esTicket = lower.includes('ticket') || lower.includes('incidencia') || lower.includes('soporte'); const esCrear = lower.includes('crear ticket') || lower.includes('abrir ticket') || lower.includes('nuevo ticket') || lower.startsWith('ticket:'); // Evitar falsos positivos con propuestas arquitectónicas, código o mensajes de coordinación if (lower.includes('propongo') || lower.includes('contrato') || lower.includes('revisé tu código') || lower.includes('preguntas para cerrar') || lower.includes('fase 2') || lower.includes('herramienta') || lower.includes('simulado') || lower.includes('recibido y aplicado') || lower.includes('prueba real')) { return null; } if (!esTicket || !esCrear) { return null; } // Detectar Empresa let companyId = 1; // Default: Telcotronics if (lower.includes('xsystem') || lower.includes('empresa 2') || lower.includes('empresa=2')) { companyId = 2; } else if (lower.includes('domus') || lower.includes('empresa 3') || lower.includes('empresa=3')) { companyId = 3; } else if (lower.includes('telcotronics') || lower.includes('empresa 1') || lower.includes('empresa=1')) { companyId = 1; } // Detectar Prioridad let prioridad = 'media'; if (lower.includes('prioridad=alta') || lower.includes('prioridad: alta') || lower.includes('alta')) prioridad = 'alta'; if (lower.includes('prioridad=critica') || lower.includes('prioridad: critica') || lower.includes('critica') || lower.includes('urgente')) prioridad = 'critica'; if (lower.includes('prioridad=baja') || lower.includes('prioridad: baja') || lower.includes('baja')) prioridad = 'baja'; // Extraer Asunto y Mensaje let asunto = 'Solicitud recibida desde sigma-robot'; let mensaje = raw; // Patrón "asunto: ... | mensaje: ..." o "crear ticket: [asunto] - [mensaje]" const matchAsunto = raw.match(/asunto\s*[:=]\s*([^|\n]+)/i); const matchMensaje = raw.match(/(?:mensaje|descripcion|detalle)\s*[:=]\s*([^|\n]+)/i); if (matchAsunto) { asunto = matchAsunto[1].trim(); } if (matchMensaje) { mensaje = matchMensaje[1].trim(); } else if (!matchAsunto) { // Limpiar prefijo tipo "crear ticket:" y usar las primeras palabras const cleaned = raw.replace(/^(?:@?\w+\s+)?(?:por\s+favor\s+)?(?:crear|abrir|generar)?\s*(?:un\s+)?ticket(?:\s*[:\-])?\s*/i, '').trim(); if (cleaned.length > 0) { const lineas = cleaned.split('\n').filter(Boolean); asunto = lineas[0].substring(0, 80); mensaje = cleaned; } } return { companyId, asunto: asunto.substring(0, 150), mensaje, prioridad }; } /** * Ejecuta un ciclo de revisión y procesamiento */ async function procesarChat() { console.log(`[${new Date().toISOString()}] 🔍 Revisando buzón de chat en sigma-robot...`); const pendientes = await leerMensajesPendientes(); console.log(` 📨 Mensajes nuevos recibidos: ${pendientes.length}`); if (pendientes.length === 0) { return { total: 0, creados: 0, respondidos: 0 }; } let creados = 0; let respondidos = 0; for (const msg of pendientes) { console.log(`\n--- Mensaje #${msg.id} de ${msg.remitente_nombre || msg.remitente_usuario || 'desconocido'} ---`); console.log(`Texto: "${msg.mensaje}"`); // Si es un mensaje propio del agente, ignorar if (msg.remitente_agente_id === AGENTE_ID) { continue; } const solicitud = parsearSolicitudTicket(msg.mensaje); // Destino para la respuesta const esDirecto = msg.destino_tipo === 'agente'; const canalRespuesta = esDirecto ? (msg.remitente_tipo === 'pablinux' ? 'pablinux' : 'agente') : 'general'; const agenteDestinoId = msg.remitente_agente_id; if (solicitud) { console.log(` 🎯 Detectada solicitud de ticket:`, solicitud); try { const company = await Company.findById(solicitud.companyId) || await Company.findById(1); // Buscar o aprovisionar usuario solicitante const remitenteNombre = msg.remitente_nombre || msg.remitente_usuario || 'Agente Externo'; const remitenteEmail = `sigmabot_${(msg.remitente_proyecto || 'robot').toLowerCase()}@sigmac.app`; let user = await User.findByEmail(remitenteEmail); if (!user) { user = await User.upsertExternal(remitenteNombre, remitenteEmail, 'sigma-robot', String(msg.id), null, company.id); } // Departamento por defecto const depts = await Department.findAll(company.id); const deptId = depts.length > 0 ? depts[0].id : null; const ticketId = await Ticket.create( company.id, user.id, deptId, solicitud.asunto, solicitud.mensaje, solicitud.prioridad ); const ticketUrl = `https://soporte.sigmac.app/agent/ticket/${ticketId}`; console.log(` ✓ Ticket #${ticketId} creado exitosamente en ${company.name}`); const respuestaTexto = `✅ Ticket #${ticketId} creado en ${company.name}.\n• Asunto: "${solicitud.asunto}"\n• Prioridad: ${solicitud.prioridad}\n• URL: ${ticketUrl}`; await enviarMensaje({ para: canalRespuesta, agente_id: agenteDestinoId, mensaje: respuestaTexto }); creados++; respondidos++; } catch (err) { console.error(` ❌ Error creando ticket para mensaje #${msg.id}:`, err.message); await enviarMensaje({ para: canalRespuesta, agente_id: agenteDestinoId, mensaje: `⚠️ No se pudo crear el ticket para la solicitud #${msg.id}: ${err.message}` }); } } else { // No es un comando de ticket directo. // Si es un mensaje de Pablinux o directo solicitando una tarea compleja: if (msg.remitente_tipo === 'pablinux') { console.log(' 💬 Mensaje directo de Pablinux recibido.'); // Si requiere confirmación o está en curso: if (msg.mensaje.toLowerCase().includes('recibido') || msg.mensaje.toLowerCase().includes('continua')) { console.log(' ✓ Instrucción de continuidad reconocida.'); } else { const respuestaTexto = `👍 Entendido @pablinux. Mensaje recibido por Agente Help Desk (ID 90). Tarea en curso / esperando tus indicaciones para ejecutar.`; await enviarMensaje({ para: 'pablinux', mensaje: respuestaTexto }); respondidos++; } } else if (esDirecto) { console.log(` ℹ️ Mensaje informativo/coordinación de otro agente (${msg.remitente_nombre || msg.remitente_agente_id}). Sin auto-respuesta para evitar spam de agentes.`); // No respondemos automáticamente con mensaje genérico para no spamear ni ciclar con otros agentes } } } console.log(`\n✅ Ciclo finalizado. Total: ${pendientes.length}, Tickets creados: ${creados}, Respuestas enviadas: ${respondidos}`); return { total: pendientes.length, creados, respondidos }; } // Ejecución if (require.main === module) { procesarChat() .then(() => process.exit(0)) .catch(err => { console.error('❌ Error fatal en sigma_robot_worker:', err); process.exit(1); }); } module.exports = { procesarChat, parsearSolicitudTicket };