import { pool } from "../../shared/db/pool.js"; // ───────────────────────────────────────────────────────────── // Tenant Settings - CRUD // ───────────────────────────────────────────────────────────── // // Modelo actual: // - Datos del comercio: store_name, bot_name, store_address, store_phone. // - Pickup (retiro en tienda): pickup_enabled + pickup_days CSV + // pickup_hours_start/end TIME, y `schedule.pickup` JSONB para horario por día. // - Delivery: TODO vive en `delivery_zones.zones[]` (polígonos GeoJSON con // costo, días y rango horario por zona). El bot valida zona usando la // ubicación que el cliente comparte por WhatsApp. export async function getSettings({ tenantId }) { const sql = ` SELECT id, tenant_id, store_name, bot_name, store_address, store_phone, pickup_enabled, pickup_days, pickup_hours_start::text as pickup_hours_start, pickup_hours_end::text as pickup_hours_end, schedule, delivery_zones, created_at, updated_at FROM tenant_settings WHERE tenant_id = $1 LIMIT 1 `; const { rows } = await pool.query(sql, [tenantId]); return rows[0] || null; } export async function upsertSettings({ tenantId, settings }) { const { store_name, bot_name, store_address, store_phone, pickup_enabled, pickup_days, pickup_hours_start, pickup_hours_end, schedule, delivery_zones, } = settings; const sql = ` INSERT INTO tenant_settings ( tenant_id, store_name, bot_name, store_address, store_phone, pickup_enabled, pickup_days, pickup_hours_start, pickup_hours_end, schedule, delivery_zones ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) ON CONFLICT (tenant_id) DO UPDATE SET store_name = COALESCE(EXCLUDED.store_name, tenant_settings.store_name), bot_name = COALESCE(EXCLUDED.bot_name, tenant_settings.bot_name), store_address = COALESCE(EXCLUDED.store_address, tenant_settings.store_address), store_phone = COALESCE(EXCLUDED.store_phone, tenant_settings.store_phone), pickup_enabled = COALESCE(EXCLUDED.pickup_enabled, tenant_settings.pickup_enabled), pickup_days = COALESCE(EXCLUDED.pickup_days, tenant_settings.pickup_days), pickup_hours_start = COALESCE(EXCLUDED.pickup_hours_start, tenant_settings.pickup_hours_start), pickup_hours_end = COALESCE(EXCLUDED.pickup_hours_end, tenant_settings.pickup_hours_end), schedule = COALESCE(EXCLUDED.schedule, tenant_settings.schedule), delivery_zones = COALESCE(EXCLUDED.delivery_zones, tenant_settings.delivery_zones), updated_at = NOW() RETURNING id, tenant_id, store_name, bot_name, store_address, store_phone, pickup_enabled, pickup_days, pickup_hours_start::text as pickup_hours_start, pickup_hours_end::text as pickup_hours_end, schedule, delivery_zones, created_at, updated_at `; const params = [ tenantId, store_name || null, bot_name || null, store_address || null, store_phone || null, pickup_enabled ?? null, pickup_days || null, pickup_hours_start || null, pickup_hours_end || null, schedule ? JSON.stringify(schedule) : null, delivery_zones ? JSON.stringify(delivery_zones) : null, ]; const { rows } = await pool.query(sql, params); return rows[0]; } /** * Formatea schedule.pickup ({ lun: { start, end } }) en prosa para mostrar al * cliente. Agrupa días con mismos horarios: "Lun a Vie de 9 a 14hs". */ function formatScheduleHours(scheduleObj, enabled) { if (!enabled || !scheduleObj || typeof scheduleObj !== "object") { return enabled === false ? "No disponible" : ""; } const dayOrder = ["lun", "mar", "mie", "jue", "vie", "sab", "dom"]; const dayNames = { lun: "Lunes", mar: "Martes", mie: "Miércoles", jue: "Jueves", vie: "Viernes", sab: "Sábado", dom: "Domingo", }; const groups = {}; for (const day of dayOrder) { const slot = scheduleObj[day]; if (!slot || !slot.start || !slot.end) continue; const key = `${slot.start}-${slot.end}`; if (!groups[key]) groups[key] = { start: slot.start, end: slot.end, days: [] }; groups[key].days.push(day); } if (!Object.keys(groups).length) return ""; const parts = Object.values(groups).map((g) => { const days = g.days; let dayStr; if (days.length >= 3) { const indices = days.map((d) => dayOrder.indexOf(d)); const isConsecutive = indices.every((v, i, arr) => i === 0 || v === arr[i - 1] + 1); dayStr = isConsecutive ? `${dayNames[days[0]]} a ${dayNames[days[days.length - 1]]}` : days.map((d) => dayNames[d]).join(", "); } else if (days.length === 2) { dayStr = `${dayNames[days[0]]} y ${dayNames[days[1]]}`; } else { dayStr = dayNames[days[0]]; } return `${dayStr} de ${g.start.slice(0, 5)} a ${g.end.slice(0, 5)}`; }); return parts.join(", "); } function formatLegacyPickupHours(enabled, days, start, end) { if (!enabled) return "No disponible"; if (!days || !start || !end) return ""; const daysFormatted = days.split(",").map((d) => d.trim()).join(", "); return `${daysFormatted} de ${start.slice(0, 5)} a ${end.slice(0, 5)}`; } /** * Forma de la storeConfig que consume el agente y su workingMemory. */ export async function getStoreConfig({ tenantId }) { const settings = await getSettings({ tenantId }); if (!settings) { return { name: "la carnicería", botName: "Piaf", hours: "", address: "", phone: "", pickupHours: "", schedule: null, delivery_zones: {}, }; } const schedule = settings.schedule || {}; const pickupHours = schedule.pickup && Object.keys(schedule.pickup).length ? formatScheduleHours(schedule.pickup, settings.pickup_enabled) : formatLegacyPickupHours( settings.pickup_enabled, settings.pickup_days, settings.pickup_hours_start, settings.pickup_hours_end, ); return { name: settings.store_name || "la carnicería", botName: settings.bot_name || "Piaf", hours: settings.pickup_enabled ? pickupHours : "", address: settings.store_address || "", phone: settings.store_phone || "", pickupHours, pickupEnabled: settings.pickup_enabled, schedule, delivery_zones: settings.delivery_zones || {}, }; }