diff --git a/server.js b/server.js index b57eda3..35771f3 100644 --- a/server.js +++ b/server.js @@ -310,8 +310,12 @@ function genCode6() { return String(Math.floor(100000 + Math.random() * 900000)) // 🛡️ MIDDLEWARE DE PLANES (CORREGIDO) async function requirePlan(req, res, next, feature) { try { + // Quitamos subscription_status para que no dé error en BD actualizadas const q = await pool.query("SELECT plan_tier FROM users WHERE id=$1", [req.user.accountId]); - const userPlan = 'pro'; // Forzamos PRO para pruebas + + // ⚠️ TRUCO TEMPORAL: Forzamos a que el sistema te lea como 'pro' para que puedas probar WhatsApp sin bloqueos + const userPlan = 'pro'; // Cuando quieras restringir planes, cambia esto por: q.rows[0]?.plan_tier || 'free'; + const limits = PLAN_LIMITS[userPlan]; if (!limits || !limits[feature]) { @@ -353,6 +357,7 @@ async function sendWhatsAppAuto(originalPhone, text, instanceName, useDelay = tr let payloadConEscribiendo; const typingTimeMs = Math.min(Math.max(text.length * 30, 1500), 8000); + // Añadimos una cabecera para que sepas a quién iba dirigido realmente el mensaje const textWithNotice = `*(PRUEBA - Iba para: ${originalPhone})*\n\n` + text; if(useDelay) { @@ -635,15 +640,14 @@ async function triggerWhatsAppEvent(ownerId, serviceId, eventType) { if (!settings[checkSwitch]) return; // Si el botón está apagado, salimos // 2. Buscamos qué plantilla corresponde a este evento - const tplTypeMap = { - 'wa_evt_welcome': 'welcome', - 'wa_evt_assigned': 'assigned', // <--- NUEVO: Asignado a operario - 'wa_evt_date': 'appointment', - 'wa_evt_update': 'update', - 'wa_evt_onway': 'on_way', - 'wa_evt_survey': 'survey' -}; - + const tplTypeMap = { + 'wa_evt_welcome': 'welcome', + 'wa_evt_assigned': 'assigned', // NUEVO: Asignado a operario + 'wa_evt_date': 'appointment', // Plantilla: Cita Creada + 'wa_evt_update': 'update', // Plantilla: Modificación de Servicio + 'wa_evt_onway': 'on_way', + 'wa_evt_survey': 'survey' + }; const tplQ = await pool.query("SELECT content FROM message_templates WHERE owner_id=$1 AND type=$2", [ownerId, tplTypeMap[eventType]]); if (tplQ.rowCount === 0 || !tplQ.rows[0].content) return; let text = tplQ.rows[0].content; @@ -660,7 +664,7 @@ async function triggerWhatsAppEvent(ownerId, serviceId, eventType) { const phoneClean = phone.replace('+34', '').trim(); const clientQ = await pool.query("SELECT portal_token FROM clients WHERE phone LIKE $1 AND owner_id=$2 LIMIT 1", [`%${phoneClean}%`, ownerId]); const token = clientQ.rowCount > 0 ? clientQ.rows[0].portal_token : "ERROR"; - const linkMagico = `https://portal.integrarepara.es/?token=${token}`; + const linkMagico = `https://portal.integrarepara.es/?token=${token}&service=${serviceId}`; // ========================================== // 🔄 5. TRADUCTOR DE FECHAS AL FORMATO ESPAÑOL + DÍA @@ -691,7 +695,6 @@ async function triggerWhatsAppEvent(ownerId, serviceId, eventType) { } catch (e) { console.error("Error Motor WA:", e.message); } } - app.get("/providers/credentials", authMiddleware, async (req, res) => { try { const q = await pool.query("SELECT provider, username, last_sync, status FROM provider_credentials WHERE owner_id=$1", [req.user.accountId]); @@ -786,6 +789,8 @@ app.post("/providers/automate/:id", authMiddleware, async (req, res) => { VALUES ($1, $2, $3, CURRENT_TIMESTAMP + INTERVAL '5 minutes') `, [id, worker.id, token]); + // CÁLCULO DE HORA 100% FIABLE: Se lo pedimos a Node forzando a España + // Así siempre saldrá "0:40" en lugar de "23:40" en el texto de WhatsApp const horaCaducidad = new Date(Date.now() + 5 * 60 * 1000).toLocaleTimeString('es-ES', { hour: '2-digit', minute: '2-digit', @@ -807,6 +812,7 @@ app.post("/providers/automate/:id", authMiddleware, async (req, res) => { 🔗 ${link}`; + // SAAS: INSTANCIA DE CLIENTE ESPECÍFICA SIN AWAIT PARA NO BLOQUEAR const instanceName = `cliente_${req.user.accountId}`; sendWhatsAppAuto(worker.phone, mensaje, instanceName, useDelay).catch(console.error); @@ -966,29 +972,19 @@ app.put("/services/set-appointment/:id", authMiddleware, async (req, res) => { const statusQ = await pool.query("SELECT name FROM service_statuses WHERE id=$1", [status_operativo]); const stName = statusQ.rows[0]?.name.toLowerCase() || ""; - // ... dentro de la lógica del Motor en set-appointment ... -const stName = statusQ.rows[0]?.name.toLowerCase() || ""; - -if (stName.includes('asignado')) { - // Si el estado contiene "asignado", disparamos el nuevo evento - triggerWhatsAppEvent(req.user.accountId, id, 'wa_evt_assigned'); -} else if (stName.includes('citado') && newDate !== "") { - // ... resto de lógica que ya tenemos ... - - - - // REGLA ESTRICTA: ¿Es estado Citado y tiene fecha? - if (stName.includes('citado') && newDate !== "") { + // --- MOTOR DE EVENTOS CORREGIDO --- + if (stName.includes('asignado')) { + // Si el estado contiene "asignado", disparar plantilla específica + triggerWhatsAppEvent(req.user.accountId, id, 'wa_evt_assigned'); + } else if (stName.includes('citado') && newDate !== "") { if (oldDate === "") { - // No tenía fecha antes -> Dispara "Cita Creada" + // Primera vez que se pone fecha triggerWhatsAppEvent(req.user.accountId, id, 'wa_evt_date'); } else if (oldDate !== newDate || oldTime !== newTime) { - // Ya tenía fecha y se ha cambiado -> Dispara "Modificación" + // Cambio de fecha u hora triggerWhatsAppEvent(req.user.accountId, id, 'wa_evt_update'); } - } - // Otras reglas - else if (stName.includes('camino')) { + } else if (stName.includes('camino')) { triggerWhatsAppEvent(req.user.accountId, id, 'wa_evt_onway'); } else if (stName.includes('finalizado') || stName.includes('terminado')) { triggerWhatsAppEvent(req.user.accountId, id, 'wa_evt_survey'); @@ -1007,10 +1003,14 @@ app.post("/services/manual-high", authMiddleware, async (req, res) => { const { phone, name, address, description, guild_id, assigned_to, mode } = req.body; const serviceRef = "MAN-" + Date.now().toString().slice(-6); const rawData = { "Nombre Cliente": name, "Teléfono": phone, "Dirección": address, "Descripción": description, "guild_id": guild_id }; - await pool.query(` + const insert = await pool.query(` INSERT INTO scraped_services (owner_id, provider, service_ref, raw_data, status, automation_status, assigned_to) - VALUES ($1, 'MANUAL', $2, $3, 'pending', $4, $5) + VALUES ($1, 'MANUAL', $2, $3, 'pending', $4, $5) RETURNING id `, [req.user.accountId, serviceRef, JSON.stringify(rawData), mode === 'auto' ? 'manual' : 'completed', mode === 'manual' ? assigned_to : null]); + + // Disparar Bienvenida / Alta + triggerWhatsAppEvent(req.user.accountId, insert.rows[0].id, 'wa_evt_welcome'); + res.json({ ok: true }); } catch (e) { res.status(500).json({ ok: false }); } });