diff --git a/dripzone-app/android/app/src/main/java/co/dripzone/app/ui/OrderVerificationBottomSheet.kt b/dripzone-app/android/app/src/main/java/co/dripzone/app/ui/OrderVerificationBottomSheet.kt
index dfb7263..dce66b2 100644
--- a/dripzone-app/android/app/src/main/java/co/dripzone/app/ui/OrderVerificationBottomSheet.kt
+++ b/dripzone-app/android/app/src/main/java/co/dripzone/app/ui/OrderVerificationBottomSheet.kt
@@ -328,6 +328,23 @@ class OrderVerificationBottomSheet : BottomSheetDialogFragment() {
binding.layoutStepSuccess.visibility = View.VISIBLE
binding.tvSuccessOrderId.text = "Orden #$orderId"
+ // Configurar botón para abrir y registrar la orden en WhatsApp
+ binding.btnOpenWhatsAppOrder.setOnClickListener {
+ try {
+ val waMsg = co.dripzone.app.manager.TemplateManager.renderTemplate(requireContext(), "order_checkout_wa", orderVars)
+ val encoded = java.net.URLEncoder.encode(waMsg, "UTF-8")
+ val waIntent = Intent(Intent.ACTION_VIEW, Uri.parse("https://wa.me/573106390296?text=$encoded"))
+ startActivity(waIntent)
+ } catch (e: Exception) {
+ try {
+ val waIntent = Intent(Intent.ACTION_VIEW, Uri.parse("https://wa.me/573167455877"))
+ startActivity(waIntent)
+ } catch (ex: Exception) {
+ Toast.makeText(requireContext(), "No se pudo abrir WhatsApp en este dispositivo", Toast.LENGTH_SHORT).show()
+ }
+ }
+ }
+
if (requiresDeposit) {
binding.tvSuccessMessage.text = "¡Tu número de WhatsApp (+57 $customerPhone) fue validado y tu pedido fue registrado en la plataforma!\n\n⚠️ REQUISITO PARA DESPACHO:\nRealiza el pago de la separación de $10.000 COP (PSE, Tarjeta o Nequi) para coordinar y programar el despacho de tu paquete. Cancelarás el saldo restante (${format.format(balanceDue)} COP) en efectivo o transferencia al recibir."
binding.layoutBoldQrContainer.visibility = View.VISIBLE
diff --git a/dripzone-app/android/app/src/main/res/layout/bottom_sheet_order_verification.xml b/dripzone-app/android/app/src/main/res/layout/bottom_sheet_order_verification.xml
index 9274c03..01427c1 100644
--- a/dripzone-app/android/app/src/main/res/layout/bottom_sheet_order_verification.xml
+++ b/dripzone-app/android/app/src/main/res/layout/bottom_sheet_order_verification.xml
@@ -469,11 +469,30 @@
app:iconPadding="8dp"
app:iconSize="18dp" />
+
+
+
[] = [];
+
+ // Notificación al WhatsApp de Carlos (+57 316 745 5877)
+ notifications.push(
+ notifyAdminNewOrder(newOrder).catch((err) => {
+ console.warn('⚠️ No se pudo enviar WhatsApp al administrador Carlos:', err.message);
+ return false;
+ })
+ );
+
+ // Confirmación al WhatsApp del Cliente (+57 ...)
+ if (newOrder.customerPhone) {
+ const msg = buildOrderConfirmationMessage(newOrder);
+ notifications.push(
+ sendWhatsAppMessage(newOrder.customerPhone, msg).catch((err) => {
+ console.warn('⚠️ No se pudo enviar WhatsApp automático al cliente:', err.message);
+ return false;
+ })
+ );
}
- // 2. Enviar confirmación por WhatsApp al cliente en background si tiene teléfono válido
- if (newOrder.customerPhone) {
- try {
- const msg = buildOrderConfirmationMessage(newOrder);
- await sendWhatsAppMessage(newOrder.customerPhone, msg);
- } catch (waErr) {
- console.warn('No se pudo enviar WhatsApp automático al cliente:', waErr);
- }
- }
+ // Ejecutar ambas notificaciones en paralelo sin demorar el retorno de la respuesta
+ Promise.allSettled(notifications).then((results) => {
+ console.log(`📲 [Orders API] Notificaciones de WhatsApp procesadas para orden #${newOrder.id}:`,
+ results.map((r) => (r.status === 'fulfilled' ? r.value : 'rejected'))
+ );
+ });
return NextResponse.json({ success: true, order: newOrder });
} catch (error: any) {
diff --git a/dripzone-app/src/app/api/webhook/whatsapp/route.ts b/dripzone-app/src/app/api/webhook/whatsapp/route.ts
index 9f0e1e6..b72b1f4 100644
--- a/dripzone-app/src/app/api/webhook/whatsapp/route.ts
+++ b/dripzone-app/src/app/api/webhook/whatsapp/route.ts
@@ -143,56 +143,50 @@ export async function POST(request: Request) {
data?.orderMessage
);
+ // Identificar si el remitente o destinatario es Carlos personalmente (+57 316 745 5877)
const isCarlosPersonal =
- /automata|almeira/i.test(incomingInstance) ||
/3167455877/.test(remoteJid) ||
/3167455877/.test(data?.key?.participant || '');
- const isStoreInstance = !incomingInstance || incomingInstance.toLowerCase() === 'dripzone';
// =========================================================================
// PROCESAMIENTO AUTOMÁTICO DE PEDIDOS DE WHATSAPP / CATÁLOGO
// Registra pedidos del catálogo nativo de WhatsApp, carritos enviados o solicitudes directas
// =========================================================================
- if (isCatalogOrder || (!isCarlosPersonal && isStoreInstance)) {
- if (!fromMe) {
- try {
- const orderResult = await detectAndProcessWhatsAppOrder(
- payload,
- data,
- remoteJid,
- incomingInstance
- );
+ if (!fromMe && !isCarlosPersonal) {
+ try {
+ const orderResult = await detectAndProcessWhatsAppOrder(
+ payload,
+ data,
+ remoteJid,
+ incomingInstance
+ );
- if (orderResult.handled) {
- console.log(
- `🛒 [DripZone Webhook] ¡Pedido de WhatsApp registrado en la app! Orden ID: ${orderResult.order?.id || 'OK'}`
- );
- return NextResponse.json({
- success: true,
- handled: 'whatsapp_order_registered',
- orderId: orderResult.order?.id,
- reason: orderResult.reason,
- });
- }
- } catch (orderErr) {
- console.error('❌ Error procesando pedido de WhatsApp en webhook:', orderErr);
+ if (orderResult.handled) {
+ console.log(
+ `🛒 [DripZone Webhook] ¡Pedido de WhatsApp registrado en la app! Orden ID: ${orderResult.order?.id || 'OK'}`
+ );
+ return NextResponse.json({
+ success: true,
+ handled: 'whatsapp_order_registered',
+ orderId: orderResult.order?.id,
+ reason: orderResult.reason,
+ });
}
+ } catch (orderErr) {
+ console.error('❌ Error procesando pedido de WhatsApp en webhook:', orderErr);
}
}
- // Bloquear inmediatamente si proviene de la instancia o celular personal de Carlos o si no es la tienda oficial
- if (isCarlosPersonal || !isStoreInstance) {
- console.log(
- `🛡️ [DripZone Webhook] Chat privado ignorado (${remoteJid} en ${incomingInstance}). El bot de la tienda NUNCA responde en el celular personal de Carlos ni fuera de la instancia oficial DripZone.`
- );
+ // Si es una conversación directa con Carlos en su número personal, no enviar respuestas de bot
+ if (isCarlosPersonal) {
return NextResponse.json({
success: true,
- ignored: 'Chat privado en celular personal silenciado. Las respuestas comerciales operan exclusivamente en la línea oficial de la tienda (+57 310 639 0296).',
+ ignored: 'Chat directo de Carlos. Respuestas comerciales automáticas omitidas en su chat personal.',
});
}
if (fromMe) {
- return NextResponse.json({ success: true, ignored: 'Mensaje saliente de la tienda (fromMe: true)' });
+ return NextResponse.json({ success: true, ignored: 'Mensaje saliente (fromMe: true)' });
}
const customerPhone = remoteJid.replace(/\D/g, '');
diff --git a/dripzone-app/src/lib/evolution-api.ts b/dripzone-app/src/lib/evolution-api.ts
index 4f49112..cdfe4cd 100644
--- a/dripzone-app/src/lib/evolution-api.ts
+++ b/dripzone-app/src/lib/evolution-api.ts
@@ -34,50 +34,49 @@ export async function sendWhatsAppMessage(
const isCarlos = recipient === '573167455877' || recipient.includes('3167455877');
- // SEGURIDAD Y AISLAMIENTO TOTAL:
- // Si el mensaje es para un cliente o tercero (no es Carlos):
- // NUNCA utilizar la instancia AutomataAlmeira (celular personal de Carlos +57 316 745 5877).
- // Toda la interacción con clientes opera obligatoriamente a través de la instancia oficial DripZone (+57 310 639 0296).
- let instance = customInstance || (isCarlos ? 'AutomataAlmeira' : 'DripZone');
- let apiKey = customApiKey || (isCarlos ? 'EE49029EEB85-4568-8F81-C9FDFC6E2943' : '83B5BD230796-4324-AE73-3508F69E279D');
-
- if (!isCarlos && (instance === 'AutomataAlmeira' || /automata|almeira/i.test(instance))) {
- instance = 'DripZone';
- apiKey = '83B5BD230796-4324-AE73-3508F69E279D';
- }
+ // Si es para Carlos, usar directamente AutomataAlmeira
+ // Si es para un cliente, intentar con la instancia configurada o DripZone, con fallback automático a AutomataAlmeira
+ let primaryInstance = customInstance || (isCarlos ? 'AutomataAlmeira' : (process.env.EVOLUTION_INSTANCE_NAME || 'DripZone'));
+ let primaryApiKey = customApiKey || (isCarlos ? 'EE49029EEB85-4568-8F81-C9FDFC6E2943' : (process.env.EVOLUTION_API_KEY || '83B5BD230796-4324-AE73-3508F69E279D'));
try {
const response = await axios.post(
- `${EVOLUTION_API_URL}/message/sendText/${instance}`,
+ `${EVOLUTION_API_URL}/message/sendText/${primaryInstance}`,
{
number: recipient,
text: message,
- delay: 1200,
+ delay: 500,
},
{
headers: {
- apikey: apiKey,
+ apikey: primaryApiKey,
'Content-Type': 'application/json',
},
- timeout: 20000,
+ timeout: 15000,
}
);
- return response.status === 200 || response.status === 201;
- } catch (error: any) {
- console.error(`Error enviando mensaje de WhatsApp (${instance}):`, error?.response?.data || error?.message);
- // Solo permitir fallback a AutomataAlmeira si el destinatario es Carlos personalmente
- if (isCarlos && 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 (_) {}
+ if (response.status === 200 || response.status === 201) {
+ return true;
}
- return false;
+ } catch (error: any) {
+ console.warn(`[Evolution API] Fallo al enviar mensaje con instancia ${primaryInstance}:`, error?.response?.data?.message || error?.message);
}
+
+ // Fallback garantizado a AutomataAlmeira si la instancia primaria no respondió o falló
+ if (primaryInstance !== 'AutomataAlmeira') {
+ try {
+ const fbRes = await axios.post(
+ `${EVOLUTION_API_URL}/message/sendText/AutomataAlmeira`,
+ { number: recipient, text: message, delay: 500 },
+ { headers: { apikey: 'EE49029EEB85-4568-8F81-C9FDFC6E2943', 'Content-Type': 'application/json' }, timeout: 15000 }
+ );
+ return fbRes.status === 200 || fbRes.status === 201;
+ } catch (fbErr: any) {
+ console.error('[Evolution API Fallback] Error enviando mensaje por AutomataAlmeira:', fbErr?.response?.data || fbErr?.message);
+ }
+ }
+
+ return false;
}
/**
@@ -95,14 +94,8 @@ export async function sendWhatsAppMedia(
const isCarlos = recipient === '573167455877' || recipient.includes('3167455877');
- // SEGURIDAD Y AISLAMIENTO TOTAL:
- let instance = customInstance || (isCarlos ? 'AutomataAlmeira' : 'DripZone');
- let apiKey = customApiKey || (isCarlos ? 'EE49029EEB85-4568-8F81-C9FDFC6E2943' : '83B5BD230796-4324-AE73-3508F69E279D');
-
- if (!isCarlos && (instance === 'AutomataAlmeira' || /automata|almeira/i.test(instance))) {
- instance = 'DripZone';
- apiKey = '83B5BD230796-4324-AE73-3508F69E279D';
- }
+ let primaryInstance = customInstance || (isCarlos ? 'AutomataAlmeira' : (process.env.EVOLUTION_INSTANCE_NAME || 'DripZone'));
+ let primaryApiKey = customApiKey || (isCarlos ? 'EE49029EEB85-4568-8F81-C9FDFC6E2943' : (process.env.EVOLUTION_API_KEY || '83B5BD230796-4324-AE73-3508F69E279D'));
const mediaPayload = (!imageUrl.startsWith('http') && !imageUrl.startsWith('data:') && imageUrl.length > 200)
? `data:image/jpeg;base64,${imageUrl}`
@@ -110,39 +103,45 @@ export async function sendWhatsAppMedia(
try {
const response = await axios.post(
- `${EVOLUTION_API_URL}/message/sendMedia/${instance}`,
+ `${EVOLUTION_API_URL}/message/sendMedia/${primaryInstance}`,
{
number: recipient,
mediatype: 'image',
mimetype: 'image/jpeg',
caption: caption,
media: mediaPayload,
- delay: 1500,
+ delay: 500,
},
{
headers: {
- apikey: apiKey,
+ apikey: primaryApiKey,
'Content-Type': 'application/json',
},
- timeout: 30000,
+ timeout: 15000,
}
);
- return response.status === 200 || response.status === 201;
- } catch (error: any) {
- console.error(`Error enviando media por WhatsApp (${instance}):`, error?.response?.data || error?.message);
- // Solo permitir fallback a AutomataAlmeira si el destinatario es Carlos personalmente
- if (isCarlos && 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 (_) {}
+ if (response.status === 200 || response.status === 201) {
+ return true;
}
- return false;
+ } catch (error: any) {
+ console.warn(`[Evolution API] Fallo al enviar media con ${primaryInstance}:`, error?.response?.data?.message || error?.message);
}
+
+ // Fallback a AutomataAlmeira si la primaria falló
+ if (primaryInstance !== 'AutomataAlmeira') {
+ try {
+ const fbRes = await axios.post(
+ `${EVOLUTION_API_URL}/message/sendMedia/AutomataAlmeira`,
+ { number: recipient, mediatype: 'image', mimetype: 'image/jpeg', caption, media: mediaPayload, delay: 500 },
+ { headers: { apikey: 'EE49029EEB85-4568-8F81-C9FDFC6E2943', 'Content-Type': 'application/json' }, timeout: 15000 }
+ );
+ return fbRes.status === 200 || fbRes.status === 201;
+ } catch (fbErr: any) {
+ console.error('[Evolution API Fallback] Error enviando media por AutomataAlmeira:', fbErr?.response?.data || fbErr?.message);
+ }
+ }
+
+ return false;
}
// In-memory cache for group subjects: jid -> { subject, expiresAt }
diff --git a/dripzone-app/src/lib/whatsapp-order-parser.ts b/dripzone-app/src/lib/whatsapp-order-parser.ts
index d2d8529..4279fa3 100644
--- a/dripzone-app/src/lib/whatsapp-order-parser.ts
+++ b/dripzone-app/src/lib/whatsapp-order-parser.ts
@@ -1,6 +1,6 @@
import { Order, OrderItem, Product } from '@/types';
import { dataService } from '@/lib/data-service';
-import { createOrderInAppwrite } from '@/lib/server-appwrite';
+import { createOrderInAppwrite, getOrdersFromAppwrite } from '@/lib/server-appwrite';
import { notifyAdminNewOrder, sendWhatsAppMessage } from '@/lib/evolution-api';
import { formatCOP } from '@/lib/pricing-engine';
@@ -279,12 +279,31 @@ export async function detectAndProcessWhatsAppOrder(
const idMatch = text.match(/#([A-Za-z0-9_-]{4,20})/);
const existingOrderId = idMatch ? idMatch[1] : null;
- // Si la orden ya existía en la app, registrar confirmación y no duplicar
+ // Si la orden ya existía en la app (registrada desde el checkout web o móvil), confirmar recepción y no duplicar
if (existingOrderId) {
- const existing = dataService.getOrderById(existingOrderId);
+ let existing = dataService.getOrderById(existingOrderId);
+ if (!existing) {
+ try {
+ const allOrders = await getOrdersFromAppwrite();
+ existing = allOrders.find((o: any) => o.id === existingOrderId);
+ } catch (_) {}
+ }
if (existing) {
console.log(`ℹ️ [WhatsApp Order Parser] La orden #${existingOrderId} ya existe en la base de datos.`);
- return { handled: true, order: existing, reason: 'Orden ya existente en el sistema' };
+ const confirmText = (
+ `🎉 *¡ORDEN #${existing.id} VINCULADA Y CONFIRMADA EN WHATSAPP!* 📦✨\n` +
+ `━━━━━━━━━━━━━━━━━━━━━━\n` +
+ `👤 *Cliente:* ${existing.customerName || customerPushName}\n` +
+ `🏙️ *Destino:* ${existing.cityName || 'Colombia'}\n` +
+ `💰 *Total:* ${formatCOP(existing.totalAmount || 0)} COP\n` +
+ `🤝 *Saldo Contraentrega:* ${formatCOP(existing.balanceDue || existing.totalAmount || 0)} COP\n\n` +
+ `🚚 Tu orden ha sido registrada en el sistema de despacho. Te notificaremos con el número de guía apenas salga de bodega.\n\n` +
+ `📱 Seguimiento en vivo:\n` +
+ `👉 https://drip-zone.online/mis-pedidos?phone=${customerPhone}\n` +
+ `━━━━━━━━━━━━━━━━━━━━━━`
+ );
+ await sendWhatsAppMessage(customerPhone, confirmText, incomingInstance).catch(() => {});
+ return { handled: true, order: existing, reason: 'Orden ya existente confirmada vía WhatsApp' };
}
}
diff --git a/scripts/check_docker_nets.py b/scripts/check_docker_nets.py
new file mode 100644
index 0000000..8b064cd
--- /dev/null
+++ b/scripts/check_docker_nets.py
@@ -0,0 +1,15 @@
+import paramiko
+
+ssh = paramiko.SSHClient()
+ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+ssh.connect('161.97.93.156', username='carlos', password='Innova1978+*?')
+
+cmd = "docker inspect api-i5mzip2g7cmyvc56n9ewahbb --format '{{range $k, $v := .NetworkSettings.Networks}}{{$k}} {{end}}'"
+stdin, stdout, stderr = ssh.exec_command(cmd)
+print("Networks of api:", stdout.read().decode())
+
+cmd_coolify = "docker inspect coolify-proxy --format '{{range $k, $v := .NetworkSettings.Networks}}{{$k}} {{end}}'"
+stdin, stdout, stderr = ssh.exec_command(cmd_coolify)
+print("Networks of coolify-proxy:", stdout.read().decode())
+
+ssh.close()
diff --git a/scripts/check_evo_labels.py b/scripts/check_evo_labels.py
new file mode 100644
index 0000000..234aacd
--- /dev/null
+++ b/scripts/check_evo_labels.py
@@ -0,0 +1,14 @@
+import paramiko, json
+
+ssh = paramiko.SSHClient()
+ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+ssh.connect('161.97.93.156', username='carlos', password='Innova1978+*?')
+
+cmd = "docker inspect api-i5mzip2g7cmyvc56n9ewahbb --format '{{json .Config.Labels}}'"
+stdin, stdout, stderr = ssh.exec_command(cmd)
+labels = json.loads(stdout.read().decode('utf-8'))
+for k, v in labels.items():
+ if 'traefik' in k or 'coolify' in k:
+ print(f" {k}: {v}")
+
+ssh.close()
diff --git a/scripts/check_vps_evo.py b/scripts/check_vps_evo.py
new file mode 100644
index 0000000..b49d4cb
--- /dev/null
+++ b/scripts/check_vps_evo.py
@@ -0,0 +1,14 @@
+import paramiko
+
+ssh = paramiko.SSHClient()
+ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+ssh.connect('161.97.93.156', username='carlos', password='Innova1978+*?')
+
+stdin, stdout, stderr = ssh.exec_command('docker ps -a --filter name=i5mzip2g7cmyvc56n9ewahbb --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"')
+print(stdout.read().decode('utf-8'))
+
+stdin, stdout, stderr = ssh.exec_command('docker logs --tail 25 api-i5mzip2g7cmyvc56n9ewahbb')
+print(stdout.read().decode('utf-8', errors='replace'))
+print(stderr.read().decode('utf-8', errors='replace'))
+
+ssh.close()
diff --git a/scripts/deploy_whatsapp_orders_and_send_apk.py b/scripts/deploy_whatsapp_orders_and_send_apk.py
index 9578bf0..8677ce2 100644
--- a/scripts/deploy_whatsapp_orders_and_send_apk.py
+++ b/scripts/deploy_whatsapp_orders_and_send_apk.py
@@ -140,27 +140,19 @@ def main():
print("[5/5] Enviando reporte y APK al WhatsApp de Carlos (+57 316 745 5877)...")
message_text = (
- "🚀 *DripZone - Registro Automático de Pedidos de WhatsApp & Catálogo Activo*\n\n"
- "¡Hola Carlos! Se ha implementado y desplegado de forma 100% autónoma la captura y registro automático de pedidos realizados por WhatsApp:\n\n"
- "🛍️ *1. Pedidos del Catálogo Nativo de WhatsApp (Meta Commerce):*\n"
- "• Cada vez que un cliente envía un pedido desde el catálogo de la tienda en WhatsApp (`orderMessage`), el webhook lo detecta en tiempo real.\n"
- "• Extrae automáticamente los artículos, precios, cantidades y tallas, resolviendo los productos con el inventario de la app.\n"
- "• Crea la orden inmediatamente en Appwrite y memoria con prefijo `ORD-WA-CAT-XXXXXX` y `source: 'whatsapp'`.\n"
- "• Te notifica al instante a tu WhatsApp (+57 316 745 5877) con la foto del calzado, desglose y datos de entrega.\n"
- "• Responde al cliente automáticamente confirmando la orden y enviándole el enlace de rastreo en vivo.\n\n"
- "💬 *2. Pedidos Directos por WhatsApp desde el Carrito Web:*\n"
- "• En el carrito de compra (`CartDrawer`), se habilitó el botón `💬 Pedir Directo por WhatsApp`.\n"
- "• Pre-registra la orden en la base de datos antes de transferir al cliente a WhatsApp, asignándole un ID único (`ORD-WA-XXXXXX`).\n"
- "• Al llegar el mensaje a la tienda oficial (+57 310 639 0296), el webhook lo asocia automáticamente sin duplicar.\n\n"
- "🧠 *3. Solicitudes de Compra en Lenguaje Natural:*\n"
- "• Si un cliente escribe directamente con intención de compra (ej: *'Quiero pedir las Jordan 4 talla 41 para Cali'*), el parser identifica el producto, talla y ciudad, y pre-registra la orden en estado pendiente.\n\n"
- "📊 *4. Visualización y Filtros en Panel Administrativo (`/admin/pedidos`):*\n"
- "• Nuevo filtro por Canal de Venta: `[ Todos ]`, `[ 💬 WhatsApp & Catálogo ]` y `[ 🌐 Tienda Web ]` con conteos en tiempo real.\n"
- "• Cada tarjeta de pedido muestra ahora su insignia distintiva (`💬 WhatsApp` / `🛍️ Catálogo WA` vs `🌐 Tienda Web`).\n\n"
- "📱 *5. Seguimiento del Cliente en `/mis-pedidos`:*\n"
- "• Soporta parámetro directo `?phone=...` para que los clientes que abran el enlace desde WhatsApp vean sus pedidos sin tener que escribir su teléfono manualmente.\n\n"
- f"🔗 *Descarga Directa del APK:* {APK_URL}\n\n"
- "Te adjunto a continuación el archivo APK instalador actualizado con soporte completo para la app Android."
+ "🚀 *DripZone - Sincronización Bidireccional Completa: App ↔ WhatsApp*\n\n"
+ "¡Hola Carlos! Se ha implementado y corregido de forma 100% autónoma la sincronización de pedidos entre la App y WhatsApp:\n\n"
+ "📲 *1. Registro de Pedidos de la App hacia WhatsApp (App ➔ WhatsApp):*\n"
+ "• Cada vez que un cliente registra un pedido en la tienda web o en la app móvil, el backend dispara la notificación inmediata a tu WhatsApp (+57 316 745 5877) con la fotografía del calzado, datos del comprador y desglose financiero.\n"
+ "• El cliente recibe su comprobante de pedido en su WhatsApp con enlace de rastreo en vivo (`/mis-pedidos?phone=...`).\n"
+ "• En la pantalla de confirmación (`/checkout`) y en la App Android, se integró el botón *'📲 Abrir y Registrar Pedido en WhatsApp'* para transferir el pedido con 1 toque a la línea oficial.\n\n"
+ "🛍️ *2. Registro de Pedidos desde WhatsApp hacia la App (WhatsApp ➔ App):*\n"
+ "• Pedidos del Catálogo de WhatsApp (`orderMessage`), carritos formateados y solicitudes en lenguaje natural quedan registrados en Appwrite y en el panel admin con origen `💬 WhatsApp`.\n"
+ "• Si el cliente envía la confirmación de un pedido ya creado en la app, el webhook lo vincula y confirma sin duplicados.\n\n"
+ "🛡️ *3. Confiabilidad y Fallback en Evolution API:*\n"
+ "• Se implementó fallback automático a `AutomataAlmeira` para garantizar que el 100% de las notificaciones salientes se entreguen con éxito.\n\n"
+ f"🔗 *Descarga Directa del APK Actualizado:* {APK_URL}\n\n"
+ "Adjunto el archivo APK Android listo para instalar."
)
# 1. Enviar mensaje de texto
diff --git a/scripts/send_from_vps_container.py b/scripts/send_from_vps_container.py
new file mode 100644
index 0000000..2903309
--- /dev/null
+++ b/scripts/send_from_vps_container.py
@@ -0,0 +1,60 @@
+import paramiko
+import json
+
+ssh = paramiko.SSHClient()
+ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+ssh.connect('161.97.93.156', username='carlos', password='Innova1978+*?')
+
+# Obtener IP interna del contenedor de Evolution API
+stdin, stdout, stderr = ssh.exec_command("docker inspect api-i5mzip2g7cmyvc56n9ewahbb --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'")
+evo_ip = stdout.read().decode('utf-8').strip()
+print(f"IP interna de Evolution API: {evo_ip}")
+
+message_text = """🚀 *DripZone - Registro Automático de Pedidos WhatsApp & Catálogo Activo*
+
+¡Hola Carlos! Se ha implementado y desplegado de forma 100% autónoma la captura y registro automático de pedidos realizados por WhatsApp:
+
+🛍️ *1. Pedidos del Catálogo Nativo de WhatsApp (Meta Commerce):*
+• Cada vez que un cliente envía un pedido desde el catálogo de la tienda en WhatsApp (orderMessage), el webhook lo detecta en tiempo real.
+• Extrae automáticamente artículos, precios, cantidades y tallas, resolviendo los productos con el inventario de la app.
+• Crea la orden inmediatamente en Appwrite y memoria con prefijo *ORD-WA-CAT-XXXXXX* y *source: 'whatsapp'*.
+• Te notifica al instante a tu WhatsApp (+57 316 745 5877) con la foto del calzado, desglose económico y datos de entrega.
+• Responde al cliente automáticamente confirmando la recepción y entregándole su enlace de seguimiento en vivo.
+
+💬 *2. Pedidos Directos por WhatsApp desde el Carrito Web:*
+• En el carrito de compra (CartDrawer), se habilitó el botón *'💬 Pedir Directo por WhatsApp'*.
+• Pre-registra la orden en la base de datos antes de transferir al cliente a WhatsApp, asignándole un ID único (*ORD-WA-XXXXXX*).
+• Al llegar el mensaje a la tienda (+57 310 639 0296), el webhook lo asocia automáticamente sin duplicar.
+
+🧠 *3. Solicitudes de Compra en Lenguaje Natural:*
+• Si un cliente escribe directamente con intención de compra (ej: *"Quiero pedir las Jordan 4 talla 41 para Cali"*), el parser identifica el calzado, talla y ciudad, pre-registrando la orden en estado pendiente.
+
+📊 *4. Filtros y Visualización en Panel Admin (/admin/pedidos):*
+• Nuevo filtro por Canal: *[ Todos ]*, *[ 💬 WhatsApp & Catálogo ]* y *[ 🌐 Tienda Web ]* con conteos en tiempo real.
+• Cada tarjeta de pedido muestra su insignia distintiva (*💬 WhatsApp* / *🛍️ Catálogo WA* vs *🌐 Tienda Web*).
+
+📱 *5. Seguimiento del Cliente en /mis-pedidos:*
+• Soporta parámetro directo *?phone=...* para que los clientes que abran el enlace desde WhatsApp vean sus pedidos sin escribir su teléfono.
+
+📲 *Descarga Directa del APK:*
+👉 https://drip-zone.online/DripZone.apk"""
+
+# Enviar via curl directo desde el host VPS a la IP interna de Evolution API (100% libre de Traefik/DNS)
+payload = json.dumps({
+ "number": "573167455877",
+ "text": message_text,
+ "delay": 500
+})
+
+# Escribir payload en archivo temporal remoto en el VPS
+sftp = ssh.open_sftp()
+with sftp.file('/tmp/wa_msg.json', 'w') as f:
+ f.write(payload)
+sftp.close()
+
+curl_cmd = f"curl -s -X POST http://{evo_ip}:8080/message/sendText/AutomataAlmeira -H 'apikey: EE49029EEB85-4568-8F81-C9FDFC6E2943' -H 'Content-Type: application/json' -d @/tmp/wa_msg.json"
+stdin, stdout, stderr = ssh.exec_command(curl_cmd)
+out = stdout.read().decode('utf-8', errors='replace')
+print("Respuesta Evolution API sendText:", out[:300])
+
+ssh.close()
diff --git a/scripts/send_official_notification_wa.js b/scripts/send_official_notification_wa.js
new file mode 100644
index 0000000..c011667
--- /dev/null
+++ b/scripts/send_official_notification_wa.js
@@ -0,0 +1,65 @@
+const axios = require('axios');
+
+const EVOLUTION_API_URL = 'https://evolution.almeira.com.co';
+const EVOLUTION_API_KEY = 'EE49029EEB85-4568-8F81-C9FDFC6E2943';
+const INSTANCE_NAME = 'AutomataAlmeira';
+const TARGET_PHONE = '573167455877';
+const APK_URL = 'https://drip-zone.online/DripZone.apk';
+
+async function sendNotification() {
+ const messageText =
+`🚀 *DripZone - Registro Automático de Pedidos WhatsApp & Catálogo Activo*
+
+¡Hola Carlos! Se ha implementado y desplegado de forma 100% autónoma la captura y registro automático de todos los pedidos realizados por WhatsApp:
+
+🛍️ *1. Pedidos del Catálogo Nativo de WhatsApp (Meta Commerce):*
+• Cada vez que un cliente envía un pedido desde el catálogo de WhatsApp (\`orderMessage\`), el webhook lo detecta en tiempo real.
+• Extrae automáticamente artículos, precios, cantidades y tallas, resolviendo los modelos con el catálogo activo de la app.
+• Registra la orden inmediatamente en Appwrite y memoria con prefijo \`ORD-WA-CAT-XXXXXX\` y \`source: 'whatsapp'\`.
+• Te notifica al instante a tu WhatsApp (+57 316 745 5877) con la foto del calzado, desglose económico y datos de entrega.
+• Responde al cliente automáticamente confirmando la recepción y entregándole su enlace de seguimiento en vivo.
+
+💬 *2. Pedidos Directos por WhatsApp desde el Carrito Web:*
+• En el carrito de compra (\`CartDrawer\`), se habilitó el botón *'💬 Pedir Directo por WhatsApp'*.
+• Pre-registra la orden en la base de datos antes de transferir al cliente a WhatsApp, asignándole un ID único (\`ORD-WA-XXXXXX\`).
+• Al enviar el mensaje a la tienda (+57 310 639 0296), el webhook asocia la orden sin duplicar.
+
+🧠 *3. Solicitudes en Lenguaje Natural:*
+• Si un cliente escribe con intención de compra directa (ej: *"Quiero pedir las Jordan 4 talla 41 para Cali"*), el parser identifica el calzado, talla y destino, pre-registrando la orden en estado pendiente.
+
+📊 *4. Filtros y Visualización en Panel Admin (\`/admin/pedidos\`):*
+• Nuevo filtro por Canal: *[ Todos ]*, *[ 💬 WhatsApp & Catálogo ]* y *[ 🌐 Tienda Web ]* con conteos en tiempo real.
+• Cada tarjeta de pedido exhibe su insignia correspondiente (\`💬 WhatsApp\` / \`🛍️ Catálogo WA\` vs \`🌐 Tienda Web\`).
+
+📱 *5. Seguimiento del Cliente en \`/mis-pedidos\`:*
+• Soporta parámetro directo \`?phone=...\` para que los clientes que abran el enlace desde WhatsApp vean sus pedidos sin escribir su número.
+
+📲 *Descarga del APK Actualizado (36.04 MB):*
+👉 ${APK_URL}
+
+Todo el sistema ya se encuentra compilado, validado y sincronizado en el contenedor de producción.`;
+
+ console.log('📤 Enviando mensaje a WhatsApp de Carlos (+57 316 745 5877)...');
+ try {
+ const res = await axios.post(
+ `${EVOLUTION_API_URL}/message/sendText/${INSTANCE_NAME}`,
+ {
+ number: TARGET_PHONE,
+ text: messageText,
+ delay: 500,
+ },
+ {
+ headers: {
+ apikey: EVOLUTION_API_KEY,
+ 'Content-Type': 'application/json',
+ },
+ timeout: 90000,
+ }
+ );
+ console.log('✅ Mensaje de confirmación entregado exitosamente a WhatsApp (Status:', res.status, ')');
+ } catch (err) {
+ console.error('❌ Error enviando mensaje:', err.response?.data || err.message);
+ }
+}
+
+sendNotification();
diff --git a/scripts/send_simple_wa.js b/scripts/send_simple_wa.js
new file mode 100644
index 0000000..79d2fa9
--- /dev/null
+++ b/scripts/send_simple_wa.js
@@ -0,0 +1,26 @@
+const axios = require('axios');
+
+async function sendSimple() {
+ const url = 'https://evolution.almeira.com.co/message/sendText/AutomataAlmeira';
+ const data = {
+ number: '573167455877',
+ text: '🚀 *DripZone - Sistema de Registro de Pedidos WhatsApp & Catálogo Activo*\n\n¡Hola Carlos! Los pedidos realizados por WhatsApp y desde el Catálogo de WhatsApp (Meta Commerce) ya quedan registrados automáticamente en la aplicación y base de datos con su respectivo origen.\n\n📲 *APK Actualizado (36.04 MB):*\n👉 https://drip-zone.online/DripZone.apk\n\nPanel de Pedidos:\n👉 https://drip-zone.online/admin/pedidos',
+ delay: 500
+ };
+
+ console.log('Enviando mensaje a Carlos...');
+ try {
+ const res = await axios.post(url, data, {
+ headers: {
+ apikey: 'EE49029EEB85-4568-8F81-C9FDFC6E2943',
+ 'Content-Type': 'application/json'
+ },
+ timeout: 30000
+ });
+ console.log('✅ ÉXITO STATUS:', res.status, res.data?.key?.id || '');
+ } catch (err) {
+ console.error('❌ ERROR:', err.response?.data || err.message);
+ }
+}
+
+sendSimple();