Rafael Lopez

obj done

...@@ -178,6 +178,10 @@ ...@@ -178,6 +178,10 @@
178 min-height: 2px; 178 min-height: 2px;
179 } 179 }
180 180
181 + :global(html.dark) .bar {
182 + background: rgba(107, 159, 212, 0.85);
183 + }
184 +
181 .bars:hover .bar { 185 .bars:hover .bar {
182 opacity: 0.35; 186 opacity: 0.35;
183 } 187 }
......
...@@ -11,6 +11,11 @@ ...@@ -11,6 +11,11 @@
11 navigateResults 11 navigateResults
12 } from '$lib/stores/searchStore'; 12 } from '$lib/stores/searchStore';
13 13
14 + let {
15 + preserveVista = false,
16 + vista = 'agregado'
17 + } = $props();
18 +
14 let searchInputRef = $state(null); 19 let searchInputRef = $state(null);
15 let searchVal = $state(''); 20 let searchVal = $state('');
16 let searchFocused = $state(false); 21 let searchFocused = $state(false);
...@@ -47,7 +52,10 @@ ...@@ -47,7 +52,10 @@
47 } 52 }
48 53
49 function handleSearchSelect(result) { 54 function handleSearchSelect(result) {
50 - goto(`/objeto/${result.codigo}`); 55 + const url = preserveVista && vista === 'comparar'
56 + ? `/objeto/${result.codigo}?vista=comparar`
57 + : `/objeto/${result.codigo}`;
58 + goto(url);
51 searchVal = ''; 59 searchVal = '';
52 clearSearch(); 60 clearSearch();
53 searchFocused = false; 61 searchFocused = false;
...@@ -116,7 +124,7 @@ ...@@ -116,7 +124,7 @@
116 display: flex; 124 display: flex;
117 align-items: center; 125 align-items: center;
118 gap: 0.5rem; 126 gap: 0.5rem;
119 - background: #f5f5f5; 127 + background: var(--search-bg, #f5f5f5);
120 border: none; 128 border: none;
121 border-radius: 8px; 129 border-radius: 8px;
122 padding: 0.625rem 1rem; 130 padding: 0.625rem 1rem;
...@@ -124,6 +132,10 @@ ...@@ -124,6 +132,10 @@
124 width: 100%; 132 width: 100%;
125 } 133 }
126 134
135 + :global(html.dark) .objeto-search {
136 + --search-bg: rgba(255, 255, 255, 0.08);
137 + }
138 +
127 .objeto-search.focused { 139 .objeto-search.focused {
128 border-color: #6B9FD4; 140 border-color: #6B9FD4;
129 box-shadow: 0 0 0 3px rgba(107, 159, 212, 0.15); 141 box-shadow: 0 0 0 3px rgba(107, 159, 212, 0.15);
......
...@@ -57,4 +57,14 @@ ...@@ -57,4 +57,14 @@
57 .vista-btn:active { 57 .vista-btn:active {
58 color: #555555; 58 color: #555555;
59 } 59 }
60 +
61 + :global(html.dark) .vista-btn {
62 + color: rgba(255, 255, 255, 0.6);
63 + text-decoration-color: rgba(255, 255, 255, 0.4);
64 + }
65 +
66 + :global(html.dark) .vista-btn:hover {
67 + color: rgba(255, 255, 255, 0.85);
68 + text-decoration-color: rgba(255, 255, 255, 0.5);
69 + }
60 </style> 70 </style>
......
...@@ -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 { goto } from '$app/navigation'; 5 import { goto } from '$app/navigation';
6 + import { page } from '$app/stores';
6 import { supabase } from '$lib/supabase'; 7 import { supabase } from '$lib/supabase';
7 import Spinner from '$lib/components/ui/Spinner.svelte'; 8 import Spinner from '$lib/components/ui/Spinner.svelte';
8 import ObjetoSearch from '$lib/components/objeto/ObjetoSearch.svelte'; 9 import ObjetoSearch from '$lib/components/objeto/ObjetoSearch.svelte';
...@@ -30,9 +31,88 @@ ...@@ -30,9 +31,88 @@
30 let showInfo = $state(false); 31 let showInfo = $state(false);
31 let showDefinicionesModal = $state(false); 32 let showDefinicionesModal = $state(false);
32 let showMobileSidebar = $state(false); 33 let showMobileSidebar = $state(false);
34 +
35 + // Estado de vista
33 let vista = $state('agregado'); // 'agregado' | 'comparar' 36 let vista = $state('agregado'); // 'agregado' | 'comparar'
34 let isDark = $state(true); 37 let isDark = $state(true);
35 38
39 + // ══════════════════════════════════════════════════════════════
40 + // MANEJO DE URL - Estado compartible
41 + // ══════════════════════════════════════════════════════════════
42 +
43 + // Leer parámetros de URL
44 + function getUrlParams() {
45 + const params = $page.url.searchParams;
46 + return {
47 + vista: params.get('vista') || 'agregado',
48 + entidad: params.get('entidad') ? parseInt(params.get('entidad')) : null,
49 + entidadA: params.get('entidadA') ? parseInt(params.get('entidadA')) : null,
50 + entidadB: params.get('entidadB') ? parseInt(params.get('entidadB')) : null
51 + };
52 + }
53 +
54 + // Actualizar URL sin recargar la página
55 + function updateUrl(newParams = {}) {
56 + const url = new URL($page.url);
57 +
58 + // Limpiar parámetros anteriores
59 + url.searchParams.delete('vista');
60 + url.searchParams.delete('entidad');
61 + url.searchParams.delete('entidadA');
62 + url.searchParams.delete('entidadB');
63 +
64 + // Agregar nuevos parámetros según el modo
65 + if (newParams.vista === 'comparar') {
66 + url.searchParams.set('vista', 'comparar');
67 + if (newParams.entidadA) url.searchParams.set('entidadA', newParams.entidadA);
68 + if (newParams.entidadB) url.searchParams.set('entidadB', newParams.entidadB);
69 + } else {
70 + // Vista agregada - solo agregar entidad si hay una seleccionada
71 + if (newParams.entidad) url.searchParams.set('entidad', newParams.entidad);
72 + }
73 +
74 + goto(url.toString(), { replaceState: true, noScroll: true });
75 + }
76 +
77 + // Aplicar estado desde URL después de cargar entidades
78 + async function applyUrlState() {
79 + const params = getUrlParams();
80 +
81 + // Aplicar vista
82 + vista = params.vista;
83 +
84 + if (params.vista === 'comparar') {
85 + // Modo comparación
86 + if (params.entidadA) {
87 + const entityA = entidades.find(e => e.entidad === params.entidadA);
88 + if (entityA) {
89 + entidadCompararA = entityA;
90 + datosCompararA = await loadDatosEntidad(entityA);
91 + }
92 + }
93 + if (params.entidadB) {
94 + const entityB = entidades.find(e => e.entidad === params.entidadB);
95 + if (entityB) {
96 + entidadCompararB = entityB;
97 + datosCompararB = await loadDatosEntidad(entityB);
98 + }
99 + }
100 + // Si no hay entidades en URL, inicializar con defaults
101 + if (!params.entidadA && !params.entidadB) {
102 + await initComparacion();
103 + }
104 + } else {
105 + // Vista agregada
106 + if (params.entidad) {
107 + const entity = entidades.find(e => e.entidad === params.entidad);
108 + if (entity) {
109 + selectedEntity = entity;
110 + await loadData();
111 + }
112 + }
113 + }
114 + }
115 +
36 116
37 // Parsear descripciones del objeto 117 // Parsear descripciones del objeto
38 function parseDescripciones(descripcionesStr) { 118 function parseDescripciones(descripcionesStr) {
...@@ -407,11 +487,53 @@ ...@@ -407,11 +487,53 @@
407 487
408 let hoveredYearComparar = $state(null); 488 let hoveredYearComparar = $state(null);
409 let loadingComparacion = $state(false); 489 let loadingComparacion = $state(false);
490 + let isDefaultSelection = $state(false); // true si la selección es automática
491 +
492 + // Estado de alertas (cerradas por el usuario)
493 + let alertDefaultDismissed = $state(false);
494 + let alertRangosDismissed = $state(false);
410 495
411 // Entidades seleccionadas para comparar 496 // Entidades seleccionadas para comparar
412 let entidadCompararA = $state(null); 497 let entidadCompararA = $state(null);
413 let entidadCompararB = $state(null); 498 let entidadCompararB = $state(null);
414 499
500 + // Pre-seleccionar entidades al cambiar a modo comparar
501 + async function initComparacion() {
502 + // Si ya hay selección manual, no hacer nada
503 + if (entidadCompararA || entidadCompararB) return;
504 +
505 + if (selectedEntity) {
506 + // Si venimos de una entidad específica, pre-seleccionarla en A
507 + entidadCompararA = selectedEntity;
508 + loadingComparacion = true;
509 + datosCompararA = await loadDatosEntidad(selectedEntity);
510 + loadingComparacion = false;
511 + isDefaultSelection = false; // No es default porque el usuario ya eligió una entidad
512 + } else if (topEntidadesTotal.length >= 2) {
513 + // Si venimos de agregado, pre-seleccionar top 2 del ranking
514 + const top1 = entidades.find(e => e.entidad_desc === topEntidadesTotal[0].nombre);
515 + const top2 = entidades.find(e => e.entidad_desc === topEntidadesTotal[1].nombre);
516 +
517 + if (top1 && top2) {
518 + isDefaultSelection = true;
519 + loadingComparacion = true;
520 +
521 + entidadCompararA = top1;
522 + datosCompararA = await loadDatosEntidad(top1);
523 +
524 + entidadCompararB = top2;
525 + datosCompararB = await loadDatosEntidad(top2);
526 +
527 + loadingComparacion = false;
528 + }
529 + }
530 + }
531 +
532 + // Limpiar selección por defecto cuando el usuario cambia manualmente
533 + function clearDefaultSelection() {
534 + isDefaultSelection = false;
535 + }
536 +
415 // Datos cargados de cada entidad 537 // Datos cargados de cada entidad
416 let datosCompararA = $state({ anual: [], historico: null }); 538 let datosCompararA = $state({ anual: [], historico: null });
417 let datosCompararB = $state({ anual: [], historico: null }); 539 let datosCompararB = $state({ anual: [], historico: null });
...@@ -467,6 +589,9 @@ ...@@ -467,6 +589,9 @@
467 dropdownAOpen = false; 589 dropdownAOpen = false;
468 searchA = ''; 590 searchA = '';
469 entityLimitA = 30; 591 entityLimitA = 30;
592 + isDefaultSelection = false; // Selección manual
593 + alertDefaultDismissed = false; // Resetear alerta
594 + alertRangosDismissed = false; // Mostrar alerta de rangos si aplica
470 loadingComparacion = true; 595 loadingComparacion = true;
471 datosCompararA = await loadDatosEntidad(entidad); 596 datosCompararA = await loadDatosEntidad(entidad);
472 loadingComparacion = false; 597 loadingComparacion = false;
...@@ -478,20 +603,58 @@ ...@@ -478,20 +603,58 @@
478 dropdownBOpen = false; 603 dropdownBOpen = false;
479 searchB = ''; 604 searchB = '';
480 entityLimitB = 30; 605 entityLimitB = 30;
606 + isDefaultSelection = false; // Selección manual
607 + alertDefaultDismissed = false; // Resetear alerta
608 + alertRangosDismissed = false; // Mostrar alerta de rangos si aplica
481 loadingComparacion = true; 609 loadingComparacion = true;
482 datosCompararB = await loadDatosEntidad(entidad); 610 datosCompararB = await loadDatosEntidad(entidad);
483 loadingComparacion = false; 611 loadingComparacion = false;
484 } 612 }
485 613
486 // Selector de rango de años 614 // Selector de rango de años
487 - let rangoAños = $state('10'); // '5', '10', '15', 'todo' 615 + let rangoAños = $state('todo'); // '5', '10', '15', 'todo'
488 616
489 - // Años comunes entre ambas entidades 617 + // Años para comparación (comunes si hay 2 entidades, o los de la única seleccionada)
490 let añosComunesTotales = $derived.by(() => { 618 let añosComunesTotales = $derived.by(() => {
491 - if (!datosCompararA.anual.length || !datosCompararB.anual.length) return []; 619 + const añosA = datosCompararA.anual.map(d => d.gestion);
492 - const añosA = new Set(datosCompararA.anual.map(d => d.gestion)); 620 + const añosB = datosCompararB.anual.map(d => d.gestion);
493 - const añosB = new Set(datosCompararB.anual.map(d => d.gestion)); 621 +
494 - return [...añosA].filter(a => añosB.has(a)).sort((a, b) => a - b); 622 + // Si hay ambas entidades, usar años en común
623 + if (añosA.length && añosB.length) {
624 + const setB = new Set(añosB);
625 + return añosA.filter(a => setB.has(a)).sort((a, b) => a - b);
626 + }
627 + // Si solo hay A, usar sus años
628 + if (añosA.length) return [...añosA].sort((a, b) => a - b);
629 + // Si solo hay B, usar sus años
630 + if (añosB.length) return [...añosB].sort((a, b) => a - b);
631 + return [];
632 + });
633 +
634 + // Flag para saber si estamos en modo "solo una entidad"
635 + let soloUnaEntidad = $derived(
636 + (entidadCompararA && !entidadCompararB) || (!entidadCompararA && entidadCompararB)
637 + );
638 +
639 + // Información de rangos para advertencia
640 + let rangoInfoA = $derived.by(() => {
641 + if (!datosCompararA.anual.length) return null;
642 + const años = datosCompararA.anual.map(d => d.gestion).sort((a, b) => a - b);
643 + return { desde: años[0], hasta: años[años.length - 1], total: años.length };
644 + });
645 +
646 + let rangoInfoB = $derived.by(() => {
647 + if (!datosCompararB.anual.length) return null;
648 + const años = datosCompararB.anual.map(d => d.gestion).sort((a, b) => a - b);
649 + return { desde: años[0], hasta: años[años.length - 1], total: años.length };
650 + });
651 +
652 + // Detectar si hay diferencia significativa en los rangos
653 + let tienenRangosDiferentes = $derived.by(() => {
654 + if (!rangoInfoA || !rangoInfoB) return false;
655 + return rangoInfoA.desde !== rangoInfoB.desde ||
656 + rangoInfoA.hasta !== rangoInfoB.hasta ||
657 + rangoInfoA.total !== rangoInfoB.total;
495 }); 658 });
496 659
497 // Años filtrados según el rango seleccionado 660 // Años filtrados según el rango seleccionado
...@@ -580,8 +743,25 @@ ...@@ -580,8 +743,25 @@
580 }); 743 });
581 744
582 // Colores 745 // Colores
583 - let colorA = '#2a9d8f'; 746 + // Colores para comparación (reactivos al tema)
584 - let colorB = '#f4a261'; 747 + let isDarkMode = $state(false);
748 +
749 + $effect(() => {
750 + if (typeof document !== 'undefined') {
751 + const checkDark = () => {
752 + isDarkMode = document.documentElement.classList.contains('dark');
753 + };
754 + checkDark();
755 + const observer = new MutationObserver(checkDark);
756 + observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
757 + return () => observer.disconnect();
758 + }
759 + });
760 +
761 + // Modo claro: gris-azulado pastel + terracota
762 + // Modo oscuro: azul claro + ámbar dorado
763 + let colorA = $derived(isDarkMode ? '#7EB8DA' : '#6B8299');
764 + let colorB = $derived(isDarkMode ? '#D4A574' : '#C4897D');
585 765
586 // Actualizar tweened de comparación 766 // Actualizar tweened de comparación
587 $effect(() => { 767 $effect(() => {
...@@ -673,10 +853,13 @@ ...@@ -673,10 +853,13 @@
673 attributeFilter: ['class'] 853 attributeFilter: ['class']
674 }); 854 });
675 855
676 - // Cargar datos 856 + // Cargar datos base y entidades
677 const loadStart = performance.now(); 857 const loadStart = performance.now();
678 await Promise.all([loadData(), loadEntidades()]); 858 await Promise.all([loadData(), loadEntidades()]);
679 console.log(`[CLIENT] Promise.all: ${(performance.now() - loadStart).toFixed(0)}ms`); 859 console.log(`[CLIENT] Promise.all: ${(performance.now() - loadStart).toFixed(0)}ms`);
860 +
861 + // Aplicar estado desde URL (después de tener entidades cargadas)
862 + await applyUrlState();
680 console.log(`[CLIENT] onMount total: ${(performance.now() - mountStart).toFixed(0)}ms`); 863 console.log(`[CLIENT] onMount total: ${(performance.now() - mountStart).toFixed(0)}ms`);
681 864
682 // Cerrar dropdowns al hacer click fuera 865 // Cerrar dropdowns al hacer click fuera
...@@ -705,16 +888,21 @@ ...@@ -705,16 +888,21 @@
705 $effect(() => { 888 $effect(() => {
706 if (mounted && objetoCodigo !== previousCodigo) { 889 if (mounted && objetoCodigo !== previousCodigo) {
707 previousCodigo = objetoCodigo; 890 previousCodigo = objetoCodigo;
708 - // Resetear estado 891 + // Resetear estado base
709 loading = true; 892 loading = true;
710 selectedEntity = null; 893 selectedEntity = null;
711 entidadCompararA = null; 894 entidadCompararA = null;
712 entidadCompararB = null; 895 entidadCompararB = null;
713 datosCompararA = { anual: [], historico: null }; 896 datosCompararA = { anual: [], historico: null };
714 datosCompararB = { anual: [], historico: null }; 897 datosCompararB = { anual: [], historico: null };
715 - vista = 'agregado'; 898 + alertDefaultDismissed = false;
716 - // Recargar datos 899 + alertRangosDismissed = false;
717 - loadData(); 900 +
901 + // Recargar datos y entidades, luego aplicar URL
902 + (async () => {
903 + await Promise.all([loadData(), loadEntidades()]);
904 + await applyUrlState();
905 + })();
718 } 906 }
719 }); 907 });
720 908
...@@ -724,6 +912,49 @@ ...@@ -724,6 +912,49 @@
724 if (mounted && selectedEntity !== previousEntity) { 912 if (mounted && selectedEntity !== previousEntity) {
725 previousEntity = selectedEntity; 913 previousEntity = selectedEntity;
726 loadData(); 914 loadData();
915 + // Actualizar URL
916 + updateUrl({
917 + vista: vista,
918 + entidad: selectedEntity?.entidad || null
919 + });
920 + }
921 + });
922 +
923 + // Actualizar URL cuando cambian entidades de comparación
924 + let prevEntidadA = entidadCompararA;
925 + let prevEntidadB = entidadCompararB;
926 + $effect(() => {
927 + if (mounted && vista === 'comparar') {
928 + if (prevEntidadA !== entidadCompararA || prevEntidadB !== entidadCompararB) {
929 + prevEntidadA = entidadCompararA;
930 + prevEntidadB = entidadCompararB;
931 + updateUrl({
932 + vista: 'comparar',
933 + entidadA: entidadCompararA?.entidad || null,
934 + entidadB: entidadCompararB?.entidad || null
935 + });
936 + }
937 + }
938 + });
939 +
940 + // Actualizar URL cuando cambia el modo vista
941 + let previousVista = vista;
942 + $effect(() => {
943 + if (mounted && vista !== previousVista) {
944 + previousVista = vista;
945 + if (vista === 'comparar') {
946 + initComparacion();
947 + updateUrl({
948 + vista: 'comparar',
949 + entidadA: entidadCompararA?.entidad || null,
950 + entidadB: entidadCompararB?.entidad || null
951 + });
952 + } else {
953 + updateUrl({
954 + vista: 'agregado',
955 + entidad: selectedEntity?.entidad || null
956 + });
957 + }
727 } 958 }
728 }); 959 });
729 960
...@@ -991,16 +1222,24 @@ ...@@ -991,16 +1222,24 @@
991 {/if} 1222 {/if}
992 1223
993 {:else} 1224 {:else}
994 - <!-- VISTA COMPARAR (mantiene layout original) --> 1225 + <!-- VISTA COMPARAR -->
995 - <div class="toolbar"> 1226 + <main class="main-comparar">
996 - <ObjetoSearch /> 1227 + <!-- Card principal del gráfico -->
997 - <VistaToggle bind:vista /> 1228 + <div class="chart-card-comparar">
998 - </div> 1229 + <!-- Header: pills + toggle -->
999 - <main> 1230 + <div class="chart-top-row">
1000 - <!-- VISTA COMPARAR --> 1231 + <div class="main-title-pills">
1232 + <span class="pill pill-nivel">{objeto.nivel}</span>
1233 + <span class="pill pill-codigo">{objeto.codigo}</span>
1234 + </div>
1235 + <VistaToggle bind:vista />
1236 + </div>
1001 1237
1002 - <!-- Selectores de entidades con búsqueda --> 1238 + <!-- Título principal -->
1003 - <div class="comparador-selectors"> 1239 + <h2 class="main-title-name">{objeto.nombre}</h2>
1240 +
1241 + <!-- Selectores de entidades con búsqueda -->
1242 + <div class="comparador-selectors">
1004 <div class="selector-group comparador-dropdown-container"> 1243 <div class="selector-group comparador-dropdown-container">
1005 <span class="selector-dot" style="background: {colorA}"></span> 1244 <span class="selector-dot" style="background: {colorA}"></span>
1006 <div class="relative" style="flex: 1;"> 1245 <div class="relative" style="flex: 1;">
...@@ -1080,20 +1319,65 @@ ...@@ -1080,20 +1319,65 @@
1080 {/if} 1319 {/if}
1081 </div> 1320 </div>
1082 </div> 1321 </div>
1322 +
1323 + <!-- Buscador de objeto de gasto -->
1324 + <div class="comparador-search-wrapper">
1325 + <ObjetoSearch preserveVista={true} {vista} />
1326 + </div>
1083 </div> 1327 </div>
1084 1328
1085 - <!-- Mensaje si no hay entidades seleccionadas --> 1329 + <!-- Mensaje de selección por defecto -->
1086 - {#if !entidadCompararA || !entidadCompararB} 1330 + {#if isDefaultSelection && entidadCompararA && entidadCompararB && !alertDefaultDismissed}
1087 - <div class="comparador-placeholder"> 1331 + <div class="comparador-hint">
1088 - <p>Selecciona dos entidades para comparar su gasto en <strong>{objeto.nombre}</strong></p> 1332 + <svg class="alert-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1333 + <circle cx="12" cy="12" r="10"/>
1334 + <line x1="12" y1="16" x2="12" y2="12"/>
1335 + <line x1="12" y1="8" x2="12.01" y2="8"/>
1336 + </svg>
1337 + <span>Mostrando las dos entidades con mayor gasto. Puedes cambiar la selección arriba.</span>
1338 + <button class="alert-close" onclick={() => alertDefaultDismissed = true} aria-label="Cerrar">
1339 + <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1340 + <line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
1341 + </svg>
1342 + </button>
1089 </div> 1343 </div>
1090 - {:else if loadingComparacion} 1344 + {/if}
1345 +
1346 + <!-- Advertencia de rangos diferentes -->
1347 + {#if tienenRangosDiferentes && entidadCompararA && entidadCompararB && !loadingComparacion && !alertRangosDismissed}
1348 + <div class="comparador-warning">
1349 + <svg class="alert-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1350 + <path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/>
1351 + <line x1="12" y1="9" x2="12" y2="13"/>
1352 + <line x1="12" y1="17" x2="12.01" y2="17"/>
1353 + </svg>
1354 + <span>
1355 + Rangos de tiempo diferentes:
1356 + <strong>{entidadCompararA.entidad_desc.substring(0, 20)}{entidadCompararA.entidad_desc.length > 20 ? '...' : ''}</strong> ({rangoInfoA?.desde}-{rangoInfoA?.hasta})
1357 + vs
1358 + <strong>{entidadCompararB.entidad_desc.substring(0, 20)}{entidadCompararB.entidad_desc.length > 20 ? '...' : ''}</strong> ({rangoInfoB?.desde}-{rangoInfoB?.hasta}).
1359 + Solo se comparan {añosComunesTotales.length} años en común.
1360 + </span>
1361 + <button class="alert-close" onclick={() => alertRangosDismissed = true} aria-label="Cerrar">
1362 + <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1363 + <line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
1364 + </svg>
1365 + </button>
1366 + </div>
1367 + {/if}
1368 +
1369 + <!-- Estados del comparador -->
1370 + {#if loadingComparacion}
1091 <div class="comparador-placeholder"> 1371 <div class="comparador-placeholder">
1092 <Spinner size={48} color="var(--theme-texto, #5A5650)" /> 1372 <Spinner size={48} color="var(--theme-texto, #5A5650)" />
1093 </div> 1373 </div>
1374 + {:else if !entidadCompararA && !entidadCompararB}
1375 + <div class="comparador-placeholder">
1376 + <p>Selecciona dos entidades para comparar su gasto en <strong>{objeto.nombre}</strong></p>
1377 + </div>
1094 {:else if añosComparacion.length === 0} 1378 {:else if añosComparacion.length === 0}
1095 <div class="comparador-placeholder"> 1379 <div class="comparador-placeholder">
1096 - <p>Las entidades seleccionadas no tienen años en común para este objeto.</p> 1380 + <p>No hay datos disponibles para comparar.</p>
1097 </div> 1381 </div>
1098 {:else} 1382 {:else}
1099 <!-- Selector de rango de años --> 1383 <!-- Selector de rango de años -->
...@@ -1110,28 +1394,28 @@ ...@@ -1110,28 +1394,28 @@
1110 {/if} 1394 {/if}
1111 1395
1112 <!-- Gráfico comparativo --> 1396 <!-- Gráfico comparativo -->
1113 - <div class="chart-section"> 1397 + <div class="chart-header">
1114 - <div class="chart-header"> 1398 + <span class="chart-title">Bs/habitante <span class="chart-period">· {displayPeriodoComparar}</span></span>
1115 - <span class="chart-title">Bs/habitante</span> 1399 + </div>
1116 - <span class="chart-period">{displayPeriodoComparar}</span>
1117 - </div>
1118 -
1119 - <div class="chart-wrapper chart-wrapper-scroll">
1120 - <div class="y-axis y-axis-sticky">
1121 - <span class="y-label">Bs {formatMonto(Math.round(maxComparacion))}</span>
1122 - <span class="y-label">Bs {formatMonto(Math.round(maxComparacion / 2))}</span>
1123 - <span class="y-label">Bs 0</span>
1124 - </div>
1125 1400
1126 - <div 1401 + <div class="chart-comparar-wrapper">
1127 - class="chart chart-comparar" 1402 + <!-- Área principal del gráfico -->
1128 - class:chart-muchos-años={añosComparacion.length > 15} 1403 + <div class="chart-comparar-inner">
1129 - onmouseleave={() => hoveredYearComparar = null} 1404 + <!-- Y-axis labels -->
1130 - role="group" 1405 + <span class="y-label-comparar" style="bottom: 100%">Bs {formatMonto(Math.round(maxComparacion))}</span>
1131 - > 1406 + <span class="y-label-comparar" style="bottom: 50%">Bs {formatMonto(Math.round(maxComparacion / 2))}</span>
1407 + <span class="y-label-comparar" style="bottom: 0%">Bs 0</span>
1408 +
1409 + <!-- Grid lines -->
1410 + <div class="grid-line-comparar" style="bottom: 100%"></div>
1411 + <div class="grid-line-comparar" style="bottom: 50%"></div>
1412 + <div class="grid-line-comparar" style="bottom: 0%"></div>
1413 +
1414 + <!-- Barras -->
1415 + <div class="bars-comparar" onmouseleave={() => hoveredYearComparar = null} role="group">
1132 {#each añosComparacion as año, i} 1416 {#each añosComparacion as año, i}
1133 <div 1417 <div
1134 - class="bar-group" 1418 + class="bar-group-comparar"
1135 class:active={hoveredYearComparar === año} 1419 class:active={hoveredYearComparar === año}
1136 onmouseenter={() => hoveredYearComparar = año} 1420 onmouseenter={() => hoveredYearComparar = año}
1137 role="button" 1421 role="button"
...@@ -1139,33 +1423,62 @@ ...@@ -1139,33 +1423,62 @@
1139 > 1423 >
1140 <div class="bars-pair"> 1424 <div class="bars-pair">
1141 <div 1425 <div
1142 - class="bar bar-a" 1426 + class="bar-comparar"
1143 - style="height: {((datosGraficoA[i]?.per_capita || 0) / maxComparacion) * 100}%; --bar-color: {colorA}" 1427 + style="height: {((datosGraficoA[i]?.per_capita || 0) / maxComparacion) * 100}%; background: {colorA}"
1144 ></div> 1428 ></div>
1145 <div 1429 <div
1146 - class="bar bar-b" 1430 + class="bar-comparar"
1147 - style="height: {((datosGraficoB[i]?.per_capita || 0) / maxComparacion) * 100}%; --bar-color: {colorB}" 1431 + style="height: {((datosGraficoB[i]?.per_capita || 0) / maxComparacion) * 100}%; background: {colorB}"
1148 ></div> 1432 ></div>
1149 </div> 1433 </div>
1150 - <span class="bar-label visible" class:highlight={hoveredYearComparar === año}>{año}</span>
1151 </div> 1434 </div>
1152 {/each} 1435 {/each}
1153 </div> 1436 </div>
1154 </div> 1437 </div>
1155 1438
1156 - <div class="chart-legend"> 1439 + <!-- Eje X -->
1440 + <div class="x-axis-comparar">
1441 + {#each añosComparacion as año, i}
1442 + {@const isFirst = i === 0}
1443 + {@const isLast = i === añosComparacion.length - 1}
1444 + {@const isMid = i === Math.floor(añosComparacion.length / 2) && añosComparacion.length > 5}
1445 + {@const showLabel = isFirst || isLast || isMid || hoveredYearComparar === año}
1446 + <span class="x-label-comparar" class:visible={showLabel}>{año}</span>
1447 + {/each}
1448 + </div>
1449 + </div>
1450 +
1451 + <div class="chart-legend">
1452 + {#if entidadCompararA}
1157 <span class="legend-item"><span class="legend-dot" style="background: {colorA}"></span>{entidadCompararA.entidad_desc}</span> 1453 <span class="legend-item"><span class="legend-dot" style="background: {colorA}"></span>{entidadCompararA.entidad_desc}</span>
1454 + {/if}
1455 + {#if entidadCompararB}
1158 <span class="legend-item"><span class="legend-dot" style="background: {colorB}"></span>{entidadCompararB.entidad_desc}</span> 1456 <span class="legend-item"><span class="legend-dot" style="background: {colorB}"></span>{entidadCompararB.entidad_desc}</span>
1159 - </div> 1457 + {:else}
1458 + <span class="legend-item legend-pending"><span class="legend-dot" style="background: {colorB}; opacity: 0.3"></span>Selecciona una entidad</span>
1459 + {/if}
1460 + </div>
1461 + {/if}
1462 + </div>
1463 +
1464 + <!-- Mensaje para seleccionar segunda entidad -->
1465 + {#if soloUnaEntidad}
1466 + <div class="comparador-hint comparador-hint-select">
1467 + <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1468 + <path d="M5 12h14M12 5l7 7-7 7"/>
1469 + </svg>
1470 + <span>Selecciona la segunda entidad arriba para ver la comparación completa.</span>
1160 </div> 1471 </div>
1472 + {/if}
1161 1473
1162 - <!-- KPIs comparativos - tabla --> 1474 + <!-- KPIs comparativos - tabla (solo cuando hay ambas entidades) -->
1163 - <div class="kpis-tabla"> 1475 + {#if entidadCompararA && entidadCompararB}
1476 + <div class="kpis-card-comparar">
1164 <!-- Header --> 1477 <!-- Header -->
1165 <div class="tabla-header"> 1478 <div class="tabla-header">
1166 <span class="tabla-label"></span> 1479 <span class="tabla-label"></span>
1167 - <span class="tabla-val" style="color: {colorA}">{entidadCompararA.entidad_desc?.slice(0, 20)}{entidadCompararA.entidad_desc?.length > 20 ? '...' : ''}</span> 1480 + <span class="tabla-val tabla-entidad" style="color: {colorA}">{entidadCompararA.entidad_desc}</span>
1168 - <span class="tabla-val" style="color: {colorB}">{entidadCompararB.entidad_desc?.slice(0, 20)}{entidadCompararB.entidad_desc?.length > 20 ? '...' : ''}</span> 1481 + <span class="tabla-val tabla-entidad" style="color: {colorB}">{entidadCompararB.entidad_desc}</span>
1169 </div> 1482 </div>
1170 <!-- Gasto --> 1483 <!-- Gasto -->
1171 <div class="tabla-row"> 1484 <div class="tabla-row">
...@@ -1192,7 +1505,7 @@ ...@@ -1192,7 +1505,7 @@
1192 <span class="tabla-val">#{displayRankingB} de {displayNEntidadesB}</span> 1505 <span class="tabla-val">#{displayRankingB} de {displayNEntidadesB}</span>
1193 </div> 1506 </div>
1194 </div> 1507 </div>
1195 - {/if} 1508 + {/if}
1196 1509
1197 </main> 1510 </main>
1198 {/if} 1511 {/if}
...@@ -1823,6 +2136,31 @@ ...@@ -1823,6 +2136,31 @@
1823 border-left-color: var(--theme-accent); 2136 border-left-color: var(--theme-accent);
1824 } 2137 }
1825 2138
2139 + :global(html.dark) .tree-node-current .tree-code,
2140 + :global(html.dark) .tree-node-current .tree-name {
2141 + color: #1a1a1a;
2142 + }
2143 +
2144 + /* Dark mode - Context card texts */
2145 + :global(html.dark) .context-desc,
2146 + :global(html.dark) .context-hint {
2147 + color: rgba(255, 255, 255, 0.6);
2148 + }
2149 +
2150 + /* Dark mode - Definición */
2151 + :global(html.dark) .def-status,
2152 + :global(html.dark) .def-btn {
2153 + color: rgba(255, 255, 255, 0.7);
2154 + }
2155 +
2156 + :global(html.dark) .def-btn {
2157 + background: rgba(255, 255, 255, 0.1);
2158 + }
2159 +
2160 + :global(html.dark) .def-btn:hover {
2161 + background: rgba(255, 255, 255, 0.15);
2162 + }
2163 +
1826 :global(html.dark) .tree-children { 2164 :global(html.dark) .tree-children {
1827 border-left-color: rgba(201, 167, 81, 0.3); 2165 border-left-color: rgba(201, 167, 81, 0.3);
1828 } 2166 }
...@@ -2086,8 +2424,9 @@ ...@@ -2086,8 +2424,9 @@
2086 background: var(--theme-fill); 2424 background: var(--theme-fill);
2087 border: 1px solid var(--theme-borde); 2425 border: 1px solid var(--theme-borde);
2088 border-radius: 6px; 2426 border-radius: 6px;
2427 + font-family: 'Qanelas', var(--font-sans);
2089 color: var(--theme-titulo); 2428 color: var(--theme-titulo);
2090 - font-size: 0.875rem; 2429 + font-size: 0.8125rem;
2091 } 2430 }
2092 .search-input:focus { 2431 .search-input:focus {
2093 outline: none; 2432 outline: none;
...@@ -2105,6 +2444,7 @@ ...@@ -2105,6 +2444,7 @@
2105 padding: 0.625rem 0.75rem; 2444 padding: 0.625rem 0.75rem;
2106 background: transparent; 2445 background: transparent;
2107 border: none; 2446 border: none;
2447 + font-family: 'Qanelas', var(--font-sans);
2108 color: var(--theme-titulo); 2448 color: var(--theme-titulo);
2109 font-size: 0.813rem; 2449 font-size: 0.813rem;
2110 text-align: left; 2450 text-align: left;
...@@ -2176,21 +2516,12 @@ ...@@ -2176,21 +2516,12 @@
2176 padding-bottom: 1.5rem; 2516 padding-bottom: 1.5rem;
2177 } 2517 }
2178 2518
2179 - .chart-section:has(.chart-comparar) {
2180 - margin-bottom: 0;
2181 - }
2182 -
2183 - .chart-section:has(.chart-comparar) .y-axis {
2184 - height: calc(100vh - 380px);
2185 - min-height: 140px;
2186 - max-height: 280px;
2187 - }
2188 -
2189 .chart-top-row { 2519 .chart-top-row {
2190 display: flex; 2520 display: flex;
2191 justify-content: space-between; 2521 justify-content: space-between;
2192 align-items: center; 2522 align-items: center;
2193 margin-bottom: 0.375rem; 2523 margin-bottom: 0.375rem;
2524 + flex-shrink: 0;
2194 } 2525 }
2195 2526
2196 .main-title-pills { 2527 .main-title-pills {
...@@ -2223,10 +2554,12 @@ ...@@ -2223,10 +2554,12 @@
2223 color: var(--theme-titulo); 2554 color: var(--theme-titulo);
2224 margin: 0 0 0.5rem 0; 2555 margin: 0 0 0.5rem 0;
2225 line-height: 1.2; 2556 line-height: 1.2;
2557 + flex-shrink: 0;
2226 } 2558 }
2227 2559
2228 .chart-header { 2560 .chart-header {
2229 margin-bottom: 0.5rem; 2561 margin-bottom: 0.5rem;
2562 + flex-shrink: 0;
2230 } 2563 }
2231 2564
2232 .chart-title { 2565 .chart-title {
...@@ -2260,6 +2593,7 @@ ...@@ -2260,6 +2593,7 @@
2260 .chart-wrapper { 2593 .chart-wrapper {
2261 display: flex; 2594 display: flex;
2262 gap: 0.75rem; 2595 gap: 0.75rem;
2596 + position: relative;
2263 } 2597 }
2264 2598
2265 .chart-wrapper-scroll { 2599 .chart-wrapper-scroll {
...@@ -2295,10 +2629,6 @@ ...@@ -2295,10 +2629,6 @@
2295 min-width: max-content; 2629 min-width: max-content;
2296 } 2630 }
2297 2631
2298 - .chart-muchos-años .bar-group {
2299 - min-width: 40px;
2300 - }
2301 -
2302 .y-axis { 2632 .y-axis {
2303 display: flex; 2633 display: flex;
2304 flex-direction: column; 2634 flex-direction: column;
...@@ -2390,9 +2720,18 @@ ...@@ -2390,9 +2720,18 @@
2390 text-align: center; 2720 text-align: center;
2391 min-width: 0; 2721 min-width: 0;
2392 padding: 0.625rem 0.5rem; 2722 padding: 0.625rem 0.5rem;
2393 - background: var(--theme-body); 2723 + background: transparent;
2394 border-radius: 8px; 2724 border-radius: 8px;
2395 - border: 1px solid var(--theme-borde); 2725 + border: 1px solid rgba(0, 0, 0, 0.08);
2726 + }
2727 +
2728 + :global(html.dark) .kpi {
2729 + border-color: rgba(255, 255, 255, 0.1);
2730 + }
2731 +
2732 + :global(html.dark) .kpi-value,
2733 + :global(html.dark) .kpi-label {
2734 + color: rgba(255, 255, 255, 0.7);
2396 } 2735 }
2397 2736
2398 .kpi-value { 2737 .kpi-value {
...@@ -2507,6 +2846,20 @@ ...@@ -2507,6 +2846,20 @@
2507 text-align: right; 2846 text-align: right;
2508 } 2847 }
2509 2848
2849 + /* Dark mode - Ranking texts */
2850 + :global(html.dark) .ranking-desc {
2851 + color: rgba(255, 255, 255, 0.6);
2852 + }
2853 +
2854 + :global(html.dark) .ranking-position,
2855 + :global(html.dark) .ranking-pct {
2856 + color: rgba(255, 255, 255, 0.85);
2857 + }
2858 +
2859 + :global(html.dark) .ranking-name {
2860 + color: rgba(255, 255, 255, 0.7);
2861 + }
2862 +
2510 .see-all { 2863 .see-all {
2511 background: transparent; 2864 background: transparent;
2512 border: 1px solid var(--theme-borde); 2865 border: 1px solid var(--theme-borde);
...@@ -2525,25 +2878,82 @@ ...@@ -2525,25 +2878,82 @@
2525 } 2878 }
2526 2879
2527 /* Comparador inline */ 2880 /* Comparador inline */
2881 + .main-comparar {
2882 + display: flex;
2883 + flex-direction: column;
2884 + gap: 0.75rem;
2885 + padding: 0.75rem 2rem 1.5rem;
2886 + width: 100%;
2887 + max-width: 1200px;
2888 + margin: 0 auto;
2889 + min-height: calc(100vh - 60px);
2890 + }
2891 +
2892 + .chart-card-comparar {
2893 + background: var(--theme-surface);
2894 + border-radius: 16px;
2895 + padding: 1rem 1.5rem;
2896 + margin-bottom: 0.75rem;
2897 + display: flex;
2898 + flex-direction: column;
2899 + flex: 1;
2900 + min-height: 0;
2901 + }
2902 +
2528 .comparador-selectors { 2903 .comparador-selectors {
2529 display: flex; 2904 display: flex;
2530 - align-items: flex-start; 2905 + align-items: center;
2531 gap: 1rem; 2906 gap: 1rem;
2532 - margin-bottom: 0.75rem; 2907 + margin-bottom: 1rem;
2908 + flex-shrink: 0;
2909 + }
2910 +
2911 + .comparador-search-wrapper {
2912 + flex: 1;
2913 + max-width: 280px;
2533 } 2914 }
2534 2915
2535 .comparador-dropdown-container { 2916 .comparador-dropdown-container {
2536 position: relative; 2917 position: relative;
2537 } 2918 }
2538 2919
2920 + .selector-btn {
2921 + display: flex;
2922 + align-items: center;
2923 + gap: 0.5rem;
2924 + padding: 0.625rem 1rem;
2925 + background: var(--theme-body);
2926 + border: 1px solid var(--theme-borde);
2927 + border-radius: 8px;
2928 + font-family: 'Qanelas', var(--font-sans);
2929 + font-size: 0.8125rem;
2930 + color: var(--theme-titulo);
2931 + cursor: pointer;
2932 + transition: border-color 0.2s, background 0.2s;
2933 + }
2934 +
2935 + .selector-btn:hover {
2936 + border-color: var(--theme-accent);
2937 + background: var(--theme-surface-hover);
2938 + }
2939 +
2539 .comparador-btn { 2940 .comparador-btn {
2540 width: 100%; 2941 width: 100%;
2541 justify-content: space-between; 2942 justify-content: space-between;
2542 } 2943 }
2543 2944
2544 .comparador-panel { 2945 .comparador-panel {
2946 + position: absolute;
2947 + top: calc(100% + 4px);
2948 + left: 0;
2545 width: 100%; 2949 width: 100%;
2546 min-width: 280px; 2950 min-width: 280px;
2951 + background: var(--theme-surface);
2952 + border: 1px solid var(--theme-borde);
2953 + border-radius: 10px;
2954 + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
2955 + z-index: 100;
2956 + overflow: hidden;
2547 } 2957 }
2548 2958
2549 .comparador-placeholder { 2959 .comparador-placeholder {
...@@ -2559,6 +2969,7 @@ ...@@ -2559,6 +2969,7 @@
2559 } 2969 }
2560 2970
2561 .comparador-placeholder p { 2971 .comparador-placeholder p {
2972 + font-family: 'Qanelas', var(--font-sans);
2562 color: var(--theme-texto); 2973 color: var(--theme-texto);
2563 font-size: 0.875rem; 2974 font-size: 0.875rem;
2564 margin: 0; 2975 margin: 0;
...@@ -2568,14 +2979,99 @@ ...@@ -2568,14 +2979,99 @@
2568 color: var(--theme-texto); 2979 color: var(--theme-texto);
2569 } 2980 }
2570 2981
2982 + .comparador-hint {
2983 + display: flex;
2984 + align-items: flex-start;
2985 + gap: 0.5rem;
2986 + padding: 0.75rem 1rem;
2987 + background: rgba(107, 159, 212, 0.1);
2988 + border-radius: 8px;
2989 + margin-bottom: 0.75rem;
2990 + font-family: 'Qanelas', var(--font-sans);
2991 + font-size: 0.8125rem;
2992 + color: #6B9FD4;
2993 + flex-shrink: 0;
2994 + }
2995 +
2996 + .comparador-hint .alert-icon {
2997 + flex-shrink: 0;
2998 + margin-top: 0.1rem;
2999 + }
3000 +
3001 + .comparador-hint span {
3002 + flex: 1;
3003 + }
3004 +
3005 + .comparador-warning {
3006 + display: flex;
3007 + align-items: flex-start;
3008 + gap: 0.5rem;
3009 + padding: 0.75rem 1rem;
3010 + background: rgba(230, 176, 75, 0.1);
3011 + border-radius: 8px;
3012 + margin-bottom: 0.75rem;
3013 + font-family: 'Qanelas', var(--font-sans);
3014 + font-size: 0.8125rem;
3015 + color: #C9A751;
3016 + flex-shrink: 0;
3017 + }
3018 +
3019 + .comparador-warning .alert-icon {
3020 + flex-shrink: 0;
3021 + margin-top: 0.1rem;
3022 + }
3023 +
3024 + .comparador-warning span {
3025 + flex: 1;
3026 + }
3027 +
3028 + .comparador-warning strong {
3029 + color: inherit;
3030 + }
3031 +
3032 + /* Botón cerrar alertas */
3033 + .alert-close {
3034 + flex-shrink: 0;
3035 + display: flex;
3036 + align-items: center;
3037 + justify-content: center;
3038 + padding: 0.25rem;
3039 + border: none;
3040 + background: transparent;
3041 + color: inherit;
3042 + opacity: 0.6;
3043 + cursor: pointer;
3044 + border-radius: 4px;
3045 + transition: opacity 0.15s, background 0.15s;
3046 + }
3047 +
3048 + .alert-close:hover {
3049 + opacity: 1;
3050 + background: rgba(0, 0, 0, 0.1);
3051 + }
3052 +
3053 + :global(html.dark) .alert-close:hover {
3054 + background: rgba(255, 255, 255, 0.1);
3055 + }
3056 +
3057 + :global(html.dark) .comparador-hint {
3058 + background: rgba(107, 159, 212, 0.15);
3059 + }
3060 +
3061 + :global(html.dark) .comparador-warning {
3062 + background: rgba(201, 167, 81, 0.15);
3063 + }
3064 +
2571 .rango-selector { 3065 .rango-selector {
2572 display: flex; 3066 display: flex;
2573 align-items: center; 3067 align-items: center;
2574 gap: 0.75rem; 3068 gap: 0.75rem;
2575 margin-bottom: 0.75rem; 3069 margin-bottom: 0.75rem;
3070 + flex-shrink: 0;
2576 } 3071 }
2577 3072
2578 .rango-label { 3073 .rango-label {
3074 + font-family: 'Qanelas', var(--font-sans);
2579 font-size: 0.75rem; 3075 font-size: 0.75rem;
2580 color: var(--theme-texto); 3076 color: var(--theme-texto);
2581 } 3077 }
...@@ -2587,24 +3083,31 @@ ...@@ -2587,24 +3083,31 @@
2587 3083
2588 .rango-btn { 3084 .rango-btn {
2589 padding: 0.375rem 0.75rem; 3085 padding: 0.375rem 0.75rem;
3086 + font-family: 'Qanelas', var(--font-sans);
2590 font-size: 0.6875rem; 3087 font-size: 0.6875rem;
2591 - font-family: 'DM Mono', monospace; 3088 + font-weight: 500;
2592 color: var(--theme-texto); 3089 color: var(--theme-texto);
2593 background: transparent; 3090 background: transparent;
2594 - border: none; 3091 + border: 1px solid transparent;
2595 border-radius: 6px; 3092 border-radius: 6px;
2596 cursor: pointer; 3093 cursor: pointer;
2597 transition: all 0.2s; 3094 transition: all 0.2s;
2598 } 3095 }
2599 3096
2600 .rango-btn:hover { 3097 .rango-btn:hover {
2601 - color: var(--theme-texto); 3098 + color: var(--theme-titulo);
2602 - background: rgba(255,255,255,0.05); 3099 + background: var(--theme-surface-hover);
2603 } 3100 }
2604 3101
2605 .rango-btn.active { 3102 .rango-btn.active {
2606 color: var(--theme-titulo); 3103 color: var(--theme-titulo);
2607 - background: rgba(255,255,255,0.1); 3104 + background: #e8e8e8;
3105 + border-color: transparent;
3106 + font-weight: 600;
3107 + }
3108 +
3109 + :global(html.dark) .rango-btn.active {
3110 + background: rgba(255, 255, 255, 0.15);
2608 } 3111 }
2609 3112
2610 .dropdown-item:disabled { 3113 .dropdown-item:disabled {
...@@ -2648,63 +3151,118 @@ ...@@ -2648,63 +3151,118 @@
2648 font-size: 0.75rem; 3151 font-size: 0.75rem;
2649 color: var(--theme-texto); 3152 color: var(--theme-texto);
2650 flex-shrink: 0; 3153 flex-shrink: 0;
3154 + padding: 0.5rem 0;
2651 } 3155 }
2652 3156
2653 - .chart-comparar { 3157 + .chart-comparar-wrapper {
2654 - gap: 1.5rem; 3158 + position: relative;
2655 - justify-content: center; 3159 + width: 100%;
2656 - height: calc(100vh - 380px); 3160 + flex: 1;
2657 - min-height: 140px; 3161 + min-height: 180px;
2658 - max-height: 280px; 3162 + }
3163 +
3164 + .chart-comparar-inner {
3165 + position: absolute;
3166 + top: 10px;
3167 + bottom: 30px;
3168 + left: 50px;
3169 + right: 10px;
3170 + }
3171 +
3172 + .y-label-comparar {
3173 + position: absolute;
3174 + left: -50px;
3175 + width: 45px;
3176 + text-align: right;
3177 + transform: translateY(50%);
3178 + font-family: 'Qanelas', var(--font-sans);
3179 + font-size: 0.6875rem;
3180 + color: var(--theme-texto);
3181 + white-space: nowrap;
2659 } 3182 }
2660 3183
2661 - .chart-comparar + .chart-legend + .kpis-comparar { 3184 + .grid-line-comparar {
2662 - margin-top: 0; 3185 + position: absolute;
3186 + left: 0;
3187 + right: 0;
3188 + border-top: 0.5px dotted var(--theme-texto);
3189 + opacity: 0.15;
3190 + pointer-events: none;
2663 } 3191 }
2664 3192
2665 - .bar-group { 3193 + .bars-comparar {
3194 + position: absolute;
3195 + top: 0;
3196 + left: 0;
3197 + right: 0;
3198 + bottom: 0;
2666 display: flex; 3199 display: flex;
2667 - flex-direction: column; 3200 + align-items: flex-end;
2668 - align-items: center; 3201 + gap: 3px;
3202 + }
3203 +
3204 + .bar-group-comparar {
3205 + flex: 1;
2669 height: 100%; 3206 height: 100%;
3207 + display: flex;
3208 + align-items: flex-end;
3209 + justify-content: center;
3210 + cursor: pointer;
2670 } 3211 }
2671 3212
2672 - .bars-pair { 3213 + .bar-group-comparar .bars-pair {
2673 display: flex; 3214 display: flex;
2674 align-items: flex-end; 3215 align-items: flex-end;
2675 - gap: 4px; 3216 + gap: 2px;
3217 + height: 100%;
3218 + width: 100%;
3219 + justify-content: center;
3220 + }
3221 +
3222 + .bar-comparar {
2676 flex: 1; 3223 flex: 1;
3224 + max-width: 24px;
3225 + border-radius: 3px 3px 0 0;
3226 + min-height: 2px;
3227 + transition: opacity 0.15s ease;
2677 } 3228 }
2678 3229
2679 - .bar-a, .bar-b { 3230 + .bars-comparar:hover .bar-comparar {
2680 - width: 20px; 3231 + opacity: 0.35;
2681 - border-radius: 4px 4px 0 0;
2682 - min-height: 4px;
2683 - background: var(--bar-color);
2684 - transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1), filter 0.25s ease;
2685 } 3232 }
2686 3233
2687 - .bar-group.active .bar-a, 3234 + .bar-group-comparar.active .bar-comparar {
2688 - .bar-group.active .bar-b { 3235 + opacity: 1;
2689 - filter: brightness(1.15);
2690 } 3236 }
2691 3237
2692 - .bar-group { 3238 + .x-axis-comparar {
2693 - cursor: pointer; 3239 + position: absolute;
2694 - padding: 0.5rem 0.35rem; 3240 + bottom: 0;
2695 - margin: -0.5rem -0.35rem; 3241 + left: 50px;
2696 - border-radius: 6px; 3242 + right: 10px;
2697 - transition: background 0.3s cubic-bezier(0.4, 0, 0.2, 1); 3243 + height: 30px;
3244 + display: flex;
3245 + align-items: center;
2698 } 3246 }
2699 3247
2700 - .bar-group.active { 3248 + .x-label-comparar {
2701 - background: rgba(42, 157, 143, 0.15); 3249 + flex: 1;
3250 + text-align: center;
3251 + font-family: 'Qanelas', var(--font-sans);
3252 + font-size: 0.6875rem;
3253 + color: var(--theme-texto);
3254 + opacity: 0;
3255 + transition: opacity 0.2s ease;
2702 } 3256 }
2703 3257
2704 - .bar-label.highlight { 3258 + .x-label-comparar.visible {
2705 - color: var(--theme-titulo); 3259 + opacity: 1;
2706 - font-weight: 500; 3260 + }
2707 - transition: color 0.25s ease; 3261 +
3262 + .kpis-card-comparar {
3263 + background: var(--theme-surface);
3264 + border-radius: 16px;
3265 + padding: 0.75rem 1.25rem;
2708 } 3266 }
2709 3267
2710 .chart-legend { 3268 .chart-legend {
...@@ -2713,12 +3271,14 @@ ...@@ -2713,12 +3271,14 @@
2713 gap: 2rem; 3271 gap: 2rem;
2714 margin-top: 0.5rem; 3272 margin-top: 0.5rem;
2715 margin-bottom: 0.75rem; 3273 margin-bottom: 0.75rem;
3274 + flex-shrink: 0;
2716 } 3275 }
2717 3276
2718 .legend-item { 3277 .legend-item {
2719 display: flex; 3278 display: flex;
2720 align-items: center; 3279 align-items: center;
2721 gap: 0.5rem; 3280 gap: 0.5rem;
3281 + font-family: 'Qanelas', var(--font-sans);
2722 font-size: 0.75rem; 3282 font-size: 0.75rem;
2723 color: var(--theme-texto); 3283 color: var(--theme-texto);
2724 } 3284 }
...@@ -2729,52 +3289,64 @@ ...@@ -2729,52 +3289,64 @@
2729 border-radius: 3px; 3289 border-radius: 3px;
2730 } 3290 }
2731 3291
2732 - /* KPIs comparativos - tabla */ 3292 + .legend-pending {
2733 - .kpis-tabla { 3293 + opacity: 0.5;
2734 - background: transparent; 3294 + font-style: italic;
2735 - box-shadow: inset 0 0 0 1px rgba(255,255,255,0.08);
2736 - border-radius: 10px;
2737 - padding: 0.5rem 1rem;
2738 } 3295 }
2739 3296
3297 + .comparador-hint-select {
3298 + margin-top: 0.75rem;
3299 + }
3300 +
3301 + /* KPIs comparativos - tabla */
3302 + /* Tabla styles inside kpis-card-comparar */
3303 +
2740 .tabla-header { 3304 .tabla-header {
2741 display: grid; 3305 display: grid;
2742 - grid-template-columns: 1fr 1fr 1fr; 3306 + grid-template-columns: 1fr 1.2fr 1.2fr;
2743 - gap: 1rem; 3307 + gap: 0.75rem;
2744 padding-bottom: 0.375rem; 3308 padding-bottom: 0.375rem;
2745 border-bottom: 1px solid var(--theme-borde); 3309 border-bottom: 1px solid var(--theme-borde);
2746 - margin-bottom: 0.375rem; 3310 + margin-bottom: 0.25rem;
2747 } 3311 }
2748 3312
2749 .tabla-header .tabla-val { 3313 .tabla-header .tabla-val {
2750 - font-family: 'DM Mono', monospace; 3314 + font-family: 'Qanelas', var(--font-sans);
2751 font-size: 0.625rem; 3315 font-size: 0.625rem;
3316 + font-weight: 500;
2752 letter-spacing: 0.05em; 3317 letter-spacing: 0.05em;
2753 text-transform: uppercase; 3318 text-transform: uppercase;
2754 text-align: center; 3319 text-align: center;
2755 } 3320 }
2756 3321
3322 + .tabla-entidad {
3323 + font-size: 0.6875rem;
3324 + line-height: 1.3;
3325 + word-break: break-word;
3326 + hyphens: auto;
3327 + }
3328 +
2757 .tabla-row { 3329 .tabla-row {
2758 display: grid; 3330 display: grid;
2759 - grid-template-columns: 1fr 1fr 1fr; 3331 + grid-template-columns: 1fr 1.2fr 1.2fr;
2760 - gap: 1rem; 3332 + gap: 0.75rem;
2761 - padding: 0.25rem 0; 3333 + padding: 0.1875rem 0;
2762 } 3334 }
2763 3335
2764 .tabla-label { 3336 .tabla-label {
2765 - font-family: 'DM Mono', monospace; 3337 + font-family: 'Qanelas', var(--font-sans);
2766 - font-size: 0.75rem; 3338 + font-size: 0.6875rem;
2767 font-weight: 500; 3339 font-weight: 500;
2768 color: var(--theme-texto); 3340 color: var(--theme-texto);
2769 display: flex; 3341 display: flex;
2770 align-items: center; 3342 align-items: center;
2771 text-transform: uppercase; 3343 text-transform: uppercase;
2772 - letter-spacing: 0.03em; 3344 + letter-spacing: 0.05em;
2773 } 3345 }
2774 3346
2775 .tabla-val { 3347 .tabla-val {
2776 - font-family: 'DM Mono', monospace; 3348 + font-family: 'Qanelas', var(--font-sans);
2777 - font-size: 0.9375rem; 3349 + font-size: 0.875rem;
2778 font-weight: 500; 3350 font-weight: 500;
2779 color: var(--theme-titulo); 3351 color: var(--theme-titulo);
2780 text-align: center; 3352 text-align: center;
...@@ -2947,6 +3519,16 @@ ...@@ -2947,6 +3519,16 @@
2947 font-size: 0.45rem; 3519 font-size: 0.45rem;
2948 } 3520 }
2949 3521
3522 + .main-comparar {
3523 + padding: 0.75rem;
3524 + }
3525 +
3526 + .chart-card-comparar,
3527 + .kpis-card-comparar {
3528 + padding: 1rem;
3529 + border-radius: 12px;
3530 + }
3531 +
2950 .comparador-selectors { 3532 .comparador-selectors {
2951 flex-direction: column; 3533 flex-direction: column;
2952 gap: 0.75rem; 3534 gap: 0.75rem;
...@@ -2956,6 +3538,14 @@ ...@@ -2956,6 +3538,14 @@
2956 display: none; 3538 display: none;
2957 } 3539 }
2958 3540
3541 + .comparador-search-wrapper {
3542 + max-width: 100%;
3543 + }
3544 +
3545 + .chart-container-comparar {
3546 + min-height: 180px;
3547 + }
3548 +
2959 /* Ranking responsive */ 3549 /* Ranking responsive */
2960 .ranking-title { 3550 .ranking-title {
2961 font-size: 1.25rem; 3551 font-size: 1.25rem;
......