import { api } from "../lib/api.js"; function formatCurrency(value) { if (value == null) return "$0"; return new Intl.NumberFormat("es-AR", { style: "currency", currency: "ARS", maximumFractionDigits: 0 }).format(value); } function formatNumber(value) { if (value == null) return "0"; return new Intl.NumberFormat("es-AR", { maximumFractionDigits: 1 }).format(value); } class HomeDashboard extends HTMLElement { constructor() { super(); this.attachShadow({ mode: "open" }); this.stats = null; this.loading = false; this.charts = {}; this.shadowRoot.innerHTML = `

Dashboard de Ventas

Ventas Totales por Mes
Web vs WhatsApp
Comparativa Año a Año
Por Canal
Delivery vs Retiro
Efectivo vs Tarjeta
Top Productos por Facturación
Top por Kg Vendidos
Top por Unidades
`; } connectedCallback() { this.loadStats(); } disconnectedCallback() { // Destruir charts para liberar memoria Object.values(this.charts).forEach(chart => chart?.destroy?.()); this.charts = {}; } async loadStats() { this.loading = true; try { this.stats = await api.getOrderStats(); this.render(); } catch (err) { console.error("[home-dashboard] loadStats error:", err); } finally { this.loading = false; } } render() { if (!this.stats) return; // Actualizar sync info const syncInfo = this.shadowRoot.querySelector(".sync-info"); syncInfo.textContent = `${this.stats.total_in_cache || 0} pedidos en cache`; if (this.stats.synced > 0) { syncInfo.textContent += ` (${this.stats.synced} nuevos sincronizados)`; } // Renderizar KPIs this.renderKPIs(); // Renderizar charts this.renderMonthlyChart(); this.renderSourceChart(); this.renderYoyChart(); this.renderDonuts(); this.renderProductsChart(); this.renderKgChart(); this.renderUnitsChart(); } renderKPIs() { const totals = this.stats.totals_aggregated || {}; const kpiRow = this.shadowRoot.querySelector(".kpi-row"); kpiRow.innerHTML = `
${formatCurrency(totals.total_revenue)}
Total Facturado
${formatNumber(totals.total_orders)}
Pedidos
${formatCurrency(totals.by_source?.whatsapp)}
WhatsApp
${formatCurrency(totals.by_source?.web)}
Web
`; } renderMonthlyChart() { const ctx = this.shadowRoot.getElementById("monthly-chart"); if (!ctx) return; if (this.charts.monthly) this.charts.monthly.destroy(); const months = this.stats.months || []; const totals = this.stats.totals || []; this.charts.monthly = new Chart(ctx, { type: "bar", data: { labels: months.map(m => { const [y, mo] = m.split("-"); return `${mo}/${y.slice(2)}`; }), datasets: [{ label: "Ventas", data: totals, backgroundColor: "rgba(59, 130, 246, 0.8)", borderColor: "#3b82f6", borderWidth: 1, }], }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false }, }, scales: { y: { beginAtZero: true, ticks: { color: "#8aa0b5" }, grid: { color: "#1e2a3a" }, }, x: { ticks: { color: "#8aa0b5" }, grid: { display: false }, }, }, }, }); } renderSourceChart() { const ctx = this.shadowRoot.getElementById("source-chart"); if (!ctx) return; if (this.charts.source) this.charts.source.destroy(); const months = this.stats.months || []; const waData = this.stats.by_source?.whatsapp || []; const webData = this.stats.by_source?.web || []; this.charts.source = new Chart(ctx, { type: "bar", data: { labels: months.map(m => { const [y, mo] = m.split("-"); return `${mo}/${y.slice(2)}`; }), datasets: [ { label: "WhatsApp", data: waData, backgroundColor: "#25D366", }, { label: "Web", data: webData, backgroundColor: "#3b82f6", }, ], }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: "top", labels: { color: "#8aa0b5" }, }, }, scales: { y: { stacked: true, beginAtZero: true, ticks: { color: "#8aa0b5" }, grid: { color: "#1e2a3a" }, }, x: { stacked: true, ticks: { color: "#8aa0b5" }, grid: { display: false }, }, }, }, }); } renderYoyChart() { const ctx = this.shadowRoot.getElementById("yoy-chart"); if (!ctx) return; if (this.charts.yoy) this.charts.yoy.destroy(); const yoy = this.stats.yoy || {}; this.charts.yoy = new Chart(ctx, { type: "line", data: { labels: yoy.months || [], datasets: [ { label: String(yoy.current_year || "Actual"), data: yoy.current_year_data || [], borderColor: "#3b82f6", backgroundColor: "rgba(59, 130, 246, 0.1)", fill: true, tension: 0.3, }, { label: String(yoy.last_year || "Anterior"), data: yoy.last_year_data || [], borderColor: "#9CA3AF", backgroundColor: "rgba(156, 163, 175, 0.1)", fill: true, tension: 0.3, }, ], }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: "top", labels: { color: "#8aa0b5" }, }, }, scales: { y: { beginAtZero: true, ticks: { color: "#8aa0b5" }, grid: { color: "#1e2a3a" }, }, x: { ticks: { color: "#8aa0b5" }, grid: { display: false }, }, }, }, }); } renderDonuts() { const totals = this.stats.totals_aggregated || {}; // Source donut const sourceCtx = this.shadowRoot.getElementById("source-donut"); if (sourceCtx) { if (this.charts.sourceDonut) this.charts.sourceDonut.destroy(); this.charts.sourceDonut = new Chart(sourceCtx, { type: "doughnut", data: { labels: ["WhatsApp", "Web"], datasets: [{ data: [totals.by_source?.whatsapp || 0, totals.by_source?.web || 0], backgroundColor: ["#25D366", "#3b82f6"], }], }, options: this.getDonutOptions(), }); } // Shipping donut const shippingCtx = this.shadowRoot.getElementById("shipping-donut"); if (shippingCtx) { if (this.charts.shippingDonut) this.charts.shippingDonut.destroy(); this.charts.shippingDonut = new Chart(shippingCtx, { type: "doughnut", data: { labels: ["Delivery", "Retiro"], datasets: [{ data: [totals.by_shipping?.delivery || 0, totals.by_shipping?.pickup || 0], backgroundColor: ["#8B5CF6", "#F59E0B"], }], }, options: this.getDonutOptions(), }); } // Payment donut const paymentCtx = this.shadowRoot.getElementById("payment-donut"); if (paymentCtx) { if (this.charts.paymentDonut) this.charts.paymentDonut.destroy(); this.charts.paymentDonut = new Chart(paymentCtx, { type: "doughnut", data: { labels: ["Efectivo", "Tarjeta"], datasets: [{ data: [totals.by_payment?.cash || 0, totals.by_payment?.card || 0], backgroundColor: ["#10B981", "#EC4899"], }], }, options: this.getDonutOptions(), }); } } getDonutOptions() { return { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: "bottom", labels: { color: "#8aa0b5" }, }, }, }; } renderProductsChart() { const ctx = this.shadowRoot.getElementById("products-chart"); if (!ctx) return; if (this.charts.products) this.charts.products.destroy(); const products = this.stats.top_products_revenue || []; const labels = products.map(p => p.name?.slice(0, 30) || "Sin nombre"); const data = products.map(p => p.revenue || 0); this.charts.products = new Chart(ctx, { type: "bar", data: { labels, datasets: [{ label: "Facturación", data, backgroundColor: "rgba(59, 130, 246, 0.8)", borderColor: "#3b82f6", borderWidth: 1, }], }, options: { indexAxis: "y", responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false }, }, scales: { x: { beginAtZero: true, ticks: { color: "#8aa0b5" }, grid: { color: "#1e2a3a" }, }, y: { ticks: { color: "#8aa0b5" }, grid: { display: false }, }, }, }, }); } renderKgChart() { const ctx = this.shadowRoot.getElementById("kg-chart"); if (!ctx) return; if (this.charts.kg) this.charts.kg.destroy(); const products = this.stats.top_products_kg || []; const labels = products.map(p => p.name?.slice(0, 25) || "Sin nombre"); const data = products.map(p => p.kg || 0); this.charts.kg = new Chart(ctx, { type: "bar", data: { labels, datasets: [{ label: "Kg", data, backgroundColor: "rgba(139, 92, 246, 0.8)", borderColor: "#8B5CF6", borderWidth: 1, }], }, options: { indexAxis: "y", responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false }, }, scales: { x: { beginAtZero: true, ticks: { color: "#8aa0b5" }, grid: { color: "#1e2a3a" }, }, y: { ticks: { color: "#8aa0b5", font: { size: 10 } }, grid: { display: false }, }, }, }, }); } renderUnitsChart() { const ctx = this.shadowRoot.getElementById("units-chart"); if (!ctx) return; if (this.charts.units) this.charts.units.destroy(); const products = this.stats.top_products_units || []; const labels = products.map(p => p.name?.slice(0, 25) || "Sin nombre"); const data = products.map(p => p.units || 0); this.charts.units = new Chart(ctx, { type: "bar", data: { labels, datasets: [{ label: "Unidades", data, backgroundColor: "rgba(245, 158, 11, 0.8)", borderColor: "#F59E0B", borderWidth: 1, }], }, options: { indexAxis: "y", responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false }, }, scales: { x: { beginAtZero: true, ticks: { color: "#8aa0b5" }, grid: { color: "#1e2a3a" }, }, y: { ticks: { color: "#8aa0b5", font: { size: 10 } }, grid: { display: false }, }, }, }, }); } } customElements.define("home-dashboard", HomeDashboard);