feat(orders): bidirectional synchronization between App and WhatsApp with direct WhatsApp confirmation button and resilient Evolution API fallback
This commit is contained in:
parent
331e93d48a
commit
277a9ca7c4
@ -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
|
||||
|
||||
@ -469,11 +469,30 @@
|
||||
app:iconPadding="8dp"
|
||||
app:iconSize="18dp" />
|
||||
|
||||
<!-- WhatsApp Order Confirmation Button -->
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnOpenWhatsAppOrder"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="50dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:filterTouchesWhenObscured="true"
|
||||
android:text="Abrir y Registrar Pedido en WhatsApp"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="13sp"
|
||||
android:textStyle="bold"
|
||||
app:backgroundTint="#059669"
|
||||
app:cornerRadius="14dp"
|
||||
app:icon="@drawable/ic_whatsapp"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="8dp"
|
||||
app:iconSize="18dp" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnViewOrderDetails"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="50dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:filterTouchesWhenObscured="true"
|
||||
android:text="Ver Estado de Mi Pedido"
|
||||
android:textAllCaps="false"
|
||||
|
||||
@ -54,22 +54,34 @@ export async function POST(request: Request) {
|
||||
const newOrder = await createOrderInAppwrite(body);
|
||||
dataService.addOrder(newOrder);
|
||||
|
||||
// 1. Notificación INMEDIATA al WhatsApp de Carlos (+57 316 745 5877)
|
||||
try {
|
||||
await notifyAdminNewOrder(newOrder);
|
||||
} catch (adminWaErr) {
|
||||
console.warn('No se pudo enviar WhatsApp al administrador Carlos:', adminWaErr);
|
||||
// 1. Notificación INMEDIATA y PARALELA a WhatsApp (Administrador Carlos y Cliente)
|
||||
const notifications: Promise<any>[] = [];
|
||||
|
||||
// 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) {
|
||||
|
||||
@ -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, '');
|
||||
|
||||
@ -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 }
|
||||
|
||||
@ -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' };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
15
scripts/check_docker_nets.py
Normal file
15
scripts/check_docker_nets.py
Normal file
@ -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()
|
||||
14
scripts/check_evo_labels.py
Normal file
14
scripts/check_evo_labels.py
Normal file
@ -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()
|
||||
14
scripts/check_vps_evo.py
Normal file
14
scripts/check_vps_evo.py
Normal file
@ -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()
|
||||
@ -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
|
||||
|
||||
60
scripts/send_from_vps_container.py
Normal file
60
scripts/send_from_vps_container.py
Normal file
@ -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()
|
||||
65
scripts/send_official_notification_wa.js
Normal file
65
scripts/send_official_notification_wa.js
Normal file
@ -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();
|
||||
26
scripts/send_simple_wa.js
Normal file
26
scripts/send_simple_wa.js
Normal file
@ -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();
|
||||
Loading…
Reference in New Issue
Block a user