306 lines
12 KiB
TypeScript
306 lines
12 KiB
TypeScript
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<boolean> {
|
|
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<boolean> {
|
|
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<boolean> {
|
|
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;
|
|
}
|
|
|