Rafael Lopez

landing

...@@ -111,35 +111,35 @@ ...@@ -111,35 +111,35 @@
111 ============================================ */ 111 ============================================ */
112 112
113 :root { 113 :root {
114 - /* Modo claro (default) - Slate/White */ 114 + /* Modo claro (default) - Estilo Apple, limpio y con contraste */
115 - --theme-body: #fafafa; 115 + --theme-body: #ffffff;
116 - --theme-background: #f8fafc; 116 + --theme-background: #ffffff;
117 - --theme-titulo: #1a1a1a; 117 + --theme-titulo: #1d1d1f;
118 - --theme-texto: #64748b; 118 + --theme-texto: #424245;
119 - --theme-fill: #f1f5f9; 119 + --theme-fill: #ffffff;
120 - --theme-borde: rgba(0, 0, 0, 0.08); 120 + --theme-borde: rgba(0, 0, 0, 0.04);
121 - --theme-accent: #c9a751; 121 + --theme-accent: #9A8550;
122 - --theme-accent-hover: #b8963f; 122 + --theme-accent-hover: #857040;
123 123
124 /* Superficies alternativas */ 124 /* Superficies alternativas */
125 --theme-surface: #ffffff; 125 --theme-surface: #ffffff;
126 - --theme-surface-hover: #f8fafc; 126 + --theme-surface-hover: #f5f5f7;
127 } 127 }
128 128
129 :root.dark { 129 :root.dark {
130 - /* Modo oscuro */ 130 + /* Modo oscuro - Cálido como el landing (#1C1C1A base) */
131 - --theme-body: #0d0d0d; 131 + --theme-body: #1C1C1A;
132 - --theme-background: #1a1a1a; 132 + --theme-background: #242422;
133 - --theme-titulo: #e8e4d8; 133 + --theme-titulo: #F5F0E8;
134 - --theme-texto: #b8b3a3; 134 + --theme-texto: #B8B5AD;
135 - --theme-fill: #2d2d2d; 135 + --theme-fill: #2A2A27;
136 - --theme-borde: rgba(255, 255, 255, 0.1); 136 + --theme-borde: rgba(255, 255, 255, 0.08);
137 - --theme-accent: #c9a751; 137 + --theme-accent: #C9A751;
138 - --theme-accent-hover: #d4b76a; 138 + --theme-accent-hover: #D4B76A;
139 139
140 /* Superficies alternativas */ 140 /* Superficies alternativas */
141 - --theme-surface: #1a1a1a; 141 + --theme-surface: #232321;
142 - --theme-surface-hover: #2d2d2d; 142 + --theme-surface-hover: #2A2A27;
143 } 143 }
144 144
145 /* ============================================ 145 /* ============================================
......
...@@ -49,6 +49,7 @@ ...@@ -49,6 +49,7 @@
49 <div class="relative group/home"> 49 <div class="relative group/home">
50 <a 50 <a
51 href="/" 51 href="/"
52 + data-sveltekit-preload-data="off"
52 class="nav-icon p-2 rounded-full transition-all block" 53 class="nav-icon p-2 rounded-full transition-all block"
53 > 54 >
54 <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20"> 55 <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
......
...@@ -35,8 +35,8 @@ const createClasificadorCache = () => { ...@@ -35,8 +35,8 @@ const createClasificadorCache = () => {
35 const cache = currentCache.objetoGasto; 35 const cache = currentCache.objetoGasto;
36 if (!cache.lastFetched) return null; 36 if (!cache.lastFetched) return null;
37 37
38 - // Cache valid for 5 minutes 38 + // Cache valid for 30 minutes
39 - const isValid = Date.now() - cache.lastFetched < 5 * 60 * 1000; 39 + const isValid = Date.now() - cache.lastFetched < 30 * 60 * 1000;
40 if (!isValid) return null; 40 if (!isValid) return null;
41 41
42 return cache; 42 return cache;
......
1 +import { writable } from 'svelte/store';
2 +
3 +export const isNavigating = writable(false);
4 +export const navigationTarget = writable(null);
1 <script> 1 <script>
2 import '../app.css'; 2 import '../app.css';
3 - import { page } from '$app/stores'; 3 + import { page, navigating } from '$app/stores';
4 import favicon from '$lib/assets/favicon.svg'; 4 import favicon from '$lib/assets/favicon.svg';
5 import Navbar from '$lib/components/ui/Navbar.svelte'; 5 import Navbar from '$lib/components/ui/Navbar.svelte';
6 import NavigationDrawer from '$lib/components/layout/NavigationDrawer.svelte'; 6 import NavigationDrawer from '$lib/components/layout/NavigationDrawer.svelte';
7 import { openDrawer } from '$lib/stores/drawer.js'; 7 import { openDrawer } from '$lib/stores/drawer.js';
8 + import Spinner from '$lib/components/ui/Spinner.svelte';
8 9
9 let { children } = $props(); 10 let { children } = $props();
10 11
11 // Don't show navbar on landing page 12 // Don't show navbar on landing page
12 let isLanding = $derived($page.url.pathname === '/'); 13 let isLanding = $derived($page.url.pathname === '/');
14 +
15 + // Navigation loading state
16 + let showLoader = $state(false);
17 + let loaderTimeout = null;
18 + let navStartTime = null;
19 +
20 + // Show loader after 150ms delay (avoid flash for fast navigations)
21 + $effect(() => {
22 + if ($navigating) {
23 + navStartTime = performance.now();
24 + console.log(`[NAV] Navigation started → ${$navigating.to?.url?.pathname}`);
25 + loaderTimeout = setTimeout(() => {
26 + showLoader = true;
27 + }, 150);
28 + } else {
29 + if (loaderTimeout) clearTimeout(loaderTimeout);
30 + if (navStartTime) {
31 + console.log(`[NAV] Navigation complete: ${(performance.now() - navStartTime).toFixed(0)}ms`);
32 + navStartTime = null;
33 + }
34 + showLoader = false;
35 + }
36 + });
13 </script> 37 </script>
14 38
15 <svelte:head> 39 <svelte:head>
...@@ -26,8 +50,55 @@ ...@@ -26,8 +50,55 @@
26 <Navbar onOpenDrawer={openDrawer} /> 50 <Navbar onOpenDrawer={openDrawer} />
27 {/if} 51 {/if}
28 52
53 +<!-- Navigation loading overlay -->
54 +{#if showLoader}
55 + <div class="nav-loader">
56 + <div class="nav-loader-content">
57 + <Spinner size={32} color="#C9A751" />
58 + <span class="nav-loader-text">Cargando...</span>
59 + </div>
60 + </div>
61 +{/if}
62 +
29 <div class="min-h-screen" style="background-color: var(--theme-body);"> 63 <div class="min-h-screen" style="background-color: var(--theme-body);">
30 <main> 64 <main>
31 {@render children()} 65 {@render children()}
32 </main> 66 </main>
33 </div> 67 </div>
68 +
69 +<style>
70 + .nav-loader {
71 + position: fixed;
72 + top: 0;
73 + left: 0;
74 + right: 0;
75 + bottom: 0;
76 + background: rgba(28, 28, 26, 0.85);
77 + backdrop-filter: blur(8px);
78 + -webkit-backdrop-filter: blur(8px);
79 + z-index: 9999;
80 + display: flex;
81 + align-items: center;
82 + justify-content: center;
83 + animation: fadeIn 0.15s ease-out;
84 + }
85 +
86 + .nav-loader-content {
87 + display: flex;
88 + flex-direction: column;
89 + align-items: center;
90 + gap: 16px;
91 + }
92 +
93 + .nav-loader-text {
94 + font-family: 'DM Mono', monospace;
95 + font-size: 13px;
96 + color: #B8B5AD;
97 + letter-spacing: 0.05em;
98 + }
99 +
100 + @keyframes fadeIn {
101 + from { opacity: 0; }
102 + to { opacity: 1; }
103 + }
104 +</style>
......
1 <script> 1 <script>
2 import { onMount } from 'svelte'; 2 import { onMount } from 'svelte';
3 import { goto } from '$app/navigation'; 3 import { goto } from '$app/navigation';
4 + import { supabase } from '$lib/supabase';
4 import { 5 import {
5 query, 6 query,
6 results, 7 results,
...@@ -15,6 +16,13 @@ ...@@ -15,6 +16,13 @@
15 navigateResults 16 navigateResults
16 } from '$lib/stores/searchStore'; 17 } from '$lib/stores/searchStore';
17 18
19 + // Pre-warm Supabase connection (evita cold start al navegar)
20 + async function warmupSupabase() {
21 + console.time('[WARMUP] Supabase');
22 + await supabase.schema('ppto').from('vista_objeto_estado').select('objeto').limit(1);
23 + console.timeEnd('[WARMUP] Supabase');
24 + }
25 +
18 let mounted = false; 26 let mounted = false;
19 let mode = 'archivo'; 27 let mode = 'archivo';
20 let searchFocused = false; 28 let searchFocused = false;
...@@ -56,6 +64,8 @@ ...@@ -56,6 +64,8 @@
56 onMount(() => { 64 onMount(() => {
57 setTimeout(() => { mounted = true; }, 80); 65 setTimeout(() => { mounted = true; }, 80);
58 initIndex(); 66 initIndex();
67 + // Pre-calentar Supabase en background para evitar cold start
68 + warmupSupabase();
59 }); 69 });
60 70
61 function toggleMobileSettings() { 71 function toggleMobileSettings() {
...@@ -791,7 +801,7 @@ ...@@ -791,7 +801,7 @@
791 <div class="descargas-formats"> 801 <div class="descargas-formats">
792 <span class="format-pill">.csv</span> 802 <span class="format-pill">.csv</span>
793 <span class="format-pill">.parquet</span> 803 <span class="format-pill">.parquet</span>
794 - <span class="format-pill">.json</span> 804 + <span class="format-pill">.gpkg</span>
795 </div> 805 </div>
796 <a href="/descargas" class="descargas-link"> 806 <a href="/descargas" class="descargas-link">
797 <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 4v12"/><polyline points="8 12 12 16 16 12"/><path d="M4 18h16"/></svg> 807 <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 4v12"/><polyline points="8 12 12 16 16 12"/><path d="M4 18h16"/></svg>
...@@ -872,7 +882,7 @@ ...@@ -872,7 +882,7 @@
872 .hero-search{display:flex;justify-content:center;position:relative;z-index:50} 882 .hero-search{display:flex;justify-content:center;position:relative;z-index:50}
873 .hero-tag{font-family:var(--mono);font-size:12px;letter-spacing:0.25em;color:#B8B5AD} 883 .hero-tag{font-family:var(--mono);font-size:12px;letter-spacing:0.25em;color:#B8B5AD}
874 .hero-title{font-size:clamp(44px,5.5vw,72px);font-weight:900;line-height:1.02;letter-spacing:-0.035em;max-width:720px;margin:14px auto 0} 884 .hero-title{font-size:clamp(44px,5.5vw,72px);font-weight:900;line-height:1.02;letter-spacing:-0.035em;max-width:720px;margin:14px auto 0}
875 - .hero-sub{font-family:var(--sans);font-size:clamp(16px,1.6vw,18px);color:#9B9890;max-width:100%;margin-top:14px;line-height:1.6} 885 + .hero-sub{font-family:var(--sans);font-size:clamp(16px,1.6vw,18px);color:#A5A29A;max-width:100%;margin-top:14px;line-height:1.6}
876 .hero-stats{font-family:var(--mono);font-size:13px;letter-spacing:0.04em;color:#B8B5AD;margin-top:16px;line-height:1} 886 .hero-stats{font-family:var(--mono);font-size:13px;letter-spacing:0.04em;color:#B8B5AD;margin-top:16px;line-height:1}
877 .stat-num{color:#F5F0E8} 887 .stat-num{color:#F5F0E8}
878 .stat-dot{margin:0 10px;opacity:0.3} 888 .stat-dot{margin:0 10px;opacity:0.3}
......
...@@ -16,6 +16,19 @@ ...@@ -16,6 +16,19 @@
16 let highlightedItem = $state(null); 16 let highlightedItem = $state(null);
17 let sidebarOpen = $state(false); 17 let sidebarOpen = $state(false);
18 18
19 + // Modo embed (cuando está dentro de un iframe en /objeto/[codigo])
20 + let isEmbed = $derived($page.url.searchParams.get('embed') === 'true');
21 + let initialObjeto = $derived($page.url.searchParams.get('objeto'));
22 +
23 + // Función para navegar a un objeto (en modo embed envía mensaje al padre)
24 + function navigateToObjeto(codigo) {
25 + if (isEmbed && window.parent !== window) {
26 + window.parent.postMessage({ type: 'navigate-objeto', codigo }, '*');
27 + } else {
28 + goto(`/objeto/${codigo}`);
29 + }
30 + }
31 +
19 // Modo de visualización desde URL 32 // Modo de visualización desde URL
20 let viewMode = $derived($page.url.searchParams.get('modo') || 'lista'); 33 let viewMode = $derived($page.url.searchParams.get('modo') || 'lista');
21 34
...@@ -300,6 +313,11 @@ ...@@ -300,6 +313,11 @@
300 return; 313 return;
301 } 314 }
302 315
316 + // No calcular si las dimensiones no están listas
317 + if (treemapWidth <= 0 || treemapHeight <= 0) {
318 + return;
319 + }
320 +
303 const treemap = d3.treemap() 321 const treemap = d3.treemap()
304 .size([treemapWidth, treemapHeight]) 322 .size([treemapWidth, treemapHeight])
305 .paddingOuter(4) 323 .paddingOuter(4)
...@@ -467,55 +485,59 @@ ...@@ -467,55 +485,59 @@
467 } 485 }
468 } 486 }
469 487
470 - // Manejar resize del contenedor (solo una vez al montar) 488 + // Manejar resize del contenedor - usando onMount para evitar conflictos de efectos
471 let resizeObserver = null; 489 let resizeObserver = null;
472 - let scrollHandler = null;
473 490
474 - $effect(() => { 491 + // Función para actualizar dimensiones del treemap
475 - if (treemapContainer && viewMode === 'mapa') { 492 + function updateTreemapDimensions() {
476 - // Función para actualizar dimensiones y recalcular layout 493 + if (!treemapContainer || viewMode !== 'mapa') return;
477 - const updateDimensions = () => { 494 +
478 const rect = treemapContainer.getBoundingClientRect(); 495 const rect = treemapContainer.getBoundingClientRect();
479 if (rect.width > 0) { 496 if (rect.width > 0) {
480 const newWidth = Math.floor(rect.width); 497 const newWidth = Math.floor(rect.width);
481 - // Calcular altura disponible para que quepa en viewport
482 const viewportHeight = window.innerHeight; 498 const viewportHeight = window.innerHeight;
483 const paddingBottom = 24; 499 const paddingBottom = 24;
484 -
485 const effectiveTop = Math.max(rect.top, 0); 500 const effectiveTop = Math.max(rect.top, 0);
486 const availableHeight = viewportHeight - effectiveTop - paddingBottom; 501 const availableHeight = viewportHeight - effectiveTop - paddingBottom;
487 const newHeight = Math.max(250, Math.floor(availableHeight)); 502 const newHeight = Math.max(250, Math.floor(availableHeight));
488 503
489 - if (newWidth !== treemapWidth || newHeight !== treemapHeight) { 504 + const dimensionsChanged = newWidth !== treemapWidth || newHeight !== treemapHeight;
505 +
506 + if (dimensionsChanged) {
490 treemapWidth = newWidth; 507 treemapWidth = newWidth;
491 treemapHeight = newHeight; 508 treemapHeight = newHeight;
509 + }
510 +
511 + // Recalcular si hay datos y dimensiones válidas
512 + if (currentTreemapNode && currentTreemapNode.children && treemapWidth > 0 && treemapHeight > 0) {
513 + if (dimensionsChanged || treemapNodes.length === 0) {
492 recalculateCurrentLayout(); 514 recalculateCurrentLayout();
493 } 515 }
494 } 516 }
495 - }; 517 + }
518 + }
496 519
497 - // Calcular dimensiones iniciales con pequeño delay para asegurar render 520 + // Efecto simple para setup/cleanup de ResizeObserver
498 - setTimeout(updateDimensions, 50); 521 + $effect(() => {
522 + const container = treemapContainer;
523 + const mode = viewMode;
499 524
500 - // Usar ResizeObserver para cambios de tamaño 525 + if (container && mode === 'mapa') {
501 - resizeObserver = new ResizeObserver(() => { 526 + // Calcular dimensiones iniciales
502 - updateDimensions(); 527 + requestAnimationFrame(() => {
528 + updateTreemapDimensions();
503 }); 529 });
504 - resizeObserver.observe(treemapContainer);
505 530
506 - // También escuchar scroll para recalcular cuando el usuario hace scroll 531 + // Setup ResizeObserver
507 - scrollHandler = () => { 532 + const observer = new ResizeObserver(() => {
508 - updateDimensions(); 533 + requestAnimationFrame(updateTreemapDimensions);
509 - }; 534 + });
510 - window.addEventListener('scroll', scrollHandler, { passive: true }); 535 + observer.observe(container);
536 + resizeObserver = observer;
511 537
512 return () => { 538 return () => {
513 - if (resizeObserver) { 539 + observer.disconnect();
514 - resizeObserver.disconnect(); 540 + resizeObserver = null;
515 - }
516 - if (scrollHandler) {
517 - window.removeEventListener('scroll', scrollHandler);
518 - }
519 }; 541 };
520 } 542 }
521 }); 543 });
...@@ -876,6 +898,45 @@ ...@@ -876,6 +898,45 @@
876 return Math.max(...years.map(y => parseInt(y))); 898 return Math.max(...years.map(y => parseInt(y)));
877 } 899 }
878 900
901 + /**
902 + * Converts a string with years into compact ranges
903 + * Example: "2006, 2007, 2008, 2009, 2015, 2016" → "2006-2009, 2015-2016"
904 + * Single years: "2006, 2008, 2010" → "2006, 2008, 2010"
905 + */
906 + function formatYearsAsRanges(rangosStr) {
907 + if (!rangosStr) return '';
908 +
909 + // Extract all 4-digit years
910 + const yearsMatch = rangosStr.match(/\d{4}/g);
911 + if (!yearsMatch || yearsMatch.length === 0) return rangosStr;
912 +
913 + // Convert to numbers and sort
914 + const years = [...new Set(yearsMatch.map(y => parseInt(y)))].sort((a, b) => a - b);
915 +
916 + if (years.length === 1) return String(years[0]);
917 +
918 + // Group consecutive years into ranges
919 + const ranges = [];
920 + let rangeStart = years[0];
921 + let rangeEnd = years[0];
922 +
923 + for (let i = 1; i < years.length; i++) {
924 + if (years[i] === rangeEnd + 1) {
925 + // Consecutive year, extend range
926 + rangeEnd = years[i];
927 + } else {
928 + // Gap found, save current range and start new one
929 + ranges.push(rangeStart === rangeEnd ? String(rangeStart) : `${rangeStart}-${rangeEnd}`);
930 + rangeStart = years[i];
931 + rangeEnd = years[i];
932 + }
933 + }
934 + // Add the last range
935 + ranges.push(rangeStart === rangeEnd ? String(rangeStart) : `${rangeStart}-${rangeEnd}`);
936 +
937 + return ranges.join(', ');
938 + }
939 +
879 function selectGrupo(grupo) { 940 function selectGrupo(grupo) {
880 selectedGrupo = grupo; 941 selectedGrupo = grupo;
881 selectedItem = null; 942 selectedItem = null;
...@@ -953,48 +1014,48 @@ ...@@ -953,48 +1014,48 @@
953 1014
954 <svelte:window onclick={handleClickOutsideEntity} /> 1015 <svelte:window onclick={handleClickOutsideEntity} />
955 1016
956 -<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;"> 1017 +<div class="min-h-screen" style="font-family: var(--font-sans); background-color: var(--theme-body); color: var(--theme-titulo);">
957 <!-- Header pedagógico --> 1018 <!-- Header pedagógico -->
958 <header class="border-b" style="border-color: var(--theme-borde); background-color: var(--theme-body);"> 1019 <header class="border-b" style="border-color: var(--theme-borde); background-color: var(--theme-body);">
959 <div class="max-w-screen-xl mx-auto px-4 sm:px-6 {viewMode === 'mapa' ? 'py-3' : 'py-6'}"> 1020 <div class="max-w-screen-xl mx-auto px-4 sm:px-6 {viewMode === 'mapa' ? 'py-3' : 'py-6'}">
960 <!-- Breadcrumb: responsive --> 1021 <!-- Breadcrumb: responsive -->
961 - <nav class="mb-4" style="font-family: 'DM Mono', monospace; font-size: 0.75rem;"> 1022 + <nav class="mb-4" style="font-family: var(--font-sans); font-variant-numeric: tabular-nums; font-size: 0.75rem;">
962 <!-- Móvil: solo padre --> 1023 <!-- Móvil: solo padre -->
963 <a href="/clasificadores" class="sm:hidden transition-colors" style="color: var(--theme-texto);"> 1024 <a href="/clasificadores" class="sm:hidden transition-colors" style="color: var(--theme-texto);">
964 ← Clasificadores 1025 ← Clasificadores
965 </a> 1026 </a>
966 <!-- Desktop: ruta completa --> 1027 <!-- Desktop: ruta completa -->
967 <div class="hidden sm:flex items-center gap-2" style="color: var(--theme-texto);"> 1028 <div class="hidden sm:flex items-center gap-2" style="color: var(--theme-texto);">
968 - <a href="/" class="transition-colors hover:opacity-80">Inicio</a> 1029 + <a href="/" data-sveltekit-preload-data="off" class="hover:opacity-100" style="opacity: 0.7;">Inicio</a>
969 <span style="opacity: 0.5;">/</span> 1030 <span style="opacity: 0.5;">/</span>
970 - <a href="/clasificadores" class="transition-colors hover:opacity-80">Clasificadores</a> 1031 + <a href="/clasificadores" data-sveltekit-preload-data="off" class="hover:opacity-100" style="opacity: 0.7;">Clasificadores</a>
971 </div> 1032 </div>
972 </nav> 1033 </nav>
973 1034
974 <div class="max-w-3xl"> 1035 <div class="max-w-3xl">
975 - <p class="text-xs uppercase tracking-widest mb-2" style="font-family: 'DM Mono', monospace; color: var(--theme-texto);"> 1036 + <p class="text-sm uppercase tracking-widest mb-2 font-semibold" style="font-family: var(--font-sans); color: var(--theme-accent);">
976 - Clasificador 03 1037 + Clasificador de Objeto del Gasto
977 </p> 1038 </p>
978 - <h1 class="{viewMode === 'mapa' ? 'text-2xl mb-2' : 'text-3xl mb-3'}" style="font-family: 'DM Serif Display', serif; color: var(--theme-titulo);"> 1039 + <h1 class="{viewMode === 'mapa' ? 'text-2xl mb-2' : 'text-3xl mb-3'}" style="font-family: var(--font-display); color: var(--theme-titulo);">
979 ¿En qué se gasta? 1040 ¿En qué se gasta?
980 </h1> 1041 </h1>
981 1042
982 {#if viewMode !== 'mapa'} 1043 {#if viewMode !== 'mapa'}
983 - <p class="leading-relaxed mb-3" style="color: var(--theme-texto);"> 1044 + <p class="text-lg leading-relaxed mb-4" style="color: var(--theme-texto);">
984 Ordena el gasto según <strong style="color: var(--theme-titulo);">qué se compra o paga</strong>: 1045 Ordena el gasto según <strong style="color: var(--theme-titulo);">qué se compra o paga</strong>:
985 sueldos, alquileres, combustible, medicamentos, construcciones, deudas, transferencias. 1046 sueldos, alquileres, combustible, medicamentos, construcciones, deudas, transferencias.
986 Es la forma más directa de entender en qué se usa el dinero público. 1047 Es la forma más directa de entender en qué se usa el dinero público.
987 </p> 1048 </p>
988 1049
989 {#if totalItems > 0} 1050 {#if totalItems > 0}
990 - <p class="text-sm mb-5" style="font-family: 'DM Mono', monospace; color: var(--theme-texto);"> 1051 + <p class="text-base mb-5" style="font-family: var(--font-sans); font-variant-numeric: tabular-nums; color: var(--theme-texto);">
991 - <span style="color: var(--theme-titulo); font-weight: 500;">{totalItems}</span> categorías de gasto: 1052 + <span style="color: var(--theme-accent); font-weight: 600;">{totalItems}</span> categorías de gasto:
992 {countByNivel.grupos} grupos, {countByNivel.subgrupos} subgrupos, {countByNivel.partidas} partidas, {countByNivel.subpartidas} subpartidas 1053 {countByNivel.grupos} grupos, {countByNivel.subgrupos} subgrupos, {countByNivel.partidas} partidas, {countByNivel.subpartidas} subpartidas
993 </p> 1054 </p>
994 {/if} 1055 {/if}
995 1056
996 <!-- Jerarquía --> 1057 <!-- Jerarquía -->
997 - <div class="hidden sm:flex flex-wrap items-center gap-2 sm:gap-3 text-xs mb-6" style="font-family: 'DM Mono', monospace;"> 1058 + <div class="hidden sm:flex flex-wrap items-center gap-2 sm:gap-3 text-sm mb-6" style="font-family: var(--font-sans); font-variant-numeric: tabular-nums;">
998 <span class="px-2.5 py-1 rounded-full font-medium" style="background-color: var(--theme-accent); color: var(--theme-body); opacity: 0.9;"> 1059 <span class="px-2.5 py-1 rounded-full font-medium" style="background-color: var(--theme-accent); color: var(--theme-body); opacity: 0.9;">
999 Grupo 1060 Grupo
1000 </span> 1061 </span>
...@@ -1012,7 +1073,7 @@ ...@@ -1012,7 +1073,7 @@
1012 </span> 1073 </span>
1013 </div> 1074 </div>
1014 <!-- Versión móvil simplificada --> 1075 <!-- Versión móvil simplificada -->
1015 - <p class="sm:hidden text-sm mb-4" style="font-family: 'DM Mono', monospace; color: var(--theme-texto);"> 1076 + <p class="sm:hidden text-sm mb-4" style="font-family: var(--font-sans); font-variant-numeric: tabular-nums; color: var(--theme-texto);">
1016 Jerarquía: Grupo → Subgrupo → Partida → Subpartida 1077 Jerarquía: Grupo → Subgrupo → Partida → Subpartida
1017 </p> 1078 </p>
1018 {/if} 1079 {/if}
...@@ -1085,12 +1146,40 @@ ...@@ -1085,12 +1146,40 @@
1085 </div> 1146 </div>
1086 </div> 1147 </div>
1087 1148
1088 - <!-- Filtros (solo en modo mapa) --> 1149 + <!-- Buscador + Filtros -->
1089 - {#if viewMode === 'mapa'}
1090 <div class="flex flex-wrap items-center gap-3 flex-shrink-0"> 1150 <div class="flex flex-wrap items-center gap-3 flex-shrink-0">
1151 + <!-- Buscador inline (estilo similar al toggle) -->
1152 + <div class="inline-flex items-center rounded-xl border-2 p-1 shadow-sm" style="border-color: var(--theme-titulo); background-color: var(--theme-surface);">
1153 + <div class="flex items-center px-3" style="color: var(--theme-texto);">
1154 + <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1155 + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
1156 + </svg>
1157 + </div>
1158 + <input
1159 + type="text"
1160 + bind:value={searchQuery}
1161 + placeholder="Buscar código o nombre..."
1162 + class="px-2 py-1.5 text-sm font-medium bg-transparent focus:outline-none w-48 sm:w-56"
1163 + style="color: var(--theme-titulo);"
1164 + />
1165 + {#if searchQuery}
1166 + <button
1167 + class="px-2 py-1.5 rounded-lg transition-all hover:bg-black/5"
1168 + onclick={() => searchQuery = ''}
1169 + style="color: var(--theme-texto);"
1170 + title="Limpiar búsqueda"
1171 + >
1172 + <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1173 + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
1174 + </svg>
1175 + </button>
1176 + {/if}
1177 + </div>
1178 +
1179 + {#if viewMode === 'mapa'}
1091 <!-- Selector de nivel --> 1180 <!-- Selector de nivel -->
1092 <div class="flex items-center gap-2 level-dropdown-container"> 1181 <div class="flex items-center gap-2 level-dropdown-container">
1093 - <label class="text-xs uppercase tracking-wide" style="font-family: 'DM Mono', monospace; color: var(--theme-texto);">Vista</label> 1182 + <label class="text-xs uppercase tracking-wide" style="font-family: var(--font-sans); font-variant-numeric: tabular-nums; color: var(--theme-texto);">Vista</label>
1094 <div class="relative"> 1183 <div class="relative">
1095 <button 1184 <button
1096 class="dropdown-control px-3 py-1.5 text-sm rounded-lg focus:outline-none min-w-[140px] text-left flex items-center justify-between gap-2" 1185 class="dropdown-control px-3 py-1.5 text-sm rounded-lg focus:outline-none min-w-[140px] text-left flex items-center justify-between gap-2"
...@@ -1119,7 +1208,7 @@ ...@@ -1119,7 +1208,7 @@
1119 </div> 1208 </div>
1120 1209
1121 <div class="flex items-center gap-2 year-dropdown-container"> 1210 <div class="flex items-center gap-2 year-dropdown-container">
1122 - <label class="text-xs uppercase tracking-wide" style="font-family: 'DM Mono', monospace; color: var(--theme-texto);">Año</label> 1211 + <label class="text-xs uppercase tracking-wide" style="font-family: var(--font-sans); font-variant-numeric: tabular-nums; color: var(--theme-texto);">Año</label>
1123 <div class="relative"> 1212 <div class="relative">
1124 <button 1213 <button
1125 class="dropdown-control px-3 py-1.5 text-sm rounded-lg focus:outline-none min-w-[80px] text-left flex items-center justify-between gap-2" 1214 class="dropdown-control px-3 py-1.5 text-sm rounded-lg focus:outline-none min-w-[80px] text-left flex items-center justify-between gap-2"
...@@ -1147,7 +1236,7 @@ ...@@ -1147,7 +1236,7 @@
1147 </div> 1236 </div>
1148 1237
1149 <div class="flex items-center gap-2 entity-dropdown-container"> 1238 <div class="flex items-center gap-2 entity-dropdown-container">
1150 - <label class="text-xs uppercase tracking-wide" style="font-family: 'DM Mono', monospace; color: var(--theme-texto);">Entidad</label> 1239 + <label class="text-xs uppercase tracking-wide" style="font-family: var(--font-sans); font-variant-numeric: tabular-nums; color: var(--theme-texto);">Entidad</label>
1151 <div class="relative"> 1240 <div class="relative">
1152 <button 1241 <button
1153 class="dropdown-control px-3 py-1.5 text-sm rounded-lg focus:outline-none min-w-[180px] text-left flex items-center justify-between gap-2" 1242 class="dropdown-control px-3 py-1.5 text-sm rounded-lg focus:outline-none min-w-[180px] text-left flex items-center justify-between gap-2"
...@@ -1210,9 +1299,9 @@ ...@@ -1210,9 +1299,9 @@
1210 {/if} 1299 {/if}
1211 </div> 1300 </div>
1212 </div> 1301 </div>
1213 - </div>
1214 {/if} 1302 {/if}
1215 </div> 1303 </div>
1304 + </div>
1216 1305
1217 <!-- Guía móvil (solo visible en móvil, no en comparar) --> 1306 <!-- Guía móvil (solo visible en móvil, no en comparar) -->
1218 {#if viewMode !== 'comparar'} 1307 {#if viewMode !== 'comparar'}
...@@ -1275,7 +1364,7 @@ ...@@ -1275,7 +1364,7 @@
1275 </button> 1364 </button>
1276 </div> 1365 </div>
1277 1366
1278 - <div class="max-w-screen-xl mx-auto flex px-4"> 1367 + <div class="max-w-screen-xl mx-auto flex px-4 lg:px-6">
1279 <!-- Sidebar izquierda: Grupos --> 1368 <!-- Sidebar izquierda: Grupos -->
1280 <!-- En móvil: overlay, en desktop: sidebar fijo --> 1369 <!-- En móvil: overlay, en desktop: sidebar fijo -->
1281 {#if sidebarOpen} 1370 {#if sidebarOpen}
...@@ -1286,7 +1375,7 @@ ...@@ -1286,7 +1375,7 @@
1286 lg:translate-x-0 1375 lg:translate-x-0
1287 fixed lg:relative 1376 fixed lg:relative
1288 inset-y-0 left-0 1377 inset-y-0 left-0
1289 - w-72 lg:w-64 1378 + w-80 lg:w-72 xl:w-80
1290 z-50 lg:z-auto 1379 z-50 lg:z-auto
1291 transition-transform duration-200 ease-in-out 1380 transition-transform duration-200 ease-in-out
1292 lg:flex-shrink-0 1381 lg:flex-shrink-0
...@@ -1304,32 +1393,21 @@ ...@@ -1304,32 +1393,21 @@
1304 </button> 1393 </button>
1305 </div> 1394 </div>
1306 1395
1307 - <!-- Buscador --> 1396 + <!-- Resultados de búsqueda (el buscador está en el header) -->
1308 - <div class="mb-6">
1309 - <input
1310 - type="text"
1311 - bind:value={searchQuery}
1312 - placeholder="Buscar..."
1313 - class="w-full px-3 py-2 text-sm rounded-md focus:outline-none focus:ring-2"
1314 - style="border: 1px solid var(--theme-borde); background-color: var(--theme-surface); color: var(--theme-titulo);"
1315 - />
1316 - </div>
1317 -
1318 - <!-- Resultados de búsqueda -->
1319 {#if isSearching} 1397 {#if isSearching}
1320 <div class="mb-4"> 1398 <div class="mb-4">
1321 - <p class="text-xs uppercase tracking-wide mb-2" style="color: var(--theme-texto);"> 1399 + <p class="text-sm uppercase tracking-wide mb-2" style="color: var(--theme-texto);">
1322 {searchResults.length} resultados 1400 {searchResults.length} resultados
1323 </p> 1401 </p>
1324 - <div class="space-y-1"> 1402 + <div class="space-y-1.5">
1325 {#each searchResults as result} 1403 {#each searchResults as result}
1326 <button 1404 <button
1327 - class="search-result-item w-full text-left px-3 py-3 text-sm rounded-lg transition-all cursor-pointer" 1405 + class="search-result-item w-full text-left px-3 py-3 text-base rounded-lg transition-all cursor-pointer"
1328 onclick={() => goToSearchResult(result)} 1406 onclick={() => goToSearchResult(result)}
1329 > 1407 >
1330 - <span class="text-xs block mb-0.5" style="color: var(--theme-texto);">{getNivelLabel(result.nivel)}</span> 1408 + <span class="text-sm block mb-0.5" style="color: var(--theme-texto);">{getNivelLabel(result.nivel)}</span>
1331 <div class="flex items-baseline gap-2"> 1409 <div class="flex items-baseline gap-2">
1332 - <span class="font-mono text-xs font-medium" style="color: var(--theme-accent);">{result.objeto}</span> 1410 + <span class="font-mono text-sm font-medium" style="color: var(--theme-accent);">{result.objeto}</span>
1333 <span class="flex-1" style="color: var(--theme-titulo);">{result.desc_objeto}</span> 1411 <span class="flex-1" style="color: var(--theme-titulo);">{result.desc_objeto}</span>
1334 <svg class="w-4 h-4 flex-shrink-0 opacity-50" fill="none" stroke="currentColor" viewBox="0 0 24 24"> 1412 <svg class="w-4 h-4 flex-shrink-0 opacity-50" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1335 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" /> 1413 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
...@@ -1345,19 +1423,19 @@ ...@@ -1345,19 +1423,19 @@
1345 {:else} 1423 {:else}
1346 <!-- Lista de grupos --> 1424 <!-- Lista de grupos -->
1347 <nav> 1425 <nav>
1348 - <p class="text-xs uppercase tracking-wide mb-3 px-2 hidden lg:block" style="color: var(--theme-texto);">Grupos</p> 1426 + <p class="text-base uppercase tracking-wide mb-4 px-2 hidden lg:block font-medium" style="color: var(--theme-texto);">Índice</p>
1349 <ul class="space-y-1"> 1427 <ul class="space-y-1">
1350 {#each grupos as grupo} 1428 {#each grupos as grupo}
1351 <li> 1429 <li>
1352 <button 1430 <button
1353 - class="w-full text-left px-3 py-2 rounded-md text-sm transition-all" 1431 + class="w-full text-left px-3 py-3 rounded-md text-base transition-all"
1354 style="{selectedGrupo?.objeto === grupo.objeto 1432 style="{selectedGrupo?.objeto === grupo.objeto
1355 - ? `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);` 1433 + ? `background-color: color-mix(in srgb, var(--theme-accent) 15%, transparent); color: var(--theme-titulo); font-weight: 500; border-left: 3px solid var(--theme-accent);`
1356 : `color: var(--theme-texto);`}" 1434 : `color: var(--theme-texto);`}"
1357 onclick={() => selectGrupo(grupo)} 1435 onclick={() => selectGrupo(grupo)}
1358 > 1436 >
1359 - <span class="font-mono text-xs block" style="color: var(--theme-texto);">{grupo.objeto}</span> 1437 + <span class="font-mono text-base block mb-0.5" style="color: var(--theme-accent);">{grupo.objeto}</span>
1360 - {grupo.desc_objeto} 1438 + <span class="leading-snug">{grupo.desc_objeto}</span>
1361 </button> 1439 </button>
1362 </li> 1440 </li>
1363 {/each} 1441 {/each}
...@@ -1373,14 +1451,15 @@ ...@@ -1373,14 +1451,15 @@
1373 {#if selectedGrupo} 1451 {#if selectedGrupo}
1374 <!-- Título del grupo --> 1452 <!-- Título del grupo -->
1375 <div class="mb-8 lg:mb-10 pb-6 lg:pb-8 border-b" style="border-color: var(--theme-borde);"> 1453 <div class="mb-8 lg:mb-10 pb-6 lg:pb-8 border-b" style="border-color: var(--theme-borde);">
1376 - <p class="text-sm font-mono mb-1" style="color: var(--theme-texto);">{selectedGrupo.objeto}</p> 1454 + <p class="text-base font-mono mb-1" style="color: var(--theme-texto);">{selectedGrupo.objeto}</p>
1377 - <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);"> 1455 + <h2 class="text-xl sm:text-2xl font-semibold mb-2 flex items-center gap-2 sm:gap-3 flex-wrap" style="color: var(--theme-titulo);">
1378 {selectedGrupo.desc_objeto} 1456 {selectedGrupo.desc_objeto}
1379 <a 1457 <a
1380 href="/objeto/{selectedGrupo.objeto}" 1458 href="/objeto/{selectedGrupo.objeto}"
1381 class="transition-colors hover:text-[var(--theme-accent)]" 1459 class="transition-colors hover:text-[var(--theme-accent)]"
1382 style="color: var(--theme-texto);" 1460 style="color: var(--theme-texto);"
1383 title="Ver detalle" 1461 title="Ver detalle"
1462 + onclick={(e) => { if (isEmbed) { e.preventDefault(); navigateToObjeto(selectedGrupo.objeto); }}}
1384 > 1463 >
1385 <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> 1464 <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1386 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /> 1465 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
...@@ -1412,14 +1491,14 @@ ...@@ -1412,14 +1491,14 @@
1412 class="scroll-mt-4" 1491 class="scroll-mt-4"
1413 style="{highlightedItem === subgrupo.objeto ? `box-shadow: 0 0 0 2px color-mix(in srgb, var(--theme-accent) 30%, transparent); border-radius: 0.5rem;` : ''}" 1492 style="{highlightedItem === subgrupo.objeto ? `box-shadow: 0 0 0 2px color-mix(in srgb, var(--theme-accent) 30%, transparent); border-radius: 0.5rem;` : ''}"
1414 > 1493 >
1415 - <div class="flex items-start gap-2 sm:gap-4 mb-3"> 1494 + <div class="flex items-start gap-3 sm:gap-4 mb-3">
1416 - <span class="font-mono text-xs sm:text-sm pt-1" style="color: var(--theme-texto);">{subgrupo.objeto}</span> 1495 + <span class="font-mono text-base pt-0.5" style="color: var(--theme-accent); font-weight: 500;">{subgrupo.objeto}</span>
1417 <div class="flex-1"> 1496 <div class="flex-1">
1418 - <h3 class="text-base sm:text-lg font-medium flex items-center gap-2 flex-wrap" style="color: var(--theme-titulo);"> 1497 + <h3 class="text-lg font-semibold flex items-center gap-2 flex-wrap" style="color: var(--theme-titulo);">
1419 <span class="text-left">{subgrupo.desc_objeto}</span> 1498 <span class="text-left">{subgrupo.desc_objeto}</span>
1420 {#if subgrupo.n_variaciones > 1} 1499 {#if subgrupo.n_variaciones > 1}
1421 <button 1500 <button
1422 - class="text-xs font-normal text-orange-500 hover:text-orange-700 transition-colors underline decoration-dotted decoration-orange-300 hover:decoration-orange-500 cursor-pointer" 1501 + class="text-sm font-normal text-orange-500 hover:text-orange-700 transition-colors underline decoration-dotted decoration-orange-300 hover:decoration-orange-500 cursor-pointer"
1423 onclick={() => openDetail(subgrupo)} 1502 onclick={() => openDetail(subgrupo)}
1424 title="Ver variaciones de descripción" 1503 title="Ver variaciones de descripción"
1425 > 1504 >
...@@ -1431,6 +1510,7 @@ ...@@ -1431,6 +1510,7 @@
1431 class="transition-colors hover:text-[var(--theme-accent)]" 1510 class="transition-colors hover:text-[var(--theme-accent)]"
1432 style="color: var(--theme-texto);" 1511 style="color: var(--theme-texto);"
1433 title="Ver página de detalle" 1512 title="Ver página de detalle"
1513 + onclick={(e) => { if (isEmbed) { e.preventDefault(); navigateToObjeto(subgrupo.objeto); }}}
1434 > 1514 >
1435 <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> 1515 <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1436 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /> 1516 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
...@@ -1438,7 +1518,7 @@ ...@@ -1438,7 +1518,7 @@
1438 </a> 1518 </a>
1439 </h3> 1519 </h3>
1440 {#if subgrupoDescs.length > 0} 1520 {#if subgrupoDescs.length > 0}
1441 - <p class="text-sm mt-1 leading-relaxed" style="color: var(--theme-texto);">{subgrupoDescs[0].descripcion}</p> 1521 + <p class="text-base mt-1 leading-relaxed" style="color: var(--theme-texto);">{subgrupoDescs[0].descripcion}</p>
1442 {/if} 1522 {/if}
1443 </div> 1523 </div>
1444 </div> 1524 </div>
...@@ -1453,10 +1533,10 @@ ...@@ -1453,10 +1533,10 @@
1453 class="scroll-mt-4" 1533 class="scroll-mt-4"
1454 style="{highlightedItem === partida.objeto ? `box-shadow: 0 0 0 2px color-mix(in srgb, var(--theme-accent) 30%, transparent); border-radius: 0.5rem; padding: 0.5rem; margin-left: -0.5rem;` : ''}" 1534 style="{highlightedItem === partida.objeto ? `box-shadow: 0 0 0 2px color-mix(in srgb, var(--theme-accent) 30%, transparent); border-radius: 0.5rem; padding: 0.5rem; margin-left: -0.5rem;` : ''}"
1455 > 1535 >
1456 - <div class="flex items-start gap-2 sm:gap-3"> 1536 + <div class="flex items-start gap-3 sm:gap-4">
1457 - <span class="font-mono text-xs pt-0.5 shrink-0" style="color: var(--theme-texto); {partida._sintetica ? 'opacity: 0.5;' : ''}">{partida.objeto}</span> 1537 + <span class="font-mono text-base pt-0.5 shrink-0" style="color: var(--theme-texto); {partida._sintetica ? 'opacity: 0.5;' : ''}">{partida.objeto}</span>
1458 <div class="flex-1 min-w-0"> 1538 <div class="flex-1 min-w-0">
1459 - <h4 class="text-sm font-medium flex items-center gap-2 flex-wrap" style="color: var(--theme-titulo);"> 1539 + <h4 class="text-base font-medium flex items-center gap-2 flex-wrap" style="color: var(--theme-titulo);">
1460 {#if partida._sintetica} 1540 {#if partida._sintetica}
1461 <span class="text-left opacity-60 italic">(Partida inferida)</span> 1541 <span class="text-left opacity-60 italic">(Partida inferida)</span>
1462 {:else} 1542 {:else}
...@@ -1464,7 +1544,7 @@ ...@@ -1464,7 +1544,7 @@
1464 {/if} 1544 {/if}
1465 {#if partida.n_variaciones > 1} 1545 {#if partida.n_variaciones > 1}
1466 <button 1546 <button
1467 - class="text-xs font-normal text-orange-500 hover:text-orange-700 transition-colors underline decoration-dotted decoration-orange-300 hover:decoration-orange-500 cursor-pointer" 1547 + class="text-sm font-normal text-orange-500 hover:text-orange-700 transition-colors underline decoration-dotted decoration-orange-300 hover:decoration-orange-500 cursor-pointer"
1468 onclick={() => openDetail(partida)} 1548 onclick={() => openDetail(partida)}
1469 title="Ver variaciones de descripción" 1549 title="Ver variaciones de descripción"
1470 > 1550 >
...@@ -1477,15 +1557,16 @@ ...@@ -1477,15 +1557,16 @@
1477 class="transition-colors hover:text-[var(--theme-accent)]" 1557 class="transition-colors hover:text-[var(--theme-accent)]"
1478 style="color: var(--theme-texto);" 1558 style="color: var(--theme-texto);"
1479 title="Ver página de detalle" 1559 title="Ver página de detalle"
1560 + onclick={(e) => { if (isEmbed) { e.preventDefault(); navigateToObjeto(partida.objeto); }}}
1480 > 1561 >
1481 - <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> 1562 + <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1482 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /> 1563 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
1483 </svg> 1564 </svg>
1484 </a> 1565 </a>
1485 {/if} 1566 {/if}
1486 </h4> 1567 </h4>
1487 {#if partidaDescs.length > 0 && !partida._sintetica} 1568 {#if partidaDescs.length > 0 && !partida._sintetica}
1488 - <p class="text-sm mt-1 leading-relaxed" style="color: var(--theme-texto);">{partidaDescs[0].descripcion}</p> 1569 + <p class="text-base mt-1 leading-relaxed" style="color: var(--theme-texto);">{partidaDescs[0].descripcion}</p>
1489 {/if} 1570 {/if}
1490 </div> 1571 </div>
1491 </div> 1572 </div>
...@@ -1500,14 +1581,14 @@ ...@@ -1500,14 +1581,14 @@
1500 class="scroll-mt-4" 1581 class="scroll-mt-4"
1501 style="{highlightedItem === subpartida.objeto ? `box-shadow: 0 0 0 2px color-mix(in srgb, var(--theme-accent) 30%, transparent); border-radius: 0.25rem; padding: 0.25rem; margin-left: -0.25rem;` : ''}" 1582 style="{highlightedItem === subpartida.objeto ? `box-shadow: 0 0 0 2px color-mix(in srgb, var(--theme-accent) 30%, transparent); border-radius: 0.25rem; padding: 0.25rem; margin-left: -0.25rem;` : ''}"
1502 > 1583 >
1503 - <div class="flex items-start gap-2"> 1584 + <div class="flex items-start gap-3">
1504 - <span class="font-mono text-xs" style="color: var(--theme-texto);">{subpartida.objeto}</span> 1585 + <span class="font-mono text-base" style="color: var(--theme-texto);">{subpartida.objeto}</span>
1505 <div class="flex-1"> 1586 <div class="flex-1">
1506 <span class="flex items-center gap-2"> 1587 <span class="flex items-center gap-2">
1507 - <span class="text-sm" style="color: var(--theme-titulo);">{subpartida.desc_objeto}</span> 1588 + <span class="text-base" style="color: var(--theme-titulo);">{subpartida.desc_objeto}</span>
1508 {#if subpartida.n_variaciones > 1} 1589 {#if subpartida.n_variaciones > 1}
1509 <button 1590 <button
1510 - class="text-xs text-orange-500 hover:text-orange-700 transition-colors underline decoration-dotted decoration-orange-300 hover:decoration-orange-500 cursor-pointer" 1591 + class="text-sm text-orange-500 hover:text-orange-700 transition-colors underline decoration-dotted decoration-orange-300 hover:decoration-orange-500 cursor-pointer"
1511 onclick={() => openDetail(subpartida)} 1592 onclick={() => openDetail(subpartida)}
1512 title="Ver variaciones de descripción" 1593 title="Ver variaciones de descripción"
1513 > 1594 >
...@@ -1519,14 +1600,15 @@ ...@@ -1519,14 +1600,15 @@
1519 class="transition-colors hover:text-[var(--theme-accent)]" 1600 class="transition-colors hover:text-[var(--theme-accent)]"
1520 style="color: var(--theme-texto);" 1601 style="color: var(--theme-texto);"
1521 title="Ver página de detalle" 1602 title="Ver página de detalle"
1603 + onclick={(e) => { if (isEmbed) { e.preventDefault(); navigateToObjeto(subpartida.objeto); }}}
1522 > 1604 >
1523 - <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"> 1605 + <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1524 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /> 1606 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
1525 </svg> 1607 </svg>
1526 </a> 1608 </a>
1527 </span> 1609 </span>
1528 {#if subpartidaDescs.length > 0} 1610 {#if subpartidaDescs.length > 0}
1529 - <p class="text-sm mt-1" style="color: var(--theme-texto);">{subpartidaDescs[0].descripcion}</p> 1611 + <p class="text-base mt-1" style="color: var(--theme-texto);">{subpartidaDescs[0].descripcion}</p>
1530 {/if} 1612 {/if}
1531 </div> 1613 </div>
1532 </div> 1614 </div>
...@@ -1545,36 +1627,36 @@ ...@@ -1545,36 +1627,36 @@
1545 </div> 1627 </div>
1546 </main> 1628 </main>
1547 1629
1548 - <!-- Sidebar derecha: En esta página --> 1630 + <!-- Sidebar derecha: Navegación rápida -->
1549 - <aside class="w-48 flex-shrink-0 hidden xl:block"> 1631 + <aside class="w-56 flex-shrink-0 hidden xl:block">
1550 - <div class="sticky top-0 h-screen overflow-y-auto py-8 pl-8 border-l" style="border-color: var(--theme-borde);"> 1632 + <div class="sticky top-0 h-screen overflow-y-auto py-8 pl-6 border-l" style="border-color: var(--theme-borde);">
1551 - <p class="text-xs uppercase tracking-wide mb-3" style="color: var(--theme-texto);">En esta página</p> 1633 + <p class="text-sm uppercase tracking-wide mb-4 font-medium" style="color: var(--theme-texto);">Navegación</p>
1552 {#if selectedGrupo && !isSearching} 1634 {#if selectedGrupo && !isSearching}
1553 - <nav class="space-y-2"> 1635 + <nav class="space-y-3">
1554 {#each grupoContent.subgrupos as subgrupo} 1636 {#each grupoContent.subgrupos as subgrupo}
1555 <div> 1637 <div>
1556 <a 1638 <a
1557 href="#sg-{subgrupo.objeto}" 1639 href="#sg-{subgrupo.objeto}"
1558 - class="block text-sm truncate transition-colors hover:opacity-80" 1640 + class="block text-base truncate transition-colors hover:text-[var(--theme-accent)]"
1559 style="color: var(--theme-titulo);" 1641 style="color: var(--theme-titulo);"
1560 title="{subgrupo.desc_objeto}" 1642 title="{subgrupo.desc_objeto}"
1561 > 1643 >
1562 {subgrupo.desc_objeto} 1644 {subgrupo.desc_objeto}
1563 </a> 1645 </a>
1564 {#if subgrupo.partidas?.length > 0} 1646 {#if subgrupo.partidas?.length > 0}
1565 - <div class="ml-3 mt-1 space-y-1 border-l pl-2" style="border-color: color-mix(in srgb, var(--theme-borde) 50%, transparent);"> 1647 + <div class="ml-3 mt-2 space-y-1.5 border-l pl-3" style="border-color: color-mix(in srgb, var(--theme-borde) 50%, transparent);">
1566 - {#each subgrupo.partidas.slice(0, 5) as partida} 1648 + {#each subgrupo.partidas.slice(0, 4) as partida}
1567 <a 1649 <a
1568 href="#p-{partida.objeto}" 1650 href="#p-{partida.objeto}"
1569 - class="block text-xs truncate transition-colors hover:opacity-80" 1651 + class="block text-sm truncate transition-colors hover:text-[var(--theme-accent)]"
1570 style="color: var(--theme-texto);" 1652 style="color: var(--theme-texto);"
1571 title="{partida.desc_objeto}" 1653 title="{partida.desc_objeto}"
1572 > 1654 >
1573 {partida.desc_objeto} 1655 {partida.desc_objeto}
1574 </a> 1656 </a>
1575 {/each} 1657 {/each}
1576 - {#if subgrupo.partidas.length > 5} 1658 + {#if subgrupo.partidas.length > 4}
1577 - <span class="text-xs" style="color: var(--theme-texto);">+{subgrupo.partidas.length - 5} más</span> 1659 + <span class="text-sm" style="color: var(--theme-texto); opacity: 0.7;">+{subgrupo.partidas.length - 4} más</span>
1578 {/if} 1660 {/if}
1579 </div> 1661 </div>
1580 {/if} 1662 {/if}
...@@ -1597,7 +1679,7 @@ ...@@ -1597,7 +1679,7 @@
1597 <div class="flex items-center justify-between mb-2"> 1679 <div class="flex items-center justify-between mb-2">
1598 <!-- Breadcrumb de navegación (solo en modo jerárquico) --> 1680 <!-- Breadcrumb de navegación (solo en modo jerárquico) -->
1599 {#if treemapViewLevel === 'jerarquico' && treemapBreadcrumb.length > 0} 1681 {#if treemapViewLevel === 'jerarquico' && treemapBreadcrumb.length > 0}
1600 - <nav class="flex items-center gap-2 flex-wrap" style="font-family: 'DM Mono', monospace; font-size: 0.75rem;"> 1682 + <nav class="flex items-center gap-2 flex-wrap" style="font-family: var(--font-sans); font-variant-numeric: tabular-nums; font-size: 0.75rem;">
1601 {#each treemapBreadcrumb as crumb, i} 1683 {#each treemapBreadcrumb as crumb, i}
1602 {#if i > 0} 1684 {#if i > 0}
1603 <span style="color: var(--theme-texto); opacity: 0.5;">›</span> 1685 <span style="color: var(--theme-texto); opacity: 0.5;">›</span>
...@@ -1615,7 +1697,7 @@ ...@@ -1615,7 +1697,7 @@
1615 </nav> 1697 </nav>
1616 {:else if treemapViewLevel !== 'jerarquico'} 1698 {:else if treemapViewLevel !== 'jerarquico'}
1617 <!-- Info de nivel aplanado --> 1699 <!-- Info de nivel aplanado -->
1618 - <div class="flex items-center gap-3" style="font-family: 'DM Mono', monospace; font-size: 0.75rem;"> 1700 + <div class="flex items-center gap-3" style="font-family: var(--font-sans); font-variant-numeric: tabular-nums; font-size: 0.75rem;">
1619 <span class="px-2 py-1 rounded" style="background-color: var(--theme-fill); color: var(--theme-titulo);"> 1701 <span class="px-2 py-1 rounded" style="background-color: var(--theme-fill); color: var(--theme-titulo);">
1620 {NIVEL_OPTIONS.find(o => o.value === treemapViewLevel)?.label || treemapViewLevel} 1702 {NIVEL_OPTIONS.find(o => o.value === treemapViewLevel)?.label || treemapViewLevel}
1621 </span> 1703 </span>
...@@ -1683,7 +1765,7 @@ ...@@ -1683,7 +1765,7 @@
1683 1765
1684 {#if width > 60 && height > 40} 1766 {#if width > 60 && height > 40}
1685 <foreignObject x="6" y="6" width={width - 12} height={height - 12}> 1767 <foreignObject x="6" y="6" width={width - 12} height={height - 12}>
1686 - <div class="h-full flex flex-col justify-between overflow-hidden" style="font-family: 'Instrument Sans', sans-serif;"> 1768 + <div class="h-full flex flex-col justify-between overflow-hidden" style="font-family: var(--font-sans);">
1687 <div class="font-medium leading-tight" style="font-size: {Math.min(14, Math.max(10, width / 12))}px; color: {textColor};"> 1769 <div class="font-medium leading-tight" style="font-size: {Math.min(14, Math.max(10, width / 12))}px; color: {textColor};">
1688 {#if width > 100} 1770 {#if width > 100}
1689 {label} 1771 {label}
...@@ -1692,7 +1774,7 @@ ...@@ -1692,7 +1774,7 @@
1692 {/if} 1774 {/if}
1693 </div> 1775 </div>
1694 {#if height > 55} 1776 {#if height > 55}
1695 - <div style="font-family: 'DM Mono', monospace; font-size: {Math.min(12, Math.max(9, width / 14))}px; color: {textColorSecondary};"> 1777 + <div style="font-family: var(--font-sans); font-variant-numeric: tabular-nums; font-size: {Math.min(12, Math.max(9, width / 14))}px; color: {textColorSecondary};">
1696 {formatMoney(node.value)} 1778 {formatMoney(node.value)}
1697 {#if width > 90 && height > 70} 1779 {#if width > 90 && height > 70}
1698 <span style="opacity: 0.7;"> · {formatPerCapita(node.value)}</span> 1780 <span style="opacity: 0.7;"> · {formatPerCapita(node.value)}</span>
...@@ -1753,7 +1835,7 @@ ...@@ -1753,7 +1835,7 @@
1753 fill={getNodeColor(grupoId, 1)} 1835 fill={getNodeColor(grupoId, 1)}
1754 font-size="10" 1836 font-size="10"
1755 font-weight="600" 1837 font-weight="600"
1756 - style="font-family: 'DM Mono', monospace; text-transform: uppercase; letter-spacing: 0.5px;" 1838 + style="font-family: var(--font-sans); font-variant-numeric: tabular-nums; text-transform: uppercase; letter-spacing: 0.5px;"
1757 > 1839 >
1758 {#if width > 180} 1840 {#if width > 180}
1759 {node.name} 1841 {node.name}
...@@ -1787,7 +1869,7 @@ ...@@ -1787,7 +1869,7 @@
1787 1869
1788 {#if width > 40 && height > 25} 1870 {#if width > 40 && height > 25}
1789 <foreignObject x="4" y="4" width={width - 8} height={height - 8}> 1871 <foreignObject x="4" y="4" width={width - 8} height={height - 8}>
1790 - <div class="h-full flex flex-col justify-between overflow-hidden" style="font-family: 'Instrument Sans', sans-serif;"> 1872 + <div class="h-full flex flex-col justify-between overflow-hidden" style="font-family: var(--font-sans);">
1791 <div class="font-medium leading-tight" style="font-size: {Math.min(12, Math.max(9, width / 14))}px; color: {textColor};"> 1873 <div class="font-medium leading-tight" style="font-size: {Math.min(12, Math.max(9, width / 14))}px; color: {textColor};">
1792 {#if width > 80} 1874 {#if width > 80}
1793 {(node.name || node.id).slice(0, Math.floor(width / 6))}{(node.name || node.id).length > Math.floor(width / 6) ? '…' : ''} 1875 {(node.name || node.id).slice(0, Math.floor(width / 6))}{(node.name || node.id).length > Math.floor(width / 6) ? '…' : ''}
...@@ -1796,7 +1878,7 @@ ...@@ -1796,7 +1878,7 @@
1796 {/if} 1878 {/if}
1797 </div> 1879 </div>
1798 {#if height > 40 && width > 50} 1880 {#if height > 40 && width > 50}
1799 - <div style="font-family: 'DM Mono', monospace; font-size: {Math.min(10, Math.max(8, width / 16))}px; color: {textColorSecondary};"> 1881 + <div style="font-family: var(--font-sans); font-variant-numeric: tabular-nums; font-size: {Math.min(10, Math.max(8, width / 16))}px; color: {textColorSecondary};">
1800 {formatMoney(node.value)} 1882 {formatMoney(node.value)}
1801 {#if width > 80 && height > 55} 1883 {#if width > 80 && height > 55}
1802 <span style="opacity: 0.7;"> · {formatPerCapita(node.value)}</span> 1884 <span style="opacity: 0.7;"> · {formatPerCapita(node.value)}</span>
...@@ -1822,31 +1904,31 @@ ...@@ -1822,31 +1904,31 @@
1822 top: {Math.min(hoveredNode.y0 + 10, treemapHeight - 100)}px; 1904 top: {Math.min(hoveredNode.y0 + 10, treemapHeight - 100)}px;
1823 background-color: var(--theme-titulo); 1905 background-color: var(--theme-titulo);
1824 color: var(--theme-body); 1906 color: var(--theme-body);
1825 - font-family: 'Instrument Sans', sans-serif; 1907 + font-family: var(--font-sans);
1826 " 1908 "
1827 > 1909 >
1828 {#if treemapViewLevel !== 'jerarquico'} 1910 {#if treemapViewLevel !== 'jerarquico'}
1829 <!-- Vista aplanada: mostrar contexto del grupo --> 1911 <!-- Vista aplanada: mostrar contexto del grupo -->
1830 - <div class="text-xs opacity-60 mb-1" style="font-family: 'DM Mono', monospace;"> 1912 + <div class="text-xs opacity-60 mb-1" style="font-family: var(--font-sans); font-variant-numeric: tabular-nums;">
1831 {hoveredNode.id} 1913 {hoveredNode.id}
1832 </div> 1914 </div>
1833 <div class="font-medium text-sm">{hoveredNode.name || hoveredNode.id}</div> 1915 <div class="font-medium text-sm">{hoveredNode.name || hoveredNode.id}</div>
1834 - <div class="text-xs opacity-80 mt-1" style="font-family: 'DM Mono', monospace;"> 1916 + <div class="text-xs opacity-80 mt-1" style="font-family: var(--font-sans); font-variant-numeric: tabular-nums;">
1835 {formatMoney(hoveredNode.value)} · {((hoveredNode.value / totalValue) * 100).toFixed(1)}% 1917 {formatMoney(hoveredNode.value)} · {((hoveredNode.value / totalValue) * 100).toFixed(1)}%
1836 </div> 1918 </div>
1837 - <div class="text-xs opacity-60 mt-0.5" style="font-family: 'DM Mono', monospace;"> 1919 + <div class="text-xs opacity-60 mt-0.5" style="font-family: var(--font-sans); font-variant-numeric: tabular-nums;">
1838 {formatPerCapita(hoveredNode.value)} 1920 {formatPerCapita(hoveredNode.value)}
1839 </div> 1921 </div>
1840 {:else} 1922 {:else}
1841 <!-- Vista jerárquica --> 1923 <!-- Vista jerárquica -->
1842 <div class="font-medium text-sm">{hoveredNode.data?.desc_objeto || hoveredNode.name || hoveredNode.id}</div> 1924 <div class="font-medium text-sm">{hoveredNode.data?.desc_objeto || hoveredNode.name || hoveredNode.id}</div>
1843 - <div class="text-xs opacity-80 mt-1" style="font-family: 'DM Mono', monospace;"> 1925 + <div class="text-xs opacity-80 mt-1" style="font-family: var(--font-sans); font-variant-numeric: tabular-nums;">
1844 {formatMoney(hoveredNode.value)} 1926 {formatMoney(hoveredNode.value)}
1845 {#if currentTreemapNode && currentTreemapNode.value} 1927 {#if currentTreemapNode && currentTreemapNode.value}
1846 · {((hoveredNode.value / currentTreemapNode.value) * 100).toFixed(1)}% 1928 · {((hoveredNode.value / currentTreemapNode.value) * 100).toFixed(1)}%
1847 {/if} 1929 {/if}
1848 </div> 1930 </div>
1849 - <div class="text-xs opacity-60 mt-0.5" style="font-family: 'DM Mono', monospace;"> 1931 + <div class="text-xs opacity-60 mt-0.5" style="font-family: var(--font-sans); font-variant-numeric: tabular-nums;">
1850 {formatPerCapita(hoveredNode.value)} 1932 {formatPerCapita(hoveredNode.value)}
1851 </div> 1933 </div>
1852 {#if hoveredNode._originalNode?.children && hoveredNode._originalNode.children.length > 0} 1934 {#if hoveredNode._originalNode?.children && hoveredNode._originalNode.children.length > 0}
...@@ -2070,7 +2152,7 @@ ...@@ -2070,7 +2152,7 @@
2070 2152
2071 <!-- Resumen de diferencias (placeholder) --> 2153 <!-- Resumen de diferencias (placeholder) -->
2072 <div class="border rounded-xl p-6" style="background-color: var(--theme-fill); border-color: var(--theme-borde);"> 2154 <div class="border rounded-xl p-6" style="background-color: var(--theme-fill); border-color: var(--theme-borde);">
2073 - <h3 class="text-sm font-medium uppercase tracking-wide mb-4" style="font-family: 'DM Mono', monospace; color: var(--theme-titulo);"> 2155 + <h3 class="text-sm font-medium uppercase tracking-wide mb-4" style="font-family: var(--font-sans); font-variant-numeric: tabular-nums; color: var(--theme-titulo);">
2074 Diferencias principales 2156 Diferencias principales
2075 </h3> 2157 </h3>
2076 <div class="grid sm:grid-cols-3 gap-4 text-center"> 2158 <div class="grid sm:grid-cols-3 gap-4 text-center">
...@@ -2136,7 +2218,7 @@ ...@@ -2136,7 +2218,7 @@
2136 <span class="font-medium" style="color: var(--theme-accent);">Vigente</span> 2218 <span class="font-medium" style="color: var(--theme-accent);">Vigente</span>
2137 <span class="mx-1" style="opacity: 0.5;">·</span> 2219 <span class="mx-1" style="opacity: 0.5;">·</span>
2138 {/if} 2220 {/if}
2139 - <span class="font-mono">{desc.rangos}</span> 2221 + <span class="font-mono">{formatYearsAsRanges(desc.rangos)}</span>
2140 </p> 2222 </p>
2141 <p class="text-base leading-relaxed" style="color: var(--theme-titulo);">{desc.descripcion}</p> 2223 <p class="text-base leading-relaxed" style="color: var(--theme-titulo);">{desc.descripcion}</p>
2142 </div> 2224 </div>
...@@ -2262,28 +2344,28 @@ ...@@ -2262,28 +2344,28 @@
2262 box-shadow: 0 0 0 2px var(--theme-accent); 2344 box-shadow: 0 0 0 2px var(--theme-accent);
2263 } 2345 }
2264 2346
2265 - /* Tema claro: transparente + ring sutil */ 2347 + /* Tema claro: fondo blanco con sombra sutil */
2266 :global(.light) .dropdown-control, 2348 :global(.light) .dropdown-control,
2267 :global(html:not(.dark)) .dropdown-control { 2349 :global(html:not(.dark)) .dropdown-control {
2268 - background: transparent; 2350 + background: #ffffff;
2269 border: none; 2351 border: none;
2270 - box-shadow: inset 0 0 0 1px rgba(0,0,0,0.08); 2352 + box-shadow: 0 1px 3px rgba(28,28,26,0.06), inset 0 0 0 1px rgba(28,28,26,0.06);
2271 } 2353 }
2272 :global(.light) .dropdown-control:focus, 2354 :global(.light) .dropdown-control:focus,
2273 :global(html:not(.dark)) .dropdown-control:focus { 2355 :global(html:not(.dark)) .dropdown-control:focus {
2274 - box-shadow: inset 0 0 0 1px rgba(0,0,0,0.15); 2356 + box-shadow: 0 1px 3px rgba(28,28,26,0.08), inset 0 0 0 1px rgba(28,28,26,0.12);
2275 } 2357 }
2276 :global(.light) .dropdown-control:hover, 2358 :global(.light) .dropdown-control:hover,
2277 :global(html:not(.dark)) .dropdown-control:hover { 2359 :global(html:not(.dark)) .dropdown-control:hover {
2278 - box-shadow: inset 0 0 0 1px rgba(0,0,0,0.12); 2360 + box-shadow: 0 2px 6px rgba(28,28,26,0.08), inset 0 0 0 1px rgba(28,28,26,0.08);
2279 } 2361 }
2280 2362
2281 /* Dropdown panels en tema claro */ 2363 /* Dropdown panels en tema claro */
2282 :global(.light) .dropdown-panel, 2364 :global(.light) .dropdown-panel,
2283 :global(html:not(.dark)) .dropdown-panel { 2365 :global(html:not(.dark)) .dropdown-panel {
2284 - background: #FAFAF8 !important; 2366 + background: #ffffff !important;
2285 border: none !important; 2367 border: none !important;
2286 - box-shadow: 0 4px 20px rgba(0,0,0,0.08), inset 0 0 0 1px rgba(0,0,0,0.08) !important; 2368 + box-shadow: 0 4px 24px rgba(28,28,26,0.12), inset 0 0 0 1px rgba(28,28,26,0.06) !important;
2287 } 2369 }
2288 2370
2289 /* Sidebar izquierdo: con fondo en móvil, transparente en desktop */ 2371 /* Sidebar izquierdo: con fondo en móvil, transparente en desktop */
...@@ -2329,13 +2411,13 @@ ...@@ -2329,13 +2411,13 @@
2329 /* Tema claro */ 2411 /* Tema claro */
2330 :global(.light) .search-result-item, 2412 :global(.light) .search-result-item,
2331 :global(html:not(.dark)) .search-result-item { 2413 :global(html:not(.dark)) .search-result-item {
2332 - background: transparent; 2414 + background: #ffffff;
2333 border: none; 2415 border: none;
2334 - box-shadow: inset 0 0 0 1px rgba(0,0,0,0.06); 2416 + box-shadow: 0 1px 3px rgba(28,28,26,0.04), inset 0 0 0 1px rgba(28,28,26,0.05);
2335 } 2417 }
2336 :global(.light) .search-result-item:hover, 2418 :global(.light) .search-result-item:hover,
2337 :global(html:not(.dark)) .search-result-item:hover { 2419 :global(html:not(.dark)) .search-result-item:hover {
2338 - background: rgba(0,0,0,0.03); 2420 + background: #ffffff;
2339 - box-shadow: inset 0 0 0 1px var(--theme-accent); 2421 + box-shadow: 0 2px 8px rgba(28,28,26,0.08), inset 0 0 0 1px var(--theme-accent);
2340 } 2422 }
2341 </style> 2423 </style>
......
...@@ -2,13 +2,16 @@ import { supabase } from '$lib/supabase'; ...@@ -2,13 +2,16 @@ import { supabase } from '$lib/supabase';
2 import { error } from '@sveltejs/kit'; 2 import { error } from '@sveltejs/kit';
3 3
4 export async function load({ params }) { 4 export async function load({ params }) {
5 + console.time('[SERVER] Total load objeto');
5 const { codigo } = params; 6 const { codigo } = params;
6 7
8 + console.time('[SERVER] Query clas_objetos');
7 const { data, error: dbError } = await supabase 9 const { data, error: dbError } = await supabase
8 .schema('ppto') 10 .schema('ppto')
9 .from('clas_objetos') 11 .from('clas_objetos')
10 .select('*') 12 .select('*')
11 .eq('objeto', codigo); 13 .eq('objeto', codigo);
14 + console.timeEnd('[SERVER] Query clas_objetos');
12 15
13 if (dbError || !data || data.length === 0) { 16 if (dbError || !data || data.length === 0) {
14 throw error(404, 'Objeto no encontrado'); 17 throw error(404, 'Objeto no encontrado');
...@@ -21,6 +24,7 @@ export async function load({ params }) { ...@@ -21,6 +24,7 @@ export async function load({ params }) {
21 let padres = []; 24 let padres = [];
22 let hijos = []; 25 let hijos = [];
23 26
27 + console.time('[SERVER] Query padres');
24 // Buscar padre según nivel 28 // Buscar padre según nivel
25 if (objeto.nivel === 'subpartida') { 29 if (objeto.nivel === 'subpartida') {
26 // Padre es partida 30 // Padre es partida
...@@ -58,7 +62,9 @@ export async function load({ params }) { ...@@ -58,7 +62,9 @@ export async function load({ params }) {
58 .eq('objeto', grupoCodigo); 62 .eq('objeto', grupoCodigo);
59 if (padreData?.length) padres.unshift(padreData[0]); 63 if (padreData?.length) padres.unshift(padreData[0]);
60 } 64 }
65 + console.timeEnd('[SERVER] Query padres');
61 66
67 + console.time('[SERVER] Query hijos');
62 // Buscar hijos según nivel 68 // Buscar hijos según nivel
63 if (objeto.nivel === 'grupo') { 69 if (objeto.nivel === 'grupo') {
64 const { data: hijosData } = await supabase 70 const { data: hijosData } = await supabase
...@@ -100,7 +106,9 @@ export async function load({ params }) { ...@@ -100,7 +106,9 @@ export async function load({ params }) {
100 return acc; 106 return acc;
101 }, []) || []; 107 }, []) || [];
102 } 108 }
109 + console.timeEnd('[SERVER] Query hijos');
103 110
111 + console.timeEnd('[SERVER] Total load objeto');
104 return { 112 return {
105 objeto, 113 objeto,
106 padres, 114 padres,
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
3 import { tweened } from 'svelte/motion'; 3 import { tweened } from 'svelte/motion';
4 import { cubicOut } from 'svelte/easing'; 4 import { cubicOut } from 'svelte/easing';
5 import { supabase } from '$lib/supabase'; 5 import { supabase } from '$lib/supabase';
6 + import { goto } from '$app/navigation';
6 import Spinner from '$lib/components/ui/Spinner.svelte'; 7 import Spinner from '$lib/components/ui/Spinner.svelte';
7 8
8 // Datos del servidor (reactivos) 9 // Datos del servidor (reactivos)
...@@ -47,6 +48,35 @@ ...@@ -47,6 +48,35 @@
47 return Math.max(...years.map(y => parseInt(y))); 48 return Math.max(...years.map(y => parseInt(y)));
48 } 49 }
49 50
51 + /**
52 + * Converts a string with years into compact ranges
53 + * Example: "2006, 2007, 2008, 2009, 2015, 2016" → "2006-2009, 2015-2016"
54 + */
55 + function formatYearsAsRanges(rangosStr) {
56 + if (!rangosStr) return '';
57 + const yearsMatch = rangosStr.match(/\d{4}/g);
58 + if (!yearsMatch || yearsMatch.length === 0) return rangosStr;
59 +
60 + const years = [...new Set(yearsMatch.map(y => parseInt(y)))].sort((a, b) => a - b);
61 + if (years.length === 1) return String(years[0]);
62 +
63 + const ranges = [];
64 + let rangeStart = years[0];
65 + let rangeEnd = years[0];
66 +
67 + for (let i = 1; i < years.length; i++) {
68 + if (years[i] === rangeEnd + 1) {
69 + rangeEnd = years[i];
70 + } else {
71 + ranges.push(rangeStart === rangeEnd ? String(rangeStart) : `${rangeStart}-${rangeEnd}`);
72 + rangeStart = years[i];
73 + rangeEnd = years[i];
74 + }
75 + }
76 + ranges.push(rangeStart === rangeEnd ? String(rangeStart) : `${rangeStart}-${rangeEnd}`);
77 + return ranges.join(', ');
78 + }
79 +
50 function getNivelLabel(nivel) { 80 function getNivelLabel(nivel) {
51 const labels = { grupo: 'Grupo', subgrupo: 'Subgrupo', partida: 'Partida', subpartida: 'Subpartida' }; 81 const labels = { grupo: 'Grupo', subgrupo: 'Subgrupo', partida: 'Partida', subpartida: 'Subpartida' };
52 return labels[nivel] || nivel; 82 return labels[nivel] || nivel;
...@@ -123,6 +153,31 @@ ...@@ -123,6 +153,31 @@
123 } 153 }
124 154
125 // ══════════════════════════════════════════════════════════════ 155 // ══════════════════════════════════════════════════════════════
156 + // SPLIT VIEW - CLASIFICADOR LADO A LADO
157 + // ══════════════════════════════════════════════════════════════
158 + let splitOpen = $state(false);
159 + let splitWidth = $state(45); // porcentaje del ancho para el clasificador
160 + let isResizing = $state(false);
161 +
162 + function startResize(e) {
163 + isResizing = true;
164 + document.body.style.cursor = 'col-resize';
165 + document.body.style.userSelect = 'none';
166 + }
167 +
168 + function handleResize(e) {
169 + if (!isResizing) return;
170 + const newWidth = (e.clientX / window.innerWidth) * 100;
171 + splitWidth = Math.max(25, Math.min(65, newWidth)); // entre 25% y 65%
172 + }
173 +
174 + function stopResize() {
175 + isResizing = false;
176 + document.body.style.cursor = '';
177 + document.body.style.userSelect = '';
178 + }
179 +
180 + // ══════════════════════════════════════════════════════════════
126 // DATOS DE SUPABASE 181 // DATOS DE SUPABASE
127 // ══════════════════════════════════════════════════════════════ 182 // ══════════════════════════════════════════════════════════════
128 let datosAnuales = $state([]); // Datos por año 183 let datosAnuales = $state([]); // Datos por año
...@@ -131,10 +186,14 @@ ...@@ -131,10 +186,14 @@
131 186
132 // Cargar datos según selección 187 // Cargar datos según selección
133 async function loadData() { 188 async function loadData() {
189 + const loadId = Date.now();
190 + console.log(`[CLIENT] loadData started (${loadId})`);
191 + const startTime = performance.now();
134 loading = true; 192 loading = true;
135 193
136 if (selectedEntity) { 194 if (selectedEntity) {
137 // Cargar desde vista_objeto_entidad 195 // Cargar desde vista_objeto_entidad
196 + const queryStart = performance.now();
138 const { data, error } = await supabase 197 const { data, error } = await supabase
139 .schema('ppto') 198 .schema('ppto')
140 .from('vista_objeto_entidad') 199 .from('vista_objeto_entidad')
...@@ -144,6 +203,7 @@ ...@@ -144,6 +203,7 @@
144 .eq('entidad', selectedEntity.entidad) 203 .eq('entidad', selectedEntity.entidad)
145 .order('gestion'); 204 .order('gestion');
146 205
206 + console.log(`[CLIENT] Query vista_objeto_entidad: ${(performance.now() - queryStart).toFixed(0)}ms`);
147 if (data) { 207 if (data) {
148 datosHistorico = data.find(d => d.gestion === 0) || null; 208 datosHistorico = data.find(d => d.gestion === 0) || null;
149 datosAnuales = data.filter(d => d.gestion > 0); 209 datosAnuales = data.filter(d => d.gestion > 0);
...@@ -151,6 +211,7 @@ ...@@ -151,6 +211,7 @@
151 } 211 }
152 } else { 212 } else {
153 // Cargar desde vista_objeto_estado (Todo el Estado) 213 // Cargar desde vista_objeto_estado (Todo el Estado)
214 + const queryStart = performance.now();
154 const { data, error } = await supabase 215 const { data, error } = await supabase
155 .schema('ppto') 216 .schema('ppto')
156 .from('vista_objeto_estado') 217 .from('vista_objeto_estado')
...@@ -158,6 +219,7 @@ ...@@ -158,6 +219,7 @@
158 .eq('objeto', objetoCodigo) 219 .eq('objeto', objetoCodigo)
159 .order('gestion'); 220 .order('gestion');
160 221
222 + console.log(`[CLIENT] Query vista_objeto_estado: ${(performance.now() - queryStart).toFixed(0)}ms`);
161 if (data) { 223 if (data) {
162 datosHistorico = data.find(d => d.gestion === 0) || null; 224 datosHistorico = data.find(d => d.gestion === 0) || null;
163 datosAnuales = data.filter(d => d.gestion > 0); 225 datosAnuales = data.filter(d => d.gestion > 0);
...@@ -175,10 +237,12 @@ ...@@ -175,10 +237,12 @@
175 } 237 }
176 238
177 loading = false; 239 loading = false;
240 + console.log(`[CLIENT] loadData total: ${(performance.now() - startTime).toFixed(0)}ms (${loadId})`);
178 } 241 }
179 242
180 // Cargar lista de entidades disponibles para este objeto 243 // Cargar lista de entidades disponibles para este objeto
181 async function loadEntidades() { 244 async function loadEntidades() {
245 + const startTime = performance.now();
182 const { data, error } = await supabase 246 const { data, error } = await supabase
183 .schema('ppto') 247 .schema('ppto')
184 .from('entidades_por_objeto') 248 .from('entidades_por_objeto')
...@@ -190,6 +254,7 @@ ...@@ -190,6 +254,7 @@
190 if (data) { 254 if (data) {
191 entidades = data; 255 entidades = data;
192 } 256 }
257 + console.log(`[CLIENT] loadEntidades: ${(performance.now() - startTime).toFixed(0)}ms`);
193 } 258 }
194 259
195 // ══════════════════════════════════════════════════════════════ 260 // ══════════════════════════════════════════════════════════════
...@@ -574,6 +639,7 @@ ...@@ -574,6 +639,7 @@
574 // MOUNT 639 // MOUNT
575 // ══════════════════════════════════════════════════════════════ 640 // ══════════════════════════════════════════════════════════════
576 onMount(async () => { 641 onMount(async () => {
642 + const mountStart = performance.now();
577 setTimeout(() => { mounted = true; }, 50); 643 setTimeout(() => { mounted = true; }, 50);
578 644
579 // Leer estado del tema 645 // Leer estado del tema
...@@ -589,7 +655,10 @@ ...@@ -589,7 +655,10 @@
589 }); 655 });
590 656
591 // Cargar datos 657 // Cargar datos
658 + const loadStart = performance.now();
592 await Promise.all([loadData(), loadEntidades()]); 659 await Promise.all([loadData(), loadEntidades()]);
660 + console.log(`[CLIENT] Promise.all: ${(performance.now() - loadStart).toFixed(0)}ms`);
661 + console.log(`[CLIENT] onMount total: ${(performance.now() - mountStart).toFixed(0)}ms`);
593 662
594 // Cerrar dropdown al hacer click fuera 663 // Cerrar dropdown al hacer click fuera
595 const handleClickOutside = (e) => { 664 const handleClickOutside = (e) => {
...@@ -606,9 +675,24 @@ ...@@ -606,9 +675,24 @@
606 }; 675 };
607 document.addEventListener('click', handleClickOutside); 676 document.addEventListener('click', handleClickOutside);
608 677
678 + // Resize handlers para split view
679 + document.addEventListener('mousemove', handleResize);
680 + document.addEventListener('mouseup', stopResize);
681 +
682 + // Escuchar mensajes del iframe (clasificador en modo embed)
683 + const handleIframeMessage = (e) => {
684 + if (e.data?.type === 'navigate-objeto' && e.data?.codigo) {
685 + goto(`/objeto/${e.data.codigo}`);
686 + }
687 + };
688 + window.addEventListener('message', handleIframeMessage);
689 +
609 return () => { 690 return () => {
610 observer.disconnect(); 691 observer.disconnect();
611 document.removeEventListener('click', handleClickOutside); 692 document.removeEventListener('click', handleClickOutside);
693 + document.removeEventListener('mousemove', handleResize);
694 + document.removeEventListener('mouseup', stopResize);
695 + window.removeEventListener('message', handleIframeMessage);
612 }; 696 };
613 }); 697 });
614 698
...@@ -660,8 +744,36 @@ ...@@ -660,8 +744,36 @@
660 <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" /> 744 <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" />
661 </svelte:head> 745 </svelte:head>
662 746
663 -<div class="page" class:mounted class:tema-claro={!isDark}> 747 +<!-- Contenedor split view -->
748 +<div class="split-container" class:split-active={splitOpen}>
749 + <!-- Panel izquierdo: Clasificador -->
750 + {#if splitOpen}
751 + <div class="split-left" style="width: {splitWidth}%">
752 + <div class="split-header">
753 + <span>Clasificador por Objeto</span>
754 + <button class="split-close" onclick={() => splitOpen = false}>
755 + <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
756 + <path d="M18 6L6 18M6 6l12 12"/>
757 + </svg>
758 + </button>
759 + </div>
760 + <iframe
761 + src="/clasificadores/objeto-gasto?embed=true&objeto={objetoCodigo}"
762 + title="Clasificador"
763 + class="split-iframe"
764 + ></iframe>
765 + </div>
766 +
767 + <!-- Resizer -->
768 + <div
769 + class="split-resizer"
770 + onmousedown={startResize}
771 + role="separator"
772 + ></div>
773 + {/if}
664 774
775 + <!-- Panel derecho: Contenido principal -->
776 + <div class="split-right" class:mounted>
665 <!-- Header compacto --> 777 <!-- Header compacto -->
666 <header> 778 <header>
667 <nav class="breadcrumb"> 779 <nav class="breadcrumb">
...@@ -690,7 +802,7 @@ ...@@ -690,7 +802,7 @@
690 802
691 <div class="meta"> 803 <div class="meta">
692 <span class="badge">{objeto.nivel}</span> 804 <span class="badge">{objeto.nivel}</span>
693 - <span class="years">{objeto.años}</span> 805 + <span class="years">{formatYearsAsRanges(objeto.años)}</span>
694 </div> 806 </div>
695 </header> 807 </header>
696 808
...@@ -705,7 +817,7 @@ ...@@ -705,7 +817,7 @@
705 <h4>Variaciones históricas</h4> 817 <h4>Variaciones históricas</h4>
706 {#each objeto.variaciones as v} 818 {#each objeto.variaciones as v}
707 <div class="variacion"> 819 <div class="variacion">
708 - <span class="variacion-rango">{v.rango}</span> 820 + <span class="variacion-rango">{formatYearsAsRanges(v.rango)}</span>
709 <p>{v.texto}</p> 821 <p>{v.texto}</p>
710 </div> 822 </div>
711 {/each} 823 {/each}
...@@ -1112,25 +1224,56 @@ ...@@ -1112,25 +1224,56 @@
1112 {/if} 1224 {/if}
1113 1225
1114 </main> 1226 </main>
1115 -</div> 1227 + </div><!-- end split-right -->
1116 -
1117 1228
1229 + <!-- Botón para abrir split view -->
1230 + <button
1231 + class="split-toggle"
1232 + onclick={() => splitOpen = !splitOpen}
1233 + title={splitOpen ? "Cerrar clasificador" : "Abrir clasificador"}
1234 + class:active={splitOpen}
1235 + >
1236 + <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1237 + <rect x="3" y="3" width="7" height="18" rx="1"/>
1238 + <rect x="14" y="3" width="7" height="18" rx="1"/>
1239 + </svg>
1240 + <span class="split-toggle-label">{splitOpen ? 'Cerrar' : 'Clasificador'}</span>
1241 + </button>
1242 +</div><!-- end split-container -->
1118 1243
1119 <style> 1244 <style>
1120 - .page { 1245 + /* Split container */
1246 + .split-container {
1247 + display: flex;
1121 min-height: 100vh; 1248 min-height: 100vh;
1122 - background: #0A0A0A; 1249 + background: var(--theme-body);
1123 - color: #F5F0E8; 1250 + }
1124 - font-family: 'Instrument Sans', -apple-system, sans-serif; 1251 +
1252 + .split-left {
1253 + flex-shrink: 0;
1254 + display: flex;
1255 + flex-direction: column;
1256 + height: 100vh;
1257 + position: sticky;
1258 + top: 0;
1259 + border-right: 1px solid var(--theme-borde);
1260 + }
1261 +
1262 + .split-right {
1263 + flex: 1;
1264 + min-width: 0;
1265 + background: var(--theme-body);
1266 + color: var(--theme-titulo);
1267 + font-family: var(--font-sans), -apple-system, sans-serif;
1125 opacity: 0; 1268 opacity: 0;
1126 transition: opacity 0.4s ease; 1269 transition: opacity 0.4s ease;
1127 } 1270 }
1128 - .mounted { opacity: 1; } 1271 + .split-right.mounted { opacity: 1; }
1129 1272
1130 /* Header */ 1273 /* Header */
1131 header { 1274 header {
1132 padding: 1rem 2rem; 1275 padding: 1rem 2rem;
1133 - border-bottom: 1px solid #1E1E1C; 1276 + border-bottom: 1px solid var(--theme-borde);
1134 } 1277 }
1135 1278
1136 .breadcrumb { 1279 .breadcrumb {
...@@ -1143,14 +1286,14 @@ ...@@ -1143,14 +1286,14 @@
1143 .breadcrumb a { 1286 .breadcrumb a {
1144 font-family: 'DM Mono', monospace; 1287 font-family: 'DM Mono', monospace;
1145 font-size: 0.75rem; 1288 font-size: 0.75rem;
1146 - color: #5A5650; 1289 + color: var(--theme-texto);
1147 text-decoration: none; 1290 text-decoration: none;
1148 transition: color 0.2s; 1291 transition: color 0.2s;
1149 } 1292 }
1150 - .breadcrumb a:hover { color: #F5F0E8; } 1293 + .breadcrumb a:hover { color: var(--theme-titulo); }
1151 1294
1152 .breadcrumb-sep { 1295 .breadcrumb-sep {
1153 - color: #3A3A38; 1296 + color: var(--theme-borde);
1154 font-size: 0.75rem; 1297 font-size: 0.75rem;
1155 } 1298 }
1156 1299
...@@ -1179,15 +1322,15 @@ ...@@ -1179,15 +1322,15 @@
1179 .codigo { 1322 .codigo {
1180 font-family: 'DM Mono', monospace; 1323 font-family: 'DM Mono', monospace;
1181 font-size: 1.5rem; 1324 font-size: 1.5rem;
1182 - color: #5A5650; 1325 + color: var(--theme-texto);
1183 font-weight: 400; 1326 font-weight: 400;
1184 } 1327 }
1185 1328
1186 h1 { 1329 h1 {
1187 - font-family: 'DM Serif Display', Georgia, serif; 1330 + font-family: var(--font-display), Georgia, serif;
1188 font-size: 1.5rem; 1331 font-size: 1.5rem;
1189 font-weight: 400; 1332 font-weight: 400;
1190 - color: #F5F0E8; 1333 + color: var(--theme-titulo);
1191 margin: 0; 1334 margin: 0;
1192 display: inline-flex; 1335 display: inline-flex;
1193 align-items: center; 1336 align-items: center;
...@@ -1198,7 +1341,7 @@ ...@@ -1198,7 +1341,7 @@
1198 background: transparent; 1341 background: transparent;
1199 border: none; 1342 border: none;
1200 padding: 0; 1343 padding: 0;
1201 - color: #5A5650; 1344 + color: var(--theme-texto);
1202 cursor: pointer; 1345 cursor: pointer;
1203 transition: all 0.2s; 1346 transition: all 0.2s;
1204 display: inline-flex; 1347 display: inline-flex;
...@@ -1207,7 +1350,7 @@ ...@@ -1207,7 +1350,7 @@
1207 vertical-align: middle; 1350 vertical-align: middle;
1208 } 1351 }
1209 .help-icon:hover, .help-icon.active { 1352 .help-icon:hover, .help-icon.active {
1210 - color: #E8C547; 1353 + color: var(--theme-accent);
1211 } 1354 }
1212 1355
1213 .meta { 1356 .meta {
...@@ -1223,22 +1366,137 @@ ...@@ -1223,22 +1366,137 @@
1223 letter-spacing: 0.1em; 1366 letter-spacing: 0.1em;
1224 text-transform: uppercase; 1367 text-transform: uppercase;
1225 padding: 0.25rem 0.5rem; 1368 padding: 0.25rem 0.5rem;
1226 - background: #1A1A18; 1369 + background: var(--theme-surface);
1227 - border: 1px solid #2E2E2C; 1370 + border: 1px solid var(--theme-borde);
1228 border-radius: 4px; 1371 border-radius: 4px;
1229 - color: #8A8578; 1372 + color: var(--theme-texto);
1230 } 1373 }
1231 1374
1232 .years { 1375 .years {
1233 font-family: 'DM Mono', monospace; 1376 font-family: 'DM Mono', monospace;
1234 font-size: 0.75rem; 1377 font-size: 0.75rem;
1235 - color: #5A5650; 1378 + color: var(--theme-texto);
1379 + }
1380 +
1381 + /* Split View - Header y controles */
1382 + .split-header {
1383 + display: flex;
1384 + align-items: center;
1385 + justify-content: space-between;
1386 + padding: 0.75rem 1rem;
1387 + background: var(--theme-surface);
1388 + border-bottom: 1px solid var(--theme-borde);
1389 + font-size: 0.75rem;
1390 + font-weight: 600;
1391 + color: var(--theme-titulo);
1392 + }
1393 +
1394 + .split-close {
1395 + padding: 0.25rem;
1396 + border: none;
1397 + background: transparent;
1398 + color: var(--theme-texto);
1399 + cursor: pointer;
1400 + border-radius: 4px;
1401 + display: flex;
1402 + align-items: center;
1403 + justify-content: center;
1404 + }
1405 +
1406 + .split-close:hover {
1407 + background: var(--theme-borde);
1408 + color: var(--theme-titulo);
1409 + }
1410 +
1411 + .split-iframe {
1412 + flex: 1;
1413 + width: 100%;
1414 + border: none;
1415 + background: var(--theme-body);
1416 + }
1417 +
1418 + .split-resizer {
1419 + width: 8px;
1420 + background: var(--theme-borde);
1421 + cursor: col-resize;
1422 + flex-shrink: 0;
1423 + position: relative;
1424 + transition: background 0.2s;
1425 + }
1426 +
1427 + .split-resizer::after {
1428 + content: '';
1429 + position: absolute;
1430 + left: 50%;
1431 + top: 50%;
1432 + transform: translate(-50%, -50%);
1433 + width: 4px;
1434 + height: 48px;
1435 + background: var(--theme-texto);
1436 + border-radius: 4px;
1437 + opacity: 0.3;
1438 + transition: opacity 0.2s, height 0.2s;
1439 + }
1440 +
1441 + .split-resizer:hover {
1442 + background: var(--theme-accent);
1443 + }
1444 +
1445 + .split-resizer:hover::after {
1446 + opacity: 0.8;
1447 + height: 64px;
1448 + }
1449 +
1450 + /* Botón para abrir/cerrar split view */
1451 + .split-toggle {
1452 + position: fixed;
1453 + left: 1rem;
1454 + bottom: 1.5rem;
1455 + display: none;
1456 + align-items: center;
1457 + gap: 0.5rem;
1458 + padding: 0.625rem 1rem;
1459 + background: var(--theme-surface);
1460 + border: 1px solid var(--theme-borde);
1461 + border-radius: 8px;
1462 + color: var(--theme-texto);
1463 + font-size: 0.75rem;
1464 + cursor: pointer;
1465 + z-index: 60;
1466 + transition: all 0.2s, left 0.3s ease;
1467 + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
1468 + }
1469 +
1470 + .split-active .split-toggle {
1471 + /* Se ajusta automáticamente con margin-left del contenedor */
1472 + }
1473 +
1474 + @media (min-width: 1024px) {
1475 + .split-toggle {
1476 + display: flex;
1477 + }
1478 + }
1479 +
1480 + .split-toggle:hover {
1481 + background: var(--theme-fill);
1482 + color: var(--theme-titulo);
1483 + border-color: var(--theme-accent);
1484 + }
1485 +
1486 + .split-toggle.active {
1487 + background: var(--theme-accent);
1488 + color: white;
1489 + border-color: var(--theme-accent);
1490 + }
1491 +
1492 + .split-toggle-label {
1493 + font-weight: 500;
1236 } 1494 }
1237 1495
1238 /* Info panel */ 1496 /* Info panel */
1239 .info-panel { 1497 .info-panel {
1240 - background: #111110; 1498 + background: var(--theme-fill);
1241 - border-bottom: 1px solid #1E1E1C; 1499 + border-bottom: 1px solid var(--theme-borde);
1242 padding: 1.5rem 2rem; 1500 padding: 1.5rem 2rem;
1243 } 1501 }
1244 1502
...@@ -1247,27 +1505,27 @@ ...@@ -1247,27 +1505,27 @@
1247 font-size: 0.625rem; 1505 font-size: 0.625rem;
1248 letter-spacing: 0.15em; 1506 letter-spacing: 0.15em;
1249 text-transform: uppercase; 1507 text-transform: uppercase;
1250 - color: #5A5650; 1508 + color: var(--theme-texto);
1251 margin: 0 0 0.5rem 0; 1509 margin: 0 0 0.5rem 0;
1252 } 1510 }
1253 1511
1254 .info-content p { 1512 .info-content p {
1255 font-size: 0.875rem; 1513 font-size: 0.875rem;
1256 line-height: 1.6; 1514 line-height: 1.6;
1257 - color: #8A8578; 1515 + color: var(--theme-texto);
1258 margin: 0 0 1.25rem 0; 1516 margin: 0 0 1.25rem 0;
1259 } 1517 }
1260 1518
1261 .variacion { 1519 .variacion {
1262 padding-left: 1rem; 1520 padding-left: 1rem;
1263 - border-left: 2px solid #2E2E2C; 1521 + border-left: 2px solid var(--theme-borde);
1264 margin-bottom: 1rem; 1522 margin-bottom: 1rem;
1265 } 1523 }
1266 1524
1267 .variacion-rango { 1525 .variacion-rango {
1268 font-family: 'DM Mono', monospace; 1526 font-family: 'DM Mono', monospace;
1269 font-size: 0.688rem; 1527 font-size: 0.688rem;
1270 - color: #5A5650; 1528 + color: var(--theme-texto);
1271 } 1529 }
1272 1530
1273 .variacion p { 1531 .variacion p {
...@@ -1294,23 +1552,23 @@ ...@@ -1294,23 +1552,23 @@
1294 } 1552 }
1295 1553
1296 .hijo-link:hover { 1554 .hijo-link:hover {
1297 - background: rgba(255, 255, 255, 0.05); 1555 + background: var(--theme-fill);
1298 } 1556 }
1299 1557
1300 .hijo-codigo { 1558 .hijo-codigo {
1301 font-family: 'DM Mono', monospace; 1559 font-family: 'DM Mono', monospace;
1302 font-size: 0.688rem; 1560 font-size: 0.688rem;
1303 - color: #5A5650; 1561 + color: var(--theme-texto);
1304 min-width: 3rem; 1562 min-width: 3rem;
1305 } 1563 }
1306 1564
1307 .hijo-nombre { 1565 .hijo-nombre {
1308 font-size: 0.813rem; 1566 font-size: 0.813rem;
1309 - color: #B0A89C; 1567 + color: var(--theme-texto);
1310 } 1568 }
1311 1569
1312 .hijo-link:hover .hijo-nombre { 1570 .hijo-link:hover .hijo-nombre {
1313 - color: #F5F0E8; 1571 + color: var(--theme-titulo);
1314 } 1572 }
1315 1573
1316 /* Main */ 1574 /* Main */
...@@ -1330,9 +1588,8 @@ ...@@ -1330,9 +1588,8 @@
1330 } 1588 }
1331 1589
1332 .interaction-hint { 1590 .interaction-hint {
1333 - font-size: 0.688rem; 1591 + font-size: 0.75rem;
1334 - color: #5A5650; 1592 + color: var(--theme-texto);
1335 - font-style: italic;
1336 margin-left: auto; 1593 margin-left: auto;
1337 } 1594 }
1338 1595
...@@ -1355,7 +1612,7 @@ ...@@ -1355,7 +1612,7 @@
1355 1612
1356 .selector-label { 1613 .selector-label {
1357 font-size: 0.813rem; 1614 font-size: 0.813rem;
1358 - color: #5A5650; 1615 + color: var(--theme-texto);
1359 } 1616 }
1360 1617
1361 .selector-btn { 1618 .selector-btn {
...@@ -1365,18 +1622,18 @@ ...@@ -1365,18 +1622,18 @@
1365 padding: 0.5rem 1rem; 1622 padding: 0.5rem 1rem;
1366 background: transparent; 1623 background: transparent;
1367 border: none; 1624 border: none;
1368 - box-shadow: inset 0 0 0 1px rgba(255,255,255,0.08); 1625 + box-shadow: inset 0 0 0 1px var(--theme-borde);
1369 border-radius: 8px; 1626 border-radius: 8px;
1370 - color: #F5F0E8; 1627 + color: var(--theme-titulo);
1371 font-size: 0.875rem; 1628 font-size: 0.875rem;
1372 cursor: pointer; 1629 cursor: pointer;
1373 transition: all 0.2s; 1630 transition: all 0.2s;
1374 } 1631 }
1375 .selector-btn:hover { 1632 .selector-btn:hover {
1376 - box-shadow: inset 0 0 0 1px rgba(255,255,255,0.15); 1633 + box-shadow: inset 0 0 0 1px var(--theme-accent);
1377 } 1634 }
1378 .selector-btn svg { 1635 .selector-btn svg {
1379 - color: #5A5650; 1636 + color: var(--theme-texto);
1380 } 1637 }
1381 1638
1382 .relative { 1639 .relative {
...@@ -1391,8 +1648,8 @@ ...@@ -1391,8 +1648,8 @@
1391 width: min(500px, 90vw); 1648 width: min(500px, 90vw);
1392 max-height: 360px; 1649 max-height: 360px;
1393 overflow: hidden; 1650 overflow: hidden;
1394 - background: #1A1A18; 1651 + background: var(--theme-surface);
1395 - border: 1px solid #2E2E2C; 1652 + border: 1px solid var(--theme-borde);
1396 border-radius: 8px; 1653 border-radius: 8px;
1397 box-shadow: 0 10px 40px rgba(0,0,0,0.4); 1654 box-shadow: 0 10px 40px rgba(0,0,0,0.4);
1398 z-index: 50; 1655 z-index: 50;
...@@ -1400,24 +1657,24 @@ ...@@ -1400,24 +1657,24 @@
1400 1657
1401 .dropdown-search { 1658 .dropdown-search {
1402 padding: 0.5rem; 1659 padding: 0.5rem;
1403 - border-bottom: 1px solid #2E2E2C; 1660 + border-bottom: 1px solid var(--theme-borde);
1404 } 1661 }
1405 1662
1406 .search-input { 1663 .search-input {
1407 width: 100%; 1664 width: 100%;
1408 padding: 0.5rem 0.75rem; 1665 padding: 0.5rem 0.75rem;
1409 - background: #111110; 1666 + background: var(--theme-fill);
1410 - border: 1px solid #2E2E2C; 1667 + border: 1px solid var(--theme-borde);
1411 border-radius: 6px; 1668 border-radius: 6px;
1412 - color: #F5F0E8; 1669 + color: var(--theme-titulo);
1413 font-size: 0.875rem; 1670 font-size: 0.875rem;
1414 } 1671 }
1415 .search-input:focus { 1672 .search-input:focus {
1416 outline: none; 1673 outline: none;
1417 - border-color: #E8C547; 1674 + border-color: var(--theme-accent);
1418 } 1675 }
1419 .search-input::placeholder { 1676 .search-input::placeholder {
1420 - color: #5A5650; 1677 + color: var(--theme-texto);
1421 } 1678 }
1422 1679
1423 .dropdown-item { 1680 .dropdown-item {
...@@ -1428,7 +1685,7 @@ ...@@ -1428,7 +1685,7 @@
1428 padding: 0.625rem 0.75rem; 1685 padding: 0.625rem 0.75rem;
1429 background: transparent; 1686 background: transparent;
1430 border: none; 1687 border: none;
1431 - color: #F5F0E8; 1688 + color: var(--theme-titulo);
1432 font-size: 0.813rem; 1689 font-size: 0.813rem;
1433 text-align: left; 1690 text-align: left;
1434 cursor: pointer; 1691 cursor: pointer;
...@@ -1449,7 +1706,7 @@ ...@@ -1449,7 +1706,7 @@
1449 .entity-code { 1706 .entity-code {
1450 font-family: 'DM Mono', monospace; 1707 font-family: 'DM Mono', monospace;
1451 font-size: 0.688rem; 1708 font-size: 0.688rem;
1452 - color: #5A5650; 1709 + color: var(--theme-texto);
1453 min-width: 2.5rem; 1710 min-width: 2.5rem;
1454 } 1711 }
1455 1712
...@@ -1499,7 +1756,7 @@ ...@@ -1499,7 +1756,7 @@
1499 1756
1500 .chart-title { 1757 .chart-title {
1501 font-size: 0.75rem; 1758 font-size: 0.75rem;
1502 - color: #5A5650; 1759 + color: var(--theme-texto);
1503 text-transform: uppercase; 1760 text-transform: uppercase;
1504 letter-spacing: 0.1em; 1761 letter-spacing: 0.1em;
1505 } 1762 }
...@@ -1507,7 +1764,7 @@ ...@@ -1507,7 +1764,7 @@
1507 .chart-period { 1764 .chart-period {
1508 font-family: 'DM Mono', monospace; 1765 font-family: 'DM Mono', monospace;
1509 font-size: 0.875rem; 1766 font-size: 0.875rem;
1510 - color: #B0A89C; 1767 + color: var(--theme-texto);
1511 font-variant-numeric: tabular-nums; 1768 font-variant-numeric: tabular-nums;
1512 min-width: 5rem; 1769 min-width: 5rem;
1513 text-align: right; 1770 text-align: right;
...@@ -1522,7 +1779,7 @@ ...@@ -1522,7 +1779,7 @@
1522 overflow-x: auto; 1779 overflow-x: auto;
1523 overflow-y: visible; 1780 overflow-y: visible;
1524 scrollbar-width: thin; 1781 scrollbar-width: thin;
1525 - scrollbar-color: #2E2E2C transparent; 1782 + scrollbar-color: var(--theme-borde) transparent;
1526 } 1783 }
1527 1784
1528 .chart-wrapper-scroll::-webkit-scrollbar { 1785 .chart-wrapper-scroll::-webkit-scrollbar {
...@@ -1534,7 +1791,7 @@ ...@@ -1534,7 +1791,7 @@
1534 } 1791 }
1535 1792
1536 .chart-wrapper-scroll::-webkit-scrollbar-thumb { 1793 .chart-wrapper-scroll::-webkit-scrollbar-thumb {
1537 - background: #2E2E2C; 1794 + background: var(--theme-borde);
1538 border-radius: 3px; 1795 border-radius: 3px;
1539 } 1796 }
1540 1797
...@@ -1542,7 +1799,7 @@ ...@@ -1542,7 +1799,7 @@
1542 position: sticky; 1799 position: sticky;
1543 left: 0; 1800 left: 0;
1544 z-index: 10; 1801 z-index: 10;
1545 - background: #0A0A0A; 1802 + background: var(--theme-body);
1546 padding-right: 0.5rem; 1803 padding-right: 0.5rem;
1547 margin-right: -0.5rem; 1804 margin-right: -0.5rem;
1548 } 1805 }
...@@ -1567,7 +1824,7 @@ ...@@ -1567,7 +1824,7 @@
1567 .y-label { 1824 .y-label {
1568 font-family: 'DM Mono', monospace; 1825 font-family: 'DM Mono', monospace;
1569 font-size: 0.625rem; 1826 font-size: 0.625rem;
1570 - color: #5A5650; 1827 + color: var(--theme-texto);
1571 } 1828 }
1572 1829
1573 .chart { 1830 .chart {
...@@ -1577,7 +1834,7 @@ ...@@ -1577,7 +1834,7 @@
1577 gap: 4px; 1834 gap: 4px;
1578 height: 160px; 1835 height: 160px;
1579 padding: 0 0.5rem; 1836 padding: 0 0.5rem;
1580 - border-left: 1px solid #2E2E2C; 1837 + border-left: 1px solid var(--theme-borde);
1581 } 1838 }
1582 1839
1583 .bar-container { 1840 .bar-container {
...@@ -1599,14 +1856,14 @@ ...@@ -1599,14 +1856,14 @@
1599 } 1856 }
1600 1857
1601 .bar.hovered { 1858 .bar.hovered {
1602 - background: #E8C547; 1859 + background: var(--theme-accent);
1603 transform: scaleX(1.1); 1860 transform: scaleX(1.1);
1604 } 1861 }
1605 1862
1606 .bar-label { 1863 .bar-label {
1607 font-family: 'DM Mono', monospace; 1864 font-family: 'DM Mono', monospace;
1608 font-size: 0.625rem; 1865 font-size: 0.625rem;
1609 - color: #B0A89C; 1866 + color: var(--theme-texto);
1610 margin-top: 0.5rem; 1867 margin-top: 0.5rem;
1611 opacity: 0; 1868 opacity: 0;
1612 transition: opacity 0.25s cubic-bezier(0.4, 0, 0.2, 1), color 0.25s ease; 1869 transition: opacity 0.25s cubic-bezier(0.4, 0, 0.2, 1), color 0.25s ease;
...@@ -1635,7 +1892,7 @@ ...@@ -1635,7 +1892,7 @@
1635 font-family: 'DM Mono', monospace; 1892 font-family: 'DM Mono', monospace;
1636 font-size: 1.125rem; 1893 font-size: 1.125rem;
1637 font-weight: 500; 1894 font-weight: 500;
1638 - color: #F5F0E8; 1895 + color: var(--theme-titulo);
1639 margin-bottom: 0; 1896 margin-bottom: 0;
1640 transition: opacity 0.2s ease; 1897 transition: opacity 0.2s ease;
1641 font-variant-numeric: tabular-nums; 1898 font-variant-numeric: tabular-nums;
...@@ -1644,7 +1901,7 @@ ...@@ -1644,7 +1901,7 @@
1644 1901
1645 .kpi-label { 1902 .kpi-label {
1646 font-size: 0.5625rem; 1903 font-size: 0.5625rem;
1647 - color: #5A5650; 1904 + color: var(--theme-texto);
1648 text-transform: uppercase; 1905 text-transform: uppercase;
1649 letter-spacing: 0.03em; 1906 letter-spacing: 0.03em;
1650 line-height: 1.3; 1907 line-height: 1.3;
...@@ -1666,7 +1923,7 @@ ...@@ -1666,7 +1923,7 @@
1666 .top-section h3 { 1923 .top-section h3 {
1667 font-family: 'DM Mono', monospace; 1924 font-family: 'DM Mono', monospace;
1668 font-size: 0.75rem; 1925 font-size: 0.75rem;
1669 - color: #5A5650; 1926 + color: var(--theme-texto);
1670 text-transform: uppercase; 1927 text-transform: uppercase;
1671 letter-spacing: 0.1em; 1928 letter-spacing: 0.1em;
1672 margin: 0; 1929 margin: 0;
...@@ -1675,7 +1932,7 @@ ...@@ -1675,7 +1932,7 @@
1675 .top-context { 1932 .top-context {
1676 font-family: 'DM Mono', monospace; 1933 font-family: 'DM Mono', monospace;
1677 font-size: 0.688rem; 1934 font-size: 0.688rem;
1678 - color: #5A5650; 1935 + color: var(--theme-texto);
1679 font-variant-numeric: tabular-nums; 1936 font-variant-numeric: tabular-nums;
1680 } 1937 }
1681 1938
...@@ -1703,7 +1960,7 @@ ...@@ -1703,7 +1960,7 @@
1703 .top-rank { 1960 .top-rank {
1704 font-family: 'DM Mono', monospace; 1961 font-family: 'DM Mono', monospace;
1705 font-size: 0.875rem; 1962 font-size: 0.875rem;
1706 - color: #5A5650; 1963 + color: var(--theme-texto);
1707 width: 24px; 1964 width: 24px;
1708 } 1965 }
1709 1966
...@@ -1714,32 +1971,32 @@ ...@@ -1714,32 +1971,32 @@
1714 .top-name { 1971 .top-name {
1715 display: block; 1972 display: block;
1716 font-size: 0.8125rem; 1973 font-size: 0.8125rem;
1717 - color: #F5F0E8; 1974 + color: var(--theme-titulo);
1718 margin-bottom: 0.375rem; 1975 margin-bottom: 0.375rem;
1719 } 1976 }
1720 1977
1721 .top-bar-bg { 1978 .top-bar-bg {
1722 height: 4px; 1979 height: 4px;
1723 - background: #1E1E1C; 1980 + background: var(--theme-surface);
1724 border-radius: 2px; 1981 border-radius: 2px;
1725 overflow: hidden; 1982 overflow: hidden;
1726 } 1983 }
1727 1984
1728 .top-bar { 1985 .top-bar {
1729 height: 100%; 1986 height: 100%;
1730 - background: #C9A751; 1987 + background: var(--theme-accent);
1731 border-radius: 2px; 1988 border-radius: 2px;
1732 transition: width 0.35s cubic-bezier(0.4, 0, 0.2, 1), background 0.25s ease; 1989 transition: width 0.35s cubic-bezier(0.4, 0, 0.2, 1), background 0.25s ease;
1733 } 1990 }
1734 1991
1735 .top-item:hover .top-bar { 1992 .top-item:hover .top-bar {
1736 - background: #E8C547; 1993 + background: var(--theme-accent);
1737 } 1994 }
1738 1995
1739 .top-pct { 1996 .top-pct {
1740 font-family: 'DM Mono', monospace; 1997 font-family: 'DM Mono', monospace;
1741 font-size: 0.875rem; 1998 font-size: 0.875rem;
1742 - color: #B0A89C; 1999 + color: var(--theme-texto);
1743 min-width: 52px; 2000 min-width: 52px;
1744 width: 52px; 2001 width: 52px;
1745 text-align: right; 2002 text-align: right;
...@@ -1749,10 +2006,10 @@ ...@@ -1749,10 +2006,10 @@
1749 2006
1750 .see-all { 2007 .see-all {
1751 background: transparent; 2008 background: transparent;
1752 - border: 1px solid #2E2E2C; 2009 + border: 1px solid var(--theme-borde);
1753 border-radius: 8px; 2010 border-radius: 8px;
1754 padding: 0.75rem 1rem; 2011 padding: 0.75rem 1rem;
1755 - color: #8A8578; 2012 + color: var(--theme-texto);
1756 font-size: 0.813rem; 2013 font-size: 0.813rem;
1757 cursor: pointer; 2014 cursor: pointer;
1758 transition: all 0.2s; 2015 transition: all 0.2s;
...@@ -1760,8 +2017,8 @@ ...@@ -1760,8 +2017,8 @@
1760 margin-top: 1rem; 2017 margin-top: 1rem;
1761 } 2018 }
1762 .see-all:hover { 2019 .see-all:hover {
1763 - border-color: #E8C547; 2020 + border-color: var(--theme-accent);
1764 - color: #E8C547; 2021 + color: var(--theme-accent);
1765 } 2022 }
1766 2023
1767 /* Vista Toggle */ 2024 /* Vista Toggle */
...@@ -1778,7 +2035,7 @@ ...@@ -1778,7 +2035,7 @@
1778 padding: 0.5rem 1.25rem; 2035 padding: 0.5rem 1.25rem;
1779 font-size: 0.813rem; 2036 font-size: 0.813rem;
1780 font-weight: 500; 2037 font-weight: 500;
1781 - color: #5A5650; 2038 + color: var(--theme-texto);
1782 background: transparent; 2039 background: transparent;
1783 border: none; 2040 border: none;
1784 border-radius: 7px; 2041 border-radius: 7px;
...@@ -1787,12 +2044,12 @@ ...@@ -1787,12 +2044,12 @@
1787 } 2044 }
1788 2045
1789 .toggle-btn:hover { 2046 .toggle-btn:hover {
1790 - color: #8A8578; 2047 + color: var(--theme-texto);
1791 } 2048 }
1792 2049
1793 .toggle-btn.active { 2050 .toggle-btn.active {
1794 - background: #F5F0E8; 2051 + background: var(--theme-titulo);
1795 - color: #0A0A0A; 2052 + color: var(--theme-body);
1796 } 2053 }
1797 2054
1798 /* Comparador inline - ajustado para 100vh */ 2055 /* Comparador inline - ajustado para 100vh */
...@@ -1823,20 +2080,20 @@ ...@@ -1823,20 +2080,20 @@
1823 justify-content: center; 2080 justify-content: center;
1824 min-height: 200px; 2081 min-height: 200px;
1825 background: rgba(255,255,255,0.02); 2082 background: rgba(255,255,255,0.02);
1826 - border: 1px dashed #2E2E2C; 2083 + border: 1px dashed var(--theme-borde);
1827 border-radius: 12px; 2084 border-radius: 12px;
1828 padding: 2rem; 2085 padding: 2rem;
1829 text-align: center; 2086 text-align: center;
1830 } 2087 }
1831 2088
1832 .comparador-placeholder p { 2089 .comparador-placeholder p {
1833 - color: #5A5650; 2090 + color: var(--theme-texto);
1834 font-size: 0.875rem; 2091 font-size: 0.875rem;
1835 margin: 0; 2092 margin: 0;
1836 } 2093 }
1837 2094
1838 .comparador-placeholder strong { 2095 .comparador-placeholder strong {
1839 - color: #8A8578; 2096 + color: var(--theme-texto);
1840 } 2097 }
1841 2098
1842 .rango-selector { 2099 .rango-selector {
...@@ -1848,7 +2105,7 @@ ...@@ -1848,7 +2105,7 @@
1848 2105
1849 .rango-label { 2106 .rango-label {
1850 font-size: 0.75rem; 2107 font-size: 0.75rem;
1851 - color: #5A5650; 2108 + color: var(--theme-texto);
1852 } 2109 }
1853 2110
1854 .rango-options { 2111 .rango-options {
...@@ -1860,7 +2117,7 @@ ...@@ -1860,7 +2117,7 @@
1860 padding: 0.375rem 0.75rem; 2117 padding: 0.375rem 0.75rem;
1861 font-size: 0.6875rem; 2118 font-size: 0.6875rem;
1862 font-family: 'DM Mono', monospace; 2119 font-family: 'DM Mono', monospace;
1863 - color: #5A5650; 2120 + color: var(--theme-texto);
1864 background: transparent; 2121 background: transparent;
1865 border: none; 2122 border: none;
1866 border-radius: 6px; 2123 border-radius: 6px;
...@@ -1869,12 +2126,12 @@ ...@@ -1869,12 +2126,12 @@
1869 } 2126 }
1870 2127
1871 .rango-btn:hover { 2128 .rango-btn:hover {
1872 - color: #8A8578; 2129 + color: var(--theme-texto);
1873 background: rgba(255,255,255,0.05); 2130 background: rgba(255,255,255,0.05);
1874 } 2131 }
1875 2132
1876 .rango-btn.active { 2133 .rango-btn.active {
1877 - color: #F5F0E8; 2134 + color: var(--theme-titulo);
1878 background: rgba(255,255,255,0.1); 2135 background: rgba(255,255,255,0.1);
1879 } 2136 }
1880 2137
...@@ -1904,20 +2161,20 @@ ...@@ -1904,20 +2161,20 @@
1904 box-shadow: inset 0 0 0 1px rgba(255,255,255,0.08); 2161 box-shadow: inset 0 0 0 1px rgba(255,255,255,0.08);
1905 border-radius: 8px; 2162 border-radius: 8px;
1906 padding: 0.5rem 0.75rem; 2163 padding: 0.5rem 0.75rem;
1907 - color: #F5F0E8; 2164 + color: var(--theme-titulo);
1908 font-size: 0.813rem; 2165 font-size: 0.813rem;
1909 font-family: 'Instrument Sans', sans-serif; 2166 font-family: 'Instrument Sans', sans-serif;
1910 cursor: pointer; 2167 cursor: pointer;
1911 } 2168 }
1912 .selector-group select:focus { 2169 .selector-group select:focus {
1913 outline: none; 2170 outline: none;
1914 - border-color: #E8C547; 2171 + border-color: var(--theme-accent);
1915 } 2172 }
1916 2173
1917 .vs { 2174 .vs {
1918 font-family: 'DM Mono', monospace; 2175 font-family: 'DM Mono', monospace;
1919 font-size: 0.75rem; 2176 font-size: 0.75rem;
1920 - color: #5A5650; 2177 + color: var(--theme-texto);
1921 flex-shrink: 0; 2178 flex-shrink: 0;
1922 } 2179 }
1923 2180
...@@ -1973,7 +2230,7 @@ ...@@ -1973,7 +2230,7 @@
1973 } 2230 }
1974 2231
1975 .bar-label.highlight { 2232 .bar-label.highlight {
1976 - color: #F5F0E8; 2233 + color: var(--theme-titulo);
1977 font-weight: 500; 2234 font-weight: 500;
1978 transition: color 0.25s ease; 2235 transition: color 0.25s ease;
1979 } 2236 }
...@@ -1991,7 +2248,7 @@ ...@@ -1991,7 +2248,7 @@
1991 align-items: center; 2248 align-items: center;
1992 gap: 0.5rem; 2249 gap: 0.5rem;
1993 font-size: 0.75rem; 2250 font-size: 0.75rem;
1994 - color: #8A8578; 2251 + color: var(--theme-texto);
1995 } 2252 }
1996 2253
1997 .legend-dot { 2254 .legend-dot {
...@@ -2013,7 +2270,7 @@ ...@@ -2013,7 +2270,7 @@
2013 grid-template-columns: 1fr 1fr 1fr; 2270 grid-template-columns: 1fr 1fr 1fr;
2014 gap: 1rem; 2271 gap: 1rem;
2015 padding-bottom: 0.375rem; 2272 padding-bottom: 0.375rem;
2016 - border-bottom: 1px solid #2E2E2C; 2273 + border-bottom: 1px solid var(--theme-borde);
2017 margin-bottom: 0.375rem; 2274 margin-bottom: 0.375rem;
2018 } 2275 }
2019 2276
...@@ -2036,7 +2293,7 @@ ...@@ -2036,7 +2293,7 @@
2036 font-family: 'DM Mono', monospace; 2293 font-family: 'DM Mono', monospace;
2037 font-size: 0.75rem; 2294 font-size: 0.75rem;
2038 font-weight: 500; 2295 font-weight: 500;
2039 - color: #8A8578; 2296 + color: var(--theme-texto);
2040 display: flex; 2297 display: flex;
2041 align-items: center; 2298 align-items: center;
2042 text-transform: uppercase; 2299 text-transform: uppercase;
...@@ -2047,7 +2304,7 @@ ...@@ -2047,7 +2304,7 @@
2047 font-family: 'DM Mono', monospace; 2304 font-family: 'DM Mono', monospace;
2048 font-size: 0.9375rem; 2305 font-size: 0.9375rem;
2049 font-weight: 500; 2306 font-weight: 500;
2050 - color: #F5F0E8; 2307 + color: var(--theme-titulo);
2051 text-align: center; 2308 text-align: center;
2052 font-variant-numeric: tabular-nums; 2309 font-variant-numeric: tabular-nums;
2053 transition: opacity 0.2s ease; 2310 transition: opacity 0.2s ease;
...@@ -2059,20 +2316,20 @@ ...@@ -2059,20 +2316,20 @@
2059 padding: 1rem 1.5rem; 2316 padding: 1rem 1.5rem;
2060 background: linear-gradient(90deg, rgba(232,197,71,0.05), rgba(74,139,110,0.05)); 2317 background: linear-gradient(90deg, rgba(232,197,71,0.05), rgba(74,139,110,0.05));
2061 border-radius: 10px; 2318 border-radius: 10px;
2062 - border: 1px solid #2E2E2C; 2319 + border: 1px solid var(--theme-borde);
2063 font-size: 0.875rem; 2320 font-size: 0.875rem;
2064 - color: #8A8578; 2321 + color: var(--theme-texto);
2065 line-height: 1.6; 2322 line-height: 1.6;
2066 } 2323 }
2067 2324
2068 .comparador-insight strong { 2325 .comparador-insight strong {
2069 - color: #F5F0E8; 2326 + color: var(--theme-titulo);
2070 } 2327 }
2071 2328
2072 .insight-year { 2329 .insight-year {
2073 font-family: 'DM Mono', monospace; 2330 font-family: 'DM Mono', monospace;
2074 font-size: 0.813rem; 2331 font-size: 0.813rem;
2075 - color: #E8C547; 2332 + color: var(--theme-accent);
2076 } 2333 }
2077 2334
2078 /* Responsive */ 2335 /* Responsive */
...@@ -2147,335 +2404,5 @@ ...@@ -2147,335 +2404,5 @@
2147 } 2404 }
2148 } 2405 }
2149 2406
2150 - /* ========== TEMA CLARO ========== */ 2407 + /* Tema claro: las variables CSS cambian automáticamente via :root.dark */
2151 - .tema-claro {
2152 - background: #FAFAF8;
2153 - color: #1A1A18;
2154 - }
2155 -
2156 - .tema-claro header {
2157 - border-bottom-color: #E5E5E0;
2158 - }
2159 -
2160 - .tema-claro .breadcrumb a {
2161 - color: #8A8578;
2162 - }
2163 - .tema-claro .breadcrumb a:hover {
2164 - color: #1A1A18;
2165 - }
2166 -
2167 - .tema-claro .breadcrumb-sep {
2168 - color: #C5C5C0;
2169 - }
2170 -
2171 - .tema-claro .codigo {
2172 - color: #8A8578;
2173 - }
2174 -
2175 - .tema-claro h1 {
2176 - color: #1A1A18;
2177 - }
2178 -
2179 - .tema-claro .help-icon {
2180 - color: #8A8578;
2181 - }
2182 - .tema-claro .help-icon:hover,
2183 - .tema-claro .help-icon.active {
2184 - color: #C9A227;
2185 - }
2186 -
2187 - .tema-claro .badge {
2188 - background: #F0F0EC;
2189 - border-color: #E5E5E0;
2190 - color: #5A5650;
2191 - }
2192 -
2193 - .tema-claro .years {
2194 - color: #8A8578;
2195 - }
2196 -
2197 - .tema-claro .info-panel {
2198 - background: #F5F5F3;
2199 - border-bottom-color: #E5E5E0;
2200 - }
2201 -
2202 - .tema-claro .info-content h4 {
2203 - color: #8A8578;
2204 - }
2205 -
2206 - .tema-claro .info-content p {
2207 - color: #5A5650;
2208 - }
2209 -
2210 - .tema-claro .variacion {
2211 - border-left-color: #D5D5D0;
2212 - }
2213 -
2214 - .tema-claro .variacion-rango {
2215 - color: #8A8578;
2216 - }
2217 -
2218 - .tema-claro .hijo-link:hover {
2219 - background: rgba(0, 0, 0, 0.03);
2220 - }
2221 -
2222 - .tema-claro .hijo-codigo {
2223 - color: #8A8578;
2224 - }
2225 -
2226 - .tema-claro .hijo-nombre {
2227 - color: #5A5650;
2228 - }
2229 -
2230 - .tema-claro .hijo-link:hover .hijo-nombre {
2231 - color: #1A1A18;
2232 - }
2233 -
2234 - .tema-claro .vista-toggle {
2235 - background: transparent;
2236 - box-shadow: inset 0 0 0 1px rgba(0,0,0,0.08);
2237 - }
2238 -
2239 - .tema-claro .toggle-btn {
2240 - color: #8A8578;
2241 - }
2242 - .tema-claro .toggle-btn:hover {
2243 - color: #5A5650;
2244 - }
2245 - .tema-claro .toggle-btn.active {
2246 - background: #1A1A18;
2247 - color: #FAFAF8;
2248 - }
2249 -
2250 - .tema-claro .entity-selector .selector-label {
2251 - color: #8A8578;
2252 - }
2253 -
2254 - .tema-claro .interaction-hint {
2255 - color: #8A8578;
2256 - }
2257 -
2258 - .tema-claro .selector-btn {
2259 - background: transparent;
2260 - border: none;
2261 - box-shadow: inset 0 0 0 1px rgba(0,0,0,0.08);
2262 - color: #1A1A18;
2263 - }
2264 - .tema-claro .selector-btn:hover {
2265 - box-shadow: inset 0 0 0 1px rgba(0,0,0,0.15);
2266 - }
2267 - .tema-claro .selector-btn svg {
2268 - color: #8A8578;
2269 - }
2270 -
2271 - .tema-claro .dropdown-panel {
2272 - background: #FAFAF8;
2273 - border-color: #E5E5E0;
2274 - }
2275 -
2276 - .tema-claro .dropdown-search {
2277 - border-bottom-color: #E5E5E0;
2278 - }
2279 -
2280 - .tema-claro .search-input {
2281 - background: #F5F5F3;
2282 - border-color: #E5E5E0;
2283 - color: #1A1A18;
2284 - }
2285 - .tema-claro .search-input:focus {
2286 - border-color: #C9A227;
2287 - }
2288 - .tema-claro .search-input::placeholder {
2289 - color: #8A8578;
2290 - }
2291 -
2292 - .tema-claro .dropdown-item {
2293 - color: #1A1A18;
2294 - }
2295 - .tema-claro .dropdown-item:hover {
2296 - background: rgba(0,0,0,0.03);
2297 - }
2298 - .tema-claro .dropdown-item.active {
2299 - background: rgba(201,162,39,0.12);
2300 - }
2301 -
2302 - .tema-claro .entity-code {
2303 - color: #8A8578;
2304 - }
2305 -
2306 - .tema-claro .chart-title {
2307 - color: #8A8578;
2308 - }
2309 -
2310 - .tema-claro .chart-period {
2311 - color: #333333;
2312 - }
2313 -
2314 - .tema-claro .y-label {
2315 - color: #8A8578;
2316 - }
2317 -
2318 - .tema-claro .y-axis-sticky {
2319 - background: #FAFAF8;
2320 - }
2321 -
2322 - .tema-claro .chart-wrapper-scroll::-webkit-scrollbar-thumb {
2323 - background: #D5D5D0;
2324 - }
2325 -
2326 - .tema-claro .chart {
2327 - border-left-color: #E5E5E0;
2328 - }
2329 -
2330 - .tema-claro .bar-container .bar {
2331 - background: #2a9d8f;
2332 - }
2333 -
2334 - .tema-claro .bar-container .bar.hovered {
2335 - background: #C9A751;
2336 - }
2337 -
2338 - /* Las barras de comparación mantienen su --bar-color */
2339 - .tema-claro .bar-a,
2340 - .tema-claro .bar-b {
2341 - background: var(--bar-color);
2342 - }
2343 -
2344 - .tema-claro .bar-label {
2345 - color: #333333;
2346 - }
2347 -
2348 - .tema-claro .kpis {
2349 - background: transparent;
2350 - }
2351 -
2352 - .tema-claro .kpi {
2353 - background: transparent;
2354 - box-shadow: inset 0 0 0 1px rgba(0,0,0,0.08);
2355 - }
2356 -
2357 - .tema-claro .kpi-value {
2358 - color: #1A1A18;
2359 - }
2360 -
2361 - .tema-claro .kpi-label {
2362 - color: #8A8578;
2363 - }
2364 -
2365 - .tema-claro .top-section h3 {
2366 - color: #8A8578;
2367 - }
2368 -
2369 - .tema-claro .top-context {
2370 - color: #8A8578;
2371 - }
2372 -
2373 - .tema-claro .top-item {
2374 - background: transparent;
2375 - box-shadow: inset 0 0 0 1px rgba(0,0,0,0.08);
2376 - }
2377 -
2378 - .tema-claro .top-rank {
2379 - color: #8A8578;
2380 - }
2381 -
2382 - .tema-claro .top-name {
2383 - color: #1A1A18;
2384 - }
2385 -
2386 - .tema-claro .top-bar-bg {
2387 - background: #F0F0EC;
2388 - }
2389 -
2390 - .tema-claro .top-bar {
2391 - background: #C9A751;
2392 - }
2393 -
2394 - .tema-claro .top-item:hover {
2395 - box-shadow: inset 0 0 0 1px rgba(201, 167, 81, 0.5);
2396 - }
2397 -
2398 - .tema-claro .top-item:hover .top-bar {
2399 - background: #B8963F;
2400 - }
2401 -
2402 - .tema-claro .top-pct {
2403 - color: #333333;
2404 - }
2405 -
2406 - /* Comparador - tema claro */
2407 - .tema-claro .comparador-selectors .selector-group select {
2408 - background: transparent;
2409 - border: none;
2410 - box-shadow: inset 0 0 0 1px rgba(0,0,0,0.08);
2411 - color: #1A1A18;
2412 - }
2413 - .tema-claro .comparador-selectors .selector-group select:focus {
2414 - box-shadow: inset 0 0 0 1px rgba(0,0,0,0.15);
2415 - outline: none;
2416 - }
2417 -
2418 - .tema-claro .comparador-placeholder {
2419 - background: rgba(0,0,0,0.02);
2420 - border-color: #E5E5E0;
2421 - }
2422 -
2423 - .tema-claro .comparador-placeholder p {
2424 - color: #8A8578;
2425 - }
2426 -
2427 - .tema-claro .comparador-placeholder strong {
2428 - color: #5A5650;
2429 - }
2430 -
2431 - .tema-claro .rango-label {
2432 - color: #8A8578;
2433 - }
2434 -
2435 - .tema-claro .rango-btn {
2436 - color: #8A8578;
2437 - }
2438 -
2439 - .tema-claro .rango-btn:hover {
2440 - color: #5A5650;
2441 - background: rgba(0,0,0,0.05);
2442 - }
2443 -
2444 - .tema-claro .rango-btn.active {
2445 - color: #1A1A18;
2446 - background: rgba(0,0,0,0.08);
2447 - }
2448 -
2449 - .tema-claro .vs {
2450 - color: #8A8578;
2451 - }
2452 -
2453 - .tema-claro .chart-legend .legend-item {
2454 - color: #5A5650;
2455 - }
2456 -
2457 - .tema-claro .kpis-tabla {
2458 - background: transparent;
2459 - box-shadow: inset 0 0 0 1px rgba(0,0,0,0.08);
2460 - }
2461 -
2462 - .tema-claro .tabla-header {
2463 - border-bottom-color: #E5E5E0;
2464 - }
2465 -
2466 - .tema-claro .tabla-label {
2467 - color: #5A5650;
2468 - }
2469 -
2470 - .tema-claro .tabla-val {
2471 - color: #1A1A18;
2472 - }
2473 -
2474 - .tema-claro .bar-group.active {
2475 - background: rgba(42, 157, 143, 0.12);
2476 - }
2477 -
2478 - .tema-claro .bar-label.highlight {
2479 - color: #1A1A18;
2480 - }
2481 </style> 2408 </style>
......