Rafael Lopez

ser personales

...@@ -24,3 +24,6 @@ vite.config.ts.timestamp-* ...@@ -24,3 +24,6 @@ vite.config.ts.timestamp-*
24 24
25 # Claude 25 # Claude
26 claude.md 26 claude.md
27 +
28 +# Docs internos
29 +static/ESTILOS_*.md
......
...@@ -20,7 +20,6 @@ ...@@ -20,7 +20,6 @@
20 let mode = $state('archivo'); 20 let mode = $state('archivo');
21 let searchFocused = $state(false); 21 let searchFocused = $state(false);
22 let hoveredStory = $state(null); 22 let hoveredStory = $state(null);
23 - let mousePos = $state({ x: 0, y: 0 });
24 let activeToggle = $state(null); 23 let activeToggle = $state(null);
25 let searchInput = $state(null); 24 let searchInput = $state(null);
26 let debounceTimer; 25 let debounceTimer;
...@@ -40,12 +39,12 @@ ...@@ -40,12 +39,12 @@
40 39
41 const MODES = { 40 const MODES = {
42 archivo: { 41 archivo: {
43 - placeholder: 'YPFB, Publicidad, Sueldos...', 42 + placeholder: 'Ej: Ministerio de Salud, YPFB, sueldos...',
44 label: 'Serie histórica', 43 label: 'Serie histórica',
45 color: COLORS.accent, 44 color: COLORS.accent,
46 }, 45 },
47 hoy: { 46 hoy: {
48 - placeholder: 'Tu municipio, Salud, Obras...', 47 + placeholder: 'Ej: Tu municipio, hospitales, obras...',
49 label: 'Actualización diaria', 48 label: 'Actualización diaria',
50 color: COLORS.green, 49 color: COLORS.green,
51 }, 50 },
...@@ -175,10 +174,7 @@ ...@@ -175,10 +174,7 @@
175 }); 174 });
176 observer.observe(document.documentElement, { attributes: true }); 175 observer.observe(document.documentElement, { attributes: true });
177 176
178 - const handleMouse = (e) => { mousePos = { x: e.clientX, y: e.clientY }; };
179 - window.addEventListener('mousemove', handleMouse);
180 return () => { 177 return () => {
181 - window.removeEventListener('mousemove', handleMouse);
182 observer.disconnect(); 178 observer.disconnect();
183 }; 179 };
184 }); 180 });
...@@ -189,9 +185,6 @@ ...@@ -189,9 +185,6 @@
189 </svelte:head> 185 </svelte:head>
190 186
191 <div class="page"> 187 <div class="page">
192 - <!-- Grain overlay -->
193 - <div class="grain"></div>
194 -
195 <!-- Header con Logo y Controles --> 188 <!-- Header con Logo y Controles -->
196 <header class="page-header"> 189 <header class="page-header">
197 {#if isDarkMode} 190 {#if isDarkMode}
...@@ -262,11 +255,6 @@ ...@@ -262,11 +255,6 @@
262 255
263 <!-- Hero --> 256 <!-- Hero -->
264 <section class="hero"> 257 <section class="hero">
265 - <div
266 - class="mouse-glow"
267 - style="background: radial-gradient(800px circle at {mousePos.x}px {mousePos.y}px, rgba(201, 167, 81, 0.03), transparent 60%)"
268 - ></div>
269 -
270 <div class="hero-content"> 258 <div class="hero-content">
271 <!-- Main title --> 259 <!-- Main title -->
272 <h1 class="hero-title fade-in" style="animation-delay: 0.3s"> 260 <h1 class="hero-title fade-in" style="animation-delay: 0.3s">
...@@ -276,11 +264,12 @@ ...@@ -276,11 +264,12 @@
276 <!-- Subtitle - the scale --> 264 <!-- Subtitle - the scale -->
277 <p class="hero-subtitle fade-in" style="animation-delay: 0.5s"> 265 <p class="hero-subtitle fade-in" style="animation-delay: 0.5s">
278 Todos los ingresos y gastos del Estado boliviano.<br /> 266 Todos los ingresos y gastos del Estado boliviano.<br />
279 - <span class="subtitle-stats">600+ entidades · 500+ rubros · 21 años · 9M de registros · actualización diaria</span> 267 + <span class="subtitle-stats">21 años · actualización diaria · 600+ entidades · 9M de registros</span>
280 </p> 268 </p>
281 269
282 <!-- Search - main CTA --> 270 <!-- Search - main CTA -->
283 <div class="search-wrapper fade-in" style="animation-delay: 0.6s"> 271 <div class="search-wrapper fade-in" style="animation-delay: 0.6s">
272 + <p class="search-instruction">Busca cualquier entidad pública o tipo de gasto</p>
284 <div 273 <div
285 class="search-bar" 274 class="search-bar"
286 class:search-focused={searchFocused} 275 class:search-focused={searchFocused}
...@@ -352,9 +341,12 @@ ...@@ -352,9 +341,12 @@
352 {/if} 341 {/if}
353 342
354 {#if !searchFocused || ($results.length === 0 && $query.length < 2)} 343 {#if !searchFocused || ($results.length === 0 && $query.length < 2)}
355 - <a href="/clasificadores" class="browse-link"> 344 + <div class="browse-section">
356 - o explora por clasificadores <span class="browse-arrow">→</span> 345 + <span class="browse-hint">¿No sabes qué buscar?</span>
357 - </a> 346 + <a href="/clasificadores" class="browse-link">
347 + Explora cómo Bolivia organiza sus ingresos y gastos <span class="browse-arrow">→</span>
348 + </a>
349 + </div>
358 {/if} 350 {/if}
359 </div> 351 </div>
360 352
...@@ -448,7 +440,6 @@ ...@@ -448,7 +440,6 @@
448 .page::-webkit-scrollbar-track { background: var(--theme-body); } 440 .page::-webkit-scrollbar-track { background: var(--theme-body); }
449 .page::-webkit-scrollbar-thumb { background: var(--theme-texto); border-radius: 3px; } 441 .page::-webkit-scrollbar-thumb { background: var(--theme-texto); border-radius: 3px; }
450 442
451 - @keyframes grain { 0%,100%{transform:translate(0,0)}10%{transform:translate(-2%,-3%)}30%{transform:translate(-1%,3%)}50%{transform:translate(-3%,1%)}70%{transform:translate(-2%,2%)}90%{transform:translate(-1%,2%)} }
452 @keyframes fadeInUp { from{opacity:0;transform:translateY(30px)}to{opacity:1;transform:translateY(0)} } 443 @keyframes fadeInUp { from{opacity:0;transform:translateY(30px)}to{opacity:1;transform:translateY(0)} }
453 444
454 .page { 445 .page {
...@@ -460,15 +451,6 @@ ...@@ -460,15 +451,6 @@
460 transition: background-color 0.3s, color 0.3s; 451 transition: background-color 0.3s, color 0.3s;
461 } 452 }
462 453
463 - /* Grain - only visible in dark mode */
464 - .grain {
465 - position: fixed; top: -50%; left: -50%; width: 200%; height: 200%;
466 - pointer-events: none; z-index: 9999; opacity: 0.03;
467 - background: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
468 - animation: grain 0.5s steps(1) infinite;
469 - }
470 - :global(:root:not(.dark)) .grain { display: none; }
471 -
472 /* Header with MEFP logo */ 454 /* Header with MEFP logo */
473 .page-header { 455 .page-header {
474 position: fixed; top: 0; left: 0; right: 0; z-index: 100; 456 position: fixed; top: 0; left: 0; right: 0; z-index: 100;
...@@ -495,11 +477,6 @@ ...@@ -495,11 +477,6 @@
495 min-height: 100vh; display: flex; flex-direction: column; justify-content: center; 477 min-height: 100vh; display: flex; flex-direction: column; justify-content: center;
496 padding: 80px 40px 60px; position: relative; max-width: 1200px; margin: 0 auto; 478 padding: 80px 40px 60px; position: relative; max-width: 1200px; margin: 0 auto;
497 } 479 }
498 - .mouse-glow {
499 - position: fixed; top: 0; left: 0; right: 0; bottom: 0;
500 - pointer-events: none; z-index: 0; transition: background 0.6s;
501 - }
502 - :global(:root:not(.dark)) .mouse-glow { display: none; }
503 .hero-content { 480 .hero-content {
504 position: relative; z-index: 1; 481 position: relative; z-index: 1;
505 display: flex; flex-direction: column; align-items: center; 482 display: flex; flex-direction: column; align-items: center;
...@@ -600,11 +577,24 @@ ...@@ -600,11 +577,24 @@
600 width: 1px; height: 36px; background: var(--theme-borde); 577 width: 1px; height: 36px; background: var(--theme-borde);
601 } 578 }
602 579
580 + .browse-section {
581 + display: flex;
582 + flex-direction: column;
583 + align-items: center;
584 + margin-top: 20px;
585 + gap: 6px;
586 + }
587 + .browse-hint {
588 + font-family: 'DM Sans', sans-serif;
589 + font-size: 12px;
590 + color: var(--theme-texto);
591 + opacity: 0.7;
592 + }
603 .browse-link { 593 .browse-link {
604 display: block; 594 display: block;
605 - margin-top: 14px;
606 font-family: 'DM Sans', sans-serif; 595 font-family: 'DM Sans', sans-serif;
607 - font-size: 12px; color: var(--theme-texto); 596 + font-size: 13px;
597 + color: var(--theme-texto);
608 text-decoration: none; 598 text-decoration: none;
609 text-align: center; 599 text-align: center;
610 transition: color 0.2s; 600 transition: color 0.2s;
...@@ -618,6 +608,13 @@ ...@@ -618,6 +608,13 @@
618 608
619 /* Search - main CTA */ 609 /* Search - main CTA */
620 .search-wrapper { margin-top: 28px; width: 100%; max-width: 520px; position: relative; z-index: 60; } 610 .search-wrapper { margin-top: 28px; width: 100%; max-width: 520px; position: relative; z-index: 60; }
611 + .search-instruction {
612 + font-family: 'DM Sans', sans-serif;
613 + font-size: 15px;
614 + color: var(--theme-titulo);
615 + margin-bottom: 12px;
616 + text-align: center;
617 + }
621 .search-bar { 618 .search-bar {
622 display: flex; align-items: center; 619 display: flex; align-items: center;
623 background: var(--theme-surface); border: 1px solid var(--theme-borde); 620 background: var(--theme-surface); border: 1px solid var(--theme-borde);
......
1 <script> 1 <script>
2 - const clasificadores = [ 2 + import { onMount } from 'svelte';
3 - { 3 + import { fade, scale } from 'svelte/transition';
4 - slug: "institucional", 4 + import { cubicOut } from 'svelte/easing';
5 - ruta: "/clasificadores/institucional", 5 +
6 - numero: "01", 6 + let hovered = null;
7 - nombre: "Institucional", 7 + let activeModal = null;
8 - titulo: "Quién", 8 + let mounted = false;
9 - desc: "Las instituciones que gastan y administran recursos públicos", 9 +
10 - ejemplo: "Ministerios, municipios, YPFB…" 10 + onMount(() => { setTimeout(() => { mounted = true; }, 80); });
11 +
12 + const MODALS = {
13 + quien: {
14 + title: '¿Quién gasta o recibe?', tech: 'CLASIFICADOR INSTITUCIONAL',
15 + everyday: 'Es como preguntar "¿quién en la familia maneja plata?". Papá tiene su sueldo, mamá maneja el negocio, el abuelo cobra su renta. Cada uno es una "entidad" distinta con su propio presupuesto.',
16 + budget: 'El clasificador institucional cataloga a todas las entidades del sector público: ministerios, gobernaciones, municipios, empresas públicas, universidades. Cada una tiene un código único y un presupuesto asignado.',
17 + example: 'Ejemplo: El Ministerio de Educación (código 040) y el Gobierno Autónomo de La Paz (código 126) son entidades distintas, cada una con sus propios ingresos y gastos.',
18 + color: 'var(--theme-titulo)',
11 }, 19 },
12 - { 20 + rubros: {
13 - slug: "objeto-gasto", 21 + title: '¿Qué tipo de ingreso es?', tech: 'CLASIFICADOR DE RECURSOS POR RUBROS',
14 - ruta: "/clasificadores/objeto-gasto", 22 + everyday: 'María gana un sueldo de su trabajo, cobra alquiler de un cuartito, y un amigo le prestó plata. Son tres tipos distintos de ingreso: laboral, patrimonial, y crédito.',
15 - numero: "02", 23 + budget: 'En el presupuesto, el Estado recibe ingresos tributarios (impuestos = el "sueldo"), regalías por explotación de recursos (= el "alquiler"), y créditos internacionales (= el "préstamo"). El rubro te dice qué naturaleza tiene cada ingreso.',
16 - nombre: "Objeto del Gasto", 24 + example: 'Ejemplo: Los Bs 2.400M que Bolivia recibe por IDH son un rubro distinto a los Bs 800M de crédito del BID, aunque ambos financien al mismo ministerio.',
17 - titulo: "En qué", 25 + color: 'var(--theme-accent)',
18 - desc: "La naturaleza del gasto: sueldos, obras, compras",
19 - ejemplo: "Servicios personales, bienes, inversión…"
20 }, 26 },
21 - { 27 + fuentes: {
22 - slug: "rubros", 28 + title: '¿Quién pone la plata?', tech: 'CLASIFICADOR DE FUENTES DE FINANCIAMIENTO',
23 - ruta: "/clasificadores/rubros", 29 + everyday: 'María sabe que su sueldo se lo paga la empresa TechCorp, el alquiler se lo paga su inquilino Juan, y el préstamo se lo hizo el Banco Nacional. Son las fuentes: quién firma el cheque.',
24 - numero: "03", 30 + budget: 'Las fuentes son los bolsillos específicos: el TGN (Tesoro General de la Nación), recursos propios de cada entidad, crédito externo del BID o Banco Mundial, o donaciones de cooperación.',
25 - nombre: "Recursos por Rubros", 31 + example: 'Ejemplo: Un crédito del BID y uno del Banco Mundial son el mismo rubro (crédito externo) pero distinta fuente. La fuente dice quién exactamente pone la plata.',
26 - titulo: "Cuánto ingresa", 32 + color: 'var(--theme-accent)',
27 - desc: "Los ingresos del Estado por tipo de recaudación",
28 - ejemplo: "Impuestos, regalías, tasas…"
29 }, 33 },
30 - { 34 + objeto: {
31 - slug: "funcional", 35 + title: '¿En qué se gasta concretamente?', tech: 'CLASIFICADOR POR OBJETO DEL GASTO',
32 - ruta: "/clasificadores/funcional", 36 + everyday: 'Cuando María gasta, compra cosas concretas: arroz, paga la luz, le paga al albañil. Cada compra es un "objeto" distinto — algo tangible que se puede listar.',
33 - numero: "04", 37 + budget: 'El Objeto del Gasto es exactamente eso: sueldos, consultorías, materiales de escritorio, combustible, medicamentos. Es la lista del supermercado del Estado.',
34 - nombre: "Finalidad y Función", 38 + example: 'Ejemplo: El grupo 10000 "Servicios Personales" incluye sueldos y aguinaldos. El 20000 "Servicios No Personales" incluye consultorías y alquileres.',
35 - titulo: "Para qué", 39 + color: '#4A8B6E',
36 - desc: "El propósito del gasto según funciones del Estado",
37 - ejemplo: "Salud, Educación, Defensa…"
38 }, 40 },
39 - { 41 + sector: {
40 - slug: "fuente", 42 + title: '¿A qué sector de la economía va?', tech: 'CLASIFICADOR POR SECTORES ECONÓMICOS',
41 - ruta: "/clasificadores/fuente", 43 + everyday: 'Si María contrata un albañil, esa plata va al sector construcción. Si compra medicina, va al sector salud. No importa qué compró — importa qué parte de la economía mueve.',
42 - numero: "05", 44 + budget: 'Este clasificador mide el impacto económico: cuánto va a infraestructura, cuánto a servicios, cuánto a industria. Permite evaluar cómo las decisiones fiscales afectan a cada sector.',
43 - nombre: "Fuente de Financiamiento", 45 + example: 'Ejemplo: Un hospital nuevo es "construcción" en Objeto del Gasto pero "salud" en Sector Económico. Misma plata, dos lentes distintos.',
44 - titulo: "De dónde", 46 + color: '#4A8B6E',
45 - desc: "El origen del dinero que financia cada gasto",
46 - ejemplo: "Tesoro, IDH, créditos externos…"
47 }, 47 },
48 - { 48 + finalidad: {
49 - slug: "organismo", 49 + title: '¿Para qué se gasta?', tech: 'CLASIFICADOR POR FINALIDAD Y FUNCIÓN',
50 - ruta: "/clasificadores/organismo", 50 + everyday: 'María compró arroz (objeto), pero ¿para qué? Para alimentar a su familia. Compró cemento: para tener casa. El "para qué" es el objetivo final, no el insumo.',
51 - numero: "06", 51 + budget: 'Este clasificador agrupa por propósito: educación, defensa, salud, protección social. No importa si se gastó en sueldos o en cemento — lo que importa es el fin.',
52 - nombre: "Organismo Financiador", 52 + example: 'Ejemplo: El sueldo de un profesor y la construcción de una escuela son objetos de gasto distintos, pero ambos tienen la misma finalidad: Educación.',
53 - titulo: "Quién financia", 53 + color: '#4A8B6E',
54 - desc: "Los organismos detrás de los créditos y donaciones",
55 - ejemplo: "BID, CAF, Banco Mundial…"
56 }, 54 },
57 - { 55 + donde: {
58 - slug: "sectores", 56 + title: '¿Dónde se gasta?', tech: 'CLASIFICADOR GEOGRÁFICO',
59 - ruta: "/clasificadores/sectores", 57 + everyday: 'María manda plata a su mamá en Cochabamba y paga el alquiler en La Paz. No es lo mismo gastar en la ciudad que en el área rural, en Santa Cruz que en Potosí.',
60 - numero: "07", 58 + budget: 'La dimensión geográfica permite ver cómo se distribuyen los recursos en el territorio: por departamento, municipio o región. Revela las prioridades territoriales del Estado.',
61 - nombre: "Sector Económico", 59 + example: 'Ejemplo: Del presupuesto de Educación, ¿cuánto va a La Paz vs. Beni? Este clasificador responde esa pregunta.',
62 - titulo: "Qué sector", 60 + color: '#4A8B6E',
63 - desc: "Los sectores productivos que activa el gasto",
64 - ejemplo: "Hidrocarburos, agro, transporte…"
65 }, 61 },
62 + };
63 +
64 + const WIDGETS_INGRESOS = [
65 + { id: 'rubros', q: '¿Qué tipo de ingreso?', d: 'Impuestos, regalías, ventas, créditos — la naturaleza de cada ingreso', t: 'Recursos por rubros', href: '/clasificadores/rubros' },
66 + { id: 'fuentes', q: '¿Quién pone la plata?', d: 'TGN, crédito externo, donaciones — de qué bolsillo sale', t: 'Fuentes de financiamiento', href: '/clasificadores/fuente' },
67 + ];
68 +
69 + const WIDGETS_EN_QUE = [
70 + { id: 'objeto', q: 'Objeto del gasto', d: 'Sueldos, consultorías, materiales — la lista concreta', t: 'Objeto del gasto', href: '/clasificadores/objeto-gasto' },
71 + { id: 'sector', q: 'Sector económico', d: 'Industria, construcción, servicios — a qué sector va', t: 'Sectores económicos', href: '/clasificadores/sectores' },
72 + ];
73 +
74 + const WIDGETS_BOTTOM = [
75 + { id: 'finalidad', q: '¿Para qué se gasta?', d: 'Educación, salud, defensa — el objetivo final', t: 'Finalidad y función', href: '/clasificadores/funcional' },
76 + { id: 'donde', q: '¿Dónde se gasta?', d: 'Departamentos, municipios — distribución territorial', t: 'Geográfico', href: '/clasificadores/geografico' },
66 ]; 77 ];
67 78
68 - </script> 79 + $: modal = activeModal ? MODALS[activeModal] : null;
80 +
81 + function openModal(e, id) {
82 + e.preventDefault();
83 + e.stopPropagation();
84 + activeModal = id;
85 + }
86 + function closeModal() { activeModal = null; }
87 +</script>
69 88
70 <svelte:head> 89 <svelte:head>
71 - <title>Explorar los datos — Presupuesto Bolivia</title> 90 + <title>Clasificadores — Presupuesto Bolivia</title>
72 <link rel="preconnect" href="https://fonts.googleapis.com" /> 91 <link rel="preconnect" href="https://fonts.googleapis.com" />
73 <link href="https://fonts.googleapis.com/css2?family=DM+Serif+Display:ital@0;1&family=DM+Mono:wght@400;500&family=Instrument+Sans:wght@400;500;600&display=swap" rel="stylesheet" /> 92 <link href="https://fonts.googleapis.com/css2?family=DM+Serif+Display:ital@0;1&family=DM+Mono:wght@400;500&family=Instrument+Sans:wght@400;500;600&display=swap" rel="stylesheet" />
74 </svelte:head> 93 </svelte:head>
75 94
76 -<main class="clasificadores-page"> 95 +<div class="page" class:mounted>
77 - <!-- Header de la página --> 96 + <!-- Header -->
78 <header class="page-header"> 97 <header class="page-header">
79 - <!-- Breadcrumb -->
80 <nav class="breadcrumb"> 98 <nav class="breadcrumb">
81 - <a href="/" class="breadcrumb-link">← Inicio</a> 99 + <a href="/" class="breadcrumb-link sm-hidden">← Inicio</a>
100 + <div class="breadcrumb-full">
101 + <a href="/">Inicio</a>
102 + <span class="breadcrumb-sep">/</span>
103 + <span class="breadcrumb-current">Clasificadores</span>
104 + </div>
82 </nav> 105 </nav>
83 - <div class="header-content"> 106 +
84 - <h1>Formas de leer el presupuesto</h1> 107 + <div class="header-content anim" style="--d:0ms">
108 + <h1>Elige cómo explorar el presupuesto</h1>
85 <p class="subtitulo"> 109 <p class="subtitulo">
86 - Cada ingreso y cada gasto del Estado se registra bajo distintas clasificaciones. 110 + Cada clasificador es una forma distinta de ver la misma información.
87 - Aquí puedes explorar los datos según cada una de ellas. 111 + Haz clic en <span class="help-inline">?</span> para entender qué es cada uno.
88 </p> 112 </p>
89 </div> 113 </div>
90 </header> 114 </header>
91 115
92 - <!-- Grid de clasificadores --> 116 + <main>
93 - <section class="grid-clasificadores"> 117 + <!-- ═══ ¿QUIÉN? ═══ -->
94 - {#each clasificadores as c} 118 + <div class="anim" style="--d:100ms">
95 - <a href={c.ruta} class="card"> 119 + <div class="group">
96 - <span class="card-meta"> 120 + <div class="group-accent" style="--c: var(--theme-titulo)"></div>
97 - <span class="card-numero">{c.numero}</span> 121 + <div class="group-header">
98 - <span class="card-nombre">{c.nombre}</span> 122 + <div class="group-icon" style="--c: var(--theme-titulo)">
99 - </span> 123 + <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
100 - <h2 class="card-titulo">{c.titulo}</h2> 124 + </div>
101 - <p class="card-desc">{c.desc}</p> 125 + <div>
102 - <p class="card-ejemplo">{c.ejemplo}</p> 126 + <div class="group-label">¿QUIÉN?</div>
103 - </a> 127 + <div class="group-sub">La entidad que gasta o recibe</div>
104 - {/each} 128 + </div>
105 - </section> 129 + </div>
106 - 130 +
107 - <!-- Footer contextual --> 131 + <a
108 - <footer class="page-footer"> 132 + href="/clasificadores/institucional"
109 - <p class="footnote"> 133 + class="widget-card widget-h"
110 - Fuente: SIGMA (Sistema Integrado de Gestión y Modernización Administrativa) y 134 + class:wh={hovered === 'quien'}
111 - SIGEP (Sistema de Gestión Pública) — Ministerio de Economía y Finanzas Públicas. 135 + on:mouseenter={() => hovered = 'quien'}
112 - Datos desde 2005. 136 + on:mouseleave={() => hovered = null}
113 - </p> 137 + >
114 - </footer> 138 + <div class="widget-h-left">
115 -</main> 139 + <div class="widget-q">Instituciones públicas</div>
140 + <div class="widget-desc">Ministerios, gobernaciones, municipios, empresas públicas, universidades</div>
141 + </div>
142 + <div class="widget-h-right">
143 + <span class="widget-tech">INSTITUCIONAL</span>
144 + <button class="help-btn help-sm" on:click={(e) => openModal(e, 'quien')}>?</button>
145 + <span class="widget-arrow" class:arrow-show={hovered === 'quien'}>→</span>
146 + </div>
147 + </a>
148 + </div>
149 + </div>
150 +
151 + <!-- ═══ INGRESOS + GASTOS ═══ -->
152 + <div class="main-grid">
153 +
154 + <!-- INGRESOS -->
155 + <div class="anim col-stretch" style="--d:200ms">
156 + <div class="group group-stretch">
157 + <div class="group-accent" style="--c: var(--theme-accent)"></div>
158 + <div class="group-header">
159 + <div class="group-icon" style="--c: var(--theme-accent)">
160 + <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M12 19V5M5 12l7-7 7 7"/></svg>
161 + </div>
162 + <div>
163 + <div class="group-label" style="color: var(--theme-accent)">INGRESOS</div>
164 + <div class="group-sub">¿De dónde viene la plata?</div>
165 + </div>
166 + </div>
167 +
168 + <div class="widgets-stretch">
169 + {#each WIDGETS_INGRESOS as w}
170 + <a
171 + href={w.href}
172 + class="widget-card widget-grow"
173 + class:wh={hovered === w.id}
174 + on:mouseenter={() => hovered = w.id}
175 + on:mouseleave={() => hovered = null}
176 + >
177 + <div class="widget-top">
178 + <h4 class="widget-q">{w.q}</h4>
179 + <button class="help-btn" on:click={(e) => openModal(e, w.id)}>?</button>
180 + </div>
181 + <p class="widget-desc widget-desc-grow">{w.d}</p>
182 + <div class="widget-footer">
183 + <span class="widget-tech">{w.t}</span>
184 + <span class="widget-arrow" class:arrow-show={hovered === w.id}>→</span>
185 + </div>
186 + <div class="widget-glow widget-glow-accent"></div>
187 + </a>
188 + {/each}
189 + </div>
190 + </div>
191 + </div>
192 +
193 + <!-- GASTOS -->
194 + <div class="anim col-stretch" style="--d:320ms">
195 + <div class="group">
196 + <div class="group-accent" style="--c: #4A8B6E"></div>
197 + <div class="group-header">
198 + <div class="group-icon" style="--c: #4A8B6E">
199 + <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M12 5v14M5 12l7 7 7-7"/></svg>
200 + </div>
201 + <div>
202 + <div class="group-label" style="color: #4A8B6E">GASTOS</div>
203 + <div class="group-sub">¿Cómo se gasta la plata?</div>
204 + </div>
205 + </div>
206 +
207 + <div class="group-widgets">
208 + <!-- ¿En qué? sub-group -->
209 + <div class="sub-group">
210 + <div class="sub-group-accent" style="--c: #4A8B6E"></div>
211 + <div class="sub-group-header">
212 + <div class="sub-group-left">
213 + <div class="sub-group-icon" style="--c: #4A8B6E">
214 + <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/></svg>
215 + </div>
216 + <div>
217 + <div class="sub-group-label">¿En qué se gasta?</div>
218 + <div class="sub-group-tag">DOS CLASIFICADORES</div>
219 + </div>
220 + </div>
221 + </div>
222 +
223 + <div class="sub-grid">
224 + {#each WIDGETS_EN_QUE as w}
225 + <a
226 + href={w.href}
227 + class="widget-card sub-card"
228 + class:wh={hovered === w.id}
229 + on:mouseenter={() => hovered = w.id}
230 + on:mouseleave={() => hovered = null}
231 + >
232 + <div class="widget-top">
233 + <h4 class="widget-q sub-q">{w.q}</h4>
234 + <button class="help-btn help-xs" on:click={(e) => openModal(e, w.id)}>?</button>
235 + </div>
236 + <p class="widget-desc sub-desc">{w.d}</p>
237 + <div class="widget-footer sub-footer">
238 + <span class="widget-tech sub-tech">{w.t}</span>
239 + <span class="widget-arrow" class:arrow-show={hovered === w.id}>→</span>
240 + </div>
241 + <div class="widget-glow widget-glow-green"></div>
242 + </a>
243 + {/each}
244 + </div>
245 + </div>
246 +
247 + <!-- Bottom row -->
248 + <div class="bottom-grid">
249 + {#each WIDGETS_BOTTOM as w}
250 + <a
251 + href={w.href}
252 + class="widget-card"
253 + class:wh={hovered === w.id}
254 + on:mouseenter={() => hovered = w.id}
255 + on:mouseleave={() => hovered = null}
256 + >
257 + <div class="widget-top">
258 + <h4 class="widget-q">{w.q}</h4>
259 + <button class="help-btn" on:click={(e) => openModal(e, w.id)}>?</button>
260 + </div>
261 + <p class="widget-desc">{w.d}</p>
262 + <div class="widget-footer">
263 + <span class="widget-tech">{w.t}</span>
264 + <span class="widget-arrow" class:arrow-show={hovered === w.id}>→</span>
265 + </div>
266 + <div class="widget-glow widget-glow-green"></div>
267 + </a>
268 + {/each}
269 + </div>
270 + </div>
271 + </div>
272 + </div>
273 + </div>
274 + </main>
275 +</div>
276 +
277 +<!-- ═══ MODAL ═══ -->
278 +{#if modal}
279 + <div
280 + class="modal-overlay"
281 + on:click={closeModal}
282 + on:keydown={(e) => e.key === 'Escape' && closeModal()}
283 + role="dialog"
284 + aria-modal="true"
285 + transition:fade={{ duration: 150 }}
286 + >
287 + <div
288 + class="modal-box"
289 + on:click|stopPropagation
290 + role="document"
291 + transition:scale={{ duration: 200, start: 0.95, easing: cubicOut }}
292 + >
293 + <button class="modal-close" on:click={closeModal}>
294 + <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M18 6L6 18M6 6l12 12"/></svg>
295 + </button>
296 +
297 + <div class="modal-badge">{modal.tech}</div>
298 + <h2 class="modal-title">{modal.title}</h2>
299 +
300 + <div class="modal-section">
301 + <div class="modal-icon">👤</div>
302 + <div>
303 + <div class="modal-label">EN LA VIDA COTIDIANA</div>
304 + <p class="modal-text">{modal.everyday}</p>
305 + </div>
306 + </div>
307 +
308 + <div class="modal-divider"></div>
309 +
310 + <div class="modal-section">
311 + <div class="modal-icon">🏛️</div>
312 + <div>
313 + <div class="modal-label">EN EL PRESUPUESTO</div>
314 + <p class="modal-text">{modal.budget}</p>
315 + </div>
316 + </div>
317 +
318 + <div class="modal-example">
319 + <p>{modal.example}</p>
320 + </div>
321 + </div>
322 + </div>
323 +{/if}
324 +
116 325
117 <style> 326 <style>
118 - .clasificadores-page { 327 + /* ── Base ── */
328 + .page {
119 background: var(--theme-body); 329 background: var(--theme-body);
120 color: var(--theme-titulo); 330 color: var(--theme-titulo);
121 - font-family: 'Instrument Sans', sans-serif;
122 min-height: 100vh; 331 min-height: 100vh;
123 - max-width: 1000px; 332 + font-family: 'Instrument Sans', sans-serif;
124 - margin: 0 auto;
125 - padding: 0 2rem;
126 - display: flex;
127 - flex-direction: column;
128 - justify-content: center;
129 transition: background-color 0.2s, color 0.2s; 333 transition: background-color 0.2s, color 0.2s;
130 } 334 }
131 335
336 + /* ── Entrance animations ── */
337 + .anim {
338 + opacity: 0;
339 + transform: translateY(24px);
340 + transition: all 0.7s cubic-bezier(0.16,1,0.3,1);
341 + transition-delay: var(--d, 0ms);
342 + }
343 + .mounted .anim {
344 + opacity: 1;
345 + transform: translateY(0);
346 + }
347 +
132 /* ── Header ── */ 348 /* ── Header ── */
133 .page-header { 349 .page-header {
134 - padding: 1.25rem 0 1.5rem; 350 + border-bottom: 1px solid var(--theme-borde);
135 - margin-bottom: 1.25rem; 351 + padding: 1.5rem 2rem 2rem;
352 + max-width: 1120px;
353 + margin: 0 auto;
136 } 354 }
137 355
138 .breadcrumb { 356 .breadcrumb {
139 - margin-bottom: 0.75rem; 357 + margin-bottom: 1rem;
358 + font-family: 'DM Mono', monospace;
359 + font-size: 0.75rem;
140 } 360 }
141 361
142 .breadcrumb-link { 362 .breadcrumb-link {
143 - display: inline-block;
144 color: var(--theme-texto); 363 color: var(--theme-texto);
145 text-decoration: none; 364 text-decoration: none;
146 - font-family: 'DM Mono', monospace;
147 - font-size: 0.75rem;
148 - letter-spacing: 0.05em;
149 transition: color 0.2s; 365 transition: color 0.2s;
150 } 366 }
151 .breadcrumb-link:hover { 367 .breadcrumb-link:hover {
152 color: var(--theme-titulo); 368 color: var(--theme-titulo);
153 } 369 }
154 370
155 - .label-top { 371 + .sm-hidden { display: block; }
156 - display: block; 372 + .breadcrumb-full { display: none; }
157 - font-family: 'DM Mono', monospace; 373 +
158 - font-size: 0.68rem; 374 + @media (min-width: 640px) {
159 - letter-spacing: 0.15em; 375 + .sm-hidden { display: none; }
160 - text-transform: uppercase; 376 + .breadcrumb-full {
161 - color: var(--theme-texto); 377 + display: flex;
162 - margin-bottom: 0.5rem; 378 + align-items: center;
379 + gap: 0.5rem;
380 + color: var(--theme-texto);
381 + }
382 + .breadcrumb-full a {
383 + color: var(--theme-texto);
384 + text-decoration: none;
385 + transition: color 0.2s;
386 + }
387 + .breadcrumb-full a:hover {
388 + color: var(--theme-titulo);
389 + }
390 + .breadcrumb-sep {
391 + opacity: 0.5;
392 + }
393 + .breadcrumb-current {
394 + color: var(--theme-titulo);
395 + }
163 } 396 }
164 397
165 - h1 { 398 + .header-content h1 {
166 font-family: 'DM Serif Display', serif; 399 font-family: 'DM Serif Display', serif;
167 - font-size: clamp(1.8rem, 4vw, 2.5rem); 400 + font-size: clamp(1.75rem, 4vw, 2rem);
168 line-height: 1.1; 401 line-height: 1.1;
169 letter-spacing: -0.02em; 402 letter-spacing: -0.02em;
170 - margin-bottom: 0.75rem; 403 + margin-bottom: 0.5rem;
171 - color: var(--theme-titulo);
172 } 404 }
173 405
174 .subtitulo { 406 .subtitulo {
175 - max-width: 520px;
176 font-size: 0.95rem; 407 font-size: 0.95rem;
177 line-height: 1.5; 408 line-height: 1.5;
178 color: var(--theme-texto); 409 color: var(--theme-texto);
410 + max-width: 600px;
179 } 411 }
180 412
181 - /* ── Masonry Grid ── */ 413 + .help-inline {
182 - .grid-clasificadores { 414 + font-family: 'DM Serif Display', serif;
183 - column-count: 3; 415 + font-weight: 700;
184 - column-gap: 0.875rem; 416 + color: var(--theme-texto);
185 - margin-bottom: 1.5rem; 417 + border: 1px solid var(--theme-borde);
418 + border-radius: 50%;
419 + padding: 0 5px;
420 + font-size: 0.7rem;
186 } 421 }
187 422
188 - .card { 423 + main {
189 - display: block; 424 + max-width: 1120px;
190 - break-inside: avoid; 425 + margin: 0 auto;
191 - margin-bottom: 0.875rem; 426 + padding: 2rem 2rem 4rem;
192 - padding: 1.25rem 1.35rem; 427 + }
428 +
429 + /* ── Group (level 1) ── */
430 + .group {
193 background: var(--theme-surface); 431 background: var(--theme-surface);
194 - border-radius: 6px;
195 - text-decoration: none;
196 - color: var(--theme-titulo);
197 - transition: all 0.15s ease;
198 - box-shadow: 0 1px 2px rgba(0,0,0,0.04);
199 border: 1px solid var(--theme-borde); 432 border: 1px solid var(--theme-borde);
433 + border-radius: 1.5rem;
434 + padding: 1.25rem 1.125rem 1.125rem;
435 + position: relative;
436 + overflow: hidden;
200 } 437 }
201 - 438 + .group-stretch {
202 - .card:hover { 439 + display: flex;
203 - box-shadow: 0 2px 8px rgba(0,0,0,0.08); 440 + flex-direction: column;
204 - border-color: var(--theme-accent); 441 + height: 100%;
205 } 442 }
206 - 443 + .group-accent {
207 - .card:nth-child(1), 444 + position: absolute;
208 - .card:nth-child(5) { 445 + top: 0;
209 - padding: 1.5rem 1.35rem; 446 + left: 0;
447 + right: 0;
448 + height: 2px;
449 + background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--c) 30%, transparent), transparent);
450 + border-radius: 1.5rem 1.5rem 0 0;
210 } 451 }
211 - 452 + .group-header {
212 - .card-meta {
213 display: flex; 453 display: flex;
214 align-items: center; 454 align-items: center;
215 - gap: 0.4rem; 455 + gap: 0.625rem;
216 - margin-bottom: 0.6rem; 456 + margin-bottom: 0.875rem;
457 + padding: 0 0.25rem;
217 } 458 }
218 - 459 + .group-icon {
219 - .card-numero { 460 + width: 2rem;
220 - font-family: 'DM Mono', monospace; 461 + height: 2rem;
221 - font-size: 0.7rem; 462 + border-radius: 0.625rem;
222 - font-weight: 500; 463 + background: color-mix(in srgb, var(--c) 10%, transparent);
223 - color: var(--theme-accent); 464 + border: 1px solid color-mix(in srgb, var(--c) 20%, transparent);
224 - letter-spacing: 0.05em; 465 + display: flex;
466 + align-items: center;
467 + justify-content: center;
468 + flex-shrink: 0;
469 + color: var(--c);
225 } 470 }
226 - 471 + .group-label {
227 - .card-nombre {
228 font-family: 'DM Mono', monospace; 472 font-family: 'DM Mono', monospace;
229 - font-size: 0.6rem; 473 + font-size: 0.75rem;
474 + letter-spacing: 0.15em;
475 + font-weight: 600;
476 + line-height: 1;
477 + color: var(--theme-titulo);
478 + }
479 + .group-sub {
480 + font-family: 'Instrument Sans', sans-serif;
481 + font-size: 0.688rem;
230 color: var(--theme-texto); 482 color: var(--theme-texto);
231 - letter-spacing: 0.02em; 483 + margin-top: 0.25rem;
232 - padding-left: 0.4rem; 484 + opacity: 0.8;
233 - border-left: 1px solid var(--theme-borde);
234 } 485 }
235 486
236 - .card-titulo { 487 + .group-widgets {
237 - font-family: 'DM Serif Display', serif; 488 + display: flex;
238 - font-size: 1.35rem; 489 + flex-direction: column;
239 - font-weight: 400; 490 + gap: 0.625rem;
240 - line-height: 1.15; 491 + }
241 - letter-spacing: -0.015em; 492 + .widgets-stretch {
242 - margin-bottom: 0.35rem; 493 + display: flex;
494 + flex-direction: column;
495 + gap: 0.625rem;
496 + flex: 1;
497 + }
498 +
499 + /* ── Widget card (level 2) ── */
500 + .widget-card {
501 + display: block;
502 + background: var(--theme-fill);
503 + border: 1px solid var(--theme-borde);
504 + border-radius: 1rem;
505 + padding: 1.375rem;
506 + position: relative;
507 + overflow: hidden;
508 + cursor: pointer;
509 + text-decoration: none;
243 color: var(--theme-titulo); 510 color: var(--theme-titulo);
244 - transition: color 0.15s; 511 + transition: all 0.4s cubic-bezier(0.16,1,0.3,1);
512 + }
513 + .wh {
514 + background: color-mix(in srgb, var(--theme-fill) 80%, var(--theme-titulo) 5%);
515 + border-color: color-mix(in srgb, var(--theme-borde) 50%, var(--theme-texto) 20%);
516 + transform: translateY(-1px);
517 + }
518 + .widget-grow {
519 + flex: 1;
520 + display: flex;
521 + flex-direction: column;
245 } 522 }
246 523
247 - .card:hover .card-titulo { 524 + /* Horizontal variant */
248 - color: var(--theme-accent); 525 + .widget-h {
526 + display: flex;
527 + justify-content: space-between;
528 + align-items: center;
529 + }
530 + .widget-h-left { flex: 1; }
531 + .widget-h-right {
532 + display: flex;
533 + align-items: center;
534 + gap: 0.625rem;
535 + flex-shrink: 0;
249 } 536 }
250 537
251 - .card-desc { 538 + .widget-top {
252 - font-size: 0.78rem; 539 + display: flex;
253 - color: var(--theme-texto); 540 + justify-content: space-between;
254 - line-height: 1.45; 541 + align-items: flex-start;
542 + gap: 0.625rem;
255 margin-bottom: 0.5rem; 543 margin-bottom: 0.5rem;
544 + position: relative;
545 + z-index: 1;
546 + }
547 + .widget-q {
548 + font-size: 1rem;
549 + font-weight: 600;
550 + letter-spacing: -0.01em;
551 + line-height: 1.2;
552 + font-family: 'Instrument Sans', sans-serif;
553 + color: var(--theme-titulo);
256 } 554 }
555 + .widget-desc {
556 + font-family: 'Instrument Sans', sans-serif;
557 + font-size: 0.75rem;
558 + color: var(--theme-texto);
559 + line-height: 1.5;
560 + position: relative;
561 + z-index: 1;
562 + }
563 + .widget-desc-grow { flex: 1; }
564 + .widget-footer {
565 + display: flex;
566 + justify-content: space-between;
567 + align-items: center;
568 + margin-top: 1rem;
569 + position: relative;
570 + z-index: 1;
571 + }
572 + .widget-tech {
573 + font-family: 'DM Mono', monospace;
574 + font-size: 0.5rem;
575 + letter-spacing: 0.12em;
576 + color: var(--theme-texto);
577 + text-transform: uppercase;
578 + }
579 + .widget-arrow {
580 + font-family: 'Instrument Sans', sans-serif;
581 + font-size: 0.813rem;
582 + color: transparent;
583 + transform: translateX(-6px);
584 + transition: all 0.35s cubic-bezier(0.16,1,0.3,1);
585 + }
586 + .arrow-show {
587 + color: var(--theme-titulo);
588 + transform: translateX(0);
589 + }
590 + .widget-glow {
591 + position: absolute;
592 + inset: 0;
593 + pointer-events: none;
594 + opacity: 0;
595 + transition: opacity 0.4s;
596 + }
597 + .widget-glow-accent {
598 + background: radial-gradient(circle at 50% 50%, color-mix(in srgb, var(--theme-accent) 6%, transparent), transparent 70%);
599 + }
600 + .widget-glow-green {
601 + background: radial-gradient(circle at 50% 50%, rgba(74,139,110,0.06), transparent 70%);
602 + }
603 + .wh > .widget-glow,
604 + .wh .widget-glow { opacity: 1; }
257 605
258 - .card-ejemplo { 606 + /* ── Sub-group (¿En qué?) ── */
259 - font-size: 0.68rem; 607 + .sub-group {
608 + background: color-mix(in srgb, var(--theme-surface) 60%, var(--theme-body) 40%);
609 + border: 1px solid var(--theme-borde);
610 + border-radius: 1.125rem;
611 + padding: 1rem 0.875rem 0.875rem;
612 + position: relative;
613 + overflow: hidden;
614 + }
615 + .sub-group-accent {
616 + position: absolute;
617 + top: 0;
618 + left: 15%;
619 + right: 15%;
620 + height: 1px;
621 + background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--c) 25%, transparent), transparent);
622 + }
623 + .sub-group-header {
624 + display: flex;
625 + align-items: center;
626 + justify-content: space-between;
627 + margin-bottom: 0.75rem;
628 + padding: 0 0.25rem;
629 + }
630 + .sub-group-left {
631 + display: flex;
632 + align-items: center;
633 + gap: 0.5rem;
634 + }
635 + .sub-group-icon {
636 + width: 1.5rem;
637 + height: 1.5rem;
638 + border-radius: 0.375rem;
639 + background: color-mix(in srgb, var(--c) 10%, transparent);
640 + border: 1px solid color-mix(in srgb, var(--c) 18%, transparent);
641 + display: flex;
642 + align-items: center;
643 + justify-content: center;
644 + flex-shrink: 0;
645 + color: var(--c);
646 + }
647 + .sub-group-label {
648 + font-family: 'Instrument Sans', sans-serif;
649 + font-size: 0.75rem;
650 + font-weight: 600;
260 color: var(--theme-texto); 651 color: var(--theme-texto);
652 + line-height: 1.1;
653 + }
654 + .sub-group-tag {
655 + font-family: 'DM Mono', monospace;
656 + font-size: 0.438rem;
657 + letter-spacing: 0.1em;
658 + margin-top: 0.125rem;
659 + color: #4A8B6E;
261 opacity: 0.7; 660 opacity: 0.7;
262 - line-height: 1.4;
263 } 661 }
264 662
265 - /* ── Footer ── */ 663 + .sub-grid {
266 - .page-footer { 664 + display: grid;
267 - padding-top: 1rem; 665 + grid-template-columns: 1fr 1fr;
666 + gap: 0.5rem;
667 + }
668 +
669 + /* ── Sub-widget (level 3) ── */
670 + .sub-card {
671 + background: var(--theme-fill);
672 + border-color: var(--theme-borde);
673 + padding: 1rem;
674 + border-radius: 0.75rem;
675 + }
676 + .sub-card.wh {
677 + background: color-mix(in srgb, var(--theme-fill) 80%, var(--theme-titulo) 5%);
678 + border-color: color-mix(in srgb, var(--theme-borde) 50%, var(--theme-texto) 20%);
679 + }
680 + .sub-q {
681 + font-size: 0.813rem;
682 + font-weight: 500;
683 + color: var(--theme-titulo);
684 + }
685 + .sub-desc {
686 + font-size: 0.688rem;
687 + opacity: 0.9;
688 + }
689 + .sub-footer { margin-top: 0.75rem; }
690 + .sub-tech { font-size: 0.375rem; }
691 +
692 + .bottom-grid {
693 + display: grid;
694 + grid-template-columns: 1fr 1fr;
695 + gap: 0.625rem;
696 + }
697 +
698 + /* ── Main grid ── */
699 + .main-grid {
700 + display: grid;
701 + grid-template-columns: 1fr 2fr;
702 + gap: 1rem;
703 + margin-top: 1rem;
704 + align-items: stretch;
705 + }
706 + .col-stretch {
268 display: flex; 707 display: flex;
269 flex-direction: column; 708 flex-direction: column;
270 - gap: 0.35rem;
271 } 709 }
272 710
273 - .page-footer p { 711 + /* ── Help button ── */
274 - font-size: 0.75rem; 712 + .help-btn {
713 + width: 1.625rem;
714 + height: 1.625rem;
715 + border-radius: 50%;
716 + border: 1px solid var(--theme-borde);
717 + background: transparent;
275 color: var(--theme-texto); 718 color: var(--theme-texto);
719 + font-family: 'DM Serif Display', serif;
720 + font-size: 0.75rem;
721 + font-weight: 600;
722 + cursor: pointer;
723 + transition: all 0.3s;
724 + display: flex;
725 + align-items: center;
726 + justify-content: center;
727 + flex-shrink: 0;
728 + }
729 + .help-btn:hover {
730 + border-color: var(--theme-accent);
731 + color: var(--theme-accent);
732 + background: color-mix(in srgb, var(--theme-accent) 8%, transparent);
276 } 733 }
734 + .help-sm { width: 1.375rem; height: 1.375rem; font-size: 0.625rem; }
735 + .help-xs { width: 1.25rem; height: 1.25rem; font-size: 0.563rem; }
277 736
278 - .page-footer a { 737 + /* ── Modal ── */
738 + .modal-overlay {
739 + position: fixed;
740 + inset: 0;
741 + background: rgba(0,0,0,0.6);
742 + backdrop-filter: blur(12px);
743 + display: flex;
744 + align-items: center;
745 + justify-content: center;
746 + z-index: 1000;
747 + padding: 2.5rem;
748 + }
749 + .modal-box {
750 + background: var(--theme-surface);
751 + border: 1px solid var(--theme-borde);
752 + border-radius: 1.5rem;
753 + max-width: 520px;
754 + width: 100%;
755 + padding: 2rem 2.25rem;
756 + position: relative;
757 + max-height: 85vh;
758 + overflow-y: auto;
759 + }
760 + .modal-close {
761 + position: absolute;
762 + top: 1rem;
763 + right: 1rem;
764 + background: transparent;
765 + border: 1px solid var(--theme-borde);
766 + border-radius: 50%;
767 + width: 2rem;
768 + height: 2rem;
769 + display: flex;
770 + align-items: center;
771 + justify-content: center;
279 color: var(--theme-texto); 772 color: var(--theme-texto);
280 - text-decoration: none; 773 + cursor: pointer;
281 - border-bottom: 1px solid var(--theme-borde); 774 + transition: all 0.2s;
282 - transition: border-color 0.2s, color 0.2s;
283 } 775 }
284 - .page-footer a:hover { 776 + .modal-close:hover {
285 - border-color: var(--theme-accent); 777 + border-color: var(--theme-texto);
286 color: var(--theme-titulo); 778 color: var(--theme-titulo);
287 } 779 }
288 - 780 + .modal-badge {
289 - .footnote { 781 + font-family: 'DM Mono', monospace;
782 + font-size: 0.563rem;
783 + letter-spacing: 0.15em;
784 + border-radius: 0.375rem;
785 + padding: 0.25rem 0.563rem;
786 + display: inline-block;
787 + margin-bottom: 0.875rem;
788 + background: color-mix(in srgb, var(--theme-accent) 12%, transparent);
789 + color: var(--theme-accent);
790 + }
791 + .modal-title {
792 + font-family: 'DM Serif Display', serif;
793 + font-size: 1.5rem;
794 + font-weight: 400;
795 + letter-spacing: -0.02em;
796 + line-height: 1.15;
797 + margin-bottom: 1.5rem;
798 + color: var(--theme-titulo);
799 + }
800 + .modal-section {
801 + display: flex;
802 + gap: 0.875rem;
803 + }
804 + .modal-icon {
805 + font-size: 1.125rem;
806 + width: 2.25rem;
807 + height: 2.25rem;
808 + background: var(--theme-fill);
809 + border-radius: 0.625rem;
810 + display: flex;
811 + align-items: center;
812 + justify-content: center;
813 + flex-shrink: 0;
814 + border: 1px solid var(--theme-borde);
815 + }
816 + .modal-label {
290 font-family: 'DM Mono', monospace; 817 font-family: 'DM Mono', monospace;
291 - font-size: 0.58rem; 818 + font-size: 0.563rem;
819 + letter-spacing: 0.15em;
292 color: var(--theme-texto); 820 color: var(--theme-texto);
293 - opacity: 0.7; 821 + margin-bottom: 0.313rem;
294 - line-height: 1.5; 822 + }
823 + .modal-text {
824 + font-family: 'Instrument Sans', sans-serif;
825 + font-size: 0.813rem;
826 + line-height: 1.65;
827 + color: var(--theme-texto);
828 + }
829 + .modal-divider {
830 + height: 1px;
831 + background: var(--theme-borde);
832 + margin: 1.125rem 0;
833 + margin-left: 3.125rem;
834 + }
835 + .modal-example {
836 + margin-top: 1.25rem;
837 + margin-left: 3.125rem;
838 + padding: 0.875rem 1.125rem;
839 + border-left: 3px solid var(--theme-accent);
840 + background: color-mix(in srgb, var(--theme-accent) 5%, transparent);
841 + border-radius: 0 0.625rem 0.625rem 0;
842 + }
843 + .modal-example p {
844 + font-family: 'Instrument Sans', sans-serif;
845 + font-size: 0.75rem;
846 + line-height: 1.6;
847 + color: var(--theme-texto);
848 + font-style: italic;
295 } 849 }
296 850
851 + /* ── Responsive ── */
297 @media (max-width: 900px) { 852 @media (max-width: 900px) {
298 - .grid-clasificadores { 853 + .main-grid {
299 - column-count: 2; 854 + grid-template-columns: 1fr;
300 } 855 }
301 - .clasificadores-page { 856 + .page-header,
302 - justify-content: flex-start; 857 + main {
303 - padding-top: 1rem; 858 + padding-left: 1.5rem;
859 + padding-right: 1.5rem;
304 } 860 }
305 } 861 }
306 862
307 - @media (max-width: 540px) { 863 + @media (max-width: 640px) {
308 - .clasificadores-page { 864 + .page-header,
309 - padding: 1rem 1rem 1.5rem; 865 + main {
310 - justify-content: flex-start; 866 + padding-left: 1rem;
867 + padding-right: 1rem;
868 + }
869 + .sub-grid,
870 + .bottom-grid {
871 + grid-template-columns: 1fr;
872 + }
873 + .widget-h {
874 + flex-direction: column;
875 + align-items: flex-start;
876 + gap: 0.75rem;
311 } 877 }
312 - .grid-clasificadores { 878 + .widget-h-right {
313 - column-count: 1; 879 + width: 100%;
880 + justify-content: space-between;
314 } 881 }
315 - .card { 882 + .modal-overlay {
316 - padding: 1rem 1.15rem; 883 + padding: 1rem;
317 } 884 }
318 - .card:nth-child(1), 885 + .modal-box {
319 - .card:nth-child(5) { 886 + padding: 1.5rem;
320 - padding: 1rem 1.15rem;
321 } 887 }
322 - .card-pregunta { 888 + .modal-example,
323 - font-size: 1.1rem; 889 + .modal-divider {
890 + margin-left: 0;
324 } 891 }
325 - h1 { font-size: 1.4rem; }
326 } 892 }
327 </style> 893 </style>
......
...@@ -836,7 +836,7 @@ ...@@ -836,7 +836,7 @@
836 <div class="min-h-screen" style="font-family: 'Instrument Sans', sans-serif; background-color: var(--theme-body); color: var(--theme-titulo); transition: background-color 0.2s, color 0.2s;"> 836 <div class="min-h-screen" style="font-family: 'Instrument Sans', sans-serif; background-color: var(--theme-body); color: var(--theme-titulo); transition: background-color 0.2s, color 0.2s;">
837 <!-- Header pedagógico --> 837 <!-- Header pedagógico -->
838 <header class="border-b" style="border-color: var(--theme-borde); background-color: var(--theme-body);"> 838 <header class="border-b" style="border-color: var(--theme-borde); background-color: var(--theme-body);">
839 - <div class="max-w-screen-xl mx-auto px-4 sm:px-6 py-6"> 839 + <div class="max-w-screen-xl mx-auto px-4 sm:px-6 {viewMode === 'mapa' ? 'py-3' : 'py-6'}">
840 <!-- Breadcrumb: responsive --> 840 <!-- Breadcrumb: responsive -->
841 <nav class="mb-4" style="font-family: 'DM Mono', monospace; font-size: 0.75rem;"> 841 <nav class="mb-4" style="font-family: 'DM Mono', monospace; font-size: 0.75rem;">
842 <!-- Móvil: solo padre --> 842 <!-- Móvil: solo padre -->
...@@ -855,48 +855,51 @@ ...@@ -855,48 +855,51 @@
855 <p class="text-xs uppercase tracking-widest mb-2" style="font-family: 'DM Mono', monospace; color: var(--theme-texto);"> 855 <p class="text-xs uppercase tracking-widest mb-2" style="font-family: 'DM Mono', monospace; color: var(--theme-texto);">
856 Clasificador 03 856 Clasificador 03
857 </p> 857 </p>
858 - <h1 class="text-3xl mb-3" style="font-family: 'DM Serif Display', serif; color: var(--theme-titulo);"> 858 + <h1 class="{viewMode === 'mapa' ? 'text-2xl mb-2' : 'text-3xl mb-3'}" style="font-family: 'DM Serif Display', serif; color: var(--theme-titulo);">
859 ¿En qué se gasta? 859 ¿En qué se gasta?
860 </h1> 860 </h1>
861 - <p class="leading-relaxed mb-3" style="color: var(--theme-texto);">
862 - Ordena el gasto según <strong style="color: var(--theme-titulo);">qué se compra o paga</strong>:
863 - sueldos, alquileres, combustible, medicamentos, construcciones, deudas, transferencias.
864 - Es la forma más directa de entender en qué se usa el dinero público.
865 - </p>
866 861
867 - {#if totalItems > 0} 862 + {#if viewMode !== 'mapa'}
868 - <p class="text-sm mb-5" style="font-family: 'DM Mono', monospace; color: var(--theme-texto);"> 863 + <p class="leading-relaxed mb-3" style="color: var(--theme-texto);">
869 - <span style="color: var(--theme-titulo); font-weight: 500;">{totalItems}</span> categorías de gasto: 864 + Ordena el gasto según <strong style="color: var(--theme-titulo);">qué se compra o paga</strong>:
870 - {countByNivel.grupos} grupos, {countByNivel.subgrupos} subgrupos, {countByNivel.partidas} partidas, {countByNivel.subpartidas} subpartidas 865 + sueldos, alquileres, combustible, medicamentos, construcciones, deudas, transferencias.
866 + Es la forma más directa de entender en qué se usa el dinero público.
871 </p> 867 </p>
872 - {/if}
873 868
874 - <!-- Jerarquía --> 869 + {#if totalItems > 0}
875 - <div class="hidden sm:flex flex-wrap items-center gap-2 sm:gap-3 text-xs mb-6" style="font-family: 'DM Mono', monospace;"> 870 + <p class="text-sm mb-5" style="font-family: 'DM Mono', monospace; color: var(--theme-texto);">
876 - <span class="px-2.5 py-1 rounded-full font-medium" style="background-color: var(--theme-accent); color: var(--theme-body); opacity: 0.9;"> 871 + <span style="color: var(--theme-titulo); font-weight: 500;">{totalItems}</span> categorías de gasto:
877 - Grupo 872 + {countByNivel.grupos} grupos, {countByNivel.subgrupos} subgrupos, {countByNivel.partidas} partidas, {countByNivel.subpartidas} subpartidas
878 - </span> 873 + </p>
879 - <span style="color: var(--theme-texto);">→</span> 874 + {/if}
880 - <span class="px-2.5 py-1 rounded-full" style="background-color: var(--theme-fill); color: var(--theme-titulo); border: 1px solid var(--theme-borde);"> 875 +
881 - Subgrupo 876 + <!-- Jerarquía -->
882 - </span> 877 + <div class="hidden sm:flex flex-wrap items-center gap-2 sm:gap-3 text-xs mb-6" style="font-family: 'DM Mono', monospace;">
883 - <span style="color: var(--theme-texto);">→</span> 878 + <span class="px-2.5 py-1 rounded-full font-medium" style="background-color: var(--theme-accent); color: var(--theme-body); opacity: 0.9;">
884 - <span class="px-2.5 py-1 rounded-full" style="background-color: var(--theme-fill); color: var(--theme-titulo); border: 1px solid var(--theme-borde);"> 879 + Grupo
885 - Partida 880 + </span>
886 - </span> 881 + <span style="color: var(--theme-texto);">→</span>
887 - <span style="color: var(--theme-texto);">→</span> 882 + <span class="px-2.5 py-1 rounded-full" style="background-color: var(--theme-fill); color: var(--theme-titulo); border: 1px solid var(--theme-borde);">
888 - <span class="px-2.5 py-1 rounded-full" style="background-color: var(--theme-fill); color: var(--theme-titulo); border: 1px solid var(--theme-borde);"> 883 + Subgrupo
889 - Subpartida 884 + </span>
890 - </span> 885 + <span style="color: var(--theme-texto);">→</span>
891 - </div> 886 + <span class="px-2.5 py-1 rounded-full" style="background-color: var(--theme-fill); color: var(--theme-titulo); border: 1px solid var(--theme-borde);">
892 - <!-- Versión móvil simplificada --> 887 + Partida
893 - <p class="sm:hidden text-sm mb-4" style="font-family: 'DM Mono', monospace; color: var(--theme-texto);"> 888 + </span>
894 - Jerarquía: Grupo → Subgrupo → Partida → Subpartida 889 + <span style="color: var(--theme-texto);">→</span>
895 - </p> 890 + <span class="px-2.5 py-1 rounded-full" style="background-color: var(--theme-fill); color: var(--theme-titulo); border: 1px solid var(--theme-borde);">
891 + Subpartida
892 + </span>
893 + </div>
894 + <!-- Versión móvil simplificada -->
895 + <p class="sm:hidden text-sm mb-4" style="font-family: 'DM Mono', monospace; color: var(--theme-texto);">
896 + Jerarquía: Grupo → Subgrupo → Partida → Subpartida
897 + </p>
898 + {/if}
896 </div> 899 </div>
897 900
898 <!-- Controles de visualización (fuera del max-w-3xl para usar todo el ancho) --> 901 <!-- Controles de visualización (fuera del max-w-3xl para usar todo el ancho) -->
899 - <div class="mt-8"> 902 + <div class="{viewMode === 'mapa' ? 'mt-3' : 'mt-8'}">
900 <!-- Fila principal: Toggle + Guía + Filtros --> 903 <!-- Fila principal: Toggle + Guía + Filtros -->
901 <div class="flex flex-col md:flex-row gap-4 md:items-center md:justify-between"> 904 <div class="flex flex-col md:flex-row gap-4 md:items-center md:justify-between">
902 <!-- Toggle + Guía inline --> 905 <!-- Toggle + Guía inline -->
......
1 <script> 1 <script>
2 import { supabase } from '$lib/supabase'; 2 import { supabase } from '$lib/supabase';
3 import { onMount } from 'svelte'; 3 import { onMount } from 'svelte';
4 + import { fade, scale } from 'svelte/transition';
5 + import { cubicOut } from 'svelte/easing';
4 6
5 let loading = $state(true); 7 let loading = $state(true);
6 let searchQuery = $state(''); 8 let searchQuery = $state('');
...@@ -9,6 +11,7 @@ ...@@ -9,6 +11,7 @@
9 let selectedTipo = $state(null); 11 let selectedTipo = $state(null);
10 let selectedItem = $state(null); 12 let selectedItem = $state(null);
11 let highlightedItem = $state(null); 13 let highlightedItem = $state(null);
14 + let sidebarOpen = $state(false);
12 15
13 onMount(async () => { 16 onMount(async () => {
14 const { data, error } = await supabase 17 const { data, error } = await supabase
...@@ -154,6 +157,7 @@ ...@@ -154,6 +157,7 @@
154 selectedItem = null; 157 selectedItem = null;
155 searchQuery = ''; 158 searchQuery = '';
156 highlightedItem = null; 159 highlightedItem = null;
160 + sidebarOpen = false;
157 } 161 }
158 162
159 function openDetail(item) { 163 function openDetail(item) {
...@@ -172,6 +176,7 @@ ...@@ -172,6 +176,7 @@
172 selectedTipo = targetTipo; 176 selectedTipo = targetTipo;
173 highlightedItem = item.rubro; 177 highlightedItem = item.rubro;
174 searchQuery = ''; 178 searchQuery = '';
179 + sidebarOpen = false;
175 180
176 setTimeout(() => { 181 setTimeout(() => {
177 const prefix = item.nivel === 'clase' ? 'cl' : item.nivel === 'cuenta' ? 'cu' : 'sc'; 182 const prefix = item.nivel === 'clase' ? 'cl' : item.nivel === 'cuenta' ? 'cu' : 'sc';
...@@ -180,6 +185,11 @@ ...@@ -180,6 +185,11 @@
180 element.scrollIntoView({ behavior: 'smooth', block: 'center' }); 185 element.scrollIntoView({ behavior: 'smooth', block: 'center' });
181 } 186 }
182 }, 100); 187 }, 100);
188 +
189 + // Limpiar highlight después de la animación (2 pulsos de 0.8s = 1.6s + margen)
190 + setTimeout(() => {
191 + highlightedItem = null;
192 + }, 2000);
183 } 193 }
184 } 194 }
185 195
...@@ -200,85 +210,192 @@ ...@@ -200,85 +210,192 @@
200 let tipoContent = $derived(selectedTipo ? getItemsForTipo(selectedTipo.rubro) : { clases: [] }); 210 let tipoContent = $derived(selectedTipo ? getItemsForTipo(selectedTipo.rubro) : { clases: [] });
201 let searchResults = $derived(searchGlobal(searchQuery)); 211 let searchResults = $derived(searchGlobal(searchQuery));
202 let isSearching = $derived(searchQuery.length >= 2); 212 let isSearching = $derived(searchQuery.length >= 2);
213 +
214 + // Contadores
215 + let totalItems = $derived(allItems.length);
216 + let countByNivel = $derived({
217 + tipos: allItems.filter(i => i.nivel === 'tipo').length,
218 + clases: allItems.filter(i => i.nivel === 'clase').length,
219 + cuentas: allItems.filter(i => i.nivel === 'cuenta').length,
220 + subcuentas: allItems.filter(i => i.nivel === 'sub_cuenta').length
221 + });
203 </script> 222 </script>
204 223
205 <svelte:head> 224 <svelte:head>
206 <title>Rubros | Presupuesto Público</title> 225 <title>Rubros | Presupuesto Público</title>
226 + <link rel="preconnect" href="https://fonts.googleapis.com" />
227 + <link href="https://fonts.googleapis.com/css2?family=DM+Serif+Display:ital@0;1&family=DM+Mono:wght@400;500&family=Instrument+Sans:wght@400;500;600&display=swap" rel="stylesheet" />
207 </svelte:head> 228 </svelte:head>
208 229
209 -<div class="min-h-screen bg-white"> 230 +<div class="min-h-screen" style="font-family: 'Instrument Sans', sans-serif; background-color: var(--theme-body); color: var(--theme-titulo); transition: background-color 0.2s, color 0.2s;">
210 - <!-- Header --> 231 + <!-- Header pedagógico -->
211 - <header class="border-b bg-slate-50"> 232 + <header class="border-b" style="border-color: var(--theme-borde); background-color: var(--theme-body);">
212 - <div class="max-w-screen-2xl mx-auto px-6 py-4"> 233 + <div class="max-w-screen-xl mx-auto px-4 sm:px-6 py-6">
213 - <nav class="text-sm text-slate-500 mb-2"> 234 + <!-- Breadcrumb: responsive -->
214 - <a href="/" class="hover:text-slate-700">Inicio</a> 235 + <nav class="mb-4" style="font-family: 'DM Mono', monospace; font-size: 0.75rem;">
215 - <span class="mx-2">/</span> 236 + <!-- Móvil: solo padre -->
216 - <a href="/clasificadores" class="hover:text-slate-700">Clasificadores</a> 237 + <a href="/clasificadores" class="sm:hidden transition-colors" style="color: var(--theme-texto);">
217 - <span class="mx-2">/</span> 238 + ← Clasificadores
218 - <span class="text-slate-900">Rubros</span> 239 + </a>
240 + <!-- Desktop: ruta completa -->
241 + <div class="hidden sm:flex items-center gap-2" style="color: var(--theme-texto);">
242 + <a href="/" class="transition-colors hover:opacity-80">Inicio</a>
243 + <span style="opacity: 0.5;">/</span>
244 + <a href="/clasificadores" class="transition-colors hover:opacity-80">Clasificadores</a>
245 + </div>
219 </nav> 246 </nav>
220 - <div class="flex items-center justify-between"> 247 +
221 - <h1 class="text-2xl font-light text-slate-900">Clasificador por Rubros de Ingresos</h1> 248 + <div class="max-w-3xl">
249 + <p class="text-xs uppercase tracking-widest mb-2" style="font-family: 'DM Mono', monospace; color: var(--theme-texto);">
250 + Clasificador 01
251 + </p>
252 + <h1 class="text-3xl mb-3" style="font-family: 'DM Serif Display', serif; color: var(--theme-titulo);">
253 + ¿De dónde vienen los ingresos?
254 + </h1>
255 +
256 + <p class="leading-relaxed mb-3" style="color: var(--theme-texto);">
257 + Organiza los ingresos del Estado según su <strong style="color: var(--theme-titulo);">origen económico</strong>:
258 + impuestos, ventas de bienes, regalías, transferencias, créditos.
259 + Es la forma de entender cómo se financia el presupuesto público.
260 + </p>
261 +
262 + {#if totalItems > 0}
263 + <p class="text-sm mb-5" style="font-family: 'DM Mono', monospace; color: var(--theme-texto);">
264 + <span style="color: var(--theme-titulo); font-weight: 500;">{totalItems}</span> categorías de ingreso:
265 + {countByNivel.tipos} tipos, {countByNivel.clases} clases, {countByNivel.cuentas} cuentas, {countByNivel.subcuentas} subcuentas
266 + </p>
267 + {/if}
268 +
269 + <!-- Jerarquía -->
270 + <div class="hidden sm:flex flex-wrap items-center gap-2 sm:gap-3 text-xs mb-6" style="font-family: 'DM Mono', monospace;">
271 + <span class="px-2.5 py-1 rounded-full font-medium" style="background-color: var(--theme-accent); color: var(--theme-body); opacity: 0.9;">
272 + Tipo
273 + </span>
274 + <span style="color: var(--theme-texto);">→</span>
275 + <span class="px-2.5 py-1 rounded-full" style="background-color: var(--theme-fill); color: var(--theme-titulo); border: 1px solid var(--theme-borde);">
276 + Clase
277 + </span>
278 + <span style="color: var(--theme-texto);">→</span>
279 + <span class="px-2.5 py-1 rounded-full" style="background-color: var(--theme-fill); color: var(--theme-titulo); border: 1px solid var(--theme-borde);">
280 + Cuenta
281 + </span>
282 + <span style="color: var(--theme-texto);">→</span>
283 + <span class="px-2.5 py-1 rounded-full" style="background-color: var(--theme-fill); color: var(--theme-titulo); border: 1px solid var(--theme-borde);">
284 + Subcuenta
285 + </span>
286 + </div>
287 + <!-- Versión móvil simplificada -->
288 + <p class="sm:hidden text-sm mb-4" style="font-family: 'DM Mono', monospace; color: var(--theme-texto);">
289 + Jerarquía: Tipo → Clase → Cuenta → Subcuenta
290 + </p>
222 </div> 291 </div>
223 </div> 292 </div>
224 </header> 293 </header>
225 294
226 {#if loading} 295 {#if loading}
227 <div class="flex items-center justify-center py-20"> 296 <div class="flex items-center justify-center py-20">
228 - <p class="text-slate-500">Cargando clasificador...</p> 297 + <p style="color: var(--theme-texto);">Cargando clasificador...</p>
229 </div> 298 </div>
230 {:else} 299 {:else}
231 - <div class="max-w-screen-2xl mx-auto flex"> 300 + <!-- Botón móvil para abrir sidebar -->
301 + <div class="lg:hidden px-4 py-3 border-b" style="border-color: var(--theme-borde); background-color: var(--theme-fill);">
302 + <button
303 + onclick={() => sidebarOpen = !sidebarOpen}
304 + class="flex items-center gap-2 text-sm"
305 + style="color: var(--theme-texto);"
306 + >
307 + <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
308 + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
309 + </svg>
310 + <span class="font-medium">{selectedTipo ? getDescripcion(selectedTipo) : 'Seleccionar tipo'}</span>
311 + <svg class="w-4 h-4 ml-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24">
312 + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
313 + </svg>
314 + </button>
315 + </div>
316 +
317 + <div class="max-w-screen-xl mx-auto flex px-4">
232 <!-- Sidebar izquierda: Tipos --> 318 <!-- Sidebar izquierda: Tipos -->
233 - <aside class="w-64 flex-shrink-0 border-r bg-slate-50/50"> 319 + <!-- En móvil: overlay, en desktop: sidebar fijo -->
234 - <div class="sticky top-0 h-screen overflow-y-auto p-4"> 320 + {#if sidebarOpen}
321 + <div
322 + transition:fade={{ duration: 150 }}
323 + class="fixed inset-0 bg-black/30 z-40 lg:hidden"
324 + onclick={() => sidebarOpen = false}
325 + ></div>
326 + {/if}
327 + <aside class="
328 + {sidebarOpen ? 'translate-x-0' : '-translate-x-full'}
329 + lg:translate-x-0
330 + fixed lg:relative
331 + inset-y-0 left-0
332 + w-72 lg:w-64
333 + z-50 lg:z-auto
334 + transition-transform duration-200 ease-in-out
335 + lg:flex-shrink-0
336 + shadow-xl lg:shadow-none
337 + sidebar-left
338 + ">
339 + <div class="h-full lg:h-screen lg:sticky lg:top-0 overflow-y-auto py-6 px-4 lg:px-0 lg:pr-6">
340 + <!-- Cerrar en móvil -->
341 + <div class="flex justify-between items-center mb-4 lg:hidden">
342 + <span class="text-sm font-medium" style="color: var(--theme-titulo);">Tipos de ingreso</span>
343 + <button onclick={() => sidebarOpen = false} style="color: var(--theme-texto);">
344 + <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
345 + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
346 + </svg>
347 + </button>
348 + </div>
349 +
235 <!-- Buscador --> 350 <!-- Buscador -->
236 <div class="mb-6"> 351 <div class="mb-6">
237 <input 352 <input
238 type="text" 353 type="text"
239 bind:value={searchQuery} 354 bind:value={searchQuery}
240 placeholder="Buscar..." 355 placeholder="Buscar..."
241 - class="w-full px-3 py-2 text-sm border border-slate-200 rounded-md bg-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" 356 + class="w-full px-3 py-2 text-sm rounded-md focus:outline-none focus:ring-2"
357 + style="border: 1px solid var(--theme-borde); background-color: var(--theme-surface); color: var(--theme-titulo);"
242 /> 358 />
243 </div> 359 </div>
244 360
245 <!-- Resultados de búsqueda --> 361 <!-- Resultados de búsqueda -->
246 {#if isSearching} 362 {#if isSearching}
247 <div class="mb-4"> 363 <div class="mb-4">
248 - <p class="text-xs text-slate-500 uppercase tracking-wide mb-2"> 364 + <p class="text-xs uppercase tracking-wide mb-2" style="color: var(--theme-texto);">
249 {searchResults.length} resultados 365 {searchResults.length} resultados
250 </p> 366 </p>
251 <div class="space-y-1"> 367 <div class="space-y-1">
252 {#each searchResults as result} 368 {#each searchResults as result}
253 <button 369 <button
254 - class="w-full text-left px-2 py-2 text-sm rounded hover:bg-white hover:shadow-sm transition-all" 370 + class="w-full text-left px-2 py-2 text-sm rounded transition-all"
371 + style="color: var(--theme-titulo);"
255 onclick={() => goToSearchResult(result)} 372 onclick={() => goToSearchResult(result)}
256 > 373 >
257 - <span class="text-xs text-slate-400 block">{getNivelLabel(result.nivel)}</span> 374 + <span class="text-xs block" style="color: var(--theme-texto);">{getNivelLabel(result.nivel)}</span>
258 - <span class="font-mono text-xs text-blue-600">{result.rubro}</span> 375 + <span class="font-mono text-xs font-medium" style="color: var(--theme-accent);">{result.rubro}</span>
259 - <span class="text-slate-700 ml-1">{getDescripcion(result)}</span> 376 + <span class="ml-1">{getDescripcion(result)}</span>
260 </button> 377 </button>
261 {/each} 378 {/each}
262 {#if searchResults.length === 0} 379 {#if searchResults.length === 0}
263 - <p class="text-sm text-slate-400 px-2">Sin resultados</p> 380 + <p class="text-sm px-2" style="color: var(--theme-texto);">Sin resultados</p>
264 {/if} 381 {/if}
265 </div> 382 </div>
266 </div> 383 </div>
267 {:else} 384 {:else}
268 <!-- Lista de tipos --> 385 <!-- Lista de tipos -->
269 <nav> 386 <nav>
270 - <p class="text-xs text-slate-500 uppercase tracking-wide mb-3 px-2">Tipos</p> 387 + <p class="text-xs uppercase tracking-wide mb-3 px-2 hidden lg:block" style="color: var(--theme-texto);">Tipos</p>
271 <ul class="space-y-1"> 388 <ul class="space-y-1">
272 {#each tipos as tipo} 389 {#each tipos as tipo}
273 <li> 390 <li>
274 <button 391 <button
275 - class="w-full text-left px-3 py-2 rounded-md text-sm transition-all 392 + class="w-full text-left px-3 py-2 rounded-md text-sm transition-all"
276 - {selectedTipo?.rubro === tipo.rubro 393 + style="{selectedTipo?.rubro === tipo.rubro
277 - ? 'bg-blue-50 text-blue-900 font-medium border-l-2 border-blue-500' 394 + ? `background-color: color-mix(in srgb, var(--theme-accent) 15%, transparent); color: var(--theme-titulo); font-weight: 500; border-left: 2px solid var(--theme-accent);`
278 - : 'text-slate-600 hover:bg-white hover:text-slate-900'}" 395 + : `color: var(--theme-texto);`}"
279 onclick={() => selectTipo(tipo)} 396 onclick={() => selectTipo(tipo)}
280 > 397 >
281 - <span class="font-mono text-xs text-slate-400 block">{tipo.rubro}</span> 398 + <span class="font-mono text-xs block" style="color: var(--theme-texto);">{tipo.rubro}</span>
282 {getDescripcion(tipo)} 399 {getDescripcion(tipo)}
283 </button> 400 </button>
284 </li> 401 </li>
...@@ -290,17 +407,20 @@ ...@@ -290,17 +407,20 @@
290 </aside> 407 </aside>
291 408
292 <!-- Contenido principal --> 409 <!-- Contenido principal -->
293 - <main class="flex-1 min-w-0"> 410 + <main class="flex-1 min-w-0 lg:border-l xl:border-r" style="border-color: var(--theme-borde);">
294 - <div class="px-8 py-6"> 411 + <div class="px-4 sm:px-6 lg:px-10 py-6 lg:py-8">
295 {#if selectedTipo} 412 {#if selectedTipo}
413 + {#key selectedTipo.rubro}
414 + <div in:fade={{ duration: 200, delay: 50 }}>
296 <!-- Título del tipo --> 415 <!-- Título del tipo -->
297 - <div class="mb-8 pb-6 border-b"> 416 + <div class="mb-8 lg:mb-10 pb-6 lg:pb-8 border-b" style="border-color: var(--theme-borde);">
298 - <p class="text-sm font-mono text-slate-400 mb-1">{selectedTipo.rubro}</p> 417 + <p class="text-sm font-mono mb-1" style="color: var(--theme-texto);">{selectedTipo.rubro}</p>
299 - <h2 class="text-xl font-medium text-slate-900 mb-2 flex items-center gap-3"> 418 + <h2 class="text-lg sm:text-xl font-medium mb-2 flex items-center gap-2 sm:gap-3 flex-wrap" style="color: var(--theme-titulo);">
300 {getDescripcion(selectedTipo)} 419 {getDescripcion(selectedTipo)}
301 <a 420 <a
302 href="/rubro/{selectedTipo.rubro}" 421 href="/rubro/{selectedTipo.rubro}"
303 - class="text-slate-300 hover:text-blue-500 transition-colors" 422 + class="transition-colors hover:text-[var(--theme-accent)]"
423 + style="color: var(--theme-texto);"
304 title="Ver detalle" 424 title="Ver detalle"
305 > 425 >
306 <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> 426 <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
...@@ -311,7 +431,7 @@ ...@@ -311,7 +431,7 @@
311 {#if selectedTipo.descripciones} 431 {#if selectedTipo.descripciones}
312 {@const tipoDescs = parseDescripciones(selectedTipo.descripciones)} 432 {@const tipoDescs = parseDescripciones(selectedTipo.descripciones)}
313 {#if tipoDescs.length > 0} 433 {#if tipoDescs.length > 0}
314 - <p class="text-slate-600 leading-relaxed">{tipoDescs[0].descripcion}</p> 434 + <p class="leading-relaxed" style="color: var(--theme-texto);">{tipoDescs[0].descripcion}</p>
315 {#if selectedTipo.n_variaciones > 1} 435 {#if selectedTipo.n_variaciones > 1}
316 <button 436 <button
317 class="text-sm text-orange-500 hover:text-orange-700 mt-2 transition-colors underline decoration-dotted decoration-orange-300 hover:decoration-orange-500 cursor-pointer" 437 class="text-sm text-orange-500 hover:text-orange-700 mt-2 transition-colors underline decoration-dotted decoration-orange-300 hover:decoration-orange-500 cursor-pointer"
...@@ -325,17 +445,17 @@ ...@@ -325,17 +445,17 @@
325 </div> 445 </div>
326 446
327 <!-- Clases --> 447 <!-- Clases -->
328 - <div class="space-y-8"> 448 + <div class="space-y-12">
329 {#each tipoContent.clases as clase} 449 {#each tipoContent.clases as clase}
330 {@const claseDescs = parseDescripciones(clase.descripciones)} 450 {@const claseDescs = parseDescripciones(clase.descripciones)}
331 <section 451 <section
332 id="cl-{clase.rubro}" 452 id="cl-{clase.rubro}"
333 - class="scroll-mt-4 {highlightedItem === clase.rubro ? 'ring-2 ring-blue-200 rounded-lg' : ''}" 453 + class="scroll-mt-4 {highlightedItem === clase.rubro ? 'highlighted' : ''}"
334 > 454 >
335 - <div class="flex items-start gap-4 mb-3"> 455 + <div class="flex items-start gap-2 sm:gap-4 mb-3">
336 - <span class="font-mono text-sm text-slate-400 pt-1">{clase.rubro}</span> 456 + <span class="font-mono text-xs sm:text-sm pt-1" style="color: var(--theme-texto);">{clase.rubro}</span>
337 <div class="flex-1"> 457 <div class="flex-1">
338 - <h3 class="text-lg font-medium text-slate-900 flex items-center gap-2"> 458 + <h3 class="text-base sm:text-lg font-medium flex items-center gap-2 flex-wrap" style="color: var(--theme-titulo);">
339 <span class="text-left">{getDescripcion(clase)}</span> 459 <span class="text-left">{getDescripcion(clase)}</span>
340 {#if clase.n_variaciones > 1} 460 {#if clase.n_variaciones > 1}
341 <button 461 <button
...@@ -348,7 +468,8 @@ ...@@ -348,7 +468,8 @@
348 {/if} 468 {/if}
349 <a 469 <a
350 href="/rubro/{clase.rubro}" 470 href="/rubro/{clase.rubro}"
351 - class="text-slate-300 hover:text-blue-500 transition-colors" 471 + class="transition-colors hover:text-[var(--theme-accent)]"
472 + style="color: var(--theme-texto);"
352 title="Ver página de detalle" 473 title="Ver página de detalle"
353 > 474 >
354 <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> 475 <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
...@@ -357,24 +478,24 @@ ...@@ -357,24 +478,24 @@
357 </a> 478 </a>
358 </h3> 479 </h3>
359 {#if claseDescs.length > 0} 480 {#if claseDescs.length > 0}
360 - <p class="text-sm text-slate-600 mt-1 leading-relaxed">{claseDescs[0].descripcion}</p> 481 + <p class="text-sm mt-1 leading-relaxed" style="color: var(--theme-texto);">{claseDescs[0].descripcion}</p>
361 {/if} 482 {/if}
362 </div> 483 </div>
363 </div> 484 </div>
364 485
365 <!-- Cuentas --> 486 <!-- Cuentas -->
366 {#if clase.cuentas?.length > 0} 487 {#if clase.cuentas?.length > 0}
367 - <div class="ml-16 space-y-4 border-l-2 border-slate-100 pl-6"> 488 + <div class="ml-4 sm:ml-8 lg:ml-16 space-y-5 border-l pl-4 sm:pl-6 lg:pl-8" style="border-color: var(--theme-borde);">
368 {#each clase.cuentas as cuenta} 489 {#each clase.cuentas as cuenta}
369 {@const cuentaDescs = parseDescripciones(cuenta.descripciones)} 490 {@const cuentaDescs = parseDescripciones(cuenta.descripciones)}
370 <div 491 <div
371 id="cu-{cuenta.rubro}" 492 id="cu-{cuenta.rubro}"
372 - class="scroll-mt-4 {highlightedItem === cuenta.rubro ? 'ring-2 ring-blue-200 rounded-lg p-2 -ml-2' : ''}" 493 + class="scroll-mt-4 {highlightedItem === cuenta.rubro ? 'highlighted-md' : ''}"
373 > 494 >
374 - <div class="flex items-start gap-3"> 495 + <div class="flex items-start gap-2 sm:gap-3">
375 - <span class="font-mono text-xs text-slate-400 pt-0.5">{cuenta.rubro}</span> 496 + <span class="font-mono text-xs pt-0.5" style="color: var(--theme-texto);">{cuenta.rubro}</span>
376 <div class="flex-1"> 497 <div class="flex-1">
377 - <h4 class="text-sm font-medium text-slate-800 flex items-center gap-2"> 498 + <h4 class="text-sm font-medium flex items-center gap-2 flex-wrap" style="color: var(--theme-titulo);">
378 <span class="text-left">{getDescripcion(cuenta)}</span> 499 <span class="text-left">{getDescripcion(cuenta)}</span>
379 {#if cuenta.n_variaciones > 1} 500 {#if cuenta.n_variaciones > 1}
380 <button 501 <button
...@@ -387,7 +508,8 @@ ...@@ -387,7 +508,8 @@
387 {/if} 508 {/if}
388 <a 509 <a
389 href="/rubro/{cuenta.rubro}" 510 href="/rubro/{cuenta.rubro}"
390 - class="text-slate-300 hover:text-blue-500 transition-colors" 511 + class="transition-colors hover:text-[var(--theme-accent)]"
512 + style="color: var(--theme-texto);"
391 title="Ver página de detalle" 513 title="Ver página de detalle"
392 > 514 >
393 <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> 515 <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
...@@ -396,25 +518,25 @@ ...@@ -396,25 +518,25 @@
396 </a> 518 </a>
397 </h4> 519 </h4>
398 {#if cuentaDescs.length > 0} 520 {#if cuentaDescs.length > 0}
399 - <p class="text-xs text-slate-500 mt-1 leading-relaxed">{cuentaDescs[0].descripcion}</p> 521 + <p class="text-xs mt-1 leading-relaxed" style="color: var(--theme-texto);">{cuentaDescs[0].descripcion}</p>
400 {/if} 522 {/if}
401 </div> 523 </div>
402 </div> 524 </div>
403 525
404 <!-- Subcuentas --> 526 <!-- Subcuentas -->
405 {#if cuenta.subcuentas?.length > 0} 527 {#if cuenta.subcuentas?.length > 0}
406 - <div class="ml-12 mt-3 space-y-2 border-l border-slate-100 pl-4"> 528 + <div class="ml-8 sm:ml-12 mt-3 space-y-2 border-l pl-4" style="border-color: var(--theme-borde);">
407 {#each cuenta.subcuentas as subcuenta} 529 {#each cuenta.subcuentas as subcuenta}
408 {@const subcuentaDescs = parseDescripciones(subcuenta.descripciones)} 530 {@const subcuentaDescs = parseDescripciones(subcuenta.descripciones)}
409 <div 531 <div
410 id="sc-{subcuenta.rubro}" 532 id="sc-{subcuenta.rubro}"
411 - class="scroll-mt-4 {highlightedItem === subcuenta.rubro ? 'ring-2 ring-blue-200 rounded p-1 -ml-1' : ''}" 533 + class="scroll-mt-4 {highlightedItem === subcuenta.rubro ? 'highlighted-sm' : ''}"
412 > 534 >
413 <div class="flex items-start gap-2"> 535 <div class="flex items-start gap-2">
414 - <span class="font-mono text-xs text-slate-300">{subcuenta.rubro}</span> 536 + <span class="font-mono text-xs" style="color: var(--theme-texto); opacity: 0.7;">{subcuenta.rubro}</span>
415 <div class="flex-1"> 537 <div class="flex-1">
416 - <span class="flex items-center gap-2"> 538 + <span class="flex items-center gap-2 flex-wrap">
417 - <span class="text-xs text-slate-700">{getDescripcion(subcuenta)}</span> 539 + <span class="text-xs" style="color: var(--theme-titulo);">{getDescripcion(subcuenta)}</span>
418 {#if subcuenta.n_variaciones > 1} 540 {#if subcuenta.n_variaciones > 1}
419 <button 541 <button
420 class="text-xs text-orange-500 hover:text-orange-700 transition-colors underline decoration-dotted decoration-orange-300 hover:decoration-orange-500 cursor-pointer" 542 class="text-xs text-orange-500 hover:text-orange-700 transition-colors underline decoration-dotted decoration-orange-300 hover:decoration-orange-500 cursor-pointer"
...@@ -426,7 +548,8 @@ ...@@ -426,7 +548,8 @@
426 {/if} 548 {/if}
427 <a 549 <a
428 href="/rubro/{subcuenta.rubro}" 550 href="/rubro/{subcuenta.rubro}"
429 - class="text-slate-300 hover:text-blue-500 transition-colors" 551 + class="transition-colors hover:text-[var(--theme-accent)]"
552 + style="color: var(--theme-texto);"
430 title="Ver página de detalle" 553 title="Ver página de detalle"
431 > 554 >
432 <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"> 555 <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
...@@ -435,7 +558,7 @@ ...@@ -435,7 +558,7 @@
435 </a> 558 </a>
436 </span> 559 </span>
437 {#if subcuentaDescs.length > 0} 560 {#if subcuentaDescs.length > 0}
438 - <p class="text-xs text-slate-400 mt-0.5">{subcuentaDescs[0].descripcion}</p> 561 + <p class="text-xs mt-0.5" style="color: var(--theme-texto); opacity: 0.8;">{subcuentaDescs[0].descripcion}</p>
439 {/if} 562 {/if}
440 </div> 563 </div>
441 </div> 564 </div>
...@@ -450,38 +573,42 @@ ...@@ -450,38 +573,42 @@
450 </section> 573 </section>
451 {/each} 574 {/each}
452 </div> 575 </div>
576 + </div>
577 + {/key}
453 {/if} 578 {/if}
454 </div> 579 </div>
455 </main> 580 </main>
456 581
457 <!-- Sidebar derecha: En esta página --> 582 <!-- Sidebar derecha: En esta página -->
458 <aside class="w-56 flex-shrink-0 hidden xl:block"> 583 <aside class="w-56 flex-shrink-0 hidden xl:block">
459 - <div class="sticky top-0 h-screen overflow-y-auto p-4 border-l"> 584 + <div class="sticky top-0 h-screen overflow-y-auto p-4 border-l" style="border-color: var(--theme-borde);">
460 - <p class="text-xs text-slate-500 uppercase tracking-wide mb-3">En esta página</p> 585 + <p class="text-xs uppercase tracking-wide mb-3" style="font-family: 'DM Mono', monospace; color: var(--theme-texto);">En esta página</p>
461 {#if selectedTipo && !isSearching} 586 {#if selectedTipo && !isSearching}
462 <nav class="space-y-2"> 587 <nav class="space-y-2">
463 {#each tipoContent.clases as clase} 588 {#each tipoContent.clases as clase}
464 <div> 589 <div>
465 <a 590 <a
466 href="#cl-{clase.rubro}" 591 href="#cl-{clase.rubro}"
467 - class="block text-sm text-slate-600 hover:text-blue-600 truncate" 592 + class="block text-sm truncate transition-colors hover:text-[var(--theme-accent)]"
593 + style="color: var(--theme-texto);"
468 title="{getDescripcion(clase)}" 594 title="{getDescripcion(clase)}"
469 > 595 >
470 {getDescripcion(clase)} 596 {getDescripcion(clase)}
471 </a> 597 </a>
472 {#if clase.cuentas?.length > 0} 598 {#if clase.cuentas?.length > 0}
473 - <div class="ml-3 mt-1 space-y-1 border-l border-slate-100 pl-2"> 599 + <div class="ml-3 mt-1 space-y-1 border-l pl-2" style="border-color: var(--theme-borde);">
474 {#each clase.cuentas.slice(0, 5) as cuenta} 600 {#each clase.cuentas.slice(0, 5) as cuenta}
475 <a 601 <a
476 href="#cu-{cuenta.rubro}" 602 href="#cu-{cuenta.rubro}"
477 - class="block text-xs text-slate-400 hover:text-blue-600 truncate" 603 + class="block text-xs truncate transition-colors hover:text-[var(--theme-accent)]"
604 + style="color: var(--theme-texto); opacity: 0.7;"
478 title="{getDescripcion(cuenta)}" 605 title="{getDescripcion(cuenta)}"
479 > 606 >
480 {getDescripcion(cuenta)} 607 {getDescripcion(cuenta)}
481 </a> 608 </a>
482 {/each} 609 {/each}
483 {#if clase.cuentas.length > 5} 610 {#if clase.cuentas.length > 5}
484 - <span class="text-xs text-slate-300">+{clase.cuentas.length - 5} más</span> 611 + <span class="text-xs" style="color: var(--theme-texto); opacity: 0.5;">+{clase.cuentas.length - 5} más</span>
485 {/if} 612 {/if}
486 </div> 613 </div>
487 {/if} 614 {/if}
...@@ -497,30 +624,37 @@ ...@@ -497,30 +624,37 @@
497 624
498 <!-- Modal de detalle --> 625 <!-- Modal de detalle -->
499 {#if selectedItem} 626 {#if selectedItem}
500 - <div class="fixed inset-0 bg-black/40 flex items-center justify-center p-4 z-50" onclick={closeDetail}> 627 + <div
628 + transition:fade={{ duration: 150 }}
629 + class="fixed inset-0 bg-black/40 flex items-center justify-center p-4 z-50"
630 + onclick={closeDetail}
631 + >
501 <div 632 <div
502 - class="bg-white rounded-xl shadow-2xl max-w-2xl w-full max-h-[85vh] overflow-hidden flex flex-col" 633 + transition:scale={{ duration: 200, start: 0.95, easing: cubicOut }}
634 + class="rounded-xl shadow-2xl max-w-2xl w-full max-h-[85vh] overflow-hidden flex flex-col"
635 + style="background-color: var(--theme-surface);"
503 onclick={(e) => e.stopPropagation()} 636 onclick={(e) => e.stopPropagation()}
504 > 637 >
505 - <div class="px-6 py-4 border-b bg-slate-50 flex justify-between items-start"> 638 + <div class="px-6 py-4 border-b flex justify-between items-start" style="background-color: var(--theme-fill); border-color: var(--theme-borde);">
506 <div> 639 <div>
507 - <p class="text-xs text-slate-500 uppercase tracking-wide">{getNivelLabel(selectedItem.nivel)}</p> 640 + <p class="text-xs uppercase tracking-wide font-medium" style="color: var(--theme-texto);">{getNivelLabel(selectedItem.nivel)}</p>
508 - <h3 class="text-lg font-medium text-slate-900 mt-1"> 641 + <h3 class="text-lg font-medium mt-1" style="color: var(--theme-titulo);">
509 - <span class="font-mono text-slate-400">{selectedItem.rubro}</span> 642 + <span class="font-mono" style="color: var(--theme-texto);">{selectedItem.rubro}</span>
510 - <span class="mx-2">·</span> 643 + <span class="mx-2" style="color: var(--theme-texto); opacity: 0.5;">·</span>
511 {getDescripcion(selectedItem)} 644 {getDescripcion(selectedItem)}
512 </h3> 645 </h3>
513 </div> 646 </div>
514 <button 647 <button
515 onclick={closeDetail} 648 onclick={closeDetail}
516 - class="text-slate-400 hover:text-slate-600 text-2xl leading-none" 649 + class="text-2xl leading-none"
650 + style="color: var(--theme-texto);"
517 > 651 >
518 &times; 652 &times;
519 </button> 653 </button>
520 </div> 654 </div>
521 655
522 <div class="p-6 overflow-y-auto flex-1"> 656 <div class="p-6 overflow-y-auto flex-1">
523 - <h4 class="text-sm font-medium text-slate-700 mb-4"> 657 + <h4 class="text-sm font-medium mb-4" style="color: var(--theme-titulo);">
524 {#if selectedItem.n_variaciones > 1} 658 {#if selectedItem.n_variaciones > 1}
525 Descripciones ({selectedItem.n_variaciones} variaciones) 659 Descripciones ({selectedItem.n_variaciones} variaciones)
526 {:else} 660 {:else}
...@@ -530,15 +664,15 @@ ...@@ -530,15 +664,15 @@
530 664
531 <div class="space-y-4"> 665 <div class="space-y-4">
532 {#each parseDescripciones(selectedItem.descripciones) as desc, i} 666 {#each parseDescripciones(selectedItem.descripciones) as desc, i}
533 - <div class="border-l-2 {i === 0 ? 'border-blue-500 bg-blue-50/50' : 'border-slate-200'} pl-4 py-2 rounded-r"> 667 + <div class="border-l-2 pl-4 py-3 rounded-r" style="{i === 0 ? `border-color: var(--theme-accent); background-color: color-mix(in srgb, var(--theme-accent) 10%, transparent);` : `border-color: var(--theme-borde);`}">
534 - <p class="text-xs text-slate-500 mb-2"> 668 + <p class="text-sm mb-2" style="color: var(--theme-texto);">
535 {#if i === 0 && selectedItem.n_variaciones > 1} 669 {#if i === 0 && selectedItem.n_variaciones > 1}
536 - <span class="text-blue-600 font-medium">Vigente</span> 670 + <span class="font-medium" style="color: var(--theme-accent);">Vigente</span>
537 - <span class="mx-1">·</span> 671 + <span class="mx-1" style="opacity: 0.5;">·</span>
538 {/if} 672 {/if}
539 - {desc.rangos} 673 + <span class="font-mono">{desc.rangos}</span>
540 </p> 674 </p>
541 - <p class="text-sm text-slate-700 leading-relaxed">{desc.descripcion}</p> 675 + <p class="text-base leading-relaxed" style="color: var(--theme-titulo);">{desc.descripcion}</p>
542 </div> 676 </div>
543 {/each} 677 {/each}
544 </div> 678 </div>
...@@ -546,3 +680,57 @@ ...@@ -546,3 +680,57 @@
546 </div> 680 </div>
547 </div> 681 </div>
548 {/if} 682 {/if}
683 +
684 +<style>
685 + /* Sidebar izquierdo: con fondo en móvil, transparente en desktop */
686 + .sidebar-left {
687 + background-color: var(--theme-surface);
688 + }
689 + @media (min-width: 1024px) {
690 + .sidebar-left {
691 + background-color: transparent;
692 + }
693 + }
694 +
695 + /* Highlight pulse animation para resultados de búsqueda */
696 + @keyframes highlight-pulse {
697 + 0%, 100% {
698 + box-shadow: 0 0 0 2px color-mix(in srgb, var(--theme-accent) 30%, transparent);
699 + }
700 + 50% {
701 + box-shadow: 0 0 0 4px color-mix(in srgb, var(--theme-accent) 50%, transparent);
702 + }
703 + }
704 +
705 + .highlighted {
706 + animation: highlight-pulse 0.8s ease-in-out 2;
707 + border-radius: 0.5rem;
708 + }
709 +
710 + .highlighted-sm {
711 + animation: highlight-pulse 0.8s ease-in-out 2;
712 + border-radius: 0.25rem;
713 + padding: 0.25rem;
714 + margin-left: -0.25rem;
715 + }
716 +
717 + .highlighted-md {
718 + animation: highlight-pulse 0.8s ease-in-out 2;
719 + border-radius: 0.5rem;
720 + padding: 0.5rem;
721 + margin-left: -0.5rem;
722 + }
723 +
724 + /* Transiciones estandarizadas */
725 + :global(.transition-fast) {
726 + transition: all 150ms cubic-bezier(0.4, 0, 0.2, 1);
727 + }
728 +
729 + :global(.transition-normal) {
730 + transition: all 200ms cubic-bezier(0.4, 0, 0.2, 1);
731 + }
732 +
733 + :global(.transition-slow) {
734 + transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1);
735 + }
736 +</style>
......
1 +<script>
2 + import { onMount } from 'svelte';
3 + import { tweened } from 'svelte/motion';
4 + import { cubicOut } from 'svelte/easing';
5 +
6 + let mounted = false;
7 + let hoveredYear = null;
8 + let hoveredYearComparar = null;
9 + let showInfo = false;
10 + let vista = 'agregado'; // 'agregado' | 'comparar'
11 + let isDark = true;
12 + let entidadA = 0;
13 + let entidadB = 1;
14 +
15 + onMount(() => {
16 + setTimeout(() => { mounted = true; }, 50);
17 +
18 + // Leer estado del tema
19 + isDark = document.documentElement.classList.contains('dark');
20 +
21 + // Observar cambios de tema
22 + const observer = new MutationObserver(() => {
23 + isDark = document.documentElement.classList.contains('dark');
24 + });
25 + observer.observe(document.documentElement, {
26 + attributes: true,
27 + attributeFilter: ['class']
28 + });
29 +
30 + return () => observer.disconnect();
31 + });
32 +
33 +
34 + // Dummy data
35 + const objeto = {
36 + codigo: '10000',
37 + nombre: 'Servicios Personales',
38 + nivel: 'Grupo',
39 + años: '2005-2024',
40 + descripcion: 'Gastos por concepto de servicios prestados por el personal permanente y no permanente del sector público.',
41 + variaciones: [
42 + { rango: '2012-2024', texto: 'Gastos por concepto de servicios prestados por el personal permanente y no permanente.' },
43 + { rango: '2005-2011', texto: 'Retribuciones y complementos al personal permanente y eventual.' }
44 + ]
45 + };
46 +
47 + // Dummy spending data (in millions Bs, population in millions)
48 + const gastoAnual = [
49 + { año: 2005, monto: 8200, poblacion: 9.4 },
50 + { año: 2006, monto: 8900, poblacion: 9.5 },
51 + { año: 2007, monto: 10200, poblacion: 9.6 },
52 + { año: 2008, monto: 12400, poblacion: 9.8 },
53 + { año: 2009, monto: 13100, poblacion: 9.9 },
54 + { año: 2010, monto: 14800, poblacion: 10.0 },
55 + { año: 2011, monto: 17200, poblacion: 10.2 },
56 + { año: 2012, monto: 19500, poblacion: 10.4 },
57 + { año: 2013, monto: 22800, poblacion: 10.5 },
58 + { año: 2014, monto: 28400, poblacion: 10.7 },
59 + { año: 2015, monto: 31200, poblacion: 10.9 },
60 + { año: 2016, monto: 33100, poblacion: 11.0 },
61 + { año: 2017, monto: 35600, poblacion: 11.2 },
62 + { año: 2018, monto: 38200, poblacion: 11.4 },
63 + { año: 2019, monto: 41500, poblacion: 11.5 },
64 + { año: 2020, monto: 39800, poblacion: 11.7 },
65 + { año: 2021, monto: 42100, poblacion: 11.8 },
66 + { año: 2022, monto: 45600, poblacion: 12.0 },
67 + { año: 2023, monto: 48900, poblacion: 12.1 },
68 + { año: 2024, monto: 52300, poblacion: 12.3 },
69 + ];
70 +
71 + // Calculate per capita for each year
72 + const gastoPerCapita = gastoAnual.map(g => ({
73 + ...g,
74 + perCapita: Math.round(g.monto / g.poblacion)
75 + }));
76 +
77 + // Top entidades por año (dummy data)
78 + const topEntidadesPorAño = {
79 + 2020: [
80 + { nombre: 'Ministerio de Educación', monto: 12500, porcentaje: 31.4 },
81 + { nombre: 'Ministerio de Salud', monto: 7800, porcentaje: 19.6 },
82 + { nombre: 'Policía Boliviana', monto: 4200, porcentaje: 10.6 },
83 + ],
84 + 2021: [
85 + { nombre: 'Ministerio de Educación', monto: 13200, porcentaje: 31.4 },
86 + { nombre: 'Ministerio de Salud', monto: 8100, porcentaje: 19.2 },
87 + { nombre: 'Policía Boliviana', monto: 4600, porcentaje: 10.9 },
88 + ],
89 + 2022: [
90 + { nombre: 'Ministerio de Educación', monto: 14800, porcentaje: 32.5 },
91 + { nombre: 'Ministerio de Salud', monto: 8400, porcentaje: 18.4 },
92 + { nombre: 'Policía Boliviana', monto: 5100, porcentaje: 11.2 },
93 + ],
94 + 2023: [
95 + { nombre: 'Ministerio de Educación', monto: 16200, porcentaje: 33.1 },
96 + { nombre: 'Ministerio de Salud', monto: 8700, porcentaje: 17.8 },
97 + { nombre: 'Policía Boliviana', monto: 5700, porcentaje: 11.7 },
98 + ],
99 + 2024: [
100 + { nombre: 'Ministerio de Educación', monto: 18200, porcentaje: 34.8 },
101 + { nombre: 'Ministerio de Salud', monto: 8900, porcentaje: 17.0 },
102 + { nombre: 'Policía Boliviana', monto: 6200, porcentaje: 11.9 },
103 + ],
104 + };
105 +
106 + // Total histórico (para cuando no hay hover)
107 + const topEntidadesTotal = [
108 + { nombre: 'Ministerio de Educación', monto: 74900, porcentaje: 32.6 },
109 + { nombre: 'Ministerio de Salud', monto: 41900, porcentaje: 18.4 },
110 + { nombre: 'Policía Boliviana', monto: 25800, porcentaje: 11.3 },
111 + ];
112 +
113 + // Datos para comparación (dummy)
114 + const entidadesComparables = [
115 + {
116 + id: 0,
117 + nombre: 'Ministerio de Educación',
118 + color: '#E8C547',
119 + datos: [
120 + { año: 2020, monto: 12500, perCapita: 1068 },
121 + { año: 2021, monto: 13200, perCapita: 1119 },
122 + { año: 2022, monto: 14800, perCapita: 1233 },
123 + { año: 2023, monto: 16200, perCapita: 1339 },
124 + { año: 2024, monto: 18200, perCapita: 1480 },
125 + ],
126 + total: 74900,
127 + promedioPerCapita: 1248,
128 + porcentajeObjeto: 34.8,
129 + ranking: 1
130 + },
131 + {
132 + id: 1,
133 + nombre: 'Ministerio de Salud',
134 + color: '#4A8B6E',
135 + datos: [
136 + { año: 2020, monto: 6800, perCapita: 581 },
137 + { año: 2021, monto: 7200, perCapita: 610 },
138 + { año: 2022, monto: 7900, perCapita: 658 },
139 + { año: 2023, monto: 8400, perCapita: 694 },
140 + { año: 2024, monto: 8900, perCapita: 724 },
141 + ],
142 + total: 39200,
143 + promedioPerCapita: 653,
144 + porcentajeObjeto: 17.0,
145 + ranking: 2
146 + },
147 + {
148 + id: 2,
149 + nombre: 'Policía Boliviana',
150 + color: '#8B5CF6',
151 + datos: [
152 + { año: 2020, monto: 4200, perCapita: 359 },
153 + { año: 2021, monto: 4600, perCapita: 390 },
154 + { año: 2022, monto: 5100, perCapita: 425 },
155 + { año: 2023, monto: 5700, perCapita: 471 },
156 + { año: 2024, monto: 6200, perCapita: 504 },
157 + ],
158 + total: 25800,
159 + promedioPerCapita: 430,
160 + porcentajeObjeto: 11.9,
161 + ranking: 3
162 + },
163 + {
164 + id: 3,
165 + nombre: 'Gobierno Autónomo de Santa Cruz',
166 + color: '#F97316',
167 + datos: [
168 + { año: 2020, monto: 3100, perCapita: 265 },
169 + { año: 2021, monto: 3400, perCapita: 288 },
170 + { año: 2022, monto: 3800, perCapita: 317 },
171 + { año: 2023, monto: 4100, perCapita: 339 },
172 + { año: 2024, monto: 4500, perCapita: 366 },
173 + ],
174 + total: 18900,
175 + promedioPerCapita: 315,
176 + porcentajeObjeto: 8.6,
177 + ranking: 4
178 + }
179 + ];
180 +
181 + // Colores fijos por posición - teal y naranja
182 + $: colorA = '#2a9d8f'; // teal
183 + $: colorB = '#f4a261'; // naranja
184 +
185 + $: entA = entidadesComparables[entidadA];
186 + $: entB = entidadesComparables[entidadB];
187 + $: maxComparacion = Math.max(
188 + ...entA.datos.map(d => d.perCapita),
189 + ...entB.datos.map(d => d.perCapita)
190 + );
191 + $: años = entA.datos.map(d => d.año);
192 +
193 + // Datos de comparación según hover
194 + $: hoveredDataA = hoveredYearComparar
195 + ? entA.datos.find(d => d.año === hoveredYearComparar)
196 + : null;
197 + $: hoveredDataB = hoveredYearComparar
198 + ? entB.datos.find(d => d.año === hoveredYearComparar)
199 + : null;
200 +
201 + $: displayMontoA = hoveredDataA ? hoveredDataA.monto : entA.total;
202 + $: displayMontoLabelA = hoveredDataA ? `gasto en ${hoveredYearComparar}` : 'gasto 2020-2024';
203 + $: displayPerCapitaA = hoveredDataA ? hoveredDataA.perCapita : entA.promedioPerCapita;
204 + $: displayPerCapitaLabelA = hoveredDataA ? `por persona en ${hoveredYearComparar}` : 'por persona (prom.)';
205 +
206 + $: displayMontoB = hoveredDataB ? hoveredDataB.monto : entB.total;
207 + $: displayMontoLabelB = hoveredDataB ? `gasto en ${hoveredYearComparar}` : 'gasto 2020-2024';
208 + $: displayPerCapitaB = hoveredDataB ? hoveredDataB.perCapita : entB.promedioPerCapita;
209 + $: displayPerCapitaLabelB = hoveredDataB ? `por persona en ${hoveredYearComparar}` : 'por persona (prom.)';
210 +
211 + $: displayPeriodoComparar = hoveredYearComparar ? hoveredYearComparar : '2020-2024';
212 +
213 + // Top entidades reactivo según hover
214 + $: displayTopEntidades = hoveredYear && topEntidadesPorAño[hoveredYear]
215 + ? topEntidadesPorAño[hoveredYear]
216 + : topEntidadesTotal;
217 + $: displayTopLabel = hoveredYear ? `en ${hoveredYear}` : '2005-2024';
218 +
219 + // Context data
220 + const totalEntidades = 620;
221 + const totalGrupos = 9; // Total grupos in objeto del gasto
222 +
223 + // Calculations
224 + const maxPerCapita = Math.max(...gastoPerCapita.map(g => g.perCapita));
225 + const minPerCapita = Math.min(...gastoPerCapita.map(g => g.perCapita));
226 + const totalHistorico = gastoAnual.reduce((sum, g) => sum + g.monto, 0);
227 + const totalPoblacionAcum = gastoAnual.reduce((sum, g) => sum + g.poblacion, 0);
228 + const promedioPerCapita = Math.round(totalHistorico / totalPoblacionAcum);
229 + const totalPorcentaje = 14.2;
230 + const ranking = 1;
231 +
232 + // Current view data (changes on hover)
233 + $: currentData = hoveredYear
234 + ? gastoPerCapita.find(g => g.año === hoveredYear)
235 + : null;
236 +
237 + $: displayMonto = currentData ? currentData.monto : totalHistorico;
238 + $: displayMontoLabel = currentData ? `gasto en ${currentData.año}` : 'gasto 2005-2024';
239 + $: displayPerCapita = currentData ? currentData.perCapita : promedioPerCapita;
240 + $: displayPerCapitaLabel = currentData ? `por persona en ${currentData.año}` : 'por persona (promedio)';
241 + $: displayPorcentaje = currentData
242 + ? (totalPorcentaje * (currentData.monto / (totalHistorico / gastoAnual.length))).toFixed(1)
243 + : totalPorcentaje;
244 + $: displayPorcentajeLabel = currentData ? `del gasto ${currentData.año}` : 'del gasto total';
245 + $: displayPeriodo = currentData ? currentData.año : '2005-2024';
246 + $: displayRanking = `${ranking} de ${totalGrupos}`;
247 + $: displayRankingLabel = currentData ? `ranking en ${currentData.año}` : 'ranking entre grupos';
248 +
249 + // Tweened values para transiciones suaves
250 + const tweenOptions = { duration: 300, easing: cubicOut };
251 +
252 + // Agregado
253 + const twMonto = tweened(totalHistorico, tweenOptions);
254 + const twPerCapita = tweened(promedioPerCapita, tweenOptions);
255 + const twPorcentaje = tweened(totalPorcentaje, tweenOptions);
256 +
257 + // Comparación
258 + const twMontoA = tweened(0, tweenOptions);
259 + const twMontoB = tweened(0, tweenOptions);
260 + const twPerCapitaA = tweened(0, tweenOptions);
261 + const twPerCapitaB = tweened(0, tweenOptions);
262 +
263 + // Top entidades
264 + const twTop1 = tweened(0, tweenOptions);
265 + const twTop2 = tweened(0, tweenOptions);
266 + const twTop3 = tweened(0, tweenOptions);
267 +
268 + // Actualizar tweened cuando cambian los valores
269 + $: twMonto.set(displayMonto);
270 + $: twPerCapita.set(displayPerCapita);
271 + $: twPorcentaje.set(parseFloat(displayPorcentaje));
272 +
273 + $: twMontoA.set(displayMontoA);
274 + $: twMontoB.set(displayMontoB);
275 + $: twPerCapitaA.set(displayPerCapitaA);
276 + $: twPerCapitaB.set(displayPerCapitaB);
277 +
278 + $: if (displayTopEntidades[0]) twTop1.set(displayTopEntidades[0].porcentaje);
279 + $: if (displayTopEntidades[1]) twTop2.set(displayTopEntidades[1].porcentaje);
280 + $: if (displayTopEntidades[2]) twTop3.set(displayTopEntidades[2].porcentaje);
281 +
282 + function formatMonto(m) {
283 + if (m >= 1000000) return (m / 1000000).toFixed(1) + 'B';
284 + if (m >= 1000) return Math.round(m / 1000) + 'K';
285 + return m.toLocaleString();
286 + }
287 +
288 + function formatMontoLargo(m) {
289 + if (m >= 1000) return (m / 1000).toFixed(1) + 'MM';
290 + return m.toLocaleString() + 'M';
291 + }
292 +</script>
293 +
294 +<svelte:head>
295 + <title>Test — Objeto Detail</title>
296 + <link rel="preconnect" href="https://fonts.googleapis.com" />
297 + <link href="https://fonts.googleapis.com/css2?family=DM+Serif+Display:ital@0;1&family=DM+Mono:wght@400;500&family=Instrument+Sans:wght@400;500;600&display=swap" rel="stylesheet" />
298 +</svelte:head>
299 +
300 +<div class="page" class:mounted class:tema-claro={!isDark}>
301 +
302 + <!-- Header compacto -->
303 + <header>
304 + <nav class="breadcrumb">
305 + <a href="/clasificadores/objeto-gasto">← Objeto del Gasto</a>
306 + </nav>
307 +
308 + <div class="title-row">
309 + <div class="title-main">
310 + <span class="codigo">{objeto.codigo}</span>
311 + <h1>
312 + {objeto.nombre}
313 + <button class="help-icon" on:click={() => showInfo = !showInfo} class:active={showInfo}>
314 + <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
315 + <circle cx="12" cy="12" r="10"/>
316 + <path d="M9.5 9a3 3 0 1 1 3.5 2.95c-.5.17-1 .62-1 1.3V15"/>
317 + <circle cx="12" cy="18" r="0.5" fill="currentColor"/>
318 + </svg>
319 + </button>
320 + </h1>
321 + </div>
322 + </div>
323 +
324 + <div class="meta">
325 + <span class="badge">{objeto.nivel}</span>
326 + <span class="years">{objeto.años}</span>
327 + </div>
328 + </header>
329 +
330 + <!-- Panel de info (colapsable) -->
331 + {#if showInfo}
332 + <div class="info-panel">
333 + <div class="info-content">
334 + <h4>Definición vigente</h4>
335 + <p>{objeto.descripcion}</p>
336 +
337 + {#if objeto.variaciones.length > 1}
338 + <h4>Variaciones históricas</h4>
339 + {#each objeto.variaciones as v}
340 + <div class="variacion">
341 + <span class="variacion-rango">{v.rango}</span>
342 + <p>{v.texto}</p>
343 + </div>
344 + {/each}
345 + {/if}
346 + </div>
347 + </div>
348 + {/if}
349 +
350 + <!-- Dashboard principal -->
351 + <main>
352 +
353 + <!-- Toggle de vista -->
354 + <div class="vista-toggle">
355 + <button
356 + class="toggle-btn"
357 + class:active={vista === 'agregado'}
358 + on:click={() => vista = 'agregado'}
359 + >
360 + Agregado
361 + </button>
362 + <button
363 + class="toggle-btn"
364 + class:active={vista === 'comparar'}
365 + on:click={() => vista = 'comparar'}
366 + >
367 + Comparar
368 + </button>
369 + </div>
370 +
371 + <!-- Selector de entidad (solo en vista agregado) -->
372 + {#if vista === 'agregado'}
373 + <div class="entity-selector">
374 + <span class="selector-label">Viendo</span>
375 + <button class="selector-btn">
376 + Todo el sector público
377 + <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
378 + <path d="M6 9l6 6 6-6"/>
379 + </svg>
380 + </button>
381 + </div>
382 + {/if}
383 +
384 + {#if vista === 'agregado'}
385 + <!-- VISTA AGREGADO -->
386 +
387 + <!-- KPIs arriba -->
388 + <div class="kpis">
389 + <div class="kpi">
390 + <span class="kpi-value">Bs {formatMontoLargo(Math.round($twMonto))}</span>
391 + <span class="kpi-label">{displayMontoLabel}</span>
392 + </div>
393 +
394 + <div class="kpi">
395 + <span class="kpi-value">Bs {Math.round($twPerCapita).toLocaleString()}</span>
396 + <span class="kpi-label">{displayPerCapitaLabel}</span>
397 + </div>
398 +
399 + <div class="kpi">
400 + <span class="kpi-value">{$twPorcentaje.toFixed(1)}%</span>
401 + <span class="kpi-label">{displayPorcentajeLabel}</span>
402 + </div>
403 +
404 + <div class="kpi">
405 + <span class="kpi-value">{displayRanking}</span>
406 + <span class="kpi-label">{displayRankingLabel}</span>
407 + </div>
408 + </div>
409 +
410 + <!-- Gráfico de barras (per cápita) - protagonista al medio -->
411 + <div class="chart-section chart-protagonista">
412 + <div class="chart-header">
413 + <span class="chart-title">Gasto por persona</span>
414 + <span class="chart-period">{displayPeriodo}</span>
415 + </div>
416 +
417 + <div class="chart-wrapper">
418 + <!-- Y-axis labels -->
419 + <div class="y-axis">
420 + <span class="y-label">Bs {formatMonto(maxPerCapita)}</span>
421 + <span class="y-label">Bs {formatMonto(Math.round(maxPerCapita / 2))}</span>
422 + <span class="y-label">Bs 0</span>
423 + </div>
424 +
425 + <div
426 + class="chart"
427 + on:mouseleave={() => hoveredYear = null}
428 + role="group"
429 + >
430 + {#each gastoPerCapita as g}
431 + <div
432 + class="bar-container"
433 + on:mouseenter={() => hoveredYear = g.año}
434 + role="button"
435 + tabindex="0"
436 + >
437 + <div
438 + class="bar"
439 + class:hovered={hoveredYear === g.año}
440 + style="height: {(g.perCapita / maxPerCapita) * 100}%"
441 + ></div>
442 + <span class="bar-label" class:visible={hoveredYear === g.año || g.año % 5 === 0}>
443 + {g.año.toString().slice(-2)}
444 + </span>
445 + </div>
446 + {/each}
447 + </div>
448 + </div>
449 + </div>
450 +
451 + <!-- Top entidades -->
452 + <div class="top-section">
453 + <div class="top-header">
454 + <h3>Mayores ejecutores</h3>
455 + <span class="top-context">{totalEntidades} entidades · {displayTopLabel}</span>
456 + </div>
457 +
458 + <div class="top-list">
459 + {#each displayTopEntidades as ent, i (ent.nombre)}
460 + <div class="top-item">
461 + <span class="top-rank">#{i + 1}</span>
462 + <div class="top-info">
463 + <span class="top-name">{ent.nombre}</span>
464 + <div class="top-bar-bg">
465 + <div class="top-bar" style="width: {((i === 0 ? $twTop1 : i === 1 ? $twTop2 : $twTop3) / $twTop1) * 100}%"></div>
466 + </div>
467 + </div>
468 + <span class="top-pct">{(i === 0 ? $twTop1 : i === 1 ? $twTop2 : $twTop3).toFixed(1)}%</span>
469 + </div>
470 + {/each}
471 + </div>
472 + </div>
473 +
474 + {:else}
475 + <!-- VISTA COMPARAR -->
476 +
477 + <!-- Selectores de entidades -->
478 + <div class="comparador-selectors">
479 + <div class="selector-group">
480 + <span class="selector-dot" style="background: {colorA}"></span>
481 + <select bind:value={entidadA}>
482 + {#each entidadesComparables as ent}
483 + <option value={ent.id} disabled={ent.id === entidadB}>{ent.nombre}</option>
484 + {/each}
485 + </select>
486 + </div>
487 + <span class="vs">vs</span>
488 + <div class="selector-group">
489 + <span class="selector-dot" style="background: {colorB}"></span>
490 + <select bind:value={entidadB}>
491 + {#each entidadesComparables as ent}
492 + <option value={ent.id} disabled={ent.id === entidadA}>{ent.nombre}</option>
493 + {/each}
494 + </select>
495 + </div>
496 + </div>
497 +
498 + <!-- Gráfico comparativo -->
499 + <div class="chart-section">
500 + <div class="chart-header">
501 + <span class="chart-title">Gasto por persona</span>
502 + <span class="chart-period">{displayPeriodoComparar}</span>
503 + </div>
504 +
505 + <div class="chart-wrapper">
506 + <div class="y-axis">
507 + <span class="y-label">Bs {formatMonto(maxComparacion)}</span>
508 + <span class="y-label">Bs {formatMonto(Math.round(maxComparacion / 2))}</span>
509 + <span class="y-label">Bs 0</span>
510 + </div>
511 +
512 + <div
513 + class="chart chart-comparar"
514 + on:mouseleave={() => hoveredYearComparar = null}
515 + role="group"
516 + >
517 + {#each años as año, i}
518 + <div
519 + class="bar-group"
520 + class:active={hoveredYearComparar === año}
521 + on:mouseenter={() => hoveredYearComparar = año}
522 + role="button"
523 + tabindex="0"
524 + >
525 + <div class="bars-pair">
526 + <div
527 + class="bar bar-a"
528 + style="height: {(entA.datos[i].perCapita / maxComparacion) * 100}%; --bar-color: {colorA}"
529 + ></div>
530 + <div
531 + class="bar bar-b"
532 + style="height: {(entB.datos[i].perCapita / maxComparacion) * 100}%; --bar-color: {colorB}"
533 + ></div>
534 + </div>
535 + <span class="bar-label visible" class:highlight={hoveredYearComparar === año}>{año}</span>
536 + </div>
537 + {/each}
538 + </div>
539 + </div>
540 +
541 + <div class="chart-legend">
542 + <span class="legend-item"><span class="legend-dot" style="background: {colorA}"></span>{entA.nombre}</span>
543 + <span class="legend-item"><span class="legend-dot" style="background: {colorB}"></span>{entB.nombre}</span>
544 + </div>
545 + </div>
546 +
547 + <!-- KPIs comparativos - tabla -->
548 + <div class="kpis-tabla">
549 + <!-- Header -->
550 + <div class="tabla-header">
551 + <span class="tabla-label"></span>
552 + <span class="tabla-val" style="color: {colorA}">{entA.nombre}</span>
553 + <span class="tabla-val" style="color: {colorB}">{entB.nombre}</span>
554 + </div>
555 + <!-- Gasto -->
556 + <div class="tabla-row">
557 + <span class="tabla-label">{hoveredYearComparar ? `Gasto ${hoveredYearComparar}` : 'Gasto 2020-2024'}</span>
558 + <span class="tabla-val">Bs {formatMontoLargo(Math.round($twMontoA))}</span>
559 + <span class="tabla-val">Bs {formatMontoLargo(Math.round($twMontoB))}</span>
560 + </div>
561 + <!-- Per cápita -->
562 + <div class="tabla-row">
563 + <span class="tabla-label">{hoveredYearComparar ? `Por persona ${hoveredYearComparar}` : 'Por persona (prom.)'}</span>
564 + <span class="tabla-val">Bs {Math.round($twPerCapitaA).toLocaleString()}</span>
565 + <span class="tabla-val">Bs {Math.round($twPerCapitaB).toLocaleString()}</span>
566 + </div>
567 + <!-- Porcentaje -->
568 + <div class="tabla-row">
569 + <span class="tabla-label">Peso sobre total</span>
570 + <span class="tabla-val">{entA.porcentajeObjeto}%</span>
571 + <span class="tabla-val">{entB.porcentajeObjeto}%</span>
572 + </div>
573 + <!-- Ranking -->
574 + <div class="tabla-row">
575 + <span class="tabla-label">Ranking</span>
576 + <span class="tabla-val">#{entA.ranking}</span>
577 + <span class="tabla-val">#{entB.ranking}</span>
578 + </div>
579 + </div>
580 +
581 + {/if}
582 +
583 + </main>
584 +</div>
585 +
586 +
587 +
588 +<style>
589 + .page {
590 + min-height: 100vh;
591 + background: #0A0A0A;
592 + color: #F5F0E8;
593 + font-family: 'Instrument Sans', -apple-system, sans-serif;
594 + opacity: 0;
595 + transition: opacity 0.4s ease;
596 + }
597 + .mounted { opacity: 1; }
598 +
599 + /* Header */
600 + header {
601 + padding: 1rem 2rem;
602 + border-bottom: 1px solid #1E1E1C;
603 + }
604 +
605 + .breadcrumb a {
606 + font-family: 'DM Mono', monospace;
607 + font-size: 0.75rem;
608 + color: #5A5650;
609 + text-decoration: none;
610 + transition: color 0.2s;
611 + }
612 + .breadcrumb a:hover { color: #F5F0E8; }
613 +
614 + .title-row {
615 + display: flex;
616 + align-items: flex-start;
617 + justify-content: space-between;
618 + margin-top: 0.5rem;
619 + gap: 1rem;
620 + }
621 +
622 + .title-main {
623 + display: flex;
624 + align-items: baseline;
625 + gap: 1rem;
626 + flex-wrap: wrap;
627 + }
628 +
629 + .codigo {
630 + font-family: 'DM Mono', monospace;
631 + font-size: 1.5rem;
632 + color: #5A5650;
633 + font-weight: 400;
634 + }
635 +
636 + h1 {
637 + font-family: 'DM Serif Display', Georgia, serif;
638 + font-size: 1.5rem;
639 + font-weight: 400;
640 + color: #F5F0E8;
641 + margin: 0;
642 + display: inline-flex;
643 + align-items: center;
644 + gap: 0.5rem;
645 + }
646 +
647 + .help-icon {
648 + background: transparent;
649 + border: none;
650 + padding: 0;
651 + color: #5A5650;
652 + cursor: pointer;
653 + transition: all 0.2s;
654 + display: inline-flex;
655 + align-items: center;
656 + justify-content: center;
657 + vertical-align: middle;
658 + }
659 + .help-icon:hover, .help-icon.active {
660 + color: #E8C547;
661 + }
662 +
663 + .meta {
664 + display: flex;
665 + align-items: center;
666 + gap: 0.75rem;
667 + margin-top: 0.5rem;
668 + }
669 +
670 + .badge {
671 + font-family: 'DM Mono', monospace;
672 + font-size: 0.625rem;
673 + letter-spacing: 0.1em;
674 + text-transform: uppercase;
675 + padding: 0.25rem 0.5rem;
676 + background: #1A1A18;
677 + border: 1px solid #2E2E2C;
678 + border-radius: 4px;
679 + color: #8A8578;
680 + }
681 +
682 + .years {
683 + font-family: 'DM Mono', monospace;
684 + font-size: 0.75rem;
685 + color: #5A5650;
686 + }
687 +
688 + /* Info panel */
689 + .info-panel {
690 + background: #111110;
691 + border-bottom: 1px solid #1E1E1C;
692 + padding: 1.5rem 2rem;
693 + }
694 +
695 + .info-content h4 {
696 + font-family: 'DM Mono', monospace;
697 + font-size: 0.625rem;
698 + letter-spacing: 0.15em;
699 + text-transform: uppercase;
700 + color: #5A5650;
701 + margin: 0 0 0.5rem 0;
702 + }
703 +
704 + .info-content p {
705 + font-size: 0.875rem;
706 + line-height: 1.6;
707 + color: #8A8578;
708 + margin: 0 0 1.25rem 0;
709 + }
710 +
711 + .variacion {
712 + padding-left: 1rem;
713 + border-left: 2px solid #2E2E2C;
714 + margin-bottom: 1rem;
715 + }
716 +
717 + .variacion-rango {
718 + font-family: 'DM Mono', monospace;
719 + font-size: 0.688rem;
720 + color: #5A5650;
721 + }
722 +
723 + .variacion p {
724 + margin-top: 0.25rem;
725 + font-size: 0.813rem;
726 + }
727 +
728 + /* Main */
729 + main {
730 + padding: 1rem 2rem;
731 + max-width: 900px;
732 + margin: 0 auto;
733 + }
734 +
735 + /* Entity selector */
736 + .entity-selector {
737 + display: flex;
738 + align-items: center;
739 + gap: 0.75rem;
740 + margin-bottom: 0.75rem;
741 + }
742 +
743 + .selector-label {
744 + font-size: 0.813rem;
745 + color: #5A5650;
746 + }
747 +
748 + .selector-btn {
749 + display: flex;
750 + align-items: center;
751 + gap: 0.5rem;
752 + padding: 0.5rem 1rem;
753 + background: #161614;
754 + border: 1px solid #2E2E2C;
755 + border-radius: 8px;
756 + color: #F5F0E8;
757 + font-size: 0.875rem;
758 + cursor: pointer;
759 + transition: all 0.2s;
760 + }
761 + .selector-btn:hover {
762 + border-color: #E8C547;
763 + }
764 + .selector-btn svg {
765 + color: #5A5650;
766 + }
767 +
768 + /* Chart */
769 + .chart-section {
770 + margin-bottom: 1rem;
771 + }
772 +
773 + .chart-protagonista {
774 + margin-bottom: 1.25rem;
775 + }
776 +
777 + .chart-protagonista .chart {
778 + height: calc(100vh - 480px);
779 + min-height: 120px;
780 + max-height: 200px;
781 + }
782 +
783 + .chart-protagonista .y-axis {
784 + height: calc(100vh - 480px);
785 + min-height: 120px;
786 + max-height: 200px;
787 + }
788 +
789 + .chart-section:has(.chart-comparar) {
790 + margin-bottom: 0;
791 + }
792 +
793 + .chart-section:has(.chart-comparar) .y-axis {
794 + height: calc(100vh - 380px);
795 + min-height: 140px;
796 + max-height: 280px;
797 + }
798 +
799 + .chart-header {
800 + display: flex;
801 + justify-content: space-between;
802 + align-items: baseline;
803 + margin-bottom: 0.5rem;
804 + }
805 +
806 + .chart-title {
807 + font-size: 0.75rem;
808 + color: #5A5650;
809 + text-transform: uppercase;
810 + letter-spacing: 0.1em;
811 + }
812 +
813 + .chart-period {
814 + font-family: 'DM Mono', monospace;
815 + font-size: 0.875rem;
816 + color: #B0A89C;
817 + font-variant-numeric: tabular-nums;
818 + min-width: 5rem;
819 + text-align: right;
820 + }
821 +
822 + .chart-wrapper {
823 + display: flex;
824 + gap: 0.75rem;
825 + }
826 +
827 + .y-axis {
828 + display: flex;
829 + flex-direction: column;
830 + justify-content: space-between;
831 + align-items: flex-end;
832 + height: 160px;
833 + padding-bottom: 1.5rem;
834 + }
835 +
836 + .y-label {
837 + font-family: 'DM Mono', monospace;
838 + font-size: 0.625rem;
839 + color: #5A5650;
840 + }
841 +
842 + .chart {
843 + flex: 1;
844 + display: flex;
845 + align-items: flex-end;
846 + gap: 4px;
847 + height: 160px;
848 + padding: 0 0.5rem;
849 + border-left: 1px solid #2E2E2C;
850 + }
851 +
852 + .bar-container {
853 + flex: 1;
854 + display: flex;
855 + flex-direction: column;
856 + align-items: center;
857 + justify-content: flex-end;
858 + height: 100%;
859 + cursor: pointer;
860 + }
861 +
862 + .bar {
863 + width: 100%;
864 + background: #2a9d8f;
865 + border-radius: 4px 4px 0 0;
866 + transition: background 0.3s cubic-bezier(0.4, 0, 0.2, 1), transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);
867 + min-height: 4px;
868 + }
869 +
870 + .bar.hovered {
871 + background: #264653;
872 + transform: scaleX(1.1);
873 + }
874 +
875 + .bar-label {
876 + font-family: 'DM Mono', monospace;
877 + font-size: 0.625rem;
878 + color: #B0A89C;
879 + margin-top: 0.5rem;
880 + opacity: 0;
881 + transition: opacity 0.25s cubic-bezier(0.4, 0, 0.2, 1), color 0.25s ease;
882 + }
883 + .bar-label.visible { opacity: 1; }
884 +
885 + /* KPIs */
886 + .kpis {
887 + display: grid;
888 + grid-template-columns: repeat(4, 1fr);
889 + gap: 0.5rem;
890 + margin-bottom: 1rem;
891 + }
892 +
893 + .kpi {
894 + background: #131312;
895 + padding: 0.75rem 0.75rem;
896 + text-align: center;
897 + border-radius: 8px;
898 + transition: background 0.3s ease;
899 + }
900 +
901 + .kpi-value {
902 + display: block;
903 + font-family: 'DM Mono', monospace;
904 + font-size: 1.125rem;
905 + font-weight: 500;
906 + color: #F5F0E8;
907 + margin-bottom: 0;
908 + transition: opacity 0.2s ease;
909 + font-variant-numeric: tabular-nums;
910 + min-height: 1.5rem;
911 + }
912 +
913 + .kpi-label {
914 + font-size: 0.5625rem;
915 + color: #5A5650;
916 + text-transform: uppercase;
917 + letter-spacing: 0.03em;
918 + line-height: 1.3;
919 + transition: opacity 0.2s ease;
920 + min-height: 1.25rem;
921 + display: flex;
922 + align-items: center;
923 + justify-content: center;
924 + }
925 +
926 + /* Top entidades */
927 + .top-header {
928 + display: flex;
929 + justify-content: space-between;
930 + align-items: baseline;
931 + margin-bottom: 0.625rem;
932 + }
933 +
934 + .top-section h3 {
935 + font-family: 'DM Mono', monospace;
936 + font-size: 0.75rem;
937 + color: #5A5650;
938 + text-transform: uppercase;
939 + letter-spacing: 0.1em;
940 + margin: 0;
941 + }
942 +
943 + .top-context {
944 + font-family: 'DM Mono', monospace;
945 + font-size: 0.688rem;
946 + color: #5A5650;
947 + font-variant-numeric: tabular-nums;
948 + }
949 +
950 + .top-list {
951 + display: flex;
952 + flex-direction: column;
953 + gap: 0.5rem;
954 + }
955 +
956 + .top-item {
957 + display: flex;
958 + align-items: center;
959 + gap: 0.75rem;
960 + padding: 0.625rem 0.875rem;
961 + background: #131312;
962 + border-radius: 8px;
963 + }
964 +
965 + .top-rank {
966 + font-family: 'DM Mono', monospace;
967 + font-size: 0.875rem;
968 + color: #5A5650;
969 + width: 24px;
970 + }
971 +
972 + .top-info {
973 + flex: 1;
974 + }
975 +
976 + .top-name {
977 + display: block;
978 + font-size: 0.8125rem;
979 + color: #F5F0E8;
980 + margin-bottom: 0.375rem;
981 + }
982 +
983 + .top-bar-bg {
984 + height: 4px;
985 + background: #1E1E1C;
986 + border-radius: 2px;
987 + overflow: hidden;
988 + }
989 +
990 + .top-bar {
991 + height: 100%;
992 + background: #2a9d8f;
993 + border-radius: 2px;
994 + transition: width 0.35s cubic-bezier(0.4, 0, 0.2, 1);
995 + }
996 +
997 + .top-pct {
998 + font-family: 'DM Mono', monospace;
999 + font-size: 0.875rem;
1000 + color: #B0A89C;
1001 + min-width: 52px;
1002 + width: 52px;
1003 + text-align: right;
1004 + transition: opacity 0.2s ease;
1005 + font-variant-numeric: tabular-nums;
1006 + }
1007 +
1008 + .see-all {
1009 + background: transparent;
1010 + border: 1px solid #2E2E2C;
1011 + border-radius: 8px;
1012 + padding: 0.75rem 1rem;
1013 + color: #8A8578;
1014 + font-size: 0.813rem;
1015 + cursor: pointer;
1016 + transition: all 0.2s;
1017 + width: 100%;
1018 + margin-top: 1rem;
1019 + }
1020 + .see-all:hover {
1021 + border-color: #E8C547;
1022 + color: #E8C547;
1023 + }
1024 +
1025 + /* Vista Toggle */
1026 + .vista-toggle {
1027 + display: inline-flex;
1028 + background: #131312;
1029 + border: 1px solid #2E2E2C;
1030 + border-radius: 10px;
1031 + padding: 4px;
1032 + margin-bottom: 0.75rem;
1033 + }
1034 +
1035 + .toggle-btn {
1036 + padding: 0.5rem 1.25rem;
1037 + font-size: 0.813rem;
1038 + font-weight: 500;
1039 + color: #5A5650;
1040 + background: transparent;
1041 + border: none;
1042 + border-radius: 7px;
1043 + cursor: pointer;
1044 + transition: all 0.2s;
1045 + }
1046 +
1047 + .toggle-btn:hover {
1048 + color: #8A8578;
1049 + }
1050 +
1051 + .toggle-btn.active {
1052 + background: #F5F0E8;
1053 + color: #0A0A0A;
1054 + }
1055 +
1056 + /* Comparador inline - ajustado para 100vh */
1057 + .comparador-selectors {
1058 + display: flex;
1059 + align-items: center;
1060 + gap: 1rem;
1061 + margin-bottom: 0.75rem;
1062 + }
1063 +
1064 + .selector-group {
1065 + flex: 1;
1066 + display: flex;
1067 + align-items: center;
1068 + gap: 0.5rem;
1069 + }
1070 +
1071 + .selector-dot {
1072 + width: 10px;
1073 + height: 10px;
1074 + border-radius: 50%;
1075 + flex-shrink: 0;
1076 + }
1077 +
1078 + .selector-group select {
1079 + flex: 1;
1080 + background: #1A1A18;
1081 + border: 1px solid #2E2E2C;
1082 + border-radius: 8px;
1083 + padding: 0.5rem 0.75rem;
1084 + color: #F5F0E8;
1085 + font-size: 0.813rem;
1086 + font-family: 'Instrument Sans', sans-serif;
1087 + cursor: pointer;
1088 + }
1089 + .selector-group select:focus {
1090 + outline: none;
1091 + border-color: #E8C547;
1092 + }
1093 +
1094 + .vs {
1095 + font-family: 'DM Mono', monospace;
1096 + font-size: 0.75rem;
1097 + color: #5A5650;
1098 + flex-shrink: 0;
1099 + }
1100 +
1101 + .chart-comparar {
1102 + gap: 1.5rem;
1103 + justify-content: center;
1104 + height: calc(100vh - 380px);
1105 + min-height: 140px;
1106 + max-height: 280px;
1107 + }
1108 +
1109 + .chart-comparar + .chart-legend + .kpis-comparar {
1110 + margin-top: 0;
1111 + }
1112 +
1113 + .bar-group {
1114 + display: flex;
1115 + flex-direction: column;
1116 + align-items: center;
1117 + height: 100%;
1118 + }
1119 +
1120 + .bars-pair {
1121 + display: flex;
1122 + align-items: flex-end;
1123 + gap: 4px;
1124 + flex: 1;
1125 + }
1126 +
1127 + .bar-a, .bar-b {
1128 + width: 20px;
1129 + border-radius: 4px 4px 0 0;
1130 + min-height: 4px;
1131 + background: var(--bar-color);
1132 + transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1), filter 0.25s ease;
1133 + }
1134 +
1135 + .bar-group.active .bar-a,
1136 + .bar-group.active .bar-b {
1137 + filter: brightness(1.15);
1138 + }
1139 +
1140 + .bar-group {
1141 + cursor: pointer;
1142 + padding: 0.5rem 0.35rem;
1143 + margin: -0.5rem -0.35rem;
1144 + border-radius: 6px;
1145 + transition: background 0.3s cubic-bezier(0.4, 0, 0.2, 1);
1146 + }
1147 +
1148 + .bar-group.active {
1149 + background: rgba(42, 157, 143, 0.15);
1150 + }
1151 +
1152 + .bar-label.highlight {
1153 + color: #F5F0E8;
1154 + font-weight: 500;
1155 + transition: color 0.25s ease;
1156 + }
1157 +
1158 + .chart-legend {
1159 + display: flex;
1160 + justify-content: center;
1161 + gap: 2rem;
1162 + margin-top: 0.5rem;
1163 + margin-bottom: 0.75rem;
1164 + }
1165 +
1166 + .legend-item {
1167 + display: flex;
1168 + align-items: center;
1169 + gap: 0.5rem;
1170 + font-size: 0.75rem;
1171 + color: #8A8578;
1172 + }
1173 +
1174 + .legend-dot {
1175 + width: 10px;
1176 + height: 10px;
1177 + border-radius: 3px;
1178 + }
1179 +
1180 + /* KPIs comparativos - tabla */
1181 + .kpis-tabla {
1182 + background: #131312;
1183 + border-radius: 10px;
1184 + padding: 0.5rem 1rem;
1185 + }
1186 +
1187 + .tabla-header {
1188 + display: grid;
1189 + grid-template-columns: 1fr 1fr 1fr;
1190 + gap: 1rem;
1191 + padding-bottom: 0.375rem;
1192 + border-bottom: 1px solid #2E2E2C;
1193 + margin-bottom: 0.375rem;
1194 + }
1195 +
1196 + .tabla-header .tabla-val {
1197 + font-family: 'DM Mono', monospace;
1198 + font-size: 0.625rem;
1199 + letter-spacing: 0.05em;
1200 + text-transform: uppercase;
1201 + text-align: center;
1202 + }
1203 +
1204 + .tabla-row {
1205 + display: grid;
1206 + grid-template-columns: 1fr 1fr 1fr;
1207 + gap: 1rem;
1208 + padding: 0.25rem 0;
1209 + }
1210 +
1211 + .tabla-label {
1212 + font-family: 'DM Mono', monospace;
1213 + font-size: 0.75rem;
1214 + font-weight: 500;
1215 + color: #8A8578;
1216 + display: flex;
1217 + align-items: center;
1218 + text-transform: uppercase;
1219 + letter-spacing: 0.03em;
1220 + }
1221 +
1222 + .tabla-val {
1223 + font-family: 'DM Mono', monospace;
1224 + font-size: 0.9375rem;
1225 + font-weight: 500;
1226 + color: #F5F0E8;
1227 + text-align: center;
1228 + font-variant-numeric: tabular-nums;
1229 + transition: opacity 0.2s ease;
1230 + }
1231 +
1232 + /* Insight comparador */
1233 + .comparador-insight {
1234 + text-align: center;
1235 + padding: 1rem 1.5rem;
1236 + background: linear-gradient(90deg, rgba(232,197,71,0.05), rgba(74,139,110,0.05));
1237 + border-radius: 10px;
1238 + border: 1px solid #2E2E2C;
1239 + font-size: 0.875rem;
1240 + color: #8A8578;
1241 + line-height: 1.6;
1242 + }
1243 +
1244 + .comparador-insight strong {
1245 + color: #F5F0E8;
1246 + }
1247 +
1248 + .insight-year {
1249 + font-family: 'DM Mono', monospace;
1250 + font-size: 0.813rem;
1251 + color: #E8C547;
1252 + }
1253 +
1254 + /* Responsive */
1255 + @media (max-width: 640px) {
1256 + header, main {
1257 + padding: 1.25rem 1rem;
1258 + }
1259 +
1260 + .title-main {
1261 + flex-direction: column;
1262 + gap: 0.25rem;
1263 + }
1264 +
1265 + .codigo {
1266 + font-size: 1.25rem;
1267 + }
1268 +
1269 + h1 {
1270 + font-size: 1.375rem;
1271 + }
1272 +
1273 + .y-axis {
1274 + display: none;
1275 + }
1276 +
1277 + .chart {
1278 + border-left: none;
1279 + height: 120px;
1280 + }
1281 +
1282 + .kpis {
1283 + grid-template-columns: repeat(2, 1fr);
1284 + }
1285 +
1286 + .kpi-value {
1287 + font-size: 1.125rem;
1288 + }
1289 +
1290 + .top-summary {
1291 + flex-direction: column;
1292 + gap: 0.25rem;
1293 + }
1294 +
1295 + .comparador-modal {
1296 + padding: 1.5rem;
1297 + }
1298 +
1299 + .comparador-selectors {
1300 + flex-direction: column;
1301 + gap: 0.75rem;
1302 + }
1303 +
1304 + .vs {
1305 + display: none;
1306 + }
1307 +
1308 + .kpi-values {
1309 + gap: 1rem;
1310 + }
1311 +
1312 + .kpi-val {
1313 + font-size: 0.75rem;
1314 + min-width: 60px;
1315 + }
1316 +
1317 + .comp-bar {
1318 + width: 12px;
1319 + }
1320 +
1321 + .grouped-chart {
1322 + gap: 0.5rem;
1323 + }
1324 + }
1325 +
1326 + /* ========== TEMA CLARO ========== */
1327 + .tema-claro {
1328 + background: #FAFAF8;
1329 + color: #1A1A18;
1330 + }
1331 +
1332 + .tema-claro header {
1333 + border-bottom-color: #E5E5E0;
1334 + }
1335 +
1336 + .tema-claro .breadcrumb a {
1337 + color: #8A8578;
1338 + }
1339 + .tema-claro .breadcrumb a:hover {
1340 + color: #1A1A18;
1341 + }
1342 +
1343 + .tema-claro .codigo {
1344 + color: #8A8578;
1345 + }
1346 +
1347 + .tema-claro h1 {
1348 + color: #1A1A18;
1349 + }
1350 +
1351 + .tema-claro .help-icon {
1352 + color: #8A8578;
1353 + }
1354 + .tema-claro .help-icon:hover,
1355 + .tema-claro .help-icon.active {
1356 + color: #C9A227;
1357 + }
1358 +
1359 + .tema-claro .badge {
1360 + background: #F0F0EC;
1361 + border-color: #E5E5E0;
1362 + color: #5A5650;
1363 + }
1364 +
1365 + .tema-claro .years {
1366 + color: #8A8578;
1367 + }
1368 +
1369 + .tema-claro .info-panel {
1370 + background: #F5F5F3;
1371 + border-bottom-color: #E5E5E0;
1372 + }
1373 +
1374 + .tema-claro .info-content h4 {
1375 + color: #8A8578;
1376 + }
1377 +
1378 + .tema-claro .info-content p {
1379 + color: #5A5650;
1380 + }
1381 +
1382 + .tema-claro .variacion {
1383 + border-left-color: #D5D5D0;
1384 + }
1385 +
1386 + .tema-claro .variacion-rango {
1387 + color: #8A8578;
1388 + }
1389 +
1390 + .tema-claro .vista-toggle {
1391 + background: #F0F0EC;
1392 + border-color: #E5E5E0;
1393 + }
1394 +
1395 + .tema-claro .toggle-btn {
1396 + color: #8A8578;
1397 + }
1398 + .tema-claro .toggle-btn:hover {
1399 + color: #5A5650;
1400 + }
1401 + .tema-claro .toggle-btn.active {
1402 + background: #1A1A18;
1403 + color: #FAFAF8;
1404 + }
1405 +
1406 + .tema-claro .entity-selector .selector-label {
1407 + color: #8A8578;
1408 + }
1409 +
1410 + .tema-claro .selector-btn {
1411 + background: transparent;
1412 + border: none;
1413 + box-shadow: inset 0 0 0 1px rgba(0,0,0,0.08);
1414 + color: #1A1A18;
1415 + }
1416 + .tema-claro .selector-btn:hover {
1417 + box-shadow: inset 0 0 0 1px rgba(0,0,0,0.15);
1418 + }
1419 + .tema-claro .selector-btn svg {
1420 + color: #8A8578;
1421 + }
1422 +
1423 + .tema-claro .chart-title {
1424 + color: #8A8578;
1425 + }
1426 +
1427 + .tema-claro .chart-period {
1428 + color: #333333;
1429 + }
1430 +
1431 + .tema-claro .y-label {
1432 + color: #8A8578;
1433 + }
1434 +
1435 + .tema-claro .chart {
1436 + border-left-color: #E5E5E0;
1437 + }
1438 +
1439 + .tema-claro .bar-container .bar {
1440 + background: #2a9d8f;
1441 + }
1442 +
1443 + .tema-claro .bar-container .bar.hovered {
1444 + background: #264653;
1445 + }
1446 +
1447 + /* Las barras de comparación mantienen su --bar-color */
1448 + .tema-claro .bar-a,
1449 + .tema-claro .bar-b {
1450 + background: var(--bar-color);
1451 + }
1452 +
1453 + .tema-claro .bar-label {
1454 + color: #333333;
1455 + }
1456 +
1457 + .tema-claro .kpis {
1458 + background: transparent;
1459 + }
1460 +
1461 + .tema-claro .kpi {
1462 + background: transparent;
1463 + box-shadow: inset 0 0 0 1px rgba(0,0,0,0.08);
1464 + }
1465 +
1466 + .tema-claro .kpi-value {
1467 + color: #1A1A18;
1468 + }
1469 +
1470 + .tema-claro .kpi-label {
1471 + color: #8A8578;
1472 + }
1473 +
1474 + .tema-claro .top-section h3 {
1475 + color: #8A8578;
1476 + }
1477 +
1478 + .tema-claro .top-context {
1479 + color: #8A8578;
1480 + }
1481 +
1482 + .tema-claro .top-item {
1483 + background: transparent;
1484 + box-shadow: inset 0 0 0 1px rgba(0,0,0,0.08);
1485 + }
1486 +
1487 + .tema-claro .top-rank {
1488 + color: #8A8578;
1489 + }
1490 +
1491 + .tema-claro .top-name {
1492 + color: #1A1A18;
1493 + }
1494 +
1495 + .tema-claro .top-bar-bg {
1496 + background: #F0F0EC;
1497 + }
1498 +
1499 + .tema-claro .top-bar {
1500 + background: #2a9d8f;
1501 + }
1502 +
1503 + .tema-claro .top-pct {
1504 + color: #333333;
1505 + }
1506 +
1507 + /* Comparador - tema claro */
1508 + .tema-claro .comparador-selectors .selector-group select {
1509 + background: transparent;
1510 + border: none;
1511 + box-shadow: inset 0 0 0 1px rgba(0,0,0,0.08);
1512 + color: #1A1A18;
1513 + }
1514 + .tema-claro .comparador-selectors .selector-group select:focus {
1515 + box-shadow: inset 0 0 0 1px rgba(0,0,0,0.15);
1516 + outline: none;
1517 + }
1518 +
1519 + .tema-claro .vs {
1520 + color: #8A8578;
1521 + }
1522 +
1523 + .tema-claro .chart-legend .legend-item {
1524 + color: #5A5650;
1525 + }
1526 +
1527 + .tema-claro .kpis-tabla {
1528 + background: transparent;
1529 + box-shadow: inset 0 0 0 1px rgba(0,0,0,0.08);
1530 + }
1531 +
1532 + .tema-claro .tabla-header {
1533 + border-bottom-color: #E5E5E0;
1534 + }
1535 +
1536 + .tema-claro .tabla-label {
1537 + color: #5A5650;
1538 + }
1539 +
1540 + .tema-claro .tabla-val {
1541 + color: #1A1A18;
1542 + }
1543 +
1544 + .tema-claro .bar-group.active {
1545 + background: rgba(42, 157, 143, 0.12);
1546 + }
1547 +
1548 + .tema-claro .bar-label.highlight {
1549 + color: #1A1A18;
1550 + }
1551 +</style>
1 +<script>
2 + import { onMount } from 'svelte';
3 +
4 + let hovered = null;
5 + let activeModal = null;
6 + let mounted = false;
7 +
8 + onMount(() => { setTimeout(() => { mounted = true; }, 80); });
9 +
10 + const C = {
11 + bg: '#0A0A0A', bgAlt: '#111111', surface: '#161616',
12 + card: '#131313', cardHover: '#1A1A1A',
13 + accent: '#E8C547', green: '#4A8B6E',
14 + text: '#F5F0E8', muted: '#8A8578', dim: '#5A5650',
15 + border: '#222220', borderLight: '#2E2E2C',
16 + };
17 +
18 + const MODALS = {
19 + quien: {
20 + title: '¿Quién gasta o recibe?', tech: 'CLASIFICADOR INSTITUCIONAL',
21 + everyday: 'Es como preguntar "¿quién en la familia maneja plata?". Papá tiene su sueldo, mamá maneja el negocio, el abuelo cobra su renta. Cada uno es una "entidad" distinta con su propio presupuesto.',
22 + budget: 'El clasificador institucional cataloga a todas las entidades del sector público: ministerios, gobernaciones, municipios, empresas públicas, universidades. Cada una tiene un código único y un presupuesto asignado.',
23 + example: 'Ejemplo: El Ministerio de Educación (código 040) y el Gobierno Autónomo de La Paz (código 126) son entidades distintas, cada una con sus propios ingresos y gastos.',
24 + color: '#F5F0E8',
25 + },
26 + rubros: {
27 + title: '¿Qué tipo de ingreso es?', tech: 'CLASIFICADOR DE RECURSOS POR RUBROS',
28 + everyday: 'María gana un sueldo de su trabajo, cobra alquiler de un cuartito, y un amigo le prestó plata. Son tres tipos distintos de ingreso: laboral, patrimonial, y crédito.',
29 + budget: 'En el presupuesto, el Estado recibe ingresos tributarios (impuestos = el "sueldo"), regalías por explotación de recursos (= el "alquiler"), y créditos internacionales (= el "préstamo"). El rubro te dice qué naturaleza tiene cada ingreso.',
30 + example: 'Ejemplo: Los Bs 2.400M que Bolivia recibe por IDH son un rubro distinto a los Bs 800M de crédito del BID, aunque ambos financien al mismo ministerio.',
31 + color: '#E8C547',
32 + },
33 + fuentes: {
34 + title: '¿Quién pone la plata?', tech: 'CLASIFICADOR DE FUENTES DE FINANCIAMIENTO',
35 + everyday: 'María sabe que su sueldo se lo paga la empresa TechCorp, el alquiler se lo paga su inquilino Juan, y el préstamo se lo hizo el Banco Nacional. Son las fuentes: quién firma el cheque.',
36 + budget: 'Las fuentes son los bolsillos específicos: el TGN (Tesoro General de la Nación), recursos propios de cada entidad, crédito externo del BID o Banco Mundial, o donaciones de cooperación.',
37 + example: 'Ejemplo: Un crédito del BID y uno del Banco Mundial son el mismo rubro (crédito externo) pero distinta fuente. La fuente dice quién exactamente pone la plata.',
38 + color: '#E8C547',
39 + },
40 + objeto: {
41 + title: '¿En qué se gasta concretamente?', tech: 'CLASIFICADOR POR OBJETO DEL GASTO',
42 + everyday: 'Cuando María gasta, compra cosas concretas: arroz, paga la luz, le paga al albañil. Cada compra es un "objeto" distinto — algo tangible que se puede listar.',
43 + budget: 'El Objeto del Gasto es exactamente eso: sueldos, consultorías, materiales de escritorio, combustible, medicamentos. Es la lista del supermercado del Estado.',
44 + example: 'Ejemplo: El grupo 10000 "Servicios Personales" incluye sueldos y aguinaldos. El 20000 "Servicios No Personales" incluye consultorías y alquileres.',
45 + color: '#4A8B6E',
46 + },
47 + sector: {
48 + title: '¿A qué sector de la economía va?', tech: 'CLASIFICADOR POR SECTORES ECONÓMICOS',
49 + everyday: 'Si María contrata un albañil, esa plata va al sector construcción. Si compra medicina, va al sector salud. No importa qué compró — importa qué parte de la economía mueve.',
50 + budget: 'Este clasificador mide el impacto económico: cuánto va a infraestructura, cuánto a servicios, cuánto a industria. Permite evaluar cómo las decisiones fiscales afectan a cada sector.',
51 + example: 'Ejemplo: Un hospital nuevo es "construcción" en Objeto del Gasto pero "salud" en Sector Económico. Misma plata, dos lentes distintos.',
52 + color: '#4A8B6E',
53 + },
54 + finalidad: {
55 + title: '¿Para qué se gasta?', tech: 'CLASIFICADOR POR FINALIDAD Y FUNCIÓN',
56 + everyday: 'María compró arroz (objeto), pero ¿para qué? Para alimentar a su familia. Compró cemento: para tener casa. El "para qué" es el objetivo final, no el insumo.',
57 + budget: 'Este clasificador agrupa por propósito: educación, defensa, salud, protección social. No importa si se gastó en sueldos o en cemento — lo que importa es el fin.',
58 + example: 'Ejemplo: El sueldo de un profesor y la construcción de una escuela son objetos de gasto distintos, pero ambos tienen la misma finalidad: Educación.',
59 + color: '#4A8B6E',
60 + },
61 + donde: {
62 + title: '¿Dónde se gasta?', tech: 'CLASIFICADOR GEOGRÁFICO',
63 + everyday: 'María manda plata a su mamá en Cochabamba y paga el alquiler en La Paz. No es lo mismo gastar en la ciudad que en el área rural, en Santa Cruz que en Potosí.',
64 + budget: 'La dimensión geográfica permite ver cómo se distribuyen los recursos en el territorio: por departamento, municipio o región. Revela las prioridades territoriales del Estado.',
65 + example: 'Ejemplo: Del presupuesto de Educación, ¿cuánto va a La Paz vs. Beni? Este clasificador responde esa pregunta.',
66 + color: '#4A8B6E',
67 + },
68 + };
69 +
70 + const WIDGETS_INGRESOS = [
71 + { id: 'rubros', q: '¿Qué tipo de ingreso?', d: 'Impuestos, regalías, ventas, créditos — la naturaleza de cada ingreso', t: 'Recursos por rubros' },
72 + { id: 'fuentes', q: '¿Quién pone la plata?', d: 'TGN, crédito externo, donaciones — de qué bolsillo sale', t: 'Fuentes de financiamiento' },
73 + ];
74 +
75 + const WIDGETS_EN_QUE = [
76 + { id: 'objeto', q: 'Objeto del gasto', d: 'Sueldos, consultorías, materiales — la lista concreta', t: 'Objeto del gasto' },
77 + { id: 'sector', q: 'Sector económico', d: 'Industria, construcción, servicios — a qué sector va', t: 'Sectores económicos' },
78 + ];
79 +
80 + const WIDGETS_BOTTOM = [
81 + { id: 'finalidad', q: '¿Para qué se gasta?', d: 'Educación, salud, defensa — el objetivo final', t: 'Finalidad y función' },
82 + { id: 'donde', q: '¿Dónde se gasta?', d: 'Departamentos, municipios — distribución territorial', t: 'Geográfico' },
83 + ];
84 +
85 + $: modal = activeModal ? MODALS[activeModal] : null;
86 +
87 + function openModal(e, id) {
88 + e.stopPropagation();
89 + activeModal = id;
90 + }
91 + function closeModal() { activeModal = null; }
92 +</script>
93 +
94 +<div class="page" class:mounted>
95 +
96 + <!-- Header -->
97 + <header>
98 + <div class="header-left">
99 + <a href="/" class="back-link">
100 + <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M19 12H5M12 19l-7-7 7-7"/></svg>
101 + </a>
102 + <div class="live-dot"></div>
103 + <span class="header-brand">PRESUPUESTO ABIERTO</span>
104 + </div>
105 + <span class="header-right">Explorar</span>
106 + </header>
107 +
108 + <main>
109 +
110 + <!-- Title -->
111 + <div class="page-title anim" style="--d:0ms">
112 + <h1>Elige cómo explorar el presupuesto</h1>
113 + <p>Cada clasificador es una forma distinta de ver la misma información. Haz clic en <span class="help-inline">?</span> para entender qué es cada uno.</p>
114 + </div>
115 +
116 + <!-- ═══ ¿QUIÉN? ═══ -->
117 + <div class="anim" style="--d:100ms">
118 + <div class="group">
119 + <div class="group-accent" style="--c: {C.text}"></div>
120 + <div class="group-header">
121 + <div class="group-icon" style="--c: {C.text}">
122 + <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke={C.text} stroke-width="1.5" stroke-linecap="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
123 + </div>
124 + <div>
125 + <div class="group-label" style="color:{C.text}">¿QUIÉN?</div>
126 + <div class="group-sub">La entidad que gasta o recibe</div>
127 + </div>
128 + </div>
129 +
130 + <div
131 + class="widget-card widget-h"
132 + class:wh={hovered === 'quien'}
133 + on:mouseenter={() => hovered = 'quien'}
134 + on:mouseleave={() => hovered = null}
135 + role="button" tabindex="0"
136 + >
137 + <div class="widget-h-left">
138 + <div class="widget-q" style="font-size:16px">Instituciones públicas</div>
139 + <div class="widget-desc">Ministerios, gobernaciones, municipios, empresas públicas, universidades</div>
140 + </div>
141 + <div class="widget-h-right">
142 + <span class="widget-tech">INSTITUCIONAL</span>
143 + <button class="help-btn help-sm" on:click={(e) => openModal(e, 'quien')}>?</button>
144 + <span class="widget-arrow" class:arrow-show={hovered === 'quien'}>→</span>
145 + </div>
146 + </div>
147 + </div>
148 + </div>
149 +
150 + <!-- ═══ INGRESOS + GASTOS ═══ -->
151 + <div class="main-grid">
152 +
153 + <!-- INGRESOS -->
154 + <div class="anim col-stretch" style="--d:200ms">
155 + <div class="group group-stretch">
156 + <div class="group-accent" style="--c: {C.accent}"></div>
157 + <div class="group-header">
158 + <div class="group-icon" style="--c: {C.accent}">
159 + <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke={C.accent} stroke-width="2" stroke-linecap="round"><path d="M12 19V5M5 12l7-7 7 7"/></svg>
160 + </div>
161 + <div>
162 + <div class="group-label" style="color:{C.accent}">INGRESOS</div>
163 + <div class="group-sub">¿De dónde viene la plata?</div>
164 + </div>
165 + </div>
166 +
167 + <div class="widgets-stretch">
168 + {#each WIDGETS_INGRESOS as w}
169 + <div
170 + class="widget-card widget-grow"
171 + class:wh={hovered === w.id}
172 + on:mouseenter={() => hovered = w.id}
173 + on:mouseleave={() => hovered = null}
174 + role="button" tabindex="0"
175 + >
176 + <div class="widget-top">
177 + <h4 class="widget-q">{w.q}</h4>
178 + <button class="help-btn" on:click={(e) => openModal(e, w.id)}>?</button>
179 + </div>
180 + <p class="widget-desc widget-desc-grow">{w.d}</p>
181 + <div class="widget-footer">
182 + <span class="widget-tech">{w.t}</span>
183 + <span class="widget-arrow" class:arrow-show={hovered === w.id}>→</span>
184 + </div>
185 + <div class="widget-glow" style="background:radial-gradient(circle at 50% 50%,{C.accent}06,transparent 70%)"></div>
186 + </div>
187 + {/each}
188 + </div>
189 + </div>
190 + </div>
191 +
192 + <!-- GASTOS -->
193 + <div class="anim col-stretch" style="--d:320ms">
194 + <div class="group">
195 + <div class="group-accent" style="--c: {C.green}"></div>
196 + <div class="group-header">
197 + <div class="group-icon" style="--c: {C.green}">
198 + <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke={C.green} stroke-width="2" stroke-linecap="round"><path d="M12 5v14M5 12l7 7 7-7"/></svg>
199 + </div>
200 + <div>
201 + <div class="group-label" style="color:{C.green}">GASTOS</div>
202 + <div class="group-sub">¿Cómo se gasta la plata?</div>
203 + </div>
204 + </div>
205 +
206 + <div class="group-widgets">
207 +
208 + <!-- ¿En qué? sub-group -->
209 + <div class="sub-group">
210 + <div class="sub-group-accent" style="--c: {C.green}"></div>
211 + <div class="sub-group-header">
212 + <div class="sub-group-left">
213 + <div class="sub-group-icon" style="--c: {C.green}">
214 + <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke={C.green} stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/></svg>
215 + </div>
216 + <div>
217 + <div class="sub-group-label">¿En qué se gasta?</div>
218 + <div class="sub-group-tag" style="color:{C.green}90">DOS CLASIFICADORES</div>
219 + </div>
220 + </div>
221 + <button class="help-btn help-sm" on:click={(e) => openModal(e, 'objeto')}>?</button>
222 + </div>
223 +
224 + <div class="sub-grid">
225 + {#each WIDGETS_EN_QUE as w}
226 + <div
227 + class="widget-card sub-card"
228 + class:wh={hovered === w.id}
229 + on:mouseenter={() => hovered = w.id}
230 + on:mouseleave={() => hovered = null}
231 + role="button" tabindex="0"
232 + >
233 + <div class="widget-top">
234 + <h4 class="widget-q sub-q">{w.q}</h4>
235 + <button class="help-btn help-xs" on:click={(e) => openModal(e, w.id)}>?</button>
236 + </div>
237 + <p class="widget-desc sub-desc">{w.d}</p>
238 + <div class="widget-footer sub-footer">
239 + <span class="widget-tech sub-tech">{w.t}</span>
240 + <span class="widget-arrow" class:arrow-show={hovered === w.id}>→</span>
241 + </div>
242 + <div class="widget-glow" style="background:radial-gradient(circle at 50% 50%,{C.green}06,transparent 70%)"></div>
243 + </div>
244 + {/each}
245 + </div>
246 + </div>
247 +
248 + <!-- Bottom row -->
249 + <div class="bottom-grid">
250 + {#each WIDGETS_BOTTOM as w}
251 + <div
252 + class="widget-card"
253 + class:wh={hovered === w.id}
254 + on:mouseenter={() => hovered = w.id}
255 + on:mouseleave={() => hovered = null}
256 + role="button" tabindex="0"
257 + >
258 + <div class="widget-top">
259 + <h4 class="widget-q">{w.q}</h4>
260 + <button class="help-btn" on:click={(e) => openModal(e, w.id)}>?</button>
261 + </div>
262 + <p class="widget-desc">{w.d}</p>
263 + <div class="widget-footer">
264 + <span class="widget-tech">{w.t}</span>
265 + <span class="widget-arrow" class:arrow-show={hovered === w.id}>→</span>
266 + </div>
267 + <div class="widget-glow" style="background:radial-gradient(circle at 50% 50%,{C.green}06,transparent 70%)"></div>
268 + </div>
269 + {/each}
270 + </div>
271 + </div>
272 + </div>
273 + </div>
274 + </div>
275 +
276 + </main>
277 +</div>
278 +
279 +<!-- ═══ MODAL ═══ -->
280 +{#if modal}
281 + <!-- svelte-ignore a11y-click-events-have-key-events -->
282 + <div class="modal-overlay" on:click={closeModal} role="dialog" aria-modal="true">
283 + <!-- svelte-ignore a11y-click-events-have-key-events -->
284 + <div class="modal-box" on:click|stopPropagation role="document">
285 + <button class="modal-close" on:click={closeModal}>
286 + <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M18 6L6 18M6 6l12 12"/></svg>
287 + </button>
288 +
289 + <div class="modal-badge" style="background:{modal.color}12;color:{modal.color}">{modal.tech}</div>
290 + <h2 class="modal-title">{modal.title}</h2>
291 +
292 + <div class="modal-section">
293 + <div class="modal-icon">👤</div>
294 + <div>
295 + <div class="modal-label">EN LA VIDA COTIDIANA</div>
296 + <p class="modal-text">{modal.everyday}</p>
297 + </div>
298 + </div>
299 +
300 + <div class="modal-divider"></div>
301 +
302 + <div class="modal-section">
303 + <div class="modal-icon">🏛️</div>
304 + <div>
305 + <div class="modal-label">EN EL PRESUPUESTO</div>
306 + <p class="modal-text">{modal.budget}</p>
307 + </div>
308 + </div>
309 +
310 + <div class="modal-example" style="border-left-color:{modal.color}">
311 + <p>{modal.example}</p>
312 + </div>
313 + </div>
314 + </div>
315 +{/if}
316 +
317 +
318 +<style>
319 + @import url('https://fonts.googleapis.com/css2?family=Source+Serif+4:opsz,wght@8..60,300;8..60,400;8..60,600;8..60,700;8..60,900&family=JetBrains+Mono:wght@300;400;500&family=DM+Sans:wght@400;500;600;700&display=swap');
320 +
321 + :global(*) { margin: 0; padding: 0; box-sizing: border-box; }
322 + :global(::selection) { background: #E8C547; color: #0A0A0A; }
323 + :global(::-webkit-scrollbar) { width: 5px; }
324 + :global(::-webkit-scrollbar-track) { background: #0A0A0A; }
325 + :global(::-webkit-scrollbar-thumb) { background: #5A5650; border-radius: 3px; }
326 +
327 + @keyframes pulseGlow { 0%,100%{opacity:0.4}50%{opacity:0.9} }
328 + @keyframes mIn { from{opacity:0;transform:translateY(16px) scale(0.97)}to{opacity:1;transform:translateY(0) scale(1)} }
329 +
330 + .page { background: #0A0A0A; color: #F5F0E8; min-height: 100vh; font-family: 'Source Serif 4', Georgia, serif; }
331 +
332 + /* ── Entrance animations ── */
333 + .anim {
334 + opacity: 0; transform: translateY(24px);
335 + transition: all 0.7s cubic-bezier(0.16,1,0.3,1);
336 + transition-delay: var(--d, 0ms);
337 + }
338 + .mounted .anim { opacity: 1; transform: translateY(0); }
339 +
340 + /* ── Header ── */
341 + header {
342 + padding: 18px 40px; display: flex; justify-content: space-between; align-items: center;
343 + border-bottom: 1px solid #222220; position: sticky; top: 0; z-index: 50;
344 + background: rgba(10,10,10,0.93); backdrop-filter: blur(12px);
345 + }
346 + .header-left { display: flex; align-items: center; gap: 12px; }
347 + .back-link { color: #5A5650; display: flex; align-items: center; transition: color 0.3s; text-decoration: none; }
348 + .back-link:hover { color: #F5F0E8; }
349 + .live-dot {
350 + width: 7px; height: 7px; border-radius: 50%; background: #4A8B6E;
351 + animation: pulseGlow 2s ease-in-out infinite;
352 + box-shadow: 0 0 10px rgba(74,139,110,0.37);
353 + }
354 + .header-brand { font-family: 'JetBrains Mono', monospace; font-size: 11px; letter-spacing: 0.2em; color: #8A8578; font-weight: 500; }
355 + .header-right { font-family: 'DM Sans', sans-serif; font-size: 14px; font-weight: 600; color: #5A5650; letter-spacing: 0.04em; }
356 +
357 + main { max-width: 1120px; margin: 0 auto; padding: 36px 36px 100px; }
358 +
359 + /* ── Page title ── */
360 + .page-title { margin-bottom: 28px; padding: 0 2px; }
361 + .page-title h1 { font-size: 28px; font-weight: 900; letter-spacing: -0.02em; line-height: 1.1; }
362 + .page-title p { font-family: 'DM Sans', sans-serif; font-size: 14px; color: #5A5650; margin-top: 6px; line-height: 1.5; }
363 + .help-inline {
364 + font-family: 'Source Serif 4', serif; font-weight: 700; color: #8A8578;
365 + border: 1px solid #222220; border-radius: 50%; padding: 0 5px; font-size: 11px;
366 + }
367 +
368 + /* ── Group (level 1) ── */
369 + .group {
370 + background: #111111; border: 1px solid #222220; border-radius: 24px;
371 + padding: 20px 18px 18px; position: relative; overflow: hidden;
372 + }
373 + .group-stretch {
374 + display: flex; flex-direction: column; height: 100%;
375 + }
376 + .group-accent {
377 + position: absolute; top: 0; left: 0; right: 0; height: 2px;
378 + background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--c) 30%, transparent), transparent);
379 + border-radius: 24px 24px 0 0;
380 + }
381 + .group-header { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; padding: 0 4px; }
382 + .group-icon {
383 + width: 32px; height: 32px; border-radius: 10px;
384 + background: color-mix(in srgb, var(--c) 10%, transparent);
385 + border: 1px solid color-mix(in srgb, var(--c) 20%, transparent);
386 + display: flex; align-items: center; justify-content: center; flex-shrink: 0;
387 + }
388 + .group-label { font-family: 'JetBrains Mono', monospace; font-size: 11px; letter-spacing: 0.18em; font-weight: 600; line-height: 1; }
389 + .group-sub { font-family: 'DM Sans', sans-serif; font-size: 12px; color: #5A5650; margin-top: 2px; }
390 +
391 + .group-widgets { display: flex; flex-direction: column; gap: 10px; }
392 + .widgets-stretch { display: flex; flex-direction: column; gap: 10px; flex: 1; }
393 +
394 + /* ── Widget card (level 2) ── */
395 + .widget-card {
396 + background: #131313; border: 1px solid #222220; border-radius: 16px;
397 + padding: 22px; position: relative; overflow: hidden; cursor: pointer;
398 + transition: all 0.4s cubic-bezier(0.16,1,0.3,1);
399 + }
400 + .wh {
401 + background: #1A1A1A; border-color: #2E2E2C; transform: translateY(-1px);
402 + }
403 + .widget-grow {
404 + flex: 1; display: flex; flex-direction: column;
405 + }
406 +
407 + /* Horizontal variant (¿Quién?) */
408 + .widget-h {
409 + display: flex; justify-content: space-between; align-items: center;
410 + }
411 + .widget-h-left { flex: 1; }
412 + .widget-h-right { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
413 +
414 + .widget-top {
415 + display: flex; justify-content: space-between; align-items: flex-start;
416 + gap: 10px; margin-bottom: 8px; position: relative; z-index: 1;
417 + }
418 + .widget-q {
419 + font-size: 17px; font-weight: 700; letter-spacing: -0.01em;
420 + line-height: 1.2; font-family: 'DM Sans', sans-serif;
421 + }
422 + .widget-desc {
423 + font-family: 'DM Sans', sans-serif; font-size: 12px; color: #8A8578;
424 + line-height: 1.5; position: relative; z-index: 1;
425 + }
426 + .widget-desc-grow { flex: 1; }
427 + .widget-footer {
428 + display: flex; justify-content: space-between; align-items: center;
429 + margin-top: 16px; position: relative; z-index: 1;
430 + }
431 + .widget-tech {
432 + font-family: 'JetBrains Mono', monospace; font-size: 8px;
433 + letter-spacing: 0.12em; color: #5A5650; text-transform: uppercase;
434 + }
435 + .widget-arrow {
436 + font-family: 'DM Sans', sans-serif; font-size: 13px; color: transparent;
437 + transform: translateX(-6px);
438 + transition: all 0.35s cubic-bezier(0.16,1,0.3,1);
439 + }
440 + .arrow-show { color: #F5F0E8; transform: translateX(0); }
441 + .widget-glow {
442 + position: absolute; inset: 0; pointer-events: none;
443 + opacity: 0; transition: opacity 0.4s;
444 + }
445 + .wh > .widget-glow,
446 + .wh .widget-glow { opacity: 1; }
447 +
448 + /* ── Sub-group (level 2 container — "¿En qué?") ── */
449 + .sub-group {
450 + background: #0E0E0E; border: 1px solid #222220; border-radius: 18px;
451 + padding: 16px 14px 14px; position: relative; overflow: hidden;
452 + }
453 + .sub-group-accent {
454 + position: absolute; top: 0; left: 15%; right: 15%; height: 1px;
455 + background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--c) 25%, transparent), transparent);
456 + }
457 + .sub-group-header {
458 + display: flex; align-items: center; justify-content: space-between;
459 + margin-bottom: 12px; padding: 0 4px;
460 + }
461 + .sub-group-left { display: flex; align-items: center; gap: 8px; }
462 + .sub-group-icon {
463 + width: 26px; height: 26px; border-radius: 8px;
464 + background: color-mix(in srgb, var(--c) 10%, transparent);
465 + border: 1px solid color-mix(in srgb, var(--c) 18%, transparent);
466 + display: flex; align-items: center; justify-content: center; flex-shrink: 0;
467 + }
468 + .sub-group-label {
469 + font-family: 'DM Sans', sans-serif; font-size: 14px;
470 + font-weight: 700; color: #F5F0E8; line-height: 1.1;
471 + }
472 + .sub-group-tag {
473 + font-family: 'JetBrains Mono', monospace; font-size: 8px;
474 + letter-spacing: 0.12em; margin-top: 2px;
475 + }
476 +
477 + .sub-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
478 +
479 + /* ── Sub-widget (level 3) ── */
480 + .sub-card {
481 + background: #111111; border-color: #1E1E1C;
482 + padding: 18px; border-radius: 14px;
483 + }
484 + .sub-card.wh { background: #161616; border-color: #2A2A28; }
485 + .sub-q { font-size: 14px; }
486 + .sub-desc { font-size: 11px; }
487 + .sub-footer { margin-top: 12px; }
488 + .sub-tech { font-size: 7px; }
489 +
490 + .bottom-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
491 +
492 + /* ── Main grid — equal height ── */
493 + .main-grid {
494 + display: grid; grid-template-columns: 1fr 2fr;
495 + gap: 16px; margin-top: 16px; align-items: stretch;
496 + }
497 + .col-stretch { display: flex; flex-direction: column; }
498 +
499 + /* ── Help button ── */
500 + .help-btn {
501 + width: 26px; height: 26px; border-radius: 50%; border: 1px solid #222220;
502 + background: transparent; color: #5A5650;
503 + font-family: 'Source Serif 4', serif; font-size: 12px; font-weight: 600;
504 + cursor: pointer; transition: all 0.3s;
505 + display: flex; align-items: center; justify-content: center; flex-shrink: 0;
506 + }
507 + .help-btn:hover { border-color: #E8C547; color: #E8C547; background: rgba(232,197,71,0.08); }
508 + .help-sm { width: 22px; height: 22px; font-size: 10px; }
509 + .help-xs { width: 20px; height: 20px; font-size: 9px; }
510 +
511 + /* ── Modal ── */
512 + .modal-overlay {
513 + position: fixed; inset: 0; background: rgba(0,0,0,0.8);
514 + backdrop-filter: blur(12px);
515 + display: flex; align-items: center; justify-content: center;
516 + z-index: 1000; padding: 40px;
517 + }
518 + .modal-box {
519 + background: #141414; border: 1px solid #222220; border-radius: 24px;
520 + max-width: 520px; width: 100%; padding: 32px 36px; position: relative;
521 + animation: mIn 0.3s cubic-bezier(0.16,1,0.3,1);
522 + max-height: 85vh; overflow-y: auto;
523 + }
524 + .modal-close {
525 + position: absolute; top: 16px; right: 16px;
526 + background: transparent; border: 1px solid #222220; border-radius: 50%;
527 + width: 32px; height: 32px; display: flex; align-items: center; justify-content: center;
528 + color: #5A5650; cursor: pointer; transition: all 0.2s;
529 + }
530 + .modal-close:hover { border-color: #5A5650; color: #F5F0E8; }
531 + .modal-badge {
532 + font-family: 'JetBrains Mono', monospace; font-size: 9px; letter-spacing: 0.15em;
533 + border-radius: 6px; padding: 4px 9px; display: inline-block; margin-bottom: 14px;
534 + }
535 + .modal-title { font-size: 24px; font-weight: 900; letter-spacing: -0.02em; line-height: 1.15; margin-bottom: 24px; }
536 + .modal-section { display: flex; gap: 14px; }
537 + .modal-icon {
538 + font-size: 18px; width: 36px; height: 36px; background: #161616; border-radius: 10px;
539 + display: flex; align-items: center; justify-content: center; flex-shrink: 0;
540 + border: 1px solid #222220;
541 + }
542 + .modal-label {
543 + font-family: 'JetBrains Mono', monospace; font-size: 9px;
544 + letter-spacing: 0.15em; color: #5A5650; margin-bottom: 5px;
545 + }
546 + .modal-text { font-family: 'DM Sans', sans-serif; font-size: 13px; line-height: 1.65; color: #8A8578; }
547 + .modal-divider { height: 1px; background: #222220; margin: 18px 0; margin-left: 50px; }
548 + .modal-example {
549 + margin-top: 20px; margin-left: 50px; padding: 14px 18px;
550 + border-left: 3px solid; background: rgba(232,197,71,0.03);
551 + border-radius: 0 10px 10px 0;
552 + }
553 + .modal-example p {
554 + font-family: 'DM Sans', sans-serif; font-size: 12px;
555 + line-height: 1.6; color: #8A8578; font-style: italic;
556 + }
557 +</style>
...\ No newline at end of file ...\ No newline at end of file
1 +<script>
2 + import { onMount } from 'svelte';
3 +
4 + let mounted = false;
5 + let hoveredYear = null;
6 + let showInfo = false;
7 +
8 + onMount(() => { setTimeout(() => { mounted = true; }, 50); });
9 +
10 + // Dummy data
11 + const objeto = {
12 + codigo: '10000',
13 + nombre: 'Servicios Personales',
14 + nivel: 'Grupo',
15 + años: '2005-2024',
16 + descripcion: 'Gastos por concepto de servicios prestados por el personal permanente y no permanente del sector público.',
17 + variaciones: [
18 + { rango: '2012-2024', texto: 'Gastos por concepto de servicios prestados por el personal permanente y no permanente.' },
19 + { rango: '2005-2011', texto: 'Retribuciones y complementos al personal permanente y eventual.' }
20 + ]
21 + };
22 +
23 + // Dummy spending data (in millions Bs)
24 + const gastoAnual = [
25 + { año: 2005, monto: 8200, poblacion: 9.4 },
26 + { año: 2006, monto: 8900, poblacion: 9.5 },
27 + { año: 2007, monto: 10200, poblacion: 9.6 },
28 + { año: 2008, monto: 12400, poblacion: 9.8 },
29 + { año: 2009, monto: 13100, poblacion: 9.9 },
30 + { año: 2010, monto: 14800, poblacion: 10.0 },
31 + { año: 2011, monto: 17200, poblacion: 10.2 },
32 + { año: 2012, monto: 19500, poblacion: 10.4 },
33 + { año: 2013, monto: 22800, poblacion: 10.5 },
34 + { año: 2014, monto: 28400, poblacion: 10.7 },
35 + { año: 2015, monto: 31200, poblacion: 10.9 },
36 + { año: 2016, monto: 33100, poblacion: 11.0 },
37 + { año: 2017, monto: 35600, poblacion: 11.2 },
38 + { año: 2018, monto: 38200, poblacion: 11.4 },
39 + { año: 2019, monto: 41500, poblacion: 11.5 },
40 + { año: 2020, monto: 39800, poblacion: 11.7 },
41 + { año: 2021, monto: 42100, poblacion: 11.8 },
42 + { año: 2022, monto: 45600, poblacion: 12.0 },
43 + { año: 2023, monto: 48900, poblacion: 12.1 },
44 + { año: 2024, monto: 52300, poblacion: 12.3 },
45 + ];
46 +
47 + const topEntidades = [
48 + { nombre: 'Ministerio de Educación', monto: 18200, porcentaje: 34.8 },
49 + { nombre: 'Ministerio de Salud', monto: 8900, porcentaje: 17.0 },
50 + { nombre: 'Policía Boliviana', monto: 6200, porcentaje: 11.9 },
51 + ];
52 +
53 + // Calculations
54 + const maxMonto = Math.max(...gastoAnual.map(g => g.monto));
55 + const totalHistorico = gastoAnual.reduce((sum, g) => sum + g.monto, 0);
56 + const totalPorcentaje = 14.2; // dummy: % del presupuesto total
57 + const ranking = 1; // dummy: #1 en gasto
58 +
59 + // Current view data (changes on hover)
60 + $: currentData = hoveredYear
61 + ? gastoAnual.find(g => g.año === hoveredYear)
62 + : null;
63 +
64 + $: displayMonto = currentData ? currentData.monto : totalHistorico;
65 + $: displayPerCapita = currentData
66 + ? Math.round(currentData.monto / currentData.poblacion)
67 + : Math.round(totalHistorico / gastoAnual.reduce((sum, g) => sum + g.poblacion, 0) * gastoAnual.length);
68 + $: displayPorcentaje = currentData ? (totalPorcentaje * (currentData.monto / (totalHistorico / gastoAnual.length)) / 1).toFixed(1) : totalPorcentaje;
69 + $: displayLabel = currentData ? currentData.año : '2005-2024';
70 + $: displayRanking = currentData ? `#${ranking} en ${currentData.año}` : `#${ranking}`;
71 +
72 + function formatMonto(m) {
73 + if (m >= 1000000) return (m / 1000000).toFixed(1) + 'B';
74 + if (m >= 1000) return (m / 1000).toFixed(1) + 'M';
75 + return m.toString();
76 + }
77 +</script>
78 +
79 +<div class="page" class:mounted>
80 +
81 + <!-- Header compacto -->
82 + <header>
83 + <nav class="breadcrumb">
84 + <a href="/clasificadores/objeto-gasto">← Objeto del Gasto</a>
85 + </nav>
86 +
87 + <div class="title-row">
88 + <div class="title-main">
89 + <span class="codigo">{objeto.codigo}</span>
90 + <h1>{objeto.nombre}</h1>
91 + </div>
92 +
93 + <button class="info-btn" on:click={() => showInfo = !showInfo} class:active={showInfo}>
94 + <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
95 + <circle cx="12" cy="12" r="10"/>
96 + <path d="M12 16v-4M12 8h.01"/>
97 + </svg>
98 + </button>
99 + </div>
100 +
101 + <div class="meta">
102 + <span class="badge">{objeto.nivel}</span>
103 + <span class="years">{objeto.años}</span>
104 + </div>
105 + </header>
106 +
107 + <!-- Panel de info (colapsable) -->
108 + {#if showInfo}
109 + <div class="info-panel">
110 + <div class="info-content">
111 + <h4>Definición vigente</h4>
112 + <p>{objeto.descripcion}</p>
113 +
114 + {#if objeto.variaciones.length > 1}
115 + <h4>Variaciones históricas</h4>
116 + {#each objeto.variaciones as v}
117 + <div class="variacion">
118 + <span class="variacion-rango">{v.rango}</span>
119 + <p>{v.texto}</p>
120 + </div>
121 + {/each}
122 + {/if}
123 + </div>
124 + </div>
125 + {/if}
126 +
127 + <!-- Dashboard principal -->
128 + <main>
129 +
130 + <!-- Selector de entidad -->
131 + <div class="entity-selector">
132 + <span class="selector-label">Viendo</span>
133 + <button class="selector-btn">
134 + Todo el sector público
135 + <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
136 + <path d="M6 9l6 6 6-6"/>
137 + </svg>
138 + </button>
139 + </div>
140 +
141 + <!-- Gráfico de barras -->
142 + <div class="chart-section">
143 + <div class="chart-header">
144 + <span class="chart-title">Ejecución presupuestaria</span>
145 + <span class="chart-period">{displayLabel}</span>
146 + </div>
147 +
148 + <div class="chart">
149 + {#each gastoAnual as g}
150 + <div
151 + class="bar-container"
152 + on:mouseenter={() => hoveredYear = g.año}
153 + on:mouseleave={() => hoveredYear = null}
154 + >
155 + <div
156 + class="bar"
157 + class:hovered={hoveredYear === g.año}
158 + style="height: {(g.monto / maxMonto) * 100}%"
159 + ></div>
160 + <span class="bar-label" class:visible={hoveredYear === g.año || g.año % 5 === 0}>
161 + {g.año.toString().slice(-2)}
162 + </span>
163 + </div>
164 + {/each}
165 + </div>
166 + </div>
167 +
168 + <!-- KPIs -->
169 + <div class="kpis">
170 + <div class="kpi">
171 + <span class="kpi-value">Bs {formatMonto(displayMonto)}</span>
172 + <span class="kpi-label">{currentData ? 'ese año' : 'acumulado'}</span>
173 + </div>
174 +
175 + <div class="kpi">
176 + <span class="kpi-value">Bs {displayPerCapita.toLocaleString()}</span>
177 + <span class="kpi-label">por persona</span>
178 + </div>
179 +
180 + <div class="kpi">
181 + <span class="kpi-value">{displayPorcentaje}%</span>
182 + <span class="kpi-label">del total</span>
183 + </div>
184 +
185 + <div class="kpi">
186 + <span class="kpi-value">{displayRanking}</span>
187 + <span class="kpi-label">en gasto</span>
188 + </div>
189 + </div>
190 +
191 + <!-- Top entidades -->
192 + <div class="top-section">
193 + <h3>Quién más gasta en esto</h3>
194 +
195 + <div class="top-list">
196 + {#each topEntidades as ent, i}
197 + <div class="top-item">
198 + <span class="top-rank">#{i + 1}</span>
199 + <div class="top-info">
200 + <span class="top-name">{ent.nombre}</span>
201 + <div class="top-bar-bg">
202 + <div class="top-bar" style="width: {ent.porcentaje}%"></div>
203 + </div>
204 + </div>
205 + <span class="top-pct">{ent.porcentaje}%</span>
206 + </div>
207 + {/each}
208 + </div>
209 +
210 + <button class="see-all">Ver todas las entidades →</button>
211 + </div>
212 +
213 + </main>
214 +</div>
215 +
216 +
217 +<style>
218 + .page {
219 + min-height: 100vh;
220 + background: #0A0A0A;
221 + color: #F5F0E8;
222 + font-family: 'Instrument Sans', -apple-system, sans-serif;
223 + opacity: 0;
224 + transition: opacity 0.4s ease;
225 + }
226 + .mounted { opacity: 1; }
227 +
228 + /* Header */
229 + header {
230 + padding: 1.5rem 2rem;
231 + border-bottom: 1px solid #1E1E1C;
232 + }
233 +
234 + .breadcrumb a {
235 + font-family: 'DM Mono', monospace;
236 + font-size: 0.75rem;
237 + color: #5A5650;
238 + text-decoration: none;
239 + transition: color 0.2s;
240 + }
241 + .breadcrumb a:hover { color: #F5F0E8; }
242 +
243 + .title-row {
244 + display: flex;
245 + align-items: flex-start;
246 + justify-content: space-between;
247 + margin-top: 1rem;
248 + gap: 1rem;
249 + }
250 +
251 + .title-main {
252 + display: flex;
253 + align-items: baseline;
254 + gap: 1rem;
255 + flex-wrap: wrap;
256 + }
257 +
258 + .codigo {
259 + font-family: 'DM Mono', monospace;
260 + font-size: 1.75rem;
261 + color: #5A5650;
262 + font-weight: 400;
263 + }
264 +
265 + h1 {
266 + font-family: 'DM Serif Display', Georgia, serif;
267 + font-size: 1.75rem;
268 + font-weight: 400;
269 + color: #F5F0E8;
270 + margin: 0;
271 + }
272 +
273 + .info-btn {
274 + background: transparent;
275 + border: 1px solid #2E2E2C;
276 + border-radius: 50%;
277 + width: 36px;
278 + height: 36px;
279 + display: flex;
280 + align-items: center;
281 + justify-content: center;
282 + color: #5A5650;
283 + cursor: pointer;
284 + transition: all 0.2s;
285 + flex-shrink: 0;
286 + }
287 + .info-btn:hover, .info-btn.active {
288 + border-color: #E8C547;
289 + color: #E8C547;
290 + }
291 +
292 + .meta {
293 + display: flex;
294 + align-items: center;
295 + gap: 0.75rem;
296 + margin-top: 0.75rem;
297 + }
298 +
299 + .badge {
300 + font-family: 'DM Mono', monospace;
301 + font-size: 0.625rem;
302 + letter-spacing: 0.1em;
303 + text-transform: uppercase;
304 + padding: 0.25rem 0.5rem;
305 + background: #1A1A18;
306 + border: 1px solid #2E2E2C;
307 + border-radius: 4px;
308 + color: #8A8578;
309 + }
310 +
311 + .years {
312 + font-family: 'DM Mono', monospace;
313 + font-size: 0.75rem;
314 + color: #5A5650;
315 + }
316 +
317 + /* Info panel */
318 + .info-panel {
319 + background: #111110;
320 + border-bottom: 1px solid #1E1E1C;
321 + padding: 1.5rem 2rem;
322 + }
323 +
324 + .info-content h4 {
325 + font-family: 'DM Mono', monospace;
326 + font-size: 0.625rem;
327 + letter-spacing: 0.15em;
328 + text-transform: uppercase;
329 + color: #5A5650;
330 + margin: 0 0 0.5rem 0;
331 + }
332 +
333 + .info-content p {
334 + font-size: 0.875rem;
335 + line-height: 1.6;
336 + color: #8A8578;
337 + margin: 0 0 1.25rem 0;
338 + }
339 +
340 + .variacion {
341 + padding-left: 1rem;
342 + border-left: 2px solid #2E2E2C;
343 + margin-bottom: 1rem;
344 + }
345 +
346 + .variacion-rango {
347 + font-family: 'DM Mono', monospace;
348 + font-size: 0.688rem;
349 + color: #5A5650;
350 + }
351 +
352 + .variacion p {
353 + margin-top: 0.25rem;
354 + font-size: 0.813rem;
355 + }
356 +
357 + /* Main */
358 + main {
359 + padding: 2rem;
360 + max-width: 900px;
361 + margin: 0 auto;
362 + }
363 +
364 + /* Entity selector */
365 + .entity-selector {
366 + display: flex;
367 + align-items: center;
368 + gap: 0.75rem;
369 + margin-bottom: 2rem;
370 + }
371 +
372 + .selector-label {
373 + font-size: 0.813rem;
374 + color: #5A5650;
375 + }
376 +
377 + .selector-btn {
378 + display: flex;
379 + align-items: center;
380 + gap: 0.5rem;
381 + padding: 0.5rem 1rem;
382 + background: #161614;
383 + border: 1px solid #2E2E2C;
384 + border-radius: 8px;
385 + color: #F5F0E8;
386 + font-size: 0.875rem;
387 + cursor: pointer;
388 + transition: all 0.2s;
389 + }
390 + .selector-btn:hover {
391 + border-color: #E8C547;
392 + }
393 + .selector-btn svg {
394 + color: #5A5650;
395 + }
396 +
397 + /* Chart */
398 + .chart-section {
399 + margin-bottom: 2rem;
400 + }
401 +
402 + .chart-header {
403 + display: flex;
404 + justify-content: space-between;
405 + align-items: baseline;
406 + margin-bottom: 1rem;
407 + }
408 +
409 + .chart-title {
410 + font-size: 0.75rem;
411 + color: #5A5650;
412 + text-transform: uppercase;
413 + letter-spacing: 0.1em;
414 + }
415 +
416 + .chart-period {
417 + font-family: 'DM Mono', monospace;
418 + font-size: 0.875rem;
419 + color: #E8C547;
420 + }
421 +
422 + .chart {
423 + display: flex;
424 + align-items: flex-end;
425 + gap: 4px;
426 + height: 160px;
427 + padding: 0 0.5rem;
428 + }
429 +
430 + .bar-container {
431 + flex: 1;
432 + display: flex;
433 + flex-direction: column;
434 + align-items: center;
435 + height: 100%;
436 + cursor: pointer;
437 + }
438 +
439 + .bar {
440 + width: 100%;
441 + background: linear-gradient(to top, #2A5A45, #4A8B6E);
442 + border-radius: 4px 4px 0 0;
443 + transition: all 0.2s ease;
444 + min-height: 4px;
445 + }
446 +
447 + .bar.hovered {
448 + background: linear-gradient(to top, #E8C547, #F5D76E);
449 + transform: scaleX(1.1);
450 + }
451 +
452 + .bar-label {
453 + font-family: 'DM Mono', monospace;
454 + font-size: 0.625rem;
455 + color: #5A5650;
456 + margin-top: 0.5rem;
457 + opacity: 0;
458 + transition: opacity 0.2s;
459 + }
460 + .bar-label.visible { opacity: 1; }
461 +
462 + /* KPIs */
463 + .kpis {
464 + display: grid;
465 + grid-template-columns: repeat(4, 1fr);
466 + gap: 1px;
467 + background: #2E2E2C;
468 + border-radius: 12px;
469 + overflow: hidden;
470 + margin-bottom: 2.5rem;
471 + }
472 +
473 + .kpi {
474 + background: #131312;
475 + padding: 1.25rem 1rem;
476 + text-align: center;
477 + }
478 +
479 + .kpi-value {
480 + display: block;
481 + font-family: 'DM Serif Display', Georgia, serif;
482 + font-size: 1.375rem;
483 + color: #F5F0E8;
484 + margin-bottom: 0.25rem;
485 + }
486 +
487 + .kpi-label {
488 + font-size: 0.688rem;
489 + color: #5A5650;
490 + text-transform: uppercase;
491 + letter-spacing: 0.05em;
492 + }
493 +
494 + /* Top entidades */
495 + .top-section h3 {
496 + font-family: 'DM Mono', monospace;
497 + font-size: 0.75rem;
498 + color: #5A5650;
499 + text-transform: uppercase;
500 + letter-spacing: 0.1em;
501 + margin: 0 0 1rem 0;
502 + }
503 +
504 + .top-list {
505 + display: flex;
506 + flex-direction: column;
507 + gap: 0.75rem;
508 + }
509 +
510 + .top-item {
511 + display: flex;
512 + align-items: center;
513 + gap: 1rem;
514 + padding: 0.875rem 1rem;
515 + background: #131312;
516 + border-radius: 10px;
517 + border: 1px solid #1E1E1C;
518 + transition: border-color 0.2s;
519 + }
520 + .top-item:hover {
521 + border-color: #2E2E2C;
522 + }
523 +
524 + .top-rank {
525 + font-family: 'DM Mono', monospace;
526 + font-size: 0.875rem;
527 + color: #5A5650;
528 + width: 24px;
529 + }
530 +
531 + .top-info {
532 + flex: 1;
533 + }
534 +
535 + .top-name {
536 + display: block;
537 + font-size: 0.875rem;
538 + color: #F5F0E8;
539 + margin-bottom: 0.5rem;
540 + }
541 +
542 + .top-bar-bg {
543 + height: 4px;
544 + background: #1E1E1C;
545 + border-radius: 2px;
546 + overflow: hidden;
547 + }
548 +
549 + .top-bar {
550 + height: 100%;
551 + background: linear-gradient(to right, #4A8B6E, #6BAF8E);
552 + border-radius: 2px;
553 + }
554 +
555 + .top-pct {
556 + font-family: 'DM Mono', monospace;
557 + font-size: 0.875rem;
558 + color: #E8C547;
559 + }
560 +
561 + .see-all {
562 + background: transparent;
563 + border: 1px solid #2E2E2C;
564 + border-radius: 8px;
565 + padding: 0.75rem 1rem;
566 + color: #8A8578;
567 + font-size: 0.813rem;
568 + cursor: pointer;
569 + transition: all 0.2s;
570 + width: 100%;
571 + margin-top: 1rem;
572 + }
573 + .see-all:hover {
574 + border-color: #E8C547;
575 + color: #E8C547;
576 + }
577 +
578 + /* Responsive */
579 + @media (max-width: 640px) {
580 + header, main {
581 + padding: 1.25rem 1rem;
582 + }
583 +
584 + .title-main {
585 + flex-direction: column;
586 + gap: 0.25rem;
587 + }
588 +
589 + .codigo {
590 + font-size: 1.25rem;
591 + }
592 +
593 + h1 {
594 + font-size: 1.375rem;
595 + }
596 +
597 + .kpis {
598 + grid-template-columns: repeat(2, 1fr);
599 + }
600 +
601 + .chart {
602 + height: 120px;
603 + }
604 + }
605 +</style>