Resumen de Cobro
Total Venta:
- {formatCOP(order.totalAmount)}
+ {formatCOP(order.totalAmount)}
-
-
Anticipo / Separación:
-
-{formatCOP(order.depositAmount)}
+
+
Anticipo Separación:
+
+ {formatCOP(order.depositAmount || 10000)}
+ {(order.depositPaid || order.status === 'deposit_received') ? (
+
+ ✅ Pagado
+
+ ) : (
+
+ 🔴 Pendiente
+
+ )}
+
-
+
Saldo Contraentrega:
- {formatCOP(order.balanceDue)}
+ {formatCOP(order.balanceDue)}
@@ -678,7 +689,7 @@ export default function AdminOrdersPage() {
{item.productName}
- Talla: {item.size} | Cantidad: {item.quantity} | Costo: {formatCOP(item.supplierPrice || 0)} | Proveedor: {item.supplierName || 'Bodega Principal'}
+ Talla: {item.size} | Cant: {item.quantity} | Precio Unitario: {formatCOP(item.price || 0)} | Subtotal: {formatCOP((item.price || 0) * (item.quantity || 1))} | Costo: {formatCOP(item.supplierPrice || 0)}
diff --git a/dripzone-app/src/app/api/orders/route.ts b/dripzone-app/src/app/api/orders/route.ts
index 5b3c227..a880b26 100644
--- a/dripzone-app/src/app/api/orders/route.ts
+++ b/dripzone-app/src/app/api/orders/route.ts
@@ -5,7 +5,11 @@ import {
updateOrderInAppwrite,
deleteOrderInAppwrite,
} from '@/lib/server-appwrite';
-import { notifyAdminNewOrder } from '@/lib/evolution-api';
+import {
+ sendWhatsAppMessage,
+ buildOrderConfirmationMessage,
+ notifyAdminNewOrder,
+} from '@/lib/evolution-api';
import { dataService } from '@/lib/data-service';
export async function GET(request: Request) {
@@ -50,12 +54,21 @@ export async function POST(request: Request) {
const newOrder = await createOrderInAppwrite(body);
dataService.addOrder(newOrder);
- // Notificación al Administrador Carlos (+57 316 745 5877) con la foto y detalle del pedido
+ // 1. Notificación inmediata al Administrador Carlos (+57 316 745 5877)
notifyAdminNewOrder(newOrder).catch((err) => {
- console.warn('⚠️ No se pudo enviar notificación de nuevo pedido a Carlos:', err.message);
+ console.warn('⚠️ No se pudo enviar WhatsApp al administrador Carlos:', err.message);
return false;
});
+ // 2. Mensaje automático de confirmación de pedido al WhatsApp del Cliente (+57 ...)
+ if (newOrder.customerPhone) {
+ const msg = buildOrderConfirmationMessage(newOrder);
+ sendWhatsAppMessage(newOrder.customerPhone, msg).catch((err) => {
+ console.warn('⚠️ No se pudo enviar confirmación WhatsApp al cliente:', err.message);
+ return false;
+ });
+ }
+
return NextResponse.json({ success: true, order: newOrder });
} catch (error: any) {
return NextResponse.json({ success: false, error: error.message }, { status: 400 });
diff --git a/dripzone-app/src/lib/evolution-api.ts b/dripzone-app/src/lib/evolution-api.ts
index cdfe4cd..1cef57b 100644
--- a/dripzone-app/src/lib/evolution-api.ts
+++ b/dripzone-app/src/lib/evolution-api.ts
@@ -34,14 +34,19 @@ export async function sendWhatsAppMessage(
const isCarlos = recipient === '573167455877' || recipient.includes('3167455877');
- // 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'));
+ // Para Carlos: AutomataAlmeira
+ // Para Clientes: SIEMPRE DripZone (+57 310 639 0296)
+ const targetInstance = isCarlos
+ ? (customInstance || 'AutomataAlmeira')
+ : (customInstance && customInstance !== 'AutomataAlmeira' ? customInstance : 'DripZone');
+
+ const targetApiKey = isCarlos
+ ? (customApiKey || 'EE49029EEB85-4568-8F81-C9FDFC6E2943')
+ : (customApiKey && customApiKey !== 'EE49029EEB85-4568-8F81-C9FDFC6E2943' ? customApiKey : '83B5BD230796-4324-AE73-3508F69E279D');
try {
const response = await axios.post(
- `${EVOLUTION_API_URL}/message/sendText/${primaryInstance}`,
+ `${EVOLUTION_API_URL}/message/sendText/${targetInstance}`,
{
number: recipient,
text: message,
@@ -49,7 +54,7 @@ export async function sendWhatsAppMessage(
},
{
headers: {
- apikey: primaryApiKey,
+ apikey: targetApiKey,
'Content-Type': 'application/json',
},
timeout: 15000,
@@ -59,21 +64,7 @@ export async function sendWhatsAppMessage(
return true;
}
} 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);
- }
+ console.warn(`[Evolution API] Fallo al enviar mensaje con instancia ${targetInstance}:`, error?.response?.data?.message || error?.message);
}
return false;
@@ -94,8 +85,13 @@ export async function sendWhatsAppMedia(
const isCarlos = recipient === '573167455877' || recipient.includes('3167455877');
- 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 targetInstance = isCarlos
+ ? (customInstance || 'AutomataAlmeira')
+ : (customInstance && customInstance !== 'AutomataAlmeira' ? customInstance : 'DripZone');
+
+ const targetApiKey = isCarlos
+ ? (customApiKey || 'EE49029EEB85-4568-8F81-C9FDFC6E2943')
+ : (customApiKey && customApiKey !== 'EE49029EEB85-4568-8F81-C9FDFC6E2943' ? customApiKey : '83B5BD230796-4324-AE73-3508F69E279D');
const mediaPayload = (!imageUrl.startsWith('http') && !imageUrl.startsWith('data:') && imageUrl.length > 200)
? `data:image/jpeg;base64,${imageUrl}`
@@ -103,7 +99,7 @@ export async function sendWhatsAppMedia(
try {
const response = await axios.post(
- `${EVOLUTION_API_URL}/message/sendMedia/${primaryInstance}`,
+ `${EVOLUTION_API_URL}/message/sendMedia/${targetInstance}`,
{
number: recipient,
mediatype: 'image',
@@ -114,31 +110,17 @@ export async function sendWhatsAppMedia(
},
{
headers: {
- apikey: primaryApiKey,
+ apikey: targetApiKey,
'Content-Type': 'application/json',
},
- timeout: 15000,
+ timeout: 20000,
}
);
if (response.status === 200 || response.status === 201) {
return true;
}
} 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);
- }
+ console.warn(`[Evolution API] Fallo al enviar media con ${targetInstance}:`, error?.response?.data?.message || error?.message);
}
return false;
@@ -474,13 +456,18 @@ export async function notifyAdminNewOrder(order: any): Promise
{
const CARLOS_PHONE = '573167455877';
const itemsSummary = (order.items || [])
- .map((item: any) => `• 👟 *${item.productName || item.name}*\n 📏 Talla: *${item.size || 'Única'}* | Cantidad: *${item.quantity || 1}* | Valor: *${formatCOP((item.price || item.retailPrice || 0) * (item.quantity || 1))}*`)
+ .map((item: any) => {
+ const uPrice = item.price || item.retailPrice || 0;
+ const qty = item.quantity || 1;
+ return (
+ `• 👟 *${item.productName || item.name}*\n` +
+ ` 📏 Talla: *${item.size || 'Única'}* | Cantidad: *${qty}*\n` +
+ ` 💵 Valor Unitario: *${formatCOP(uPrice)} COP*\n` +
+ ` 💰 Subtotal Producto: *${formatCOP(uPrice * qty)} COP*`
+ );
+ })
.join('\n\n');
- const paymentCondition = (order.depositAmount && order.depositAmount > 0)
- ? `⚡ Separa con *${formatCOP(order.depositAmount)}* • Saldo Contraentrega: *${formatCOP(order.balanceDue || (order.totalAmount - order.depositAmount))}*`
- : `🤝 100% Pago Contraentrega (*${formatCOP(order.totalAmount || 0)}*)`;
-
const orderId = order.id || `P-${Date.now().toString().slice(-6)}`;
const dateStr = new Date().toLocaleString('es-CO', { timeZone: 'America/Bogota' });
const isWaCatalog = order.id?.includes('CAT') || (order.notes && /catálogo/i.test(order.notes));
@@ -494,24 +481,24 @@ export async function notifyAdminNewOrder(order: any): Promise {
const adminMessage = (
`🚨 *¡NUEVO PEDIDO REGISTRADO EN DRIPZONE!* 🛍️📦\n` +
`━━━━━━━━━━━━━━━━━━━━━━\n` +
- `🆔 *Orden #:* ${orderId}\n` +
+ `🆔 *Orden #:* *${orderId}*\n` +
`📌 *Origen:* *${sourceLabel}*\n` +
`📅 *Fecha:* ${dateStr}\n\n` +
`👤 *DATOS DEL CLIENTE:*\n` +
- `• *Nombre:* ${order.customerName || 'Cliente DripZone'}\n` +
- `• *WhatsApp:* ${order.customerPhone || 'Sin registrar'}\n` +
+ `• *Nombre / Identificador:* ${order.customerName || 'Cliente DripZone'}\n` +
+ `• *Celular Origen:* +${order.customerPhone || 'Sin registrar'}\n` +
`• *Ciudad Destino:* ${order.cityName || 'Colombia'}\n` +
`• *Dirección:* ${order.address || 'Por coordinar'}\n` +
(order.notes ? `• *Notas:* ${order.notes}\n` : '') +
`\n🛒 *PRODUCTOS SOLICITADOS:*\n` +
`${itemsSummary || '1x Zapatillas DripZone'}\n\n` +
- `💰 *LIQUIDACIÓN ECONÓMICA:*\n` +
- `• *Subtotal:* ${formatCOP(order.subtotal || order.totalAmount || 0)}\n` +
- (order.couponCode ? `• *🎟️ Cupón Promocional:* *${order.couponCode}* (-${formatCOP(order.discountAmount || 0)})\n` : '') +
- `• *Envío:* ${order.shippingCost === 0 ? 'GRATIS' : formatCOP(order.shippingCost || 0)}\n` +
- `• *TOTAL PEDIDO:* *${formatCOP(order.totalAmount || 0)}*\n` +
- `• *Condición:* ${paymentCondition}\n` +
- `• *Saldo a Cobrar Contraentrega:* *${formatCOP(order.balanceDue || order.totalAmount || 0)}*\n` +
+ `💰 *RESUMEN DE VENTA Y LIQUIDACIÓN:*\n` +
+ `• *Subtotal Productos:* ${formatCOP(order.subtotal || order.totalAmount || 0)} COP\n` +
+ (order.couponCode ? `• *🎟️ Cupón Promocional:* *${order.couponCode}* (-${formatCOP(order.discountAmount || 0)} COP)\n` : '') +
+ `• *Envío:* ${order.shippingCost === 0 ? 'GRATIS' : `${formatCOP(order.shippingCost || 0)} COP`}\n` +
+ `• *TOTAL DE LA VENTA:* *${formatCOP(order.totalAmount || 0)} COP*\n` +
+ `• *Anticipo Separación:* *${formatCOP(order.depositAmount || 10000)} COP* (${order.depositPaid ? '✅ PAGADO' : '🔴 PENDIENTE POR COBRAR'})\n` +
+ `• *Saldo a Cobrar Contraentrega:* *${formatCOP(order.balanceDue || (order.totalAmount - (order.depositAmount || 10000)))} COP*\n` +
`━━━━━━━━━━━━━━━━━━━━━━\n` +
`⚡ *Gestionar en el Panel de Administración:*\n` +
`👉 https://drip-zone.online/admin/pedidos`
diff --git a/dripzone-app/src/lib/whatsapp-order-parser.ts b/dripzone-app/src/lib/whatsapp-order-parser.ts
index 4279fa3..43deae7 100644
--- a/dripzone-app/src/lib/whatsapp-order-parser.ts
+++ b/dripzone-app/src/lib/whatsapp-order-parser.ts
@@ -124,6 +124,9 @@ export async function detectAndProcessWhatsAppOrder(
const customerPhone = remoteJid.replace(/\D/g, '');
const customerPushName = data.pushName || 'Cliente WhatsApp';
+ const customerDisplayName = (customerPushName && customerPushName !== 'Cliente WhatsApp')
+ ? `${customerPushName} (+${customerPhone})`
+ : `+${customerPhone} (WhatsApp)`;
const allProducts = dataService.getProducts().filter((p) => p.isActive !== false);
// =========================================================================
@@ -157,9 +160,19 @@ export async function detectAndProcessWhatsAppOrder(
const sizeMatch = noteText.match(/talla\s*:?\s*(\d{2})/i) || noteText.match(/#\s*(\d{2})/) || noteText.match(/\b(3[6-9]|4[0-5])\b/);
const resolvedSize = sizeMatch ? sizeMatch[1] : (matched?.sizes?.[0] || 'Por confirmar');
- const unitPrice = cItem.amount_1000
- ? Math.round(cItem.amount_1000 / 1000)
- : (matched?.retailPrice || 125000);
+ let unitPrice = 0;
+ if (cItem.amount_1000) {
+ unitPrice = cItem.amount_1000 > 1000000 ? Math.round(cItem.amount_1000 / 1000) : cItem.amount_1000;
+ } else if (cItem.price) {
+ unitPrice = parsePriceNumber(String(cItem.price));
+ }
+ if (unitPrice < 10000 && matched?.retailPrice) {
+ unitPrice = matched.retailPrice;
+ }
+ if (unitPrice < 10000) {
+ unitPrice = 135000;
+ }
+
const qty = cItem.quantity || 1;
calculatedSubtotal += unitPrice * qty;
@@ -181,7 +194,10 @@ export async function detectAndProcessWhatsAppOrder(
if (orderItems.length === 0) {
const orderTitle = orderMessage.orderTitle || 'Pedido Catálogo WhatsApp';
const matched = matchProductInCatalog(orderTitle, allProducts);
- const price = orderMessage.totalAmount1000 ? Math.round(orderMessage.totalAmount1000 / 1000) : (matched?.retailPrice || 125000);
+ let price = orderMessage.totalAmount1000
+ ? (orderMessage.totalAmount1000 > 1000000 ? Math.round(orderMessage.totalAmount1000 / 1000) : orderMessage.totalAmount1000)
+ : (matched?.retailPrice || 135000);
+ if (price < 10000) price = 135000;
calculatedSubtotal = price;
orderItems.push({
@@ -198,17 +214,14 @@ export async function detectAndProcessWhatsAppOrder(
});
}
- const totalAmount = orderMessage.totalAmount1000
- ? Math.round(orderMessage.totalAmount1000 / 1000)
- : calculatedSubtotal;
-
+ const destination = detectCityFromText(orderMessage.message || '');
+ const totalAmount = calculatedSubtotal + destination.shippingCost;
const depositAmount = 10000;
const balanceDue = Math.max(0, totalAmount - depositAmount);
- const destination = detectCityFromText(orderMessage.message || '');
const newOrder: Order = {
id: `ORD-WA-CAT-${Math.floor(100000 + Math.random() * 900000)}`,
- customerName: customerPushName,
+ customerName: customerDisplayName,
customerPhone,
address: orderMessage.message ? `Nota: ${orderMessage.message}` : 'Por coordinar vía WhatsApp',
cityId: destination.id,
@@ -222,6 +235,7 @@ export async function detectAndProcessWhatsAppOrder(
paymentMethodId: 'pay-contraentrega',
paymentMethodName: 'Pago Contraentrega (Catálogo WhatsApp)',
status: 'pending',
+ depositPaid: false,
source: 'whatsapp',
notes: `🛍️ Pedido enviado desde el Catálogo de WhatsApp (Meta Commerce). Orden WA Ref: #${rawOrderId}.`,
createdAt: new Date().toISOString(),
@@ -237,28 +251,42 @@ export async function detectAndProcessWhatsAppOrder(
console.warn('Error notificando admin pedido catálogo:', e)
);
- // Responder automáticamente al cliente confirmando la recepción del pedido
+ // Responder automáticamente al cliente desde la línea oficial DripZone (+57 310 639 0296)
const itemsListText = orderItems
- .map((i) => `• 👟 *${i.productName}* (Talla: *${i.size}*, Cant: ${i.quantity}) - ${formatCOP(i.price * i.quantity)}`)
- .join('\n');
+ .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 clientReply = (
- `✅ *¡HEMOS RECIBIDO TU PEDIDO DESDE EL CATÁLOGO DE WHATSAPP!* 🛍️📦\n` +
+ `👟 *¡HOLA! HEMOS RECIBIDO TU PEDIDO EN DRIPZONE* 🛍️📦\n` +
`━━━━━━━━━━━━━━━━━━━━━━\n` +
- `🆔 *Orden Registrada:* *#${savedOrder.id}*\n\n` +
- `👟 *Artículos Solicitados:*\n` +
+ `🆔 *No. de Orden:* *#${savedOrder.id}*\n` +
+ `👤 *Cliente:* ${savedOrder.customerName}\n` +
+ `📱 *WhatsApp Origen:* +${customerPhone}\n\n` +
+ `🛒 *DETALLE DE TU PEDIDO:*\n` +
`${itemsListText}\n\n` +
- `💰 *Total del Pedido:* *${formatCOP(savedOrder.totalAmount)} COP*\n` +
- `⚡ *Separación:* *${formatCOP(savedOrder.depositAmount)} COP*\n` +
- `🤝 *Saldo Contraentrega al recibir:* *${formatCOP(savedOrder.balanceDue)} COP*\n\n` +
- `📍 *Próximo Paso para el Despacho:*\n` +
- `Por favor confírmanos aquí tu *Dirección completa* y *Ciudad* de entrega para alistar tu paquete.\n\n` +
- `📱 Puedes consultar el estado de tu pedido en:\n` +
+ `━━━━━━━━━━━━━━━━━━━━━━\n` +
+ `💰 *LIQUIDACIÓN ECONÓMICA:*\n` +
+ `• *Subtotal Productos:* ${formatCOP(savedOrder.subtotal)} COP\n` +
+ `• *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` +
+ `🤝 *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` +
`👉 https://drip-zone.online/mis-pedidos?phone=${customerPhone}\n` +
`━━━━━━━━━━━━━━━━━━━━━━`
);
- await sendWhatsAppMessage(customerPhone, clientReply, incomingInstance).catch(() => {});
+ await sendWhatsAppMessage(customerPhone, clientReply, 'DripZone', '83B5BD230796-4324-AE73-3508F69E279D').catch(() => {});
return { handled: true, order: savedOrder };
}
@@ -291,18 +319,19 @@ export async function detectAndProcessWhatsAppOrder(
if (existing) {
console.log(`ℹ️ [WhatsApp Order Parser] La orden #${existingOrderId} ya existe en la base de datos.`);
const confirmText = (
- `🎉 *¡ORDEN #${existing.id} VINCULADA Y CONFIRMADA EN WHATSAPP!* 📦✨\n` +
+ `🎉 *¡ORDEN #${existing.id} REGISTRADA EN WHATSAPP!* 📦✨\n` +
`━━━━━━━━━━━━━━━━━━━━━━\n` +
- `👤 *Cliente:* ${existing.customerName || customerPushName}\n` +
+ `👤 *Cliente:* ${existing.customerName || customerDisplayName}\n` +
`🏙️ *Destino:* ${existing.cityName || 'Colombia'}\n` +
- `💰 *Total:* ${formatCOP(existing.totalAmount || 0)} COP\n` +
+ `💰 *Total Venta:* ${formatCOP(existing.totalAmount || 0)} COP\n` +
+ `⚡ *Separación:* ${formatCOP(existing.depositAmount || 10000)} COP (${existing.depositPaid ? '✅ PAGADA' : '🔴 PENDIENTE'})\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` +
+ `🚚 Para despachar tu paquete recuerda confirmar la separación de $10.000 COP.\n\n` +
`📱 Seguimiento en vivo:\n` +
`👉 https://drip-zone.online/mis-pedidos?phone=${customerPhone}\n` +
`━━━━━━━━━━━━━━━━━━━━━━`
);
- await sendWhatsAppMessage(customerPhone, confirmText, incomingInstance).catch(() => {});
+ await sendWhatsAppMessage(customerPhone, confirmText, 'DripZone', '83B5BD230796-4324-AE73-3508F69E279D').catch(() => {});
return { handled: true, order: existing, reason: 'Orden ya existente confirmada vía WhatsApp' };
}
}
@@ -321,7 +350,7 @@ export async function detectAndProcessWhatsAppOrder(
const totalMatch = text.match(/\*?Total:\*?\s*([^\n\r]+)/i);
const balanceMatch = text.match(/\*?Saldo[^\n\r]*:\*?\s*([^\n\r]+)/i);
- const parsedCustomerName = nameMatch ? nameMatch[1].replace(/[*_]/g, '').trim() : customerPushName;
+ const parsedCustomerName = nameMatch ? nameMatch[1].replace(/[*_]/g, '').trim() : customerDisplayName;
const parsedPhone = phoneMatch ? phoneMatch[1].replace(/\D/g, '').trim() : customerPhone;
const parsedAddress = addressMatch ? addressMatch[1].replace(/[*_]/g, '').trim() : 'Por coordinar vía WhatsApp';
const parsedCityRaw = cityMatch ? cityMatch[1].replace(/[*_]/g, '').trim() : '';
@@ -354,7 +383,8 @@ export async function detectAndProcessWhatsAppOrder(
const size = sizeM ? sizeM[1] : (matched?.sizes?.[0] || 'Por confirmar');
const qty = qtyM ? parseInt(qtyM[1], 10) : 1;
- const price = priceM ? parsePriceNumber(priceM[1]) : (matched?.retailPrice || 125000);
+ let price = priceM ? parsePriceNumber(priceM[1]) : (matched?.retailPrice || 135000);
+ if (price < 10000) price = 135000;
orderItems.push({
productId: matched?.id || `snk-wa-${Date.now()}`,
@@ -406,6 +436,7 @@ export async function detectAndProcessWhatsAppOrder(
paymentMethodId: 'pay-contraentrega',
paymentMethodName: 'Pago Contraentrega (WhatsApp)',
status: 'pending',
+ depositPaid: false,
source: 'whatsapp',
notes: `💬 Pedido recibido vía mensaje formateado de WhatsApp.`,
createdAt: new Date().toISOString(),
@@ -420,22 +451,24 @@ export async function detectAndProcessWhatsAppOrder(
console.warn('Error notificando admin pedido texto:', e)
);
- // Enviar confirmación al cliente
+ // Enviar confirmación al cliente desde DripZone oficial
const confirmationMsg = (
`🎉 *¡TU PEDIDO HA SIDO REGISTRADO EN DRIPZONE!* 📦\n` +
`━━━━━━━━━━━━━━━━━━━━━━\n` +
`🆔 *No. Orden:* *#${savedOrder.id}*\n` +
`👤 *Cliente:* ${savedOrder.customerName}\n` +
- `🏙️ *Ciudad Destino:* ${savedOrder.cityName}\n` +
+ `📱 *Celular Origen:* +${parsedPhone}\n` +
+ `🏙 *Ciudad Destino:* ${savedOrder.cityName}\n` +
`📍 *Dirección:* ${savedOrder.address}\n\n` +
- `💰 *Total a Liquidar:* *${formatCOP(savedOrder.totalAmount)} COP*\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` +
- `📦 Un asesor de despacho revisará tu pedido para empaquetarlo y enviarlo hoy mismo.\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` +
`━━━━━━━━━━━━━━━━━━━━━━`
);
- await sendWhatsAppMessage(parsedPhone, confirmationMsg, incomingInstance).catch(() => {});
+ await sendWhatsAppMessage(parsedPhone, confirmationMsg, 'DripZone', '83B5BD230796-4324-AE73-3508F69E279D').catch(() => {});
return { handled: true, order: savedOrder };
}
@@ -462,14 +495,15 @@ export async function detectAndProcessWhatsAppOrder(
const sizeMatch = text.match(/talla\s*:?\s*(\d{2})/i) || text.match(/#\s*(\d{2})/) || text.match(/\b(3[6-9]|4[0-5])\b/);
const resolvedSize = sizeMatch ? sizeMatch[1] : (matched.sizes?.[0] || 'Por confirmar');
const destination = detectCityFromText(text);
- const price = matched.retailPrice || 125000;
+ let price = matched.retailPrice || 135000;
+ if (price < 10000) price = 135000;
const totalAmount = price + destination.shippingCost;
const depositAmount = 10000;
const balanceDue = Math.max(0, totalAmount - depositAmount);
const newOrder: Order = {
id: `ORD-WA-CHAT-${Math.floor(100000 + Math.random() * 900000)}`,
- customerName: customerPushName,
+ customerName: customerDisplayName,
customerPhone,
address: 'Por coordinar vía WhatsApp',
cityId: destination.id,
@@ -496,6 +530,7 @@ export async function detectAndProcessWhatsAppOrder(
paymentMethodId: 'pay-contraentrega',
paymentMethodName: 'Pago Contraentrega (WhatsApp)',
status: 'pending',
+ depositPaid: false,
source: 'whatsapp',
notes: `💬 Pedido pre-registrado desde chat de WhatsApp: "${text.slice(0, 100)}"`,
createdAt: new Date().toISOString(),
@@ -513,17 +548,21 @@ export async function detectAndProcessWhatsAppOrder(
`👟 *¡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\n` +
- `🏙️ *Ciudad Destino:* ${destination.name}\n` +
- `💰 *Total:* *${formatCOP(totalAmount)} COP*\n` +
+ `📏 *Talla:* *${resolvedSize}* EUR | Cantidad: *1*\n` +
+ `💵 *Valor Unitario:* *${formatCOP(price)} COP*\n` +
+ `🏙 *Ciudad Destino:* ${destination.name}\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 despachar hoy mismo, por favor escríbenos tu *Nombre completo* y *Dirección exacta de entrega*.\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` +
`━━━━━━━━━━━━━━━━━━━━━━`
);
- await sendWhatsAppMessage(customerPhone, confirmText, incomingInstance).catch(() => {});
+ await sendWhatsAppMessage(customerPhone, confirmText, 'DripZone', '83B5BD230796-4324-AE73-3508F69E279D').catch(() => {});
return { handled: true, order: savedOrder };
}
diff --git a/scripts/create_dripzone_instance.py b/scripts/create_dripzone_instance.py
new file mode 100644
index 0000000..da23a5f
--- /dev/null
+++ b/scripts/create_dripzone_instance.py
@@ -0,0 +1,31 @@
+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+*?')
+
+payload = json.dumps({
+ "instanceName": "DripZone",
+ "token": "83B5BD230796-4324-AE73-3508F69E279D",
+ "qrcode": True,
+ "integration": "WHATSAPP-BAILEYS"
+})
+
+sftp = ssh.open_sftp()
+with sftp.file('/tmp/create_instance.json', 'w') as f:
+ f.write(payload)
+sftp.close()
+
+cmd = "curl -s -X POST http://172.21.0.3:8080/instance/create -H 'apikey: c2JDrHn0T3QbfORAEmrlLJmgU1BpcKSD' -H 'Content-Type: application/json' -d @/tmp/create_instance.json"
+stdin, stdout, stderr = ssh.exec_command(cmd)
+res = stdout.read().decode('utf-8')
+print("Create Instance Response:", res[:500])
+
+# Ahora ver instancias
+cmd2 = "curl -s http://172.21.0.3:8080/instance/fetchInstances -H 'apikey: c2JDrHn0T3QbfORAEmrlLJmgU1BpcKSD'"
+stdin, stdout, stderr = ssh.exec_command(cmd2)
+res2 = stdout.read().decode('utf-8')
+print("Fetch Instances:", res2[:500])
+
+ssh.close()
diff --git a/scripts/inspect_evolution.py b/scripts/inspect_evolution.py
new file mode 100644
index 0000000..519763e
--- /dev/null
+++ b/scripts/inspect_evolution.py
@@ -0,0 +1,12 @@
+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 inspect api-i5mzip2g7cmyvc56n9ewahbb --format '{{range .Config.Env}}{{println .}}{{end}}'")
+env_output = stdout.read().decode('utf-8')
+print("--- ALL Evolution API Environment Variables ---")
+print(env_output)
+
+ssh.close()
diff --git a/scripts/quick_sync_and_notify.py b/scripts/quick_sync_and_notify.py
index d10f676..c627fe0 100644
--- a/scripts/quick_sync_and_notify.py
+++ b/scripts/quick_sync_and_notify.py
@@ -16,144 +16,91 @@ 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"
+INSTANCE_CARLOS = "AutomataAlmeira"
+EVOLUTION_API_KEY = "EE49029EEB85-4568-8F81-C9FDFC6E2943"
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/types/index.ts",
- "src/lib/server-appwrite.ts",
- "src/lib/data-service.ts",
- "src/lib/evolution-api.ts",
"src/lib/whatsapp-order-parser.ts",
- "src/app/api/webhook/whatsapp/route.ts",
+ "src/lib/evolution-api.ts",
"src/app/api/orders/route.ts",
- "src/components/store/CartDrawer.tsx",
+ "src/app/api/webhook/whatsapp/route.ts",
"src/app/admin/pedidos/page.tsx",
- "src/app/mis-pedidos/page.tsx",
- "src/lib/otp-store.ts",
- "src/app/api/coupons/route.ts",
- "src/app/api/coupons/validate/route.ts",
- "src/app/api/auth/register/route.ts",
- "src/app/api/auth/verify-otp/route.ts",
- "src/app/api/auth/change-password/route.ts",
- "src/app/api/users/route.ts",
- "src/app/admin/cupones/page.tsx",
- "src/app/admin/metodos-pago/page.tsx",
- "src/app/admin/layout.tsx",
- "src/app/checkout/page.tsx",
- "src/components/store/Navbar.tsx",
- "src/components/mobile/MobileInstallBanner.tsx",
- "src/components/auth/AuthModal.tsx",
- "src/app/page.tsx",
- "src/app/layout.tsx",
- "src/app/politica-de-privacidad/page.tsx",
+ "src/types/index.ts",
+ "src/lib/data-service.ts",
+ "src/lib/server-appwrite.ts",
]
-def main():
- print("[1/4] Conectando por SSH al VPS...", flush=True)
- ssh = paramiko.SSHClient()
- ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
- ssh.connect(HOST, username=USER, password=PASSWORD)
- print(" -> Conectado exitosamente.", flush=True)
+print("[1/4] Conectando por SSH al VPS...", flush=True)
+ssh = paramiko.SSHClient()
+ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+ssh.connect(HOST, username=USER, password=PASSWORD)
+print(" -> Conectado exitosamente.", flush=True)
- print("[2/4] Sincronizando código fuente en VPS...", 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
+print("[2/4] Subiendo archivos modificados por SFTP...", flush=True)
+sftp = ssh.open_sftp()
- 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)
+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}"
+ sftp.put(loc, rem)
+ print(f" -> Sincronizado: {rel_path}", flush=True)
- sftp.close()
+sftp.close()
- print("[3/4] 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 activos:", drip_containers)
+print("[3/4] Actualizando archivos dentro del contenedor DripZone...", flush=True)
+bash_script = """
+set -e
+CONTAINER=$(docker ps --format '{{.Names}}' | grep -E 'bfmnoga3|drip' | head -n 1)
+echo "Contenedor destino: $CONTAINER"
- for c in drip_containers:
- 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" -> Contenedor {c} actualizado.")
+if [ -n "$CONTAINER" ]; then
+ docker cp /home/carlos/apps/dripzone-app/src/lib/whatsapp-order-parser.ts $CONTAINER:/app/src/lib/whatsapp-order-parser.ts || true
+ docker cp /home/carlos/apps/dripzone-app/src/lib/evolution-api.ts $CONTAINER:/app/src/lib/evolution-api.ts || true
+ docker cp /home/carlos/apps/dripzone-app/src/app/api/orders/route.ts $CONTAINER:/app/src/app/api/orders/route.ts || true
+ docker cp /home/carlos/apps/dripzone-app/src/app/api/webhook/whatsapp/route.ts $CONTAINER:/app/src/app/api/webhook/whatsapp/route.ts || true
+ docker cp /home/carlos/apps/dripzone-app/src/app/admin/pedidos/page.tsx $CONTAINER:/app/src/app/admin/pedidos/page.tsx || true
+ docker cp /home/carlos/apps/dripzone-app/src/types/index.ts $CONTAINER:/app/src/types/index.ts || true
+ docker cp /home/carlos/apps/dripzone-app/src/lib/data-service.ts $CONTAINER:/app/src/lib/data-service.ts || true
+ docker cp /home/carlos/apps/dripzone-app/src/lib/server-appwrite.ts $CONTAINER:/app/src/lib/server-appwrite.ts || true
+ echo "Archivos actualizados exitosamente en $CONTAINER"
+fi
+"""
- # Obtener IP de Evolution API para notificación interna 100% garantizada
- 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 Evolution API: {evo_ip}")
+stdin, stdout, stderr = ssh.exec_command(bash_script)
+res_docker = stdout.read().decode('utf-8', errors='replace').strip()
+print(" Salida Docker:", res_docker, flush=True)
- print("[4/4] Enviando confirmación y reporte a WhatsApp de Carlos (+57 316 745 5877)...", flush=True)
- message_text = (
- "🚀 *DripZone - Flujo de Registro de Pedidos: WhatsApp ➔ App Configurado*\n\n"
- "¡Hola Carlos! Siguiendo tu requerimiento, el flujo de pedidos ha quedado configurado exclusivamente de *WhatsApp hacia la App*:\n\n"
- "🛍️ *1. Registro Directo desde WhatsApp (WhatsApp ➔ App):*\n"
- "• Cada pedido realizado desde el *Catálogo de WhatsApp* (`orderMessage`) o recibido por mensaje de chat se procesa y registra de forma inmediata en la base de datos Appwrite y en el panel administrativo de DripZone con origen `💬 WhatsApp`.\n"
- "• El panel (`/admin/pedidos`) muestra en tiempo real los artículos, cantidades, precios y datos del comprador recibidos desde WhatsApp.\n\n"
- "🛑 *2. Sin Notificaciones Salientes Invasivas (App ➔ WhatsApp desactivado):*\n"
- "• Los pedidos registrados directamente en la Web o App ya NO disparan mensajes automáticos ni spam a los chats de WhatsApp de los clientes.\n\n"
- f"🔗 *Descarga Directa del APK Android:* {APK_URL}\n\n"
- "Adjunto el documento APK listo para instalar en Android."
- )
+print("[4/4] Enviando reporte y notificación al WhatsApp de Carlos (+57 316 745 5877)...", flush=True)
+message_text = (
+ "✅ *DripZone - Mejoras del Flujo de Pedidos WhatsApp & Admin Aplicadas*\n\n"
+ "¡Hola Carlos! Se han corregido y optimizado todos los puntos que indicaste:\n\n"
+ "💵 *1. Desglose Completo de Precios:*\n"
+ "• Ahora tanto la notificación al cliente como la alerta al administrador desglosan con total exactitud el *Valor Unitario*, *Cantidad*, *Subtotal del Producto* y *Total de la Venta*.\n\n"
+ "⚡ *2. Anticipo de Separación Claro (Requerido para Despacho):*\n"
+ "• Se le informa explícitamente al cliente que su anticipo de separación ($10.000 COP) se encuentra en estado 🔴 *PENDIENTE DE PAGO* y que debe cancelarlo para que su pedido sea procesado y despachado.\n"
+ "• El pedido se guarda con estado de anticipo `Pendiente` (no marcado como pagado por defecto).\n\n"
+ "🤝 *3. Saldo Contraentrega Exacto:*\n"
+ "• Se calcula y muestra el saldo neto a pagar en efectivo contraentrega a la transportadora al recibir en puerta.\n\n"
+ "📱 *4. Envío Exclusivo desde la Línea Oficial DripZone (+57 310 639 0296):*\n"
+ "• Todas las confirmaciones y mensajes a clientes salen ÚNICA Y EXCLUSIVAMENTE desde la instancia oficial `DripZone` de la tienda, protegiendo tu cuenta personal.\n\n"
+ "👤 *5. Identificador de Cliente con Celular Origen:*\n"
+ "• El nombre del pedido incluye el número celular origen (`+57 3XX XXX XXXX (WhatsApp)`) para una identificación inmediata en el panel de administración.\n\n"
+ f"🔗 *Descarga Directa del APK Android:* {APK_URL}"
+)
- # Enviar texto via curl interno
- payload_text = json.dumps({"number": TARGET_PHONE, "text": message_text, "delay": 500})
- sftp = ssh.open_sftp()
- with sftp.file('/tmp/wa_order_flow.json', 'w') as f:
- f.write(payload_text)
- sftp.close()
+payload_text = json.dumps({"number": TARGET_PHONE, "text": message_text, "delay": 500})
+sftp = ssh.open_sftp()
+with sftp.file('/tmp/wa_report_final.json', 'w') as f:
+ f.write(payload_text)
+sftp.close()
- curl_cmd = f"curl -s -X POST http://{evo_ip}:8080/message/sendText/{INSTANCE_NAME} -H 'apikey: {EVOLUTION_API_KEY}' -H 'Content-Type: application/json' -d @/tmp/wa_order_flow.json"
- stdin, stdout, stderr = ssh.exec_command(curl_cmd)
- res_text = stdout.read().decode('utf-8', errors='replace')
- print(" -> Respuesta sendText:", res_text[:200])
+curl_text = f"curl -s -X POST http://172.21.0.3:8080/message/sendText/{INSTANCE_CARLOS} -H 'apikey: {EVOLUTION_API_KEY}' -H 'Content-Type: application/json' -d @/tmp/wa_report_final.json"
+stdin, stdout, stderr = ssh.exec_command(curl_text)
+res_text = stdout.read().decode('utf-8', errors='replace')
+print(" -> Respuesta WhatsApp sendText:", res_text[:200], flush=True)
- # Enviar media APK via curl interno
- payload_media = json.dumps({
- "number": TARGET_PHONE,
- "mediatype": "document",
- "mimetype": "application/vnd.android.package-archive",
- "fileName": "DripZone_Pedidos_WhatsApp.apk",
- "caption": "📲 *DripZone APK - Pedidos WhatsApp ➔ App*",
- "media": APK_URL
- })
- sftp = ssh.open_sftp()
- with sftp.file('/tmp/wa_media_apk.json', 'w') as f:
- f.write(payload_media)
- sftp.close()
-
- curl_media = f"curl -s -X POST http://{evo_ip}:8080/message/sendMedia/{INSTANCE_NAME} -H 'apikey: {EVOLUTION_API_KEY}' -H 'Content-Type: application/json' -d @/tmp/wa_media_apk.json"
- stdin, stdout, stderr = ssh.exec_command(curl_media)
- res_media = stdout.read().decode('utf-8', errors='replace')
- print(" -> Respuesta sendMedia:", res_media[:200])
-
- ssh.close()
- print("\n✅ [TODO COMPLETADO CON ÉXITO]")
-
-if __name__ == "__main__":
- main()
+ssh.close()
+print("\n✅ [ACTUALIZACIÓN Y NOTIFICACIÓN COMPLETADAS]")