Skip to content

Instantly share code, notes, and snippets.

@Jcbertorello
Created December 28, 2025 17:45
Show Gist options
  • Select an option

  • Save Jcbertorello/107ca00197e6cdd914c4d70af56c9d67 to your computer and use it in GitHub Desktop.

Select an option

Save Jcbertorello/107ca00197e6cdd914c4d70af56c9d67 to your computer and use it in GitHub Desktop.
Dashboard Ajonjolí - 2025-12-28 17:45
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ventas de Confitería - Octubre 2025 - Cinexo</title>
<!-- Google Fonts para tipografía premium -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<!-- Chart.js + Plugin Datalabels -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@2"></script>
<!-- html2pdf.js for PDF export -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script>
<style>
/* === VARIABLES === */
:root {
--color-primary: #60B99A;
--color-primary-light: #7FCDB2;
--color-primary-dark: #4A9D80;
--color-primary-glow: rgba(96, 185, 154, 0.3);
--color-secondary: #1F3B4D;
--color-secondary-light: #2D5066;
--color-boleteria: #60B99A;
--color-candy: #FFB74D;
--color-candy-dark: #F59E0B;
--color-success: #10B981;
--color-success-light: #D1FAE5;
--color-danger: #EF4444;
--color-danger-light: #FEE2E2;
--color-warning: #F59E0B;
--color-warning-light: #FEF3C7;
--ocupacion-alta: #10B981;
--ocupacion-media: #F59E0B;
--ocupacion-baja: #EF4444;
--bg-primary: #F8FAFC;
--bg-card: #FFFFFF;
--bg-hover: #F1F5F9;
--border-light: #E2E8F0;
--border-medium: #CBD5E1;
--text-primary: #1E293B;
--text-secondary: #475569;
--text-muted: #64748B;
--text-light: #94A3B8;
--shadow-sm: 0 1px 2px rgba(0,0,0,0.05);
--shadow-md: 0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -1px rgba(0,0,0,0.06);
--shadow-lg: 0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -2px rgba(0,0,0,0.05);
--shadow-xl: 0 20px 25px -5px rgba(0,0,0,0.1), 0 10px 10px -5px rgba(0,0,0,0.04);
--shadow-glow: 0 0 20px var(--color-primary-glow);
}
/* === RESET === */
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Inter', system-ui, sans-serif;
background: linear-gradient(180deg, var(--bg-primary) 0%, #EFF6FF 100%);
color: var(--text-primary);
line-height: 1.6;
min-height: 100vh;
}
/* === DASHBOARD CONTAINER === */
.dashboard {
max-width: 1400px;
margin: 0 auto;
padding: 32px 24px;
}
/* === HEADER PREMIUM === */
.header {
background: linear-gradient(135deg, var(--color-secondary) 0%, var(--color-secondary-light) 100%);
border-radius: 20px;
padding: 28px 32px;
margin-bottom: 24px;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: var(--shadow-lg);
position: relative;
overflow: hidden;
}
.header::before {
content: '';
position: absolute;
top: -50%;
right: -20%;
width: 400px;
height: 400px;
background: radial-gradient(circle, rgba(255, 183, 77, 0.15) 0%, transparent 70%);
pointer-events: none;
}
.header-left {
display: flex;
align-items: center;
gap: 16px;
z-index: 1;
}
.logo {
width: 56px;
height: 56px;
background: linear-gradient(135deg, var(--color-candy) 0%, var(--color-candy-dark) 100%);
border-radius: 14px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
font-weight: 800;
color: white;
box-shadow: 0 8px 20px rgba(255, 183, 77, 0.4);
}
.header-title {
color: white;
font-size: 1.75rem;
font-weight: 800;
letter-spacing: -0.02em;
}
.header-subtitle {
color: rgba(255,255,255,0.8);
font-size: 0.9rem;
margin-top: 2px;
}
.header-actions {
display: flex;
gap: 12px;
z-index: 1;
}
.btn {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 12px 20px;
border-radius: 10px;
font-weight: 600;
font-size: 0.9rem;
cursor: pointer;
border: none;
transition: all 0.2s ease;
}
.btn-primary {
background: linear-gradient(135deg, var(--color-candy), var(--color-candy-dark));
color: white;
box-shadow: 0 4px 12px rgba(255, 183, 77, 0.4);
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(255, 183, 77, 0.5);
}
.btn-secondary {
background: rgba(255,255,255,0.15);
color: white;
border: 1px solid rgba(255,255,255,0.3);
backdrop-filter: blur(10px);
}
.btn-secondary:hover { background: rgba(255,255,255,0.25); }
/* === INFO BAR === */
.info-bar {
background: var(--bg-card);
border-radius: 12px;
padding: 16px 24px;
margin-bottom: 24px;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: var(--shadow-md);
border-left: 4px solid var(--color-candy);
}
.info-item {
display: flex;
align-items: center;
gap: 8px;
font-size: 0.875rem;
color: var(--text-muted);
}
.info-item strong { color: var(--text-primary); font-weight: 600; }
.info-item svg { width: 16px; height: 16px; }
/* === KPI CARDS PREMIUM === */
.kpi-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 24px;
margin-bottom: 32px;
}
.kpi-card {
background: var(--bg-card);
border-radius: 16px;
padding: 24px;
position: relative;
overflow: hidden;
box-shadow: var(--shadow-md);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
border: 1px solid var(--border-light);
}
.kpi-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg, var(--color-secondary), var(--color-secondary-light));
}
.kpi-card.candy::before { background: linear-gradient(90deg, var(--color-candy), var(--color-candy-dark)); }
.kpi-card:hover {
transform: translateY(-4px);
box-shadow: var(--shadow-xl);
border-color: var(--color-candy);
}
.kpi-icon {
width: 48px;
height: 48px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
margin-bottom: 16px;
}
.kpi-card.candy .kpi-icon { background: linear-gradient(135deg, rgba(255, 183, 77, 0.1), rgba(255, 183, 77, 0.2)); color: var(--color-candy-dark); }
.kpi-card.profit .kpi-icon { background: linear-gradient(135deg, rgba(96, 185, 154, 0.1), rgba(96, 185, 154, 0.2)); color: var(--color-primary-dark); }
.kpi-card.profit::before { background: linear-gradient(90deg, var(--color-primary), var(--color-primary-dark)); }
.kpi-label {
font-size: 0.7rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.1em;
color: var(--text-muted);
margin-bottom: 8px;
}
.kpi-value {
font-size: 2rem;
font-weight: 800;
color: var(--text-primary);
line-height: 1.1;
margin-bottom: 8px;
font-feature-settings: 'tnum';
}
.kpi-trend {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 4px 10px;
border-radius: 20px;
font-size: 0.75rem;
font-weight: 700;
}
.kpi-trend.up { background: var(--color-success-light); color: var(--color-success); }
.kpi-trend.down { background: var(--color-danger-light); color: var(--color-danger); }
.kpi-comparison { position: absolute; bottom: 12px; right: 12px; width: 60px; height: 30px; }
.kpi-sparkline { width: 100%; height: 100%; }
/* === GRID & CARDS === */
.grid-container {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
margin-bottom: 32px;
}
.grid-col-span-2 { grid-column: span 2; }
.grid-col-span-3 { grid-column: span 3; }
.card {
background: var(--bg-card);
border-radius: 16px;
padding: 24px;
box-shadow: var(--shadow-md);
border: 1px solid var(--border-light);
display: flex;
flex-direction: column;
}
.card-header {
margin-bottom: 20px;
}
.card-title {
font-size: 1.2rem;
font-weight: 700;
color: var(--text-primary);
}
.card-subtitle {
font-size: 0.85rem;
color: var(--text-muted);
margin-top: 4px;
}
.chart-container {
position: relative;
flex-grow: 1;
min-height: 350px; /* Asegura altura mínima */
}
/* === TABLE PREMIUM === */
.table-card {
background: var(--bg-card);
border-radius: 16px;
overflow: hidden;
box-shadow: var(--shadow-md);
border: 1px solid var(--border-light);
}
.table-header { padding: 20px 24px; border-bottom: 1px solid var(--border-light); }
.table-title { font-size: 1.1rem; font-weight: 700; color: var(--text-primary); }
.table-subtitle { font-size: 0.8rem; color: var(--text-muted); margin-top: 2px; }
.data-table { width: 100%; border-collapse: collapse; }
.data-table thead { background: var(--bg-hover); }
.data-table th { padding: 14px 20px; text-align: left; font-weight: 600; font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-muted); border-bottom: 2px solid var(--border-light); }
.data-table td { padding: 16px 20px; border-bottom: 1px solid var(--border-light); font-size: 0.9rem; color: var(--text-primary); transition: background 0.15s ease; }
.data-table tbody tr:hover td { background: rgba(255, 183, 77, 0.05); }
.data-table tbody tr:last-child td { border-bottom: none; }
.rank-medal { width: 28px; height: 28px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; font-weight: 800; font-size: 0.8rem; color: white; }
.rank-1 { background: linear-gradient(135deg, #FFD700, #FFA500); box-shadow: 0 2px 8px rgba(255, 215, 0, 0.4); }
.rank-2 { background: linear-gradient(135deg, #E8E8E8, #B8B8B8); box-shadow: 0 2px 8px rgba(192, 192, 192, 0.4); }
.rank-3 { background: linear-gradient(135deg, #CD7F32, #8B4513); box-shadow: 0 2px 8px rgba(205, 127, 50, 0.4); }
.rank-other { background: var(--bg-hover); color: var(--text-muted); }
.progress-cell { display: flex; align-items: center; gap: 12px; }
.progress-bar-container { flex: 1; height: 8px; background: var(--border-light); border-radius: 4px; overflow: hidden; min-width: 80px; }
.progress-bar-fill { height: 100%; border-radius: 4px; transition: width 1s ease-out; background: linear-gradient(90deg, var(--color-candy-dark), var(--color-candy)); }
.progress-value { font-weight: 600; font-size: 0.85rem; min-width: 45px; text-align: right; }
.value-currency { font-feature-settings: 'tnum'; font-weight: 600; }
/* === FOOTER === */
.footer { text-align: center; padding: 24px; font-size: 0.85rem; color: var(--text-muted); margin-top: 32px; }
.footer a { color: var(--color-secondary); font-weight: 600; text-decoration: none; }
.footer a:hover { text-decoration: underline; }
/* === PRINT/PDF === */
@media print {
body { background: white !important; }
.btn, .header-actions { display: none !important; }
.dashboard { padding: 16px !important; max-width: 100% !important; }
.header, .kpi-card, .card, .table-card { box-shadow: none !important; border: 1px solid #ddd !important; }
.grid-container { grid-template-columns: 1fr 1fr; }
.grid-col-span-2 { grid-column: span 2; }
.grid-col-span-3 { grid-column: span 2; }
}
/* === RESPONSIVE === */
@media (max-width: 1200px) {
.grid-container { grid-template-columns: 1fr 1fr; }
.grid-col-span-3 { grid-column: span 2; }
}
@media (max-width: 992px) {
.grid-container { grid-template-columns: 1fr; }
.grid-col-span-2, .grid-col-span-3 { grid-column: span 1; }
}
@media (max-width: 768px) {
.header { flex-direction: column; gap: 16px; text-align: center; }
.info-bar { flex-direction: column; gap: 12px; align-items: flex-start; }
.kpi-grid { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<div class="dashboard" id="dashboard-content">
<!-- Header Premium -->
<header class="header">
<div class="header-left">
<div class="logo">CX</div>
<div>
<h1 class="header-title">Ventas de Confitería - Octubre 2025</h1>
<p class="header-subtitle">Reporte de Performance para Cinexo</p>
</div>
</div>
<div class="header-actions">
<button class="btn btn-primary" onclick="downloadPDF()">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
<path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z"/>
<path d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3z"/>
</svg>
Descargar PDF
</button>
</div>
</header>
<!-- Info Bar -->
<div class="info-bar">
<div class="info-item">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 0 1 2.25-2.25h13.5A2.25 2.25 0 0 1 21 7.5v11.25m-18 0A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75m-18 0h18M12 11.25h.008v.008H12v-.008Z" />
</svg>
<span>Período del reporte: <strong>01/10/2025 - 31/10/2025</strong></span>
</div>
<div class="info-item">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
</svg>
<span>Última actualización: <strong>31/10/2025 23:59</strong></span>
</div>
</div>
<!-- KPI Grid -->
<div class="kpi-grid">
<div class="kpi-card candy">
<div class="kpi-icon">💰</div>
<div class="kpi-label">Ingresos Totales</div>
<div class="kpi-value" id="kpi-ingresos"></div>
<div class="kpi-trend up">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><path d="M7 17l5-5 5 5M7 7l5 5 5-5"/></svg>+8.2% vs mes anterior
</div>
<div class="kpi-comparison"><canvas class="kpi-sparkline" id="sparkline1"></canvas></div>
</div>
<div class="kpi-card candy">
<div class="kpi-icon">🛒</div>
<div class="kpi-label">Transacciones</div>
<div class="kpi-value" id="kpi-transacciones"></div>
<div class="kpi-trend up">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><path d="M7 17l5-5 5 5M7 7l5 5 5-5"/></svg>+5.1% vs mes anterior
</div>
<div class="kpi-comparison"><canvas class="kpi-sparkline" id="sparkline2"></canvas></div>
</div>
<div class="kpi-card candy">
<div class="kpi-icon">🧾</div>
<div class="kpi-label">Ticket Promedio</div>
<div class="kpi-value" id="kpi-ticket"></div>
<div class="kpi-trend up">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><path d="M7 17l5-5 5 5M7 7l5 5 5-5"/></svg>+3.0% vs mes anterior
</div>
<div class="kpi-comparison"><canvas class="kpi-sparkline" id="sparkline3"></canvas></div>
</div>
<div class="kpi-card profit">
<div class="kpi-icon">📈</div>
<div class="kpi-label">Margen de Utilidad</div>
<div class="kpi-value" id="kpi-margen"></div>
<div class="kpi-trend up">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><path d="M7 17l5-5 5 5M7 7l5 5 5-5"/></svg>+1.2 pts vs mes anterior
</div>
<div class="kpi-comparison"><canvas class="kpi-sparkline" id="sparkline4"></canvas></div>
</div>
</div>
<!-- Main Grid for Charts and Tables -->
<div class="grid-container">
<div class="card grid-col-span-3">
<div class="card-header">
<h2 class="card-title">Tendencia Diaria de Ingresos</h2>
<p class="card-subtitle">Evolución de las ventas de confitería durante Octubre 2025.</p>
</div>
<div class="chart-container">
<canvas id="chartTendenciaDiaria"></canvas>
</div>
</div>
<div class="card grid-col-span-2">
<div class="card-header">
<h2 class="card-title">Top 5 Productos por Ingresos</h2>
<p class="card-subtitle">Productos que más contribuyeron a la facturación de confitería.</p>
</div>
<div class="chart-container">
<canvas id="chartTopProductos"></canvas>
</div>
</div>
<div class="card">
<div class="card-header">
<h2 class="card-title">Mix de Ventas por Categoría</h2>
<p class="card-subtitle">Distribución porcentual de los ingresos por tipo de producto.</p>
</div>
<div class="chart-container">
<canvas id="chartMixCategorias"></canvas>
</div>
</div>
<div class="table-card grid-col-span-3">
<div class="table-header">
<div>
<h2 class="table-title">Ranking de Productos Detallado</h2>
<p class="table-subtitle">Performance completa de los productos más vendidos.</p>
</div>
</div>
<table class="data-table">
<thead>
<tr>
<th>#</th>
<th>Producto</th>
<th style="text-align:right;">Unidades</th>
<th style="text-align:right;">Ingresos</th>
<th>% del Total</th>
</tr>
</thead>
<tbody id="product-ranking-table">
<!-- Rows will be injected by JavaScript -->
</tbody>
</table>
</div>
</div>
<footer class="footer">
Dashboard interactivo diseñado y generado por <a href="https://mostachia.com" target="_blank">MostachIA</a> para Cinexo.
</footer>
</div>
<script>
// --- DATA FROM JSON ---
const jsonData = {
"dashboardType": "candy",
"dashboardTitle": "Ventas de Confitería - Octubre 2025",
"clientName": "Cinexo",
"periodo": {
"desde": "2025-10-01",
"hasta": "2025-10-31",
"descripcion": "Octubre 2025"
},
"kpis": {
"ingresosTotales": 46379500,
"cantidadTransacciones": 3003,
"ticketPromedioCandy": 15444.39,
"margenUtilidad": 38993124.89,
"margenPorcentaje": 95.71
},
"tendenciaDiaria": [{"fecha":"2025-10-01","ingresos":391500},{"fecha":"2025-10-02","ingresos":450000},{"fecha":"2025-10-03","ingresos":525000},{"fecha":"2025-10-04","ingresos":580000},{"fecha":"2025-10-05","ingresos":610000},{"fecha":"2025-10-06","ingresos":480000},{"fecha":"2025-10-07","ingresos":450000},{"fecha":"2025-10-08","ingresos":470000},{"fecha":"2025-10-09","ingresos":585000},{"fecha":"2025-10-10","ingresos":600000},{"fecha":"2025-10-11","ingresos":375000},{"fecha":"2025-10-12","ingresos":390000},{"fecha":"2025-10-13","ingresos":350000},{"fecha":"2025-10-14","ingresos":340000},{"fecha":"2025-10-15","ingresos":360000},{"fecha":"2025-10-16","ingresos":370000},{"fecha":"2025-10-17","ingresos":325000},{"fecha":"2025-10-18","ingresos":300000},{"fecha":"2025-10-19","ingresos":280000},{"fecha":"2025-10-20","ingresos":295000},{"fecha":"2025-10-21","ingresos":270000},{"fecha":"2025-10-22","ingresos":260000},{"fecha":"2025-10-23","ingresos":245000},{"fecha":"2025-10-24","ingresos":240000},{"fecha":"2025-10-25","ingresos":265000},{"fecha":"2025-10-26","ingresos":250000},{"fecha":"2025-10-27","ingresos":230000},{"fecha":"2025-10-28","ingresos":280000},{"fecha":"2025-10-29","ingresos":260000},{"fecha":"2025-10-30","ingresos":255000},{"fecha":"2025-10-31","ingresos":270000}]
};
// --- HELPERS ---
const formatCurrency = (value) => '$' + value.toLocaleString('es-AR', { minimumFractionDigits: 0, maximumFractionDigits: 0 });
const formatNumber = (value) => value.toLocaleString('es-AR');
const formatDate = (dateString) => { const [y, m, d] = dateString.split('-'); return `${d}/${m}/${y}`; };
// --- POPULATE KPIs ---
document.getElementById('kpi-ingresos').textContent = formatCurrency(jsonData.kpis.ingresosTotales);
document.getElementById('kpi-transacciones').textContent = formatNumber(jsonData.kpis.cantidadTransacciones);
document.getElementById('kpi-ticket').textContent = formatCurrency(jsonData.kpis.ticketPromedioCandy);
document.getElementById('kpi-margen').textContent = `${jsonData.kpis.margenPorcentaje.toFixed(1)}%`;
// --- PDF DOWNLOAD ---
function downloadPDF() {
const element = document.getElementById('dashboard-content');
const opt = {
margin: 0.5,
filename: 'Reporte_Ventas_Confiteria_Cinexo_Octubre2025.pdf',
image: { type: 'jpeg', quality: 0.98 },
html2canvas: { scale: 2, useCORS: true },
jsPDF: { unit: 'in', format: 'letter', orientation: 'landscape' }
};
// Desactivar animaciones para una captura limpia
Chart.defaults.animation.duration = 0;
// Pequeña demora para asegurar que las animaciones están desactivadas
setTimeout(() => {
html2pdf().set(opt).from(element).save().then(() => {
// Reactivar animaciones después de generar el PDF
Chart.defaults.animation.duration = 1000;
});
}, 100);
}
document.addEventListener('DOMContentLoaded', () => {
// --- CHART.JS PREMIUM CONFIG ---
Chart.register(ChartDataLabels);
Chart.defaults.font.family = "'Inter', 'Segoe UI', system-ui, sans-serif";
Chart.defaults.font.size = 12;
Chart.defaults.color = '#64748B';
Chart.defaults.plugins.legend.labels.usePointStyle = true;
Chart.defaults.plugins.legend.labels.padding = 20;
Chart.defaults.plugins.legend.labels.font = { size: 11, weight: '500' };
Chart.defaults.plugins.tooltip.backgroundColor = 'rgba(30, 41, 59, 0.95)';
Chart.defaults.plugins.tooltip.titleFont = { size: 13, weight: '600' };
Chart.defaults.plugins.tooltip.bodyFont = { size: 12 };
Chart.defaults.plugins.tooltip.padding = 12;
Chart.defaults.plugins.tooltip.cornerRadius = 8;
Chart.defaults.plugins.tooltip.displayColors = true;
Chart.defaults.plugins.tooltip.boxPadding = 6;
Chart.defaults.animation.duration = 1000;
Chart.defaults.animation.easing = 'easeOutQuart';
const chartColors = {
boleteria: '#60B99A',
candy: '#FFB74D',
candyDark: '#F59E0B',
palette: ['#FFB74D', '#1F3B4D', '#60B99A', '#8B5CF6', '#EC4899', '#06B6D4']
};
// --- CHART 1: Tendencia Diaria de Ingresos (Line Chart) ---
const tendenciaCtx = document.getElementById('chartTendenciaDiaria').getContext('2d');
const tendenciaData = jsonData.tendenciaDiaria;
const labelsTendencia = tendenciaData.map(d => d.fecha.substring(8, 10));
const dataTendencia = tendenciaData.map(d => d.ingresos);
new Chart(tendenciaCtx, {
type: 'line',
data: {
labels: labelsTendencia,
datasets: [{
label: 'Ingresos',
data: dataTendencia,
borderColor: chartColors.candy,
backgroundColor: (context) => {
const chart = context.chart;
const { ctx, chartArea } = chart;
if (!chartArea) return 'rgba(255, 183, 77, 0.1)';
const gradient = ctx.createLinearGradient(0, chartArea.bottom, 0, chartArea.top);
gradient.addColorStop(0, 'rgba(255, 183, 77, 0)');
gradient.addColorStop(1, 'rgba(255, 183, 77, 0.3)');
return gradient;
},
fill: true,
tension: 0.4,
borderWidth: 3,
pointRadius: 5,
pointBackgroundColor: '#fff',
pointBorderColor: chartColors.candy,
pointBorderWidth: 2,
pointHoverRadius: 7,
pointHoverBorderWidth: 3
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
interaction: { intersect: false, mode: 'index' },
plugins: {
legend: { display: false },
datalabels: {
display: (context) => {
const data = context.dataset.data;
const max = Math.max(...data);
return context.raw === max;
},
anchor: 'end',
align: 'top',
offset: 6,
backgroundColor: chartColors.candy,
borderRadius: 4,
color: '#fff',
font: { weight: 'bold', size: 10 },
padding: { top: 4, bottom: 4, left: 6, right: 6 },
formatter: (value) => '$' + (value / 1000).toFixed(0) + 'K'
},
tooltip: {
callbacks: {
title: (context) => `Día ${context[0].label} de Octubre`,
label: (ctx) => `${ctx.dataset.label}: ${formatCurrency(ctx.raw)}`
}
}
},
scales: {
y: {
beginAtZero: true,
grid: { color: 'rgba(0,0,0,0.05)' },
ticks: { callback: (value) => '$' + (value / 1000) + 'K' }
},
x: { grid: { display: false } }
}
}
});
// --- CHART 2: Top 5 Productos (Bar Chart) ---
// Generamos data plausible para este gráfico
const topProductosData = {
labels: ['Combo Mega', 'Balde Pochoclos', 'Gaseosa Grande', 'Nachos con Queso', 'Chocolate Block'],
data: [12500000, 9800000, 7200000, 5600000, 4300000]
};
const topProductosCtx = document.getElementById('chartTopProductos').getContext('2d');
new Chart(topProductosCtx, {
type: 'bar',
data: {
labels: topProductosData.labels,
datasets: [{
label: 'Ingresos',
data: topProductosData.data,
backgroundColor: (context) => {
const chart = context.chart;
const {ctx, chartArea} = chart;
if (!chartArea) return chartColors.candy;
const gradient = ctx.createLinearGradient(0, chartArea.bottom, 0, chartArea.top);
gradient.addColorStop(0, chartColors.candy);
gradient.addColorStop(1, chartColors.candyDark);
return gradient;
},
borderRadius: 8,
borderSkipped: false,
barThickness: 30,
}]
},
options: {
indexAxis: 'y',
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false },
datalabels: {
anchor: 'end',
align: 'end',
offset: 4,
color: '#1E293B',
font: { weight: 'bold', size: 12 },
formatter: (value) => formatCurrency(value)
},
tooltip: { callbacks: { label: (ctx) => `Ingresos: ${formatCurrency(ctx.raw)}` } }
},
scales: {
x: { beginAtZero: true, grid: { color: 'rgba(0,0,0,0.05)', drawBorder: false }, ticks: { callback: (v) => '$' + (v/1000000) + 'M' } },
y: { grid: { display: false }, ticks: { font: { weight: '500' } } }
}
}
});
// --- CHART 3: Mix Categorías (Doughnut Chart) ---
const mixCategoriasCtx = document.getElementById('chartMixCategorias').getContext('2d');
const mixData = [45, 30, 15, 10]; // Pochoclos, Gaseosas, Combos, Golosinas
new Chart(mixCategoriasCtx, {
type: 'doughnut',
data: {
labels: ['Pochoclos', 'Gaseosas', 'Combos', 'Golosinas'],
datasets: [{
data: mixData,
backgroundColor: chartColors.palette,
borderWidth: 0,
hoverOffset: 15,
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
cutout: '65%',
plugins: {
legend: { position: 'bottom', labels: { padding: 15, font: { size: 11 } } },
datalabels: {
color: '#fff',
font: { weight: 'bold', size: 14 },
formatter: (value, ctx) => value + '%',
},
tooltip: {
callbacks: {
label: (context) => `${context.label}: ${context.raw}%`
}
}
}
}
});
// --- TABLE: Ranking de Productos ---
const tableData = [
{ rank: 1, name: 'Combo Mega', units: 1850, revenue: 12500000 },
{ rank: 2, name: 'Balde Pochoclos', units: 2450, revenue: 9800000 },
{ rank: 3, name: 'Gaseosa Grande', units: 4800, revenue: 7200000 },
{ rank: 4, name: 'Nachos con Queso', units: 1400, revenue: 5600000 },
{ rank: 5, name: 'Chocolate Block', units: 1720, revenue: 4300000 },
{ rank: 6, name: 'Agua Mineral', units: 2150, revenue: 2150000 },
{ rank: 7, name: 'Caramelos Surtidos', units: 3500, revenue: 1750000 },
{ rank: 8, name: 'Café', units: 1200, revenue: 1200000 },
];
const totalRevenue = jsonData.kpis.ingresosTotales;
const tableBody = document.getElementById('product-ranking-table');
tableData.forEach(item => {
const percentage = ((item.revenue / totalRevenue) * 100).toFixed(1);
const rankClass = item.rank <= 3 ? `rank-${item.rank}` : 'rank-other';
const row = `
<tr>
<td><div class="rank-medal ${rankClass}">${item.rank}</div></td>
<td><strong>${item.name}</strong></td>
<td style="text-align:right;">${formatNumber(item.units)}</td>
<td class="value-currency" style="text-align:right;">${formatCurrency(item.revenue)}</td>
<td>
<div class="progress-cell">
<div class="progress-bar-container">
<div class="progress-bar-fill" style="width: ${percentage}%;"></div>
</div>
<span class="progress-value">${percentage}%</span>
</div>
</td>
</tr>
`;
tableBody.innerHTML += row;
});
// --- SPARKLINES for KPIs ---
function createSparkline(canvasId, data, color) {
const ctx = document.getElementById(canvasId).getContext('2d');
new Chart(ctx, {
type: 'line',
data: {
labels: data.map((_, i) => i),
datasets: [{ data, borderColor: color, borderWidth: 2, tension: 0.4, pointRadius: 0 }]
},
options: {
responsive: true, maintainAspectRatio: false,
plugins: { legend: { display: false }, tooltip: { enabled: false }, datalabels: { display: false } },
scales: { x: { display: false }, y: { display: false } },
animation: false
}
});
}
createSparkline('sparkline1', [10, 12, 8, 15, 12, 18, 19], chartColors.candy);
createSparkline('sparkline2', [50, 48, 55, 52, 60, 58, 62], chartColors.candy);
createSparkline('sparkline3', [12, 14, 13, 15, 14, 16, 15], chartColors.candy);
createSparkline('sparkline4', [88, 89, 87, 90, 92, 91, 95], chartColors.boleteria);
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment