modularizado de prompts

This commit is contained in:
Lucas Tettamanti
2026-01-25 20:51:33 -03:00
parent b91ece867b
commit a489ec66a2
43 changed files with 5408 additions and 89 deletions

View File

@@ -0,0 +1,371 @@
import { api } from "../lib/api.js";
const DAYS = [
{ id: "lun", label: "Lun" },
{ id: "mar", label: "Mar" },
{ id: "mie", label: "Mié" },
{ id: "jue", label: "Jue" },
{ id: "vie", label: "Vie" },
{ id: "sab", label: "Sáb" },
{ id: "dom", label: "Dom" },
];
class SettingsCrud extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
this.settings = null;
this.loading = false;
this.saving = false;
this.shadowRoot.innerHTML = `
<style>
:host { display:block; height:100%; padding:16px; overflow:auto; }
* { box-sizing:border-box; font-family:system-ui,Segoe UI,Roboto,Arial; }
.container { max-width:800px; margin:0 auto; }
.panel { background:#121823; border:1px solid #1e2a3a; border-radius:10px; padding:20px; margin-bottom:16px; }
.panel-title { font-size:16px; font-weight:700; color:#e7eef7; margin-bottom:16px; display:flex; align-items:center; gap:8px; }
.panel-title svg { width:20px; height:20px; fill:#1f6feb; }
.form-row { display:grid; grid-template-columns:1fr 1fr; gap:16px; margin-bottom:16px; }
.form-row.full { grid-template-columns:1fr; }
.field { }
.field label { display:block; font-size:12px; color:#8aa0b5; margin-bottom:6px; text-transform:uppercase; letter-spacing:.4px; }
.field-hint { font-size:11px; color:#6c7a89; margin-top:4px; }
input, select, textarea {
background:#0f1520; color:#e7eef7; border:1px solid #253245;
border-radius:8px; padding:10px 14px; font-size:14px; width:100%;
}
input:focus, select:focus, textarea:focus { outline:none; border-color:#1f6feb; }
input:disabled { opacity:.6; cursor:not-allowed; }
button {
cursor:pointer; background:#1f6feb; color:#fff; border:none;
border-radius:8px; padding:10px 20px; font-size:14px; font-weight:600;
}
button:hover { background:#1a5fd0; }
button:disabled { opacity:.5; cursor:not-allowed; }
button.secondary { background:#253245; }
button.secondary:hover { background:#2d3e52; }
.toggle-row { display:flex; align-items:center; gap:12px; margin-bottom:12px; }
.toggle {
position:relative; width:48px; height:26px;
background:#253245; border-radius:13px; cursor:pointer;
transition:background .2s;
}
.toggle.active { background:#1f6feb; }
.toggle::after {
content:''; position:absolute; top:3px; left:3px;
width:20px; height:20px; background:#fff; border-radius:50%;
transition:transform .2s;
}
.toggle.active::after { transform:translateX(22px); }
.toggle-label { font-size:14px; color:#e7eef7; }
.days-selector { display:flex; gap:6px; flex-wrap:wrap; }
.day-btn {
padding:8px 12px; border-radius:6px; font-size:12px; font-weight:600;
background:#253245; color:#8aa0b5; border:none; cursor:pointer;
transition:all .15s;
}
.day-btn:hover { background:#2d3e52; color:#e7eef7; }
.day-btn.selected { background:#1f6feb; color:#fff; }
.hours-row { display:flex; align-items:center; gap:12px; margin-top:12px; }
.hours-row input { width:90px; text-align:center; font-family:monospace; font-size:15px; letter-spacing:1px; }
.hours-row input::placeholder { color:#6c7a89; }
.hours-row span { color:#8aa0b5; }
.hours-row .hour-hint { font-size:11px; color:#6c7a89; margin-left:8px; }
.actions { display:flex; gap:12px; margin-top:24px; }
.loading { text-align:center; padding:60px; color:#8aa0b5; }
.success-msg {
background:#2ecc7130; border:1px solid #2ecc71;
color:#2ecc71; padding:12px 16px; border-radius:8px;
margin-bottom:16px; font-size:14px;
}
.error-msg {
background:#e74c3c30; border:1px solid #e74c3c;
color:#e74c3c; padding:12px 16px; border-radius:8px;
margin-bottom:16px; font-size:14px;
}
</style>
<div class="container">
<div id="messages"></div>
<div id="content">
<div class="loading">Cargando configuración...</div>
</div>
</div>
`;
}
connectedCallback() {
this.load();
}
async load() {
this.loading = true;
this.render();
try {
this.settings = await api.getSettings();
this.loading = false;
this.render();
} catch (e) {
console.error("Error loading settings:", e);
this.loading = false;
this.showError("Error cargando configuración: " + e.message);
}
}
render() {
const content = this.shadowRoot.getElementById("content");
if (this.loading) {
content.innerHTML = `<div class="loading">Cargando configuración...</div>`;
return;
}
if (!this.settings) {
content.innerHTML = `<div class="loading">No se pudo cargar la configuración</div>`;
return;
}
const s = this.settings;
const deliveryDays = (s.delivery_days || "").split(",").filter(d => d);
const pickupDays = (s.pickup_days || "").split(",").filter(d => d);
content.innerHTML = `
<!-- Info del Negocio -->
<div class="panel">
<div class="panel-title">
<svg viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg>
Información del Negocio
</div>
<div class="form-row">
<div class="field">
<label>Nombre del negocio</label>
<input type="text" id="storeName" value="${this.escapeHtml(s.store_name || "")}" placeholder="Ej: Carnicería Don Pedro" />
<div class="field-hint">Se usa en los mensajes del bot</div>
</div>
<div class="field">
<label>Nombre del bot</label>
<input type="text" id="botName" value="${this.escapeHtml(s.bot_name || "")}" placeholder="Ej: Piaf" />
<div class="field-hint">El asistente virtual</div>
</div>
</div>
<div class="form-row">
<div class="field">
<label>Dirección</label>
<input type="text" id="storeAddress" value="${this.escapeHtml(s.store_address || "")}" placeholder="Ej: Av. Corrientes 1234, CABA" />
</div>
<div class="field">
<label>Teléfono</label>
<input type="text" id="storePhone" value="${this.escapeHtml(s.store_phone || "")}" placeholder="Ej: +54 11 1234-5678" />
</div>
</div>
</div>
<!-- Delivery -->
<div class="panel">
<div class="panel-title">
<svg viewBox="0 0 24 24"><path d="M18.92 6.01C18.72 5.42 18.16 5 17.5 5h-11c-.66 0-1.21.42-1.42 1.01L3 12v8c0 .55.45 1 1 1h1c.55 0 1-.45 1-1v-1h12v1c0 .55.45 1 1 1h1c.55 0 1-.45 1-1v-8l-2.08-5.99zM6.5 16c-.83 0-1.5-.67-1.5-1.5S5.67 13 6.5 13s1.5.67 1.5 1.5S7.33 16 6.5 16zm11 0c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zM5 11l1.5-4.5h11L19 11H5z"/></svg>
Delivery (Envío a domicilio)
</div>
<div class="toggle-row">
<div class="toggle ${s.delivery_enabled ? "active" : ""}" id="deliveryToggle"></div>
<span class="toggle-label">Delivery habilitado</span>
</div>
<div id="deliveryOptions" style="${s.delivery_enabled ? "" : "opacity:0.5;pointer-events:none;"}">
<div class="field">
<label>Días disponibles</label>
<div class="days-selector" id="deliveryDays">
${DAYS.map(d => `
<button type="button" class="day-btn ${deliveryDays.includes(d.id) ? "selected" : ""}" data-day="${d.id}">${d.label}</button>
`).join("")}
</div>
</div>
<div class="hours-row">
<span>Horario:</span>
<input type="text" id="deliveryStart" value="${s.delivery_hours_start || "09:00"}" placeholder="09:00" pattern="([01]?[0-9]|2[0-3]):[0-5][0-9]" maxlength="5" />
<span>a</span>
<input type="text" id="deliveryEnd" value="${s.delivery_hours_end || "18:00"}" placeholder="18:00" pattern="([01]?[0-9]|2[0-3]):[0-5][0-9]" maxlength="5" />
<span class="hour-hint">(formato 24hs)</span>
</div>
<div class="field" style="margin-top:12px;">
<label>Pedido mínimo ($)</label>
<input type="number" id="deliveryMinOrder" value="${s.delivery_min_order || 0}" min="0" step="100" style="width:150px;" />
</div>
</div>
</div>
<!-- Retiro en tienda -->
<div class="panel">
<div class="panel-title">
<svg viewBox="0 0 24 24"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 14l-5-5 1.41-1.41L12 14.17l4.59-4.58L18 11l-6 6z"/></svg>
Retiro en Tienda
</div>
<div class="toggle-row">
<div class="toggle ${s.pickup_enabled ? "active" : ""}" id="pickupToggle"></div>
<span class="toggle-label">Retiro en tienda habilitado</span>
</div>
<div id="pickupOptions" style="${s.pickup_enabled ? "" : "opacity:0.5;pointer-events:none;"}">
<div class="field">
<label>Días disponibles</label>
<div class="days-selector" id="pickupDays">
${DAYS.map(d => `
<button type="button" class="day-btn ${pickupDays.includes(d.id) ? "selected" : ""}" data-day="${d.id}">${d.label}</button>
`).join("")}
</div>
</div>
<div class="hours-row">
<span>Horario:</span>
<input type="text" id="pickupStart" value="${s.pickup_hours_start || "08:00"}" placeholder="08:00" pattern="([01]?[0-9]|2[0-3]):[0-5][0-9]" maxlength="5" />
<span>a</span>
<input type="text" id="pickupEnd" value="${s.pickup_hours_end || "20:00"}" placeholder="20:00" pattern="([01]?[0-9]|2[0-3]):[0-5][0-9]" maxlength="5" />
<span class="hour-hint">(formato 24hs)</span>
</div>
</div>
</div>
<div class="actions">
<button id="saveBtn" ${this.saving ? "disabled" : ""}>${this.saving ? "Guardando..." : "Guardar Configuración"}</button>
<button id="resetBtn" class="secondary">Restaurar</button>
</div>
`;
this.setupEventListeners();
}
setupEventListeners() {
// Toggle delivery
const deliveryToggle = this.shadowRoot.getElementById("deliveryToggle");
deliveryToggle?.addEventListener("click", () => {
this.settings.delivery_enabled = !this.settings.delivery_enabled;
this.render();
});
// Toggle pickup
const pickupToggle = this.shadowRoot.getElementById("pickupToggle");
pickupToggle?.addEventListener("click", () => {
this.settings.pickup_enabled = !this.settings.pickup_enabled;
this.render();
});
// Delivery days
this.shadowRoot.querySelectorAll("#deliveryDays .day-btn").forEach(btn => {
btn.addEventListener("click", () => {
const day = btn.dataset.day;
let days = (this.settings.delivery_days || "").split(",").filter(d => d);
if (days.includes(day)) {
days = days.filter(d => d !== day);
} else {
days.push(day);
}
// Ordenar días
days.sort((a, b) => DAYS.findIndex(d => d.id === a) - DAYS.findIndex(d => d.id === b));
this.settings.delivery_days = days.join(",");
this.render();
});
});
// Pickup days
this.shadowRoot.querySelectorAll("#pickupDays .day-btn").forEach(btn => {
btn.addEventListener("click", () => {
const day = btn.dataset.day;
let days = (this.settings.pickup_days || "").split(",").filter(d => d);
if (days.includes(day)) {
days = days.filter(d => d !== day);
} else {
days.push(day);
}
days.sort((a, b) => DAYS.findIndex(d => d.id === a) - DAYS.findIndex(d => d.id === b));
this.settings.pickup_days = days.join(",");
this.render();
});
});
// Save button
this.shadowRoot.getElementById("saveBtn")?.addEventListener("click", () => this.save());
// Reset button
this.shadowRoot.getElementById("resetBtn")?.addEventListener("click", () => this.load());
}
async save() {
// Collect form data BEFORE re-rendering
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 || "",
delivery_enabled: this.settings.delivery_enabled,
delivery_days: this.settings.delivery_days,
delivery_hours_start: this.shadowRoot.getElementById("deliveryStart")?.value || "09:00",
delivery_hours_end: this.shadowRoot.getElementById("deliveryEnd")?.value || "18:00",
delivery_min_order: parseFloat(this.shadowRoot.getElementById("deliveryMinOrder")?.value) || 0,
pickup_enabled: this.settings.pickup_enabled,
pickup_days: this.settings.pickup_days,
pickup_hours_start: this.shadowRoot.getElementById("pickupStart")?.value || "08:00",
pickup_hours_end: this.shadowRoot.getElementById("pickupEnd")?.value || "20:00",
};
// Update settings with form values so they persist through render
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
}
showSuccess(msg) {
const messages = this.shadowRoot.getElementById("messages");
messages.innerHTML = `<div class="success-msg">${msg}</div>`;
setTimeout(() => { messages.innerHTML = ""; }, 4000);
}
showError(msg) {
const messages = this.shadowRoot.getElementById("messages");
messages.innerHTML = `<div class="error-msg">${msg}</div>`;
setTimeout(() => { messages.innerHTML = ""; }, 5000);
}
}
customElements.define("settings-crud", SettingsCrud);