import { api } from "../lib/api.js"; const DAYS = [ { id: "lun", label: "Lunes", short: "L" }, { id: "mar", label: "Martes", short: "M" }, { id: "mie", label: "Miércoles", short: "X" }, { id: "jue", label: "Jueves", short: "J" }, { id: "vie", label: "Viernes", short: "V" }, { id: "sab", label: "Sábado", short: "S" }, { id: "dom", label: "Domingo", short: "D" }, ]; function makeZoneId(name) { return String(name || "").toLowerCase() .normalize("NFD").replace(/[̀-ͯ]/g, "") .replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") .slice(0, 40) || `zona-${Date.now().toString(36)}`; } class SettingsCrud extends HTMLElement { constructor() { super(); this.attachShadow({ mode: "open" }); this.settings = null; this.loading = false; this.saving = false; this.zones = []; this.selectedZoneId = null; this._mapEditor = null; this.shadowRoot.innerHTML = `
Cargando configuración...
`; } connectedCallback() { this.load(); } async load() { this.loading = true; this.render(); try { this.settings = await api.getSettings(); if (!this.settings.schedule) { this.settings.schedule = { delivery: {}, pickup: {} }; } const dz = this.settings.delivery_zones || {}; this.zones = Array.isArray(dz.zones) ? dz.zones.map((z) => ({ ...z })) : []; this.selectedZoneId = this.zones[0]?.id || null; this.loading = false; this.render(); } catch (e) { console.error("Error loading settings:", e); this.loading = false; this.showError("Error cargando configuración: " + e.message); } } getScheduleSlot(type, dayId) { return this.settings?.schedule?.[type]?.[dayId] || null; } setScheduleSlot(type, dayId, slot) { if (!this.settings.schedule) { this.settings.schedule = { delivery: {}, pickup: {} }; } if (!this.settings.schedule[type]) { this.settings.schedule[type] = {}; } this.settings.schedule[type][dayId] = slot; } renderScheduleGrid(type, enabled) { const defaultStart = type === "delivery" ? "09:00" : "08:00"; const defaultEnd = type === "delivery" ? "18:00" : "20:00"; return DAYS.map(day => { const slot = this.getScheduleSlot(type, day.id); const isActive = slot !== null && slot !== undefined; const start = slot?.start || defaultStart; const end = slot?.end || defaultEnd; return `
${day.label}
a
`; }).join(""); } ZONE_PALETTE_VARS() { return ["--chart-blue", "--chart-green", "--chart-purple", "--chart-orange", "--chart-pink", "--chart-gray"]; } zoneSwatchColor(idx) { const palette = this.ZONE_PALETTE_VARS(); return `var(${palette[idx % palette.length]})`; } formatDaysShort(days) { if (!Array.isArray(days) || !days.length) return "\u2014"; const order = ["lun","mar","mie","jue","vie","sab","dom"]; const idx = days.map((d) => order.indexOf(d)).filter((i) => i >= 0).sort((a, b) => a - b); if (idx.length >= 3 && idx.every((v, i, arr) => i === 0 || v === arr[i - 1] + 1)) { return `${order[idx[0]]}-${order[idx[idx.length - 1]]}`; } return idx.map((i) => order[i]).join("/"); } renderZonesList() { if (!this.zones.length) { return `
No hay zonas dibujadas todavía. Tocá Crear zona y dibujá un polígono en el mapa.
`; } return this.zones.map((z, i) => { const cost = z.delivery_cost != null ? `$${Number(z.delivery_cost).toLocaleString("es-AR")}` : ""; const days = this.formatDaysShort(z.delivery_days); const start = z.delivery_hours?.start?.slice(0, 5) || ""; const end = z.delivery_hours?.end?.slice(0, 5) || ""; const hours = start && end ? `${start}-${end}` : ""; const meta = [cost, days, hours].filter(Boolean).join(" · "); const active = z.id === this.selectedZoneId ? "active" : ""; const disabled = z.enabled === false ? "disabled" : ""; return `
${this.escapeHtml(z.name || z.id)}
${this.escapeHtml(meta || "sin configurar")}
`; }).join(""); } renderZoneForm() { const z = this.zones.find((x) => x.id === this.selectedZoneId); if (!z) { return `
Seleccioná una zona en la lista o dibujá una nueva en el mapa para configurarla.
`; } const days = z.delivery_days || []; const start = z.delivery_hours?.start?.slice(0, 5) || "10:00"; const end = z.delivery_hours?.end?.slice(0, 5) || "20:00"; return `
${DAYS.map((d) => ` ${d.short} `).join("")}
`; } renderZonesSummary() { const enabled = this.zones.filter((z) => z.enabled !== false); if (!this.zones.length) { return `
Dibujá al menos una zona en el mapa para que el bot pueda confirmar envíos. Sin zonas, todas las direcciones quedan sin validar.
`; } return `
${enabled.length} de ${this.zones.length} zona${this.zones.length > 1 ? "s" : ""} habilitada${enabled.length === 1 ? "" : "s"}.
`; } render() { const content = this.shadowRoot.getElementById("content"); if (this.loading) { content.innerHTML = `
Cargando configuración...
`; return; } if (!this.settings) { content.innerHTML = `
No se pudo cargar la configuración
`; return; } const s = this.settings; content.innerHTML = `

Configuración

Información del Negocio
Se usa en los mensajes del bot
El asistente virtual
Retiro en Tienda
Retiro habilitado
${this.renderScheduleGrid("pickup", s.pickup_enabled)}
Zonas de Entrega
Dibujá los polígonos en el mapa. Cada zona tiene su costo, días y rango horario. El bot matchea con la ubicación que el cliente comparta por WhatsApp.

Zonas

${this.renderZonesList()}
${this.renderZoneForm()}
${this.renderZonesSummary()}
`; this.setupEventListeners(); } setupEventListeners() { // Toggle pickup const pickupToggle = this.shadowRoot.getElementById("pickupToggle"); pickupToggle?.addEventListener("click", () => { this.settings.pickup_enabled = !this.settings.pickup_enabled; this.render(); }); // Day toggles this.shadowRoot.querySelectorAll(".day-toggle").forEach(toggle => { toggle.addEventListener("click", () => { const type = toggle.dataset.type; const day = toggle.dataset.day; const currentSlot = this.getScheduleSlot(type, day); if (currentSlot) { // Desactivar día this.setScheduleSlot(type, day, null); } else { // Activar día con horarios default const defaultStart = type === "delivery" ? "09:00" : "08:00"; const defaultEnd = type === "delivery" ? "18:00" : "20:00"; this.setScheduleSlot(type, day, { start: defaultStart, end: defaultEnd }); } this.render(); }); }); // Hour inputs - update on blur this.shadowRoot.querySelectorAll(".hour-start, .hour-end").forEach(input => { input.addEventListener("blur", () => { const type = input.dataset.type; const day = input.dataset.day; const isStart = input.classList.contains("hour-start"); const slot = this.getScheduleSlot(type, day); if (!slot) return; const value = input.value.trim(); if (isStart) { slot.start = value || (type === "delivery" ? "09:00" : "08:00"); } else { slot.end = value || (type === "delivery" ? "18:00" : "20:00"); } this.setScheduleSlot(type, day, slot); }); }); // Save button this.shadowRoot.getElementById("saveBtn")?.addEventListener("click", () => this.save()); // Reset button this.shadowRoot.getElementById("resetBtn")?.addEventListener("click", () => this.load()); this.setupZoneEditor(); } setupZoneEditor() { const editor = this.shadowRoot.getElementById("zoneMapEditor"); if (!editor) return; this._mapEditor = editor; editor.zones = this.zones; if (this.selectedZoneId) editor.selectedId = this.selectedZoneId; editor.addEventListener("change", (e) => { const next = e.detail.zones || []; const merged = []; for (const z of next) { const existing = this.zones.find((x) => x.id === z.id); if (existing) merged.push({ ...existing, polygon: z.polygon }); else merged.push(z); } this.zones = merged; this.refreshZonesPanel(); }); editor.addEventListener("select", (e) => { this.selectedZoneId = e.detail.id || null; this.refreshZonesPanel(); }); this.shadowRoot.getElementById("zoneCreateBtn")?.addEventListener("click", () => { this._mapEditor?.startDrawing(); }); this.attachZoneSideListeners(); } attachZoneSideListeners() { this.shadowRoot.querySelectorAll(".zone-row[data-zone-id]").forEach((row) => { row.addEventListener("click", () => { const id = row.dataset.zoneId; this.selectedZoneId = id; if (this._mapEditor) this._mapEditor.selectedId = id; this.refreshZonesPanel(); }); }); const z = this.zones.find((x) => x.id === this.selectedZoneId); if (!z) return; const onChange = () => this.refreshZonesList(); const nameEl = this.shadowRoot.getElementById("zoneName"); nameEl?.addEventListener("input", () => { z.name = nameEl.value; onChange(); }); const costEl = this.shadowRoot.getElementById("zoneCost"); costEl?.addEventListener("change", () => { z.delivery_cost = Number(costEl.value) || 0; onChange(); }); const minEl = this.shadowRoot.getElementById("zoneMin"); minEl?.addEventListener("change", () => { z.min_order_amount = Number(minEl.value) || 0; }); const startEl = this.shadowRoot.getElementById("zoneStart"); startEl?.addEventListener("change", () => { z.delivery_hours = { ...(z.delivery_hours || {}), start: startEl.value }; onChange(); }); const endEl = this.shadowRoot.getElementById("zoneEnd"); endEl?.addEventListener("change", () => { z.delivery_hours = { ...(z.delivery_hours || {}), end: endEl.value }; onChange(); }); const enabledEl = this.shadowRoot.getElementById("zoneEnabled"); enabledEl?.addEventListener("change", () => { z.enabled = enabledEl.value === "true"; onChange(); }); this.shadowRoot.querySelectorAll(".zone-day-pick").forEach((btn) => { btn.addEventListener("click", () => { const day = btn.dataset.day; const days = z.delivery_days || []; const idx = days.indexOf(day); if (idx >= 0) days.splice(idx, 1); else days.push(day); z.delivery_days = days; btn.classList.toggle("active", days.includes(day)); onChange(); }); }); this.shadowRoot.getElementById("zoneFitBtn")?.addEventListener("click", () => { if (this._mapEditor) this._mapEditor.selectedId = z.id; }); this.shadowRoot.getElementById("zoneDeleteBtn")?.addEventListener("click", () => { if (this._mapEditor) this._mapEditor.removeZone(z.id); this.zones = this.zones.filter((x) => x.id !== z.id); if (this.selectedZoneId === z.id) this.selectedZoneId = this.zones[0]?.id || null; this.refreshZonesPanel(); }); } refreshZonesPanel() { const list = this.shadowRoot.getElementById("zonesList"); const formSlot = this.shadowRoot.getElementById("zoneFormSlot"); if (list) list.innerHTML = this.renderZonesList(); if (formSlot) formSlot.innerHTML = this.renderZoneForm(); this.attachZoneSideListeners(); } refreshZonesList() { const list = this.shadowRoot.getElementById("zonesList"); if (list) list.innerHTML = this.renderZonesList(); this.shadowRoot.querySelectorAll(".zone-row[data-zone-id]").forEach((row) => { row.addEventListener("click", () => { const id = row.dataset.zoneId; this.selectedZoneId = id; if (this._mapEditor) this._mapEditor.selectedId = id; this.refreshZonesPanel(); }); }); } collectScheduleFromInputs() { // Sólo pickup: el horario de delivery vive ahora dentro de cada zona. const schedule = { pickup: {} }; this.shadowRoot.querySelectorAll(`.hour-start[data-type="pickup"]`).forEach((input) => { const day = input.dataset.day; const endInput = this.shadowRoot.querySelector(`.hour-end[data-type="pickup"][data-day="${day}"]`); const toggle = this.shadowRoot.querySelector(`.day-toggle[data-type="pickup"][data-day="${day}"]`); if (toggle?.classList.contains("active")) { schedule.pickup[day] = { start: input.value.trim() || "08:00", end: endInput?.value.trim() || "20:00", }; } }); return schedule; } async save() { // Collect schedule from inputs const schedule = this.collectScheduleFromInputs(); // Antes de serializar, refrescar polígonos desde el editor por si el // usuario editó vértices y no llegó a disparar otro evento change. if (this._mapEditor) { const live = this._mapEditor.zones; this.zones = this.zones.map((z) => { const fromMap = live.find((x) => x.id === z.id); return fromMap ? { ...z, polygon: fromMap.polygon } : z; }); } const cleanZones = this.zones .filter((z) => z.polygon && Array.isArray(z.polygon.coordinates)) .map((z) => ({ id: z.id, name: z.name || z.id, polygon: z.polygon, delivery_cost: Number(z.delivery_cost) || 0, delivery_days: Array.isArray(z.delivery_days) ? z.delivery_days : [], delivery_hours: z.delivery_hours || { start: "10:00", end: "20:00" }, min_order_amount: Number(z.min_order_amount) || 0, enabled: z.enabled !== false, })); const delivery_zones = { zones: cleanZones }; const data = { store_name: this.shadowRoot.getElementById("storeName")?.value || "", bot_name: this.shadowRoot.getElementById("botName")?.value || "", store_address: this.shadowRoot.getElementById("storeAddress")?.value || "", store_phone: this.shadowRoot.getElementById("storePhone")?.value || "", pickup_enabled: this.settings.pickup_enabled, schedule, delivery_zones, }; // Update settings with form values this.settings = { ...this.settings, ...data }; this.saving = true; this.render(); try { console.log("[settings-crud] Saving:", data); const result = await api.saveSettings(data); console.log("[settings-crud] Save result:", result); if (result.ok === false) { throw new Error(result.message || result.error || "Error desconocido"); } this.settings = result.settings || data; this.saving = false; this.showSuccess(result.message || "Configuración guardada correctamente"); this.render(); } catch (e) { console.error("[settings-crud] Error saving settings:", e); this.saving = false; this.showError("Error guardando: " + (e.message || e)); this.render(); } } escapeHtml(str) { return (str || "").replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); } showSuccess(msg) { const messages = this.shadowRoot.getElementById("messages"); messages.innerHTML = `
${msg}
`; setTimeout(() => { messages.innerHTML = ""; }, 4000); } showError(msg) { const messages = this.shadowRoot.getElementById("messages"); messages.innerHTML = `
${msg}
`; setTimeout(() => { messages.innerHTML = ""; }, 5000); } } customElements.define("settings-crud", SettingsCrud);