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(); /** * Normaliza textos para comparaciones seguras sin tildes ni caracteres especiales */ function normalizeText(text: string): string { return (text || '') .toLowerCase() .normalize('NFD') .replace(/[\u0300-\u036f]/g, '') .trim(); } /** * Extrae números limpios de precios (ej: "$135.000 COP" -> 135000) */ function parsePriceNumber(raw: string): number { if (!raw) return 0; const digits = raw.replace(/[^\d]/g, ''); const num = parseInt(digits, 10); return isNaN(num) ? 0 : num; } /** * Busca una ciudad colombiana dentro de un texto */ function detectCityFromText(text: string): { id: string; name: string; shippingCost: number } { const norm = normalizeText(text); const cities = dataService.getCities(); for (const c of cities) { const cNorm = normalizeText(c.name); if (norm.includes(cNorm)) { return { id: c.id, name: c.name, shippingCost: c.shippingCost }; } } // Comprobar ciudades principales if (norm.includes('cali') || norm.includes('jamundi') || norm.includes('yumbo')) { return { id: 'city-cali', name: 'Cali', shippingCost: 0 }; } if (norm.includes('bogota') || norm.includes('soacha')) { return { id: 'city-bogota', name: 'Bogotá D.C.', shippingCost: 15000 }; } if (norm.includes('medellin') || norm.includes('bello') || norm.includes('envigado') || norm.includes('itagui')) { return { id: 'city-medellin', name: 'Medellín', shippingCost: 15000 }; } if (norm.includes('barranquilla') || norm.includes('soledad')) { return { id: 'city-barranquilla', name: 'Barranquilla', shippingCost: 18000 }; } return { id: 'city-cali', name: 'Cali', shippingCost: 0 }; } /** * Intenta hacer match de un texto con un producto del catálogo de la tienda */ function matchProductInCatalog(titleOrReference: string, allProducts: Product[]): Product | null { if (!titleOrReference) return null; const norm = normalizeText(titleOrReference); // 1. Por ID directo (ej: snk-...) const directId = allProducts.find((p) => p.id && norm.includes(p.id.toLowerCase())); if (directId) return directId; // 2. Por nombre exacto const exact = allProducts.find((p) => p.name && normalizeText(p.name) === norm); if (exact) return exact; // 3. Por inclusión de nombre const partial = allProducts.find((p) => { if (!p.name) return false; const pNorm = normalizeText(p.name); return norm.includes(pNorm) || (pNorm.length > 8 && norm.includes(pNorm.slice(0, 15))); }); if (partial) return partial; // 4. Por palabras clave significativas (marca + modelo) const words = norm.split(/\s+/).filter((w) => w.length >= 4); if (words.length >= 2) { const scored = allProducts.map((p) => { const pNorm = normalizeText(p.name + ' ' + (p.modelName || '') + ' ' + (p.brand || '')); const matches = words.filter((w) => pNorm.includes(w)).length; return { product: p, score: matches }; }).filter((item) => item.score >= 2); if (scored.length > 0) { scored.sort((a, b) => b.score - a.score); return scored[0].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 = { 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 { 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). * 2. Mensajes formateados de pedido (order_checkout_wa o carritos enviados por texto). * 3. Solicitudes directas de compra con producto y talla. */ export async function detectAndProcessWhatsAppOrder( payload: any, data: any, remoteJid: string, incomingInstance: string ): Promise<{ handled: boolean; order?: Order; reason?: string }> { try { const rawMessage = data.message || {}; const text = ( rawMessage.conversation || rawMessage.extendedTextMessage?.text || rawMessage.imageMessage?.caption || data.text || '' ).trim(); 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); // ========================================================================= // CASO 1: PEDIDO NATIVO DEL CATÁLOGO DE WHATSAPP (Meta Commerce orderMessage) // ========================================================================= const orderMessage = rawMessage.orderMessage || rawMessage.extendedTextMessage?.contextInfo?.quotedMessage?.orderMessage || data.orderMessage; if (orderMessage) { console.log('🛍️ [WhatsApp Order Parser] ¡Detectado pedido nativo del Catálogo de WhatsApp!', JSON.stringify(orderMessage).slice(0, 300)); const rawOrderId = orderMessage.orderId || `WA-CAT-${Date.now().toString().slice(-6)}`; const cacheKey = `cat-order:${customerPhone}:${rawOrderId}`; if (processedOrdersCache.has(cacheKey)) { return { handled: true, reason: 'Pedido de catálogo ya procesado previamente' }; } processedOrdersCache.add(cacheKey); const catalogItems = Array.isArray(orderMessage.orderItems) ? orderMessage.orderItems : []; const orderItems: OrderItem[] = []; let calculatedSubtotal = 0; for (const cItem of catalogItems) { const itemName = cItem.name || 'Zapatilla Catálogo'; const matched = matchProductInCatalog(cItem.retailer_id || itemName, allProducts); // Extraer talla si está indicada en el nombre o en la nota del cliente const noteText = (orderMessage.message || '') + ' ' + itemName; 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'); 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; orderItems.push({ productId: matched?.id || cItem.retailer_id || `snk-cat-${Date.now()}`, productName: matched?.name || itemName, brand: matched?.brand || 'Sneakers', size: resolvedSize, quantity: qty, price: unitPrice, supplierPrice: matched?.supplierPrice || Math.round(unitPrice * 0.7), supplierName: matched?.supplierName || 'DIFUSIÓN JOVIC STORE', supplierPhone: matched?.supplierPhone || '573167455877', image: matched?.images?.[0] || 'https://images.unsplash.com/photo-1595950653106-6c9ebd614d3a?auto=format&fit=crop&w=800&q=80', }); } // Si no vinieron items desglosados en orderItems, crear item genérico basado en el título del pedido if (orderItems.length === 0) { const orderTitle = orderMessage.orderTitle || 'Pedido Catálogo WhatsApp'; const matched = matchProductInCatalog(orderTitle, allProducts); 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({ productId: matched?.id || `snk-cat-${Date.now()}`, productName: matched?.name || orderTitle, brand: matched?.brand || 'Sneakers', size: 'Por confirmar', quantity: orderMessage.itemCount || 1, price, supplierPrice: matched?.supplierPrice || Math.round(price * 0.7), supplierName: matched?.supplierName || 'DIFUSIÓN JOVIC STORE', supplierPhone: matched?.supplierPhone || '573167455877', image: matched?.images?.[0] || 'https://images.unsplash.com/photo-1595950653106-6c9ebd614d3a?auto=format&fit=crop&w=800&q=80', }); } const destination = detectCityFromText(orderMessage.message || ''); 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)}`, userId: customerUser.id, customerName: customerUser.name || customerDisplayName, customerPhone, address: deliveryAddress, cityId: destination.id, cityName: destination.name, items: orderItems, subtotal: calculatedSubtotal, shippingCost: destination.shippingCost, totalAmount, depositAmount, balanceDue, 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(), }; // 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.`); // 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) ); // 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` + ` 📏 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 = ( `👟 *¡HOLA! HEMOS RECIBIDO TU PEDIDO EN DRIPZONE* 🛍️📦\n` + `━━━━━━━━━━━━━━━━━━━━━━\n` + `🆔 *No. de Orden:* *#${savedOrder.id}*\n` + `👤 *Cliente:* ${savedOrder.customerName}\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` + `💰 *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\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` + `👤 *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` + `━━━━━━━━━━━━━━━━━━━━━━` ); await sendWhatsAppMessage(customerPhone, clientReply, 'DripZone', '83B5BD230796-4324-AE73-3508F69E279D').catch(() => {}); return { handled: true, order: savedOrder }; } // ========================================================================= // CASO 2: MENSAJE DE TEXTO CON FORMATO DE PEDIDO ESTRUCTURADO (Carrito o Web) // ========================================================================= const isFormattedOrder = text.includes('CONFIRMACIÓN DE PEDIDO') || text.includes('NUEVO PEDIDO DE CALZADO') || text.includes('NUEVO PEDIDO EN') || (text.includes('Cliente:') && text.includes('Artículos:') && (text.includes('Total:') || text.includes('Saldo'))); if (isFormattedOrder) { console.log('📝 [WhatsApp Order Parser] Detectado mensaje de texto con formato de pedido estructurado'); // 1. Extraer ID si ya viene en el mensaje (ej: #ORD-WA-8921 o #WEB-4921) 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 enviar link de Bold si está pendiente if (existingOrderId) { let existing = dataService.getOrderById(existingOrderId); if (!existing) { try { const allOrders = await getOrdersFromAppwrite(); existing = allOrders.find((o: any) => o.id === existingOrderId); } catch (_) {} } if (existing) { console.log(`ℹ️ [WhatsApp Order Parser] La orden #${existingOrderId} ya existe en la base de datos.`); 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` + `👤 *Cliente:* ${existing.customerName || customerDisplayName}\n` + `🏙️ *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` + `👉 https://drip-zone.online/mis-pedidos?phone=${customerPhone}\n` + `━━━━━━━━━━━━━━━━━━━━━━` ); await sendWhatsAppMessage(customerPhone, confirmText, 'DripZone', '83B5BD230796-4324-AE73-3508F69E279D').catch(() => {}); return { handled: true, order: existing, reason: 'Orden ya existente confirmada vía WhatsApp' }; } } const cacheKey = `txt-order:${customerPhone}:${existingOrderId || text.slice(0, 40)}`; if (processedOrdersCache.has(cacheKey)) { return { handled: true, reason: 'Pedido de texto ya procesado' }; } processedOrdersCache.add(cacheKey); // Extraer datos del cliente mediante regex const nameMatch = text.match(/\*?Cliente:\*?\s*([^\n\r]+)/i); const phoneMatch = text.match(/\*?Teléfono:\*?\s*([^\n\r]+)/i); const addressMatch = text.match(/\*?Dirección:\*?\s*([^\n\r]+)/i); const cityMatch = text.match(/\*?Ciudad:\*?\s*([^\n\r]+)/i); 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() : 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() : ''; const destination = detectCityFromText(parsedCityRaw || text); const totalAmount = totalMatch ? parsePriceNumber(totalMatch[1]) : 135000; 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'); let inItemsSection = false; for (const line of lines) { if (/art[íi]culos:/i.test(line)) { inItemsSection = true; continue; } if (inItemsSection && (/total:/i.test(line) || /condici[oó]n/i.test(line) || /m[eé]todo/i.test(line))) { inItemsSection = false; break; } if (inItemsSection && line.trim().length > 3) { const cleanLine = line.replace(/^[•*\-\s]+/, '').replace(/[*_]/g, '').trim(); const matched = matchProductInCatalog(cleanLine, allProducts); const sizeM = cleanLine.match(/talla\s*:?\s*(\d{2})/i) || cleanLine.match(/#\s*(\d{2})/) || cleanLine.match(/\b(3[6-9]|4[0-5])\b/); const qtyM = cleanLine.match(/cant\s*:?\s*(\d+)/i) || cleanLine.match(/x(\d+)/i); const priceM = cleanLine.match(/\$?([0-9.,]+)\s*(?:cop)?$/i); const size = sizeM ? sizeM[1] : (matched?.sizes?.[0] || 'Por confirmar'); const qty = qtyM ? parseInt(qtyM[1], 10) : 1; let price = priceM ? parsePriceNumber(priceM[1]) : (matched?.retailPrice || 135000); if (price < 10000) price = 135000; orderItems.push({ productId: matched?.id || `snk-wa-${Date.now()}`, productName: matched?.name || cleanLine.slice(0, 40), brand: matched?.brand || 'Sneakers', size, quantity: qty, price, supplierPrice: matched?.supplierPrice || Math.round(price * 0.7), supplierName: matched?.supplierName || 'DIFUSIÓN JOVIC STORE', supplierPhone: matched?.supplierPhone || '573167455877', image: matched?.images?.[0] || 'https://images.unsplash.com/photo-1595950653106-6c9ebd614d3a?auto=format&fit=crop&w=800&q=80', }); } } // Si no se extrajeron items específicos, crear un item con el catálogo if (orderItems.length === 0) { const matched = matchProductInCatalog(text, allProducts); orderItems.push({ productId: matched?.id || `snk-wa-${Date.now()}`, productName: matched?.name || 'Zapatilla DripZone', brand: matched?.brand || 'Sneakers', size: 'Por confirmar', quantity: 1, price: totalAmount, supplierPrice: matched?.supplierPrice || Math.round(totalAmount * 0.7), supplierName: matched?.supplierName || 'DIFUSIÓN JOVIC STORE', supplierPhone: matched?.supplierPhone || '573167455877', image: matched?.images?.[0] || 'https://images.unsplash.com/photo-1595950653106-6c9ebd614d3a?auto=format&fit=crop&w=800&q=80', }); } const generatedId = existingOrderId || `ORD-WA-${Math.floor(100000 + Math.random() * 900000)}`; const newOrder: Order = { id: generatedId, userId: customerUser.id, customerName: customerUser.name || parsedCustomerName, customerPhone: parsedPhone, address: parsedAddress, cityId: destination.id, cityName: destination.name, items: orderItems, subtotal: totalAmount, shippingCost: destination.shippingCost, totalAmount, depositAmount, balanceDue, 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(), }; // 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.`); // 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) ); // 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` + `🆔 *No. Orden:* *#${savedOrder.id}*\n` + `👤 *Cliente:* ${savedOrder.customerName}\n` + `📱 *Celular Origen:* +${parsedPhone}\n` + `🏙 *Ciudad Destino:* ${savedOrder.cityName}\n` + `📍 *Dirección:* ${savedOrder.address}\n\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` + `━━━━━━━━━━━━━━━━━━━━━━` ); await sendWhatsAppMessage(parsedPhone, confirmationMsg, 'DripZone', '83B5BD230796-4324-AE73-3508F69E279D').catch(() => {}); return { handled: true, order: savedOrder }; } // ========================================================================= // CASO 3: PEDIDO EN LENGUAJE NATURAL (Ej: "Quiero pedir las Jordan 4 talla 41 para Cali") // ========================================================================= const lower = text.toLowerCase(); const hasOrderIntent = /quiero pedir|hacer un pedido|hacer el pedido|para pedir|me interesa pedir|quiero comprar|deseo comprar|encargar/i.test(lower); const hasSize = /talla\s*:?\s*(\d{2})|#\s*(\d{2})|\b(3[6-9]|4[0-5])\b/i.test(lower); if (hasOrderIntent && hasSize) { const matched = matchProductInCatalog(text, allProducts); if (matched) { console.log(`💡 [WhatsApp Order Parser] Detectado pedido en lenguaje natural para: "${matched.name}"`); const cacheKey = `nat-order:${customerPhone}:${matched.id}`; if (processedOrdersCache.has(cacheKey)) { return { handled: true, reason: 'Pedido natural reciente ya procesado' }; } processedOrdersCache.add(cacheKey); 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); 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); // 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)}`, userId: customerUser.id, customerName: customerUser.name || customerDisplayName, customerPhone, address: 'Por coordinar vía WhatsApp', cityId: destination.id, cityName: destination.name, items: [ { productId: matched.id, productName: matched.name, brand: matched.brand || 'Sneakers', size: resolvedSize, quantity: 1, price, supplierPrice: matched.supplierPrice || Math.round(price * 0.7), supplierName: matched.supplierName || 'DIFUSIÓN JOVIC STORE', supplierPhone: matched.supplierPhone || '573167455877', image: matched.images[0] || '', }, ], subtotal: price, shippingCost: destination.shippingCost, totalAmount, depositAmount, balanceDue, 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(), }; // 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` + `🏙 *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\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` + `━━━━━━━━━━━━━━━━━━━━━━` ); await sendWhatsAppMessage(customerPhone, confirmText, 'DripZone', '83B5BD230796-4324-AE73-3508F69E279D').catch(() => {}); return { handled: true, order: savedOrder }; } } return { handled: false }; } catch (error: any) { console.error('❌ Error en detectAndProcessWhatsAppOrder:', error); return { handled: false, reason: error.message }; } }