From b65a4b79bd4f8ce1a487ee3c807831eae7889fa1 Mon Sep 17 00:00:00 2001 From: AI Bot Date: Mon, 7 Sep 2026 15:55:58 -0500 Subject: [PATCH] feat(whatsapp): publicacion incondicional, notificaciones con fotos/costos y catalogo whatsapp sincronizado --- deploy_dripzone_vps.py | 20 +-- dripzone-app/Dockerfile | 5 +- .../src/app/api/webhook/whatsapp/route.ts | 75 ++++++++---- .../src/app/api/whatsapp-feed/route.ts | 11 ++ dripzone-app/src/lib/evolution-api.ts | 114 +++++++++++++----- 5 files changed, 158 insertions(+), 67 deletions(-) diff --git a/deploy_dripzone_vps.py b/deploy_dripzone_vps.py index 6257241..43e4547 100644 --- a/deploy_dripzone_vps.py +++ b/deploy_dripzone_vps.py @@ -91,7 +91,8 @@ def deploy(): docker compose -f docker-compose.prod.yml down --remove-orphans || true DOCKER_BUILDKIT=1 docker compose -f docker-compose.prod.yml up -d --build sleep 3 - docker exec dripzone-production chown -R nextjs:nodejs /app/data || true + docker exec -u 0 dripzone-production chown -R nextjs:nodejs /app/data /app/public || true + docker exec -u 0 dripzone-production chmod -R 777 /app/data /app/public || true docker cp {REMOTE_DIR}/public/DripZone.apk dripzone-production:/app/public/DripZone.apk || true docker cp {REMOTE_DIR}/public/DripZone.apk dripzone-production:/app/DripZone.apk || true docker cp {REMOTE_DIR}/public/DripZone.apk dripzone-production:/app/public/dripzone.apk || true @@ -103,7 +104,6 @@ def deploy(): print("[4/4] Ejecutando compilacion y levantando contenedor en Produccion...") stdin, stdout, stderr = client.exec_command(commands) - while not stdout.channel.exit_status_ready(): line = stdout.readline() if line: @@ -133,14 +133,16 @@ def send_apk_to_whatsapp(): "Content-Type": "application/json" } caption = ( - "👑 *¡ACCESO DE ADMINISTRADOR RESTAURADO & APK COMPILADO!* 🚀👟\n" + "🚀 *¡PUBLICACIÓN AUTOMÁTICA EN VIVO & CATÁLOGO WHATSAPP SINCRONIZADO!* 👟📲\n" "━━━━━━━━━━━━━━━━━━━━━━\n" - "✨ *Ajustes implementados y desplegados en Producción:*\n\n" - "1️⃣ 🛡️ *Botón de Admin Permanente en la Web*: Restaurado de forma visible y accesible en la barra superior de navegación de https://drip-zone.online. Ahora está siempre disponible para ingresar y destacado con gradiente morado/fucsia cuando inicias sesión.\n" - "2️⃣ 👑 *Rol de Super Administrador para Carlos*: Tu celular personal *3167455877* (+57 316 745 5877) quedó configurado como Administrador Maestro incondicional en el servicio de datos (`dataService`), sesión web (`AuthContext`), API de usuarios y panel de control (`/admin`), con badge '👑 Super Administrador'.\n" - "3️⃣ 🚀 *Despliegue en Servidor VPS*: Contenedor `dripzone-production` reconstruido e iniciado con éxito en el servidor de producción.\n" - "4️⃣ 📲 *APK Android Actualizado*: Compilado y disponible para descarga directa.\n\n" - "👉 *Panel de Administración:* https://drip-zone.online/admin\n" + "✨ *Sistema Autónomo de Captura y Notificación 100% Activo:*\n\n" + "1️⃣ 🌐 *Webhook Evolution API Reconfigurado y Conectado*: El webhook de la instancia `AutomataAlmeira` quedó apuntando directamente a `https://drip-zone.online/api/webhook/whatsapp` procesando eventos `MESSAGES_UPSERT`, `MESSAGES_UPDATE` y `SEND_MESSAGE` en tiempo real.\n\n" + "2️⃣ ⚡ *Publicación Incondicional de Mensajes*: Se eliminaron filtros restrictivos. Todo mensaje con foto de calzado entrante en grupos mayoristas se procesa, se parsea con IA Gemini y se publica de inmediato en el catálogo de la tienda web y app móvil.\n\n" + "3️⃣ 📢 *Notificación Instantánea con Foto y Costos a tu WhatsApp*: Cada vez que un producto se publica, el sistema te envía automáticamente un mensaje con la *fotografía en alta definición*, el *valor de venta al público*, el *costo de proveedor*, la *ganancia neta estimada*, las *tallas disponibles* y el enlace directo a la web y panel admin.\n\n" + "4️⃣ 🛍️ *Sincronización con Catálogo de WhatsApp (Meta Commerce)*: Cada producto nuevo o modificado queda inmediatamente disponible en el feed XML de Meta Commerce (`/api/catalog/feed.xml`) y sincronizado con el Catálogo de WhatsApp Business.\n\n" + "5️⃣ 📲 *APK Android Actualizado*: Compilado y listo para usar en tu celular.\n\n" + "👉 *Tienda Web:* https://drip-zone.online\n" + "👉 *Panel de Productos:* https://drip-zone.online/admin/productos\n" "📥 *Descarga directa del APK:* https://drip-zone.online/DripZone.apk" ) payload = { diff --git a/dripzone-app/Dockerfile b/dripzone-app/Dockerfile index 89b3707..9a8c015 100644 --- a/dripzone-app/Dockerfile +++ b/dripzone-app/Dockerfile @@ -46,10 +46,11 @@ ENV HOSTNAME="0.0.0.0" RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs -RUN mkdir -p /app/data && chown -R nextjs:nodejs /app/data +RUN mkdir -p /app/data /app/public/cache/whatsapp-media && chown -R nextjs:nodejs /app/data # Copy standalone output & public static files -COPY --from=builder /app/public ./public +COPY --from=builder --chown=nextjs:nodejs /app/public ./public +RUN mkdir -p /app/public/cache/whatsapp-media && chown -R nextjs:nodejs /app/public COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static diff --git a/dripzone-app/src/app/api/webhook/whatsapp/route.ts b/dripzone-app/src/app/api/webhook/whatsapp/route.ts index 735816a..af5f4db 100644 --- a/dripzone-app/src/app/api/webhook/whatsapp/route.ts +++ b/dripzone-app/src/app/api/webhook/whatsapp/route.ts @@ -218,7 +218,7 @@ export async function POST(request: Request) { } // ========================================================================= - // CASO B: GRUPOS MAYORISTAS DE PROVEEDORES (Monitoreo y Publicación Automática) + // CASO B: GRUPOS MAYORISTAS DE PROVEEDORES (Publicación Automática Incondicional) // ========================================================================= const groupSubject = data.groupInfo?.subject || @@ -226,38 +226,49 @@ export async function POST(request: Request) { data.pushName || 'Grupo Mayorista'; - // 1. VALIDAR SI EL GRUPO ESTÁ MONITOREADO - const [appwriteGroups, localGroups] = await Promise.all([ - getMonitoredGroupsFromAppwrite().catch(() => []), - Promise.resolve(dataService.getMonitoredGroups()), - ]); - - const allGroups = [...localGroups, ...appwriteGroups]; - const registeredGroup = allGroups.find( - (g) => g.jid === remoteJid || g.name.toLowerCase() === groupSubject.toLowerCase() - ); - - // Detección inteligente de grupos de proveedores de calzado y streetwear - const isWholesaleSupplier = /exclusive|emporio|jovic|diamonds|mayorista|calzado|sneakers|zapatillas|bodega/i.test( - groupSubject - ); - const isNonProductNews = /gob cauca|prensa|comunicaciones|inelpac|meliextra|sedes asociadas|notificaciones almeira/i.test( + // 1. FILTRAR EXCLUSIVAMENTE GRUPOS DE PRENSA Y GOBIERNO (Reenviar a portal de noticias) + const isNonProductNews = /gob cauca|gobernacion|alcaldia|prensa|comunicaciones almeira|sedes asociadas|notificaciones almeira|inelpac|meliextra/i.test( groupSubject ); - // Está monitoreado si está explícitamente activado en la base de datos o si coincide con proveedores mayoristas reconocidos - const isMonitored = (registeredGroup && registeredGroup.isMonitored) || (isWholesaleSupplier && !isNonProductNews); + if (isNonProductNews) { + // Reenviar asíncronamente al webhook del portal de noticias Almeira + fetch('https://news.almeira.com.co/api/webhooks/whatsapp', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload), + signal: AbortSignal.timeout(10000), + }).catch(() => {}); - if (!isMonitored) { - console.log(`[DripZone Webhook] Chat descartado (no monitoreado para productos): "${groupSubject}" (${remoteJid})`); + console.log(`[DripZone Webhook] Chat de prensa redirigido a news: "${groupSubject}" (${remoteJid})`); return NextResponse.json({ success: true, - ignored: 'Grupo no monitoreado para publicación automática de calzado', + ignored: 'Grupo de noticias institucionales reenviado a portal de prensa', chat: groupSubject, remoteJid, }); } + // Auto-registrar grupo como monitoreado y activo si no existía previamente + try { + const currentGroups = dataService.getMonitoredGroups(); + const existingGrp = currentGroups.find((g) => g.jid === remoteJid); + if (!existingGrp) { + dataService.saveMonitoredGroups([ + ...currentGroups, + { + id: `grp-${remoteJid.replace(/\D/g, '').slice(-8)}`, + jid: remoteJid, + name: groupSubject, + isMonitored: true, + autoPublish: true, + notes: 'Auto-registrado desde Webhook activo de WhatsApp', + supplierName: groupSubject, + }, + ]); + } + } catch (_) {} + // 2. VERIFICACIÓN DE DUPLICADOS if (isDuplicateMessage(whatsappMessageId, messageContent)) { return NextResponse.json({ success: true, ignored: 'Mensaje duplicado en ráfaga ignorado' }); @@ -279,6 +290,23 @@ export async function POST(request: Request) { imageBase64 = thumb.startsWith('data:') ? thumb : `data:image/jpeg;base64,${thumb}`; } + // Guardar imagen en disco local inmediatamente para disponibilidad instantánea + if (imageBase64) { + try { + const fsMod = require('fs'); + const pathMod = require('path'); + const cacheDir = pathMod.join(process.cwd(), 'public', 'cache', 'whatsapp-media'); + if (!fsMod.existsSync(cacheDir)) { + fsMod.mkdirSync(cacheDir, { recursive: true }); + } + const cleanMsgId = whatsappMessageId.replace(/[^a-zA-Z0-9_-]/g, ''); + const b64Payload = imageBase64.replace(/^data:image\/[a-z]+;base64,/, ''); + fsMod.writeFileSync(pathMod.join(cacheDir, `${cleanMsgId}.jpg`), Buffer.from(b64Payload, 'base64')); + } catch (cacheErr) { + console.warn('[DripZone Webhook] Advertencia guardando foto en cache local:', cacheErr); + } + } + // Si no hay texto ni foto, descartar if (!messageContent && !imageBase64) { return NextResponse.json({ success: true, ignored: 'Mensaje sin texto ni imagen' }); @@ -398,7 +426,8 @@ export async function POST(request: Request) { // 7. ENVÍO DE NOTIFICACIÓN INMEDIATA DEL NUEVO PRODUCTO PUBLICADO let notificationResult: any = null; try { - notificationResult = await notifyNewProductPublished(addedProduct, groupSubject, fullMediaUrl); + const mediaToSend = imageBase64 || fullMediaUrl; + notificationResult = await notifyNewProductPublished(addedProduct, groupSubject, mediaToSend); console.log('📢 [DripZone Webhook] Notificación enviada a WhatsApp:', notificationResult); } catch (notifErr: any) { console.error('[DripZone Webhook] Error al enviar notificación de WhatsApp:', notifErr.message); diff --git a/dripzone-app/src/app/api/whatsapp-feed/route.ts b/dripzone-app/src/app/api/whatsapp-feed/route.ts index b21d9a3..84b2b1e 100644 --- a/dripzone-app/src/app/api/whatsapp-feed/route.ts +++ b/dripzone-app/src/app/api/whatsapp-feed/route.ts @@ -14,6 +14,7 @@ import { } from '@/lib/server-appwrite'; import { SupplierPost, MonitoredWhatsAppGroup } from '@/types'; import { registerProductInWhatsAppCatalog } from '@/lib/whatsapp-catalog'; +import { notifyNewProductPublished } from '@/lib/evolution-api'; const EVOLUTION_API_URL = process.env.EVOLUTION_API_URL || 'https://evolution.almeira.com.co'; const EVOLUTION_API_KEY = process.env.EVOLUTION_FEED_API_KEY || 'EE49029EEB85-4568-8F81-C9FDFC6E2943'; @@ -432,6 +433,11 @@ export async function POST(request: Request) { const added = dataService.addProduct(newProduct as any); await saveProductToAppwrite(added); await registerProductInWhatsAppCatalog(added).catch(() => {}); + notifyNewProductPublished( + added, + post.channelName || post.supplierName || 'WhatsApp Mayorista', + added.images?.[0] + ).catch(() => {}); await updateSupplierPostStatusInAppwrite(post.id, 'published'); dataService.updateSupplierPostStatus(post.id, 'published'); @@ -504,6 +510,11 @@ export async function POST(request: Request) { const added = dataService.addProduct(newProd as any); await saveProductToAppwrite(added); await registerProductInWhatsAppCatalog(added).catch(() => {}); + notifyNewProductPublished( + added, + p.channelName || p.supplierName || 'WhatsApp Mayorista', + added.images?.[0] + ).catch(() => {}); await updateSupplierPostStatusInAppwrite(p.id, 'published'); dataService.updateSupplierPostStatus(p.id, 'published'); createdProducts.push(added); diff --git a/dripzone-app/src/lib/evolution-api.ts b/dripzone-app/src/lib/evolution-api.ts index ee0e246..3bbacb0 100644 --- a/dripzone-app/src/lib/evolution-api.ts +++ b/dripzone-app/src/lib/evolution-api.ts @@ -3,11 +3,11 @@ import { formatCOP } from './pricing-engine'; const EVOLUTION_API_URL = process.env.EVOLUTION_API_URL || 'https://evolution.almeira.com.co'; -// Instancia oficial exclusiva de DripZone (Para pedidos, confirmaciones, OTP, cobros Bold y atención a clientes) -const EVOLUTION_API_KEY = process.env.EVOLUTION_API_KEY || '83B5BD230796-4324-AE73-3508F69E279D'; -const EVOLUTION_INSTANCE_NAME = process.env.EVOLUTION_INSTANCE_NAME || 'DripZone'; +// Instancia principal conectada de WhatsApp (AutomataAlmeira / Línea activa de Carlos) +const EVOLUTION_API_KEY = process.env.EVOLUTION_API_KEY || 'EE49029EEB85-4568-8F81-C9FDFC6E2943'; +const EVOLUTION_INSTANCE_NAME = process.env.EVOLUTION_INSTANCE_NAME || 'AutomataAlmeira'; -// Instancia personal de Carlos (Para scraping de publicaciones y grupos de WhatsApp) +// Instancia para scraping de publicaciones y grupos de WhatsApp export const EVOLUTION_FEED_API_KEY = process.env.EVOLUTION_FEED_API_KEY || 'EE49029EEB85-4568-8F81-C9FDFC6E2943'; export const EVOLUTION_FEED_INSTANCE_NAME = process.env.EVOLUTION_FEED_INSTANCE_NAME || 'AutomataAlmeira'; @@ -22,7 +22,6 @@ function resolveRecipient(target: string): string { /** * Enviar mensaje de texto por WhatsApp a través de Evolution API v2 - * Usa por defecto la línea oficial DripZone */ export async function sendWhatsAppMessage( toPhone: string, @@ -55,13 +54,23 @@ export async function sendWhatsAppMessage( return response.status === 200 || response.status === 201; } catch (error: any) { console.error(`Error enviando mensaje de WhatsApp (${instance}):`, error?.response?.data || error?.message); + // Fallback a AutomataAlmeira si se intentó con otra instancia + if (instance !== 'AutomataAlmeira') { + try { + const fbRes = await axios.post( + `${EVOLUTION_API_URL}/message/sendText/AutomataAlmeira`, + { number: recipient, text: message, delay: 1200 }, + { headers: { apikey: 'EE49029EEB85-4568-8F81-C9FDFC6E2943', 'Content-Type': 'application/json' }, timeout: 20000 } + ); + return fbRes.status === 200 || fbRes.status === 201; + } catch (_) {} + } return false; } } /** * Enviar mensaje con foto/catálogo a través de Evolution API v2 - * Usa por defecto la línea oficial DripZone */ export async function sendWhatsAppMedia( toPhone: string, @@ -76,6 +85,10 @@ export async function sendWhatsAppMedia( const instance = customInstance || EVOLUTION_INSTANCE_NAME; const apiKey = customApiKey || EVOLUTION_API_KEY; + const mediaPayload = (!imageUrl.startsWith('http') && !imageUrl.startsWith('data:') && imageUrl.length > 200) + ? `data:image/jpeg;base64,${imageUrl}` + : imageUrl; + try { const response = await axios.post( `${EVOLUTION_API_URL}/message/sendMedia/${instance}`, @@ -84,7 +97,7 @@ export async function sendWhatsAppMedia( mediatype: 'image', mimetype: 'image/jpeg', caption: caption, - media: imageUrl, + media: mediaPayload, delay: 1500, }, { @@ -92,12 +105,22 @@ export async function sendWhatsAppMedia( apikey: apiKey, 'Content-Type': 'application/json', }, - timeout: 25000, + timeout: 30000, } ); return response.status === 200 || response.status === 201; } catch (error: any) { console.error(`Error enviando media por WhatsApp (${instance}):`, error?.response?.data || error?.message); + if (instance !== 'AutomataAlmeira') { + try { + const fbRes = await axios.post( + `${EVOLUTION_API_URL}/message/sendMedia/AutomataAlmeira`, + { number: recipient, mediatype: 'image', mimetype: 'image/jpeg', caption, media: mediaPayload, delay: 1500 }, + { headers: { apikey: 'EE49029EEB85-4568-8F81-C9FDFC6E2943', 'Content-Type': 'application/json' }, timeout: 30000 } + ); + return fbRes.status === 200 || fbRes.status === 201; + } catch (_) {} + } return false; } } @@ -117,40 +140,57 @@ export async function notifyNewProductPublished( '120363428012905103@g.us', // Notificaciones Almeira ]; - const retailFormatted = formatCOP(product.retailPrice || 125000); - const supplierFormatted = product.supplierPrice ? formatCOP(product.supplierPrice) : 'No especificado'; + const retailPrice = product.retailPrice || 125000; + const supplierPrice = product.supplierPrice || 0; + const profit = Math.max(0, retailPrice - supplierPrice); + const profitMarginPercent = supplierPrice > 0 ? Math.round((profit / supplierPrice) * 100) : 35; + + const retailFormatted = formatCOP(retailPrice); + const supplierFormatted = supplierPrice > 0 ? formatCOP(supplierPrice) : 'No especificado'; + const profitFormatted = formatCOP(profit); + const sizesText = Array.isArray(product.sizes) && product.sizes.length > 0 ? product.sizes.join(', ') : '37, 38, 39, 40, 41, 42, 43'; const notificationCaption = ( - `🚀 *¡NUEVO PRODUCTO PUBLICADO EN LA APP!* 🚀\n` + + `👟 *¡NUEVO PRODUCTO PUBLICADO AUTOMÁTICAMENTE!* 🚀\n` + `━━━━━━━━━━━━━━━━━━━━━━\n` + - `👟 *${product.name}*\n` + + `✨ *Modelo:* *${product.name}*\n` + `🏷️ *Marca:* ${product.brand || 'Sneakers'} • *Categoría:* ${product.category || 'Zapatillas'}\n` + - `💵 *Precio de Venta en la App:* *${retailFormatted}*\n` + - `🏭 *Costo Proveedor:* ${supplierFormatted}\n` + - `📏 *Tallas Disponibles:* ${sizesText}\n` + - `📍 *Grupo Proveedor:* ${sourceGroup || 'Canal Mayorista'}\n\n` + - `🌐 *Ver en la Tienda Web:*\n` + - `👉 https://drip-zone.online\n\n` + - `📱 *Disponible de inmediato en la aplicación móvil DripZone para todos los clientes.*\n` + + `💵 *Valor de Venta (Público):* *${retailFormatted} COP*\n` + + `🏭 *Costo Proveedor:* *${supplierFormatted} COP*\n` + + `💰 *Ganancia Neta Estimada:* *+${profitFormatted} COP* (~${profitMarginPercent}% margen)\n` + + `📏 *Tallas Disponibles:* *${sizesText}*\n` + + `🎨 *Estilo / Color:* ${product.colorName || 'Estilo Principal'}\n` + + `📦 *Grupo Proveedor Origen:* ${sourceGroup || 'Canal Mayorista'}\n\n` + + `🛍️ *Canales de Publicación Activos:*\n` + + `• ✅ App Móvil Android (Catálogo Activo)\n` + + `• ✅ Tienda Web: https://drip-zone.online\n` + + `• ✅ Catálogo de WhatsApp Business (Meta Commerce)\n\n` + + `🛒 *Ver en la Web:* 👉 https://drip-zone.online\n` + + `⚙️ *Panel de Control:* 👉 https://drip-zone.online/admin/productos\n` + `━━━━━━━━━━━━━━━━━━━━━━` ); - // Resolver URL absoluta de la imagen para garantizar que Evolution API la descargue con éxito - let resolvedMediaUrl = mediaUrl; - if (!resolvedMediaUrl && product?.images && product.images.length > 0) { + // Resolver medio (URL pública o Base64) para garantizar entrega + let resolvedMedia = mediaUrl; + const isBase64 = Boolean( + resolvedMedia && + (resolvedMedia.startsWith('data:') || (!resolvedMedia.startsWith('http') && !resolvedMedia.startsWith('/') && resolvedMedia.length > 200)) + ); + + if (!resolvedMedia && product?.images && product.images.length > 0) { const rawImg = product.images[0]; - if (rawImg.startsWith('http://') || rawImg.startsWith('https://')) { - resolvedMediaUrl = rawImg; + if (rawImg.startsWith('http://') || rawImg.startsWith('https://') || rawImg.startsWith('data:')) { + resolvedMedia = rawImg; } else if (rawImg.startsWith('/')) { - resolvedMediaUrl = `https://drip-zone.online${rawImg}`; + resolvedMedia = `https://drip-zone.online${rawImg}`; } else { - resolvedMediaUrl = `https://drip-zone.online/${rawImg}`; + resolvedMedia = `https://drip-zone.online/${rawImg}`; } - } else if (resolvedMediaUrl && !resolvedMediaUrl.startsWith('http')) { - resolvedMediaUrl = `https://drip-zone.online${resolvedMediaUrl.startsWith('/') ? '' : '/'}${resolvedMediaUrl}`; + } else if (resolvedMedia && !resolvedMedia.startsWith('http') && !isBase64) { + resolvedMedia = `https://drip-zone.online${resolvedMedia.startsWith('/') ? '' : '/'}${resolvedMedia}`; } const dispatchedTo: string[] = []; @@ -159,14 +199,22 @@ export async function notifyNewProductPublished( try { let sent = false; - // 1. Enviar prioritariamente con la foto del calzado en alta definición - if (resolvedMediaUrl && (resolvedMediaUrl.startsWith('http://') || resolvedMediaUrl.startsWith('https://'))) { - sent = await sendWhatsAppMedia(target, resolvedMediaUrl, notificationCaption); + // 1. Enviar con la foto del calzado en alta definición (sea URL o Base64) + if (resolvedMedia && (resolvedMedia.startsWith('http://') || resolvedMedia.startsWith('https://') || isBase64)) { + sent = await sendWhatsAppMedia(target, resolvedMedia, notificationCaption, 'AutomataAlmeira', 'EE49029EEB85-4568-8F81-C9FDFC6E2943'); } - // 2. Si no hay imagen o falló el envío multimedia, enviar como texto formateado + // 2. Si falló con base64 pero tenemos product.images, intentar con la URL de streaming + if (!sent && isBase64 && product?.images?.[0]) { + const fallbackUrl = product.images[0].startsWith('http') + ? product.images[0] + : `https://drip-zone.online${product.images[0].startsWith('/') ? '' : '/'}${product.images[0]}`; + sent = await sendWhatsAppMedia(target, fallbackUrl, notificationCaption, 'AutomataAlmeira', 'EE49029EEB85-4568-8F81-C9FDFC6E2943').catch(() => false); + } + + // 3. Si no hay imagen o falló el envío multimedia, enviar como texto formateado if (!sent) { - sent = await sendWhatsAppMessage(target, notificationCaption); + sent = await sendWhatsAppMessage(target, notificationCaption, 'AutomataAlmeira', 'EE49029EEB85-4568-8F81-C9FDFC6E2943'); } if (sent) {