feat: auto-register customers on whatsapp orders and include Bold payment link in confirmation notification

This commit is contained in:
AI Bot 2026-10-09 23:15:24 -05:00
parent c55abbac25
commit 610c1024e0
3 changed files with 422 additions and 30 deletions

View File

@ -51,6 +51,37 @@ export async function GET(request: Request) {
export async function POST(request: Request) {
try {
const body = await request.json();
if (body.customerPhone) {
const cleanPhone = body.customerPhone.replace(/\D/g, '');
let user = dataService.getUsers().find((u) => {
const uPhone = (u.phone || '').replace(/\D/g, '');
return uPhone.length >= 7 && (uPhone.endsWith(cleanPhone) || cleanPhone.endsWith(uPhone));
});
if (!user) {
user = dataService.addUser({
id: `usr-${Date.now()}`,
name: body.customerName || 'Cliente DripZone',
email: body.customerEmail || `${cleanPhone}@drip-zone.online`,
phone: cleanPhone,
city: body.cityName || 'Cali',
address: body.address || '',
role: 'customer',
isActive: true,
phoneVerified: true,
totalOrders: 1,
createdAt: new Date().toISOString().split('T')[0],
lastLogin: 'Web Checkout',
});
} else {
dataService.updateUser(user.id, {
totalOrders: (user.totalOrders || 0) + 1,
lastLogin: 'Web Checkout',
});
}
body.userId = user.id;
}
const newOrder = await createOrderInAppwrite(body);
dataService.addOrder(newOrder);

View File

@ -1,8 +1,9 @@
import { Order, OrderItem, Product } from '@/types';
import { Order, OrderItem, Product, User } from '@/types';
import { dataService } from '@/lib/data-service';
import { createOrderInAppwrite, getOrdersFromAppwrite } from '@/lib/server-appwrite';
import { notifyAdminNewOrder, sendWhatsAppMessage } from '@/lib/evolution-api';
import { formatCOP } from '@/lib/pricing-engine';
import { createBoldPayment } from '@/lib/bold-service';
// Cache para evitar procesar la misma orden múltiples veces si Evolution API reenvía el webhook
const processedOrdersCache = new Set<string>();
@ -100,6 +101,101 @@ function matchProductInCatalog(titleOrReference: string, allProducts: Product[])
return null;
}
/**
* Garantiza que el cliente que realiza el pedido quede registrado en la base de datos de la App.
* Si el usuario no existe, lo crea automáticamente con rol 'customer' y datos iniciales.
* Si ya existe, actualiza su conteo de pedidos y última actividad.
*/
function ensureCustomerRegistered(
customerPhone: string,
customerPushName: string,
cityName: string,
address: string
): User {
const cleanPhone = customerPhone.replace(/\D/g, '');
let user = dataService.getUsers().find((u) => {
const uPhone = (u.phone || '').replace(/\D/g, '');
return (uPhone.length >= 7 && (uPhone.endsWith(cleanPhone) || cleanPhone.endsWith(uPhone)));
});
if (!user) {
const cleanName =
customerPushName && customerPushName !== 'Cliente WhatsApp'
? customerPushName.trim()
: `Cliente WhatsApp (+${cleanPhone})`;
const newUser: User = {
id: `usr-wa-${cleanPhone.slice(-6)}-${Date.now().toString().slice(-4)}`,
name: cleanName,
email: `${cleanPhone}@drip-zone.online`,
phone: cleanPhone,
city: cityName || 'Cali',
address: address && address !== 'Por coordinar vía WhatsApp' ? address : '',
role: 'customer',
isActive: true,
phoneVerified: true,
requiresShippingDeposit: true,
depositReason: 'Garantía de separación requerida para primer pedido WhatsApp',
totalOrders: 1,
createdAt: new Date().toISOString().split('T')[0],
lastLogin: 'WhatsApp Order',
};
user = dataService.addUser(newUser);
console.log(`👤 [WhatsApp Order Parser] ¡Nuevo cliente AUTO-REGISTRADO en la App! ID: ${user.id}, Nombre: ${user.name}, Tel: ${user.phone}`);
} else {
const updates: Partial<User> = {
totalOrders: (user.totalOrders || 0) + 1,
lastLogin: 'WhatsApp Order',
};
if (!user.address && address && address !== 'Por coordinar vía WhatsApp') {
updates.address = address;
}
if ((!user.city || user.city === 'Cali') && cityName) {
updates.city = cityName;
}
if ((user.name.startsWith('+') || user.name === 'Cliente WhatsApp') && customerPushName && customerPushName !== 'Cliente WhatsApp') {
updates.name = customerPushName.trim();
}
const updated = dataService.updateUser(user.id, updates);
if (updated) user = updated;
}
return user;
}
/**
* Genera el enlace directo de pago en línea de Bold para el anticipo / separación del pedido.
* Si la API de Bold responde exitosamente, devuelve el checkoutUrl oficial (soporta PSE, Nequi, Tarjetas, Botón Bancolombia, Bre-B).
* En caso de contingencia devuelve el enlace seguro de la pasarela web de DripZone.
*/
async function generateBoldDepositLink(
orderId: string,
depositAmount: number,
customerPhone: string,
customerEmail?: string
): Promise<string> {
try {
const res = await createBoldPayment({
orderId,
amount: depositAmount || 10000,
description: `Separación Pedido #${orderId} - DripZone`,
payerEmail: customerEmail || `${customerPhone}@drip-zone.online`,
redirectionUrl: `https://drip-zone.online/mis-pedidos?phone=${customerPhone}&orderId=${orderId}&paid=true`,
type: 'deposit',
});
if (res.success && res.checkoutUrl) {
console.log(`💳 [WhatsApp Order Parser] Link de Bold generado con éxito para pedido #${orderId}: ${res.checkoutUrl}`);
return res.checkoutUrl;
}
} catch (err: any) {
console.warn(`⚠️ [WhatsApp Order Parser] No se pudo generar enlace directo en Bold para #${orderId}:`, err.message);
}
return `https://drip-zone.online/checkout?orderId=${orderId}&payDeposit=true`;
}
/**
* Motor Principal: Detecta y procesa pedidos recibidos desde WhatsApp:
* 1. Mensajes de Pedido del Catálogo Nativo de WhatsApp (Meta Commerce / orderMessage).
@ -218,12 +314,17 @@ export async function detectAndProcessWhatsAppOrder(
const totalAmount = calculatedSubtotal + destination.shippingCost;
const depositAmount = 10000;
const balanceDue = Math.max(0, totalAmount - depositAmount);
const deliveryAddress = orderMessage.message ? `Nota: ${orderMessage.message}` : 'Por coordinar vía WhatsApp';
// 1. AUTO-REGISTRAR AL CLIENTE EN LA APP SI NO ESTÁ REGISTRADO
const customerUser = ensureCustomerRegistered(customerPhone, customerPushName, destination.name, deliveryAddress);
const newOrder: Order = {
id: `ORD-WA-CAT-${Math.floor(100000 + Math.random() * 900000)}`,
customerName: customerDisplayName,
userId: customerUser.id,
customerName: customerUser.name || customerDisplayName,
customerPhone,
address: orderMessage.message ? `Nota: ${orderMessage.message}` : 'Por coordinar vía WhatsApp',
address: deliveryAddress,
cityId: destination.id,
cityName: destination.name,
items: orderItems,
@ -241,17 +342,25 @@ export async function detectAndProcessWhatsAppOrder(
createdAt: new Date().toISOString(),
};
// Guardar pedido en base de datos persistente (Appwrite y memoria)
// 2. REGISTRAR EL PEDIDO EN APPWRITE Y EN MEMORIA
const savedOrder = await createOrderInAppwrite(newOrder);
dataService.addOrder(savedOrder);
console.log(`✅ [WhatsApp Order Parser] Pedido de Catálogo #${savedOrder.id} REGISTRADO EN LA APP exitosamente.`);
// Notificar de inmediato al administrador Carlos con foto del producto
// 3. GENERAR ENLACE DE PAGO EN LÍNEA DE BOLD PARA EL ANTICIPO
const boldPaymentUrl = await generateBoldDepositLink(
savedOrder.id,
savedOrder.depositAmount,
customerPhone,
customerUser.email
);
// 4. NOTIFICAR DE INMEDIATO AL ADMINISTRADOR CARLOS (+57 316 745 5877)
await notifyAdminNewOrder(savedOrder).catch((e) =>
console.warn('Error notificando admin pedido catálogo:', e)
);
// Responder automáticamente al cliente desde la línea oficial DripZone (+57 310 639 0296)
// 5. RESPONDER AUTOMÁTICAMENTE AL CLIENTE DESDE LA LÍNEA OFICIAL DRIPZONE (+57 310 639 0296) CON EL ENLACE DE BOLD
const itemsListText = orderItems
.map((i) => (
`• 👟 *${i.productName}*\n` +
@ -266,7 +375,9 @@ export async function detectAndProcessWhatsAppOrder(
`━━━━━━━━━━━━━━━━━━━━━━\n` +
`🆔 *No. de Orden:* *#${savedOrder.id}*\n` +
`👤 *Cliente:* ${savedOrder.customerName}\n` +
`📱 *WhatsApp Origen:* +${customerPhone}\n\n` +
`📱 *WhatsApp Origen:* +${customerPhone}\n` +
`🏙️ *Ciudad Destino:* ${savedOrder.cityName}\n` +
`📍 *Dirección de Entrega:* ${savedOrder.address}\n\n` +
`🛒 *DETALLE DE TU PEDIDO:*\n` +
`${itemsListText}\n\n` +
`━━━━━━━━━━━━━━━━━━━━━━\n` +
@ -275,13 +386,14 @@ export async function detectAndProcessWhatsAppOrder(
`• *Costo de Envío:* ${savedOrder.shippingCost === 0 ? 'GRATIS' : `${formatCOP(savedOrder.shippingCost)} COP`}\n` +
`• *TOTAL DE LA VENTA:* *${formatCOP(savedOrder.totalAmount)} COP*\n\n` +
`⚡ *ANTICIPO DE SEPARACIÓN (REQUERIDO PARA DESPACHO):*\n` +
`⚠️ *Estado Anticipo:* 🔴 *PENDIENTE DE PAGO* (*${formatCOP(savedOrder.depositAmount)} COP*)\n` +
`👉 *Para procesar y despachar tu pedido debes realizar el pago de la separación ($10.000 COP).* Solicítanos aquí los datos de Bancolombia, Nequi, Dale o link de pago.\n\n` +
`⚠️ *Estado Anticipo:* 🔴 *PENDIENTE DE PAGO* (*${formatCOP(savedOrder.depositAmount)} COP*)\n\n` +
`💳 *ENLACE DE PAGO EN LÍNEA CON BOLD (SEPARACIÓN):*\n` +
`👉 ${boldPaymentUrl}\n` +
`_(Acepta PSE, Nequi, Botón Bancolombia, Tarjetas de Crédito/Débito y QR Bre-B sin comisiones)_\n\n` +
`🤝 *SALDO A PAGAR CONTRAENTREGA AL RECIBIR:*\n` +
`💵 *${formatCOP(savedOrder.balanceDue)} COP* (se cancela en efectivo a la transportadora al recibir en tu puerta).\n\n` +
`📍 *Dirección de Entrega:* ${savedOrder.address}\n` +
`🏙️ *Ciudad:* ${savedOrder.cityName}\n\n` +
`📱 *Consulta el estado de tu pedido en vivo:*\n` +
`👤 *Tu Cuenta DripZone:* Registrada y vinculada automáticamente a tu número (+${customerPhone}).\n` +
`📱 *Consulta y rastrea el estado de tu pedido en vivo:*\n` +
`👉 https://drip-zone.online/mis-pedidos?phone=${customerPhone}\n` +
`━━━━━━━━━━━━━━━━━━━━━━`
);
@ -307,7 +419,7 @@ 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 (registrada desde el checkout web o móvil), confirmar recepció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 enviar link de Bold si está pendiente
if (existingOrderId) {
let existing = dataService.getOrderById(existingOrderId);
if (!existing) {
@ -318,6 +430,15 @@ export async function detectAndProcessWhatsAppOrder(
}
if (existing) {
console.log(`ℹ️ [WhatsApp Order Parser] La orden #${existingOrderId} ya existe en la base de datos.`);
const boldPaymentUrl = !existing.depositPaid
? await generateBoldDepositLink(existing.id, existing.depositAmount || 10000, customerPhone)
: null;
const boldPaymentBlock = boldPaymentUrl
? `\n💳 *ENLACE DE PAGO EN LÍNEA CON BOLD (SEPARACIÓN):*\n👉 ${boldPaymentUrl}\n_(Acepta PSE, Nequi, Botón Bancolombia, Tarjetas y QR Bre-B)_\n`
: '';
const confirmText = (
`🎉 *¡ORDEN #${existing.id} REGISTRADA EN WHATSAPP!* 📦✨\n` +
`━━━━━━━━━━━━━━━━━━━━━━\n` +
@ -325,6 +446,7 @@ export async function detectAndProcessWhatsAppOrder(
`🏙️ *Destino:* ${existing.cityName || 'Colombia'}\n` +
`💰 *Total Venta:* ${formatCOP(existing.totalAmount || 0)} COP\n` +
`⚡ *Separación:* ${formatCOP(existing.depositAmount || 10000)} COP (${existing.depositPaid ? '✅ PAGADA' : '🔴 PENDIENTE'})\n` +
boldPaymentBlock +
`🤝 *Saldo Contraentrega:* ${formatCOP(existing.balanceDue || existing.totalAmount || 0)} COP\n\n` +
`🚚 Para despachar tu paquete recuerda confirmar la separación de $10.000 COP.\n\n` +
`📱 Seguimiento en vivo:\n` +
@ -360,6 +482,9 @@ export async function detectAndProcessWhatsAppOrder(
const balanceDue = balanceMatch ? parsePriceNumber(balanceMatch[1]) : Math.max(0, totalAmount - 10000);
const depositAmount = totalAmount > balanceDue ? totalAmount - balanceDue : 10000;
// 1. AUTO-REGISTRAR AL CLIENTE EN LA APP
const customerUser = ensureCustomerRegistered(parsedPhone, parsedCustomerName, destination.name, parsedAddress);
// Parsear artículos
const orderItems: OrderItem[] = [];
const lines = text.split('\n');
@ -422,7 +547,8 @@ export async function detectAndProcessWhatsAppOrder(
const newOrder: Order = {
id: generatedId,
customerName: parsedCustomerName,
userId: customerUser.id,
customerName: customerUser.name || parsedCustomerName,
customerPhone: parsedPhone,
address: parsedAddress,
cityId: destination.id,
@ -442,16 +568,34 @@ export async function detectAndProcessWhatsAppOrder(
createdAt: new Date().toISOString(),
};
// 2. REGISTRAR EL PEDIDO EN APPWRITE Y EN MEMORIA
const savedOrder = await createOrderInAppwrite(newOrder);
dataService.addOrder(savedOrder);
console.log(`✅ [WhatsApp Order Parser] Pedido de texto #${savedOrder.id} REGISTRADO EN LA APP con éxito.`);
// Notificar al administrador Carlos
// 3. GENERAR ENLACE DE PAGO BOLD
const boldPaymentUrl = await generateBoldDepositLink(
savedOrder.id,
savedOrder.depositAmount,
parsedPhone,
customerUser.email
);
// 4. NOTIFICAR AL ADMINISTRADOR CARLOS (+57 316 745 5877)
await notifyAdminNewOrder(savedOrder).catch((e) =>
console.warn('Error notificando admin pedido texto:', e)
);
// Enviar confirmación al cliente desde DripZone oficial
// 5. ENVIAR CONFIRMACIÓN AL CLIENTE DESDE DRIPZONE OFICIAL CON EL ENLACE DE BOLD
const itemsListText = orderItems
.map((i) => (
`• 👟 *${i.productName}*\n` +
` 📏 Talla: *${i.size}* | Cantidad: *${i.quantity}*\n` +
` 💵 Valor Unitario: *${formatCOP(i.price)} COP*\n` +
` 💰 Subtotal Producto: *${formatCOP(i.price * i.quantity)} COP*`
))
.join('\n\n');
const confirmationMsg = (
`🎉 *¡TU PEDIDO HA SIDO REGISTRADO EN DRIPZONE!* 📦\n` +
`━━━━━━━━━━━━━━━━━━━━━━\n` +
@ -460,11 +604,23 @@ export async function detectAndProcessWhatsAppOrder(
`📱 *Celular Origen:* +${parsedPhone}\n` +
`🏙 *Ciudad Destino:* ${savedOrder.cityName}\n` +
`📍 *Dirección:* ${savedOrder.address}\n\n` +
`💰 *Total de la Venta:* *${formatCOP(savedOrder.totalAmount)} COP*\n` +
`⚡ *Anticipo de Separación:* *${formatCOP(savedOrder.depositAmount)} COP* (🔴 PENDIENTE DE PAGO)\n` +
`🤝 *Saldo a Pagar Contraentrega:* *${formatCOP(savedOrder.balanceDue)} COP*\n\n` +
`👉 *Recuerda que para procesar tu despacho debes pagar la separación de $10.000 COP.* Solicítanos el medio de pago aquí.\n\n` +
`Consulta tu orden en vivo: 👉 https://drip-zone.online/mis-pedidos?phone=${parsedPhone}\n` +
`🛒 *DETALLE DE ARTÍCULOS:*\n` +
`${itemsListText}\n\n` +
`━━━━━━━━━━━━━━━━━━━━━━\n` +
`💰 *LIQUIDACIÓN ECONÓMICA:*\n` +
`• *Subtotal Productos:* ${formatCOP(savedOrder.subtotal)} COP\n` +
`• *Envío:* ${savedOrder.shippingCost === 0 ? 'GRATIS' : `${formatCOP(savedOrder.shippingCost)} COP`}\n` +
`• *TOTAL DE LA VENTA:* *${formatCOP(savedOrder.totalAmount)} COP*\n\n` +
`⚡ *ANTICIPO DE SEPARACIÓN (REQUERIDO PARA DESPACHO):*\n` +
`⚠️ *Estado Anticipo:* 🔴 *PENDIENTE DE PAGO* (*${formatCOP(savedOrder.depositAmount)} COP*)\n\n` +
`💳 *ENLACE DE PAGO EN LÍNEA CON BOLD (SEPARACIÓN):*\n` +
`👉 ${boldPaymentUrl}\n` +
`_(Paga al instante con PSE, Nequi, Botón Bancolombia, Tarjetas de Crédito/Débito y QR Bre-B)_\n\n` +
`🤝 *SALDO A PAGAR CONTRAENTREGA AL RECIBIR:*\n` +
`💵 *${formatCOP(savedOrder.balanceDue)} COP* (se cancela en efectivo a la transportadora al recibir en tu puerta).\n\n` +
`👤 *Cuenta DripZone:* Registrada y vinculada automáticamente a tu número (+${parsedPhone}).\n` +
`📱 *Consulta tu orden en vivo:*\n` +
`👉 https://drip-zone.online/mis-pedidos?phone=${parsedPhone}\n` +
`━━━━━━━━━━━━━━━━━━━━━━`
);
@ -501,9 +657,13 @@ export async function detectAndProcessWhatsAppOrder(
const depositAmount = 10000;
const balanceDue = Math.max(0, totalAmount - depositAmount);
// 1. AUTO-REGISTRAR AL CLIENTE EN LA APP
const customerUser = ensureCustomerRegistered(customerPhone, customerPushName, destination.name, 'Por coordinar vía WhatsApp');
const newOrder: Order = {
id: `ORD-WA-CHAT-${Math.floor(100000 + Math.random() * 900000)}`,
customerName: customerDisplayName,
userId: customerUser.id,
customerName: customerUser.name || customerDisplayName,
customerPhone,
address: 'Por coordinar vía WhatsApp',
cityId: destination.id,
@ -536,29 +696,46 @@ export async function detectAndProcessWhatsAppOrder(
createdAt: new Date().toISOString(),
};
// 2. REGISTRAR EL PEDIDO EN APPWRITE Y EN MEMORIA
const savedOrder = await createOrderInAppwrite(newOrder);
dataService.addOrder(savedOrder);
console.log(`✅ [WhatsApp Order Parser] Pedido de chat #${savedOrder.id} REGISTRADO EN LA APP exitosamente.`);
// 3. GENERAR ENLACE DE PAGO BOLD
const boldPaymentUrl = await generateBoldDepositLink(
savedOrder.id,
savedOrder.depositAmount,
customerPhone,
customerUser.email
);
// 4. NOTIFICAR AL ADMINISTRADOR CARLOS (+57 316 745 5877)
await notifyAdminNewOrder(savedOrder).catch((e) =>
console.warn('Error notificando admin pedido chat:', e)
);
// 5. ENVIAR CONFIRMACIÓN AL CLIENTE DESDE DRIPZONE OFICIAL CON ENLACE DE BOLD
const confirmText = (
`👟 *¡EXCELENTE ELECCIÓN! PEDIDO REGISTRADO EN DRIPZONE* 📦\n` +
`━━━━━━━━━━━━━━━━━━━━━━\n` +
`🆔 *Orden #:* *#${savedOrder.id}*\n` +
`👤 *Cliente:* ${savedOrder.customerName}\n` +
`📱 *Celular:* +${customerPhone}\n\n` +
`✨ *Producto:* ${matched.name}\n` +
`📏 *Talla:* *${resolvedSize}* EUR | Cantidad: *1*\n` +
`💵 *Valor Unitario:* *${formatCOP(price)} COP*\n` +
`📱 *Celular:* +${customerPhone}\n` +
`🏙 *Ciudad Destino:* ${destination.name}\n\n` +
`🛒 *DETALLE DEL PRODUCTO:*\n` +
`• 👟 *${matched.name}*\n` +
` 📏 Talla: *${resolvedSize}* EUR | Cantidad: *1*\n` +
` 💵 Valor Unitario: *${formatCOP(price)} COP*\n` +
` 💰 Subtotal: *${formatCOP(price)} COP*\n\n` +
`━━━━━━━━━━━━━━━━━━━━━━\n` +
`💰 *TOTAL DE LA VENTA:* *${formatCOP(totalAmount)} COP*\n` +
`⚡ *Anticipo Separación:* *${formatCOP(depositAmount)} COP* (🔴 PENDIENTE DE PAGO)\n` +
`🤝 *Saldo Contraentrega:* *${formatCOP(balanceDue)} COP*\n\n` +
`👉 *Para procesar tu despacho debes pagar los $10.000 COP de separación.* Escríbenos tu nombre y dirección exacta.\n\n` +
`Rastreo en vivo: 👉 https://drip-zone.online/mis-pedidos?phone=${customerPhone}\n` +
`⚡ *Anticipo Separación:* *${formatCOP(depositAmount)} COP* (🔴 PENDIENTE DE PAGO)\n\n` +
`💳 *ENLACE DE PAGO EN LÍNEA CON BOLD (SEPARACIÓN):*\n` +
`👉 ${boldPaymentUrl}\n` +
`_(Acepta PSE, Nequi, Botón Bancolombia, Tarjetas de Crédito/Débito y QR Bre-B)_\n\n` +
`🤝 *Saldo Contraentrega al Recibir:* *${formatCOP(balanceDue)} COP*\n\n` +
`👤 *Tu Cuenta DripZone:* Registrada y vinculada automáticamente a tu número (+${customerPhone}).\n` +
`📱 *Rastreo en vivo:* 👉 https://drip-zone.online/mis-pedidos?phone=${customerPhone}\n` +
`━━━━━━━━━━━━━━━━━━━━━━`
);

View File

@ -0,0 +1,184 @@
import paramiko
import os
import urllib.request
import json
import ssl
import sys
import io
import time
if sys.platform == 'win32':
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
HOST = "161.97.93.156"
USER = "carlos"
PASSWORD = "Innova1978+*?"
REMOTE_BASE = "/home/carlos/apps/dripzone-app"
LOCAL_APP = r"c:\Users\Carlos\dev\almeira-web\dripzone-app"
LOCAL_APK = os.path.join(LOCAL_APP, "public", "DripZone.apk")
REMOTE_APK = f"{REMOTE_BASE}/public/DripZone.apk"
EVOLUTION_API_URL = "https://evolution.almeira.com.co"
EVOLUTION_API_KEY = "EE49029EEB85-4568-8F81-C9FDFC6E2943"
INSTANCE_NAME = "AutomataAlmeira"
TARGET_PHONE = "573167455877"
APK_URL = "https://drip-zone.online/DripZone.apk"
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
SYNC_FILES = [
"src/lib/whatsapp-order-parser.ts",
"src/lib/evolution-api.ts",
"src/app/api/orders/route.ts",
"src/lib/bold-service.ts",
"src/app/admin/pedidos/page.tsx",
"src/types/index.ts",
"src/lib/data-service.ts",
"src/lib/server-appwrite.ts",
]
def main():
print("[1/5] Verificando APK local...", flush=True)
if not os.path.exists(LOCAL_APK):
print(f" [ERROR] El archivo {LOCAL_APK} no existe.")
sys.exit(1)
size_mb = os.path.getsize(LOCAL_APK) / (1024 * 1024)
print(f" -> APK local verificado: {LOCAL_APK} ({size_mb:.2f} MB)", flush=True)
print("[2/5] Conectando por SSH al VPS...", flush=True)
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
connected = False
for attempt in range(1, 5):
try:
print(f" Intento de conexión SSH {attempt}/4 a {HOST}...", flush=True)
ssh.connect(
hostname=HOST,
username=USER,
password=PASSWORD,
timeout=60,
banner_timeout=60,
auth_timeout=60
)
connected = True
break
except Exception as e:
print(f" [Aviso]: Intento {attempt} falló: {e}. Reintentando en 4s...", flush=True)
time.sleep(4)
if not connected:
print("[ERROR] No se pudo conectar al VPS tras 4 intentos.")
sys.exit(1)
print("[3/5] Subiendo APK y código fuente al VPS vía SFTP...", flush=True)
sftp = ssh.open_sftp()
def ensure_remote_dir(remote_dir):
parts = remote_dir.strip("/").split("/")
current = ""
for p in parts:
current += "/" + p
try:
sftp.mkdir(current)
except:
pass
ensure_remote_dir(f"{REMOTE_BASE}/public")
print(f" -> Subiendo APK ({size_mb:.2f} MB)...", flush=True)
sftp.put(LOCAL_APK, REMOTE_APK)
for rel_path in SYNC_FILES:
loc = os.path.join(LOCAL_APP, rel_path.replace("/", os.sep))
if os.path.exists(loc):
rem = f"{REMOTE_BASE}/{rel_path}"
rem_dir = os.path.dirname(rem)
ensure_remote_dir(rem_dir)
sftp.put(loc, rem)
print(f" -> Sincronizado: {rel_path}", flush=True)
sftp.close()
print(" [OK] Archivos subidos por SFTP con éxito.", flush=True)
print("[4/5] Actualizando contenedor DripZone...", flush=True)
stdin, stdout, stderr = ssh.exec_command("docker ps --format '{{.Names}}'")
containers = [c.strip() for c in stdout.read().decode('utf-8').splitlines() if c.strip()]
drip_containers = [c for c in containers if 'drip' in c.lower() or 'bfmno' in c.lower()]
print(" Contenedores detectados:", drip_containers, flush=True)
for c in drip_containers:
ssh.exec_command(f"docker cp {REMOTE_APK} {c}:/app/public/DripZone.apk 2>/dev/null || true")
ssh.exec_command(f"docker cp {REMOTE_APK} {c}:/app/public/dripzone.apk 2>/dev/null || true")
ssh.exec_command(f"docker cp {REMOTE_APK} {c}:/app/DripZone.apk 2>/dev/null || true")
for rel_path in SYNC_FILES:
loc = f"{REMOTE_BASE}/{rel_path}"
cmd = f"docker cp {loc} {c}:/app/{rel_path} 2>/dev/null || true"
ssh.exec_command(cmd)
print(f" -> Actualizado contenedor: {c}", flush=True)
ssh.close()
time.sleep(2)
try:
req = urllib.request.Request(APK_URL, method='HEAD')
with urllib.request.urlopen(req, context=ctx, timeout=15) as res:
live_size_mb = int(res.headers.get('Content-Length', 0)) / (1024 * 1024)
print(f" [OK] APK online verificado en {APK_URL} (HTTP {res.status}, {live_size_mb:.2f} MB)", flush=True)
except Exception as e:
print(f" [WARN] Verificación HEAD APK: {e}", flush=True)
print("[5/5] Enviando reporte y APK al WhatsApp de Carlos (+57 316 745 5877)...", flush=True)
message_text = (
"🚀 *DripZone - Auto-Registro de Clientes & Pasarela Bold Activados*\n\n"
"¡Hola Carlos! Se ha implementado y desplegado el flujo completo que solicitaste:\n\n"
"👤 *1. Auto-Registro Automático de Clientes en la App:*\n"
"• Cuando un cliente hace un pedido por WhatsApp o desde el Catálogo de WhatsApp y aún no está registrado, el sistema crea de forma automática su cuenta en la App (`usr-wa-...`), vinculando su número celular, nombre y ciudad.\n"
"• Si el cliente ya existe, se actualiza su conteo de órdenes y última actividad.\n\n"
"📦 *2. Registro del Pedido Vinculado al Cliente:*\n"
"• El pedido se registra en Appwrite y en la App enlazado al `userId` del cliente con todos los ítems desglosados (valor unitario, talla, subtotal, envío y saldo contraentrega).\n\n"
"💳 *3. Enlace de Pago en Línea con Bold en la Notificación:*\n"
"• Al enviarle la confirmación del pedido al cliente desde la línea oficial DripZone (+57 310 639 0296), se le incluye el enlace directo de pago de la separación ($10.000 COP) generado en Bold.\n"
"• El cliente puede pagar de inmediato por *PSE, Nequi, Botón Bancolombia, Tarjetas de Crédito / Débito o QR Bre-B*.\n\n"
f"🔗 *Descarga Directa del APK Android:* {APK_URL}\n\n"
"Adjunto el archivo APK actualizado y listo para instalar."
)
# 1. Enviar mensaje de texto a Carlos
req_text = urllib.request.Request(
f"{EVOLUTION_API_URL}/message/sendText/{INSTANCE_NAME}",
data=json.dumps({"number": TARGET_PHONE, "text": message_text, "delay": 500}).encode("utf-8"),
headers={"apikey": EVOLUTION_API_KEY, "Content-Type": "application/json"}
)
try:
with urllib.request.urlopen(req_text, context=ctx, timeout=30) as resp:
print(" [OK] Mensaje de texto enviado a WhatsApp (Status:", resp.status, ")", flush=True)
except Exception as e:
print(" [Error texto WhatsApp]:", e, flush=True)
# 2. Enviar documento APK a Carlos
req_media = urllib.request.Request(
f"{EVOLUTION_API_URL}/message/sendMedia/{INSTANCE_NAME}",
data=json.dumps({
"number": TARGET_PHONE,
"mediatype": "document",
"mimetype": "application/vnd.android.package-archive",
"fileName": "DripZone_AutoRegistro_Bold.apk",
"caption": "📲 *DripZone APK Actualizado - Auto-Registro de Clientes & Pasarela Bold*",
"media": APK_URL
}).encode("utf-8"),
headers={"apikey": EVOLUTION_API_KEY, "Content-Type": "application/json"}
)
try:
with urllib.request.urlopen(req_media, context=ctx, timeout=120) as resp:
print(" [OK] Documento APK enviado a WhatsApp (Status:", resp.status, ")", flush=True)
except Exception as e:
print(" [Error envío documento APK directo]:", e, flush=True)
print("\n✅ [DESPLIEGUE, SINCRONIZACIÓN Y ENVÍO A WHATSAPP COMPLETADOS]", flush=True)
if __name__ == "__main__":
main()