import { api } from "../lib/api.js"; import { modal } from "../lib/modal.js"; class AliasesCrud extends HTMLElement { constructor() { super(); this.attachShadow({ mode: "open" }); this.items = []; this.products = []; this.selected = null; this.loading = false; this.searchQuery = ""; this.editMode = null; // 'create' | 'edit' | null // Productos mapeados con scores this.productMappings = []; this.shadowRoot.innerHTML = `
Equivalencias (Aliases)
Cargando...
Detalle
Selecciona un alias o crea uno nuevo
`; } connectedCallback() { this.shadowRoot.getElementById("search").oninput = (e) => { this.searchQuery = e.target.value; clearTimeout(this._searchTimer); this._searchTimer = setTimeout(() => this.load(), 300); }; this.shadowRoot.getElementById("newBtn").onclick = () => this.showCreateForm(); this.load(); this.loadProducts(); } async load() { this.loading = true; this.renderList(); try { const data = await api.aliases({ q: this.searchQuery, limit: 500 }); this.items = data.items || []; this.loading = false; this.renderList(); } catch (e) { console.error("Error loading aliases:", e); this.items = []; this.loading = false; this.renderList(); } } async loadProducts() { try { const data = await api.products({ limit: 2000 }); this.products = data.items || []; } catch (e) { console.error("Error loading products:", e); this.products = []; } } getProductName(id) { const p = this.products.find(x => x.woo_product_id === id); return p?.name || `Producto #${id}`; } renderList() { const list = this.shadowRoot.getElementById("list"); if (this.loading) { list.innerHTML = `
Cargando...
`; return; } if (!this.items.length) { list.innerHTML = `
No se encontraron aliases
`; return; } list.innerHTML = ""; for (const item of this.items) { const el = document.createElement("div"); el.className = "item" + (this.selected?.alias === item.alias ? " active" : ""); // Mostrar productos mapeados const mappings = item.product_mappings || []; let productsHtml = ""; if (mappings.length > 0) { const productNames = mappings.slice(0, 3).map(m => { const name = this.getProductName(m.woo_product_id); return `${name} (${m.score})`; }).join(", "); const more = mappings.length > 3 ? ` +${mappings.length - 3} más` : ""; productsHtml = `
→ ${productNames}${more}
`; } else if (item.woo_product_id) { // Fallback al producto único legacy const productName = this.getProductName(item.woo_product_id); const boost = item.boost ? ` (boost: +${item.boost})` : ""; productsHtml = `
→ ${productName}${boost}
`; } else { productsHtml = `
→ Sin productos
`; } el.innerHTML = `
"${item.alias}" ${mappings.length || (item.woo_product_id ? 1 : 0)} productos
${productsHtml} `; el.onclick = () => { this.selected = item; this.editMode = "edit"; // Cargar mappings this.productMappings = [...(item.product_mappings || [])]; // Si no hay mappings pero hay woo_product_id, crear uno por defecto if (this.productMappings.length === 0 && item.woo_product_id) { this.productMappings = [{ woo_product_id: item.woo_product_id, score: item.boost || 1.0 }]; } this.renderList(); this.renderForm(); }; list.appendChild(el); } } showCreateForm() { this.selected = null; this.editMode = "create"; this.productMappings = []; this.renderList(); this.renderForm(); } renderForm() { const form = this.shadowRoot.getElementById("form"); const title = this.shadowRoot.getElementById("formTitle"); if (!this.editMode) { title.textContent = "Detalle"; form.innerHTML = `
Selecciona un alias o crea uno nuevo
`; return; } const isCreate = this.editMode === "create"; title.textContent = isCreate ? "Nuevo Alias" : "Editar Alias"; const alias = this.selected?.alias || ""; const categoryHint = this.selected?.category_hint || ""; form.innerHTML = `
Sin tildes, en minusculas. Ej: "chimi" mapea a "Chimichurri"
${this.renderMappingsRows()}
Producto Score (0-1)
Mayor score = mayor prioridad. Si hay ambiguedad, se pregunta al usuario.
${!isCreate ? `` : ""}
`; this.shadowRoot.getElementById("saveBtn").onclick = () => this.save(); this.shadowRoot.getElementById("cancelBtn").onclick = () => this.cancel(); if (!isCreate) { this.shadowRoot.getElementById("deleteBtn").onclick = () => this.delete(); } this.setupMappingsTable(); this.setupAddMappingSelector(); } renderMappingsRows() { if (!this.productMappings.length) { return `No hay productos mapeados`; } return this.productMappings.map((mapping, idx) => { const name = this.getProductName(mapping.woo_product_id); return ` ${name} `; }).join(""); } setupMappingsTable() { const tbody = this.shadowRoot.getElementById("mappingsTableBody"); if (!tbody) return; // Handle score changes tbody.querySelectorAll(".mapping-score").forEach((input, idx) => { input.onchange = () => { this.productMappings[idx].score = parseFloat(input.value) || 1.0; }; }); // Handle remove buttons tbody.querySelectorAll(".btn-remove").forEach(btn => { btn.onclick = () => { const idx = parseInt(btn.dataset.idx, 10); this.productMappings.splice(idx, 1); this.renderForm(); }; }); } setupAddMappingSelector() { const searchInput = this.shadowRoot.getElementById("mappingSearch"); const dropdown = this.shadowRoot.getElementById("mappingDropdown"); const addBtn = this.shadowRoot.getElementById("addMappingBtn"); if (!searchInput || !dropdown) return; let selectedProductId = null; const renderDropdown = (query) => { const q = (query || "").toLowerCase().trim(); const existingIds = new Set(this.productMappings.map(m => m.woo_product_id)); let filtered = this.products.filter(p => !existingIds.has(p.woo_product_id)); if (q) { filtered = filtered.filter(p => p.name.toLowerCase().includes(q)); } filtered = filtered.slice(0, 30); if (!filtered.length) { dropdown.classList.remove("open"); return; } dropdown.innerHTML = filtered.map(p => `
${p.name} $${p.price || 0}
`).join(""); dropdown.querySelectorAll(".product-option").forEach(opt => { opt.onclick = () => { selectedProductId = parseInt(opt.dataset.id, 10); searchInput.value = this.getProductName(selectedProductId); dropdown.classList.remove("open"); }; }); dropdown.classList.add("open"); }; searchInput.oninput = () => { selectedProductId = null; clearTimeout(this._mappingTimer); this._mappingTimer = setTimeout(() => renderDropdown(searchInput.value), 150); }; searchInput.onfocus = () => renderDropdown(searchInput.value); addBtn.onclick = () => { if (!selectedProductId) { modal.warn("Selecciona un producto primero"); return; } this.productMappings.push({ woo_product_id: selectedProductId, score: 1.0, }); searchInput.value = ""; selectedProductId = null; this.renderForm(); }; // Close on outside click document.addEventListener("click", (e) => { if (!this.shadowRoot.getElementById("mappingSelector")?.contains(e.target)) { dropdown.classList.remove("open"); } }); } async save() { const aliasInput = this.shadowRoot.getElementById("aliasInput").value.trim().toLowerCase(); const categoryInput = this.shadowRoot.getElementById("categoryInput").value.trim(); if (!aliasInput) { modal.warn("El alias es requerido"); return; } if (!this.productMappings.length) { modal.warn("Agrega al menos un producto"); return; } // Usar el primer producto con mayor score como woo_product_id principal (legacy) const sortedMappings = [...this.productMappings].sort((a, b) => (b.score || 0) - (a.score || 0)); const primaryProduct = sortedMappings[0]; const data = { alias: aliasInput, woo_product_id: primaryProduct.woo_product_id, boost: primaryProduct.score || 1.0, category_hint: categoryInput || null, product_mappings: this.productMappings, }; try { if (this.editMode === "create") { await api.createAlias(data); } else { await api.updateAlias(this.selected.alias, data); } this.editMode = null; this.selected = null; this.productMappings = []; await this.load(); this.renderForm(); } catch (e) { console.error("Error saving alias:", e); modal.error("Error guardando: " + (e.message || e)); } } async delete() { if (!this.selected?.alias) return; const confirmed = await modal.confirm(`¿Eliminar el alias "${this.selected.alias}"?`); if (!confirmed) return; try { await api.deleteAlias(this.selected.alias); this.editMode = null; this.selected = null; this.productMappings = []; await this.load(); this.renderForm(); } catch (e) { console.error("Error deleting alias:", e); modal.error("Error eliminando: " + (e.message || e)); } } cancel() { this.editMode = null; this.selected = null; this.productMappings = []; this.renderList(); this.renderForm(); } } customElements.define("aliases-crud", AliasesCrud);