Actualizar server.js
This commit is contained in:
98
server.js
98
server.js
@@ -4,6 +4,7 @@ import bcrypt from "bcryptjs";
|
|||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken";
|
||||||
import pg from "pg";
|
import pg from "pg";
|
||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
|
import OpenAI from "openai";
|
||||||
|
|
||||||
const { Pool } = pg;
|
const { Pool } = pg;
|
||||||
const app = express();
|
const app = express();
|
||||||
@@ -401,6 +402,43 @@ async function registrarMovimiento(serviceId, userId, action, details) {
|
|||||||
} catch (e) { console.error("Error Robot Notario:", e); }
|
} catch (e) { console.error("Error Robot Notario:", e); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HELPER: Procesar mensaje con IA (ChatGPT)
|
||||||
|
async function procesarConIA(ownerId, mensajeCliente, datosExpediente) {
|
||||||
|
try {
|
||||||
|
const userQ = await pool.query("SELECT wa_settings FROM users WHERE id=$1", [ownerId]);
|
||||||
|
const settings = userQ.rows[0]?.wa_settings || {};
|
||||||
|
if (!settings.wa_ai_enabled) return null;
|
||||||
|
|
||||||
|
const promptSistema = `
|
||||||
|
Eres el asistente de IntegraRepara. Responde al cliente de forma profesional y corta.
|
||||||
|
DATOS DEL EXPEDIENTE #${datosExpediente.ref}:
|
||||||
|
- Estado: ${datosExpediente.estado}
|
||||||
|
- Operario: ${datosExpediente.operario || 'Pendiente de asignar'}
|
||||||
|
- Fecha Cita: ${datosExpediente.cita || 'Pendiente de agendar'}
|
||||||
|
|
||||||
|
REGLAS:
|
||||||
|
- Si el cliente pregunta cuándo van, dale la fecha si existe.
|
||||||
|
- Si no hay operario, di que estamos buscando al mejor técnico.
|
||||||
|
- NO inventes datos. Si no sabes algo, pide que espere a que un humano le atienda.
|
||||||
|
- Máximo 2 frases.
|
||||||
|
`;
|
||||||
|
|
||||||
|
const completion = await openai.chat.completions.create({
|
||||||
|
model: "gpt-4o-mini",
|
||||||
|
messages: [
|
||||||
|
{ role: "system", content: promptSistema },
|
||||||
|
{ role: "user", content: mensajeCliente }
|
||||||
|
],
|
||||||
|
temperature: 0.7,
|
||||||
|
});
|
||||||
|
|
||||||
|
return completion.choices[0].message.content;
|
||||||
|
} catch (e) {
|
||||||
|
console.error("❌ Error OpenAI:", e.message);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 🛡️ MIDDLEWARE DE PLANES
|
// 🛡️ MIDDLEWARE DE PLANES
|
||||||
async function requirePlan(req, res, next, feature) {
|
async function requirePlan(req, res, next, feature) {
|
||||||
try {
|
try {
|
||||||
@@ -2901,6 +2939,66 @@ app.post("/services/:id/chat", authMiddleware, async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 🤖 WEBHOOK PARA RECIBIR MENSAJES DE WHATSAPP Y RESPONDER CON IA
|
||||||
|
app.post("/webhook/evolution", async (req, res) => {
|
||||||
|
try {
|
||||||
|
const data = req.body;
|
||||||
|
// Solo procesamos si es un mensaje de texto entrante
|
||||||
|
if (data.event !== "messages.upsert" || data.data.key.fromMe) return res.sendStatus(200);
|
||||||
|
|
||||||
|
const telefonoCliente = data.data.key.remoteJid.split("@")[0];
|
||||||
|
const mensajeTexto = data.data.message?.conversation || data.data.message?.extendedTextMessage?.text;
|
||||||
|
const instanceName = data.instance; // ej: cliente_1
|
||||||
|
|
||||||
|
if (!mensajeTexto) return res.sendStatus(200);
|
||||||
|
|
||||||
|
// 1. Identificar al dueño de la instancia (owner_id)
|
||||||
|
const ownerId = instanceName.split("_")[1];
|
||||||
|
|
||||||
|
// 2. Buscar si este teléfono tiene un servicio activo
|
||||||
|
const svcQ = await pool.query(`
|
||||||
|
SELECT s.id, s.service_ref, s.assigned_to, u.full_name as worker_name,
|
||||||
|
st.name as status_name, s.raw_data->>'scheduled_date' as cita
|
||||||
|
FROM scraped_services s
|
||||||
|
LEFT JOIN users u ON s.assigned_to = u.id
|
||||||
|
LEFT JOIN service_statuses st ON (s.raw_data->>'status_operativo')::text = st.id::text
|
||||||
|
WHERE s.owner_id = $1 AND s.raw_data->>'Teléfono' ILIKE $2
|
||||||
|
ORDER BY s.created_at DESC LIMIT 1
|
||||||
|
`, [ownerId, `%${telefonoCliente.slice(-9)}%`]);
|
||||||
|
|
||||||
|
if (svcQ.rowCount > 0) {
|
||||||
|
const service = svcQ.rows[0];
|
||||||
|
|
||||||
|
// 3. Llamar a la IA
|
||||||
|
const respuestaIA = await procesarConIA(ownerId, mensajeTexto, {
|
||||||
|
ref: service.service_ref,
|
||||||
|
estado: service.status_name,
|
||||||
|
operario: service.worker_name,
|
||||||
|
cita: service.cita
|
||||||
|
});
|
||||||
|
|
||||||
|
if (respuestaIA) {
|
||||||
|
// 4. Enviar respuesta por WhatsApp
|
||||||
|
await sendWhatsAppAuto(telefonoCliente, respuestaIA, instanceName, true);
|
||||||
|
|
||||||
|
// 5. Registrar en el chat para que la oficina lo vea
|
||||||
|
await pool.query(`
|
||||||
|
INSERT INTO service_communications (scraped_id, owner_id, sender_name, sender_role, message)
|
||||||
|
VALUES ($1, $2, $3, $4, $5)
|
||||||
|
`, [service.id, ownerId, "Asistente IA", "ia", respuestaIA]);
|
||||||
|
|
||||||
|
// 6. Registrar en el historial
|
||||||
|
await registrarMovimiento(service.id, null, "Respuesta IA", `ChatGPT respondió: ${respuestaIA}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res.sendStatus(200);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error Webhook IA:", e.message);
|
||||||
|
res.sendStatus(500);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// 🕒 EL RELOJ DEL SISTEMA (Ejecutar cada minuto)
|
// 🕒 EL RELOJ DEL SISTEMA (Ejecutar cada minuto)
|
||||||
// ==========================================
|
// ==========================================
|
||||||
|
|||||||
Reference in New Issue
Block a user