Skip to content

Instantly share code, notes, and snippets.

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

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

Select an option

Save Jcbertorello/854aa8d1b7859f866ea251678a263d74 to your computer and use it in GitHub Desktop.
Dashboard Ajonjolí - 2025-12-29 02:50
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Reporte Top Películas Agosto 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>
<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-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 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-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));
color: var(--color-primary-dark);
}
.kpi-card.total .kpi-icon {
background: linear-gradient(135deg, rgba(31, 59, 77, 0.1), rgba(31, 59, 77, 0.2));
color: var(--color-secondary);
}
.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-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;
}
.grid-container {
display: grid;
grid-template-columns: 2fr 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;
}
.doughnut-container {
position: relative;
height: 250px;
}
.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 th.right, .data-table td.right {
text-align: right;
}
.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 td .value-highlight {
font-weight: 600;
color: var(--text-primary);
}
.data-table td .value-currency {
font-feature-settings: 'tnum';
}
.data-table tbody tr:hover td {
background: rgba(96, 185, 154, 0.05);
}
.data-table tbody tr:last-child td {
border-bottom: none;
}
.rank-cell {
display: flex;
align-items: center;
gap: 16px;
}
.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;
flex-shrink: 0;
}
.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: #475569;
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);
}
.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;
margin-left: 8px;
}
.badge-2D { background: #DBEAFE; color: #1E40AF; }
.badge-3D { background: #FEF3C7; color: #B45309; }
.badge-4D { background: #FCE7F3; color: #BE185D; }
.badge-IMAX { background: #EDE9FE; color: #7C3AED; }
/* === FOOTER PREMIUM === */
.footer {
margin-top: 40px;
padding: 24px;
text-align: center;
}
.mostachia-brand {
display: inline-flex;
align-items: center;
gap: 12px;
text-decoration: none;
}
.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 { display: none !important; }
.dashboard { padding: 16px !important; max-width: 100% !important; margin: 0; }
.header, .kpi-card, .card, .table-card { box-shadow: none !important; border: 1px solid #ddd !important; }
.grid-container { grid-template-columns: 2fr 1fr !important; }
}
/* === RESPONSIVE === */
@media (max-width: 1200px) {
.grid-container {
grid-template-columns: 1fr;
}
}
@media (max-width: 768px) {
.header { flex-direction: column; gap: 16px; text-align: center; }
.kpi-grid { grid-template-columns: 1fr; }
.info-bar { flex-direction: column; gap: 8px; }
}
</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" id="dashboard-title"></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">
<span>🗓️</span> Período: <strong id="periodo-descripcion"></strong>
</div>
<div class="info-item">
<span>📍</span> Complejo: <strong>Cinexo Central</strong>
</div>
<div class="info-item">
<span>📊</span> Reporte: <strong>Top Películas</strong>
</div>
</div>
<!-- KPI Grid -->
<div class="kpi-grid" id="kpi-container">
<!-- KPIs will be injected here by JavaScript -->
</div>
<!-- Charts Grid -->
<div class="grid-container">
<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" style="height: 400px;">
<canvas id="chartTopPeliculas"></canvas>
</div>
</div>
<div style="display: flex; flex-direction: column; gap: 24px;">
<div class="card">
<div class="card-header">
<h3 class="card-title">Asistentes por Formato</h3>
<p class="card-subtitle">Distribución de espectadores.</p>
</div>
<div class="doughnut-container">
<canvas id="chartFormatos"></canvas>
</div>
</div>
<div class="card">
<div class="card-header">
<h3 class="card-title">Market Share por Distribuidora</h3>
<p class="card-subtitle">Asistentes por empresa.</p>
</div>
<div class="doughnut-container">
<canvas id="chartDistribuidoras"></canvas>
</div>
</div>
</div>
</div>
<!-- Table -->
<div class="table-card">
<div class="table-header">
<h2 class="table-title">Detalle de Performance por Película</h2>
<p class="table-subtitle">Ranking completo de las 10 películas más vistas.</p>
</div>
<table class="data-table">
<thead>
<tr>
<th>Rank</th>
<th>Película</th>
<th>Distribuidora</th>
<th class="right">Espectadores</th>
<th class="right">Recaudación</th>
</tr>
</thead>
<tbody id="peliculas-table-body">
<!-- Table rows will be injected here -->
</tbody>
</table>
</div>
<!-- Footer -->
<footer class="footer">
<a href="https://mostachia.com" target="_blank" class="mostachia-brand">
<div class="mostachia-logo">M</div>
<div class="mostachia-text">
<strong>Generado por MostachIA</strong>
Inteligencia de Negocios para Cines
</div>
</a>
</footer>
</div>
<script>
// Data from JSON
const data = {
"dashboardType": "pelicula",
"dashboardTitle": "Top Películas Agosto 2025 - Asistentes y Facturación",
"clientName": "Cinexo",
"periodo": {
"desde": "2025-08-01",
"hasta": "2025-08-31",
"descripcion": "Agosto 2025"
},
"topPeliculas": [
{ "pelicula": "Homo argentum", "formato": "2D", "distribuidora": "THE WALT DISNEY COMPANY SA", "espectadores": 8180, "recaudacion": 39308000 },
{ "pelicula": "F1 2D+4D", "formato": "4D", "distribuidora": "WARNER BROS", "espectadores": 2490, "recaudacion": 13926000 },
{ "pelicula": "Otro viernes de locos", "formato": "2D", "distribuidora": "THE WALT DISNEY COMPANY SA", "espectadores": 1863, "recaudacion": 8882000 },
{ "pelicula": "Los 4 Fantásticos: Primeros pasos 3D+4D", "formato": "4D", "distribuidora": "THE WALT DISNEY COMPANY SA", "espectadores": 1707, "recaudacion": 9835000 },
{ "pelicula": "La hora de la desaparición 2D+4D", "formato": "4D", "distribuidora": "WARNER BROS", "espectadores": 1395, "recaudacion": 7459500 },
{ "pelicula": "Jurassic World: Renace 3D+4D", "formato": "4D", "distribuidora": "UNITED INTERNATIONAL PICTURES SRL", "espectadores": 1064, "recaudacion": 6235000 },
{ "pelicula": "Los 4 Fantásticos: Primeros pasos 2D+4D", "formato": "4D", "distribuidora": "THE WALT DISNEY COMPANY SA", "espectadores": 1015, "recaudacion": 5307000 },
{ "pelicula": "Jurassic World: Renace 2D+4D", "formato": "4D", "distribuidora": "UNITED INTERNATIONAL PICTURES SRL", "espectadores": 582, "recaudacion": 3280000 },
{ "pelicula": "Los tipos malos 2", "formato": "2D", "distribuidora": "UNITED INTERNATIONAL PICTURES SRL", "espectadores": 487, "recaudacion": 2300000 },
{ "pelicula": "Los tipos malos 2 3D", "formato": "3D", "distribuidora": "UNITED INTERNATIONAL PICTURES SRL", "espectadores": 474, "recaudacion": 2467000 }
],
"distribucionPorFormato": {
"labels": ["2D", "3D", "4D"],
"datasets": [{ "label": "Asistentes", "data": [10530, 474, 8153] }]
},
"distribucionPorDistribuidora": {
"labels": ["THE WALT DISNEY COMPANY SA", "WARNER BROS", "UNITED INTERNATIONAL PICTURES SRL"],
"datasets": [{ "label": "Asistentes", "data": [12765, 3885, 2607] }]
}
};
// Helper functions
const formatCurrency = (value) => new Intl.NumberFormat('es-AR', { style: 'currency', currency: 'ARS', minimumFractionDigits: 0 }).format(value);
const formatNumber = (value) => new Intl.NumberFormat('es-AR').format(value);
// Download as PDF function
function downloadPDF() {
const element = document.getElementById('dashboard-content');
const opt = {
margin: [0.5, 0.2, 0.5, 0.2],
filename: `Reporte_Cinexo_${data.periodo.descripcion.replace(' ', '_')}.pdf`,
image: { type: 'jpeg', quality: 0.98 },
html2canvas: { scale: 2, useCORS: true },
jsPDF: { unit: 'in', format: 'a4', orientation: 'portrait' }
};
html2pdf().from(element).set(opt).save();
}
document.addEventListener('DOMContentLoaded', () => {
// Populate header and info
document.getElementById('dashboard-title').textContent = data.dashboardTitle;
document.getElementById('periodo-descripcion').textContent = data.periodo.descripcion;
// Calculate KPIs
const totalEspectadores = data.topPeliculas.reduce((sum, p) => sum + p.espectadores, 0);
const totalRecaudacion = data.topPeliculas.reduce((sum, p) => sum + p.recaudacion, 0);
const ticketPromedio = totalEspectadores > 0 ? totalRecaudacion / totalEspectadores : 0;
const peliculaTop = data.topPeliculas[0];
// Render KPIs
const kpiContainer = document.getElementById('kpi-container');
kpiContainer.innerHTML = `
<div class="kpi-card total">
<div class="kpi-icon">💰</div>
<div class="kpi-label">Recaudación Total</div>
<div class="kpi-value">${formatCurrency(totalRecaudacion)}</div>
<div class="kpi-detail">${formatNumber(data.topPeliculas.length)} títulos en cartelera</div>
</div>
<div class="kpi-card">
<div class="kpi-icon">🎟️</div>
<div class="kpi-label">Espectadores Totales</div>
<div class="kpi-value">${formatNumber(totalEspectadores)}</div>
<div class="kpi-detail">En todas las funciones del período</div>
</div>
<div class="kpi-card candy">
<div class="kpi-icon">📈</div>
<div class="kpi-label">Ticket Promedio</div>
<div class="kpi-value">${formatCurrency(ticketPromedio)}</div>
<div class="kpi-detail">Ingreso promedio por espectador</div>
</div>
<div class="kpi-card">
<div class="kpi-icon">🏆</div>
<div class="kpi-label">Película #1</div>
<div class="kpi-value" style="font-size: 1.5rem;">${peliculaTop.pelicula}</div>
<div class="kpi-detail">${formatNumber(peliculaTop.espectadores)} espectadores</div>
</div>
`;
// Render Table
const tableBody = document.getElementById('peliculas-table-body');
let tableHTML = '';
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';
tableHTML += `
<tr>
<td>
<div class="rank-cell">
<span class="rank-medal ${rankClass}">${rank}</span>
</div>
</td>
<td>
<span class="value-highlight">${p.pelicula}</span>
<span class="format-badge badge-${p.formato}">${p.formato}</span>
</td>
<td>${p.distribuidora}</td>
<td class="right value-highlight">${formatNumber(p.espectadores)}</td>
<td class="right value-currency">${formatCurrency(p.recaudacion)}</td>
</tr>
`;
});
tableBody.innerHTML = tableHTML;
// Chart.js Configuration
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.position = 'bottom';
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',
primaryDark: '#4A9D80',
secondary: '#1F3B4D',
secondaryLight: '#2D5066',
palette: ['#60B99A', '#1F3B4D', '#FFB74D', '#7FCDB2', '#2D5066', '#F59E0B', '#8B5CF6', '#EC4899']
};
// Bar Chart: Top Peliculas
const top5Peliculas = data.topPeliculas.slice(0, 5);
new Chart(document.getElementById('chartTopPeliculas'), {
type: 'bar',
data: {
labels: top5Peliculas.map(p => p.pelicula),
datasets: [{
label: 'Espectadores',
data: top5Peliculas.map(p => p.espectadores),
backgroundColor: function(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: 30,
maxBarThickness: 40
}]
},
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' } }
}
}
}
});
// Doughnut Chart: Formatos
new Chart(document.getElementById('chartFormatos'), {
type: 'doughnut',
data: {
labels: data.distribucionPorFormato.labels,
datasets: [{
data: data.distribucionPorFormato.datasets[0].data,
backgroundColor: chartColors.palette,
borderWidth: 0,
hoverOffset: 15,
hoverBorderWidth: 4,
hoverBorderColor: '#fff'
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
cutout: '60%',
plugins: {
legend: { position: 'right' },
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(0);
return percentage > 5 ? 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}%)`;
}
}
}
}
}
});
// Doughnut Chart: Distribuidoras
new Chart(document.getElementById('chartDistribuidoras'), {
type: 'doughnut',
data: {
labels: data.distribucionPorDistribuidora.labels.map(l => l.split(' ')[0]),
datasets: [{
data: data.distribucionPorDistribuidora.datasets[0].data,
backgroundColor: chartColors.palette,
borderWidth: 0,
hoverOffset: 15,
hoverBorderWidth: 4,
hoverBorderColor: '#fff'
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
cutout: '60%',
plugins: {
legend: { position: 'right' },
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(0);
return percentage > 5 ? percentage + '%' : '';
}
},
tooltip: {
callbacks: {
label: function(context) {
const originalLabel = data.distribucionPorDistribuidora.labels[context.dataIndex];
const total = context.dataset.data.reduce((a, b) => a + b, 0);
const value = context.raw;
const percentage = ((value / total) * 100).toFixed(1);
return `${originalLabel}: ${formatNumber(value)} (${percentage}%)`;
}
}
}
}
}
});
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment