Actualizar server.js

This commit is contained in:
2026-03-01 16:20:19 +00:00
parent 8d33cc3f6a
commit f49b441351

View File

@@ -1064,7 +1064,7 @@ app.get("/providers/scraped", authMiddleware, async (req, res) => {
}); });
// ========================================== // ==========================================
// 🤖 RUTA DE AUTOMATIZACIÓN (CORREGIDA) // 🤖 RUTA DE AUTOMATIZACIÓN (MEJORADA)
// ========================================== // ==========================================
app.post("/providers/automate/:id", authMiddleware, async (req, res) => { app.post("/providers/automate/:id", authMiddleware, async (req, res) => {
const { id } = req.params; const { id } = req.params;
@@ -1073,9 +1073,9 @@ app.post("/providers/automate/:id", authMiddleware, async (req, res) => {
try { try {
const { guild_id, cp, useDelay } = req.body; const { guild_id, cp, useDelay } = req.body;
if (!guild_id || !cp) { if (!guild_id) {
console.error("❌ [AUTOMATE] Faltan datos: guild_id o CP"); console.error("❌ [AUTOMATE] Faltan datos: guild_id");
return res.status(400).json({ ok: false, error: "Faltan datos (Gremio o CP)" }); return res.status(400).json({ ok: false, error: "Falta Gremio" });
} }
// 1. Verificar si el expediente existe // 1. Verificar si el expediente existe
@@ -1085,11 +1085,8 @@ app.post("/providers/automate/:id", authMiddleware, async (req, res) => {
return res.status(404).json({ ok: false, error: "Expediente no encontrado" }); return res.status(404).json({ ok: false, error: "Expediente no encontrado" });
} }
const serviceData = serviceQ.rows[0];
const raw = serviceData.raw_data;
// 2. Buscar operarios que cumplan Gremio + Zona (CP) + Activos // 2. Buscar operarios que cumplan Gremio + Zona (CP) + Activos
const workersQ = await pool.query(` let workersQ = await pool.query(`
SELECT u.id, u.full_name, u.phone SELECT u.id, u.full_name, u.phone
FROM users u FROM users u
JOIN user_guilds ug ON u.id = ug.user_id JOIN user_guilds ug ON u.id = ug.user_id
@@ -1098,11 +1095,26 @@ app.post("/providers/automate/:id", authMiddleware, async (req, res) => {
AND u.status = 'active' AND u.status = 'active'
AND ug.guild_id = $2 AND ug.guild_id = $2
AND u.zones::jsonb @> $3::jsonb AND u.zones::jsonb @> $3::jsonb
`, [req.user.accountId, guild_id, JSON.stringify([{ cps: cp.toString() }])]); `, [req.user.accountId, guild_id, JSON.stringify([{ cps: (cp || "00000").toString() }])]);
// MEJORA: Si no hay nadie para ese CP exacto (o si no se puso CP en el presupuesto),
// buscamos a CUALQUIER operario de ese gremio
if (workersQ.rowCount === 0) {
console.log(`⚠️ [AUTOMATE] No hay operario para el CP exacto, buscando a cualquiera del gremio ${guild_id}...`);
workersQ = await pool.query(`
SELECT u.id, u.full_name, u.phone
FROM users u
JOIN user_guilds ug ON u.id = ug.user_id
WHERE u.owner_id = $1
AND u.role = 'operario'
AND u.status = 'active'
AND ug.guild_id = $2
`, [req.user.accountId, guild_id]);
}
if (workersQ.rowCount === 0) { if (workersQ.rowCount === 0) {
console.warn(`⚠️ [AUTOMATE] No hay operarios activos para Gremio:${guild_id} y CP:${cp}`); console.warn(` [AUTOMATE] No hay operarios activos para el Gremio:${guild_id}`);
return res.status(404).json({ ok: false, error: "No hay operarios disponibles para esta zona/gremio" }); return res.status(404).json({ ok: false, error: "No hay operarios disponibles para este gremio" });
} }
// 3. Marcar como "En progreso" // 3. Marcar como "En progreso"
@@ -1123,9 +1135,10 @@ app.post("/providers/automate/:id", authMiddleware, async (req, res) => {
}); });
const link = `https://web.integrarepara.es/aceptar.html?t=${token}`; const link = `https://web.integrarepara.es/aceptar.html?t=${token}`;
const mensaje = `🛠️ *NUEVO SERVICIO DISPONIBLE*\n\n👤 *Operario:* ${worker.full_name}\n📍 *Zona:* ${cp}\n⏱️ *Expira:* ${horaCaducidad}\n\nRevisa y acepta aquí:\n🔗 ${link}`; const cpMostrar = (cp && cp !== "00000") ? cp : "Zona asignada";
const mensaje = `🛠️ *NUEVO SERVICIO DISPONIBLE*\n\n👤 *Operario:* ${worker.full_name}\n📍 *Zona:* ${cpMostrar}\n⏱️ *Expira:* ${horaCaducidad}\n\nRevisa y acepta aquí:\n🔗 ${link}`;
// 6. Envío WA (Sandbox 667248132 activo en sendWhatsAppAuto) // 6. Envío WA
const instanceName = `cliente_${req.user.accountId}`; const instanceName = `cliente_${req.user.accountId}`;
sendWhatsAppAuto(worker.phone, mensaje, instanceName, useDelay).catch(e => console.error("Error WA Automate:", e.message)); sendWhatsAppAuto(worker.phone, mensaje, instanceName, useDelay).catch(e => console.error("Error WA Automate:", e.message));
@@ -2096,7 +2109,7 @@ app.patch("/budgets/:id/status", authMiddleware, async (req, res) => {
} catch(e) { res.status(500).json({ok: false}); } } catch(e) { res.status(500).json({ok: false}); }
}); });
// Convertir Presupuesto en Servicio Activo // Convertir Presupuesto en Servicio Activo (CON SOPORTE RED INTERNA)
app.post("/budgets/:id/convert", authMiddleware, async (req, res) => { app.post("/budgets/:id/convert", authMiddleware, async (req, res) => {
try { try {
const { date, time, guild_id, assigned_to, use_automation } = req.body; const { date, time, guild_id, assigned_to, use_automation } = req.body;
@@ -2117,13 +2130,12 @@ app.post("/budgets/:id/convert", authMiddleware, async (req, res) => {
"scheduled_time": time || "" "scheduled_time": time || ""
}; };
// 2. Insertamos en el Panel Operativo (Buzón) // 2. Insertamos en el Panel Operativo (Buzón) empezando en manual
const insertSvc = await pool.query( const insertSvc = await pool.query(
"INSERT INTO scraped_services (owner_id, provider, service_ref, status, automation_status, assigned_to, raw_data) VALUES ($1, 'particular', $2, 'pending', $3, $4, $5) RETURNING id", "INSERT INTO scraped_services (owner_id, provider, service_ref, status, automation_status, assigned_to, raw_data) VALUES ($1, 'particular', $2, 'pending', 'manual', $3, $4) RETURNING id",
[ [
req.user.accountId, req.user.accountId,
`PRE-${budget.id}`, `PRE-${budget.id}`,
use_automation ? 'manual' : 'manual', // Empezamos en manual siempre
assigned_to || null, assigned_to || null,
JSON.stringify(rawData) JSON.stringify(rawData)
] ]
@@ -2138,36 +2150,38 @@ app.post("/budgets/:id/convert", authMiddleware, async (req, res) => {
[newServiceId, budget.total] [newServiceId, budget.total]
); );
// 4. Si pide automatización, la disparamos internamente simulando la llamada // 4. Si pide automatización, la disparamos internamente llamando a nuestra propia IP (127.0.0.1)
if (use_automation && guild_id) { if (use_automation && guild_id) {
// Buscamos código postal por si acaso para la automatización
const cpMatch = budget.client_address ? budget.client_address.match(/\b\d{5}\b/) : null; const cpMatch = budget.client_address ? budget.client_address.match(/\b\d{5}\b/) : null;
const cp = cpMatch ? cpMatch[0] : "00000"; // Fallback si no hay CP const cp = cpMatch ? cpMatch[0] : "00000";
// Reutilizamos la lógica de automatización (hacemos un fetch a nosotros mismos para aprovechar el motor existente)
const port = process.env.PORT || 3000; const port = process.env.PORT || 3000;
const autoUrl = `http://localhost:${port}/providers/automate/${newServiceId}`; // IMPORTANTE: 127.0.0.1 en lugar de localhost para evitar errores en Node
const autoUrl = `http://127.0.0.1:${port}/providers/automate/${newServiceId}`;
// Lo lanzamos sin esperar (asíncrono) para no bloquear la respuesta
fetch(autoUrl, { fetch(autoUrl, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${req.headers.authorization.split(' ')[1]}` }, headers: {
'Content-Type': 'application/json',
'Authorization': req.headers.authorization
},
body: JSON.stringify({ guild_id, cp, useDelay: false }) body: JSON.stringify({ guild_id, cp, useDelay: false })
}).catch(e => console.error("Error lanzando automatización interna:", e)); })
.then(r => r.json())
.then(d => console.log("Llamada interna a automatización finalizada.", d))
.catch(e => console.error("Error lanzando automatización interna:", e));
// Si va a la bolsa, no avisamos al cliente de la fecha (aún no se sabe)
if (budget.client_phone) { if (budget.client_phone) {
const msg = `✅ *PRESUPUESTO ACEPTADO*\n\nHola ${budget.client_name}, confirmamos la aceptación del presupuesto por un total de *${budget.total}€*.\n\nEn breve un técnico se pondrá en contacto contigo para agendar la cita. ¡Gracias por confiar en nosotros!`; const msg = `✅ *PRESUPUESTO ACEPTADO*\n\nHola ${budget.client_name}, confirmamos la aceptación del presupuesto por un total de *${budget.total}€*.\n\nEn breve un técnico se pondrá en contacto contigo para agendar la cita. ¡Gracias por confiar en nosotros!`;
sendWhatsAppAuto(budget.client_phone, msg, `cliente_${req.user.accountId}`, false).catch(console.error); sendWhatsAppAuto(budget.client_phone, msg, `cliente_${req.user.accountId}`, false).catch(console.error);
} }
} }
// 5. Si se asigna manual con fecha
else if (budget.client_phone && date && time) { else if (budget.client_phone && date && time) {
// Asignación directa a un técnico con fecha y hora
const [y, m, d] = date.split('-'); const [y, m, d] = date.split('-');
const msg = `✅ *PRESUPUESTO ACEPTADO*\n\nHola ${budget.client_name}, confirmamos la aceptación del presupuesto por un total de *${budget.total}€*.\n\nEl servicio ha sido agendado para el *${d}/${m}/${y} a las ${time}*. ¡Gracias por confiar en nosotros!`; const msg = `✅ *PRESUPUESTO ACEPTADO*\n\nHola ${budget.client_name}, confirmamos la aceptación del presupuesto por un total de *${budget.total}€*.\n\nEl servicio ha sido agendado para el *${d}/${m}/${y} a las ${time}*. ¡Gracias por confiar en nosotros!`;
sendWhatsAppAuto(budget.client_phone, msg, `cliente_${req.user.accountId}`, false).catch(console.error); sendWhatsAppAuto(budget.client_phone, msg, `cliente_${req.user.accountId}`, false).catch(console.error);
// Forzar estado Asignado si hay un técnico
if (assigned_to) { if (assigned_to) {
const statusQ = await pool.query("SELECT id FROM service_statuses WHERE owner_id=$1 AND name ILIKE '%asignado%' LIMIT 1", [req.user.accountId]); const statusQ = await pool.query("SELECT id FROM service_statuses WHERE owner_id=$1 AND name ILIKE '%asignado%' LIMIT 1", [req.user.accountId]);
if (statusQ.rowCount > 0) { if (statusQ.rowCount > 0) {