From c15a0fc41bde642d29665d74fbc0e27d950adda2 Mon Sep 17 00:00:00 2001 From: marsalva Date: Thu, 20 Aug 2026 21:37:51 +0000 Subject: [PATCH] =?UTF-8?q?A=C3=B1ade=20envio=20de=20WhatsApp=20de=20bienv?= =?UTF-8?q?enida=20al=20crear=20un=20expediente=20nuevo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- robot.js | 135 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 127 insertions(+), 8 deletions(-) diff --git a/robot.js b/robot.js index b6b8bf8..a2a6711 100644 --- a/robot.js +++ b/robot.js @@ -496,28 +496,147 @@ async function syncAndArchive(ownerId, provider, currentWebRefs) { } } +// ========================================== +// 👋 MENSAJE DE BIENVENIDA (WHATSAPP AL CLIENTE, SOLO EXPEDIENTES NUEVOS) +// ========================================== +const { EVOLUTION_BASE_URL, EVOLUTION_API_KEY } = process.env; + +function extractValidPhoneRobot(rawPhone) { + if (!rawPhone) return ""; + let str = String(rawPhone).replace(/\D/g, ""); + if (str.startsWith("0034")) str = str.slice(4); + if (str.startsWith("34") && str.length >= 11) str = str.slice(2); + const match = str.match(/[6789]\d{8}/); + return match ? match[0] : (str.length >= 9 ? str.slice(-9) : str); +} + +async function sendWhatsAppWelcomeText(phone, message, instanceName) { + if (!EVOLUTION_BASE_URL || !EVOLUTION_API_KEY) { + console.log("⚠️ [Bienvenida] Faltan EVOLUTION_BASE_URL / EVOLUTION_API_KEY en este robot. Se omite el envío."); + return false; + } + try { + const baseUrl = EVOLUTION_BASE_URL.replace(/\/$/, ""); + const url = `${baseUrl}/message/sendText/${instanceName}`; + const response = await fetch(url, { + method: 'POST', + headers: { "Content-Type": "application/json", "apikey": EVOLUTION_API_KEY.trim() }, + body: JSON.stringify({ number: phone, text: message, delay: 0 }) + }); + if (!response.ok) { + console.error("❌ [Bienvenida] Fallo enviando WhatsApp. Estado Evolution:", response.status); + return false; + } + return true; + } catch (e) { + console.error("❌ [Bienvenida] Error de red con Evolution API:", e.message); + return false; + } +} + +const NOMBRE_COMPANIA_POR_PROVEEDOR = { multiasistencia: 'Multiasistencia', homeserve: 'HomeServe' }; + +// Réplica ligera de la lógica de triggerWhatsAppEvent('wa_evt_welcome') de server.js, +// pero autocontenida aquí porque el robot no pasa por el servidor (escribe directo a la BD). +async function sendWelcomeMessage(ownerId, provider, ref, data, serviceId) { + // 1. ¿Tiene la empresa activado el aviso de bienvenida? + const userQ = await pool.query("SELECT wa_settings FROM users WHERE id=$1", [ownerId]); + const settings = userQ.rows[0]?.wa_settings || {}; + if (!settings.wa_evt_welcome) return; + + // 2. ¿Existe la plantilla de bienvenida y tiene contenido? + const tplQ = await pool.query("SELECT content FROM message_templates WHERE owner_id=$1 AND type='welcome'", [ownerId]); + if (tplQ.rowCount === 0 || !tplQ.rows[0].content) return; + let text = tplQ.rows[0].content; + + // 3. Teléfono válido + const cleanPhone = extractValidPhoneRobot(data['Teléfono']); + if (cleanPhone.length < 9) { + console.log(`⚠️ [Bienvenida] Expediente #${ref}: sin teléfono válido, no se envía.`); + return; + } + const finalPhone = "34" + cleanPhone; + + // 4. Enlace al portal del cliente (por si la plantilla lo usa) + let enlace = ""; + try { + const clientQ = await pool.query("SELECT portal_token FROM clients WHERE phone LIKE $1 AND owner_id=$2 LIMIT 1", [`%${cleanPhone}%`, ownerId]); + let token; + if (clientQ.rowCount > 0) { + token = clientQ.rows[0].portal_token; + } else { + const insertC = await pool.query( + "INSERT INTO clients (owner_id, full_name, phone, addresses) VALUES ($1, $2, $3, $4) RETURNING portal_token", + [ownerId, data['Nombre Cliente'] || "Asegurado", finalPhone, JSON.stringify([data['Dirección'] || ""])] + ); + token = insertC.rows[0].portal_token; + } + enlace = `https://portal.integrarepara.es/?token=${token}&service=${serviceId}`; + } catch (e) { + console.error(`⚠️ [Bienvenida] No se pudo generar el enlace del portal para #${ref}:`, e.message); + } + + // 5. Sustituir variables de la plantilla + text = text.replace(/{{NOMBRE}}/g, data['Nombre Cliente'] || "Cliente"); + text = text.replace(/{{DIRECCION}}/g, data['Dirección'] || "su domicilio"); + text = text.replace(/{{FECHA}}/g, "la fecha acordada"); + text = text.replace(/{{HORA}}/g, "la hora acordada"); + text = text.replace(/{{COMPANIA}}/g, NOMBRE_COMPANIA_POR_PROVEEDOR[provider] || "su Aseguradora"); + text = text.replace(/{{REFERENCIA}}/g, ref); + text = text.replace(/{{ENLACE}}/g, enlace); + + // 6. Enviar + const instanceName = `cliente_${ownerId}`; + const ok = await sendWhatsAppWelcomeText(finalPhone, text, instanceName); + if (ok) console.log(`👋 [Bienvenida] WhatsApp enviado a ${finalPhone} para el expediente #${ref}.`); +} + async function saveServiceToDB(ownerId, provider, ref, data) { console.log(`💾 Guardando/Actualizando ${provider.toUpperCase()} ${ref}...`); - + // Aquí está la solución al problema del tilde ("sí" vs "si") que te comenté antes const isUrgent = (data['Urgente'] && (data['Urgente'].toLowerCase().trim() === 'sí' || data['Urgente'].toLowerCase().trim() === 'si')) ? true : false; - - await pool.query(` + + // 👋 NUEVO: Comprobamos ANTES de guardar si este expediente ya existía en el buzón, + // para saber si hay que disparar el WhatsApp de bienvenida (solo la primera vez que aparece). + let isNewRecord = false; + try { + const preCheck = await pool.query( + "SELECT id FROM scraped_services WHERE owner_id=$1 AND provider=$2 AND service_ref=$3", + [ownerId, provider, ref] + ); + isNewRecord = preCheck.rowCount === 0; + } catch (e) { + console.error(`⚠️ [Bienvenida] No se pudo comprobar si ${ref} es nuevo:`, e.message); + } + + const result = await pool.query(` INSERT INTO scraped_services (owner_id, provider, service_ref, raw_data, status, is_urgent) VALUES ($1, $2, $3, $4, 'pending', $5) - ON CONFLICT (owner_id, provider, service_ref) - DO UPDATE SET + ON CONFLICT (owner_id, provider, service_ref) + DO UPDATE SET -- 🛡️ EL ESCUDO: Fusiona todo, pero si ya había un gremio guardado, lo blinda para que el robot no lo pise - raw_data = (scraped_services.raw_data || EXCLUDED.raw_data) || + raw_data = (scraped_services.raw_data || EXCLUDED.raw_data) || jsonb_build_object('guild_id', COALESCE(scraped_services.raw_data->'guild_id', EXCLUDED.raw_data->'guild_id')), - + is_urgent = EXCLUDED.is_urgent, - status = CASE + status = CASE WHEN scraped_services.status = 'archived' THEN 'archived' WHEN scraped_services.status = 'imported' THEN 'imported' ELSE 'pending' END + RETURNING id `, [ownerId, provider, ref, JSON.stringify(data), isUrgent]); + + // 👋 NUEVO: Si es la primera vez que vemos este expediente, disparamos el WhatsApp de + // bienvenida en segundo plano (no bloquea ni puede romper el guardado de arriba). + if (isNewRecord && result.rows[0]) { + const newSvcId = result.rows[0].id; + console.log(`👋 [BIENVENIDA] Expediente #${ref} acaba de nacer. Disparando WhatsApp...`); + sendWelcomeMessage(ownerId, provider, ref, data, newSvcId).catch(e => + console.error(`❌ [Bienvenida] Error inesperado para #${ref}:`, e.message) + ); + } } main(); \ No newline at end of file