Skip to content

Instantly share code, notes, and snippets.

@Jcbertorello
Created December 29, 2025 02:56
Show Gist options
  • Select an option

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

Select an option

Save Jcbertorello/cec1f4603abbe4b38838a81633c83324 to your computer and use it in GitHub Desktop.
Dashboard Ajonjolí - 2025-12-29 02:56
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Top Películas Agosto 2025 - Cinexo</title>
<!-- Google Fonts para tipografía premium -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<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 -->
<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(96, 185, 154, 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-primary) 0%, var(--color-primary-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(96, 185, 154, 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-primary), var(--color-primary-dark));
color: white;
box-shadow: 0 4px 12px rgba(96, 185, 154, 0.4);
}
.btn-primary:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(96, 185, 154, 0.5); }
/* === 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-primary);
}
.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; }
/* === KPI CARDS === */
.kpi-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 20px;
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-boleteria), var(--color-primary-light));
}
.kpi-card.candy::before { background: linear-gradient(90deg, var(--color-candy), var(--color-candy-dark)); }
.kpi-card.total::before { background: linear-gradient(90deg, var(--color-secondary), var(--color-secondary-light)); }
.kpi-card:hover { transform: translateY(-4px); box-shadow: var(--shadow-xl), var(--shadow-glow); border-color: var(--color-primary); }
.kpi-header { display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: 12px; }
.kpi-icon {
width: 48px; height: 48px; border-radius: 12px; display: flex; align-items: center;
justify-content: center; font-size: 1.5rem; color: var(--color-boleteria);
background: linear-gradient(135deg, rgba(96, 185, 154, 0.05), rgba(96, 185, 154, 0.15));
}
.kpi-card.candy .kpi-icon { color: var(--color-candy); background: linear-gradient(135deg, rgba(255, 183, 77, 0.05), rgba(255, 183, 77, 0.15)); }
.kpi-card.total .kpi-icon { color: var(--color-secondary); background: linear-gradient(135deg, rgba(31, 59, 77, 0.05), rgba(31, 59, 77, 0.15)); }
.kpi-label { font-size: 0.8rem; font-weight: 600; color: var(--text-secondary); margin-bottom: 4px; }
.kpi-value { font-size: 2rem; font-weight: 800; color: var(--text-primary); line-height: 1.1; font-feature-settings: 'tnum'; }
.kpi-detail { font-size: 0.8rem; color: var(--text-muted); margin-top: 8px; }
/* === GRID LAYOUT === */
.grid-layout {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 24px;
margin-bottom: 32px;
}
.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.1rem; font-weight: 700; color: var(--text-primary); }
.card-subtitle { font-size: 0.8rem; color: var(--text-muted); margin-top: 2px; }
.chart-container { position: relative; flex-grow: 1; min-height: 350px; }
/* === TABLE CARD === */
.table-card {
grid-column: 1 / -1; 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); }
.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-secondary); transition: background 0.15s ease;
}
.data-table tbody tr:hover td { background: rgba(96, 185, 154, 0.05); }
.data-table tbody tr:last-child td { border-bottom: none; }
/* TABLE STYLES */
.rank-medal {
width: 32px; height: 32px; border-radius: 50%; display: inline-flex;
align-items: center; justify-content: center; font-weight: 800; font-size: 0.85rem;
}
.rank-1 { background: linear-gradient(135deg, #FFD700, #FFA500); color: white; box-shadow: 0 2px 8px rgba(255, 215, 0, 0.4); }
.rank-2 { background: linear-gradient(135deg, #E8E8E8, #B8B8B8); color: white; box-shadow: 0 2px 8px rgba(192, 192, 192, 0.4); }
.rank-3 { background: linear-gradient(135deg, #CD7F32, #8B4513); color: white; box-shadow: 0 2px 8px rgba(205, 127, 50, 0.4); }
.rank-other { background: var(--bg-hover); color: var(--text-muted); }
.value-highlight { font-weight: 700; color: var(--text-primary); font-size: 0.95rem; }
.value-currency { font-feature-settings: 'tnum'; }
.format-badge {
display: inline-flex; align-items: center; padding: 4px 10px; border-radius: 6px;
font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em;
}
.badge-2d { background: #DBEAFE; color: #1E40AF; }
.badge-3d { background: #FEF3C7; color: #B45309; }
.badge-4d { background: #EDE9FE; color: #7C3AED; }
/* === FOOTER === */
.footer { margin-top: 40px; padding: 24px 0; border-top: 1px solid var(--border-light); }
.footer-content {
display: flex; justify-content: space-between; align-items: center; color: var(--text-muted);
font-size: 0.85rem;
}
.mostachia-brand { display: flex; align-items: center; gap: 12px; }
.mostachia-logo {
width: 40px; height: 40px; background: linear-gradient(135deg, var(--color-primary), var(--color-primary-dark));
border-radius: 10px; display: flex; align-items: center; justify-content: center;
color: white; font-weight: 800; font-size: 1.1rem;
}
.mostachia-text strong { display: block; color: var(--text-primary); font-weight: 700; }
/* === PRINT/PDF === */
@media print {
body { background: white !important; -webkit-print-color-adjust: exact; print-color-adjust: exact; }
.btn, .header-actions { display: none !important; }
.dashboard { padding: 16px !important; max-width: 100% !important; margin: 0 !important; }
.header, .kpi-card, .card, .table-card { box-shadow: none !important; border: 1px solid #ddd !important; break-inside: avoid; }
.grid-layout { grid-template-columns: 1fr 1fr; }
.table-card { grid-column: 1 / -1; }
}
/* === RESPONSIVE === */
@media (max-width: 1024px) { .grid-layout { grid-template-columns: 1fr; } }
@media (max-width: 768px) {
.header { flex-direction: column; gap: 16px; text-align: center; }
.kpi-grid { grid-template-columns: 1fr 1fr; }
.info-bar { flex-direction: column; gap: 10px; align-items: flex-start; }
}
@media (max-width: 500px) { .kpi-grid { grid-template-columns: 1fr; } }
</style>
</head>
<body>
<div class="dashboard" id="dashboard-content">
<!-- HEADER -->
<header class="header">
<div class="header-left">
<div class="logo">CX</div>
<div>
<h1 class="header-title"></h1>
<p class="header-subtitle"></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" id="info-bar"></div>
<!-- KPI CARDS -->
<div class="kpi-grid" id="kpi-grid"></div>
<!-- CHARTS GRID -->
<div class="grid-layout">
<div class="card">
<div class="card-header">
<h2 class="card-title">Top 5 Películas</h2>
<p class="card-subtitle">Por cantidad de espectadores</p>
</div>
<div class="chart-container">
<canvas id="chartPeliculas"></canvas>
</div>
</div>
<div class="card">
<div class="card-header">
<h2 class="card-title">Ocupación por Sala</h2>
<p class="card-subtitle">Comparativo Espectadores vs. Capacidad</p>
</div>
<div class="chart-container">
<canvas id="chartOcupacion"></canvas>
</div>
</div>
<div class="card">
<div class="card-header">
<h2 class="card-title">Asistencia por Distribuidora</h2>
<p class="card-subtitle">Porcentaje de espectadores</p>
</div>
<div class="chart-container">
<canvas id="chartDistribuidoras"></canvas>
</div>
</div>
<div class="card">
<div class="card-header">
<h2 class="card-title">Canal de Venta de Entradas</h2>
<p class="card-subtitle">Comparativo Online vs. Mostrador</p>
</div>
<div class="chart-container">
<canvas id="chartCanalVenta"></canvas>
</div>
</div>
</div>
<!-- TABLE -->
<div class="table-card">
<div class="table-header">
<div>
<h2 class="card-title">Ranking Detallado de Películas</h2>
<p class="card-subtitle">Top 10 en recaudación y espectadores</p>
</div>
</div>
<table class="data-table">
<thead>
<tr>
<th style="width: 5%;">#</th>
<th>Película</th>
<th style="text-align: center;">Formato</th>
<th style="text-align: right;">Espectadores</th>
<th style="text-align: right;">Recaudación</th>
</tr>
</thead>
<tbody id="top-peliculas-table"></tbody>
</table>
</div>
<!-- FOOTER -->
<footer class="footer">
<div class="footer-content">
<div class="mostachia-brand">
<div class="mostachia-logo">M</div>
<p class="mostachia-text">
<strong>Dashboard Interactivo</strong>
Generado por MostachIA
</p>
</div>
<p>Reporte para Cinexo - Agosto 2025</p>
</div>
</footer>
</div>
<script>
const data = {
"dashboardType": "ejecutivo", "dashboardTitle": "Top Películas Agosto 2025", "clientName": "Cinexo",
"periodo": { "desde": "2025-08-01", "hasta": "2025-08-31", "descripcion": "Agosto 2025" },
"kpis": { "espectadoresTotales": 22223, "recaudacionBoleteria": 114150500, "ventaCandy": 126199500, "ingresosTotales": 240350000, "ticketPromedioBoleteria": 5136.23, "ticketPromedioCandy": 15618.75, "cpp": 5686.53, "funcionesRealizadas": 872, "ocupacionPromedio": 13.00, "porcentajeVentasOnline": 16.65 },
"topPeliculas": [ { "pelicula": "Homo argentum", "formato": "2D", "espectadores": 8180, "recaudacion": 39308000, "ticketPromedio": 4805.38 }, { "pelicula": "F1 2D+4D", "formato": "4D", "espectadores": 2490, "recaudacion": 13926000, "ticketPromedio": 5592.77 }, { "pelicula": "Otro viernes de locos", "formato": "2D", "espectadores": 1863, "recaudacion": 8882000, "ticketPromedio": 4767.58 }, { "pelicula": "Los 4 Fantásticos: Primeros pasos 3D+4D", "formato": "4D", "espectadores": 1707, "recaudacion": 9835000, "ticketPromedio": 5761.57 }, { "pelicula": "La hora de la desaparición 2D+4D", "formato": "4D", "espectadores": 1395, "recaudacion": 7459500, "ticketPromedio": 5347.31 }, { "pelicula": "Jurassic World: Renace 3D+4D", "formato": "4D", "espectadores": 1064, "recaudacion": 6235000, "ticketPromedio": 5859.96 }, { "pelicula": "Los 4 Fantásticos: Primeros pasos 2D+4D", "formato": "4D", "espectadores": 1015, "recaudacion": 5307000, "ticketPromedio": 5228.57 }, { "pelicula": "Jurassic World: Renace 2D+4D", "formato": "4D", "espectadores": 582, "recaudacion": 3280000, "ticketPromedio": 5635.74 }, { "pelicula": "Los tipos malos 2", "formato": "2D", "espectadores": 487, "recaudacion": 2300000, "ticketPromedio": 4722.79 }, { "pelicula": "Los tipos malos 2 3D", "formato": "3D", "espectadores": 474, "recaudacion": 2467000, "ticketPromedio": 5204.64 } ],
"ocupacionPorSala": [ { "sala": 1, "nombreSala": "Sala 1", "capacidad": 194, "funciones": 122, "espectadores": 4235, "ocupacion": 17.89 }, { "sala": 4, "nombreSala": "Sala 4", "capacidad": 160, "funciones": 113, "espectadores": 3193, "ocupacion": 17.66 }, { "sala": 6, "nombreSala": "Sala 6", "capacidad": 160, "funciones": 101, "espectadores": 2729, "ocupacion": 16.89 }, { "sala": 5, "nombreSala": "Sala 5", "capacidad": 160, "funciones": 111, "espectadores": 2915, "ocupacion": 16.41 }, { "sala": 8, "nombreSala": "Sala 8", "capacidad": 327, "funciones": 95, "espectadores": 4058, "ocupacion": 13.06 }, { "sala": 3, "nombreSala": "Sala 3", "capacidad": 160, "funciones": 110, "espectadores": 2016, "ocupacion": 11.45 }, { "sala": 7, "nombreSala": "Sala 7", "capacidad": 148, "funciones": 123, "espectadores": 1902, "ocupacion": 10.45 }, { "sala": 2, "nombreSala": "Sala 2", "capacidad": 133, "funciones": 97, "espectadores": 1175, "ocupacion": 9.11 } ],
"topDistribuidoras": [ { "distribuidora": "Disney", "asistentes": 13510, "facturacion": 67276500 }, { "distribuidora": "Warner Bros", "asistentes": 4217, "facturacion": 23019000 }, { "distribuidora": "UIP", "asistentes": 3916, "facturacion": 21084000 }, { "distribuidora": "BF Paris", "asistentes": 580, "facturacion": 2771000 } ],
"ventasOnlineVsMostrador": { "online": 3780, "mostrador": 18923, "porcentajeOnline": 16.65 }
};
// === HELPERS ===
const formatCurrency = (value) => '$' + new Intl.NumberFormat('es-AR', { maximumFractionDigits: 0 }).format(value);
const formatNumber = (value) => new Intl.NumberFormat('es-AR').format(value);
const formatDate = (dateString) => {
const date = new Date(dateString + 'T00:00:00');
return date.toLocaleDateString('es-AR', { day: '2-digit', month: '2-digit', year: 'numeric' });
};
// === CHART.JS GLOBAL 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.pointStyle = 'circle';
Chart.defaults.plugins.legend.labels.padding = 20;
Chart.defaults.plugins.legend.labels.font = { size: 12, 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', secondary: '#1F3B4D', secondaryLight: 'rgba(31, 59, 77, 0.2)',
palette: ['#60B99A', '#1F3B4D', '#FFB74D', '#8B5CF6', '#EC4899', '#06B6D4', '#84CC16'],
boleteriaGradient: (ctx) => {
const gradient = ctx.createLinearGradient(0, ctx.chart.chartArea.bottom, 0, ctx.chart.chartArea.top);
gradient.addColorStop(0, '#7FCDB2'); gradient.addColorStop(1, '#60B99A'); return gradient;
}
};
// === DATA POPULATION ===
document.addEventListener('DOMContentLoaded', () => {
// Header
document.querySelector('.header-title').textContent = data.dashboardTitle;
document.querySelector('.header-subtitle').textContent = `${data.clientName} | Período: ${data.periodo.descripcion}`;
// Info Bar
document.getElementById('info-bar').innerHTML = `
<div class="info-item">Funciones Realizadas: <strong>${formatNumber(data.kpis.funcionesRealizadas)}</strong></div>
<div class="info-item">Ocupación Promedio: <strong>${data.kpis.ocupacionPromedio.toFixed(2)}%</strong></div>
<div class="info-item">Consumo p/cápita (Candy): <strong>${formatCurrency(data.kpis.cpp)}</strong></div>
<div class="info-item">Período: <strong>${formatDate(data.periodo.desde)} - ${formatDate(data.periodo.hasta)}</strong></div>
`;
// KPIs
const kpiContainer = document.getElementById('kpi-grid');
kpiContainer.innerHTML = `
<div class="kpi-card total">
<div class="kpi-header"><div class="kpi-icon">💰</div></div>
<div class="kpi-label">Ingresos Totales</div>
<div class="kpi-value">${formatCurrency(data.kpis.ingresosTotales)}</div>
<div class="kpi-detail">${formatCurrency(data.kpis.recaudacionBoleteria)} (Boletería) + ${formatCurrency(data.kpis.ventaCandy)} (Candy)</div>
</div>
<div class="kpi-card">
<div class="kpi-header"><div class="kpi-icon">🎟️</div></div>
<div class="kpi-label">Recaudación Boletería</div>
<div class="kpi-value">${formatCurrency(data.kpis.recaudacionBoleteria)}</div>
<div class="kpi-detail">Ticket Promedio: ${formatCurrency(data.kpis.ticketPromedioBoleteria)}</div>
</div>
<div class="kpi-card candy">
<div class="kpi-header"><div class="kpi-icon">🍿</div></div>
<div class="kpi-label">Venta Candy</div>
<div class="kpi-value">${formatCurrency(data.kpis.ventaCandy)}</div>
<div class="kpi-detail">Ticket Promedio: ${formatCurrency(data.kpis.ticketPromedioCandy)}</div>
</div>
<div class="kpi-card">
<div class="kpi-header"><div class="kpi-icon">🎬</div></div>
<div class="kpi-label">Espectadores Totales</div>
<div class="kpi-value">${formatNumber(data.kpis.espectadoresTotales)}</div>
<div class="kpi-detail">${data.kpis.porcentajeVentasOnline}% venta online</div>
</div>
`;
// Table
const tableBody = document.getElementById('top-peliculas-table');
data.topPeliculas.forEach((p, index) => {
const rank = index + 1;
let rankClass = 'rank-other';
if (rank === 1) rankClass = 'rank-1';
if (rank === 2) rankClass = 'rank-2';
if (rank === 3) rankClass = 'rank-3';
let formatClass = 'badge-2d';
if (p.formato.includes('3D')) formatClass = 'badge-3d';
if (p.formato.includes('4D') || p.formato.includes('4D')) formatClass = 'badge-4d';
const row = `
<tr>
<td style="text-align: center;"><div class="rank-medal ${rankClass}">${rank}</div></td>
<td class="value-highlight">${p.pelicula}</td>
<td style="text-align: center;"><span class="format-badge ${formatClass}">${p.formato}</span></td>
<td class="value-currency" style="text-align: right;">${formatNumber(p.espectadores)}</td>
<td class="value-highlight value-currency" style="text-align: right;">${formatCurrency(p.recaudacion)}</td>
</tr>
`;
tableBody.innerHTML += row;
});
renderCharts();
});
function renderCharts() {
// Chart 1: Top Películas
const top5Peliculas = data.topPeliculas.slice(0, 5).reverse();
new Chart(document.getElementById('chartPeliculas'), {
type: 'bar',
data: {
labels: top5Peliculas.map(p => p.pelicula),
datasets: [{
label: 'Espectadores',
data: top5Peliculas.map(p => p.espectadores),
backgroundColor: (context) => chartColors.boleteriaGradient(context.chart),
borderRadius: 8,
borderSkipped: false,
barThickness: 30,
}]
},
options: {
indexAxis: 'y', responsive: true, maintainAspectRatio: false,
plugins: {
legend: { display: false },
datalabels: {
anchor: 'end', align: 'end', offset: 8, color: '#1E293B',
font: { weight: 'bold', size: 12 },
formatter: (value) => formatNumber(value)
},
tooltip: { callbacks: { label: (ctx) => `${formatNumber(ctx.raw)} espectadores` } }
},
scales: {
x: {
beginAtZero: true, grid: { color: 'rgba(0,0,0,0.05)', drawBorder: false },
ticks: { callback: (value) => value >= 1000 ? (value/1000) + 'K' : value }
},
y: { grid: { display: false }, ticks: { font: { weight: '500' } } }
}
}
});
// Chart 2: Ocupación por Sala
new Chart(document.getElementById('chartOcupacion'), {
type: 'bar',
data: {
labels: data.ocupacionPorSala.map(s => s.nombreSala),
datasets: [
{ label: 'Capacidad', data: data.ocupacionPorSala.map(s => s.capacidad), backgroundColor: chartColors.secondaryLight, borderRadius: 6, borderSkipped: false },
{ label: 'Espectadores', data: data.ocupacionPorSala.map(s => s.espectadores), backgroundColor: chartColors.boleteria, borderRadius: 6, borderSkipped: false }
]
},
options: {
responsive: true, maintainAspectRatio: false,
plugins: {
legend: { position: 'top', align: 'end' },
datalabels: {
display: (ctx) => ctx.datasetIndex === 1,
anchor: 'end', align: 'top', offset: 2, color: '#1E293B',
font: { weight: 'bold', size: 11 },
formatter: (value, context) => `${data.ocupacionPorSala[context.dataIndex].ocupacion.toFixed(0)}%`
},
tooltip: {
callbacks: {
label: function(context) {
let label = context.dataset.label || '';
if (label) label += ': ';
if (context.parsed.y !== null) label += formatNumber(context.parsed.y);
if (context.datasetIndex === 1) {
const ocupacion = data.ocupacionPorSala[context.dataIndex].ocupacion;
label += ` (${ocupacion.toFixed(1)}%)`;
}
return label;
}
}
}
},
scales: {
y: { beginAtZero: true, grid: { color: 'rgba(0,0,0,0.05)' }, title: { display: true, text: 'Butacas', font: { weight: '500' } } },
x: { grid: { display: false } }
}
}
});
// Chart 3: Distribuidoras
new Chart(document.getElementById('chartDistribuidoras'), {
type: 'doughnut',
data: {
labels: data.topDistribuidoras.map(d => d.distribuidora),
datasets: [{
data: data.topDistribuidoras.map(d => d.asistentes),
backgroundColor: chartColors.palette, borderWidth: 0, hoverOffset: 15
}]
},
options: {
responsive: true, maintainAspectRatio: false, cutout: '60%',
plugins: {
legend: { position: 'right', align: 'center' },
datalabels: {
color: '#fff', font: { weight: 'bold', size: 14 },
formatter: (value, ctx) => {
const total = ctx.chart.data.datasets[0].data.reduce((a, b) => a + b, 0);
const percentage = ((value / total) * 100);
return percentage > 5 ? percentage.toFixed(0) + '%' : '';
}
},
tooltip: {
callbacks: {
label: function(context) {
const total = context.dataset.data.reduce((a, b) => a + b, 0);
const value = context.raw;
const percentage = ((value / total) * 100).toFixed(1);
return `${context.label}: ${formatNumber(value)} (${percentage}%)`;
}
}
}
}
}
});
// Chart 4: Canal de Venta
new Chart(document.getElementById('chartCanalVenta'), {
type: 'doughnut',
data: {
labels: ['Online', 'Mostrador'],
datasets: [{
data: [data.ventasOnlineVsMostrador.online, data.ventasOnlineVsMostrador.mostrador],
backgroundColor: [chartColors.boleteria, chartColors.secondary], borderWidth: 0, hoverOffset: 15
}]
},
options: {
responsive: true, maintainAspectRatio: false, cutout: '60%',
plugins: {
legend: { position: 'right', align: 'center' },
datalabels: {
color: '#fff', font: { weight: 'bold', size: 14 },
formatter: (value, ctx) => {
const total = ctx.chart.data.datasets[0].data.reduce((a, b) => a + b, 0);
const percentage = ((value / total) * 100).toFixed(1);
return percentage + '%';
}
},
tooltip: {
callbacks: {
label: function(context) {
const total = context.dataset.data.reduce((a, b) => a + b, 0);
const value = context.raw;
const percentage = ((value / total) * 100).toFixed(1);
return `${context.label}: ${formatNumber(value)} (${percentage}%)`;
}
}
}
}
}
});
}
// === PDF DOWNLOAD FUNCTION ===
function downloadPDF() {
const element = document.getElementById('dashboard-content');
const opt = {
margin: [0.5, 0.5, 0.5, 0.5],
filename: `Cinexo_Reporte_Agosto_2025.pdf`,
image: { type: 'jpeg', quality: 0.98 },
html2canvas: { scale: 2, useCORS: true, logging: false },
jsPDF: { unit: 'in', format: 'a3', orientation: 'landscape' }
};
html2pdf().from(element).set(opt).save();
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment