/** * customerProfile — perfil del cliente para "lo de siempre". * * Lookup por teléfono (extraído del chat_id WhatsApp) en woo_orders_cache. * Agrupa items por woo_product_id en los últimos 6 meses, top 5 frequent_items. * * Cache 10 min por chat_id. */ import { pool } from "../../shared/db/pool.js"; const CACHE_TTL_MS = 10 * 60 * 1000; const _cache = new Map(); function phoneFromChatId(chatId) { if (!chatId) return null; // chat_id típico: "5491133230322@s.whatsapp.net" const m = /^(\d+)/.exec(String(chatId)); return m ? m[1] : null; } function normalizePhone(p) { return String(p || "").replace(/[^\d]/g, ""); } /** * Devuelve perfil del cliente o null si no hay datos. */ export async function getCustomerProfile({ tenantId, chat_id }) { if (!tenantId || !chat_id) return null; const cacheKey = `${tenantId}:${chat_id}`; const cached = _cache.get(cacheKey); if (cached && Date.now() - cached.t < CACHE_TTL_MS) return cached.value; const phone = phoneFromChatId(chat_id); if (!phone) { _cache.set(cacheKey, { value: null, t: Date.now() }); return null; } try { const profile = await fetchProfile({ tenantId, phone }); _cache.set(cacheKey, { value: profile, t: Date.now() }); return profile; } catch (err) { console.error("[customerProfile] error:", err?.message || err); return null; } } async function fetchProfile({ tenantId, phone }) { const phoneClean = normalizePhone(phone); if (!phoneClean) return null; // Match phones que terminen igual (los Woo a veces vienen con +54 o sin) const phoneSuffix = phoneClean.slice(-8); const orderSql = ` SELECT id, woo_order_id, total, date_created, shipping_address_1, shipping_address_2, shipping_city, customer_name FROM woo_orders_cache WHERE tenant_id = $1 AND regexp_replace(coalesce(customer_phone,''), '\\D', '', 'g') LIKE '%' || $2 AND date_created > NOW() - INTERVAL '6 months' ORDER BY date_created DESC LIMIT 30 `; const { rows: orders } = await pool.query(orderSql, [tenantId, phoneSuffix]); if (orders.length === 0) { return { is_returning: false, last_order_at: null, frequent_items: [], preferred_address: null }; } const orderIds = orders.map((o) => o.woo_order_id); const itemsSql = ` SELECT woo_product_id, product_name, sell_unit, COUNT(*) AS times, SUM(quantity) AS total_qty, AVG(quantity) AS avg_qty FROM woo_order_items WHERE tenant_id = $1 AND woo_order_id = ANY($2::bigint[]) AND woo_product_id IS NOT NULL GROUP BY woo_product_id, product_name, sell_unit ORDER BY times DESC, total_qty DESC LIMIT 5 `; const { rows: items } = await pool.query(itemsSql, [tenantId, orderIds]); const frequent_items = items.map((it) => ({ woo_id: Number(it.woo_product_id), name: it.product_name, times_ordered: Number(it.times), avg_qty: Number(it.avg_qty), avg_unit: it.sell_unit || null, })); const lastOrder = orders[0]; const preferredAddress = [lastOrder.shipping_address_1, lastOrder.shipping_address_2, lastOrder.shipping_city] .filter(Boolean) .join(", "); return { is_returning: true, last_order_at: lastOrder.date_created, customer_name: lastOrder.customer_name || null, frequent_items, preferred_address: preferredAddress || null, total_orders_last_6m: orders.length, }; } export function invalidateCustomerProfileCache(chat_id) { for (const k of _cache.keys()) if (k.endsWith(`:${chat_id}`)) _cache.delete(k); }