import axios from 'axios'; import { formatCOP } from './pricing-engine'; const EVOLUTION_API_URL = process.env.EVOLUTION_API_URL || 'https://evolution.almeira.com.co'; // Instancia oficial exclusiva de DripZone (Para pedidos, confirmaciones, OTP, cobros Bold y atención a clientes) const EVOLUTION_API_KEY = process.env.EVOLUTION_API_KEY || '83B5BD230796-4324-AE73-3508F69E279D'; const EVOLUTION_INSTANCE_NAME = process.env.EVOLUTION_INSTANCE_NAME || 'DripZone'; // Instancia personal de Carlos (Para scraping de publicaciones y grupos de WhatsApp) export const EVOLUTION_FEED_API_KEY = process.env.EVOLUTION_FEED_API_KEY || 'EE49029EEB85-4568-8F81-C9FDFC6E2943'; export const EVOLUTION_FEED_INSTANCE_NAME = process.env.EVOLUTION_FEED_INSTANCE_NAME || 'AutomataAlmeira'; function resolveRecipient(target: string): string { const trimmed = target.trim(); if (trimmed.includes('@')) { return trimmed; } const cleanDigits = trimmed.replace(/\D/g, ''); return cleanDigits.startsWith('57') ? cleanDigits : `57${cleanDigits}`; } /** * Enviar mensaje de texto por WhatsApp a través de Evolution API v2 * Usa por defecto la línea oficial DripZone */ export async function sendWhatsAppMessage( toPhone: string, message: string, customInstance?: string, customApiKey?: string ): Promise { const recipient = resolveRecipient(toPhone); if (!recipient) return false; const instance = customInstance || EVOLUTION_INSTANCE_NAME; const apiKey = customApiKey || EVOLUTION_API_KEY; try { const response = await axios.post( `${EVOLUTION_API_URL}/message/sendText/${instance}`, { number: recipient, text: message, delay: 1200, }, { headers: { apikey: apiKey, 'Content-Type': 'application/json', }, timeout: 20000, } ); return response.status === 200 || response.status === 201; } catch (error: any) { console.error(`Error enviando mensaje de WhatsApp (${instance}):`, error?.response?.data || error?.message); return false; } } /** * Enviar mensaje con foto/catálogo a través de Evolution API v2 * Usa por defecto la línea oficial DripZone */ export async function sendWhatsAppMedia( toPhone: string, imageUrl: string, caption: string, customInstance?: string, customApiKey?: string ): Promise { const recipient = resolveRecipient(toPhone); if (!recipient) return false; const instance = customInstance || EVOLUTION_INSTANCE_NAME; const apiKey = customApiKey || EVOLUTION_API_KEY; try { const response = await axios.post( `${EVOLUTION_API_URL}/message/sendMedia/${instance}`, { number: recipient, mediatype: 'image', mimetype: 'image/jpeg', caption: caption, media: imageUrl, delay: 1500, }, { headers: { apikey: apiKey, 'Content-Type': 'application/json', }, timeout: 25000, } ); return response.status === 200 || response.status === 201; } catch (error: any) { console.error(`Error enviando media por WhatsApp (${instance}):`, error?.response?.data || error?.message); return false; } } /** * Notifica inmediatamente la publicación automática de un nuevo producto * tanto al WhatsApp personal de Carlos (+57 316 745 5877) como al canal * de Notificaciones Almeira (120363428012905103@g.us). */ export async function notifyNewProductPublished( product: any, sourceGroup: string, mediaUrl?: string ): Promise<{ success: boolean; dispatchedTo: string[] }> { const NOTIFICATION_TARGETS = [ '573167455877', // Carlos (WhatsApp personal) '120363428012905103@g.us', // Notificaciones Almeira ]; const retailFormatted = formatCOP(product.retailPrice || 125000); const supplierFormatted = product.supplierPrice ? formatCOP(product.supplierPrice) : 'No especificado'; const sizesText = Array.isArray(product.sizes) && product.sizes.length > 0 ? product.sizes.join(', ') : '37, 38, 39, 40, 41, 42, 43'; const notificationCaption = ( `🚀 *¡NUEVO PRODUCTO PUBLICADO EN LA APP!* 🚀\n` + `━━━━━━━━━━━━━━━━━━━━━━\n` + `👟 *${product.name}*\n` + `🏷️ *Marca:* ${product.brand || 'Sneakers'} • *Categoría:* ${product.category || 'Zapatillas'}\n` + `💵 *Precio de Venta en la App:* *${retailFormatted}*\n` + `🏭 *Costo Proveedor:* ${supplierFormatted}\n` + `📏 *Tallas Disponibles:* ${sizesText}\n` + `📍 *Grupo Proveedor:* ${sourceGroup || 'Canal Mayorista'}\n\n` + `🌐 *Ver en la Tienda Web:*\n` + `👉 https://drip-zone.online\n\n` + `📱 *Disponible de inmediato en la aplicación móvil DripZone para todos los clientes.*\n` + `━━━━━━━━━━━━━━━━━━━━━━` ); // Resolver URL absoluta de la imagen para garantizar que Evolution API la descargue con éxito let resolvedMediaUrl = mediaUrl; if (!resolvedMediaUrl && product?.images && product.images.length > 0) { const rawImg = product.images[0]; if (rawImg.startsWith('http://') || rawImg.startsWith('https://')) { resolvedMediaUrl = rawImg; } else if (rawImg.startsWith('/')) { resolvedMediaUrl = `https://drip-zone.online${rawImg}`; } else { resolvedMediaUrl = `https://drip-zone.online/${rawImg}`; } } else if (resolvedMediaUrl && !resolvedMediaUrl.startsWith('http')) { resolvedMediaUrl = `https://drip-zone.online${resolvedMediaUrl.startsWith('/') ? '' : '/'}${resolvedMediaUrl}`; } const dispatchedTo: string[] = []; for (const target of NOTIFICATION_TARGETS) { try { let sent = false; // 1. Enviar prioritariamente con la foto del calzado en alta definición if (resolvedMediaUrl && (resolvedMediaUrl.startsWith('http://') || resolvedMediaUrl.startsWith('https://'))) { sent = await sendWhatsAppMedia(target, resolvedMediaUrl, notificationCaption); } // 2. Si no hay imagen o falló el envío multimedia, enviar como texto formateado if (!sent) { sent = await sendWhatsAppMessage(target, notificationCaption); } if (sent) { dispatchedTo.push(target); } } catch (err: any) { console.warn(`[notifyNewProductPublished] Error despachando a ${target}:`, err.message); } } return { success: dispatchedTo.length > 0, dispatchedTo, }; } import { dataService } from './data-service'; /** * Genera el mensaje de confirmación de pedido para el cliente y el administrador * utilizando el sistema dinámico de plantillas configurables. */ export function buildOrderConfirmationMessage(order: any): string { const itemsText = (order.items || []) .map((item: any) => `👟 *${item.productName}*\n 📏 Talla: *${item.size}* | Cantidad: ${item.quantity} | Valor: ${formatCOP(item.price)}`) .join('\n\n'); const settings = dataService.getSettings(); const paymentCondition = order.depositAmount > 0 ? `⚡ *Separación / Anticipo:* ${formatCOP(order.depositAmount)}\n🤝 *Saldo a Pagar Contraentrega:* ${formatCOP(order.balanceDue)}` : '🤝 *Pago 100% Contraentrega al recibir en tu casa.*'; return dataService.renderMessageTemplate('order_confirmation_customer', { tienda_nombre: settings?.storeName || 'DripZone Luxury', orden_id: order.id, cliente_nombre: order.customerName || 'Cliente', cliente_telefono: order.customerPhone || '', cliente_direccion: order.address || '', cliente_ciudad: order.cityName || 'Cali', articulos_resumen: itemsText, subtotal: formatCOP(order.subtotal || 0), costo_envio: order.shippingCost === 0 ? 'GRATIS' : formatCOP(order.shippingCost), total: formatCOP(order.totalAmount || 0), condicion_pago_resumen: paymentCondition, metodo_pago: order.paymentMethodName || 'Contraentrega', saldo_contraentrega: formatCOP(order.balanceDue || 0), anticipo: formatCOP(order.depositAmount || 0), }); } /** * Notifica inmediatamente al administrador Carlos (+57 316 745 5877) * y al canal de notificaciones cuando entra un nuevo pedido en la plataforma (web o app móvil), * INCLUYENDO la fotografía del calzado en la notificación oficial. */ export async function notifyAdminNewOrder(order: any): Promise { const CARLOS_PHONE = '573167455877'; const NOTIFICATION_GROUP = '120363428012905103@g.us'; 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))}*`) .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 adminMessage = ( `🚨 *¡NUEVO PEDIDO REGISTRADO EN DRIPZONE!* 🛍️📦\n` + `━━━━━━━━━━━━━━━━━━━━━━\n` + `🆔 *Orden #:* ${orderId}\n` + `📅 *Fecha:* ${dateStr}\n\n` + `👤 *DATOS DEL CLIENTE:*\n` + `• *Nombre:* ${order.customerName || 'Cliente DripZone'}\n` + `• *WhatsApp:* ${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` + `• *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` + `━━━━━━━━━━━━━━━━━━━━━━\n` + `⚡ *Gestionar en el Panel de Administración:*\n` + `👉 https://drip-zone.online/admin/pedidos` ); // Extraer y resolver la URL absoluta de la fotografía del calzado const firstItem = order.items?.[0]; const rawImage = firstItem?.image || (Array.isArray(firstItem?.images) ? firstItem.images[0] : null); let resolvedImageUrl: string | null = null; if (rawImage && typeof rawImage === 'string') { if (rawImage.startsWith('http://') || rawImage.startsWith('https://')) { resolvedImageUrl = rawImage; } else if (rawImage.startsWith('/')) { resolvedImageUrl = `https://drip-zone.online${rawImage}`; } else { resolvedImageUrl = `https://drip-zone.online/${rawImage}`; } } let success = false; try { // 1. Notificación principal al WhatsApp personal de Carlos CON LA FOTO DEL PRODUCTO let sentCarlos = false; if (resolvedImageUrl) { sentCarlos = await sendWhatsAppMedia(CARLOS_PHONE, resolvedImageUrl, adminMessage); } if (!sentCarlos) { sentCarlos = await sendWhatsAppMessage(CARLOS_PHONE, adminMessage); } if (sentCarlos) success = true; // 2. Notificación al canal de alertas (grupo) CON LA FOTO DEL PRODUCTO let sentGroup = false; if (resolvedImageUrl) { sentGroup = await sendWhatsAppMedia(NOTIFICATION_GROUP, resolvedImageUrl, adminMessage).catch(() => false); } if (!sentGroup) { await sendWhatsAppMessage(NOTIFICATION_GROUP, adminMessage).catch(() => {}); } } catch (err: any) { console.error('Error enviando notificación de pedido a Carlos:', err.message); } return success; }