feat: auto-register customers on whatsapp orders and include Bold payment link in confirmation notification
This commit is contained in:
parent
c55abbac25
commit
610c1024e0
@ -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);
|
||||
|
||||
|
||||
@ -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` +
|
||||
`━━━━━━━━━━━━━━━━━━━━━━`
|
||||
);
|
||||
|
||||
|
||||
184
scripts/deploy_auto_register_and_bold_apk.py
Normal file
184
scripts/deploy_auto_register_and_bold_apk.py
Normal 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()
|
||||
Loading…
Reference in New Issue
Block a user