Skip to content

Instantly share code, notes, and snippets.

@Jcbertorello
Created December 29, 2025 03:13
Show Gist options
  • Select an option

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

Select an option

Save Jcbertorello/2e769356269f3bcbc28d61ca20797357 to your computer and use it in GitHub Desktop.
Dashboard Ajonjolí - 2025-12-29 03:13
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Resumen Ejecutivo de Rendimiento - 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 para exportar -->
<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: 1320px;
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); }
.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;
flex-wrap: wrap;
gap: 16px;
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; }
/* === LAYOUT & CARDS === */
.grid { display: grid; gap: 24px; }
.grid-2 { grid-template-columns: repeat(2, 1fr); }
.grid-3 { grid-template-columns: repeat(3, 1fr); }
.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;
transition: all 0.3s ease;
}
.card:hover { transform: translateY(-3px); box-shadow: var(--shadow-lg); }
.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: 300px; }
/* === KPI CARDS PREMIUM === */
.kpi-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 24px; margin-bottom: 24px; }
.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-primary), 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-icon {
width: 48px; height: 48px; border-radius: 12px; display: flex; align-items: center;
justify-content: center; font-size: 1.5rem; margin-bottom: 16px;
background: linear-gradient(135deg, rgba(96, 185, 154, 0.1), rgba(96, 185, 154, 0.2));
}
.kpi-card.candy .kpi-icon { background: linear-gradient(135deg, rgba(255, 183, 77, 0.1), rgba(255, 183, 77, 0.2)); }
.kpi-card.total .kpi-icon { background: linear-gradient(135deg, rgba(31, 59, 77, 0.1), rgba(31, 59, 77, 0.2)); }
.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-detail { font-size: 0.85rem; color: var(--text-secondary); margin-bottom: 8px; }
.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%; }
/* === TABLAS PREMIUM === */
.table-card { padding: 0; }
.table-header { padding: 20px 24px; border-bottom: 1px solid var(--border-light); display: flex; justify-content: space-between; align-items: center; }
.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: linear-gradient(180deg, var(--bg-hover), var(--bg-primary)); }
.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(96, 185, 154, 0.05); }
.data-table tbody tr:last-child td { border-bottom: none; }
.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); }
.value-currency { font-feature-settings: 'tnum'; }
/* === FOOTER PREMIUM === */
.footer { margin-top: 40px; padding: 24px; text-align: center; }
.mostachia-brand { display: inline-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 { font-size: 0.85rem; color: var(--text-muted); text-align: left; }
.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, .footer { display: none !important; }
.dashboard { padding: 16px !important; max-width: 100% !important; margin: 0; }
.header { box-shadow: none !important; border: 1px solid #ddd !important; }
.card, .kpi-card { page-break-inside: avoid; box-shadow: none !important; border: 1px solid #ddd !important; }
.grid { grid-template-columns: 1fr !important; }
.kpi-grid { grid-template-columns: 1fr 1fr !important; }
}
/* === RESPONSIVE === */
@media (max-width: 1024px) {
.grid-2, .grid-3 { grid-template-columns: 1fr; }
}
@media (max-width: 768px) {
.header { flex-direction: column; gap: 16px; text-align: center; }
.info-bar { justify-content: center; }
.kpi-grid { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 500px) {
.kpi-grid { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<div class="dashboard" id="dashboard-to-print">
<header class="header">
<div class="header-left">
<div class="logo">CX</div>
<div>
<h1 class="header-title" id="dashboard-title"></h1>
<p class="header-subtitle" id="dashboard-subtitle"></p>
</div>
</div>
<div class="header-actions">
<button class="btn btn-primary" onclick="downloadPDF()">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>
Descargar PDF
</button>
</div>
</header>
<div class="info-bar">
<div class="info-item">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect><line x1="16" y1="2" x2="16" y2="6"></line><line x1="8" y1="2" x2="8" y2="6"></line><line x1="3" y1="10" x2="21" y2="10"></line></svg>
<span>Período: <strong id="info-periodo"></strong></span>
</div>
<div class="info-item">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><line x1="10" y1="9" x2="8" y2="9"></line></svg>
<span>Funciones Realizadas: <strong id="info-funciones"></strong></span>
</div>
<div class="info-item">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10z"></path><path d="m9 12 2 2 4-4"></path></svg>
<span>Ocupación Promedio: <strong id="info-ocupacion"></strong></span>
</div>
</div>
<section class="kpi-grid">
<div class="kpi-card total">
<div class="kpi-icon">💰</div>
<div class="kpi-label">Ingresos Totales</div>
<div class="kpi-value" id="kpi-ingresos"></div>
<div class="kpi-detail" id="kpi-cpp"></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="M18 15l-6-6-6 6"/></svg>
+8.2% vs mes ant.
</div>
</div>
<div class="kpi-card">
<div class="kpi-icon">🎟️</div>
<div class="kpi-label">Boletería</div>
<div class="kpi-value" id="kpi-boleteria"></div>
<div class="kpi-detail" id="kpi-ticket-boleteria"></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">Candy</div>
<div class="kpi-value" id="kpi-candy"></div>
<div class="kpi-detail" id="kpi-ticket-candy"></div>
<div class="kpi-comparison">
<canvas class="kpi-sparkline" id="sparkline2"></canvas>
</div>
</div>
<div class="kpi-card">
<div class="kpi-icon">👥</div>
<div class="kpi-label">Espectadores</div>
<div class="kpi-value" id="kpi-espectadores"></div>
<div class="kpi-detail" id="kpi-ventas-online"></div>
<div class="kpi-comparison">
<canvas class="kpi-sparkline" id="sparkline3"></canvas>
</div>
</div>
</section>
<main class="grid grid-2">
<div class="card">
<div class="card-header">
<h2 class="card-title">Top 5 Películas por Espectadores</h2>
<p class="card-subtitle">Ranking de las películas más vistas en el período</p>
</div>
<div class="chart-container">
<canvas id="chartPeliculas"></canvas>
</div>
</div>
<div class="card">
<div class="card-header">
<h2 class="card-title">Mix de Ingresos</h2>
<p class="card-subtitle">Distribución porcentual de los ingresos totales</p>
</div>
<div class="chart-container">
<canvas id="chartIngresosFuente"></canvas>
</div>
</div>
<div class="card table-card">
<div class="table-header">
<div>
<h2 class="table-title">Top 5 Productos del Candy</h2>
<p class="table-subtitle">Productos más vendidos por recaudación</p>
</div>
</div>
<table class="data-table" id="tablaTopCandy">
<thead>
<tr>
<th style="width: 50px; text-align: center;">#</th>
<th>Producto</th>
<th style="text-align: right;">Cantidad</th>
<th style="text-align: right;">Recaudación</th>
</tr>
</thead>
<tbody>
<!-- Filas insertadas por JS -->
</tbody>
</table>
</div>
<div class="card">
<div class="card-header">
<h2 class="card-title">Ocupación por Sala</h2>
<p class="card-subtitle">Porcentaje de ocupación promedio para cada sala</p>
</div>
<div class="chart-container">
<canvas id="chartOcupacionSalas"></canvas>
</div>
</div>
</main>
<footer class="footer">
<div class="mostachia-brand">
<div class="mostachia-logo">M</div>
<div class="mostachia-text">
<strong>Reporte Generado por MostachIA</strong>
Análisis de Datos para la Industria del Entretenimiento
</div>
</div>
</footer>
</div>
<script>
// Data from JSON
const data = {
"dashboardType": "ejecutivo",
"dashboardTitle": "Resumen Ejecutivo de Rendimiento - Cinexo",
"clientName": "Cinexo",
"periodo": {
"desde": "2025-01-01",
"hasta": "2025-01-31",
"descripcion": "Enero 2025"
},
"kpis": {
"espectadoresTotales": 22223,
"recaudacionBoleteria": 114150500,
"ventaCandy": 126199500,
"ingresosTotales": 240350000,
"ticketPromedioBoleteria": 5136.59,
"ticketPromedioCandy": 15618.75,
"cpp": 11344.63,
"funcionesRealizadas": 872,
"ocupacionPromedio": 14.83,
"porcentajeVentasOnline": 16.65
},
"topPeliculas": [
{ "pelicula": "Homo argentum", "espectadores": 8180 },
{ "pelicula": "F1 2D+4D", "espectadores": 2490 },
{ "pelicula": "Otro viernes de locos", "espectadores": 1863 },
{ "pelicula": "Los 4 Fantásticos...", "espectadores": 1707 },
{ "pelicula": "La hora de la desaparición...", "espectadores": 1395 }
],
"ocupacionPorSala": [
{ "nombreSala": "Sala 1", "capacidad": 194, "ocupacion": 17.89 },
{ "nombreSala": "Sala 4", "capacidad": 160, "ocupacion": 17.66 },
{ "nombreSala": "Sala 6", "capacidad": 160, "ocupacion": 16.89 },
{ "nombreSala": "Sala 5", "capacidad": 160, "ocupacion": 16.41 },
{ "nombreSala": "Sala 8", "capacidad": 327, "ocupacion": 13.06 },
{ "nombreSala": "Sala 3", "capacidad": 160, "ocupacion": 11.45 },
{ "nombreSala": "Sala 7", "capacidad": 148, "ocupacion": 10.45 },
{ "nombreSala": "Sala 2", "capacidad": 133, "ocupacion": 9.11 }
],
"topProductosCandy": [
{ "producto": "COMBO PAREJA", "cantidad": 2656, "total": 35780000 },
{ "producto": "COMBO MEDIANO", "cantidad": 1673, "total": 15893500 },
{ "producto": "AGUA", "cantidad": 1474, "total": 5795000 },
{ "producto": "BALDE POP", "cantidad": 1416, "total": 17235000 },
{ "producto": "COMBO BALDE", "cantidad": 1156, "total": 14988000 }
]
};
// Helpers
const formatCurrency = (value) => '$' + Math.round(value).toLocaleString('es-AR');
const formatNumber = (value) => Math.round(value).toLocaleString('es-AR');
// Función PDF
function downloadPDF() {
const element = document.getElementById('dashboard-to-print');
const opt = {
margin: [0.5, 0.2, 0.5, 0.2], // [top, left, bottom, right] in inches
filename: `Reporte_Cinexo_${data.periodo.descripcion.replace(' ','_')}.pdf`,
image: { type: 'jpeg', quality: 0.98 },
html2canvas: { scale: 2, useCORS: true, logging: false },
jsPDF: { unit: 'in', format: 'letter', orientation: 'landscape' }
};
html2pdf().set(opt).from(element).save();
}
document.addEventListener('DOMContentLoaded', () => {
// Populate Header & Info Bar
document.getElementById('dashboard-title').textContent = data.dashboardTitle;
document.getElementById('dashboard-subtitle').textContent = `Período: ${data.periodo.descripcion}`;
document.getElementById('info-periodo').textContent = data.periodo.descripcion;
document.getElementById('info-funciones').textContent = formatNumber(data.kpis.funcionesRealizadas);
document.getElementById('info-ocupacion').textContent = `${data.kpis.ocupacionPromedio.toFixed(1)}%`;
// Populate KPIs
document.getElementById('kpi-ingresos').textContent = formatCurrency(data.kpis.ingresosTotales);
document.getElementById('kpi-cpp').textContent = `CPP: ${formatCurrency(data.kpis.cpp)}`;
document.getElementById('kpi-boleteria').textContent = formatCurrency(data.kpis.recaudacionBoleteria);
document.getElementById('kpi-ticket-boleteria').textContent = `Ticket Prom: ${formatCurrency(data.kpis.ticketPromedioBoleteria)}`;
document.getElementById('kpi-candy').textContent = formatCurrency(data.kpis.ventaCandy);
document.getElementById('kpi-ticket-candy').textContent = `Ticket Prom: ${formatCurrency(data.kpis.ticketPromedioCandy)}`;
document.getElementById('kpi-espectadores').textContent = formatNumber(data.kpis.espectadoresTotales);
document.getElementById('kpi-ventas-online').textContent = `${data.kpis.porcentajeVentasOnline.toFixed(1)}% ventas online`;
// Populate Top Candy Table
const candyTableBody = document.querySelector('#tablaTopCandy tbody');
const rankClasses = ['rank-1', 'rank-2', 'rank-3', 'rank-other', 'rank-other'];
data.topProductosCandy.forEach((item, index) => {
const row = `
<tr>
<td style="text-align: center;"><span class="rank-medal ${rankClasses[index]}">${index + 1}</span></td>
<td>${item.producto}</td>
<td class="value-highlight" style="text-align: right;">${formatNumber(item.cantidad)}</td>
<td class="value-currency value-highlight" style="text-align: right;">${formatCurrency(item.total)}</td>
</tr>
`;
candyTableBody.innerHTML += row;
});
// Charting
initializeCharts();
});
function initializeCharts() {
// Registrar plugin
Chart.register(ChartDataLabels);
// Configuración global de Chart.js
Chart.defaults.font.family = "'Inter', 'Segoe UI', system-ui, sans-serif";
Chart.defaults.font.size = 12;
Chart.defaults.color = '#64748B';
Chart.defaults.plugins.legend.position = 'bottom';
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 = {
primary: '#60B99A',
primaryLight: '#7FCDB2',
boleteria: '#60B99A',
candy: '#FFB74D',
palette: ['#60B99A', '#1F3B4D', '#FFB74D', '#7FCDB2', '#2D5066', '#F59E0B'],
ocupacionAlta: '#10B981',
ocupacionMedia: '#F59E0B',
ocupacionBaja: '#EF4444'
};
// === GRÁFICO 1: TOP PELÍCULAS (BARRAS) ===
new Chart(document.getElementById('chartPeliculas'), {
type: 'bar',
data: {
labels: data.topPeliculas.map(p => p.pelicula),
datasets: [{
label: 'Espectadores',
data: data.topPeliculas.map(p => p.espectadores),
backgroundColor: (context) => {
const chart = context.chart;
const { ctx, chartArea } = chart;
if (!chartArea) return chartColors.primary;
const gradient = ctx.createLinearGradient(0, chartArea.bottom, 0, chartArea.top);
gradient.addColorStop(0, chartColors.primaryLight);
gradient.addColorStop(1, chartColors.primary);
return gradient;
},
borderRadius: 8,
borderSkipped: false,
barThickness: 35,
maxBarThickness: 45
}]
},
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) => 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' } } }
}
}
});
// === GRÁFICO 2: INGRESOS FUENTE (DONA) ===
new Chart(document.getElementById('chartIngresosFuente'), {
type: 'doughnut',
data: {
labels: ['Boletería', 'Candy'],
datasets: [{
data: [data.kpis.recaudacionBoleteria, data.kpis.ventaCandy],
backgroundColor: [chartColors.boleteria, chartColors.candy],
borderWidth: 0, hoverOffset: 15, hoverBorderWidth: 4, hoverBorderColor: '#fff'
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
cutout: '60%',
plugins: {
legend: {
position: 'right',
labels: {
generateLabels: function(chart) {
const d = chart.data;
const total = d.datasets[0].data.reduce((a, b) => a + b, 0);
return d.labels.map((label, i) => ({
text: `${label}: ${formatCurrency(d.datasets[0].data[i])}`,
fillStyle: d.datasets[0].backgroundColor[i],
hidden: false, index: i
}));
},
padding: 15, font: { size: 12 }
}
},
datalabels: {
color: '#fff', font: { weight: 'bold', size: 16 },
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 + '%';
},
anchor: 'center', align: 'center', offset: 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}: ${formatCurrency(value)} (${percentage}%)`;
}
}
}
}
}
});
// === GRÁFICO 3: OCUPACIÓN SALAS (BARRAS) ===
new Chart(document.getElementById('chartOcupacionSalas'), {
type: 'bar',
data: {
labels: data.ocupacionPorSala.map(s => s.nombreSala),
datasets: [{
label: 'Ocupación',
data: data.ocupacionPorSala.map(s => s.ocupacion),
backgroundColor: data.ocupacionPorSala.map(s => {
if (s.ocupacion >= 17) return chartColors.ocupacionAlta;
if (s.ocupacion >= 12) return chartColors.ocupacionMedia;
return chartColors.ocupacionBaja;
}),
borderRadius: 6,
borderSkipped: false,
barThickness: 20
}]
},
options: {
indexAxis: 'y',
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false },
datalabels: {
anchor: 'end',
align: 'end',
offset: 4,
color: '#1E293B',
font: { weight: 'bold', size: 11 },
formatter: (value) => `${value.toFixed(1)}%`
},
tooltip: { callbacks: { label: (ctx) => `Ocupación: ${ctx.raw.toFixed(1)}%` } }
},
scales: {
x: { beginAtZero: true, grid: { color: 'rgba(0,0,0,0.05)' },
ticks: { callback: (value) => value + '%' }
},
y: { grid: { display: false } }
}
}
});
// === SPARKLINES para KPIs ===
function createSparkline(canvasId, dataPoints, color) {
const ctx = document.getElementById(canvasId).getContext('2d');
new Chart(ctx, {
type: 'line',
data: {
labels: dataPoints.map((_, i) => i),
datasets: [{
data: dataPoints, borderColor: color, borderWidth: 2,
fill: false, 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, 15], chartColors.boleteria);
createSparkline('sparkline2', [50, 48, 55, 52, 60, 58, 65], chartColors.candy);
createSparkline('sparkline3', [20, 22, 19, 25, 23, 28, 26], chartColors.primary);
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment