Created
December 29, 2025 02:56
-
-
Save Jcbertorello/cec1f4603abbe4b38838a81633c83324 to your computer and use it in GitHub Desktop.
Dashboard Ajonjolí - 2025-12-29 02:56
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!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