fix(orders): detailed price breakdown, pending deposit notice, and official DripZone line routing
This commit is contained in:
parent
418f2c96ce
commit
c55abbac25
@ -622,19 +622,30 @@ export default function AdminOrdersPage() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-1 bg-neutral-950/60 p-3 rounded-2xl border border-neutral-800/80">
|
||||
<div className="space-y-1.5 bg-neutral-950/60 p-3 rounded-2xl border border-neutral-800/80">
|
||||
<p className="font-bold text-neutral-400 uppercase text-[10px]">Resumen de Cobro</p>
|
||||
<div className="flex justify-between text-neutral-300">
|
||||
<span>Total Venta:</span>
|
||||
<strong className="text-white">{formatCOP(order.totalAmount)}</strong>
|
||||
<strong className="text-white font-mono">{formatCOP(order.totalAmount)}</strong>
|
||||
</div>
|
||||
<div className="flex justify-between text-neutral-300">
|
||||
<span>Anticipo / Separación:</span>
|
||||
<strong className="text-purple-400">-{formatCOP(order.depositAmount)}</strong>
|
||||
<div className="flex justify-between items-center text-neutral-300">
|
||||
<span>Anticipo Separación:</span>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<strong className="text-purple-300 font-mono">{formatCOP(order.depositAmount || 10000)}</strong>
|
||||
{(order.depositPaid || order.status === 'deposit_received') ? (
|
||||
<span className="text-[10px] font-bold px-1.5 py-0.5 rounded-md bg-emerald-500/20 text-emerald-400 border border-emerald-500/30">
|
||||
✅ Pagado
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-[10px] font-bold px-1.5 py-0.5 rounded-md bg-amber-500/20 text-amber-300 border border-amber-500/30">
|
||||
🔴 Pendiente
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between text-neutral-300 pt-1 border-t border-neutral-800 font-bold">
|
||||
<div className="flex justify-between text-neutral-300 pt-1.5 border-t border-neutral-800 font-bold">
|
||||
<span className="text-purple-300">Saldo Contraentrega:</span>
|
||||
<span className="text-emerald-400 text-sm">{formatCOP(order.balanceDue)}</span>
|
||||
<span className="text-emerald-400 text-sm font-mono">{formatCOP(order.balanceDue)}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -678,7 +689,7 @@ export default function AdminOrdersPage() {
|
||||
<div>
|
||||
<p className="font-bold text-white text-xs">{item.productName}</p>
|
||||
<p className="text-[11px] text-neutral-400">
|
||||
Talla: <strong className="text-purple-400">{item.size}</strong> | Cantidad: <strong>{item.quantity}</strong> | Costo: <strong className="font-mono text-emerald-300">{formatCOP(item.supplierPrice || 0)}</strong> | Proveedor: <strong className="text-purple-300">{item.supplierName || 'Bodega Principal'}</strong>
|
||||
Talla: <strong className="text-purple-400">{item.size}</strong> | Cant: <strong>{item.quantity}</strong> | Precio Unitario: <strong className="text-white font-mono">{formatCOP(item.price || 0)}</strong> | Subtotal: <strong className="text-emerald-400 font-mono">{formatCOP((item.price || 0) * (item.quantity || 1))}</strong> | Costo: <strong className="font-mono text-neutral-400">{formatCOP(item.supplierPrice || 0)}</strong>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -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 });
|
||||
|
||||
@ -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<boolean> {
|
||||
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<boolean> {
|
||||
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`
|
||||
|
||||
@ -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 };
|
||||
}
|
||||
|
||||
31
scripts/create_dripzone_instance.py
Normal file
31
scripts/create_dripzone_instance.py
Normal file
@ -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()
|
||||
12
scripts/inspect_evolution.py
Normal file
12
scripts/inspect_evolution.py
Normal file
@ -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()
|
||||
@ -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]")
|
||||
|
||||
Loading…
Reference in New Issue
Block a user