Showing
16 changed files
with
592 additions
and
25 deletions
| ... | @@ -106,6 +106,43 @@ | ... | @@ -106,6 +106,43 @@ |
| 106 | } | 106 | } |
| 107 | 107 | ||
| 108 | /* ============================================ | 108 | /* ============================================ |
| 109 | + SISTEMA DE TEMAS (LIGHT/DARK MODE) | ||
| 110 | + Para cambiar la paleta, solo modifica estos valores | ||
| 111 | + ============================================ */ | ||
| 112 | + | ||
| 113 | +:root { | ||
| 114 | + /* Modo claro (default) - Slate/White */ | ||
| 115 | + --theme-body: #fafafa; | ||
| 116 | + --theme-background: #f8fafc; | ||
| 117 | + --theme-titulo: #1a1a1a; | ||
| 118 | + --theme-texto: #64748b; | ||
| 119 | + --theme-fill: #f1f5f9; | ||
| 120 | + --theme-borde: rgba(0, 0, 0, 0.08); | ||
| 121 | + --theme-accent: #c9a751; | ||
| 122 | + --theme-accent-hover: #b8963f; | ||
| 123 | + | ||
| 124 | + /* Superficies alternativas */ | ||
| 125 | + --theme-surface: #ffffff; | ||
| 126 | + --theme-surface-hover: #f8fafc; | ||
| 127 | +} | ||
| 128 | + | ||
| 129 | +:root.dark { | ||
| 130 | + /* Modo oscuro */ | ||
| 131 | + --theme-body: #0d0d0d; | ||
| 132 | + --theme-background: #1a1a1a; | ||
| 133 | + --theme-titulo: #e8e4d8; | ||
| 134 | + --theme-texto: #b8b3a3; | ||
| 135 | + --theme-fill: #2d2d2d; | ||
| 136 | + --theme-borde: rgba(255, 255, 255, 0.1); | ||
| 137 | + --theme-accent: #c9a751; | ||
| 138 | + --theme-accent-hover: #d4b76a; | ||
| 139 | + | ||
| 140 | + /* Superficies alternativas */ | ||
| 141 | + --theme-surface: #1a1a1a; | ||
| 142 | + --theme-surface-hover: #2d2d2d; | ||
| 143 | +} | ||
| 144 | + | ||
| 145 | +/* ============================================ | ||
| 109 | ESTILOS BASE | 146 | ESTILOS BASE |
| 110 | ============================================ */ | 147 | ============================================ */ |
| 111 | 148 | ||
| ... | @@ -115,22 +152,51 @@ html { | ... | @@ -115,22 +152,51 @@ html { |
| 115 | 152 | ||
| 116 | body { | 153 | body { |
| 117 | font-family: var(--font-sans); | 154 | font-family: var(--font-sans); |
| 118 | - color: var(--color-graphite); | 155 | + color: var(--theme-titulo); |
| 119 | - background-color: #FAFAFA; | 156 | + background-color: var(--theme-body); |
| 120 | -webkit-font-smoothing: antialiased; | 157 | -webkit-font-smoothing: antialiased; |
| 121 | -moz-osx-font-smoothing: grayscale; | 158 | -moz-osx-font-smoothing: grayscale; |
| 159 | + transition: background-color 0.2s ease, color 0.2s ease; | ||
| 122 | } | 160 | } |
| 123 | 161 | ||
| 124 | /* Focus ring consistente con identidad */ | 162 | /* Focus ring consistente con identidad */ |
| 125 | *:focus-visible { | 163 | *:focus-visible { |
| 126 | - outline: 2px solid var(--color-gold); | 164 | + outline: 2px solid var(--theme-accent); |
| 127 | outline-offset: 2px; | 165 | outline-offset: 2px; |
| 128 | } | 166 | } |
| 129 | 167 | ||
| 130 | /* Selección de texto */ | 168 | /* Selección de texto */ |
| 131 | ::selection { | 169 | ::selection { |
| 132 | - background-color: var(--color-gold); | 170 | + background-color: var(--theme-accent); |
| 133 | - color: white; | 171 | + color: var(--theme-body); |
| 172 | +} | ||
| 173 | + | ||
| 174 | +/* ============================================ | ||
| 175 | + UTILIDADES DE TEMA | ||
| 176 | + ============================================ */ | ||
| 177 | + | ||
| 178 | +.theme-bg { | ||
| 179 | + background-color: var(--theme-body); | ||
| 180 | +} | ||
| 181 | + | ||
| 182 | +.theme-surface { | ||
| 183 | + background-color: var(--theme-surface); | ||
| 184 | +} | ||
| 185 | + | ||
| 186 | +.theme-text { | ||
| 187 | + color: var(--theme-titulo); | ||
| 188 | +} | ||
| 189 | + | ||
| 190 | +.theme-text-muted { | ||
| 191 | + color: var(--theme-texto); | ||
| 192 | +} | ||
| 193 | + | ||
| 194 | +.theme-border { | ||
| 195 | + border-color: var(--theme-borde); | ||
| 196 | +} | ||
| 197 | + | ||
| 198 | +.theme-accent { | ||
| 199 | + color: var(--theme-accent); | ||
| 134 | } | 200 | } |
| 135 | 201 | ||
| 136 | /* ============================================ | 202 | /* ============================================ | ... | ... |
| 1 | <!doctype html> | 1 | <!doctype html> |
| 2 | -<html lang="en"> | 2 | +<html lang="es"> |
| 3 | <head> | 3 | <head> |
| 4 | <meta charset="utf-8" /> | 4 | <meta charset="utf-8" /> |
| 5 | <meta name="viewport" content="width=device-width, initial-scale=1" /> | 5 | <meta name="viewport" content="width=device-width, initial-scale=1" /> |
| 6 | + | ||
| 7 | + <!-- Theme initialization - ANTES de cargar CSS para evitar flash --> | ||
| 8 | + <script> | ||
| 9 | + (function() { | ||
| 10 | + const savedTheme = localStorage.getItem('theme'); | ||
| 11 | + if (savedTheme === 'dark') { | ||
| 12 | + document.documentElement.classList.add('dark'); | ||
| 13 | + } | ||
| 14 | + // Por defecto modo claro (no detectar sistema) | ||
| 15 | + })(); | ||
| 16 | + </script> | ||
| 17 | + | ||
| 6 | %sveltekit.head% | 18 | %sveltekit.head% |
| 7 | </head> | 19 | </head> |
| 8 | <body data-sveltekit-preload-data="hover"> | 20 | <body data-sveltekit-preload-data="hover"> | ... | ... |
This diff is collapsed. Click to expand it.
| ... | @@ -10,12 +10,21 @@ | ... | @@ -10,12 +10,21 @@ |
| 10 | error, | 10 | error, |
| 11 | selectedIndex, | 11 | selectedIndex, |
| 12 | showResults, | 12 | showResults, |
| 13 | + filterType, | ||
| 13 | initIndex, | 14 | initIndex, |
| 14 | performSearch, | 15 | performSearch, |
| 15 | clearSearch, | 16 | clearSearch, |
| 16 | - navigateResults | 17 | + navigateResults, |
| 18 | + setFilter | ||
| 17 | } from '$lib/stores/searchStore'; | 19 | } from '$lib/stores/searchStore'; |
| 18 | 20 | ||
| 21 | + const filterOptions = [ | ||
| 22 | + { value: 'todos', label: 'Todos' }, | ||
| 23 | + { value: 'entidad', label: 'Entidades' }, | ||
| 24 | + { value: 'objeto_gasto', label: 'Objetos' }, | ||
| 25 | + { value: 'rubro', label: 'Rubros' } | ||
| 26 | + ]; | ||
| 27 | + | ||
| 19 | let inputRef; | 28 | let inputRef; |
| 20 | let debounceTimer; | 29 | let debounceTimer; |
| 21 | let isFocused = $state(false); | 30 | let isFocused = $state(false); |
| ... | @@ -77,16 +86,28 @@ | ... | @@ -77,16 +86,28 @@ |
| 77 | <svelte:window onclick={handleClickOutside} /> | 86 | <svelte:window onclick={handleClickOutside} /> |
| 78 | 87 | ||
| 79 | <div class="search-container relative"> | 88 | <div class="search-container relative"> |
| 89 | + <div class="flex gap-2"> | ||
| 80 | <input | 90 | <input |
| 81 | bind:this={inputRef} | 91 | bind:this={inputRef} |
| 82 | type="text" | 92 | type="text" |
| 83 | - placeholder={$indexLoaded ? "Buscar entidad, objeto de gasto o rubro..." : "Cargando..."} | 93 | + placeholder={$indexLoaded ? "Buscar..." : "Cargando..."} |
| 84 | disabled={!$indexLoaded} | 94 | disabled={!$indexLoaded} |
| 85 | - class="w-full p-2 border rounded" | 95 | + class="flex-1 p-2 border rounded" |
| 86 | oninput={handleInput} | 96 | oninput={handleInput} |
| 87 | onkeydown={handleKeydown} | 97 | onkeydown={handleKeydown} |
| 88 | onfocus={() => isFocused = true} | 98 | onfocus={() => isFocused = true} |
| 89 | /> | 99 | /> |
| 100 | + <select | ||
| 101 | + class="px-3 py-2 border rounded bg-white text-sm text-slate-600 cursor-pointer" | ||
| 102 | + value={$filterType} | ||
| 103 | + onchange={(e) => setFilter(e.target.value)} | ||
| 104 | + disabled={!$indexLoaded} | ||
| 105 | + > | ||
| 106 | + {#each filterOptions as option} | ||
| 107 | + <option value={option.value}>{option.label}</option> | ||
| 108 | + {/each} | ||
| 109 | + </select> | ||
| 110 | + </div> | ||
| 90 | 111 | ||
| 91 | {#if $isLoading} | 112 | {#if $isLoading} |
| 92 | <span class="absolute right-2 top-2 text-sm text-gray-400">...</span> | 113 | <span class="absolute right-2 top-2 text-sm text-gray-400">...</span> | ... | ... |
| 1 | <script> | 1 | <script> |
| 2 | import { goto } from '$app/navigation'; | 2 | import { goto } from '$app/navigation'; |
| 3 | - import { clearSearch } from '$lib/stores/searchStore'; | 3 | + import { clearSearch, filterType } from '$lib/stores/searchStore'; |
| 4 | 4 | ||
| 5 | let { results = [], query = '', selectedIndex = -1 } = $props(); | 5 | let { results = [], query = '', selectedIndex = -1 } = $props(); |
| 6 | 6 | ||
| ... | @@ -23,6 +23,9 @@ | ... | @@ -23,6 +23,9 @@ |
| 23 | return { entidades, objetos, rubros }; | 23 | return { entidades, objetos, rubros }; |
| 24 | }); | 24 | }); |
| 25 | 25 | ||
| 26 | + // Si hay un filtro específico, no mostrar headers de grupo | ||
| 27 | + let showGroupHeaders = $derived($filterType === 'todos'); | ||
| 28 | + | ||
| 26 | // Calcular índice global para navegación con teclado | 29 | // Calcular índice global para navegación con teclado |
| 27 | function getGlobalIndex(tipo, localIndex) { | 30 | function getGlobalIndex(tipo, localIndex) { |
| 28 | const g = groupedResults(); | 31 | const g = groupedResults(); |
| ... | @@ -39,9 +42,11 @@ | ... | @@ -39,9 +42,11 @@ |
| 39 | {#if results.length > 0} | 42 | {#if results.length > 0} |
| 40 | <div class="absolute top-full left-0 right-0 mt-1 bg-white border rounded shadow-lg max-h-80 overflow-y-auto z-50"> | 43 | <div class="absolute top-full left-0 right-0 mt-1 bg-white border rounded shadow-lg max-h-80 overflow-y-auto z-50"> |
| 41 | {#if groupedResults().entidades.length > 0} | 44 | {#if groupedResults().entidades.length > 0} |
| 45 | + {#if showGroupHeaders} | ||
| 42 | <div class="px-3 py-1.5 bg-slate-50 border-b"> | 46 | <div class="px-3 py-1.5 bg-slate-50 border-b"> |
| 43 | <span class="text-xs font-medium text-slate-500 uppercase tracking-wide">Entidades</span> | 47 | <span class="text-xs font-medium text-slate-500 uppercase tracking-wide">Entidades</span> |
| 44 | </div> | 48 | </div> |
| 49 | + {/if} | ||
| 45 | {#each groupedResults().entidades as item, index} | 50 | {#each groupedResults().entidades as item, index} |
| 46 | {@const globalIdx = getGlobalIndex('entidad', index)} | 51 | {@const globalIdx = getGlobalIndex('entidad', index)} |
| 47 | <button | 52 | <button |
| ... | @@ -61,9 +66,11 @@ | ... | @@ -61,9 +66,11 @@ |
| 61 | {/if} | 66 | {/if} |
| 62 | 67 | ||
| 63 | {#if groupedResults().objetos.length > 0} | 68 | {#if groupedResults().objetos.length > 0} |
| 69 | + {#if showGroupHeaders} | ||
| 64 | <div class="px-3 py-1.5 bg-slate-50 border-b {groupedResults().entidades.length > 0 ? 'border-t' : ''}"> | 70 | <div class="px-3 py-1.5 bg-slate-50 border-b {groupedResults().entidades.length > 0 ? 'border-t' : ''}"> |
| 65 | <span class="text-xs font-medium text-slate-500 uppercase tracking-wide">Objetos del Gasto</span> | 71 | <span class="text-xs font-medium text-slate-500 uppercase tracking-wide">Objetos del Gasto</span> |
| 66 | </div> | 72 | </div> |
| 73 | + {/if} | ||
| 67 | {#each groupedResults().objetos as item, index} | 74 | {#each groupedResults().objetos as item, index} |
| 68 | {@const globalIdx = getGlobalIndex('objeto_gasto', index)} | 75 | {@const globalIdx = getGlobalIndex('objeto_gasto', index)} |
| 69 | <button | 76 | <button |
| ... | @@ -81,9 +88,11 @@ | ... | @@ -81,9 +88,11 @@ |
| 81 | {/if} | 88 | {/if} |
| 82 | 89 | ||
| 83 | {#if groupedResults().rubros.length > 0} | 90 | {#if groupedResults().rubros.length > 0} |
| 91 | + {#if showGroupHeaders} | ||
| 84 | <div class="px-3 py-1.5 bg-slate-50 border-b {groupedResults().entidades.length > 0 || groupedResults().objetos.length > 0 ? 'border-t' : ''}"> | 92 | <div class="px-3 py-1.5 bg-slate-50 border-b {groupedResults().entidades.length > 0 || groupedResults().objetos.length > 0 ? 'border-t' : ''}"> |
| 85 | <span class="text-xs font-medium text-slate-500 uppercase tracking-wide">Rubros</span> | 93 | <span class="text-xs font-medium text-slate-500 uppercase tracking-wide">Rubros</span> |
| 86 | </div> | 94 | </div> |
| 95 | + {/if} | ||
| 87 | {#each groupedResults().rubros as item, index} | 96 | {#each groupedResults().rubros as item, index} |
| 88 | {@const globalIdx = getGlobalIndex('rubro', index)} | 97 | {@const globalIdx = getGlobalIndex('rubro', index)} |
| 89 | <button | 98 | <button | ... | ... |
| 1 | <script> | 1 | <script> |
| 2 | - import { page } from '$app/stores'; | 2 | + import { onMount } from 'svelte'; |
| 3 | + | ||
| 4 | + let { onOpenDrawer = () => {} } = $props(); | ||
| 5 | + | ||
| 6 | + let isDark = $state(false); | ||
| 7 | + | ||
| 8 | + function toggleTheme() { | ||
| 9 | + isDark = !isDark; | ||
| 10 | + document.documentElement.classList.toggle('dark', isDark); | ||
| 11 | + localStorage.setItem('theme', isDark ? 'dark' : 'light'); | ||
| 12 | + } | ||
| 13 | + | ||
| 14 | + onMount(() => { | ||
| 15 | + // Read actual state from document (set by app.html) | ||
| 16 | + isDark = document.documentElement.classList.contains('dark'); | ||
| 17 | + | ||
| 18 | + // Listen for theme changes (e.g., from drawer on mobile) | ||
| 19 | + const observer = new MutationObserver(() => { | ||
| 20 | + isDark = document.documentElement.classList.contains('dark'); | ||
| 21 | + }); | ||
| 22 | + observer.observe(document.documentElement, { | ||
| 23 | + attributes: true, | ||
| 24 | + attributeFilter: ['class'] | ||
| 25 | + }); | ||
| 26 | + | ||
| 27 | + return () => observer.disconnect(); | ||
| 28 | + }); | ||
| 3 | </script> | 29 | </script> |
| 4 | 30 | ||
| 5 | -<nav class="border-b p-4"> | 31 | +<!-- MÓVIL: Solo hamburguesa arriba a la derecha --> |
| 6 | - <div class="max-w-4xl mx-auto flex items-center justify-between"> | 32 | +<div class="fixed top-4 right-4 z-[110] md:hidden"> |
| 7 | - <a href="/">Presupuesto Público Bolivia</a> | 33 | + <button |
| 8 | - <div class="flex gap-4 text-sm"> | 34 | + onclick={onOpenDrawer} |
| 9 | - <a href="/">Inicio</a> | 35 | + class="p-2 rounded-full transition-all flex-shrink-0" |
| 10 | - <a href="/clasificadores">Clasificadores</a> | 36 | + style="background-color: transparent; color: var(--theme-texto); cursor: pointer;" |
| 37 | + onmouseenter={(e) => e.currentTarget.style.color = 'var(--theme-titulo)'} | ||
| 38 | + onmouseleave={(e) => e.currentTarget.style.color = 'var(--theme-texto)'} | ||
| 39 | + aria-label="Abrir menú" | ||
| 40 | + > | ||
| 41 | + <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2"> | ||
| 42 | + <path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"/> | ||
| 43 | + </svg> | ||
| 44 | + </button> | ||
| 45 | +</div> | ||
| 46 | + | ||
| 47 | +<!-- DESKTOP: Barra completa con Home + Theme + Menu --> | ||
| 48 | +<div class="hidden md:flex fixed top-6 right-6 z-[110]"> | ||
| 49 | + <div class="flex items-center gap-1"> | ||
| 50 | + | ||
| 51 | + <!-- BOTÓN HOME (4 cuadrados) --> | ||
| 52 | + <div class="relative group/home"> | ||
| 53 | + <a | ||
| 54 | + href="/" | ||
| 55 | + class="p-2 rounded-full transition-all block" | ||
| 56 | + style="background-color: transparent; color: var(--theme-texto);" | ||
| 57 | + onmouseenter={(e) => e.currentTarget.style.color = 'var(--theme-titulo)'} | ||
| 58 | + onmouseleave={(e) => e.currentTarget.style.color = 'var(--theme-texto)'} | ||
| 59 | + > | ||
| 60 | + <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20"> | ||
| 61 | + <rect x="2" y="2" width="7" height="7" rx="1.5"/> | ||
| 62 | + <rect x="11" y="2" width="7" height="7" rx="1.5"/> | ||
| 63 | + <rect x="2" y="11" width="7" height="7" rx="1.5"/> | ||
| 64 | + <rect x="11" y="11" width="7" height="7" rx="1.5"/> | ||
| 65 | + </svg> | ||
| 66 | + </a> | ||
| 67 | + <!-- Tooltip --> | ||
| 68 | + <div class="absolute right-0 top-full mt-2 px-3 py-1.5 bg-gray-900/90 text-white text-xs rounded-lg whitespace-nowrap opacity-0 invisible group-hover/home:opacity-100 group-hover/home:visible transition-all duration-200 pointer-events-none backdrop-blur-sm"> | ||
| 69 | + Página principal | ||
| 70 | + <div class="absolute right-3 bottom-full mb-[-4px] w-2 h-2 bg-gray-900/90 rotate-45"></div> | ||
| 11 | </div> | 71 | </div> |
| 12 | </div> | 72 | </div> |
| 13 | -</nav> | 73 | + |
| 74 | + <!-- BOTÓN THEME TOGGLE (Sol/Luna) --> | ||
| 75 | + <div class="relative group/theme"> | ||
| 76 | + <button | ||
| 77 | + onclick={toggleTheme} | ||
| 78 | + class="p-2 rounded-full transition-all" | ||
| 79 | + style="background-color: transparent; color: var(--theme-texto); cursor: pointer;" | ||
| 80 | + onmouseenter={(e) => e.currentTarget.style.color = 'var(--theme-titulo)'} | ||
| 81 | + onmouseleave={(e) => e.currentTarget.style.color = 'var(--theme-texto)'} | ||
| 82 | + aria-label={isDark ? 'Cambiar a modo claro' : 'Cambiar a modo oscuro'} | ||
| 83 | + > | ||
| 84 | + {#if isDark} | ||
| 85 | + <!-- LUNA (modo oscuro activo) --> | ||
| 86 | + <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20"> | ||
| 87 | + <path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"/> | ||
| 88 | + </svg> | ||
| 89 | + {:else} | ||
| 90 | + <!-- SOL (modo claro activo) --> | ||
| 91 | + <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20"> | ||
| 92 | + <path fill-rule="evenodd" d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z" clip-rule="evenodd"/> | ||
| 93 | + </svg> | ||
| 94 | + {/if} | ||
| 95 | + </button> | ||
| 96 | + <!-- Tooltip --> | ||
| 97 | + <div class="absolute right-0 top-full mt-2 px-3 py-1.5 bg-gray-900/90 text-white text-xs rounded-lg whitespace-nowrap opacity-0 invisible group-hover/theme:opacity-100 group-hover/theme:visible transition-all duration-200 pointer-events-none backdrop-blur-sm"> | ||
| 98 | + {isDark ? 'Cambiar a modo claro' : 'Cambiar a modo oscuro'} | ||
| 99 | + <div class="absolute right-3 bottom-full mb-[-4px] w-2 h-2 bg-gray-900/90 rotate-45"></div> | ||
| 100 | + </div> | ||
| 101 | + </div> | ||
| 102 | + | ||
| 103 | + <!-- BOTÓN MENÚ HAMBURGUESA --> | ||
| 104 | + <div class="relative group/menu"> | ||
| 105 | + <button | ||
| 106 | + onclick={onOpenDrawer} | ||
| 107 | + class="p-2 rounded-full transition-all" | ||
| 108 | + style="background-color: transparent; color: var(--theme-texto); cursor: pointer;" | ||
| 109 | + onmouseenter={(e) => e.currentTarget.style.color = 'var(--theme-titulo)'} | ||
| 110 | + onmouseleave={(e) => e.currentTarget.style.color = 'var(--theme-texto)'} | ||
| 111 | + aria-label="Abrir menú" | ||
| 112 | + > | ||
| 113 | + <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2"> | ||
| 114 | + <path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"/> | ||
| 115 | + </svg> | ||
| 116 | + </button> | ||
| 117 | + <!-- Tooltip --> | ||
| 118 | + <div class="absolute right-0 top-full mt-2 px-3 py-1.5 bg-gray-900/90 text-white text-xs rounded-lg whitespace-nowrap opacity-0 invisible group-hover/menu:opacity-100 group-hover/menu:visible transition-all duration-200 pointer-events-none backdrop-blur-sm"> | ||
| 119 | + Menú | ||
| 120 | + <div class="absolute right-3 bottom-full mb-[-4px] w-2 h-2 bg-gray-900/90 rotate-45"></div> | ||
| 121 | + </div> | ||
| 122 | + </div> | ||
| 123 | + | ||
| 124 | + </div> | ||
| 125 | +</div> | ... | ... |
src/lib/stores/drawer.js
0 → 100644
| 1 | +import { writable } from 'svelte/store'; | ||
| 2 | + | ||
| 3 | +export const drawerOpen = writable(false); | ||
| 4 | + | ||
| 5 | +export function openDrawer() { | ||
| 6 | + drawerOpen.set(true); | ||
| 7 | +} | ||
| 8 | + | ||
| 9 | +export function closeDrawer() { | ||
| 10 | + drawerOpen.set(false); | ||
| 11 | +} | ||
| 12 | + | ||
| 13 | +export function toggleDrawer() { | ||
| 14 | + drawerOpen.update(v => !v); | ||
| 15 | +} |
| 1 | -import { writable, derived } from 'svelte/store'; | 1 | +import { writable, derived, get } from 'svelte/store'; |
| 2 | import { initSearchIndex, search, isIndexReady } from '$lib/services/search'; | 2 | import { initSearchIndex, search, isIndexReady } from '$lib/services/search'; |
| 3 | 3 | ||
| 4 | // Estado del buscador | 4 | // Estado del buscador |
| ... | @@ -8,6 +8,7 @@ export const isLoading = writable(false); | ... | @@ -8,6 +8,7 @@ export const isLoading = writable(false); |
| 8 | export const indexLoaded = writable(false); | 8 | export const indexLoaded = writable(false); |
| 9 | export const error = writable(null); | 9 | export const error = writable(null); |
| 10 | export const selectedIndex = writable(-1); | 10 | export const selectedIndex = writable(-1); |
| 11 | +export const filterType = writable('todos'); // todos, entidad, objeto_gasto, rubro | ||
| 11 | 12 | ||
| 12 | // Estado derivado: si está mostrando resultados | 13 | // Estado derivado: si está mostrando resultados |
| 13 | export const showResults = derived( | 14 | export const showResults = derived( |
| ... | @@ -41,7 +42,7 @@ export async function initIndex() { | ... | @@ -41,7 +42,7 @@ export async function initIndex() { |
| 41 | /** | 42 | /** |
| 42 | * Realiza la búsqueda | 43 | * Realiza la búsqueda |
| 43 | */ | 44 | */ |
| 44 | -export function performSearch(searchQuery) { | 45 | +export function performSearch(searchQuery, filter = null) { |
| 45 | query.set(searchQuery); | 46 | query.set(searchQuery); |
| 46 | selectedIndex.set(-1); | 47 | selectedIndex.set(-1); |
| 47 | 48 | ||
| ... | @@ -50,8 +51,16 @@ export function performSearch(searchQuery) { | ... | @@ -50,8 +51,16 @@ export function performSearch(searchQuery) { |
| 50 | return; | 51 | return; |
| 51 | } | 52 | } |
| 52 | 53 | ||
| 53 | - const searchResults = search(searchQuery, 15); | 54 | + let searchResults = search(searchQuery, 30); |
| 54 | - results.set(searchResults); | 55 | + |
| 56 | + // Filtrar por tipo si no es "todos" | ||
| 57 | + const currentFilter = filter !== null ? filter : get(filterType); | ||
| 58 | + if (currentFilter !== 'todos') { | ||
| 59 | + searchResults = searchResults.filter(r => r.tipo === currentFilter); | ||
| 60 | + } | ||
| 61 | + | ||
| 62 | + // Limitar resultados | ||
| 63 | + results.set(searchResults.slice(0, 15)); | ||
| 55 | } | 64 | } |
| 56 | 65 | ||
| 57 | /** | 66 | /** |
| ... | @@ -64,6 +73,18 @@ export function clearSearch() { | ... | @@ -64,6 +73,18 @@ export function clearSearch() { |
| 64 | } | 73 | } |
| 65 | 74 | ||
| 66 | /** | 75 | /** |
| 76 | + * Cambia el filtro de tipo | ||
| 77 | + */ | ||
| 78 | +export function setFilter(type) { | ||
| 79 | + filterType.set(type); | ||
| 80 | + // Re-ejecutar búsqueda con el nuevo filtro | ||
| 81 | + const currentQuery = get(query); | ||
| 82 | + if (currentQuery.length >= 2) { | ||
| 83 | + performSearch(currentQuery, type); | ||
| 84 | + } | ||
| 85 | +} | ||
| 86 | + | ||
| 87 | +/** | ||
| 67 | * Navegación por teclado | 88 | * Navegación por teclado |
| 68 | */ | 89 | */ |
| 69 | export function navigateResults(direction, totalResults) { | 90 | export function navigateResults(direction, totalResults) { | ... | ... |
| 1 | <script> | 1 | <script> |
| 2 | import '../app.css'; | 2 | import '../app.css'; |
| 3 | + import { page } from '$app/stores'; | ||
| 3 | import favicon from '$lib/assets/favicon.svg'; | 4 | import favicon from '$lib/assets/favicon.svg'; |
| 4 | 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'; | ||
| 7 | + import { openDrawer } from '$lib/stores/drawer.js'; | ||
| 5 | 8 | ||
| 6 | let { children } = $props(); | 9 | let { children } = $props(); |
| 10 | + | ||
| 11 | + // Don't show navbar on landing page | ||
| 12 | + let isLanding = $derived($page.url.pathname === '/'); | ||
| 7 | </script> | 13 | </script> |
| 8 | 14 | ||
| 9 | <svelte:head> | 15 | <svelte:head> |
| 10 | <link rel="icon" href={favicon} /> | 16 | <link rel="icon" href={favicon} /> |
| 17 | + <link rel="preconnect" href="https://fonts.googleapis.com" /> | ||
| 18 | + <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" /> | ||
| 11 | </svelte:head> | 19 | </svelte:head> |
| 12 | 20 | ||
| 13 | -<div class="min-h-screen flex flex-col"> | 21 | +<!-- Navigation Drawer (siempre presente) --> |
| 14 | - <Navbar /> | 22 | +<NavigationDrawer /> |
| 15 | - <main class="flex-1"> | 23 | + |
| 24 | +<!-- Floating controls bar (fixed position - no afecta el flujo) --> | ||
| 25 | +{#if !isLanding} | ||
| 26 | + <Navbar onOpenDrawer={openDrawer} /> | ||
| 27 | +{/if} | ||
| 28 | + | ||
| 29 | +<div class="min-h-screen" style="background-color: var(--theme-body);"> | ||
| 30 | + <main> | ||
| 16 | {@render children()} | 31 | {@render children()} |
| 17 | </main> | 32 | </main> |
| 18 | </div> | 33 | </div> | ... | ... |
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
static/ESTILOS_UI.md
0 → 100644
This diff is collapsed. Click to expand it.
static/data.csv
0 → 100644
| 1 | +gestion,objeto,devengado | ||
| 2 | +2025,11100,7010195151.280003 | ||
| 3 | +2025,11210,5812831318.110004 | ||
| 4 | +2025,11220,3960643633.5099993 | ||
| 5 | +2025,11310,477377380.7399999 | ||
| 6 | +2025,11321,611358929.5699998 | ||
| 7 | +2025,11322,344284069.2800001 | ||
| 8 | +2025,11323,8492765.11 | ||
| 9 | +2025,11324,119472850.73000005 | ||
| 10 | +2025,11331,3741918.29 | ||
| 11 | +2025,11332,222831.86 | ||
| 12 | +2025,11339,991911009.5900005 | ||
| 13 | +2025,11400,2598265843.6899953 | ||
| 14 | +2025,11510,115618445.64 | ||
| 15 | +2025,11520,3684768.92 | ||
| 16 | +2025,11600,347937539.3 | ||
| 17 | +2025,11700,16012422722.88999 | ||
| 18 | +2025,11810,1897633.19 | ||
| 19 | +2025,11820,5236920.13 | ||
| 20 | +2025,11910,418427037.15000004 | ||
| 21 | +2025,11920,49552831.83999999 | ||
| 22 | +2025,11930,141140855.32999998 | ||
| 23 | +2025,11940,4603197.24 | ||
| 24 | +2025,12100,3204535824.6000023 | ||
| 25 | +2025,13110,3901953807.7400007 | ||
| 26 | +2025,13120,619414677.6200007 | ||
| 27 | +2025,13131,1362025647.0700052 | ||
| 28 | +2025,13132,16517337.85 | ||
| 29 | +2025,13200,780321050.7899984 | ||
| 30 | +2025,14100,1139341529.1000001 | ||
| 31 | +2025,15100,0 | ||
| 32 | +2025,15200,0 | ||
| 33 | +2025,15300,0 | ||
| 34 | +2025,15400,4691146.74 | ||
| 35 | +2025,21100,18994647.069999997 | ||
| 36 | +2025,21200,1185053965.7300012 | ||
| 37 | +2025,21300,187133399.53000006 | ||
| 38 | +2025,21400,53465476.169999935 | ||
| 39 | +2025,21500,32025660.46999999 | ||
| 40 | +2025,21600,194624988.81999987 | ||
| 41 | +2025,22110,91267157.03 | ||
| 42 | +2025,22120,18054096.22 | ||
| 43 | +2025,22210,150388054.74000007 | ||
| 44 | +2025,22220,34629325.89 | ||
| 45 | +2025,22300,1959574229.3100035 | ||
| 46 | +2025,22400,2341473.77 | ||
| 47 | +2025,22500,701307983.6299998 | ||
| 48 | +2025,22600,129774059.36000001 | ||
| 49 | +2025,23100,177818037.08 | ||
| 50 | +2025,23200,705817678.630001 | ||
| 51 | +2025,23300,267332.29 | ||
| 52 | +2025,23400,102877216.77999996 | ||
| 53 | +2025,24110,347305124.4300007 | ||
| 54 | +2025,24120,495875030.0700008 | ||
| 55 | +2025,24130,4734068.450000001 | ||
| 56 | +2025,24200,186441122.7200001 | ||
| 57 | +2025,24300,2640074895.480003 | ||
| 58 | +2025,25120,968577449.3700005 | ||
| 59 | +2025,25130,48331129.9 | ||
| 60 | +2025,25210,124279655.01999998 | ||
| 61 | +2025,25220,1788157637.100003 | ||
| 62 | +2025,25230,38750297.28 | ||
| 63 | +2025,25300,561398734.3999997 | ||
| 64 | +2025,25400,878953716.1200004 | ||
| 65 | +2025,25500,306587141.63999945 | ||
| 66 | +2025,25600,313083444.33000004 | ||
| 67 | +2025,25700,6976563.890000001 | ||
| 68 | +2025,25810,119466843.19999999 | ||
| 69 | +2025,25820,224542509.50000012 | ||
| 70 | +2025,25900,237665083.13999984 | ||
| 71 | +2025,26200,15655261.7 | ||
| 72 | +2025,26300,34604066.51 | ||
| 73 | +2025,26610,123238100.68000004 | ||
| 74 | +2025,26620,65803840.900000006 | ||
| 75 | +2025,26630,5856917.720000001 | ||
| 76 | +2025,26640,12578208.32 | ||
| 77 | +2025,26700,14848951.62 | ||
| 78 | +2025,26910,663385.5700000003 | ||
| 79 | +2025,26920,235250 | ||
| 80 | +2025,26930,8978735.689999998 | ||
| 81 | +2025,26940,66563836.099999994 | ||
| 82 | +2025,26990,1503662082.0000036 | ||
| 83 | +2025,27110,1825790733.1499996 | ||
| 84 | +2025,27120,364659427.3 | ||
| 85 | +2025,31110,542443427.4200002 | ||
| 86 | +2025,31120,216381918.43999982 | ||
| 87 | +2025,31130,664933780.7900007 | ||
| 88 | +2025,31140,546467610.53 | ||
| 89 | +2025,31150,58934973.47 | ||
| 90 | +2025,31200,33094567.35000001 | ||
| 91 | +2025,31300,2447995585.1100035 | ||
| 92 | +2025,32100,143276681.52000007 | ||
| 93 | +2025,32200,132315063.70000002 | ||
| 94 | +2025,32300,5561928.509999998 | ||
| 95 | +2025,32400,159320 | ||
| 96 | +2025,32500,1136682.8700000003 | ||
| 97 | +2025,33100,24196989.06 | ||
| 98 | +2025,33200,44402437.73000002 | ||
| 99 | +2025,33300,140159482.78000006 | ||
| 100 | +2025,33400,44503112.73000003 | ||
| 101 | +2025,34110,1233206780.699983 | ||
| 102 | +2025,34120,22281523272.74 | ||
| 103 | +2025,34130,210854679.98 | ||
| 104 | +2025,34200,2971103666.2100005 | ||
| 105 | +2025,34300,107990911.47999994 | ||
| 106 | +2025,34400,7452099.93 | ||
| 107 | +2025,34500,643334205.2399997 | ||
| 108 | +2025,34600,256516535.3800006 | ||
| 109 | +2025,34700,4499642859.289998 | ||
| 110 | +2025,34800,31720752.09 | ||
| 111 | +2025,34900,3886088.93 | ||
| 112 | +2025,39100,110497000.54 | ||
| 113 | +2025,39200,48738034.710000016 | ||
| 114 | +2025,39300,4137761.2899999996 | ||
| 115 | +2025,39400,88709616.89000002 | ||
| 116 | +2025,39500,308909532.08000034 | ||
| 117 | +2025,39600,13789089.190000001 | ||
| 118 | +2025,39700,260527546.88999993 | ||
| 119 | +2025,39800,711478491.3899997 | ||
| 120 | +2025,39911,25451599.84 | ||
| 121 | +2025,39912,229716435.09 | ||
| 122 | +2025,39990,85810785.37999989 | ||
| 123 | +2025,41100,4018555.16 | ||
| 124 | +2025,41200,11738231.740000002 | ||
| 125 | +2025,41300,0 | ||
| 126 | +2025,42210,2475688.8800000004 | ||
| 127 | +2025,42220,5772449.3 | ||
| 128 | +2025,42230,3644887583.5699887 | ||
| 129 | +2025,42240,119884214.51000004 | ||
| 130 | +2025,42310,3262092590.7700067 | ||
| 131 | +2025,42320,162411814.5 | ||
| 132 | +2025,42400,748574159.99 | ||
| 133 | +2025,42500,368399418.97999996 | ||
| 134 | +2025,43110,68317124.34 | ||
| 135 | +2025,43120,223834030.73999995 | ||
| 136 | +2025,43200,145257372.71 | ||
| 137 | +2025,43310,18770602.6 | ||
| 138 | +2025,43320,7603011.960000001 | ||
| 139 | +2025,43330,290260838.97 | ||
| 140 | +2025,43340,4231983.279999999 | ||
| 141 | +2025,43400,398948895.01000005 | ||
| 142 | +2025,43500,328470832.01999986 | ||
| 143 | +2025,43600,51696765.46999999 | ||
| 144 | +2025,43700,169086594.95000005 | ||
| 145 | +2025,46110,125733899.86000003 | ||
| 146 | +2025,46120,74691013.75999999 | ||
| 147 | +2025,46210,57348534.56999999 | ||
| 148 | +2025,46220,9583654.360000001 | ||
| 149 | +2025,46310,30831570.299999986 | ||
| 150 | +2025,46320,2784494.7899999996 | ||
| 151 | +2025,49100,41480273 | ||
| 152 | +2025,49300,492862.94999999995 | ||
| 153 | +2025,49400,85275 | ||
| 154 | +2025,49900,660922.38 | ||
| 155 | +2025,51100,0 | ||
| 156 | +2025,51200,620015014.37 | ||
| 157 | +2025,51600,481042090 | ||
| 158 | +2025,51700,29014116.87 | ||
| 159 | +2025,53410,52050949.82 | ||
| 160 | +2025,53420,351090144.51 | ||
| 161 | +2025,53430,3378151.15 | ||
| 162 | +2025,53440,25716472.12 | ||
| 163 | +2025,54300,77289717.23 | ||
| 164 | +2025,54800,632675260.65 | ||
| 165 | +2025,55130,0 | ||
| 166 | +2025,56100,2147721911.8500004 | ||
| 167 | +2025,57100,836275.9 | ||
| 168 | +2025,57200,0 | ||
| 169 | +2025,61100,70227731033.1 | ||
| 170 | +2025,61200,3503973693.6899977 | ||
| 171 | +2025,61300,7686201.74 | ||
| 172 | +2025,61400,165316.78 | ||
| 173 | +2025,61600,4660620766.32 | ||
| 174 | +2025,61700,3610015023.1800003 | ||
| 175 | +2025,61800,578767.5200000001 | ||
| 176 | +2025,61900,560031.13 | ||
| 177 | +2025,62100,106585021.14999999 | ||
| 178 | +2025,62200,38756619.58 | ||
| 179 | +2025,62300,3500499.439999999 | ||
| 180 | +2025,62400,405.55 | ||
| 181 | +2025,62600,4871024753.510001 | ||
| 182 | +2025,62700,3091378138.92 | ||
| 183 | +2025,62800,66184743.53000001 | ||
| 184 | +2025,62900,236631.16 | ||
| 185 | +2025,63100,6480763.82 | ||
| 186 | +2025,63200,0 | ||
| 187 | +2025,63300,100360.43 | ||
| 188 | +2025,63400,462857.7 | ||
| 189 | +2025,63500,35529.2 | ||
| 190 | +2025,63600,0 | ||
| 191 | +2025,63700,2369055.65 | ||
| 192 | +2025,63800,0 | ||
| 193 | +2025,63900,1087614.1800000002 | ||
| 194 | +2025,64100,0 | ||
| 195 | +2025,64200,0 | ||
| 196 | +2025,65100,586447491.8299999 | ||
| 197 | +2025,65210,398987753.5799999 | ||
| 198 | +2025,65220,240387686.01999998 | ||
| 199 | +2025,65230,132581381.01 | ||
| 200 | +2025,65240,0 | ||
| 201 | +2025,65300,2444818356.6 | ||
| 202 | +2025,65400,644864385.6300001 | ||
| 203 | +2025,65500,797927308.79 | ||
| 204 | +2025,65600,622100032.9 | ||
| 205 | +2025,65800,6897016.98 | ||
| 206 | +2025,65900,66705420.480000004 | ||
| 207 | +2025,66100,403828926.02000004 | ||
| 208 | +2025,66210,2597435653.4000015 | ||
| 209 | +2025,66220,1829365908.85 | ||
| 210 | +2025,66230,646299428.4800001 | ||
| 211 | +2025,66240,80232240.73 | ||
| 212 | +2025,66250,209697820.00000003 | ||
| 213 | +2025,66300,239921321.82000005 | ||
| 214 | +2025,66400,134798065.3299999 | ||
| 215 | +2025,66900,1844595842.08 | ||
| 216 | +2025,67100,23208064.040000003 | ||
| 217 | +2025,68200,230881944.88000008 | ||
| 218 | +2025,69100,29581681.77 | ||
| 219 | +2025,69200,203500678.33 | ||
| 220 | +2025,71100,7630007944.97 | ||
| 221 | +2025,71210,2392898.6 | ||
| 222 | +2025,71220,322149243.09 | ||
| 223 | +2025,71230,4408152.6 | ||
| 224 | +2025,71300,26644234.430000003 | ||
| 225 | +2025,71610,1883091507.0600004 | ||
| 226 | +2025,71630,343800974.93 | ||
| 227 | +2025,71700,65080225.449999996 | ||
| 228 | +2025,71800,4478984170.690001 | ||
| 229 | +2025,72200,2046897058.8299997 | ||
| 230 | +2025,72420,316010589.22 | ||
| 231 | +2025,72520,7584584602.65001 | ||
| 232 | +2025,73100,2664574022.970007 | ||
| 233 | +2025,73200,24681254941.659996 | ||
| 234 | +2025,73410,4845222515.11 | ||
| 235 | +2025,73420,315258596.03999996 | ||
| 236 | +2025,73430,4314037.58 | ||
| 237 | +2025,73440,1652127 | ||
| 238 | +2025,73700,15015040108.359999 | ||
| 239 | +2025,73820,1364681.67 | ||
| 240 | +2025,75110,0 | ||
| 241 | +2025,75120,683772757.9200002 | ||
| 242 | +2025,75211,469127620.05 | ||
| 243 | +2025,75212,50308009.760000005 | ||
| 244 | +2025,75221,11649919.909999998 | ||
| 245 | +2025,75222,305831148.79999995 | ||
| 246 | +2025,75320,0 | ||
| 247 | +2025,77100,346626643.30999994 | ||
| 248 | +2025,77200,1783047515.49 | ||
| 249 | +2025,77410,154404059.40000007 | ||
| 250 | +2025,77440,13005182.149999999 | ||
| 251 | +2025,77520,34542653.12 | ||
| 252 | +2025,77530,1359464924.2300012 | ||
| 253 | +2025,77700,0 | ||
| 254 | +2025,77820,42931153.11 | ||
| 255 | +2025,78100,346333.4 | ||
| 256 | +2025,79100,20183063.04 | ||
| 257 | +2025,79200,74336.66 | ||
| 258 | +2025,79310,69600 | ||
| 259 | +2025,81100,189467352 | ||
| 260 | +2025,81200,1327189189.5399992 | ||
| 261 | +2025,81300,551652107.6699998 | ||
| 262 | +2025,81400,299412884.65 | ||
| 263 | +2025,81500,2442434 | ||
| 264 | +2025,81600,395569 | ||
| 265 | +2025,81950,39137 | ||
| 266 | +2025,81960,32790665.14 | ||
| 267 | +2025,81990,2007174694.16 | ||
| 268 | +2025,82100,879007438.5400001 | ||
| 269 | +2025,83110,29113391.8 | ||
| 270 | +2025,83120,4240482.35 | ||
| 271 | +2025,83210,351070.72 | ||
| 272 | +2025,83220,50460 | ||
| 273 | +2025,84100,65887753.279999994 | ||
| 274 | +2025,84230,394819507.59 | ||
| 275 | +2025,84240,723835763.5600002 | ||
| 276 | +2025,84250,65803251.14 | ||
| 277 | +2025,84900,8503359.91 | ||
| 278 | +2025,85100,94254502.63999999 | ||
| 279 | +2025,85200,10478575.300000003 | ||
| 280 | +2025,85400,6221225.630000003 | ||
| 281 | +2025,85500,3581209.3799999994 | ||
| 282 | +2025,85900,20640115.529999997 | ||
| 283 | +2025,86100,29028844.980000008 | ||
| 284 | +2025,91200,512102826.06 | ||
| 285 | +2025,92100,0 | ||
| 286 | +2025,94100,11526054.86 | ||
| 287 | +2025,94200,7279202.82 | ||
| 288 | +2025,94300,1551638.3499999999 | ||
| 289 | +2025,95100,291453722.56 | ||
| 290 | +2025,96100,5934986991.019999 | ||
| 291 | +2025,96200,614954091.44 | ||
| 292 | +2025,96900,2947311655.2299995 | ||
| 293 | +2025,97100,70099589.35000001 | ||
| 294 | +2025,98300,125000000 | ||
| 295 | +2025,99100,0 | ||
| 296 | +2025,99200,0 |
static/treemap_objeto_estado.csv
0 → 100644
This diff could not be displayed because it is too large.
-
Please register or login to post a comment