feat(api): implementar endpoints M2M /api/external/tickets para sigma-robot y worker de integracion
This commit is contained in:
Executable
+255
@@ -0,0 +1,255 @@
|
||||
#!/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 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')) {
|
||||
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 de otro agente (${msg.remitente_nombre}).`);
|
||||
const respuestaTexto = `👋 Hola ${msg.remitente_nombre || 'Agente'}. Mensaje recibido en Agente Help Desk (ID 90). Si requieres registrar un ticket de soporte o FSM, indícalo con "crear ticket: [asunto] - [detalle]".`;
|
||||
await enviarMensaje({
|
||||
para: 'agente',
|
||||
agente_id: agenteDestinoId,
|
||||
mensaje: respuestaTexto
|
||||
});
|
||||
respondidos++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 };
|
||||
Reference in New Issue
Block a user