252 lines
10 KiB
JavaScript
Executable File
252 lines
10 KiB
JavaScript
Executable File
#!/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 };
|