Showing
14 changed files
with
7334 additions
and
326 deletions
| ... | @@ -14,46 +14,37 @@ | ... | @@ -14,46 +14,37 @@ |
| 14 | let selectedIdx = $state(-1); | 14 | let selectedIdx = $state(-1); |
| 15 | let debounceTimer; | 15 | let debounceTimer; |
| 16 | 16 | ||
| 17 | - // Mapeo de clasificadores landing → filtros modal | 17 | + let searchMode = $state(get(landingSearchMode)); |
| 18 | - const classifierToFilter = { | 18 | + let selectedClassifiers = $state([...get(landingSelectedClassifiers)]); |
| 19 | - entidad: 'entidad', | 19 | + |
| 20 | - objeto: 'objeto', | 20 | + const CLASIFICADORES = [ |
| 21 | - rubro: 'rubro', | 21 | + { id: 'entidad', label: '¿Quién gasta?', tecnico: 'Institucional' }, |
| 22 | - finfun: 'finfun', | 22 | + { id: 'objeto', label: '¿En qué se gasta?', tecnico: 'Objeto de gasto' }, |
| 23 | - organismo: 'organismo', | 23 | + { id: 'finfun', label: '¿Para qué se gasta?', tecnico: 'Finalidad y función' }, |
| 24 | - fuente: 'fuente' | 24 | + { id: 'geografico', label: '¿Dónde se gasta?', tecnico: 'Geográfico' }, |
| 25 | - }; | 25 | + { id: 'sectores', label: '¿En qué sectores?', tecnico: 'Sectores económicos' }, |
| 26 | - | 26 | + { id: 'rubro', label: '¿Con qué recursos?', tecnico: 'Rubros' }, |
| 27 | - function getInitialFilters() { | 27 | + { id: 'organismo', label: '¿Quién financia?', tecnico: 'Organismos' }, |
| 28 | - const mode = get(landingSearchMode); | 28 | + { id: 'fuente', label: 'Origen del ingreso', tecnico: 'Fuentes' }, |
| 29 | - const classifiers = get(landingSelectedClassifiers); | 29 | + ]; |
| 30 | - if (mode === 'clasificadores' && classifiers.length > 0) { | 30 | + |
| 31 | - const filters = { entidad: false, objeto_gasto: false, rubro: false, finfun: false, organismo: false, fuente: false }; | 31 | + function toggleClassifier(id) { |
| 32 | - classifiers.forEach(c => { | 32 | + if (selectedClassifiers.includes(id)) { |
| 33 | - const key = classifierToFilter[c]; | 33 | + selectedClassifiers = selectedClassifiers.filter(c => c !== id); |
| 34 | - if (key === 'objeto') filters.objeto_gasto = true; | 34 | + } else { |
| 35 | - else if (key && filters[key] !== undefined) filters[key] = true; | 35 | + selectedClassifiers = [...selectedClassifiers, id]; |
| 36 | - }); | ||
| 37 | - return filters; | ||
| 38 | - } | ||
| 39 | - return { entidad: true, objeto_gasto: true, rubro: true, finfun: true, organismo: true, fuente: true }; | ||
| 40 | } | 36 | } |
| 41 | - | 37 | + // Sync to landing store |
| 42 | - let searchFilters = $state(getInitialFilters()); | 38 | + landingSelectedClassifiers.set(selectedClassifiers); |
| 43 | - | ||
| 44 | - function toggleFilter(tipo) { | ||
| 45 | - searchFilters[tipo] = !searchFilters[tipo]; | ||
| 46 | - // Re-buscar con filtros actualizados | ||
| 47 | if (searchVal.length >= 2) doSearch(searchVal); | 39 | if (searchVal.length >= 2) doSearch(searchVal); |
| 48 | } | 40 | } |
| 49 | 41 | ||
| 50 | - // Filtros activos → class_ param para Typesense | 42 | + // Clasificadores seleccionados → class_ param para Typesense |
| 43 | + const CLASS_API_MAP = { geografico: 'ubigeo', sectores: 'acteco' }; | ||
| 44 | + | ||
| 51 | function getActiveClasses() { | 45 | function getActiveClasses() { |
| 52 | - const map = { entidad: 'entidad', objeto_gasto: 'objeto', finfun: 'finfun' }; | 46 | + if (selectedClassifiers.length === 0) return []; |
| 53 | - return Object.entries(searchFilters) | 47 | + return selectedClassifiers.map(c => CLASS_API_MAP[c] || c); |
| 54 | - .filter(([_, v]) => v) | ||
| 55 | - .map(([k]) => map[k] || k) | ||
| 56 | - .filter(Boolean); | ||
| 57 | } | 48 | } |
| 58 | 49 | ||
| 59 | function parseMetadatos(meta) { | 50 | function parseMetadatos(meta) { |
| ... | @@ -66,33 +57,39 @@ | ... | @@ -66,33 +57,39 @@ |
| 66 | searchLoading = true; | 57 | searchLoading = true; |
| 67 | try { | 58 | try { |
| 68 | const classes = getActiveClasses(); | 59 | const classes = getActiveClasses(); |
| 60 | + const isClassMode = searchMode === 'clasificadores' && classes.length > 0; | ||
| 61 | + const isProgramMode = searchMode === 'programas'; | ||
| 69 | const params = new URLSearchParams({ | 62 | const params = new URLSearchParams({ |
| 70 | q: query, | 63 | q: query, |
| 71 | - is_class: 'true', | 64 | + per_page: '50' |
| 72 | - per_page: '30' | ||
| 73 | }); | 65 | }); |
| 74 | - if (classes.length > 0 && classes.length < 6) { | 66 | + if (isClassMode) { |
| 67 | + params.set('is_class', 'true'); | ||
| 75 | params.set('class_', classes.join(',')); | 68 | params.set('class_', classes.join(',')); |
| 69 | + } else if (isProgramMode) { | ||
| 70 | + params.set('is_class', 'false'); | ||
| 76 | } | 71 | } |
| 72 | + // Si ninguno: busca en todo | ||
| 77 | const res = await fetch(`/api/search?${params}`); | 73 | const res = await fetch(`/api/search?${params}`); |
| 78 | if (!res.ok) throw new Error(); | 74 | if (!res.ok) throw new Error(); |
| 79 | const data = await res.json(); | 75 | const data = await res.json(); |
| 80 | searchResults = (data.hits || []).map(hit => { | 76 | searchResults = (data.hits || []).map(hit => { |
| 81 | const meta = parseMetadatos(hit.document.metadatos); | 77 | const meta = parseMetadatos(hit.document.metadatos); |
| 82 | const cls = hit.document.class_; | 78 | const cls = hit.document.class_; |
| 83 | - let tipo = cls; | 79 | + const isClass = hit.document.is_class === true; |
| 80 | + let tipo = cls || 'programa'; | ||
| 84 | let codigo = ''; | 81 | let codigo = ''; |
| 85 | let nombre = hit.document.texto; | 82 | let nombre = hit.document.texto; |
| 86 | let highlight = hit.highlights?.[0]?.snippet || nombre; | 83 | let highlight = hit.highlights?.[0]?.snippet || nombre; |
| 87 | 84 | ||
| 88 | - if (cls === 'entidad') { | 85 | + if (isClass && cls === 'entidad') { |
| 89 | const esDA = !!meta.da; | 86 | const esDA = !!meta.da; |
| 90 | codigo = esDA ? `${meta.entidad}.${meta.da}` : String(meta.entidad); | 87 | codigo = esDA ? `${meta.entidad}.${meta.da}` : String(meta.entidad); |
| 91 | tipo = 'entidad'; | 88 | tipo = 'entidad'; |
| 92 | - } else if (cls === 'objeto') { | 89 | + } else if (isClass && cls === 'objeto') { |
| 93 | codigo = meta.objeto_subpartida || meta.objeto_partida || meta.objeto_subgrupo || meta.objeto_grupo || ''; | 90 | codigo = meta.objeto_subpartida || meta.objeto_partida || meta.objeto_subgrupo || meta.objeto_grupo || ''; |
| 94 | tipo = 'objeto_gasto'; | 91 | tipo = 'objeto_gasto'; |
| 95 | - } else if (cls === 'finfun') { | 92 | + } else if (isClass && cls === 'finfun') { |
| 96 | const fin = String(meta.finfun_finalidad || ''); | 93 | const fin = String(meta.finfun_finalidad || ''); |
| 97 | if (meta.finfun_funcion !== undefined && meta.finfun_grpfuncion !== undefined) { | 94 | if (meta.finfun_funcion !== undefined && meta.finfun_grpfuncion !== undefined) { |
| 98 | codigo = `${fin}.${meta.finfun_grpfuncion}.${meta.finfun_funcion}`; | 95 | codigo = `${fin}.${meta.finfun_grpfuncion}.${meta.finfun_funcion}`; |
| ... | @@ -102,9 +99,16 @@ | ... | @@ -102,9 +99,16 @@ |
| 102 | codigo = fin; | 99 | codigo = fin; |
| 103 | } | 100 | } |
| 104 | tipo = 'finfun'; | 101 | tipo = 'finfun'; |
| 102 | + } else if (isClass && cls === 'ubigeo') { | ||
| 103 | + codigo = String(meta.municipio_ubigeo || ''); | ||
| 104 | + tipo = 'ubigeo'; | ||
| 105 | + } else if (!isClass) { | ||
| 106 | + // Programa/proyecto | ||
| 107 | + tipo = 'programa'; | ||
| 108 | + codigo = `${meta.entidad || ''}-${meta.programa || ''}-${meta.proyecto || ''}`; | ||
| 105 | } | 109 | } |
| 106 | 110 | ||
| 107 | - return { tipo, codigo, nombre, highlight }; | 111 | + return { tipo, codigo, nombre, highlight, devengado: hit.document.devengado, gestion: hit.document.gestion }; |
| 108 | }); | 112 | }); |
| 109 | selectedIdx = -1; | 113 | selectedIdx = -1; |
| 110 | } catch { | 114 | } catch { |
| ... | @@ -115,10 +119,31 @@ | ... | @@ -115,10 +119,31 @@ |
| 115 | 119 | ||
| 116 | let filteredResults = $derived(searchResults); | 120 | let filteredResults = $derived(searchResults); |
| 117 | 121 | ||
| 118 | - // Focus input and sync filters when modal opens | 122 | + // Focus input and sync state when modal opens |
| 119 | $effect(() => { | 123 | $effect(() => { |
| 120 | if (open) { | 124 | if (open) { |
| 121 | - searchFilters = getInitialFilters(); | 125 | + const storeMode = get(landingSearchMode); |
| 126 | + const storeClassifiers = get(landingSelectedClassifiers); | ||
| 127 | + searchMode = storeMode; | ||
| 128 | + selectedClassifiers = [...storeClassifiers]; | ||
| 129 | + | ||
| 130 | + // Inferir del contexto de ruta si no hay clasificadores | ||
| 131 | + if (storeMode !== 'clasificadores' || storeClassifiers.length === 0) { | ||
| 132 | + if (typeof window !== 'undefined') { | ||
| 133 | + const path = window.location.pathname; | ||
| 134 | + if (path.startsWith('/entidad/')) { | ||
| 135 | + searchMode = 'clasificadores'; | ||
| 136 | + selectedClassifiers = ['entidad']; | ||
| 137 | + } else if (path.startsWith('/objeto/')) { | ||
| 138 | + searchMode = 'clasificadores'; | ||
| 139 | + selectedClassifiers = ['objeto']; | ||
| 140 | + } else if (path.startsWith('/finfun/')) { | ||
| 141 | + searchMode = 'clasificadores'; | ||
| 142 | + selectedClassifiers = ['finfun']; | ||
| 143 | + } | ||
| 144 | + } | ||
| 145 | + } | ||
| 146 | + | ||
| 122 | tick().then(() => { | 147 | tick().then(() => { |
| 123 | searchInput?.focus(); | 148 | searchInput?.focus(); |
| 124 | }); | 149 | }); |
| ... | @@ -160,19 +185,18 @@ | ... | @@ -160,19 +185,18 @@ |
| 160 | 185 | ||
| 161 | function goToResult(item) { | 186 | function goToResult(item) { |
| 162 | closeModal(); | 187 | closeModal(); |
| 163 | - if (item.tipo === 'entidad') { | 188 | + const routes = { |
| 164 | - goto(`/entidad/${item.codigo}`); | 189 | + entidad: '/entidad/', |
| 165 | - } else if (item.tipo === 'objeto_gasto') { | 190 | + objeto_gasto: '/objeto/', |
| 166 | - goto(`/objeto/${item.codigo}`); | 191 | + finfun: '/finfun/', |
| 167 | - } else if (item.tipo === 'finfun') { | 192 | + rubro: '/rubro/', |
| 168 | - goto(`/finfun/${item.codigo}`); | 193 | + organismo: '/organismo/', |
| 169 | - } else if (item.tipo === 'rubro') { | 194 | + fuente: '/fuente/', |
| 170 | - goto(`/rubro/${item.codigo}`); | 195 | + ubigeo: '/ubicacion/', |
| 171 | - } else if (item.tipo === 'organismo') { | 196 | + programa: '/proyecto/' |
| 172 | - goto(`/organismo/${item.codigo}`); | 197 | + }; |
| 173 | - } else if (item.tipo === 'fuente') { | 198 | + const base = routes[item.tipo]; |
| 174 | - goto(`/fuente/${item.codigo}`); | 199 | + if (base) goto(`${base}${item.codigo}`); |
| 175 | - } | ||
| 176 | } | 200 | } |
| 177 | 201 | ||
| 178 | function handleBackdropClick(e) { | 202 | function handleBackdropClick(e) { |
| ... | @@ -212,6 +236,16 @@ | ... | @@ -212,6 +236,16 @@ |
| 212 | label: 'Fuente', | 236 | label: 'Fuente', |
| 213 | color: '#7BC9A1', | 237 | color: '#7BC9A1', |
| 214 | description: 'Origen del financiamiento: TGN, créditos...' | 238 | description: 'Origen del financiamiento: TGN, créditos...' |
| 239 | + }, | ||
| 240 | + ubigeo: { | ||
| 241 | + label: 'Ubicación', | ||
| 242 | + color: '#6B9FD4', | ||
| 243 | + description: 'Municipios y ubicaciones geográficas' | ||
| 244 | + }, | ||
| 245 | + programa: { | ||
| 246 | + label: 'Programa', | ||
| 247 | + color: '#8A8578', | ||
| 248 | + description: 'Programas y proyectos de inversión' | ||
| 215 | } | 249 | } |
| 216 | }; | 250 | }; |
| 217 | </script> | 251 | </script> |
| ... | @@ -229,7 +263,7 @@ | ... | @@ -229,7 +263,7 @@ |
| 229 | bind:this={searchInput} | 263 | bind:this={searchInput} |
| 230 | type="text" | 264 | type="text" |
| 231 | class="search-modal-input" | 265 | class="search-modal-input" |
| 232 | - placeholder="Buscar instituciones, gastos, ingresos..." | 266 | + placeholder={searchMode === 'programas' ? 'Buscar programas y proyectos...' : selectedClassifiers.length > 0 ? 'Buscar en clasificadores seleccionados...' : 'Selecciona al menos un clasificador...'} |
| 233 | value={searchVal} | 267 | value={searchVal} |
| 234 | oninput={handleInput} | 268 | oninput={handleInput} |
| 235 | /> | 269 | /> |
| ... | @@ -238,85 +272,30 @@ | ... | @@ -238,85 +272,30 @@ |
| 238 | </button> | 272 | </button> |
| 239 | </div> | 273 | </div> |
| 240 | 274 | ||
| 241 | - <!-- Filters section with explicit descriptions --> | 275 | + <!-- Mode toggle + classifier pills --> |
| 242 | <div class="search-modal-filters"> | 276 | <div class="search-modal-filters"> |
| 243 | - <div class="filters-header"> | 277 | + <div class="mode-toggle"> |
| 244 | - <span class="filters-label">Buscar en:</span> | 278 | + <button class="mode-btn" class:active={searchMode === 'programas'} onclick={() => { searchMode = 'programas'; selectedClassifiers = []; landingSearchMode.set('programas'); landingSelectedClassifiers.set([]); if (searchVal.length >= 2) doSearch(searchVal); }}> |
| 245 | - </div> | 279 | + Programas |
| 246 | - <div class="filters-grid"> | ||
| 247 | - <button | ||
| 248 | - class="search-filter-card" | ||
| 249 | - class:filter-active={searchFilters.entidad} | ||
| 250 | - onclick={() => toggleFilter('entidad')} | ||
| 251 | - > | ||
| 252 | - <div class="filter-card-header"> | ||
| 253 | - <span class="filter-dot" style="background:{typeConfig.entidad.color}"></span> | ||
| 254 | - <span class="filter-title">Instituciones</span> | ||
| 255 | - <span class="filter-check">{searchFilters.entidad ? '✓' : ''}</span> | ||
| 256 | - </div> | ||
| 257 | - <span class="filter-desc">{typeConfig.entidad.description}</span> | ||
| 258 | </button> | 280 | </button> |
| 259 | - <button | 281 | + <button class="mode-btn" class:active={searchMode === 'clasificadores'} onclick={() => { searchMode = 'clasificadores'; landingSearchMode.set('clasificadores'); if (searchVal.length >= 2) doSearch(searchVal); }}> |
| 260 | - class="search-filter-card" | 282 | + Clasificadores |
| 261 | - class:filter-active={searchFilters.objeto_gasto} | ||
| 262 | - onclick={() => toggleFilter('objeto_gasto')} | ||
| 263 | - > | ||
| 264 | - <div class="filter-card-header"> | ||
| 265 | - <span class="filter-dot" style="background:{typeConfig.objeto_gasto.color}"></span> | ||
| 266 | - <span class="filter-title">Gastos</span> | ||
| 267 | - <span class="filter-check">{searchFilters.objeto_gasto ? '✓' : ''}</span> | ||
| 268 | - </div> | ||
| 269 | - <span class="filter-desc">{typeConfig.objeto_gasto.description}</span> | ||
| 270 | </button> | 283 | </button> |
| 271 | - <button | ||
| 272 | - class="search-filter-card" | ||
| 273 | - class:filter-active={searchFilters.rubro} | ||
| 274 | - onclick={() => toggleFilter('rubro')} | ||
| 275 | - > | ||
| 276 | - <div class="filter-card-header"> | ||
| 277 | - <span class="filter-dot" style="background:{typeConfig.rubro.color}"></span> | ||
| 278 | - <span class="filter-title">Ingresos</span> | ||
| 279 | - <span class="filter-check">{searchFilters.rubro ? '✓' : ''}</span> | ||
| 280 | </div> | 284 | </div> |
| 281 | - <span class="filter-desc">{typeConfig.rubro.description}</span> | 285 | + {#if searchMode === 'clasificadores'} |
| 282 | - </button> | 286 | + <div class="classifier-pills"> |
| 287 | + {#each CLASIFICADORES as cls} | ||
| 283 | <button | 288 | <button |
| 284 | - class="search-filter-card" | 289 | + class="classifier-pill" |
| 285 | - class:filter-active={searchFilters.finfun} | 290 | + class:active={selectedClassifiers.includes(cls.id)} |
| 286 | - onclick={() => toggleFilter('finfun')} | 291 | + onclick={() => toggleClassifier(cls.id)} |
| 287 | > | 292 | > |
| 288 | - <div class="filter-card-header"> | 293 | + <span class="pill-label">{cls.label}</span> |
| 289 | - <span class="filter-dot" style="background:{typeConfig.finfun.color}"></span> | 294 | + <span class="pill-tecnico">{cls.tecnico}</span> |
| 290 | - <span class="filter-title">Finalidad</span> | ||
| 291 | - <span class="filter-check">{searchFilters.finfun ? '✓' : ''}</span> | ||
| 292 | - </div> | ||
| 293 | - <span class="filter-desc">{typeConfig.finfun.description}</span> | ||
| 294 | - </button> | ||
| 295 | - <button | ||
| 296 | - class="search-filter-card" | ||
| 297 | - class:filter-active={searchFilters.organismo} | ||
| 298 | - onclick={() => toggleFilter('organismo')} | ||
| 299 | - > | ||
| 300 | - <div class="filter-card-header"> | ||
| 301 | - <span class="filter-dot" style="background:{typeConfig.organismo.color}"></span> | ||
| 302 | - <span class="filter-title">Organismos</span> | ||
| 303 | - <span class="filter-check">{searchFilters.organismo ? '✓' : ''}</span> | ||
| 304 | - </div> | ||
| 305 | - <span class="filter-desc">{typeConfig.organismo.description}</span> | ||
| 306 | - </button> | ||
| 307 | - <button | ||
| 308 | - class="search-filter-card" | ||
| 309 | - class:filter-active={searchFilters.fuente} | ||
| 310 | - onclick={() => toggleFilter('fuente')} | ||
| 311 | - > | ||
| 312 | - <div class="filter-card-header"> | ||
| 313 | - <span class="filter-dot" style="background:{typeConfig.fuente.color}"></span> | ||
| 314 | - <span class="filter-title">Fuentes</span> | ||
| 315 | - <span class="filter-check">{searchFilters.fuente ? '✓' : ''}</span> | ||
| 316 | - </div> | ||
| 317 | - <span class="filter-desc">{typeConfig.fuente.description}</span> | ||
| 318 | </button> | 295 | </button> |
| 296 | + {/each} | ||
| 319 | </div> | 297 | </div> |
| 298 | + {/if} | ||
| 320 | </div> | 299 | </div> |
| 321 | 300 | ||
| 322 | <!-- Results --> | 301 | <!-- Results --> |
| ... | @@ -485,11 +464,14 @@ | ... | @@ -485,11 +464,14 @@ |
| 485 | background: rgba(0, 0, 0, 0.1); | 464 | background: rgba(0, 0, 0, 0.1); |
| 486 | } | 465 | } |
| 487 | 466 | ||
| 488 | - /* Filters */ | 467 | + /* Filters: mode toggle + classifier pills */ |
| 489 | .search-modal-filters { | 468 | .search-modal-filters { |
| 490 | padding: 12px 16px; | 469 | padding: 12px 16px; |
| 491 | border-bottom: 1px solid rgba(255, 255, 255, 0.08); | 470 | border-bottom: 1px solid rgba(255, 255, 255, 0.08); |
| 492 | background: rgba(255, 255, 255, 0.03); | 471 | background: rgba(255, 255, 255, 0.03); |
| 472 | + display: flex; | ||
| 473 | + flex-direction: column; | ||
| 474 | + gap: 10px; | ||
| 493 | } | 475 | } |
| 494 | 476 | ||
| 495 | :global(html:not(.dark)) .search-modal-filters { | 477 | :global(html:not(.dark)) .search-modal-filters { |
| ... | @@ -497,90 +479,98 @@ | ... | @@ -497,90 +479,98 @@ |
| 497 | background: rgba(0, 0, 0, 0.02); | 479 | background: rgba(0, 0, 0, 0.02); |
| 498 | } | 480 | } |
| 499 | 481 | ||
| 500 | - .filters-header { | 482 | + .mode-toggle { |
| 501 | - margin-bottom: 8px; | 483 | + display: flex; |
| 484 | + gap: 4px; | ||
| 485 | + background: rgba(255, 255, 255, 0.06); | ||
| 486 | + border-radius: 8px; | ||
| 487 | + padding: 3px; | ||
| 502 | } | 488 | } |
| 503 | 489 | ||
| 504 | - .filters-label { | 490 | + :global(html:not(.dark)) .mode-toggle { |
| 505 | - font-size: 0.6875rem; | 491 | + background: rgba(0, 0, 0, 0.05); |
| 492 | + } | ||
| 493 | + | ||
| 494 | + .mode-btn { | ||
| 495 | + flex: 1; | ||
| 496 | + padding: 7px 14px; | ||
| 497 | + font-size: 0.875rem; | ||
| 506 | font-weight: 600; | 498 | font-weight: 600; |
| 507 | - text-transform: uppercase; | 499 | + font-family: var(--font-sans); |
| 508 | - letter-spacing: 0.08em; | 500 | + border: none; |
| 501 | + border-radius: 6px; | ||
| 502 | + cursor: pointer; | ||
| 503 | + background: transparent; | ||
| 509 | color: var(--theme-texto); | 504 | color: var(--theme-texto); |
| 510 | - opacity: 0.5; | 505 | + opacity: 0.7; |
| 506 | + transition: all 0.2s; | ||
| 511 | } | 507 | } |
| 512 | 508 | ||
| 513 | - .filters-grid { | 509 | + .mode-btn:hover { |
| 514 | - display: grid; | 510 | + opacity: 0.9; |
| 515 | - grid-template-columns: repeat(2, 1fr); | ||
| 516 | - gap: 8px; | ||
| 517 | } | 511 | } |
| 518 | 512 | ||
| 519 | - .search-filter-card { | 513 | + .mode-btn.active { |
| 520 | - display: flex; | 514 | + opacity: 1; |
| 521 | - flex-direction: column; | 515 | + background: rgba(255, 255, 255, 0.12); |
| 522 | - gap: 3px; | 516 | + color: var(--theme-titulo); |
| 523 | - padding: 8px 10px; | ||
| 524 | - background: rgba(255, 255, 255, 0.05); | ||
| 525 | - border: 1px solid rgba(255, 255, 255, 0.1); | ||
| 526 | - border-radius: 8px; | ||
| 527 | - cursor: pointer; | ||
| 528 | - transition: all 0.2s ease; | ||
| 529 | - text-align: left; | ||
| 530 | - opacity: 0.5; | ||
| 531 | } | 517 | } |
| 532 | 518 | ||
| 533 | - :global(html:not(.dark)) .search-filter-card { | 519 | + :global(html:not(.dark)) .mode-btn.active { |
| 534 | - background: rgba(0, 0, 0, 0.03); | 520 | + background: rgba(255, 255, 255, 0.8); |
| 535 | - border-color: rgba(0, 0, 0, 0.1); | 521 | + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08); |
| 536 | } | 522 | } |
| 537 | 523 | ||
| 538 | - .search-filter-card:hover { | 524 | + .classifier-pills { |
| 539 | - opacity: 0.8; | 525 | + display: flex; |
| 540 | - background: rgba(255, 255, 255, 0.08); | 526 | + flex-wrap: wrap; |
| 527 | + gap: 6px; | ||
| 541 | } | 528 | } |
| 542 | 529 | ||
| 543 | - :global(html:not(.dark)) .search-filter-card:hover { | 530 | + .classifier-pill { |
| 544 | - background: rgba(0, 0, 0, 0.06); | 531 | + display: flex; |
| 532 | + align-items: baseline; | ||
| 533 | + gap: 4px; | ||
| 534 | + padding: 5px 10px; | ||
| 535 | + font-size: 0.8125rem; | ||
| 536 | + font-weight: 500; | ||
| 537 | + font-family: var(--font-sans); | ||
| 538 | + border: 1px solid rgba(255, 255, 255, 0.1); | ||
| 539 | + border-radius: 20px; | ||
| 540 | + cursor: pointer; | ||
| 541 | + background: transparent; | ||
| 542 | + color: var(--theme-texto); | ||
| 543 | + opacity: 0.65; | ||
| 544 | + transition: all 0.2s; | ||
| 545 | } | 545 | } |
| 546 | 546 | ||
| 547 | - .search-filter-card.filter-active { | 547 | + .pill-label { |
| 548 | - opacity: 1; | 548 | + color: var(--theme-titulo); |
| 549 | - border-color: rgba(201, 167, 81, 0.5); | ||
| 550 | - background: rgba(201, 167, 81, 0.1); | ||
| 551 | } | 549 | } |
| 552 | 550 | ||
| 553 | - .filter-card-header { | 551 | + .pill-tecnico { |
| 554 | - display: flex; | 552 | + font-size: 0.6875rem; |
| 555 | - align-items: center; | 553 | + opacity: 0.7; |
| 556 | - gap: 6px; | ||
| 557 | } | 554 | } |
| 558 | 555 | ||
| 559 | - .filter-dot { | 556 | + :global(html:not(.dark)) .classifier-pill { |
| 560 | - width: 8px; | 557 | + border-color: rgba(0, 0, 0, 0.12); |
| 561 | - height: 8px; | ||
| 562 | - border-radius: 50%; | ||
| 563 | - flex-shrink: 0; | ||
| 564 | } | 558 | } |
| 565 | 559 | ||
| 566 | - .filter-title { | 560 | + .classifier-pill:hover { |
| 567 | - font-size: 0.8125rem; | 561 | + opacity: 0.8; |
| 568 | - font-weight: 600; | 562 | + background: rgba(255, 255, 255, 0.06); |
| 569 | - color: var(--theme-titulo); | ||
| 570 | - flex: 1; | ||
| 571 | } | 563 | } |
| 572 | 564 | ||
| 573 | - .filter-check { | 565 | + :global(html:not(.dark)) .classifier-pill:hover { |
| 574 | - font-size: 0.6875rem; | 566 | + background: rgba(0, 0, 0, 0.04); |
| 575 | - color: #C9A751; | ||
| 576 | - font-weight: 600; | ||
| 577 | } | 567 | } |
| 578 | 568 | ||
| 579 | - .filter-desc { | 569 | + .classifier-pill.active { |
| 580 | - font-size: 0.625rem; | 570 | + opacity: 1; |
| 581 | - color: var(--theme-texto); | 571 | + border-color: rgba(201, 167, 81, 0.5); |
| 582 | - opacity: 0.6; | 572 | + background: rgba(201, 167, 81, 0.12); |
| 583 | - line-height: 1.3; | 573 | + color: var(--theme-titulo); |
| 584 | } | 574 | } |
| 585 | 575 | ||
| 586 | /* Results */ | 576 | /* Results */ | ... | ... |
| 1 | import { writable } from 'svelte/store'; | 1 | import { writable } from 'svelte/store'; |
| 2 | 2 | ||
| 3 | // Persiste el estado de búsqueda del landing entre navegaciones | 3 | // Persiste el estado de búsqueda del landing entre navegaciones |
| 4 | -export const landingSearchMode = writable('programas'); // 'programas' | 'clasificadores' | 4 | +export const landingSearchMode = writable('todo'); // 'todo' | 'programas' | 'clasificadores' |
| 5 | export const landingSelectedClassifiers = writable([]); // ['entidad', 'objeto', ...] | 5 | export const landingSelectedClassifiers = writable([]); // ['entidad', 'objeto', ...] |
| 6 | export const landingSearchQuery = writable(''); | 6 | export const landingSearchQuery = writable(''); | ... | ... |
src/lib/stores/mapaCache.js
0 → 100644
| ... | @@ -8,6 +8,7 @@ | ... | @@ -8,6 +8,7 @@ |
| 8 | let searchFocused = false; | 8 | let searchFocused = false; |
| 9 | let searchVal = $landingSearchQuery || ''; | 9 | let searchVal = $landingSearchQuery || ''; |
| 10 | let searchInput; | 10 | let searchInput; |
| 11 | + let resultsAreaEl; | ||
| 11 | let isLoading = false; | 12 | let isLoading = false; |
| 12 | let results = null; | 13 | let results = null; |
| 13 | let error = null; | 14 | let error = null; |
| ... | @@ -59,28 +60,49 @@ | ... | @@ -59,28 +60,49 @@ |
| 59 | } | 60 | } |
| 60 | } | 61 | } |
| 61 | 62 | ||
| 62 | - $: sortedHits = allHits.length > 0 ? [...allHits].sort((a, b) => { | 63 | + $: sortedHits = filteredHits.length > 0 ? [...filteredHits].sort((a, b) => { |
| 63 | const dir = sortOrder === 'desc' ? 1 : -1; | 64 | const dir = sortOrder === 'desc' ? 1 : -1; |
| 64 | if (sortBy === 'alpha') return dir * (a.document.texto || '').localeCompare(b.document.texto || ''); | 65 | if (sortBy === 'alpha') return dir * (a.document.texto || '').localeCompare(b.document.texto || ''); |
| 65 | return dir * ((b.document.devengado || 0) - (a.document.devengado || 0)); | 66 | return dir * ((b.document.devengado || 0) - (a.document.devengado || 0)); |
| 66 | }) : []; | 67 | }) : []; |
| 67 | 68 | ||
| 68 | // Detectar tipos de clasificadores presentes en los resultados | 69 | // Detectar tipos de clasificadores presentes en los resultados |
| 70 | + // Mapeo inverso: API class_ → id interno del clasificador | ||
| 71 | + const CLASS_REVERSE_MAP = { ubigeo: 'geografico', acteco: 'sectores' }; | ||
| 72 | + | ||
| 73 | + // Filtrar hits según clasificadores seleccionados (la API puede devolver tipos extra) | ||
| 74 | + $: filteredHits = (() => { | ||
| 75 | + if (searchMode !== 'clasificadores' || selectedClassifiers.length === 0) return allHits; | ||
| 76 | + const allowedClasses = new Set(selectedClassifiers.map(c => { | ||
| 77 | + const map = { geografico: 'ubigeo', sectores: 'acteco' }; | ||
| 78 | + return map[c] || c; | ||
| 79 | + })); | ||
| 80 | + return allHits.filter(h => allowedClasses.has(h.document.class_)); | ||
| 81 | + })(); | ||
| 82 | + | ||
| 69 | $: activeClassTypes = (() => { | 83 | $: activeClassTypes = (() => { |
| 70 | const types = new Set(); | 84 | const types = new Set(); |
| 71 | - allHits.forEach(h => { if (h.document.class_) types.add(h.document.class_); }); | 85 | + filteredHits.forEach(h => { if (h.document.class_) types.add(h.document.class_); }); |
| 72 | return types; | 86 | return types; |
| 73 | })(); | 87 | })(); |
| 74 | $: isMixedClassSearch = activeClassTypes.size > 1; | 88 | $: isMixedClassSearch = activeClassTypes.size > 1; |
| 75 | $: isEntidadSearchActive = !isMixedClassSearch && activeClassTypes.has('entidad'); | 89 | $: isEntidadSearchActive = !isMixedClassSearch && activeClassTypes.has('entidad'); |
| 76 | $: isObjetoSearchActive = !isMixedClassSearch && activeClassTypes.has('objeto'); | 90 | $: isObjetoSearchActive = !isMixedClassSearch && activeClassTypes.has('objeto'); |
| 77 | $: isFinfunSearchActive = !isMixedClassSearch && activeClassTypes.has('finfun'); | 91 | $: isFinfunSearchActive = !isMixedClassSearch && activeClassTypes.has('finfun'); |
| 92 | + $: isUbigeoSearchActive = !isMixedClassSearch && activeClassTypes.has('ubigeo'); | ||
| 78 | 93 | ||
| 79 | // Labels para tipos de clasificador | 94 | // Labels para tipos de clasificador |
| 80 | const CLASS_LABELS = { | 95 | const CLASS_LABELS = { |
| 81 | entidad: 'Entidades', | 96 | entidad: 'Entidades', |
| 82 | objeto: 'Objetos de Gasto', | 97 | objeto: 'Objetos de Gasto', |
| 83 | finfun: 'Finalidad y Función', | 98 | finfun: 'Finalidad y Función', |
| 99 | + rubro: 'Rubros de Ingreso', | ||
| 100 | + organismo: 'Organismos', | ||
| 101 | + fuente: 'Fuentes de Financiamiento', | ||
| 102 | + ubigeo: 'Ubicación geográfica', | ||
| 103 | + geografico: 'Ubicación geográfica', | ||
| 104 | + acteco: 'Sectores Económicos', | ||
| 105 | + sectores: 'Sectores Económicos', | ||
| 84 | }; | 106 | }; |
| 85 | 107 | ||
| 86 | // Extraer código de finfun desde metadatos (formato con puntos: 1, 1.1, 1.1.3) | 108 | // Extraer código de finfun desde metadatos (formato con puntos: 1, 1.1, 1.1.3) |
| ... | @@ -108,12 +130,12 @@ | ... | @@ -108,12 +130,12 @@ |
| 108 | 130 | ||
| 109 | // Agrupar resultados según tipo | 131 | // Agrupar resultados según tipo |
| 110 | $: groupedHits = (() => { | 132 | $: groupedHits = (() => { |
| 111 | - if (allHits.length === 0) return null; | 133 | + if (filteredHits.length === 0) return null; |
| 112 | 134 | ||
| 113 | // Búsqueda mixta: agrupar por tipo de clasificador | 135 | // Búsqueda mixta: agrupar por tipo de clasificador |
| 114 | if (isMixedClassSearch) { | 136 | if (isMixedClassSearch) { |
| 115 | // Filtrar duplicados de finfun | 137 | // Filtrar duplicados de finfun |
| 116 | - const filtered = allHits.filter(hit => { | 138 | + const filtered = filteredHits.filter(hit => { |
| 117 | if (hit.document.class_ !== 'finfun') return true; | 139 | if (hit.document.class_ !== 'finfun') return true; |
| 118 | const m = parseMetadatos(hit.document.metadatos); | 140 | const m = parseMetadatos(hit.document.metadatos); |
| 119 | return !(m.finfun_funcion === 0 && m.finfun_grpfuncion !== undefined); | 141 | return !(m.finfun_funcion === 0 && m.finfun_grpfuncion !== undefined); |
| ... | @@ -149,7 +171,7 @@ | ... | @@ -149,7 +171,7 @@ |
| 149 | 171 | ||
| 150 | if (isEntidadSearchActive) { | 172 | if (isEntidadSearchActive) { |
| 151 | const groups = {}; | 173 | const groups = {}; |
| 152 | - allHits.forEach(hit => { | 174 | + filteredHits.forEach(hit => { |
| 153 | const m = parseMetadatos(hit.document.metadatos); | 175 | const m = parseMetadatos(hit.document.metadatos); |
| 154 | const subarea = extractAfterHyphen(m.entidad_desc_subarea) || 'Otros'; | 176 | const subarea = extractAfterHyphen(m.entidad_desc_subarea) || 'Otros'; |
| 155 | if (!groups[subarea]) { | 177 | if (!groups[subarea]) { |
| ... | @@ -180,7 +202,7 @@ | ... | @@ -180,7 +202,7 @@ |
| 180 | 202 | ||
| 181 | if (isObjetoSearchActive) { | 203 | if (isObjetoSearchActive) { |
| 182 | const groups = {}; | 204 | const groups = {}; |
| 183 | - allHits.forEach(hit => { | 205 | + filteredHits.forEach(hit => { |
| 184 | const m = parseMetadatos(hit.document.metadatos); | 206 | const m = parseMetadatos(hit.document.metadatos); |
| 185 | const groupName = extractAfterHyphen(m.objeto_desc_grupo) || 'Otros'; | 207 | const groupName = extractAfterHyphen(m.objeto_desc_grupo) || 'Otros'; |
| 186 | if (!groups[groupName]) { | 208 | if (!groups[groupName]) { |
| ... | @@ -209,7 +231,7 @@ | ... | @@ -209,7 +231,7 @@ |
| 209 | 231 | ||
| 210 | if (isFinfunSearchActive) { | 232 | if (isFinfunSearchActive) { |
| 211 | // Filtrar duplicados: función 0 es idéntica a su grupo función padre | 233 | // Filtrar duplicados: función 0 es idéntica a su grupo función padre |
| 212 | - const filtered = allHits.filter(hit => { | 234 | + const filtered = filteredHits.filter(hit => { |
| 213 | const m = parseMetadatos(hit.document.metadatos); | 235 | const m = parseMetadatos(hit.document.metadatos); |
| 214 | return !(m.finfun_funcion === 0 && m.finfun_grpfuncion !== undefined); | 236 | return !(m.finfun_funcion === 0 && m.finfun_grpfuncion !== undefined); |
| 215 | }); | 237 | }); |
| ... | @@ -242,6 +264,11 @@ | ... | @@ -242,6 +264,11 @@ |
| 242 | return sortedGroups; | 264 | return sortedGroups; |
| 243 | } | 265 | } |
| 244 | 266 | ||
| 267 | + if (isUbigeoSearchActive) { | ||
| 268 | + // Sin agrupación, se usa la vista plana | ||
| 269 | + return null; | ||
| 270 | + } | ||
| 271 | + | ||
| 245 | return null; | 272 | return null; |
| 246 | })(); | 273 | })(); |
| 247 | 274 | ||
| ... | @@ -258,16 +285,52 @@ | ... | @@ -258,16 +285,52 @@ |
| 258 | 285 | ||
| 259 | // Clasificadores unificados (transversales) | 286 | // Clasificadores unificados (transversales) |
| 260 | const CLASIFICADORES = [ | 287 | const CLASIFICADORES = [ |
| 261 | - { id: 'entidad', label: '¿Quién gasta?', icon: '🏛️' }, | 288 | + { id: 'entidad', label: '¿Quién gasta?', icon: '🏛️', tecnico: 'Clasificador institucional', desc: 'Ministerios, municipios, universidades, empresas públicas y todas las entidades del sector público que ejecutan presupuesto.' }, |
| 262 | - { id: 'objeto', label: '¿En qué se gasta?', icon: '📦' }, | 289 | + { id: 'objeto', label: '¿En qué se gasta?', icon: '📦', tecnico: 'Objeto de gasto', desc: 'Categorías estandarizadas del destino del gasto: sueldos, viáticos, materiales, servicios básicos, inversiones, deuda, etc.' }, |
| 263 | - { id: 'finfun', label: '¿Para qué se gasta?', icon: '🎯' }, | 290 | + { id: 'finfun', label: '¿Para qué se gasta?', icon: '🎯', tecnico: 'Finalidad y función', desc: 'El propósito del gasto según su función social: salud, educación, defensa, justicia, infraestructura, etc.' }, |
| 264 | - { id: 'geografico', label: '¿Dónde se gasta?', icon: '📍' }, | 291 | + { id: 'geografico', label: '¿Dónde se gasta?', icon: '📍', tecnico: 'Geográfico', desc: 'Distribución territorial del gasto por departamento y municipio.' }, |
| 265 | - { id: 'sectores', label: '¿En qué sectores se gasta?', icon: '🏢' }, | 292 | + { id: 'sectores', label: '¿En qué sectores se gasta?', icon: '🏢', tecnico: 'Sectores económicos', desc: 'Sectores de actividad económica donde se dirige el gasto: agricultura, minería, manufactura, transporte, etc.' }, |
| 266 | - { id: 'rubro', label: '¿Con qué recursos?', icon: '💰' }, | 293 | + { id: 'rubro', label: '¿Con qué recursos?', icon: '💰', tecnico: 'Rubros de ingreso', desc: 'Tipos de recursos que financian el gasto: impuestos, tasas, regalías, transferencias, donaciones, créditos, etc.' }, |
| 267 | - { id: 'organismo', label: '¿Quién financia?', icon: '🏦' }, | 294 | + { id: 'organismo', label: '¿Quién financia?', icon: '🏦', tecnico: 'Organismos financiadores', desc: 'Origen institucional del financiamiento: Tesoro General, gobiernos subnacionales, organismos internacionales, etc.' }, |
| 268 | - { id: 'fuente', label: 'Origen del ingreso', icon: '💵' }, | 295 | + { id: 'fuente', label: 'Origen del ingreso', icon: '💵', tecnico: 'Fuentes de financiamiento', desc: 'Clasificación por tipo de fuente: recursos propios, transferencias, crédito interno/externo, donaciones.' }, |
| 269 | ]; | 296 | ]; |
| 270 | 297 | ||
| 298 | + // Modal explicativo contextual | ||
| 299 | + let showExplainModal = false; | ||
| 300 | + let explainClassifier = null; // id del clasificador a explicar | ||
| 301 | + | ||
| 302 | + function openExplain() { | ||
| 303 | + if (selectedClassifiers.length === 1) { | ||
| 304 | + explainClassifier = selectedClassifiers[0]; | ||
| 305 | + } else { | ||
| 306 | + explainClassifier = null; // modal genérico | ||
| 307 | + } | ||
| 308 | + showExplainModal = true; | ||
| 309 | + } | ||
| 310 | + | ||
| 311 | + const EXPLAIN_CONTENT = { | ||
| 312 | + entidad: { | ||
| 313 | + titulo: 'Instituciones públicas', | ||
| 314 | + subtitulo: 'Clasificador institucional', | ||
| 315 | + intro: 'El presupuesto público boliviano se ejecuta a través de aproximadamente 2000 instituciones. Cada una recibe recursos, los gasta y reporta su ejecución.', | ||
| 316 | + bloques: [ | ||
| 317 | + { | ||
| 318 | + titulo: 'Entidades', | ||
| 319 | + texto: 'Son las instituciones principales: ministerios, gobiernos municipales, universidades, empresas públicas, fondos, etc. Hay aproximadamente 500 y cada una tiene presupuesto propio consolidado.' | ||
| 320 | + }, | ||
| 321 | + { | ||
| 322 | + titulo: 'Unidades internas', | ||
| 323 | + texto: 'Dentro de cada entidad pueden existir unidades con ejecución propia: hospitales municipales, concejos, direcciones ejecutivas, centros de salud, etc. Hay aproximadamente 1500 y siempre pertenecen a una entidad madre.' | ||
| 324 | + }, | ||
| 325 | + { | ||
| 326 | + titulo: 'Jerarquía', | ||
| 327 | + texto: 'Las entidades se organizan en: Sector, Subsector, Área, Subárea y Entidad. Por ejemplo: Sector Público → No Financiero → Administración Territorial → Gobiernos Municipales → Gobierno Municipal de Cobija.' | ||
| 328 | + } | ||
| 329 | + ], | ||
| 330 | + link: { href: '/clasificadores/institucional', texto: 'Ver la lista completa de instituciones' } | ||
| 331 | + } | ||
| 332 | + }; | ||
| 333 | + | ||
| 271 | function toggleClassifier(id) { | 334 | function toggleClassifier(id) { |
| 272 | if (selectedClassifiers.includes(id)) { | 335 | if (selectedClassifiers.includes(id)) { |
| 273 | selectedClassifiers = selectedClassifiers.filter(c => c !== id); | 336 | selectedClassifiers = selectedClassifiers.filter(c => c !== id); |
| ... | @@ -282,11 +345,17 @@ | ... | @@ -282,11 +345,17 @@ |
| 282 | } | 345 | } |
| 283 | 346 | ||
| 284 | function setMode(mode) { | 347 | function setMode(mode) { |
| 348 | + if (searchMode === mode) { | ||
| 349 | + // Click en la misma card → deseleccionar, volver a "todo" | ||
| 350 | + searchMode = 'todo'; | ||
| 351 | + selectedClassifiers = []; | ||
| 352 | + } else { | ||
| 285 | searchMode = mode; | 353 | searchMode = mode; |
| 286 | if (mode === 'programas') { | 354 | if (mode === 'programas') { |
| 287 | selectedClassifiers = []; | 355 | selectedClassifiers = []; |
| 288 | } | 356 | } |
| 289 | } | 357 | } |
| 358 | + } | ||
| 290 | 359 | ||
| 291 | // Persistir estado de búsqueda al store | 360 | // Persistir estado de búsqueda al store |
| 292 | $: $landingSearchMode = searchMode; | 361 | $: $landingSearchMode = searchMode; |
| ... | @@ -464,6 +533,19 @@ | ... | @@ -464,6 +533,19 @@ |
| 464 | return () => { sectionObserver?.disconnect(); cleanupHero?.(); }; | 533 | return () => { sectionObserver?.disconnect(); cleanupHero?.(); }; |
| 465 | }); | 534 | }); |
| 466 | 535 | ||
| 536 | + // Medir posición del panel para max-height dinámico | ||
| 537 | + function updateResultsHeight() { | ||
| 538 | + if (resultsAreaEl) { | ||
| 539 | + const rect = resultsAreaEl.getBoundingClientRect(); | ||
| 540 | + resultsAreaEl.style.setProperty('--results-top', `${rect.top}px`); | ||
| 541 | + } | ||
| 542 | + } | ||
| 543 | + | ||
| 544 | + // Actualizar al cambiar resultados | ||
| 545 | + $: if (allHits.length > 0 && resultsAreaEl) { | ||
| 546 | + updateResultsHeight(); | ||
| 547 | + } | ||
| 548 | + | ||
| 467 | async function handleInput(e) { | 549 | async function handleInput(e) { |
| 468 | searchVal = e.target.value; | 550 | searchVal = e.target.value; |
| 469 | clearTimeout(debounceTimer); | 551 | clearTimeout(debounceTimer); |
| ... | @@ -481,13 +563,6 @@ | ... | @@ -481,13 +563,6 @@ |
| 481 | return; | 563 | return; |
| 482 | } | 564 | } |
| 483 | 565 | ||
| 484 | - const needsClassifiers = searchMode === 'clasificadores'; | ||
| 485 | - if (needsClassifiers && selectedClassifiers.length === 0) { | ||
| 486 | - results = null; | ||
| 487 | - error = null; | ||
| 488 | - return; | ||
| 489 | - } | ||
| 490 | - | ||
| 491 | debounceTimer = setTimeout(() => performSearch(searchVal), DEBOUNCE_MS); | 566 | debounceTimer = setTimeout(() => performSearch(searchVal), DEBOUNCE_MS); |
| 492 | } | 567 | } |
| 493 | 568 | ||
| ... | @@ -505,20 +580,25 @@ | ... | @@ -505,20 +580,25 @@ |
| 505 | currentPage = page; | 580 | currentPage = page; |
| 506 | 581 | ||
| 507 | try { | 582 | try { |
| 508 | - const isClassSearch = searchMode === 'clasificadores'; | 583 | + const isClassSearch = searchMode === 'clasificadores' && selectedClassifiers.length > 0; |
| 584 | + const isProgramSearch = searchMode === 'programas'; | ||
| 509 | const params = new URLSearchParams({ | 585 | const params = new URLSearchParams({ |
| 510 | q: query, | 586 | q: query, |
| 511 | - is_class: isClassSearch ? 'true' : 'false', | ||
| 512 | mode: 'gastos', | 587 | mode: 'gastos', |
| 513 | page: page.toString() | 588 | page: page.toString() |
| 514 | }); | 589 | }); |
| 515 | - // Para clasificadores, pedir más resultados para tener conteos precisos | 590 | + |
| 591 | + // Mapeo de ids internos a class_ de la API | ||
| 592 | + const CLASS_API_MAP = { geografico: 'ubigeo', sectores: 'acteco' }; | ||
| 593 | + | ||
| 516 | if (isClassSearch) { | 594 | if (isClassSearch) { |
| 595 | + params.set('is_class', 'true'); | ||
| 517 | params.set('per_page', '250'); | 596 | params.set('per_page', '250'); |
| 597 | + params.set('class_', selectedClassifiers.map(c => CLASS_API_MAP[c] || c).join(',')); | ||
| 598 | + } else if (isProgramSearch) { | ||
| 599 | + params.set('is_class', 'false'); | ||
| 518 | } | 600 | } |
| 519 | - if (isClassSearch && selectedClassifiers.length > 0) { | 601 | + // Si ninguno: no envía is_class → busca en todo |
| 520 | - params.set('class_', selectedClassifiers.join(',')); | ||
| 521 | - } | ||
| 522 | 602 | ||
| 523 | console.log('[Search]', `${API_BASE}?${params}`); | 603 | console.log('[Search]', `${API_BASE}?${params}`); |
| 524 | const res = await fetch(`${API_BASE}?${params}`); | 604 | const res = await fetch(`${API_BASE}?${params}`); |
| ... | @@ -550,7 +630,6 @@ | ... | @@ -550,7 +630,6 @@ |
| 550 | function handleGlobalKeydown(e) { | 630 | function handleGlobalKeydown(e) { |
| 551 | if (e.key === 'Escape' && (searchVal || allHits.length > 0)) { | 631 | if (e.key === 'Escape' && (searchVal || allHits.length > 0)) { |
| 552 | clearSearch(); | 632 | clearSearch(); |
| 553 | - searchInput?.blur(); | ||
| 554 | selectedResultIndex = -1; | 633 | selectedResultIndex = -1; |
| 555 | } | 634 | } |
| 556 | } | 635 | } |
| ... | @@ -558,7 +637,6 @@ | ... | @@ -558,7 +637,6 @@ |
| 558 | function handleKeydown(e) { | 637 | function handleKeydown(e) { |
| 559 | if (e.key === 'Escape') { | 638 | if (e.key === 'Escape') { |
| 560 | clearSearch(); | 639 | clearSearch(); |
| 561 | - searchInput?.blur(); | ||
| 562 | selectedResultIndex = -1; | 640 | selectedResultIndex = -1; |
| 563 | } | 641 | } |
| 564 | 642 | ||
| ... | @@ -596,9 +674,13 @@ | ... | @@ -596,9 +674,13 @@ |
| 596 | let url; | 674 | let url; |
| 597 | const isFinfunClass = hit.document.class_ === 'finfun'; | 675 | const isFinfunClass = hit.document.class_ === 'finfun'; |
| 598 | 676 | ||
| 677 | + const isUbigeoClass = hit.document.class_ === 'ubigeo'; | ||
| 678 | + | ||
| 599 | if (isEntidadClass) { | 679 | if (isEntidadClass) { |
| 600 | const entCodigo = meta.da ? `${meta.entidad}.${meta.da}` : meta.entidad; | 680 | const entCodigo = meta.da ? `${meta.entidad}.${meta.da}` : meta.entidad; |
| 601 | url = `/entidad/${entCodigo}`; | 681 | url = `/entidad/${entCodigo}`; |
| 682 | + } else if (isUbigeoClass) { | ||
| 683 | + url = `/ubicacion/${meta.municipio_ubigeo}`; | ||
| 602 | } else if (isObjetoClass) { | 684 | } else if (isObjetoClass) { |
| 603 | url = `/objeto/${getObjetoCodigo(meta)}`; | 685 | url = `/objeto/${getObjetoCodigo(meta)}`; |
| 604 | } else if (isFinfunClass) { | 686 | } else if (isFinfunClass) { |
| ... | @@ -623,6 +705,7 @@ | ... | @@ -623,6 +705,7 @@ |
| 623 | clearTimeout(debounceTimer); | 705 | clearTimeout(debounceTimer); |
| 624 | clearTimeout(hintTimer); | 706 | clearTimeout(hintTimer); |
| 625 | searchInput?.focus(); | 707 | searchInput?.focus(); |
| 708 | + searchInput?.focus(); | ||
| 626 | } | 709 | } |
| 627 | 710 | ||
| 628 | // Helpers | 711 | // Helpers |
| ... | @@ -719,27 +802,38 @@ | ... | @@ -719,27 +802,38 @@ |
| 719 | return `${m.entidad}-${m.programa}-${m.proyecto}-${m.actividad}-${hit.document.gestion}`; | 802 | return `${m.entidad}-${m.programa}-${m.proyecto}-${m.actividad}-${hit.document.gestion}`; |
| 720 | } | 803 | } |
| 721 | 804 | ||
| 722 | - $: stats = allHits.length > 0 ? getStats( | 805 | + $: stats = filteredHits.length > 0 ? getStats( |
| 723 | - (isFinfunSearchActive || isMixedClassSearch) ? flatHitsForNav : allHits, | 806 | + (isFinfunSearchActive || isMixedClassSearch) ? flatHitsForNav : filteredHits, |
| 724 | - (isFinfunSearchActive || isMixedClassSearch) ? flatHitsForNav.length : (results?.found || allHits.length) | 807 | + (isFinfunSearchActive || isMixedClassSearch) ? flatHitsForNav.length : filteredHits.length |
| 725 | ) : null; | 808 | ) : null; |
| 726 | 809 | ||
| 727 | // Texto contextual para resultados | 810 | // Texto contextual para resultados |
| 811 | + const CONTEXT_LABELS = { | ||
| 812 | + entidad: 'Buscando entre 2000 instituciones públicas', | ||
| 813 | + objeto: 'Buscando entre 2500 categorías de gasto', | ||
| 814 | + finfun: 'Buscando entre 200 finalidades y funciones', | ||
| 815 | + geografico: 'Buscando entre 467 geografías del país', | ||
| 816 | + sectores: 'Buscando entre sectores económicos', | ||
| 817 | + rubro: 'Buscando entre rubros de ingreso', | ||
| 818 | + organismo: 'Buscando entre organismos financiadores', | ||
| 819 | + fuente: 'Buscando entre fuentes de financiamiento', | ||
| 820 | + }; | ||
| 821 | + | ||
| 728 | $: searchContextText = (() => { | 822 | $: searchContextText = (() => { |
| 729 | if (searchMode === 'programas') { | 823 | if (searchMode === 'programas') { |
| 730 | return 'Buscando en programas y proyectos'; | 824 | return 'Buscando en programas y proyectos'; |
| 731 | - } else { | 825 | + } |
| 732 | - if (selectedClassifiers.length === 0) return ''; | 826 | + if (searchMode === 'clasificadores' && selectedClassifiers.length > 0) { |
| 827 | + if (selectedClassifiers.length === 1) { | ||
| 828 | + return CONTEXT_LABELS[selectedClassifiers[0]] || ''; | ||
| 829 | + } | ||
| 733 | const labels = selectedClassifiers.map(id => { | 830 | const labels = selectedClassifiers.map(id => { |
| 734 | const clf = CLASIFICADORES.find(c => c.id === id); | 831 | const clf = CLASIFICADORES.find(c => c.id === id); |
| 735 | - return clf ? clf.label.replace('¿', '').replace('?', '').toLowerCase() : id; | 832 | + return clf ? clf.tecnico.toLowerCase() : id; |
| 736 | }); | 833 | }); |
| 737 | - if (labels.length === 1) { | 834 | + return `Buscando en ${labels.slice(0, -1).join(', ')} y ${labels[labels.length - 1]}`; |
| 738 | - return `Buscando por ${labels[0]}`; | ||
| 739 | - } else { | ||
| 740 | - return `Buscando por ${labels.slice(0, -1).join(', ')} y ${labels[labels.length - 1]}`; | ||
| 741 | - } | ||
| 742 | } | 835 | } |
| 836 | + return 'Buscando en todo'; | ||
| 743 | })(); | 837 | })(); |
| 744 | 838 | ||
| 745 | $: { | 839 | $: { |
| ... | @@ -747,14 +841,7 @@ | ... | @@ -747,14 +841,7 @@ |
| 747 | const _classifiers = selectedClassifiers; | 841 | const _classifiers = selectedClassifiers; |
| 748 | if (searchVal.length >= 3 && mounted) { | 842 | if (searchVal.length >= 3 && mounted) { |
| 749 | clearTimeout(debounceTimer); | 843 | clearTimeout(debounceTimer); |
| 750 | - const needsClassifiers = _mode === 'clasificadores'; | ||
| 751 | - if (!needsClassifiers || _classifiers.length > 0) { | ||
| 752 | debounceTimer = setTimeout(() => performSearch(searchVal), DEBOUNCE_MS); | 844 | debounceTimer = setTimeout(() => performSearch(searchVal), DEBOUNCE_MS); |
| 753 | - } else { | ||
| 754 | - // No hay clasificadores seleccionados - limpiar resultados | ||
| 755 | - results = null; | ||
| 756 | - allHits = []; | ||
| 757 | - } | ||
| 758 | } | 845 | } |
| 759 | } | 846 | } |
| 760 | 847 | ||
| ... | @@ -807,15 +894,15 @@ | ... | @@ -807,15 +894,15 @@ |
| 807 | <div class="ambient-glow"></div> | 894 | <div class="ambient-glow"></div> |
| 808 | 895 | ||
| 809 | <!-- Título --> | 896 | <!-- Título --> |
| 810 | - <h1 class="hero-title" class:hero-collapsed={allHits.length > 0}> | 897 | + <h1 class="hero-title"> |
| 811 | Presupuesto <span class="gold">Abierto</span> | 898 | Presupuesto <span class="gold">Abierto</span> |
| 812 | </h1> | 899 | </h1> |
| 813 | - <p class="hero-sub" class:hero-collapsed={allHits.length > 0}> | 900 | + <p class="hero-sub"> |
| 814 | Información histórica y diaria de todo el Estado Boliviano. | 901 | Información histórica y diaria de todo el Estado Boliviano. |
| 815 | </p> | 902 | </p> |
| 816 | 903 | ||
| 817 | <!-- Buscador --> | 904 | <!-- Buscador --> |
| 818 | - <div class="search-wrap" class:search-wrap-with-results={allHits.length > 0}> | 905 | + <div class="search-wrap"> |
| 819 | <div class="search-bar" class:search-focused={searchVal.length > 0} class:has-results={isSearching}> | 906 | <div class="search-bar" class:search-focused={searchVal.length > 0} class:has-results={isSearching}> |
| 820 | <svg class="search-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" | 907 | <svg class="search-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" |
| 821 | stroke={searchVal.length > 0 ? '#FFFFFF' : '#F0C14D'} stroke-width="2" stroke-linecap="round"> | 908 | stroke={searchVal.length > 0 ? '#FFFFFF' : '#F0C14D'} stroke-width="2" stroke-linecap="round"> |
| ... | @@ -846,6 +933,7 @@ | ... | @@ -846,6 +933,7 @@ |
| 846 | <line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/> | 933 | <line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/> |
| 847 | </svg> | 934 | </svg> |
| 848 | </button> | 935 | </button> |
| 936 | + <kbd class="search-esc" on:click={clearSearch}>Esc</kbd> | ||
| 849 | {/if} | 937 | {/if} |
| 850 | {#if isLoading} | 938 | {#if isLoading} |
| 851 | <div class="search-loading"><span class="loading-dot"></span></div> | 939 | <div class="search-loading"><span class="loading-dot"></span></div> |
| ... | @@ -854,11 +942,11 @@ | ... | @@ -854,11 +942,11 @@ |
| 854 | </div> | 942 | </div> |
| 855 | 943 | ||
| 856 | <!-- ─── ÁREA DE RESULTADOS ─── --> | 944 | <!-- ─── ÁREA DE RESULTADOS ─── --> |
| 857 | - <div class="results-area"> | 945 | + <div class="results-area" bind:this={resultsAreaEl}> |
| 858 | 946 | ||
| 859 | <!-- ─── CARDS DE MODO (se tapan con resultados) ─── --> | 947 | <!-- ─── CARDS DE MODO (se tapan con resultados) ─── --> |
| 860 | - <div class="section-group" class:section-group-hidden={allHits.length > 0}> | 948 | + <div class="section-group"> |
| 861 | - <span class="section-label">Configura tu búsqueda</span> | 949 | + <span class="section-label">Refinar búsqueda</span> |
| 862 | <div class="mode-cards"> | 950 | <div class="mode-cards"> |
| 863 | 951 | ||
| 864 | <!-- Card: Programas y Proyectos --> | 952 | <!-- Card: Programas y Proyectos --> |
| ... | @@ -986,36 +1074,31 @@ | ... | @@ -986,36 +1074,31 @@ |
| 986 | </a> | 1074 | </a> |
| 987 | </div> | 1075 | </div> |
| 988 | 1076 | ||
| 989 | - <!-- Estados de búsqueda (mensajes) --> | 1077 | + <!-- Contenedor de resultados (overlay absoluto) --> |
| 990 | - {#if showMinLengthHint && searchVal.length > 0 && searchVal.length < 3} | 1078 | + <!-- svelte-ignore a11y_no_static_element_interactions --> |
| 991 | - <div class="results-message"> | 1079 | + <div |
| 992 | - <div class="results-hint">Escribe al menos 3 letras para buscar</div> | 1080 | + class="results-content results-content-filled" |
| 993 | - </div> | 1081 | + class:results-content-visible={allHits.length > 0 || (results && results.hits?.length === 0)} |
| 994 | - {:else if error && !results} | 1082 | + on:mousedown|preventDefault |
| 995 | - <div class="results-message"> | 1083 | + > |
| 996 | - <div class="results-error">{error}</div> | 1084 | + {#if results && results.hits?.length === 0 && !isLoading} |
| 997 | - </div> | 1085 | + <div class="results-message-inline"> |
| 998 | - {:else if isLoading && !results} | 1086 | + <span class="results-empty">Sin resultados para "{searchVal}"</span> |
| 999 | - <div class="results-message"> | ||
| 1000 | - <div class="results-loading">Buscando...</div> | ||
| 1001 | - </div> | ||
| 1002 | - {:else if results && results.hits?.length === 0} | ||
| 1003 | - <div class="results-message"> | ||
| 1004 | - <div class="results-empty">Sin resultados para "{searchVal}"</div> | ||
| 1005 | </div> | 1087 | </div> |
| 1006 | {/if} | 1088 | {/if} |
| 1007 | 1089 | ||
| 1008 | - <!-- Título contextual de búsqueda --> | 1090 | + <!-- Título contextual --> |
| 1009 | {#if allHits.length > 0 && searchContextText} | 1091 | {#if allHits.length > 0 && searchContextText} |
| 1010 | - <div class="search-context-title">{searchContextText}</div> | 1092 | + <div class="search-context-row"> |
| 1093 | + <span class="search-context-title">{searchContextText}</span> | ||
| 1094 | + {#if searchMode === 'clasificadores' && selectedClassifiers.length > 0} | ||
| 1095 | + <button class="search-explain-link" on:click|stopPropagation={openExplain}> | ||
| 1096 | + ¿Qué busco? | ||
| 1097 | + </button> | ||
| 1011 | {/if} | 1098 | {/if} |
| 1012 | - | 1099 | + </div> |
| 1013 | - <!-- Contenedor de resultados (siempre montado para evitar flicker) --> | 1100 | + {/if} |
| 1014 | - <div | 1101 | + {#if stats && !isUbigeoSearchActive} |
| 1015 | - class="results-content results-content-filled" | ||
| 1016 | - class:results-content-visible={allHits.length > 0} | ||
| 1017 | - > | ||
| 1018 | - {#if stats} | ||
| 1019 | <div class="results-summary"> | 1102 | <div class="results-summary"> |
| 1020 | <div class="summary-row"> | 1103 | <div class="summary-row"> |
| 1021 | <div class="summary-stats"> | 1104 | <div class="summary-stats"> |
| ... | @@ -1150,6 +1233,24 @@ | ... | @@ -1150,6 +1233,24 @@ |
| 1150 | </div> | 1233 | </div> |
| 1151 | </div> | 1234 | </div> |
| 1152 | </a> | 1235 | </a> |
| 1236 | + {:else if hitClass === 'ubigeo'} | ||
| 1237 | + <a href="/ubicacion/{meta.municipio_ubigeo}" class="result-card result-card-grouped" | ||
| 1238 | + class:result-card-selected={selectedResultIndex === globalIdx} | ||
| 1239 | + data-result-index={globalIdx}> | ||
| 1240 | + <div class="result-content"> | ||
| 1241 | + <div class="result-text"> | ||
| 1242 | + {#if hit.highlights?.[0]?.snippet} | ||
| 1243 | + {@html hit.highlights[0].snippet} | ||
| 1244 | + {:else} | ||
| 1245 | + {hit.document.texto} | ||
| 1246 | + {/if} | ||
| 1247 | + </div> | ||
| 1248 | + <div class="result-meta"> | ||
| 1249 | + <span class="result-subarea">{meta.desc_provincia}</span> | ||
| 1250 | + <span class="result-monto">{formatMonto(hit.document.devengado)}</span> | ||
| 1251 | + </div> | ||
| 1252 | + </div> | ||
| 1253 | + </a> | ||
| 1153 | {/if} | 1254 | {/if} |
| 1154 | {/each} | 1255 | {/each} |
| 1155 | </div> | 1256 | </div> |
| ... | @@ -1165,10 +1266,14 @@ | ... | @@ -1165,10 +1266,14 @@ |
| 1165 | {@const parentEntity = isDA ? extractAfterHyphen(meta.entidad_desc_entidad) : null} | 1266 | {@const parentEntity = isDA ? extractAfterHyphen(meta.entidad_desc_entidad) : null} |
| 1166 | {@const isObjetoClass = hit.document.class_ === 'objeto'} | 1267 | {@const isObjetoClass = hit.document.class_ === 'objeto'} |
| 1167 | {@const isFinfunClass = hit.document.class_ === 'finfun'} | 1268 | {@const isFinfunClass = hit.document.class_ === 'finfun'} |
| 1269 | + {@const isUbigeoClass = hit.document.class_ === 'ubigeo'} | ||
| 1168 | {@const isClassResult = hit.document.is_class === true} | 1270 | {@const isClassResult = hit.document.is_class === true} |
| 1271 | + {@const hitType = hit.document.class_} | ||
| 1169 | <a | 1272 | <a |
| 1170 | href={isEntidadClass | 1273 | href={isEntidadClass |
| 1171 | ? `/entidad/${meta.da ? `${meta.entidad}.${meta.da}` : meta.entidad}` | 1274 | ? `/entidad/${meta.da ? `${meta.entidad}.${meta.da}` : meta.entidad}` |
| 1275 | + : isUbigeoClass | ||
| 1276 | + ? `/ubicacion/${meta.municipio_ubigeo}` | ||
| 1172 | : isObjetoClass | 1277 | : isObjetoClass |
| 1173 | ? `/objeto/${getObjetoCodigo(meta)}` | 1278 | ? `/objeto/${getObjetoCodigo(meta)}` |
| 1174 | : isFinfunClass | 1279 | : isFinfunClass |
| ... | @@ -1180,6 +1285,9 @@ | ... | @@ -1180,6 +1285,9 @@ |
| 1180 | > | 1285 | > |
| 1181 | <div class="result-content"> | 1286 | <div class="result-content"> |
| 1182 | <div class="result-text"> | 1287 | <div class="result-text"> |
| 1288 | + {#if isMixedClassSearch && hitType} | ||
| 1289 | + <span class="result-type-badge">{CLASS_LABELS[hitType] || hitType}</span> | ||
| 1290 | + {/if} | ||
| 1183 | {#if hit.highlights?.[0]?.snippet} | 1291 | {#if hit.highlights?.[0]?.snippet} |
| 1184 | {@html hit.highlights[0].snippet} | 1292 | {@html hit.highlights[0].snippet} |
| 1185 | {:else} | 1293 | {:else} |
| ... | @@ -1190,8 +1298,12 @@ | ... | @@ -1190,8 +1298,12 @@ |
| 1190 | {#if isEntidadClass} | 1298 | {#if isEntidadClass} |
| 1191 | {#if isDA}<span class="result-parent">Dependiente de {parentEntity}</span>{/if} | 1299 | {#if isDA}<span class="result-parent">Dependiente de {parentEntity}</span>{/if} |
| 1192 | {#if subarea}<span class="result-subarea">{subarea}</span>{/if} | 1300 | {#if subarea}<span class="result-subarea">{subarea}</span>{/if} |
| 1301 | + {:else if isUbigeoClass} | ||
| 1302 | + <span class="result-subarea">Departamento de {meta.desc_departamento}</span> | ||
| 1303 | + <span class="result-sep">·</span> | ||
| 1304 | + <span class="result-subarea">Provincia {meta.desc_provincia}</span> | ||
| 1193 | {:else if isClassResult} | 1305 | {:else if isClassResult} |
| 1194 | - <span class="result-class-type">{hit.document.class_}</span> | 1306 | + <span class="result-class-type">{CLASS_LABELS[hitType] || hitType}</span> |
| 1195 | {:else} | 1307 | {:else} |
| 1196 | <span class="result-year">{formatYears(meta.gestion)}</span> | 1308 | <span class="result-year">{formatYears(meta.gestion)}</span> |
| 1197 | <span class="result-sep">·</span> | 1309 | <span class="result-sep">·</span> |
| ... | @@ -1616,6 +1728,87 @@ | ... | @@ -1616,6 +1728,87 @@ |
| 1616 | </footer> | 1728 | </footer> |
| 1617 | </div> | 1729 | </div> |
| 1618 | 1730 | ||
| 1731 | +<!-- Modal explicativo contextual --> | ||
| 1732 | +{#if showExplainModal} | ||
| 1733 | + {@const activeContents = selectedClassifiers.map(id => EXPLAIN_CONTENT[id]).filter(Boolean)} | ||
| 1734 | + {@const isSingle = activeContents.length === 1} | ||
| 1735 | + <div class="explain-backdrop" on:click={() => { showExplainModal = false; }} role="dialog" aria-modal="true"> | ||
| 1736 | + <div class="explain-modal" on:click|stopPropagation> | ||
| 1737 | + <div class="explain-header"> | ||
| 1738 | + <h2 class="explain-title">{isSingle ? activeContents[0].titulo : '¿Qué busco?'}</h2> | ||
| 1739 | + <button class="explain-close" on:click={() => { showExplainModal = false; }}> | ||
| 1740 | + <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | ||
| 1741 | + <line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/> | ||
| 1742 | + </svg> | ||
| 1743 | + </button> | ||
| 1744 | + </div> | ||
| 1745 | + | ||
| 1746 | + {#if isSingle} | ||
| 1747 | + {@const content = activeContents[0]} | ||
| 1748 | + <span class="explain-subtitle">{content.subtitulo}</span> | ||
| 1749 | + <p class="explain-intro">{content.intro}</p> | ||
| 1750 | + <div class="explain-list"> | ||
| 1751 | + {#each content.bloques as bloque} | ||
| 1752 | + <div class="explain-item"> | ||
| 1753 | + <span class="explain-item-label">{bloque.titulo}</span> | ||
| 1754 | + <p class="explain-item-desc">{bloque.texto}</p> | ||
| 1755 | + </div> | ||
| 1756 | + {/each} | ||
| 1757 | + </div> | ||
| 1758 | + {#if content.link} | ||
| 1759 | + <a href={content.link.href} class="explain-cta" on:click={() => { showExplainModal = false; }}> | ||
| 1760 | + {content.link.texto} | ||
| 1761 | + <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | ||
| 1762 | + <path d="M5 12h14M12 5l7 7-7 7"/> | ||
| 1763 | + </svg> | ||
| 1764 | + </a> | ||
| 1765 | + {/if} | ||
| 1766 | + {:else if activeContents.length > 1} | ||
| 1767 | + {#each activeContents as content, idx} | ||
| 1768 | + <div class="explain-section" class:explain-section-border={idx > 0}> | ||
| 1769 | + <div class="explain-section-header"> | ||
| 1770 | + <h3 class="explain-section-title">{content.titulo}</h3> | ||
| 1771 | + <span class="explain-subtitle">{content.subtitulo}</span> | ||
| 1772 | + </div> | ||
| 1773 | + <p class="explain-intro">{content.intro}</p> | ||
| 1774 | + <div class="explain-list"> | ||
| 1775 | + {#each content.bloques as bloque} | ||
| 1776 | + <div class="explain-item"> | ||
| 1777 | + <span class="explain-item-label">{bloque.titulo}</span> | ||
| 1778 | + <p class="explain-item-desc">{bloque.texto}</p> | ||
| 1779 | + </div> | ||
| 1780 | + {/each} | ||
| 1781 | + </div> | ||
| 1782 | + {#if content.link} | ||
| 1783 | + <a href={content.link.href} class="explain-cta" on:click={() => { showExplainModal = false; }}> | ||
| 1784 | + {content.link.texto} | ||
| 1785 | + <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | ||
| 1786 | + <path d="M5 12h14M12 5l7 7-7 7"/> | ||
| 1787 | + </svg> | ||
| 1788 | + </a> | ||
| 1789 | + {/if} | ||
| 1790 | + </div> | ||
| 1791 | + {/each} | ||
| 1792 | + {:else} | ||
| 1793 | + <p class="explain-intro"> | ||
| 1794 | + Los clasificadores son las categorías estandarizadas que usa el Estado boliviano para organizar su presupuesto. Cada uno responde a una pregunta diferente sobre el gasto o ingreso público. | ||
| 1795 | + </p> | ||
| 1796 | + <div class="explain-list"> | ||
| 1797 | + {#each CLASIFICADORES as clf} | ||
| 1798 | + <div class="explain-item"> | ||
| 1799 | + <div class="explain-item-header"> | ||
| 1800 | + <span class="explain-item-label">{clf.label}</span> | ||
| 1801 | + <span class="explain-item-tecnico">{clf.tecnico}</span> | ||
| 1802 | + </div> | ||
| 1803 | + <p class="explain-item-desc">{clf.desc}</p> | ||
| 1804 | + </div> | ||
| 1805 | + {/each} | ||
| 1806 | + </div> | ||
| 1807 | + {/if} | ||
| 1808 | + </div> | ||
| 1809 | + </div> | ||
| 1810 | +{/if} | ||
| 1811 | + | ||
| 1619 | <style> | 1812 | <style> |
| 1620 | @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500&display=swap'); | 1813 | @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500&display=swap'); |
| 1621 | :global(*){margin:0;padding:0;box-sizing:border-box} | 1814 | :global(*){margin:0;padding:0;box-sizing:border-box} |
| ... | @@ -1692,10 +1885,9 @@ | ... | @@ -1692,10 +1885,9 @@ |
| 1692 | 1885 | ||
| 1693 | /* Search */ | 1886 | /* Search */ |
| 1694 | .search-wrap{max-width:960px;width:100%;margin:0 auto;position:relative;z-index:9999;transition:all 0.3s cubic-bezier(0.16,1,0.3,1)} | 1887 | .search-wrap{max-width:960px;width:100%;margin:0 auto;position:relative;z-index:9999;transition:all 0.3s cubic-bezier(0.16,1,0.3,1)} |
| 1695 | - .search-wrap-with-results{background:rgba(255,255,255,0.04);border-radius:20px;border:1px solid rgba(255,255,255,0.08);padding:6px} | 1888 | + .search-bar{display:flex;align-items:center;background:rgba(255,255,255,0.05);border-radius:16px;padding:8px 14px 8px 14px;transition:all 0.6s cubic-bezier(0.16,1,0.3,1);box-shadow:0 12px 40px rgba(0,0,0,0.4),0 4px 12px rgba(0,0,0,0.2);position:relative} |
| 1696 | - .search-bar{display:flex;align-items:center;background:rgba(255,255,255,0.05);border-radius:16px;padding:8px 14px 8px 14px;transition:all 0.3s cubic-bezier(0.16,1,0.3,1);box-shadow:0 12px 40px rgba(0,0,0,0.4),0 4px 12px rgba(0,0,0,0.2);position:relative} | 1889 | + .search-focused{background:rgba(255,255,255,0.1);backdrop-filter:blur(24px);-webkit-backdrop-filter:blur(24px)} |
| 1697 | - .search-focused{background:rgba(255,255,255,0.12)} | 1890 | + .has-results{border-radius:16px 16px 0 0;box-shadow:0 2px 8px rgba(0,0,0,0.1)} |
| 1698 | - .search-has-results{border-radius:16px 16px 0 0} | ||
| 1699 | .search-meta{display:flex;align-items:center;gap:8px;flex-shrink:0;padding:0 4px} | 1891 | .search-meta{display:flex;align-items:center;gap:8px;flex-shrink:0;padding:0 4px} |
| 1700 | .search-meta-item{display:flex;align-items:center;gap:7px;font-family:var(--sans);font-size:13px;font-weight:500;padding:10px 14px;border-radius:10px;text-decoration:none;transition:all 0.2s} | 1892 | .search-meta-item{display:flex;align-items:center;gap:7px;font-family:var(--sans);font-size:13px;font-weight:500;padding:10px 14px;border-radius:10px;text-decoration:none;transition:all 0.2s} |
| 1701 | .search-meta-item svg{display:block;flex-shrink:0} | 1893 | .search-meta-item svg{display:block;flex-shrink:0} |
| ... | @@ -1760,6 +1952,10 @@ | ... | @@ -1760,6 +1952,10 @@ |
| 1760 | 1952 | ||
| 1761 | /* Search clear button */ | 1953 | /* Search clear button */ |
| 1762 | .search-clear{background:none;border:none;cursor:pointer;color:#6B6860;padding:4px;display:flex;align-items:center;justify-content:center;transition:color 0.2s;flex-shrink:0} | 1954 | .search-clear{background:none;border:none;cursor:pointer;color:#6B6860;padding:4px;display:flex;align-items:center;justify-content:center;transition:color 0.2s;flex-shrink:0} |
| 1955 | + .search-esc{font-family:inherit;font-size:0.625rem;padding:3px 6px;background:rgba(255,255,255,0.08);border:1px solid rgba(255,255,255,0.12);border-radius:4px;color:#6B6860;cursor:pointer;flex-shrink:0;transition:all 0.15s} | ||
| 1956 | + .search-esc:hover{background:rgba(255,255,255,0.14);color:#B8B5AD} | ||
| 1957 | + :global(html:not(.dark)) .search-esc{background:rgba(0,0,0,0.05);border-color:rgba(0,0,0,0.1);color:#888} | ||
| 1958 | + :global(html:not(.dark)) .search-esc:hover{background:rgba(0,0,0,0.1);color:#555} | ||
| 1763 | .search-clear:hover{color:#F5F0E8} | 1959 | .search-clear:hover{color:#F5F0E8} |
| 1764 | 1960 | ||
| 1765 | /* Search Results Dropdown */ | 1961 | /* Search Results Dropdown */ |
| ... | @@ -1785,8 +1981,8 @@ | ... | @@ -1785,8 +1981,8 @@ |
| 1785 | .pill:hover::before{opacity:1} | 1981 | .pill:hover::before{opacity:1} |
| 1786 | .pill:hover{transform:translateY(-1px)} | 1982 | .pill:hover{transform:translateY(-1px)} |
| 1787 | 1983 | ||
| 1788 | - .scroll-hint{position:absolute;bottom:28px;left:0;right:0;margin:0 auto;width:fit-content;display:flex;flex-direction:column;align-items:center;gap:10px;animation:scrollHint 2.5s ease-in-out infinite;z-index:100;transition:opacity 0.3s;color:#B8B5AD} | 1984 | + .scroll-hint{position:absolute;bottom:28px;left:0;right:0;margin:0 auto;width:fit-content;display:flex;flex-direction:column;align-items:center;gap:10px;animation:scrollHint 2.5s ease-in-out infinite;z-index:10;transition:opacity 0.3s;color:#B8B5AD} |
| 1789 | - .scroll-hint-hidden{opacity:0;pointer-events:none} | 1985 | + .scroll-hint-hidden{opacity:0;pointer-events:none;visibility:hidden} |
| 1790 | .scroll-hint span{font-family:var(--mono);font-size:10px;letter-spacing:0.2em;color:#B8B5AD;opacity:0.7} | 1986 | .scroll-hint span{font-family:var(--mono);font-size:10px;letter-spacing:0.2em;color:#B8B5AD;opacity:0.7} |
| 1791 | .scroll-icon-mouse{display:block} | 1987 | .scroll-icon-mouse{display:block} |
| 1792 | .scroll-icon-touch{display:none} | 1988 | .scroll-icon-touch{display:none} |
| ... | @@ -1852,7 +2048,7 @@ | ... | @@ -1852,7 +2048,7 @@ |
| 1852 | .radio-dot-active::after{opacity:1} | 2048 | .radio-dot-active::after{opacity:1} |
| 1853 | 2049 | ||
| 1854 | /* Results Area */ | 2050 | /* Results Area */ |
| 1855 | - .results-area{width:100%;position:relative;min-height:60px;text-align:center} | 2051 | + .results-area{width:100%;position:relative;text-align:center;margin-top:0;padding-top:0} |
| 1856 | 2052 | ||
| 1857 | /* Daily Card */ | 2053 | /* Daily Card */ |
| 1858 | .daily-card{display:flex;align-items:center;gap:12px;padding:14px 18px;background:rgba(232,168,76,0.04);border:1px solid rgba(232,168,76,0.15);border-radius:12px;text-decoration:none;transition:all 0.3s cubic-bezier(0.16,1,0.3,1)} | 2054 | .daily-card{display:flex;align-items:center;gap:12px;padding:14px 18px;background:rgba(232,168,76,0.04);border:1px solid rgba(232,168,76,0.15);border-radius:12px;text-decoration:none;transition:all 0.3s cubic-bezier(0.16,1,0.3,1)} |
| ... | @@ -1867,17 +2063,47 @@ | ... | @@ -1867,17 +2063,47 @@ |
| 1867 | .daily-card:hover .daily-card-arrow{opacity:1;transform:translateX(4px)} | 2063 | .daily-card:hover .daily-card-arrow{opacity:1;transform:translateX(4px)} |
| 1868 | 2064 | ||
| 1869 | /* Results Messages */ | 2065 | /* Results Messages */ |
| 1870 | - .results-message{text-align:center;padding:20px} | 2066 | + .results-message-inline{text-align:center;padding:20px} |
| 1871 | .results-hint,.results-loading{font-family:var(--sans);font-size:14px;color:#6B6860} | 2067 | .results-hint,.results-loading{font-family:var(--sans);font-size:14px;color:#6B6860} |
| 1872 | .results-error{font-family:var(--sans);font-size:14px;color:#C44B3F} | 2068 | .results-error{font-family:var(--sans);font-size:14px;color:#C44B3F} |
| 1873 | .results-empty{font-family:var(--sans);font-size:14px;color:#8B8880} | 2069 | .results-empty{font-family:var(--sans);font-size:14px;color:#8B8880} |
| 1874 | 2070 | ||
| 1875 | /* Search Context Title */ | 2071 | /* Search Context Title */ |
| 1876 | - .search-context-title{font-family:var(--sans);font-size:12px;font-weight:500;color:#9B9890;text-transform:uppercase;letter-spacing:0.05em;margin-bottom:8px;padding-left:4px;text-align:left} | 2072 | + .search-context-row{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;padding:0 4px} |
| 2073 | + .search-context-title{font-family:var(--sans);font-size:12px;font-weight:500;color:#9B9890;text-transform:uppercase;letter-spacing:0.05em} | ||
| 2074 | + .search-explain-link{background:none;border:none;border-bottom:1px dotted #9B9890;color:#9B9890;font-size:11px;font-family:var(--sans);cursor:pointer;padding:0;transition:color 0.2s,border-color 0.2s} | ||
| 2075 | + .search-explain-link:hover{color:#C9A751;border-bottom-color:#C9A751} | ||
| 2076 | + | ||
| 2077 | + /* Modal explicativo */ | ||
| 2078 | + .explain-backdrop{position:fixed;inset:0;background:rgba(0,0,0,0.6);z-index:10000;display:flex;align-items:center;justify-content:center;padding:2rem;animation:fadeIn 0.15s ease-out} | ||
| 2079 | + .explain-modal{width:100%;max-width:560px;max-height:80vh;overflow-y:auto;background:rgba(40,40,38,0.85);backdrop-filter:blur(40px) saturate(200%);-webkit-backdrop-filter:blur(40px) saturate(200%);border:1px solid rgba(255,255,255,0.12);border-radius:16px;padding:1.5rem;box-shadow:0 25px 50px rgba(0,0,0,0.5)} | ||
| 2080 | + :global(html:not(.dark)) .explain-modal{background:rgba(255,255,255,0.85);border-color:rgba(0,0,0,0.08);box-shadow:0 25px 50px rgba(0,0,0,0.15)} | ||
| 2081 | + .explain-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:1rem} | ||
| 2082 | + .explain-title{font-size:1.1rem;font-weight:700;color:var(--theme-titulo);margin:0} | ||
| 2083 | + .explain-subtitle{font-size:0.75rem;color:var(--theme-texto);opacity:0.6;margin-top:2px;display:block} | ||
| 2084 | + .explain-close{background:none;border:none;cursor:pointer;color:var(--theme-texto);opacity:0.6;padding:4px;transition:opacity 0.15s} | ||
| 2085 | + .explain-close:hover{opacity:1} | ||
| 2086 | + .explain-intro{font-size:0.8125rem;color:var(--theme-texto);line-height:1.5;margin-bottom:1.25rem;opacity:0.8} | ||
| 2087 | + .explain-list{display:flex;flex-direction:column;gap:0.75rem} | ||
| 2088 | + .explain-item{padding:0.75rem;background:rgba(255,255,255,0.05);border-radius:10px;border:1px solid rgba(255,255,255,0.06)} | ||
| 2089 | + :global(html:not(.dark)) .explain-item{background:rgba(0,0,0,0.03);border-color:rgba(0,0,0,0.06)} | ||
| 2090 | + .explain-item-header{display:flex;align-items:baseline;gap:0.5rem;margin-bottom:0.25rem} | ||
| 2091 | + .explain-item-label{font-size:0.875rem;font-weight:600;color:var(--theme-titulo)} | ||
| 2092 | + .explain-item-tecnico{font-size:0.75rem;color:var(--theme-texto);opacity:0.6} | ||
| 2093 | + .explain-item-desc{font-size:0.75rem;color:var(--theme-texto);opacity:0.75;line-height:1.4;margin:0.25rem 0 0} | ||
| 2094 | + .explain-section{margin-top:0} | ||
| 2095 | + .explain-section-border{margin-top:1.25rem;padding-top:1.25rem;border-top:1px solid rgba(255,255,255,0.08)} | ||
| 2096 | + :global(html:not(.dark)) .explain-section-border{border-top-color:rgba(0,0,0,0.08)} | ||
| 2097 | + .explain-section-header{margin-bottom:0.5rem} | ||
| 2098 | + .explain-section-title{font-size:1rem;font-weight:700;color:var(--theme-titulo);margin:0} | ||
| 2099 | + .explain-cta{display:inline-flex;align-items:center;gap:0.4rem;margin-top:1.25rem;padding:0.6rem 1rem;font-size:0.8125rem;font-weight:600;color:var(--theme-titulo);background:rgba(201,167,81,0.12);border:1px solid rgba(201,167,81,0.25);border-radius:10px;text-decoration:none;transition:all 0.2s} | ||
| 2100 | + .explain-cta:hover{background:rgba(201,167,81,0.2);border-color:rgba(201,167,81,0.4)} | ||
| 1877 | 2101 | ||
| 1878 | /* Results Content */ | 2102 | /* Results Content */ |
| 1879 | - .results-content{max-height:0;overflow:hidden;opacity:0;transition:all 0.4s cubic-bezier(0.16,1,0.3,1)} | 2103 | + .results-content{max-height:0;overflow:hidden;opacity:0;transition:max-height 0.5s cubic-bezier(0.16,1,0.3,1),opacity 0.3s ease;position:absolute;left:0;right:0;z-index:9999;top:-2px} |
| 1880 | - .results-content-visible{max-height:60vh;overflow-y:auto;opacity:1;background:#1C1C1A;border-radius:16px;border:none;padding:14px 10px;text-align:left;position:relative;z-index:10;overscroll-behavior:contain} | 2104 | + .results-content-visible{max-height:calc(100dvh - var(--results-top, 200px) - 24px);overflow-y:auto;opacity:1;background:rgba(28,28,26,0.6);backdrop-filter:blur(24px);-webkit-backdrop-filter:blur(24px);border-radius:12px 12px 16px 16px;border:none;padding:14px 10px;text-align:left;overscroll-behavior:contain;box-shadow:none;margin-top:0;scrollbar-width:none;-ms-overflow-style:none} |
| 2105 | + .results-content-visible::-webkit-scrollbar{display:none} | ||
| 2106 | + :global(html:not(.dark)) .results-content-visible{background:rgba(255,255,255,0.85)} | ||
| 1881 | 2107 | ||
| 1882 | /* Results Summary */ | 2108 | /* Results Summary */ |
| 1883 | .results-summary{background:transparent;border-radius:0;padding:0 0 12px 0;margin-bottom:12px;border-bottom:1px solid rgba(255,255,255,0.08)} | 2109 | .results-summary{background:transparent;border-radius:0;padding:0 0 12px 0;margin-bottom:12px;border-bottom:1px solid rgba(255,255,255,0.08)} |
| ... | @@ -1918,7 +2144,8 @@ | ... | @@ -1918,7 +2144,8 @@ |
| 1918 | .result-card-grouped:last-child{border-bottom:none} | 2144 | .result-card-grouped:last-child{border-bottom:none} |
| 1919 | .result-card-grouped:hover{background:rgba(255,255,255,0.04)} | 2145 | .result-card-grouped:hover{background:rgba(255,255,255,0.04)} |
| 1920 | .result-content{flex:1;min-width:0} | 2146 | .result-content{flex:1;min-width:0} |
| 1921 | - .result-text{font-family:var(--sans);font-size:14px;color:#F5F0E8;line-height:1.4} | 2147 | + .result-text{font-family:var(--sans);font-size:14px;color:#F5F0E8;line-height:1.4;display:flex;align-items:baseline;gap:6px;flex-wrap:wrap} |
| 2148 | + .result-type-badge{font-size:0.6rem;font-weight:600;text-transform:uppercase;letter-spacing:0.04em;padding:2px 6px;border-radius:4px;background:rgba(201,167,81,0.12);color:#C9A751;flex-shrink:0;white-space:nowrap} | ||
| 1922 | .result-text :global(mark){background:rgba(255,213,79,0.9);color:#1C1C1A;border-radius:3px;padding:1px 5px;font-weight:600} | 2149 | .result-text :global(mark){background:rgba(255,213,79,0.9);color:#1C1C1A;border-radius:3px;padding:1px 5px;font-weight:600} |
| 1923 | .result-meta{display:flex;align-items:center;gap:10px;margin-top:6px;flex-wrap:wrap} | 2150 | .result-meta{display:flex;align-items:center;gap:10px;margin-top:6px;flex-wrap:wrap} |
| 1924 | .result-parent,.result-subarea,.result-class-type,.result-objeto-nivel{font-family:var(--mono);font-size:11px;color:#8B8880} | 2151 | .result-parent,.result-subarea,.result-class-type,.result-objeto-nivel{font-family:var(--mono);font-size:11px;color:#8B8880} |
| ... | @@ -2191,9 +2418,9 @@ | ... | @@ -2191,9 +2418,9 @@ |
| 2191 | .pills-list{gap:5px} | 2418 | .pills-list{gap:5px} |
| 2192 | .mode-card-radio{top:14px;right:14px} | 2419 | .mode-card-radio{top:14px;right:14px} |
| 2193 | .radio-dot{width:16px;height:16px} | 2420 | .radio-dot{width:16px;height:16px} |
| 2194 | - .results-area{margin-top:4px;min-height:40px} | 2421 | + .results-area{min-height:40px} |
| 2195 | - .results-content-visible{padding:2px 4px} | 2422 | + .results-content-visible{max-height:calc(100vh - 160px);padding:6px 4px} |
| 2196 | - .search-wrap-with-results{padding:2px} | 2423 | + .search-wrap-with-results{padding:0} |
| 2197 | .daily-card{padding:16px 18px;gap:12px;border-radius:14px;margin-bottom:24px} | 2424 | .daily-card{padding:16px 18px;gap:12px;border-radius:14px;margin-bottom:24px} |
| 2198 | .daily-card-title{font-size:14px} | 2425 | .daily-card-title{font-size:14px} |
| 2199 | .daily-card-text{font-size:12px} | 2426 | .daily-card-text{font-size:12px} |
| ... | @@ -2322,9 +2549,9 @@ | ... | @@ -2322,9 +2549,9 @@ |
| 2322 | .mode-card-radio{top:12px;right:12px} | 2549 | .mode-card-radio{top:12px;right:12px} |
| 2323 | .radio-dot{width:14px;height:14px} | 2550 | .radio-dot{width:14px;height:14px} |
| 2324 | .radio-dot::after{width:6px;height:6px} | 2551 | .radio-dot::after{width:6px;height:6px} |
| 2325 | - .results-area{margin-top:2px;min-height:28px} | 2552 | + .results-area{min-height:28px} |
| 2326 | - .results-content-visible{padding:0 2px} | 2553 | + .results-content-visible{max-height:calc(100vh - 140px);padding:4px 2px} |
| 2327 | - .search-wrap-with-results{padding:2px} | 2554 | + .search-wrap-with-results{padding:0} |
| 2328 | .section-group{margin-top:12px} | 2555 | .section-group{margin-top:12px} |
| 2329 | .section-label{font-size:9px;margin-bottom:8px} | 2556 | .section-label{font-size:9px;margin-bottom:8px} |
| 2330 | .section-label-goto{margin-top:12px} | 2557 | .section-label-goto{margin-top:12px} | ... | ... |
| 1 | // Proxy para evitar CORS | 1 | // Proxy para evitar CORS |
| 2 | -const API_BASE = 'http://34.66.240.236/api/proyecto/search'; | 2 | +const API_BASE = 'http://34.171.18.2/api/proyecto/search'; |
| 3 | 3 | ||
| 4 | export async function GET({ url }) { | 4 | export async function GET({ url }) { |
| 5 | const q = url.searchParams.get('q') || ''; | 5 | const q = url.searchParams.get('q') || ''; |
| 6 | - const is_class = url.searchParams.get('is_class') || 'false'; | 6 | + const is_class = url.searchParams.get('is_class'); // null si no viene |
| 7 | - const mode = url.searchParams.get('mode') || 'gastos'; // 'gastos' | 'ingresos' | 7 | + const mode = url.searchParams.get('mode') || ''; |
| 8 | - const class_ = url.searchParams.get('class_') || ''; // comma-separated: entidad, objeto, finfun, acteco | 8 | + const class_ = url.searchParams.get('class_') || ''; |
| 9 | - const page = url.searchParams.get('page') || '1'; // Paginación | 9 | + const page = url.searchParams.get('page') || '1'; |
| 10 | - const per_page = url.searchParams.get('per_page') || ''; // Resultados por página | 10 | + const per_page = url.searchParams.get('per_page') || ''; |
| 11 | 11 | ||
| 12 | - // Build URL with all parameters | ||
| 13 | const apiUrl = new URL(API_BASE); | 12 | const apiUrl = new URL(API_BASE); |
| 14 | apiUrl.searchParams.set('q', q); | 13 | apiUrl.searchParams.set('q', q); |
| 15 | - apiUrl.searchParams.set('is_class', is_class); | ||
| 16 | apiUrl.searchParams.set('page', page); | 14 | apiUrl.searchParams.set('page', page); |
| 15 | + | ||
| 16 | + // Solo enviar is_class si viene explícitamente | ||
| 17 | + if (is_class !== null) { | ||
| 18 | + apiUrl.searchParams.set('is_class', is_class); | ||
| 19 | + } | ||
| 17 | if (per_page) apiUrl.searchParams.set('per_page', per_page); | 20 | if (per_page) apiUrl.searchParams.set('per_page', per_page); |
| 18 | if (mode) apiUrl.searchParams.set('mode', mode); | 21 | if (mode) apiUrl.searchParams.set('mode', mode); |
| 19 | if (class_) apiUrl.searchParams.set('class_', class_); | 22 | if (class_) apiUrl.searchParams.set('class_', class_); | ... | ... |
| ... | @@ -5,21 +5,29 @@ | ... | @@ -5,21 +5,29 @@ |
| 5 | import { supabase } from '$lib/supabase'; | 5 | import { supabase } from '$lib/supabase'; |
| 6 | 6 | ||
| 7 | let { data } = $props(); | 7 | let { data } = $props(); |
| 8 | - const entidad = data.entidad; | 8 | + let entidad = $derived(data.entidad); |
| 9 | - const isDA = data.isDA; | 9 | + let isDA = $derived(data.isDA); |
| 10 | - const nombreDA = data.nombreDA; | 10 | + let nombreDA = $derived(data.nombreDA); |
| 11 | - const nombreEntidadMadre = data.nombreEntidadMadre; | 11 | + let nombreEntidadMadre = $derived(data.nombreEntidadMadre); |
| 12 | - const codigoEntidadPadre = data.codigoEntidadPadre; | 12 | + let codigoEntidadPadre = $derived(data.codigoEntidadPadre); |
| 13 | 13 | ||
| 14 | // Datos desde el loader (Supabase) | 14 | // Datos desde el loader (Supabase) |
| 15 | let codigoSeleccionado = $derived($page.params.codigo); | 15 | let codigoSeleccionado = $derived($page.params.codigo); |
| 16 | let gestionSeleccionada = $state(data.gestionInicial); | 16 | let gestionSeleccionada = $state(data.gestionInicial); |
| 17 | - let resumenData = $state(data.resumenData); | 17 | + let resumenData = $derived(data.resumenData); |
| 18 | let distribucionesData = $state(data.distribucionesData); | 18 | let distribucionesData = $state(data.distribucionesData); |
| 19 | let cargando = $state(false); | 19 | let cargando = $state(false); |
| 20 | let distCache = $state({ [data.gestionInicial]: data.distribucionesData }); | 20 | let distCache = $state({ [data.gestionInicial]: data.distribucionesData }); |
| 21 | let cargandoDist = $state(false); | 21 | let cargandoDist = $state(false); |
| 22 | 22 | ||
| 23 | + // Resetear al cambiar de entidad | ||
| 24 | + $effect(() => { | ||
| 25 | + const g = data.gestionInicial; | ||
| 26 | + gestionSeleccionada = g; | ||
| 27 | + distribucionesData = data.distribucionesData; | ||
| 28 | + distCache = { [g]: data.distribucionesData }; | ||
| 29 | + }); | ||
| 30 | + | ||
| 23 | async function cargarDistribuciones(gestion) { | 31 | async function cargarDistribuciones(gestion) { |
| 24 | if (distCache[gestion]) { | 32 | if (distCache[gestion]) { |
| 25 | distribucionesData = distCache[gestion]; | 33 | distribucionesData = distCache[gestion]; |
| ... | @@ -148,14 +156,21 @@ | ... | @@ -148,14 +156,21 @@ |
| 148 | return years; | 156 | return years; |
| 149 | }); | 157 | }); |
| 150 | 158 | ||
| 159 | + function parseRanking(rankingStr) { | ||
| 160 | + if (!rankingStr) return null; | ||
| 161 | + const parts = String(rankingStr).split('/'); | ||
| 162 | + if (parts.length !== 2) return null; | ||
| 163 | + return { posicion: parseInt(parts[0]), total: parseInt(parts[1]) }; | ||
| 164 | + } | ||
| 165 | + | ||
| 151 | let rankingGastos = $derived(() => { | 166 | let rankingGastos = $derived(() => { |
| 152 | const row = historiaGastos.find(d => d.gestion === gestionSeleccionada); | 167 | const row = historiaGastos.find(d => d.gestion === gestionSeleccionada); |
| 153 | - return row ? { posicion: row.ranking, total: '~600' } : null; | 168 | + return row ? parseRanking(row.ranking) : null; |
| 154 | }); | 169 | }); |
| 155 | 170 | ||
| 156 | let rankingIngresos = $derived(() => { | 171 | let rankingIngresos = $derived(() => { |
| 157 | const row = historiaIngresos.find(d => d.gestion === gestionSeleccionada); | 172 | const row = historiaIngresos.find(d => d.gestion === gestionSeleccionada); |
| 158 | - return row ? { posicion: row.ranking, total: '~600' } : null; | 173 | + return row ? parseRanking(row.ranking) : null; |
| 159 | }); | 174 | }); |
| 160 | 175 | ||
| 161 | let distFiltradas = $derived(distribucionesData); | 176 | let distFiltradas = $derived(distribucionesData); |
| ... | @@ -672,34 +687,42 @@ | ... | @@ -672,34 +687,42 @@ |
| 672 | {#if rankingGastos()} | 687 | {#if rankingGastos()} |
| 673 | {@const r = rankingGastos()} | 688 | {@const r = rankingGastos()} |
| 674 | {@const total = parseInt(r.total) || 600} | 689 | {@const total = parseInt(r.total) || 600} |
| 675 | - {@const barIdx = Math.max(0, Math.min(79, Math.round((r.posicion / total) * 80)))} | 690 | + {@const barIdx = Math.max(0, Math.min(79, 80 - Math.round((r.posicion / total) * 80)))} |
| 676 | <div class="ranking-card"> | 691 | <div class="ranking-card"> |
| 677 | - <span class="ranking-card-label">Ranking en gasto</span> | 692 | + <div class="ranking-headline"> |
| 693 | + <span class="ranking-pos-big">#{r.posicion}</span> | ||
| 694 | + <span class="ranking-pos-context">de <span class="ranking-total-num">{total}</span> instituciones · {r.posicion <= total / 2 ? 'entre las que más gastan' : 'entre las que menos gastan'}</span> | ||
| 695 | + </div> | ||
| 678 | <div class="ranking-bars"> | 696 | <div class="ranking-bars"> |
| 679 | {#each Array(80) as _, i} | 697 | {#each Array(80) as _, i} |
| 680 | <div class="ranking-bar-tick"></div> | 698 | <div class="ranking-bar-tick"></div> |
| 681 | {/each} | 699 | {/each} |
| 682 | <div class="ranking-marker" style="left: {barIdx * 1.25}%"></div> | 700 | <div class="ranking-marker" style="left: {barIdx * 1.25}%"></div> |
| 683 | </div> | 701 | </div> |
| 684 | - <div class="ranking-caption"> | 702 | + <div class="ranking-extremos"> |
| 685 | - <span class="ranking-pos">#{r.posicion}</span> de {total} entidades | 703 | + <span class="ranking-extremo">Menos gasto</span> |
| 704 | + <span class="ranking-extremo">Más gasto</span> | ||
| 686 | </div> | 705 | </div> |
| 687 | </div> | 706 | </div> |
| 688 | {/if} | 707 | {/if} |
| 689 | {#if tieneIngresos && rankingIngresos()} | 708 | {#if tieneIngresos && rankingIngresos()} |
| 690 | {@const r = rankingIngresos()} | 709 | {@const r = rankingIngresos()} |
| 691 | {@const total = parseInt(r.total) || 600} | 710 | {@const total = parseInt(r.total) || 600} |
| 692 | - {@const barIdx = Math.max(0, Math.min(79, Math.round((r.posicion / total) * 80)))} | 711 | + {@const barIdx = Math.max(0, Math.min(79, 80 - Math.round((r.posicion / total) * 80)))} |
| 693 | <div class="ranking-card"> | 712 | <div class="ranking-card"> |
| 694 | - <span class="ranking-card-label">Ranking en ingresos</span> | 713 | + <div class="ranking-headline"> |
| 714 | + <span class="ranking-pos-big">#{r.posicion}</span> | ||
| 715 | + <span class="ranking-pos-context">de <span class="ranking-total-num">{total}</span> instituciones · {r.posicion <= total / 2 ? 'entre las que más ingresan' : 'entre las que menos ingresan'}</span> | ||
| 716 | + </div> | ||
| 695 | <div class="ranking-bars"> | 717 | <div class="ranking-bars"> |
| 696 | {#each Array(80) as _, i} | 718 | {#each Array(80) as _, i} |
| 697 | <div class="ranking-bar-tick"></div> | 719 | <div class="ranking-bar-tick"></div> |
| 698 | {/each} | 720 | {/each} |
| 699 | <div class="ranking-marker" style="left: {barIdx * 1.25}%"></div> | 721 | <div class="ranking-marker" style="left: {barIdx * 1.25}%"></div> |
| 700 | </div> | 722 | </div> |
| 701 | - <div class="ranking-caption"> | 723 | + <div class="ranking-extremos"> |
| 702 | - <span class="ranking-pos">#{r.posicion}</span> de {total} entidades | 724 | + <span class="ranking-extremo">Menos ingresos</span> |
| 725 | + <span class="ranking-extremo">Más ingresos</span> | ||
| 703 | </div> | 726 | </div> |
| 704 | </div> | 727 | </div> |
| 705 | {/if} | 728 | {/if} |
| ... | @@ -712,13 +735,20 @@ | ... | @@ -712,13 +735,20 @@ |
| 712 | <div class="clasificadores-grid"> | 735 | <div class="clasificadores-grid"> |
| 713 | {#each clasificadoresGasto as { key, label, data }} | 736 | {#each clasificadoresGasto as { key, label, data }} |
| 714 | {@const displayItem = getDisplayItem(key, data)} | 737 | {@const displayItem = getDisplayItem(key, data)} |
| 738 | + {@const totalClasif = getTotal(data)} | ||
| 739 | + {@const pct = totalClasif > 0 && displayItem ? Math.round((displayItem.monto / totalClasif) * 100) : 0} | ||
| 715 | <div class="clasificador-card"> | 740 | <div class="clasificador-card"> |
| 716 | <div class="clasificador-header"> | 741 | <div class="clasificador-header"> |
| 717 | <div class="clasificador-info"> | 742 | <div class="clasificador-info"> |
| 718 | - <span class="clasificador-monto">{formatearMonto(displayItem?.monto || 0)} de Bolivianos</span> | 743 | + <div class="clasificador-nombres"> |
| 719 | <span class="clasificador-padre">{displayItem?.padre || ''}</span> | 744 | <span class="clasificador-padre">{displayItem?.padre || ''}</span> |
| 720 | <span class="clasificador-nombre">{displayItem?.nombre || ''}</span> | 745 | <span class="clasificador-nombre">{displayItem?.nombre || ''}</span> |
| 721 | </div> | 746 | </div> |
| 747 | + <div class="clasificador-valores"> | ||
| 748 | + <span class="clasificador-monto">{formatearMonto(displayItem?.monto || 0)} de Bolivianos</span> | ||
| 749 | + <span class="clasificador-pct">{pct}%</span> | ||
| 750 | + </div> | ||
| 751 | + </div> | ||
| 722 | <span class="clasificador-label">{label}</span> | 752 | <span class="clasificador-label">{label}</span> |
| 723 | </div> | 753 | </div> |
| 724 | <div class="barra-container" bind:this={barContainers[key]}></div> | 754 | <div class="barra-container" bind:this={barContainers[key]}></div> |
| ... | @@ -735,13 +765,20 @@ | ... | @@ -735,13 +765,20 @@ |
| 735 | <div class="clasificadores-grid"> | 765 | <div class="clasificadores-grid"> |
| 736 | {#each clasificadoresIngreso as { key, label, data }} | 766 | {#each clasificadoresIngreso as { key, label, data }} |
| 737 | {@const displayItem = getDisplayItem(key, data)} | 767 | {@const displayItem = getDisplayItem(key, data)} |
| 768 | + {@const totalClasif = getTotal(data)} | ||
| 769 | + {@const pct = totalClasif > 0 && displayItem ? Math.round((displayItem.monto / totalClasif) * 100) : 0} | ||
| 738 | <div class="clasificador-card"> | 770 | <div class="clasificador-card"> |
| 739 | <div class="clasificador-header"> | 771 | <div class="clasificador-header"> |
| 740 | <div class="clasificador-info"> | 772 | <div class="clasificador-info"> |
| 741 | - <span class="clasificador-monto">{formatearMonto(displayItem?.monto || 0)} de Bolivianos</span> | 773 | + <div class="clasificador-nombres"> |
| 742 | <span class="clasificador-padre">{displayItem?.padre || ''}</span> | 774 | <span class="clasificador-padre">{displayItem?.padre || ''}</span> |
| 743 | <span class="clasificador-nombre">{displayItem?.nombre || ''}</span> | 775 | <span class="clasificador-nombre">{displayItem?.nombre || ''}</span> |
| 744 | </div> | 776 | </div> |
| 777 | + <div class="clasificador-valores"> | ||
| 778 | + <span class="clasificador-monto">{formatearMonto(displayItem?.monto || 0)} de Bolivianos</span> | ||
| 779 | + <span class="clasificador-pct">{pct}%</span> | ||
| 780 | + </div> | ||
| 781 | + </div> | ||
| 745 | <span class="clasificador-label">{label}</span> | 782 | <span class="clasificador-label">{label}</span> |
| 746 | </div> | 783 | </div> |
| 747 | <div class="barra-container" bind:this={barContainers[key]}></div> | 784 | <div class="barra-container" bind:this={barContainers[key]}></div> |
| ... | @@ -1039,14 +1076,39 @@ | ... | @@ -1039,14 +1076,39 @@ |
| 1039 | background: #D4A574; | 1076 | background: #D4A574; |
| 1040 | } | 1077 | } |
| 1041 | 1078 | ||
| 1042 | - .ranking-caption { | 1079 | + .ranking-extremos { |
| 1043 | - margin-top: 0.35rem; | 1080 | + display: flex; |
| 1044 | - font-size: 0.7rem; | 1081 | + justify-content: space-between; |
| 1082 | + margin-top: 0.25rem; | ||
| 1083 | + } | ||
| 1084 | + | ||
| 1085 | + .ranking-extremo { | ||
| 1086 | + font-size: 0.6rem; | ||
| 1045 | color: var(--theme-texto); | 1087 | color: var(--theme-texto); |
| 1046 | - opacity: 0.6; | 1088 | + opacity: 0.4; |
| 1089 | + } | ||
| 1090 | + | ||
| 1091 | + .ranking-headline { | ||
| 1092 | + display: flex; | ||
| 1093 | + align-items: baseline; | ||
| 1094 | + gap: 0.5rem; | ||
| 1095 | + margin-bottom: 0.75rem; | ||
| 1047 | } | 1096 | } |
| 1048 | 1097 | ||
| 1049 | - .ranking-pos { | 1098 | + .ranking-pos-big { |
| 1099 | + font-size: 1.5rem; | ||
| 1100 | + font-weight: 700; | ||
| 1101 | + color: var(--theme-titulo); | ||
| 1102 | + line-height: 1; | ||
| 1103 | + } | ||
| 1104 | + | ||
| 1105 | + .ranking-pos-context { | ||
| 1106 | + font-size: 0.8rem; | ||
| 1107 | + color: var(--theme-texto); | ||
| 1108 | + opacity: 0.7; | ||
| 1109 | + } | ||
| 1110 | + | ||
| 1111 | + .ranking-total-num { | ||
| 1050 | font-weight: 700; | 1112 | font-weight: 700; |
| 1051 | color: var(--theme-titulo); | 1113 | color: var(--theme-titulo); |
| 1052 | opacity: 1; | 1114 | opacity: 1; |
| ... | @@ -1150,7 +1212,7 @@ | ... | @@ -1150,7 +1212,7 @@ |
| 1150 | 1212 | ||
| 1151 | :global(html:not(.dark)) .gestion-option.active { | 1213 | :global(html:not(.dark)) .gestion-option.active { |
| 1152 | background: rgba(90, 157, 191, 0.1); | 1214 | background: rgba(90, 157, 191, 0.1); |
| 1153 | - color: #5A9DBF; | 1215 | + color: #3D7A9C; |
| 1154 | } | 1216 | } |
| 1155 | 1217 | ||
| 1156 | .seccion-sub { | 1218 | .seccion-sub { |
| ... | @@ -1230,7 +1292,7 @@ | ... | @@ -1230,7 +1292,7 @@ |
| 1230 | 1292 | ||
| 1231 | .historia-monto.ingresos, | 1293 | .historia-monto.ingresos, |
| 1232 | .historia-monto.gastos { | 1294 | .historia-monto.gastos { |
| 1233 | - color: #5A9DBF; | 1295 | + color: #3D7A9C; |
| 1234 | } | 1296 | } |
| 1235 | 1297 | ||
| 1236 | :global(html.dark) .historia-monto.ingresos, | 1298 | :global(html.dark) .historia-monto.ingresos, |
| ... | @@ -1270,20 +1332,22 @@ | ... | @@ -1270,20 +1332,22 @@ |
| 1270 | } | 1332 | } |
| 1271 | 1333 | ||
| 1272 | .area-path { | 1334 | .area-path { |
| 1273 | - fill: rgba(90, 157, 191, 0.12); | 1335 | + fill: rgba(61, 122, 156, 0.4); |
| 1274 | } | 1336 | } |
| 1275 | 1337 | ||
| 1276 | .line-path { | 1338 | .line-path { |
| 1277 | - stroke: #5A9DBF; | 1339 | + stroke: #3D7A9C; |
| 1340 | + opacity: 0.9; | ||
| 1278 | } | 1341 | } |
| 1279 | 1342 | ||
| 1280 | .dot { | 1343 | .dot { |
| 1281 | - fill: #5A9DBF; | 1344 | + fill: #3D7A9C; |
| 1345 | + opacity: 0.9; | ||
| 1282 | transition: r 0.15s; | 1346 | transition: r 0.15s; |
| 1283 | } | 1347 | } |
| 1284 | 1348 | ||
| 1285 | .dot.active { | 1349 | .dot.active { |
| 1286 | - fill: #5A9DBF; | 1350 | + fill: #3D7A9C; |
| 1287 | } | 1351 | } |
| 1288 | 1352 | ||
| 1289 | .hover-line { | 1353 | .hover-line { |
| ... | @@ -1339,12 +1403,25 @@ | ... | @@ -1339,12 +1403,25 @@ |
| 1339 | 1403 | ||
| 1340 | .clasificador-info { | 1404 | .clasificador-info { |
| 1341 | display: flex; | 1405 | display: flex; |
| 1406 | + flex-direction: column; | ||
| 1407 | + gap: 0.15rem; | ||
| 1408 | + min-width: 0; | ||
| 1409 | + overflow: hidden; | ||
| 1410 | + } | ||
| 1411 | + | ||
| 1412 | + .clasificador-nombres { | ||
| 1413 | + display: flex; | ||
| 1342 | align-items: baseline; | 1414 | align-items: baseline; |
| 1343 | gap: 0.4rem; | 1415 | gap: 0.4rem; |
| 1344 | - min-width: 0; | ||
| 1345 | overflow: hidden; | 1416 | overflow: hidden; |
| 1346 | } | 1417 | } |
| 1347 | 1418 | ||
| 1419 | + .clasificador-valores { | ||
| 1420 | + display: flex; | ||
| 1421 | + align-items: baseline; | ||
| 1422 | + gap: 0.4rem; | ||
| 1423 | + } | ||
| 1424 | + | ||
| 1348 | .clasificador-monto { | 1425 | .clasificador-monto { |
| 1349 | font-size: 0.95rem; | 1426 | font-size: 0.95rem; |
| 1350 | font-weight: 600; | 1427 | font-weight: 600; |
| ... | @@ -1353,6 +1430,15 @@ | ... | @@ -1353,6 +1430,15 @@ |
| 1353 | flex-shrink: 0; | 1430 | flex-shrink: 0; |
| 1354 | } | 1431 | } |
| 1355 | 1432 | ||
| 1433 | + .clasificador-pct { | ||
| 1434 | + font-size: 0.8rem; | ||
| 1435 | + font-weight: 600; | ||
| 1436 | + color: var(--theme-texto); | ||
| 1437 | + opacity: 0.5; | ||
| 1438 | + white-space: nowrap; | ||
| 1439 | + flex-shrink: 0; | ||
| 1440 | + } | ||
| 1441 | + | ||
| 1356 | .clasificador-padre { | 1442 | .clasificador-padre { |
| 1357 | font-size: 0.8rem; | 1443 | font-size: 0.8rem; |
| 1358 | font-weight: 400; | 1444 | font-weight: 400; | ... | ... |
src/routes/test/mapa/+page.svelte
0 → 100644
| 1 | +<script> | ||
| 2 | + import { onMount } from 'svelte'; | ||
| 3 | + import * as d3 from 'd3'; | ||
| 4 | + | ||
| 5 | + let mapaData = $state(null); | ||
| 6 | + let boliviaPath = $state(''); | ||
| 7 | + let deptoPath = $state(''); | ||
| 8 | + let muniPath = $state(''); | ||
| 9 | + let error = $state(''); | ||
| 10 | + let debug = $state(''); | ||
| 11 | + | ||
| 12 | + onMount(async () => { | ||
| 13 | + try { | ||
| 14 | + const res = await fetch('/mapa.json'); | ||
| 15 | + mapaData = await res.json(); | ||
| 16 | + | ||
| 17 | + debug = `bolivia: ${mapaData.bolivia.features.length} features, `; | ||
| 18 | + debug += `deptos: ${mapaData.departamentos.features.length}, `; | ||
| 19 | + debug += `munis: ${mapaData.municipios.features.length}`; | ||
| 20 | + | ||
| 21 | + // Quitar CRS y corregir winding order | ||
| 22 | + delete mapaData.bolivia.crs; | ||
| 23 | + delete mapaData.departamentos.crs; | ||
| 24 | + delete mapaData.municipios.crs; | ||
| 25 | + | ||
| 26 | + // Corregir winding order: invertir todos los anillos | ||
| 27 | + function fixWinding(geojson) { | ||
| 28 | + const fix = (coords, isHole) => { | ||
| 29 | + // Para geo coords: exterior debe ser counterclockwise (area < 0 en D3) | ||
| 30 | + // Simplemente invertimos todos los anillos exteriores | ||
| 31 | + coords.forEach((ring, i) => { | ||
| 32 | + if (i === 0) ring.reverse(); // exterior | ||
| 33 | + // holes quedan como están | ||
| 34 | + }); | ||
| 35 | + }; | ||
| 36 | + for (const feat of geojson.features || [geojson]) { | ||
| 37 | + const geom = (feat.geometry || feat); | ||
| 38 | + if (geom.type === 'Polygon') { | ||
| 39 | + fix(geom.coordinates); | ||
| 40 | + } else if (geom.type === 'MultiPolygon') { | ||
| 41 | + geom.coordinates.forEach(poly => fix(poly)); | ||
| 42 | + } | ||
| 43 | + } | ||
| 44 | + } | ||
| 45 | + | ||
| 46 | + fixWinding(mapaData.bolivia); | ||
| 47 | + fixWinding(mapaData.departamentos); | ||
| 48 | + fixWinding(mapaData.municipios); | ||
| 49 | + | ||
| 50 | + const bounds = d3.geoBounds(mapaData.bolivia); | ||
| 51 | + debug += ` | bounds: ${JSON.stringify(bounds)}`; | ||
| 52 | + | ||
| 53 | + const projection = d3.geoMercator().fitSize([380, 480], mapaData.bolivia); | ||
| 54 | + const pathGen = d3.geoPath(projection); | ||
| 55 | + | ||
| 56 | + boliviaPath = pathGen(mapaData.bolivia) || ''; | ||
| 57 | + debug += ` | boliviaPath length: ${boliviaPath.length}`; | ||
| 58 | + | ||
| 59 | + deptoPath = mapaData.departamentos.features.map(f => pathGen(f)).join(' '); | ||
| 60 | + debug += ` | deptoPath length: ${deptoPath.length}`; | ||
| 61 | + | ||
| 62 | + const muni = mapaData.municipios.features[0]; | ||
| 63 | + muniPath = pathGen(muni) || ''; | ||
| 64 | + debug += ` | muni code: ${muni.properties.codigo}`; | ||
| 65 | + | ||
| 66 | + } catch (e) { | ||
| 67 | + error = e.message; | ||
| 68 | + } | ||
| 69 | + }); | ||
| 70 | +</script> | ||
| 71 | + | ||
| 72 | +<div style="padding: 2rem; background: #1C1C1A; color: #F5F0E8; min-height: 100vh;"> | ||
| 73 | + <h1>Test Mapa</h1> | ||
| 74 | + <p style="font-size: 12px; color: #888;">{debug}</p> | ||
| 75 | + {#if error} | ||
| 76 | + <p style="color: red;">{error}</p> | ||
| 77 | + {/if} | ||
| 78 | + | ||
| 79 | + <svg width="400" height="500" viewBox="0 0 400 500" style="border: 1px solid #333;"> | ||
| 80 | + {#if boliviaPath} | ||
| 81 | + <path d={boliviaPath} fill="none" stroke="#555" stroke-width="1" /> | ||
| 82 | + {/if} | ||
| 83 | + {#if deptoPath} | ||
| 84 | + <path d={deptoPath} fill="rgba(255,255,255,0.1)" stroke="#777" stroke-width="0.5" /> | ||
| 85 | + {/if} | ||
| 86 | + {#if muniPath} | ||
| 87 | + <path d={muniPath} fill="#D4A574" stroke="#D4A574" stroke-width="1" /> | ||
| 88 | + {/if} | ||
| 89 | + </svg> | ||
| 90 | +</div> |
src/routes/ubicacion/+page.js
0 → 100644
| 1 | +import { supabase } from '$lib/supabase'; | ||
| 2 | + | ||
| 3 | +export async function load({ fetch }) { | ||
| 4 | + // Cargar resumen y población en paralelo | ||
| 5 | + const [resumenRes, pobRes] = await Promise.all([ | ||
| 6 | + supabase | ||
| 7 | + .schema('ppto') | ||
| 8 | + .from('entidad_resumen') | ||
| 9 | + .select('codigo, desc, gestion, devengado, ranking') | ||
| 10 | + .eq('tipo', 'gastos') | ||
| 11 | + .eq('tipo_codigo', 'municipio_ubigeo') | ||
| 12 | + .eq('gestion', 2025) | ||
| 13 | + .order('devengado', { ascending: false }) | ||
| 14 | + .limit(500), | ||
| 15 | + | ||
| 16 | + fetch('/poblacion.csv').then(r => r.text()) | ||
| 17 | + ]); | ||
| 18 | + | ||
| 19 | + const poblacionMap = {}; | ||
| 20 | + pobRes.split('\n').slice(1).forEach(line => { | ||
| 21 | + const [cod, gestion, pob] = line.split(','); | ||
| 22 | + if (parseInt(gestion) === 2025) { | ||
| 23 | + poblacionMap[cod] = parseInt(pob); | ||
| 24 | + } | ||
| 25 | + }); | ||
| 26 | + | ||
| 27 | + const gestiones = [2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025]; | ||
| 28 | + | ||
| 29 | + return { | ||
| 30 | + resumen: resumenRes.data || [], | ||
| 31 | + poblacionMap, | ||
| 32 | + gestiones, | ||
| 33 | + gestionInicial: 2025 | ||
| 34 | + }; | ||
| 35 | +} |
src/routes/ubicacion/+page.svelte
0 → 100644
| 1 | +<script> | ||
| 2 | + import { onMount } from 'svelte'; | ||
| 3 | + import { get } from 'svelte/store'; | ||
| 4 | + import * as d3 from 'd3'; | ||
| 5 | + import { supabase } from '$lib/supabase'; | ||
| 6 | + import { mapaCache } from '$lib/stores/mapaCache'; | ||
| 7 | + | ||
| 8 | + let { data } = $props(); | ||
| 9 | + let resumen = $state(data.resumen); | ||
| 10 | + let poblacionMap = $state(data.poblacionMap); | ||
| 11 | + let gestionSeleccionada = $state(data.gestionInicial); | ||
| 12 | + let modoPerCapita = $state(false); | ||
| 13 | + let dimensionSeleccionada = $state('total'); // total, objeto, finfun, acteco | ||
| 14 | + let gestionDropdownOpen = $state(false); | ||
| 15 | + let hoveredMunicipio = $state(null); | ||
| 16 | + let drawerOpen = $state(false); | ||
| 17 | + let hoveredQuintil = $state(-1); | ||
| 18 | + let activeQuintiles = $state(new Set([0, 1, 2, 3, 4])); | ||
| 19 | + let sortDrawer = $state('monto'); | ||
| 20 | + let sortDrawerOrder = $state('desc'); | ||
| 21 | + let mostrarDepartamentos = $state(false); | ||
| 22 | + let clasificadorDropdownOpen = $state(false); | ||
| 23 | + let clasificadorSeleccionado = $state('total'); // total, objeto, finfun, acteco | ||
| 24 | + let subPartidaSearch = $state(''); | ||
| 25 | + let subPartidaSeleccionada = $state(null); | ||
| 26 | + | ||
| 27 | + const CLASIFICADORES_MAPA = [ | ||
| 28 | + { id: 'total', label: 'Gasto total', disponible: true }, | ||
| 29 | + { id: 'objeto', label: 'Objetos de gasto', disponible: false }, | ||
| 30 | + { id: 'finfun', label: 'Finalidad y función', disponible: false }, | ||
| 31 | + { id: 'acteco', label: 'Sectores económicos', disponible: false }, | ||
| 32 | + ]; | ||
| 33 | + | ||
| 34 | + function titleCase(str) { | ||
| 35 | + if (!str) return ''; | ||
| 36 | + return str.toLowerCase().replace(/\b\w/g, c => c.toUpperCase()); | ||
| 37 | + } | ||
| 38 | + | ||
| 39 | + // Mapa de municipio a departamento (desde geojson) | ||
| 40 | + let muniDepartamento = $derived.by(() => { | ||
| 41 | + if (!mapaData) return {}; | ||
| 42 | + const map = {}; | ||
| 43 | + mapaData.municipios.features.forEach(f => { | ||
| 44 | + map[String(f.properties.codigo)] = f.properties.DEPARTAMEN; | ||
| 45 | + }); | ||
| 46 | + return map; | ||
| 47 | + }); | ||
| 48 | + | ||
| 49 | + // Slider de rango | ||
| 50 | + let rangoMin = $state(0); | ||
| 51 | + let rangoMax = $state(100); | ||
| 52 | + | ||
| 53 | + // Mapa | ||
| 54 | + let mapaData = $state(null); | ||
| 55 | + let projection = $state(null); | ||
| 56 | + let pathGen = $state(null); | ||
| 57 | + | ||
| 58 | + function fixWinding(geojson) { | ||
| 59 | + for (const feat of geojson.features || [geojson]) { | ||
| 60 | + const geom = feat.geometry || feat; | ||
| 61 | + if (geom.type === 'Polygon') { | ||
| 62 | + geom.coordinates.forEach((ring, i) => { if (i === 0) ring.reverse(); }); | ||
| 63 | + } else if (geom.type === 'MultiPolygon') { | ||
| 64 | + geom.coordinates.forEach(poly => poly.forEach((ring, i) => { if (i === 0) ring.reverse(); })); | ||
| 65 | + } | ||
| 66 | + } | ||
| 67 | + } | ||
| 68 | + | ||
| 69 | + // Crear mapa de datos por código (cacheado, no función) | ||
| 70 | + let datosPorCodigo = $derived.by(() => { | ||
| 71 | + console.time('datosPorCodigo'); | ||
| 72 | + const map = {}; | ||
| 73 | + resumen.forEach(d => { | ||
| 74 | + const val = modoPerCapita && poblacionMap[d.codigo] | ||
| 75 | + ? d.devengado / poblacionMap[d.codigo] | ||
| 76 | + : d.devengado; | ||
| 77 | + map[d.codigo] = { ...d, valor: val }; | ||
| 78 | + }); | ||
| 79 | + console.timeEnd('datosPorCodigo'); | ||
| 80 | + return map; | ||
| 81 | + }); | ||
| 82 | + | ||
| 83 | + // Detectar modo oscuro | ||
| 84 | + let isDark = $state(false); | ||
| 85 | + | ||
| 86 | + // 5 quintiles | ||
| 87 | + const colorsLight = ['#f2ece6', '#e0c8b0', '#c4897d', '#a86858', '#8B4A3A']; | ||
| 88 | + const colorsDark = ['#2E2B27', '#4A4035', '#6B5A48', '#9A8050', '#C9A751']; | ||
| 89 | + | ||
| 90 | + let palette = $derived(isDark ? colorsDark : colorsLight); | ||
| 91 | + | ||
| 92 | + let colorScale = $derived.by(() => { | ||
| 93 | + const valores = Object.values(datosPorCodigo).map(d => d.valor).filter(v => v > 0).sort((a, b) => a - b); | ||
| 94 | + if (valores.length === 0) return d3.scaleQuantile().domain([0, 1]).range(palette); | ||
| 95 | + return d3.scaleQuantile().domain(valores).range(palette); | ||
| 96 | + }); | ||
| 97 | + | ||
| 98 | + // Cortes de quintiles para la leyenda | ||
| 99 | + let quintiles = $derived.by(() => { | ||
| 100 | + if (!colorScale.quantiles) return []; | ||
| 101 | + const cortes = colorScale.quantiles(); | ||
| 102 | + const items = []; | ||
| 103 | + for (let i = 0; i < palette.length; i++) { | ||
| 104 | + const desde = i === 0 ? valoresExtremos.min : cortes[i - 1]; | ||
| 105 | + const hasta = i < cortes.length ? cortes[i] : valoresExtremos.max; | ||
| 106 | + items.push({ color: palette[i], desde, hasta }); | ||
| 107 | + } | ||
| 108 | + return items; | ||
| 109 | + }); | ||
| 110 | + | ||
| 111 | + // Valores extremos para el slider | ||
| 112 | + let valoresExtremos = $derived.by(() => { | ||
| 113 | + const vals = Object.values(datosPorCodigo).map(d => d.valor).filter(v => v > 0); | ||
| 114 | + if (vals.length === 0) return { min: 0, max: 1 }; | ||
| 115 | + return { min: d3.min(vals), max: d3.max(vals) }; | ||
| 116 | + }); | ||
| 117 | + | ||
| 118 | + // Slider con escala logarítmica (más resolución en valores bajos) | ||
| 119 | + function sliderToVal(pct) { | ||
| 120 | + const { min, max } = valoresExtremos; | ||
| 121 | + if (min <= 0 || max <= 0) return min + (max - min) * (pct / 100); | ||
| 122 | + const logMin = Math.log(min); | ||
| 123 | + const logMax = Math.log(max); | ||
| 124 | + return Math.exp(logMin + (logMax - logMin) * (pct / 100)); | ||
| 125 | + } | ||
| 126 | + | ||
| 127 | + let sliderValMin = $derived(sliderToVal(rangoMin)); | ||
| 128 | + let sliderValMax = $derived(sliderToVal(rangoMax)); | ||
| 129 | + | ||
| 130 | + // Quintil de un valor | ||
| 131 | + function getQuintilIdx(valor) { | ||
| 132 | + if (!colorScale.quantiles || quintiles.length === 0) return -1; | ||
| 133 | + const cortes = colorScale.quantiles(); | ||
| 134 | + for (let i = 0; i < cortes.length; i++) { | ||
| 135 | + if (valor < cortes[i]) return i; | ||
| 136 | + } | ||
| 137 | + return palette.length - 1; | ||
| 138 | + } | ||
| 139 | + | ||
| 140 | + // Municipio visible según rango y quintiles activos | ||
| 141 | + function enRango(codigo) { | ||
| 142 | + const datos = datosPorCodigo[codigo]; | ||
| 143 | + if (!datos || datos.valor <= 0) return false; | ||
| 144 | + if (rangoMin > 0 && datos.valor < sliderValMin * 0.999) return false; | ||
| 145 | + if (rangoMax < 100 && datos.valor > sliderValMax * 1.001) return false; | ||
| 146 | + const qi = getQuintilIdx(datos.valor); | ||
| 147 | + return activeQuintiles.has(qi); | ||
| 148 | + } | ||
| 149 | + | ||
| 150 | + // Opacidad del municipio según hover de quintil | ||
| 151 | + function opacidadMuni(codigo) { | ||
| 152 | + if (hoveredQuintil === -1) return 1; | ||
| 153 | + const datos = datosPorCodigo[codigo]; | ||
| 154 | + if (!datos) return 0.15; | ||
| 155 | + const qi = getQuintilIdx(datos.valor); | ||
| 156 | + return qi === hoveredQuintil ? 1 : 0.15; | ||
| 157 | + } | ||
| 158 | + | ||
| 159 | + function toggleQuintil(idx) { | ||
| 160 | + const next = new Set(activeQuintiles); | ||
| 161 | + if (next.has(idx)) { | ||
| 162 | + next.delete(idx); | ||
| 163 | + } else { | ||
| 164 | + next.add(idx); | ||
| 165 | + } | ||
| 166 | + activeQuintiles = next; | ||
| 167 | + } | ||
| 168 | + | ||
| 169 | + // Ranking fijo por monto (posición nunca cambia) | ||
| 170 | + let rankingFijo = $derived.by(() => { | ||
| 171 | + const sorted = Object.values(datosPorCodigo) | ||
| 172 | + .filter(d => d.valor > 0) | ||
| 173 | + .sort((a, b) => b.valor - a.valor); | ||
| 174 | + const map = {}; | ||
| 175 | + sorted.forEach((d, i) => { map[d.codigo] = i + 1; }); | ||
| 176 | + return map; | ||
| 177 | + }); | ||
| 178 | + | ||
| 179 | + // Barras (filtradas por rango y quintiles, ordenables) | ||
| 180 | + let barrasRankeadas = $derived.by(() => { | ||
| 181 | + const dir = sortDrawerOrder === 'desc' ? 1 : -1; | ||
| 182 | + return Object.values(datosPorCodigo) | ||
| 183 | + .filter(d => { | ||
| 184 | + if (d.valor <= 0) return false; | ||
| 185 | + if (rangoMin > 0 && d.valor < sliderValMin * 0.999) return false; | ||
| 186 | + if (rangoMax < 100 && d.valor > sliderValMax * 1.001) return false; | ||
| 187 | + return activeQuintiles.has(getQuintilIdx(d.valor)); | ||
| 188 | + }) | ||
| 189 | + .sort((a, b) => { | ||
| 190 | + if (sortDrawer === 'alfa') return dir * (a.desc || '').localeCompare(b.desc || ''); | ||
| 191 | + if (sortDrawer === 'dept') return dir * (muniDepartamento[a.codigo] || '').localeCompare(muniDepartamento[b.codigo] || ''); | ||
| 192 | + return dir * (b.valor - a.valor); | ||
| 193 | + }); | ||
| 194 | + }); | ||
| 195 | + | ||
| 196 | + function formatearMonto(valor) { | ||
| 197 | + if (valor >= 1e9) return `${(valor / 1e9).toFixed(1)} mil millones`; | ||
| 198 | + if (valor >= 1e6) return `${(valor / 1e6).toFixed(0)} millones`; | ||
| 199 | + if (valor >= 1e3) return `${(valor / 1e3).toFixed(0)} mil`; | ||
| 200 | + return valor.toFixed(0); | ||
| 201 | + } | ||
| 202 | + | ||
| 203 | + function formatearPerCapita(valor) { | ||
| 204 | + if (valor >= 1e6) return `${(valor / 1e6).toFixed(1)} millones`; | ||
| 205 | + if (valor >= 1e4) return `${(valor / 1e3).toFixed(1)} mil`; | ||
| 206 | + return Math.round(valor).toLocaleString('es-BO'); | ||
| 207 | + } | ||
| 208 | + | ||
| 209 | + function fmt(valor) { | ||
| 210 | + return modoPerCapita ? formatearPerCapita(valor) : formatearMonto(valor); | ||
| 211 | + } | ||
| 212 | + | ||
| 213 | + let unidad = $derived(modoPerCapita ? 'Bs por persona' : 'de Bolivianos'); | ||
| 214 | + | ||
| 215 | + // Cache de población por año (parsear CSV una sola vez) | ||
| 216 | + let poblacionCache = $state({}); | ||
| 217 | + let pobCSV = $state(null); | ||
| 218 | + | ||
| 219 | + async function getPoblacionAno(gestion) { | ||
| 220 | + if (poblacionCache[gestion]) return poblacionCache[gestion]; | ||
| 221 | + if (!pobCSV) { | ||
| 222 | + pobCSV = await fetch('/poblacion.csv').then(r => r.text()); | ||
| 223 | + } | ||
| 224 | + const map = {}; | ||
| 225 | + pobCSV.split('\n').slice(1).forEach(line => { | ||
| 226 | + const [cod, g, pob] = line.split(','); | ||
| 227 | + if (parseInt(g) === gestion) { | ||
| 228 | + map[cod] = parseInt(pob); | ||
| 229 | + } | ||
| 230 | + }); | ||
| 231 | + poblacionCache[gestion] = map; | ||
| 232 | + return map; | ||
| 233 | + } | ||
| 234 | + | ||
| 235 | + // Cache de resumen por año | ||
| 236 | + let resumenCache = $state({}); | ||
| 237 | + | ||
| 238 | + async function cargarGestion(gestion) { | ||
| 239 | + console.time('cargarGestion'); | ||
| 240 | + if (!resumenCache[gestion]) { | ||
| 241 | + console.time('supabase'); | ||
| 242 | + const { data: rows } = await supabase | ||
| 243 | + .schema('ppto') | ||
| 244 | + .from('entidad_resumen') | ||
| 245 | + .select('codigo, desc, gestion, devengado, ranking') | ||
| 246 | + .eq('tipo', 'gastos') | ||
| 247 | + .eq('tipo_codigo', 'municipio_ubigeo') | ||
| 248 | + .eq('gestion', gestion) | ||
| 249 | + .order('devengado', { ascending: false }) | ||
| 250 | + .limit(500); | ||
| 251 | + console.timeEnd('supabase'); | ||
| 252 | + resumenCache[gestion] = rows || []; | ||
| 253 | + } | ||
| 254 | + resumen = resumenCache[gestion]; | ||
| 255 | + | ||
| 256 | + console.time('poblacion'); | ||
| 257 | + poblacionMap = await getPoblacionAno(gestion); | ||
| 258 | + console.timeEnd('poblacion'); | ||
| 259 | + console.timeEnd('cargarGestion'); | ||
| 260 | + } | ||
| 261 | + | ||
| 262 | + onMount(async () => { | ||
| 263 | + const cached = get(mapaCache); | ||
| 264 | + if (cached) { | ||
| 265 | + mapaData = cached; | ||
| 266 | + } else { | ||
| 267 | + const res = await fetch('/mapa.json'); | ||
| 268 | + mapaData = await res.json(); | ||
| 269 | + delete mapaData.bolivia.crs; | ||
| 270 | + delete mapaData.departamentos.crs; | ||
| 271 | + delete mapaData.municipios.crs; | ||
| 272 | + fixWinding(mapaData.bolivia); | ||
| 273 | + fixWinding(mapaData.departamentos); | ||
| 274 | + fixWinding(mapaData.municipios); | ||
| 275 | + mapaCache.set(mapaData); | ||
| 276 | + } | ||
| 277 | + | ||
| 278 | + projection = d3.geoMercator().fitSize([500, 600], mapaData.bolivia); | ||
| 279 | + pathGen = d3.geoPath(projection); | ||
| 280 | + | ||
| 281 | + isDark = document.documentElement.classList.contains('dark'); | ||
| 282 | + const themeObserver = new MutationObserver(() => { | ||
| 283 | + isDark = document.documentElement.classList.contains('dark'); | ||
| 284 | + }); | ||
| 285 | + themeObserver.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] }); | ||
| 286 | + | ||
| 287 | + function handleClickOutside(e) { | ||
| 288 | + if (gestionDropdownOpen && !e.target.closest('.gestion-selector')) { | ||
| 289 | + gestionDropdownOpen = false; | ||
| 290 | + } | ||
| 291 | + if (drawerOpen && !e.target.closest('.ranking-drawer') && !e.target.closest('.drawer-toggle')) { | ||
| 292 | + drawerOpen = false; | ||
| 293 | + } | ||
| 294 | + if (clasificadorDropdownOpen && !e.target.closest('.clasificador-selector')) { | ||
| 295 | + clasificadorDropdownOpen = false; | ||
| 296 | + } | ||
| 297 | + } | ||
| 298 | + document.addEventListener('click', handleClickOutside); | ||
| 299 | + return () => document.removeEventListener('click', handleClickOutside); | ||
| 300 | + }); | ||
| 301 | +</script> | ||
| 302 | + | ||
| 303 | +<svelte:head> | ||
| 304 | + <title>Gasto por geografía | Presupuesto Público</title> | ||
| 305 | +</svelte:head> | ||
| 306 | + | ||
| 307 | +<div class="dashboard"> | ||
| 308 | + <div class="mapa-fullscreen"> | ||
| 309 | + <div class="mapa-top-controls"> | ||
| 310 | + <nav class="breadcrumb"> | ||
| 311 | + <button class="back-btn" onclick={() => { history.back(); }}>←</button> | ||
| 312 | + <a href="/">Inicio</a> | ||
| 313 | + <span class="sep">/</span> | ||
| 314 | + <span>Ubicación geográfica</span> | ||
| 315 | + </nav> | ||
| 316 | + <!-- Fila 1: Dropdowns inline --> | ||
| 317 | + <div class="titulo-dropdowns"> | ||
| 318 | + <div class="clasificador-selector"> | ||
| 319 | + <button class="clasificador-btn" onclick={() => { clasificadorDropdownOpen = !clasificadorDropdownOpen; }}> | ||
| 320 | + <svg class="gestion-chevron" class:open={clasificadorDropdownOpen} width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | ||
| 321 | + <path d="M6 9l6 6 6-6"/> | ||
| 322 | + </svg> | ||
| 323 | + <span>{CLASIFICADORES_MAPA.find(c => c.id === clasificadorSeleccionado)?.label || 'Gasto total'}</span> | ||
| 324 | + </button> | ||
| 325 | + {#if clasificadorDropdownOpen} | ||
| 326 | + <div class="clasificador-dropdown"> | ||
| 327 | + {#each CLASIFICADORES_MAPA as cls} | ||
| 328 | + <button | ||
| 329 | + class="clasificador-option" | ||
| 330 | + class:active={clasificadorSeleccionado === cls.id} | ||
| 331 | + class:disabled={!cls.disponible} | ||
| 332 | + onclick={() => { if (cls.disponible) { clasificadorSeleccionado = cls.id; clasificadorDropdownOpen = false; } }} | ||
| 333 | + > | ||
| 334 | + <span class="option-label">{cls.label}</span> | ||
| 335 | + {#if cls.sub} | ||
| 336 | + <span class="option-sub">{cls.sub}</span> | ||
| 337 | + {/if} | ||
| 338 | + </button> | ||
| 339 | + {/each} | ||
| 340 | + </div> | ||
| 341 | + {/if} | ||
| 342 | + </div> | ||
| 343 | + <span class="titulo-sep">por geografía</span> | ||
| 344 | + <div class="gestion-selector"> | ||
| 345 | + <button class="gestion-btn" onclick={() => { gestionDropdownOpen = !gestionDropdownOpen; }}> | ||
| 346 | + <span>{gestionSeleccionada}</span> | ||
| 347 | + <svg class="gestion-chevron" class:open={gestionDropdownOpen} width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | ||
| 348 | + <path d="M6 9l6 6 6-6"/> | ||
| 349 | + </svg> | ||
| 350 | + </button> | ||
| 351 | + {#if gestionDropdownOpen} | ||
| 352 | + <div class="gestion-dropdown"> | ||
| 353 | + {#each data.gestiones as g} | ||
| 354 | + <button | ||
| 355 | + class="gestion-option" | ||
| 356 | + class:active={gestionSeleccionada === g} | ||
| 357 | + onclick={() => { gestionSeleccionada = g; gestionDropdownOpen = false; cargarGestion(g); }} | ||
| 358 | + > | ||
| 359 | + {g} | ||
| 360 | + </button> | ||
| 361 | + {/each} | ||
| 362 | + </div> | ||
| 363 | + {/if} | ||
| 364 | + </div> | ||
| 365 | + </div> | ||
| 366 | + | ||
| 367 | + <!-- Fila 2: Controles --> | ||
| 368 | + <div class="controls-row"> | ||
| 369 | + <div class="percapita-toggle"> | ||
| 370 | + <button class="percapita-btn" class:active={!modoPerCapita} onclick={() => { modoPerCapita = false; }}>Total</button> | ||
| 371 | + <button class="percapita-btn" class:active={modoPerCapita} onclick={() => { modoPerCapita = true; }}>Per cápita</button> | ||
| 372 | + </div> | ||
| 373 | + <button class="dept-toggle" class:active={mostrarDepartamentos} onclick={() => { mostrarDepartamentos = !mostrarDepartamentos; }}> | ||
| 374 | + {mostrarDepartamentos ? '✓ ' : ''}Límites departamentales | ||
| 375 | + </button> | ||
| 376 | + <button class="drawer-toggle" onclick={() => { drawerOpen = !drawerOpen; }}> | ||
| 377 | + <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | ||
| 378 | + <path d="M3 6h18M3 12h18M3 18h18"/> | ||
| 379 | + </svg> | ||
| 380 | + Ranking | ||
| 381 | + </button> | ||
| 382 | + </div> | ||
| 383 | + </div> | ||
| 384 | + | ||
| 385 | + <!-- Mapa --> | ||
| 386 | + <div class="mapa-container"> | ||
| 387 | + <div class="mapa-card"> | ||
| 388 | + {#if mapaData && pathGen} | ||
| 389 | + <svg viewBox="0 0 500 600" class="mapa-svg-full"> | ||
| 390 | + <path d={pathGen(mapaData.bolivia)} class="mapa-pais-fill" /> | ||
| 391 | + <path d={pathGen(mapaData.bolivia)} class="mapa-pais-outline" /> | ||
| 392 | + {#each mapaData.municipios.features as feat} | ||
| 393 | + {@const cod = String(feat.properties.codigo)} | ||
| 394 | + {@const datos = datosPorCodigo[cod]} | ||
| 395 | + {@const visible = enRango(cod)} | ||
| 396 | + {@const color = datos && visible ? colorScale(datos.valor) : 'transparent'} | ||
| 397 | + <a href="/ubicacion/{cod}"> | ||
| 398 | + <path | ||
| 399 | + d={pathGen(feat)} | ||
| 400 | + fill={color} | ||
| 401 | + stroke={hoveredMunicipio === cod ? (isDark ? '#F5F0E8' : '#1d1d1f') : (visible ? (isDark ? 'rgba(255,255,255,0.05)' : 'white') : 'transparent')} | ||
| 402 | + stroke-width={hoveredMunicipio === cod ? 2 : 0.3} | ||
| 403 | + opacity={visible ? opacidadMuni(cod) : 0} | ||
| 404 | + style="cursor: pointer; transition: fill 0.2s, opacity 0.2s;" | ||
| 405 | + onmouseenter={() => { if (visible) hoveredMunicipio = cod; }} | ||
| 406 | + onmouseleave={() => { hoveredMunicipio = null; }} | ||
| 407 | + /> | ||
| 408 | + </a> | ||
| 409 | + {/each} | ||
| 410 | + {#if mostrarDepartamentos} | ||
| 411 | + {#each mapaData.departamentos.features as feat} | ||
| 412 | + <path d={pathGen(feat)} fill="none" stroke={isDark ? '#F5F0E8' : '#1d1d1f'} stroke-width="2.5" style="pointer-events:none;" /> | ||
| 413 | + {/each} | ||
| 414 | + {/if} | ||
| 415 | + </svg> | ||
| 416 | + {/if} | ||
| 417 | + <!-- Leyenda quintiles --> | ||
| 418 | + {#if quintiles.length > 0} | ||
| 419 | + <div class="leyenda-quintiles" onmouseleave={() => { hoveredQuintil = -1; }}> | ||
| 420 | + <span class="leyenda-titulo">Filtrar por rango</span> | ||
| 421 | + {#each quintiles as q, i} | ||
| 422 | + <button | ||
| 423 | + class="quintil-item" | ||
| 424 | + class:dimmed={!activeQuintiles.has(i)} | ||
| 425 | + onmouseenter={() => { hoveredQuintil = i; }} | ||
| 426 | + onclick={() => { toggleQuintil(i); }} | ||
| 427 | + > | ||
| 428 | + <div class="quintil-color" style="background: {q.color}"></div> | ||
| 429 | + <span class="quintil-label">{fmt(q.desde)} – {fmt(q.hasta)} {modoPerCapita ? 'Bs' : ''}</span> | ||
| 430 | + </button> | ||
| 431 | + {/each} | ||
| 432 | + </div> | ||
| 433 | + {/if} | ||
| 434 | + <!-- Tooltip fijo dentro del card --> | ||
| 435 | + <div class="mapa-tooltip-fijo"> | ||
| 436 | + {#if hoveredMunicipio && datosPorCodigo[hoveredMunicipio]} | ||
| 437 | + {@const d = datosPorCodigo[hoveredMunicipio]} | ||
| 438 | + {@const allBarras = barrasRankeadas} | ||
| 439 | + {@const pos = allBarras.findIndex(b => b.codigo === hoveredMunicipio) + 1} | ||
| 440 | + <span class="tooltip-nombre">{titleCase(d.desc)}</span> | ||
| 441 | + <span class="tooltip-dept">{titleCase(muniDepartamento[hoveredMunicipio] || '')}</span> | ||
| 442 | + <span class="tooltip-valor">{fmt(d.valor)} {unidad}</span> | ||
| 443 | + <span class="tooltip-rank">#{pos}/{allBarras.length}</span> | ||
| 444 | + {/if} | ||
| 445 | + </div> | ||
| 446 | + <span class="mapa-muni-count">{barrasRankeadas.length} municipios</span> | ||
| 447 | + </div> | ||
| 448 | + </div> | ||
| 449 | + | ||
| 450 | + <!-- Controles abajo --> | ||
| 451 | + <div class="mapa-bottom-controls"> | ||
| 452 | + <div class="rango-slider"> | ||
| 453 | + <div class="rango-track-wrap"> | ||
| 454 | + <span class="rango-val-float" style="left: {rangoMin}%">{fmt(sliderValMin)} Bs</span> | ||
| 455 | + <span class="rango-val-float" style="left: {rangoMax}%">{fmt(sliderValMax)} Bs</span> | ||
| 456 | + <div class="rango-gradiente"></div> | ||
| 457 | + <div class="rango-mask-left" style="width: {rangoMin}%"></div> | ||
| 458 | + <div class="rango-mask-right" style="width: {100 - rangoMax}%"></div> | ||
| 459 | + <input type="range" min="0" max="100" bind:value={rangoMin} class="rango-input" /> | ||
| 460 | + <input type="range" min="0" max="100" bind:value={rangoMax} class="rango-input" /> | ||
| 461 | + </div> | ||
| 462 | + </div> | ||
| 463 | + </div> | ||
| 464 | + </div> | ||
| 465 | + | ||
| 466 | + <!-- Drawer lateral de ranking --> | ||
| 467 | + <div class="ranking-drawer" class:open={drawerOpen}> | ||
| 468 | + <div class="drawer-header"> | ||
| 469 | + <span class="drawer-titulo">{barrasRankeadas.length} municipios</span> | ||
| 470 | + <button class="drawer-close" onclick={() => { drawerOpen = false; }}> | ||
| 471 | + <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | ||
| 472 | + <line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/> | ||
| 473 | + </svg> | ||
| 474 | + </button> | ||
| 475 | + </div> | ||
| 476 | + <div class="drawer-table-wrap"> | ||
| 477 | + <table class="drawer-table"> | ||
| 478 | + <thead> | ||
| 479 | + <tr> | ||
| 480 | + <th class="th-rank th-sortable" onclick={() => { sortDrawer = 'monto'; sortDrawerOrder = 'desc'; }}> | ||
| 481 | + # | ||
| 482 | + </th> | ||
| 483 | + <th class="th-sortable" onclick={() => { if (sortDrawer === 'alfa') sortDrawerOrder = sortDrawerOrder === 'asc' ? 'desc' : 'asc'; else { sortDrawer = 'alfa'; sortDrawerOrder = 'asc'; } }}> | ||
| 484 | + Municipio {sortDrawer === 'alfa' ? (sortDrawerOrder === 'asc' ? '↑' : '↓') : ''} | ||
| 485 | + </th> | ||
| 486 | + <th class="th-sortable" onclick={() => { if (sortDrawer === 'dept') sortDrawerOrder = sortDrawerOrder === 'asc' ? 'desc' : 'asc'; else { sortDrawer = 'dept'; sortDrawerOrder = 'asc'; } }}> | ||
| 487 | + Depto. {sortDrawer === 'dept' ? (sortDrawerOrder === 'asc' ? '↑' : '↓') : ''} | ||
| 488 | + </th> | ||
| 489 | + <th class="th-sortable th-right" onclick={() => { if (sortDrawer === 'monto') sortDrawerOrder = sortDrawerOrder === 'desc' ? 'asc' : 'desc'; else { sortDrawer = 'monto'; sortDrawerOrder = 'desc'; } }}> | ||
| 490 | + {modoPerCapita ? 'Bs per cápita' : 'Bs'} {sortDrawer === 'monto' ? (sortDrawerOrder === 'desc' ? '↓' : '↑') : ''} | ||
| 491 | + </th> | ||
| 492 | + </tr> | ||
| 493 | + </thead> | ||
| 494 | + <tbody> | ||
| 495 | + {#each barrasRankeadas as item, i} | ||
| 496 | + {@const barColor = colorScale(item.valor)} | ||
| 497 | + <tr | ||
| 498 | + class="tabla-row" | ||
| 499 | + class:hovered={hoveredMunicipio === item.codigo} | ||
| 500 | + onmouseenter={() => { hoveredMunicipio = item.codigo; }} | ||
| 501 | + onmouseleave={() => { hoveredMunicipio = null; }} | ||
| 502 | + > | ||
| 503 | + <td class="td-rank">{rankingFijo[item.codigo] || ''}</td> | ||
| 504 | + <td class="td-nombre"> | ||
| 505 | + <span class="td-color-dot" style="background: {barColor}"></span> | ||
| 506 | + <a href="/ubicacion/{item.codigo}">{titleCase(item.desc)}</a> | ||
| 507 | + </td> | ||
| 508 | + <td class="td-dept">{titleCase(muniDepartamento[item.codigo] || '')}</td> | ||
| 509 | + <td class="td-monto">{fmt(item.valor)} Bs</td> | ||
| 510 | + </tr> | ||
| 511 | + {/each} | ||
| 512 | + </tbody> | ||
| 513 | + </table> | ||
| 514 | + </div> | ||
| 515 | + </div> | ||
| 516 | +</div> | ||
| 517 | + | ||
| 518 | +<style> | ||
| 519 | + .dashboard { | ||
| 520 | + min-height: 100vh; | ||
| 521 | + background: var(--theme-body); | ||
| 522 | + color: var(--theme-texto); | ||
| 523 | + font-family: var(--font-sans); | ||
| 524 | + position: relative; | ||
| 525 | + } | ||
| 526 | + :global(html:not(.dark)) .dashboard { background: #f5f5f7; } | ||
| 527 | + /* Mapa fullscreen */ | ||
| 528 | + .mapa-fullscreen { | ||
| 529 | + height: 100vh; | ||
| 530 | + position: relative; | ||
| 531 | + display: flex; | ||
| 532 | + flex-direction: column; | ||
| 533 | + overflow: hidden; | ||
| 534 | + } | ||
| 535 | + | ||
| 536 | + /* Controles arriba */ | ||
| 537 | + .mapa-top-controls { | ||
| 538 | + display: flex; | ||
| 539 | + flex-direction: column; | ||
| 540 | + gap: 0.5rem; | ||
| 541 | + padding: 3.5rem 1.5rem 0; | ||
| 542 | + max-width: 700px; | ||
| 543 | + margin: 0 auto; | ||
| 544 | + width: 100%; | ||
| 545 | + z-index: 10; | ||
| 546 | + flex-shrink: 0; | ||
| 547 | + } | ||
| 548 | + | ||
| 549 | + .breadcrumb { font-size: 0.7rem; color: var(--theme-texto); opacity: 0.5; display: flex; align-items: center; gap: 0.3rem; } | ||
| 550 | + .back-btn { background: none; border: none; cursor: pointer; color: var(--theme-texto); font-size: 0.85rem; padding: 0; opacity: 0.6; transition: opacity 0.15s; } | ||
| 551 | + .back-btn:hover { opacity: 1; } | ||
| 552 | + .breadcrumb a { color: var(--theme-texto); text-decoration: none; } | ||
| 553 | + .breadcrumb a:hover { text-decoration: underline; opacity: 1; } | ||
| 554 | + .breadcrumb .sep { margin: 0 0.3rem; } | ||
| 555 | + | ||
| 556 | + .titulo-dropdowns { | ||
| 557 | + display: flex; | ||
| 558 | + align-items: baseline; | ||
| 559 | + gap: 0.4rem; | ||
| 560 | + flex-wrap: wrap; | ||
| 561 | + } | ||
| 562 | + | ||
| 563 | + .titulo-sep { | ||
| 564 | + font-size: 1.1rem; | ||
| 565 | + font-weight: 700; | ||
| 566 | + color: var(--theme-titulo); | ||
| 567 | + } | ||
| 568 | + | ||
| 569 | + .controls-row { | ||
| 570 | + display: flex; | ||
| 571 | + align-items: center; | ||
| 572 | + gap: 0.75rem; | ||
| 573 | + } | ||
| 574 | + | ||
| 575 | + /* Selector de clasificador */ | ||
| 576 | + .clasificador-selector { | ||
| 577 | + display: flex; | ||
| 578 | + align-items: center; | ||
| 579 | + gap: 0.4rem; | ||
| 580 | + position: relative; | ||
| 581 | + } | ||
| 582 | + | ||
| 583 | + .clasificador-label { | ||
| 584 | + font-size: 0.7rem; | ||
| 585 | + color: var(--theme-texto); | ||
| 586 | + opacity: 0.5; | ||
| 587 | + } | ||
| 588 | + | ||
| 589 | + .clasificador-btn { | ||
| 590 | + display: inline-flex; | ||
| 591 | + align-items: center; | ||
| 592 | + gap: 0.3rem; | ||
| 593 | + padding: 0.125rem 0; | ||
| 594 | + background: transparent; | ||
| 595 | + border: none; | ||
| 596 | + border-bottom: 1.5px dotted var(--theme-texto); | ||
| 597 | + color: var(--theme-titulo); | ||
| 598 | + font-size: 1.1rem; | ||
| 599 | + font-weight: 700; | ||
| 600 | + cursor: pointer; | ||
| 601 | + font-family: var(--font-sans); | ||
| 602 | + transition: border-color 0.2s; | ||
| 603 | + } | ||
| 604 | + .clasificador-btn:hover { border-bottom-color: var(--theme-accent); } | ||
| 605 | + | ||
| 606 | + .clasificador-dropdown { | ||
| 607 | + position: absolute; | ||
| 608 | + top: calc(100% + 6px); | ||
| 609 | + left: 0; | ||
| 610 | + min-width: 240px; | ||
| 611 | + background: var(--theme-surface); | ||
| 612 | + border: 1px solid var(--theme-borde); | ||
| 613 | + border-radius: 10px; | ||
| 614 | + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); | ||
| 615 | + z-index: 100; | ||
| 616 | + overflow: hidden; | ||
| 617 | + } | ||
| 618 | + | ||
| 619 | + .clasificador-option { | ||
| 620 | + display: flex; | ||
| 621 | + align-items: center; | ||
| 622 | + gap: 0.5rem; | ||
| 623 | + width: 100%; | ||
| 624 | + padding: 0.6rem 0.75rem; | ||
| 625 | + border: none; | ||
| 626 | + background: transparent; | ||
| 627 | + color: var(--theme-titulo); | ||
| 628 | + font-size: 0.8rem; | ||
| 629 | + font-weight: 500; | ||
| 630 | + text-align: left; | ||
| 631 | + cursor: pointer; | ||
| 632 | + font-family: var(--font-sans); | ||
| 633 | + transition: background 0.15s; | ||
| 634 | + } | ||
| 635 | + .clasificador-option:hover { background: var(--theme-surface-hover); } | ||
| 636 | + .clasificador-option.active { background: rgba(107, 159, 212, 0.1); } | ||
| 637 | + .clasificador-option.disabled { opacity: 0.4; cursor: default; } | ||
| 638 | + .clasificador-option.disabled:hover { background: transparent; } | ||
| 639 | + | ||
| 640 | + .option-label { flex: 1; } | ||
| 641 | + .option-sub { font-size: 0.65rem; color: var(--theme-texto); opacity: 0.5; } | ||
| 642 | + .option-badge { | ||
| 643 | + font-size: 0.55rem; | ||
| 644 | + font-weight: 600; | ||
| 645 | + text-transform: uppercase; | ||
| 646 | + letter-spacing: 0.03em; | ||
| 647 | + padding: 2px 5px; | ||
| 648 | + border-radius: 3px; | ||
| 649 | + background: var(--theme-borde); | ||
| 650 | + color: var(--theme-texto); | ||
| 651 | + opacity: 0.6; | ||
| 652 | + } | ||
| 653 | + | ||
| 654 | + .clasificador-search { | ||
| 655 | + padding: 0.5rem; | ||
| 656 | + border-top: 1px solid var(--theme-borde); | ||
| 657 | + } | ||
| 658 | + | ||
| 659 | + .clasificador-search input { | ||
| 660 | + width: 100%; | ||
| 661 | + padding: 0.4rem 0.6rem; | ||
| 662 | + border: 1px solid var(--theme-borde); | ||
| 663 | + border-radius: 6px; | ||
| 664 | + background: var(--theme-body); | ||
| 665 | + color: var(--theme-titulo); | ||
| 666 | + font-size: 0.75rem; | ||
| 667 | + font-family: var(--font-sans); | ||
| 668 | + outline: none; | ||
| 669 | + } | ||
| 670 | + .clasificador-search input:focus { border-color: var(--theme-accent); } | ||
| 671 | + | ||
| 672 | + /* Slider de rango */ | ||
| 673 | + .rango-slider { | ||
| 674 | + flex: 1; | ||
| 675 | + } | ||
| 676 | + | ||
| 677 | + .rango-track-wrap { | ||
| 678 | + position: relative; | ||
| 679 | + height: 20px; | ||
| 680 | + width: 100%; | ||
| 681 | + margin-top: 1.25rem; | ||
| 682 | + } | ||
| 683 | + | ||
| 684 | + .rango-val-float { | ||
| 685 | + position: absolute; | ||
| 686 | + top: -1.1rem; | ||
| 687 | + transform: translateX(-50%); | ||
| 688 | + font-size: 0.6rem; | ||
| 689 | + font-weight: 600; | ||
| 690 | + color: var(--theme-titulo); | ||
| 691 | + opacity: 0.7; | ||
| 692 | + white-space: nowrap; | ||
| 693 | + pointer-events: none; | ||
| 694 | + z-index: 4; | ||
| 695 | + } | ||
| 696 | + | ||
| 697 | + .mapa-muni-count { | ||
| 698 | + position: absolute; | ||
| 699 | + bottom: 1rem; | ||
| 700 | + left: 1.5rem; | ||
| 701 | + font-size: 0.7rem; | ||
| 702 | + font-weight: 600; | ||
| 703 | + color: var(--theme-accent); | ||
| 704 | + pointer-events: none; | ||
| 705 | + } | ||
| 706 | + | ||
| 707 | + .rango-gradiente { | ||
| 708 | + position: absolute; | ||
| 709 | + top: 50%; | ||
| 710 | + left: 7px; | ||
| 711 | + right: 7px; | ||
| 712 | + height: 8px; | ||
| 713 | + transform: translateY(-50%); | ||
| 714 | + border-radius: 4px; | ||
| 715 | + background: linear-gradient(to right, #f2ece6, #e0c8b0, #c4897d, #a86858, #8B4A3A); | ||
| 716 | + } | ||
| 717 | + | ||
| 718 | + :global(html.dark) .rango-gradiente { | ||
| 719 | + background: linear-gradient(to right, #2E2B27, #4A4035, #6B5A48, #9A8050, #C9A751); | ||
| 720 | + } | ||
| 721 | + | ||
| 722 | + .rango-input { | ||
| 723 | + position: absolute; | ||
| 724 | + width: 100%; | ||
| 725 | + top: 50%; | ||
| 726 | + transform: translateY(-50%); | ||
| 727 | + height: 20px; | ||
| 728 | + z-index: 3; | ||
| 729 | + margin: 0; | ||
| 730 | + -webkit-appearance: none; | ||
| 731 | + appearance: none; | ||
| 732 | + background: transparent; | ||
| 733 | + pointer-events: none; | ||
| 734 | + } | ||
| 735 | + | ||
| 736 | + .rango-input::-webkit-slider-thumb { | ||
| 737 | + -webkit-appearance: none; | ||
| 738 | + width: 16px; | ||
| 739 | + height: 16px; | ||
| 740 | + border-radius: 50%; | ||
| 741 | + background: #F5F0E8; | ||
| 742 | + cursor: pointer; | ||
| 743 | + pointer-events: all; | ||
| 744 | + border: 3px solid #1C1C1A; | ||
| 745 | + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4); | ||
| 746 | + margin-top: -7px; | ||
| 747 | + } | ||
| 748 | + | ||
| 749 | + :global(html:not(.dark)) .rango-input::-webkit-slider-thumb { | ||
| 750 | + background: #1d1d1f; | ||
| 751 | + border-color: #f5f5f7; | ||
| 752 | + } | ||
| 753 | + | ||
| 754 | + .rango-input::-moz-range-thumb { | ||
| 755 | + width: 16px; | ||
| 756 | + height: 16px; | ||
| 757 | + border-radius: 50%; | ||
| 758 | + background: #F5F0E8; | ||
| 759 | + cursor: pointer; | ||
| 760 | + pointer-events: all; | ||
| 761 | + border: 3px solid #1C1C1A; | ||
| 762 | + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4); | ||
| 763 | + } | ||
| 764 | + | ||
| 765 | + :global(html:not(.dark)) .rango-input::-moz-range-thumb { | ||
| 766 | + background: #1d1d1f; | ||
| 767 | + border-color: #f5f5f7; | ||
| 768 | + } | ||
| 769 | + | ||
| 770 | + .rango-input::-webkit-slider-runnable-track { height: 0; background: transparent; } | ||
| 771 | + .rango-input::-moz-range-track { height: 0; background: transparent; } | ||
| 772 | + | ||
| 773 | + .rango-mask-left, .rango-mask-right { | ||
| 774 | + position: absolute; | ||
| 775 | + top: 50%; | ||
| 776 | + height: 8px; | ||
| 777 | + transform: translateY(-50%); | ||
| 778 | + background: var(--theme-body); | ||
| 779 | + opacity: 0.7; | ||
| 780 | + pointer-events: none; | ||
| 781 | + z-index: 2; | ||
| 782 | + } | ||
| 783 | + | ||
| 784 | + :global(html:not(.dark)) .rango-mask-left, | ||
| 785 | + :global(html:not(.dark)) .rango-mask-right { | ||
| 786 | + background: #f5f5f7; | ||
| 787 | + } | ||
| 788 | + | ||
| 789 | + .rango-mask-left { | ||
| 790 | + left: 0; | ||
| 791 | + border-radius: 4px 0 0 4px; | ||
| 792 | + } | ||
| 793 | + | ||
| 794 | + .rango-mask-right { | ||
| 795 | + right: 0; | ||
| 796 | + border-radius: 0 4px 4px 0; | ||
| 797 | + } | ||
| 798 | + | ||
| 799 | + .mapa-title { | ||
| 800 | + font-size: 1.1rem; | ||
| 801 | + font-weight: 700; | ||
| 802 | + color: var(--theme-titulo); | ||
| 803 | + margin: 0; | ||
| 804 | + } | ||
| 805 | + | ||
| 806 | + /* Toggle per cápita */ | ||
| 807 | + .percapita-toggle { | ||
| 808 | + display: flex; | ||
| 809 | + gap: 2px; | ||
| 810 | + background: rgba(255, 255, 255, 0.06); | ||
| 811 | + border-radius: 8px; | ||
| 812 | + padding: 3px; | ||
| 813 | + backdrop-filter: blur(12px); | ||
| 814 | + } | ||
| 815 | + :global(html:not(.dark)) .percapita-toggle { background: rgba(0, 0, 0, 0.05); } | ||
| 816 | + | ||
| 817 | + .percapita-btn { | ||
| 818 | + padding: 5px 12px; | ||
| 819 | + font-size: 0.75rem; | ||
| 820 | + font-weight: 600; | ||
| 821 | + font-family: var(--font-sans); | ||
| 822 | + border: none; | ||
| 823 | + border-radius: 6px; | ||
| 824 | + cursor: pointer; | ||
| 825 | + background: transparent; | ||
| 826 | + color: var(--theme-texto); | ||
| 827 | + opacity: 0.6; | ||
| 828 | + transition: all 0.2s; | ||
| 829 | + } | ||
| 830 | + .percapita-btn:hover { opacity: 0.8; } | ||
| 831 | + .percapita-btn.active { opacity: 1; background: rgba(255, 255, 255, 0.12); color: var(--theme-titulo); } | ||
| 832 | + :global(html:not(.dark)) .percapita-btn.active { background: rgba(255, 255, 255, 0.8); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08); } | ||
| 833 | + | ||
| 834 | + /* Dropdown gestión */ | ||
| 835 | + .gestion-selector { position: relative; } | ||
| 836 | + .gestion-btn { | ||
| 837 | + display: inline-flex; | ||
| 838 | + align-items: center; | ||
| 839 | + gap: 0.375rem; | ||
| 840 | + padding: 0.125rem 0; | ||
| 841 | + background: transparent; | ||
| 842 | + border: none; | ||
| 843 | + border-bottom: 1.5px dotted var(--theme-texto); | ||
| 844 | + color: var(--theme-titulo); | ||
| 845 | + font-size: 1rem; | ||
| 846 | + font-weight: 600; | ||
| 847 | + cursor: pointer; | ||
| 848 | + font-family: var(--font-sans); | ||
| 849 | + } | ||
| 850 | + .gestion-btn:hover { border-bottom-color: var(--theme-accent); } | ||
| 851 | + .gestion-chevron { color: var(--theme-texto); opacity: 0.5; transition: transform 0.2s; } | ||
| 852 | + .gestion-chevron.open { transform: rotate(180deg); } | ||
| 853 | + .gestion-dropdown { | ||
| 854 | + position: absolute; | ||
| 855 | + top: calc(100% + 6px); | ||
| 856 | + right: 0; | ||
| 857 | + min-width: 100px; | ||
| 858 | + max-height: 240px; | ||
| 859 | + overflow-y: auto; | ||
| 860 | + background: var(--theme-surface); | ||
| 861 | + border: 1px solid var(--theme-borde); | ||
| 862 | + border-radius: 10px; | ||
| 863 | + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); | ||
| 864 | + z-index: 100; | ||
| 865 | + } | ||
| 866 | + .gestion-option { | ||
| 867 | + display: block; | ||
| 868 | + width: 100%; | ||
| 869 | + padding: 0.5rem 0.75rem; | ||
| 870 | + border: none; | ||
| 871 | + background: transparent; | ||
| 872 | + color: var(--theme-titulo); | ||
| 873 | + font-size: 0.8125rem; | ||
| 874 | + font-weight: 500; | ||
| 875 | + text-align: left; | ||
| 876 | + cursor: pointer; | ||
| 877 | + font-family: var(--font-sans); | ||
| 878 | + } | ||
| 879 | + .gestion-option:hover { background: var(--theme-surface-hover); } | ||
| 880 | + .gestion-option.active { background: rgba(107, 159, 212, 0.1); color: #6B9FD4; } | ||
| 881 | + | ||
| 882 | + /* Drawer toggle */ | ||
| 883 | + .drawer-toggle { | ||
| 884 | + display: inline-flex; | ||
| 885 | + align-items: center; | ||
| 886 | + gap: 0.375rem; | ||
| 887 | + padding: 6px 12px; | ||
| 888 | + font-size: 0.75rem; | ||
| 889 | + font-weight: 600; | ||
| 890 | + font-family: var(--font-sans); | ||
| 891 | + background: rgba(255, 255, 255, 0.08); | ||
| 892 | + border: 1px solid rgba(255, 255, 255, 0.1); | ||
| 893 | + border-radius: 8px; | ||
| 894 | + color: var(--theme-texto); | ||
| 895 | + cursor: pointer; | ||
| 896 | + backdrop-filter: blur(12px); | ||
| 897 | + transition: all 0.2s; | ||
| 898 | + } | ||
| 899 | + .drawer-toggle:hover { background: rgba(255, 255, 255, 0.14); color: var(--theme-titulo); } | ||
| 900 | + :global(html:not(.dark)) .drawer-toggle { background: rgba(0, 0, 0, 0.05); border-color: rgba(0, 0, 0, 0.08); } | ||
| 901 | + :global(html:not(.dark)) .drawer-toggle:hover { background: rgba(0, 0, 0, 0.1); } | ||
| 902 | + | ||
| 903 | + /* Mapa container */ | ||
| 904 | + .mapa-container { | ||
| 905 | + flex: 1; | ||
| 906 | + display: flex; | ||
| 907 | + align-items: stretch; | ||
| 908 | + justify-content: center; | ||
| 909 | + padding: 0.5rem 0; | ||
| 910 | + min-height: 0; | ||
| 911 | + overflow: hidden; | ||
| 912 | + max-width: 700px; | ||
| 913 | + margin: 0 auto; | ||
| 914 | + width: 100%; | ||
| 915 | + } | ||
| 916 | + | ||
| 917 | + /* Controles abajo */ | ||
| 918 | + .mapa-bottom-controls { | ||
| 919 | + display: flex; | ||
| 920 | + align-items: flex-end; | ||
| 921 | + gap: 1rem; | ||
| 922 | + padding: 0.5rem 1.5rem 1.5rem; | ||
| 923 | + max-width: 700px; | ||
| 924 | + margin: 0 auto; | ||
| 925 | + width: 100%; | ||
| 926 | + flex-shrink: 0; | ||
| 927 | + } | ||
| 928 | + | ||
| 929 | + .mapa-card { | ||
| 930 | + background: var(--theme-surface); | ||
| 931 | + border-radius: 16px; | ||
| 932 | + padding: 1.5rem; | ||
| 933 | + position: relative; | ||
| 934 | + height: 100%; | ||
| 935 | + display: flex; | ||
| 936 | + flex-direction: column; | ||
| 937 | + align-items: center; | ||
| 938 | + justify-content: center; | ||
| 939 | + } | ||
| 940 | + | ||
| 941 | + .mapa-svg-full { | ||
| 942 | + width: 100%; | ||
| 943 | + height: 100%; | ||
| 944 | + display: block; | ||
| 945 | + } | ||
| 946 | + | ||
| 947 | + /* Leyenda quintiles */ | ||
| 948 | + .leyenda-quintiles { | ||
| 949 | + display: flex; | ||
| 950 | + flex-direction: column; | ||
| 951 | + gap: 3px; | ||
| 952 | + position: absolute; | ||
| 953 | + bottom: 1rem; | ||
| 954 | + right: 1rem; | ||
| 955 | + } | ||
| 956 | + | ||
| 957 | + .leyenda-titulo { | ||
| 958 | + font-size: 0.6rem; | ||
| 959 | + font-weight: 600; | ||
| 960 | + color: var(--theme-texto); | ||
| 961 | + opacity: 0.4; | ||
| 962 | + margin-bottom: 2px; | ||
| 963 | + text-transform: uppercase; | ||
| 964 | + letter-spacing: 0.04em; | ||
| 965 | + } | ||
| 966 | + | ||
| 967 | + .quintil-item { | ||
| 968 | + display: flex; | ||
| 969 | + align-items: center; | ||
| 970 | + gap: 0.4rem; | ||
| 971 | + background: none; | ||
| 972 | + border: none; | ||
| 973 | + cursor: pointer; | ||
| 974 | + padding: 2px 0; | ||
| 975 | + transition: opacity 0.2s; | ||
| 976 | + font-family: var(--font-sans); | ||
| 977 | + } | ||
| 978 | + | ||
| 979 | + .quintil-item.dimmed { | ||
| 980 | + opacity: 0.25; | ||
| 981 | + } | ||
| 982 | + | ||
| 983 | + .quintil-color { | ||
| 984 | + width: 18px; | ||
| 985 | + height: 12px; | ||
| 986 | + border-radius: 2px; | ||
| 987 | + flex-shrink: 0; | ||
| 988 | + } | ||
| 989 | + | ||
| 990 | + .quintil-label { | ||
| 991 | + font-size: 0.65rem; | ||
| 992 | + color: var(--theme-titulo); | ||
| 993 | + opacity: 0.8; | ||
| 994 | + white-space: nowrap; | ||
| 995 | + } | ||
| 996 | + | ||
| 997 | + .mapa-pais-fill { fill: #eeece8; } | ||
| 998 | + :global(html.dark) .mapa-pais-fill { fill: #2A2A27; } | ||
| 999 | + .mapa-pais-outline { fill: none; stroke: var(--theme-borde); stroke-width: 1; } | ||
| 1000 | + | ||
| 1001 | + /* Tooltip fijo dentro del card */ | ||
| 1002 | + .mapa-tooltip-fijo { | ||
| 1003 | + position: absolute; | ||
| 1004 | + top: 1.5rem; | ||
| 1005 | + right: 1.5rem; | ||
| 1006 | + display: flex; | ||
| 1007 | + flex-direction: column; | ||
| 1008 | + gap: 0.15rem; | ||
| 1009 | + pointer-events: none; | ||
| 1010 | + min-height: 3rem; | ||
| 1011 | + } | ||
| 1012 | + | ||
| 1013 | + .tooltip-nombre { font-size: 0.9rem; font-weight: 700; color: var(--theme-titulo); } | ||
| 1014 | + .tooltip-dept { font-size: 0.7rem; color: var(--theme-texto); opacity: 0.5; } | ||
| 1015 | + .tooltip-valor { font-size: 0.8rem; color: var(--theme-texto); } | ||
| 1016 | + .tooltip-rank { font-size: 0.75rem; font-weight: 700; color: var(--theme-accent); } | ||
| 1017 | + | ||
| 1018 | + /* Leyenda abajo */ | ||
| 1019 | + .mapa-leyenda-bottom { | ||
| 1020 | + position: absolute; | ||
| 1021 | + bottom: 1.5rem; | ||
| 1022 | + left: 50%; | ||
| 1023 | + transform: translateX(-50%); | ||
| 1024 | + display: flex; | ||
| 1025 | + align-items: center; | ||
| 1026 | + gap: 0.5rem; | ||
| 1027 | + } | ||
| 1028 | + | ||
| 1029 | + .leyenda-label { font-size: 0.6rem; color: var(--theme-texto); opacity: 0.5; } | ||
| 1030 | + | ||
| 1031 | + .leyenda-gradiente { | ||
| 1032 | + width: 120px; | ||
| 1033 | + height: 6px; | ||
| 1034 | + border-radius: 3px; | ||
| 1035 | + background: linear-gradient(to right, #e8e6e1, #3D7A9C); | ||
| 1036 | + } | ||
| 1037 | + :global(html.dark) .leyenda-gradiente { background: linear-gradient(to right, #2A2A27, #D4A574); } | ||
| 1038 | + | ||
| 1039 | + /* Drawer lateral */ | ||
| 1040 | + .ranking-drawer { | ||
| 1041 | + position: fixed; | ||
| 1042 | + top: 0; | ||
| 1043 | + right: 0; | ||
| 1044 | + width: 440px; | ||
| 1045 | + height: 100vh; | ||
| 1046 | + background: var(--theme-surface); | ||
| 1047 | + border-left: 1px solid var(--theme-borde); | ||
| 1048 | + box-shadow: -8px 0 24px rgba(0, 0, 0, 0.15); | ||
| 1049 | + z-index: 200; | ||
| 1050 | + display: flex; | ||
| 1051 | + flex-direction: column; | ||
| 1052 | + transform: translateX(100%); | ||
| 1053 | + transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1); | ||
| 1054 | + } | ||
| 1055 | + | ||
| 1056 | + .ranking-drawer.open { | ||
| 1057 | + transform: translateX(0); | ||
| 1058 | + } | ||
| 1059 | + | ||
| 1060 | + .drawer-header { | ||
| 1061 | + display: flex; | ||
| 1062 | + justify-content: space-between; | ||
| 1063 | + align-items: center; | ||
| 1064 | + padding: 1.25rem; | ||
| 1065 | + border-bottom: 1px solid var(--theme-borde); | ||
| 1066 | + flex-shrink: 0; | ||
| 1067 | + } | ||
| 1068 | + | ||
| 1069 | + .drawer-titulo { | ||
| 1070 | + font-size: 0.85rem; | ||
| 1071 | + font-weight: 600; | ||
| 1072 | + color: var(--theme-titulo); | ||
| 1073 | + } | ||
| 1074 | + | ||
| 1075 | + .drawer-close { | ||
| 1076 | + background: none; | ||
| 1077 | + border: none; | ||
| 1078 | + cursor: pointer; | ||
| 1079 | + color: var(--theme-texto); | ||
| 1080 | + opacity: 0.6; | ||
| 1081 | + padding: 4px; | ||
| 1082 | + } | ||
| 1083 | + .drawer-close:hover { opacity: 1; } | ||
| 1084 | + | ||
| 1085 | + .drawer-list { | ||
| 1086 | + flex: 1; | ||
| 1087 | + overflow-y: auto; | ||
| 1088 | + padding: 0.75rem; | ||
| 1089 | + scrollbar-width: none; | ||
| 1090 | + } | ||
| 1091 | + .drawer-list::-webkit-scrollbar { display: none; } | ||
| 1092 | + | ||
| 1093 | + /* Tabla reactable */ | ||
| 1094 | + .drawer-table-wrap { | ||
| 1095 | + flex: 1; | ||
| 1096 | + overflow-y: auto; | ||
| 1097 | + scrollbar-width: none; | ||
| 1098 | + } | ||
| 1099 | + .drawer-table-wrap::-webkit-scrollbar { display: none; } | ||
| 1100 | + | ||
| 1101 | + .drawer-table { | ||
| 1102 | + width: 100%; | ||
| 1103 | + border-collapse: collapse; | ||
| 1104 | + font-size: 0.7rem; | ||
| 1105 | + } | ||
| 1106 | + | ||
| 1107 | + .drawer-table thead { | ||
| 1108 | + position: sticky; | ||
| 1109 | + top: 0; | ||
| 1110 | + background: var(--theme-surface); | ||
| 1111 | + z-index: 1; | ||
| 1112 | + } | ||
| 1113 | + | ||
| 1114 | + .drawer-table th { | ||
| 1115 | + padding: 0.5rem 0.4rem; | ||
| 1116 | + font-weight: 600; | ||
| 1117 | + color: var(--theme-texto); | ||
| 1118 | + opacity: 0.6; | ||
| 1119 | + text-align: left; | ||
| 1120 | + border-bottom: 1px solid var(--theme-borde); | ||
| 1121 | + white-space: nowrap; | ||
| 1122 | + font-size: 0.65rem; | ||
| 1123 | + } | ||
| 1124 | + | ||
| 1125 | + .th-rank { width: 2rem; text-align: right; padding-right: 0.5rem; } | ||
| 1126 | + .th-sortable { cursor: pointer; transition: color 0.15s; user-select: none; } | ||
| 1127 | + .th-sortable:hover { color: var(--theme-titulo); opacity: 1; } | ||
| 1128 | + .th-right { text-align: right; } | ||
| 1129 | + | ||
| 1130 | + .tabla-row { transition: background 0.1s; } | ||
| 1131 | + .tabla-row:hover, .tabla-row.hovered { background: var(--theme-surface-hover); } | ||
| 1132 | + | ||
| 1133 | + .drawer-table td { | ||
| 1134 | + padding: 0.35rem 0.4rem; | ||
| 1135 | + border-bottom: 1px solid var(--theme-borde); | ||
| 1136 | + color: var(--theme-titulo); | ||
| 1137 | + } | ||
| 1138 | + | ||
| 1139 | + .td-rank { | ||
| 1140 | + text-align: right; | ||
| 1141 | + padding-right: 0.5rem; | ||
| 1142 | + font-variant-numeric: tabular-nums; | ||
| 1143 | + color: var(--theme-texto); | ||
| 1144 | + opacity: 0.4; | ||
| 1145 | + } | ||
| 1146 | + | ||
| 1147 | + .td-nombre { | ||
| 1148 | + display: flex; | ||
| 1149 | + align-items: center; | ||
| 1150 | + gap: 0.35rem; | ||
| 1151 | + } | ||
| 1152 | + | ||
| 1153 | + .td-nombre a { | ||
| 1154 | + color: var(--theme-titulo); | ||
| 1155 | + text-decoration: none; | ||
| 1156 | + font-weight: 500; | ||
| 1157 | + white-space: nowrap; | ||
| 1158 | + overflow: hidden; | ||
| 1159 | + text-overflow: ellipsis; | ||
| 1160 | + } | ||
| 1161 | + .td-nombre a:hover { text-decoration: underline; } | ||
| 1162 | + | ||
| 1163 | + .td-color-dot { | ||
| 1164 | + width: 6px; | ||
| 1165 | + height: 6px; | ||
| 1166 | + border-radius: 50%; | ||
| 1167 | + flex-shrink: 0; | ||
| 1168 | + } | ||
| 1169 | + | ||
| 1170 | + .td-dept { | ||
| 1171 | + color: var(--theme-texto); | ||
| 1172 | + opacity: 0.5; | ||
| 1173 | + white-space: nowrap; | ||
| 1174 | + } | ||
| 1175 | + | ||
| 1176 | + .td-monto { | ||
| 1177 | + text-align: right; | ||
| 1178 | + font-variant-numeric: tabular-nums; | ||
| 1179 | + font-weight: 600; | ||
| 1180 | + white-space: nowrap; | ||
| 1181 | + } | ||
| 1182 | + | ||
| 1183 | + /* Toggle departamentos */ | ||
| 1184 | + .dept-toggle { | ||
| 1185 | + padding: 5px 12px; | ||
| 1186 | + font-size: 0.75rem; | ||
| 1187 | + font-weight: 600; | ||
| 1188 | + font-family: var(--font-sans); | ||
| 1189 | + border: 1px solid rgba(255, 255, 255, 0.15); | ||
| 1190 | + border-radius: 6px; | ||
| 1191 | + cursor: pointer; | ||
| 1192 | + background: transparent; | ||
| 1193 | + color: var(--theme-texto); | ||
| 1194 | + opacity: 0.6; | ||
| 1195 | + transition: all 0.2s; | ||
| 1196 | + } | ||
| 1197 | + :global(html:not(.dark)) .dept-toggle { border-color: rgba(0, 0, 0, 0.12); } | ||
| 1198 | + .dept-toggle:hover { opacity: 0.8; } | ||
| 1199 | + .dept-toggle.active { opacity: 1; color: var(--theme-titulo); background: rgba(255, 255, 255, 0.08); border-color: rgba(255, 255, 255, 0.3); } | ||
| 1200 | + :global(html:not(.dark)) .dept-toggle.active { background: rgba(0, 0, 0, 0.05); border-color: rgba(0, 0, 0, 0.2); } | ||
| 1201 | + | ||
| 1202 | + | ||
| 1203 | + @media (max-width: 768px) { | ||
| 1204 | + .mapa-top-controls { padding: 4rem 1rem 0; } | ||
| 1205 | + .mapa-bottom-controls { padding: 0 1rem 1rem; } | ||
| 1206 | + .controls-row { flex-wrap: wrap; } | ||
| 1207 | + .ranking-drawer { width: 100%; } | ||
| 1208 | + | ||
| 1209 | + .leyenda-quintiles { | ||
| 1210 | + position: static; | ||
| 1211 | + flex-direction: row; | ||
| 1212 | + flex-wrap: wrap; | ||
| 1213 | + gap: 4px; | ||
| 1214 | + margin-top: 0.5rem; | ||
| 1215 | + } | ||
| 1216 | + | ||
| 1217 | + .leyenda-titulo { display: none; } | ||
| 1218 | + | ||
| 1219 | + .mapa-muni-count { | ||
| 1220 | + position: static; | ||
| 1221 | + margin-top: 0.25rem; | ||
| 1222 | + } | ||
| 1223 | + | ||
| 1224 | + .mapa-card { | ||
| 1225 | + padding: 1rem; | ||
| 1226 | + } | ||
| 1227 | + } | ||
| 1228 | +</style> |
src/routes/ubicacion/[ubigeo]/+page.js
0 → 100644
| 1 | +import { supabase } from '$lib/supabase'; | ||
| 2 | +import { error } from '@sveltejs/kit'; | ||
| 3 | + | ||
| 4 | +export async function load({ params, fetch }) { | ||
| 5 | + const codigo = params.ubigeo; | ||
| 6 | + | ||
| 7 | + // Cargar resumen y población en paralelo | ||
| 8 | + const [resumenRes, pobRes] = await Promise.all([ | ||
| 9 | + supabase | ||
| 10 | + .schema('ppto') | ||
| 11 | + .from('entidad_resumen') | ||
| 12 | + .select('tipo, tipo_codigo, codigo, desc, desc_padre, gestion, devengado, ranking') | ||
| 13 | + .eq('codigo', codigo), | ||
| 14 | + | ||
| 15 | + fetch('/poblacion.csv').then(r => r.text()) | ||
| 16 | + ]); | ||
| 17 | + | ||
| 18 | + if (resumenRes.error || !resumenRes.data?.length) { | ||
| 19 | + throw error(404, 'Ubicación no encontrada'); | ||
| 20 | + } | ||
| 21 | + | ||
| 22 | + // Parsear población para este código | ||
| 23 | + const poblacionMap = {}; | ||
| 24 | + pobRes.split('\n').slice(1).forEach(line => { | ||
| 25 | + const [cod, gestion, pob] = line.split(','); | ||
| 26 | + if (cod === codigo) { | ||
| 27 | + poblacionMap[parseInt(gestion)] = parseInt(pob); | ||
| 28 | + } | ||
| 29 | + }); | ||
| 30 | + | ||
| 31 | + // Determinar última gestión | ||
| 32 | + const gestiones = [...new Set(resumenRes.data.map(d => d.gestion))].sort((a, b) => b - a); | ||
| 33 | + const ultimaGestion = gestiones[0] || 2025; | ||
| 34 | + | ||
| 35 | + // Cargar distribuciones de la última gestión | ||
| 36 | + const distRes = await supabase | ||
| 37 | + .schema('ppto') | ||
| 38 | + .from('entidad_distribuciones') | ||
| 39 | + .select('tipo, dimension, gestion, padre, desc_padre, hijo, desc_hijo, devengado') | ||
| 40 | + .eq('codigo', codigo) | ||
| 41 | + .eq('gestion', ultimaGestion); | ||
| 42 | + | ||
| 43 | + const firstRow = resumenRes.data[0]; | ||
| 44 | + | ||
| 45 | + return { | ||
| 46 | + nombre: firstRow.desc || `Ubicación ${codigo}`, | ||
| 47 | + nombrePadre: firstRow.desc_padre || null, | ||
| 48 | + codigo, | ||
| 49 | + resumenData: resumenRes.data || [], | ||
| 50 | + distribucionesData: distRes.data || [], | ||
| 51 | + gestionInicial: ultimaGestion, | ||
| 52 | + poblacionMap | ||
| 53 | + }; | ||
| 54 | +} |
src/routes/ubicacion/[ubigeo]/+page.svelte
0 → 100644
| 1 | +<script> | ||
| 2 | + import { onMount, tick } from 'svelte'; | ||
| 3 | + import { page } from '$app/stores'; | ||
| 4 | + import { get } from 'svelte/store'; | ||
| 5 | + import * as d3 from 'd3'; | ||
| 6 | + import { supabase } from '$lib/supabase'; | ||
| 7 | + import { mapaCache } from '$lib/stores/mapaCache'; | ||
| 8 | + | ||
| 9 | + let { data } = $props(); | ||
| 10 | + let nombre = $derived(data.nombre); | ||
| 11 | + let nombrePadre = $derived(data.nombrePadre); | ||
| 12 | + let poblacionMap = $derived(data.poblacionMap); | ||
| 13 | + | ||
| 14 | + // Toggle total / per cápita (default per cápita en ubigeo) | ||
| 15 | + let modoPerCapita = $state(true); | ||
| 16 | + | ||
| 17 | + // Mapa geográfico | ||
| 18 | + let mapaData = $state(null); | ||
| 19 | + let mapaBoliviaPath = $state(''); | ||
| 20 | + let mapaDepartamentoPath = $state(''); | ||
| 21 | + let mapaMunicipioPath = $state(''); | ||
| 22 | + let mapaAllMunisPaths = $state([]); | ||
| 23 | + let mapaHovered = $state(false); | ||
| 24 | + let mapaPathGen = $state(null); | ||
| 25 | + let mapaDepartamentoNombre = $state(''); | ||
| 26 | + | ||
| 27 | + function fixWinding(geojson) { | ||
| 28 | + for (const feat of geojson.features || [geojson]) { | ||
| 29 | + const geom = feat.geometry || feat; | ||
| 30 | + if (geom.type === 'Polygon') { | ||
| 31 | + geom.coordinates.forEach((ring, i) => { if (i === 0) ring.reverse(); }); | ||
| 32 | + } else if (geom.type === 'MultiPolygon') { | ||
| 33 | + geom.coordinates.forEach(poly => poly.forEach((ring, i) => { if (i === 0) ring.reverse(); })); | ||
| 34 | + } | ||
| 35 | + } | ||
| 36 | + } | ||
| 37 | + | ||
| 38 | + async function cargarMapa(codigo) { | ||
| 39 | + const cached = get(mapaCache); | ||
| 40 | + if (cached) { | ||
| 41 | + mapaData = cached; | ||
| 42 | + } else if (!mapaData) { | ||
| 43 | + const res = await fetch('/mapa.json'); | ||
| 44 | + mapaData = await res.json(); | ||
| 45 | + delete mapaData.bolivia.crs; | ||
| 46 | + delete mapaData.departamentos.crs; | ||
| 47 | + delete mapaData.municipios.crs; | ||
| 48 | + fixWinding(mapaData.bolivia); | ||
| 49 | + fixWinding(mapaData.departamentos); | ||
| 50 | + fixWinding(mapaData.municipios); | ||
| 51 | + mapaCache.set(mapaData); | ||
| 52 | + } | ||
| 53 | + | ||
| 54 | + const codigoNum = parseInt(codigo); | ||
| 55 | + const municipio = mapaData.municipios.features.find( | ||
| 56 | + f => f.properties.codigo === codigoNum | ||
| 57 | + ); | ||
| 58 | + | ||
| 59 | + if (!municipio) return; | ||
| 60 | + | ||
| 61 | + const deptNombre = municipio.properties.DEPARTAMEN; | ||
| 62 | + mapaDepartamentoNombre = deptNombre; | ||
| 63 | + | ||
| 64 | + const departamento = mapaData.departamentos.features.find( | ||
| 65 | + f => f.properties.DEPARTAMEN === deptNombre | ||
| 66 | + ); | ||
| 67 | + | ||
| 68 | + const projection = d3.geoMercator().fitSize([200, 240], mapaData.bolivia); | ||
| 69 | + const pathGen = d3.geoPath(projection); | ||
| 70 | + mapaPathGen = pathGen; | ||
| 71 | + | ||
| 72 | + mapaBoliviaPath = pathGen(mapaData.bolivia) || ''; | ||
| 73 | + mapaDepartamentoPath = departamento ? (pathGen(departamento) || '') : ''; | ||
| 74 | + mapaMunicipioPath = pathGen(municipio) || ''; | ||
| 75 | + | ||
| 76 | + // Pre-calcular paths de todos los municipios para hover | ||
| 77 | + mapaAllMunisPaths = mapaData.municipios.features.map(f => ({ | ||
| 78 | + path: pathGen(f) || '', | ||
| 79 | + codigo: f.properties.codigo, | ||
| 80 | + isCurrent: f.properties.codigo === codigoNum | ||
| 81 | + })); | ||
| 82 | + } | ||
| 83 | + | ||
| 84 | + // Datos desde el loader (Supabase) | ||
| 85 | + let codigoSeleccionado = $derived($page.params.ubigeo); | ||
| 86 | + let gestionSeleccionada = $state(data.gestionInicial); | ||
| 87 | + let resumenData = $derived(data.resumenData); | ||
| 88 | + let distribucionesData = $state(data.distribucionesData); | ||
| 89 | + let cargando = $state(false); | ||
| 90 | + let distCache = $state({ [data.gestionInicial]: data.distribucionesData }); | ||
| 91 | + let cargandoDist = $state(false); | ||
| 92 | + | ||
| 93 | + // Resetear al cambiar de ubicación | ||
| 94 | + let prevCodigo = codigoSeleccionado; | ||
| 95 | + $effect(() => { | ||
| 96 | + if (data.gestionInicial !== gestionSeleccionada || codigoSeleccionado !== prevCodigo) { | ||
| 97 | + prevCodigo = codigoSeleccionado; | ||
| 98 | + gestionSeleccionada = data.gestionInicial; | ||
| 99 | + distribucionesData = data.distribucionesData; | ||
| 100 | + distCache = { [data.gestionInicial]: data.distribucionesData }; | ||
| 101 | + } | ||
| 102 | + }); | ||
| 103 | + | ||
| 104 | + async function cargarDistribuciones(gestion) { | ||
| 105 | + if (distCache[gestion]) { | ||
| 106 | + distribucionesData = distCache[gestion]; | ||
| 107 | + return; | ||
| 108 | + } | ||
| 109 | + cargandoDist = true; | ||
| 110 | + const { data: rows } = await supabase | ||
| 111 | + .schema('ppto') | ||
| 112 | + .from('entidad_distribuciones') | ||
| 113 | + .select('tipo, dimension, gestion, padre, desc_padre, hijo, desc_hijo, devengado') | ||
| 114 | + .eq('codigo', codigoSeleccionado) | ||
| 115 | + .eq('gestion', gestion); | ||
| 116 | + const result = rows || []; | ||
| 117 | + distCache[gestion] = result; | ||
| 118 | + distribucionesData = result; | ||
| 119 | + cargandoDist = false; | ||
| 120 | + } | ||
| 121 | + | ||
| 122 | + // Cargar distribuciones solo al cambiar gestión por el usuario | ||
| 123 | + let prevGestion = gestionSeleccionada; | ||
| 124 | + $effect(() => { | ||
| 125 | + if (gestionSeleccionada !== prevGestion) { | ||
| 126 | + prevGestion = gestionSeleccionada; | ||
| 127 | + cargarDistribuciones(gestionSeleccionada); | ||
| 128 | + } | ||
| 129 | + }); | ||
| 130 | + | ||
| 131 | + // Hover en gráficos de historia | ||
| 132 | + let hoveredIngresos = $state(null); | ||
| 133 | + let hoveredGastos = $state(null); | ||
| 134 | + | ||
| 135 | + // Dropdown de gestión | ||
| 136 | + let gestionDropdownOpen = $state(false); | ||
| 137 | + | ||
| 138 | + // Mini-buscador de entidades | ||
| 139 | + let searchQuery = $state(''); | ||
| 140 | + let searchResults = $state([]); | ||
| 141 | + let searchLoading = $state(false); | ||
| 142 | + let searchOpen = $state(false); | ||
| 143 | + let searchSelectedIdx = $state(-1); | ||
| 144 | + let searchDebounce = null; | ||
| 145 | + | ||
| 146 | + function parseMetadatos(meta) { | ||
| 147 | + if (!meta) return {}; | ||
| 148 | + if (typeof meta === 'object') return meta; | ||
| 149 | + try { return JSON.parse(meta); } catch { return {}; } | ||
| 150 | + } | ||
| 151 | + | ||
| 152 | + function onSearchInput() { | ||
| 153 | + clearTimeout(searchDebounce); | ||
| 154 | + if (searchQuery.length < 2) { | ||
| 155 | + searchResults = []; | ||
| 156 | + searchOpen = false; | ||
| 157 | + return; | ||
| 158 | + } | ||
| 159 | + searchDebounce = setTimeout(async () => { | ||
| 160 | + searchLoading = true; | ||
| 161 | + try { | ||
| 162 | + const params = new URLSearchParams({ | ||
| 163 | + q: searchQuery, | ||
| 164 | + is_class: 'true', | ||
| 165 | + class_: 'ubigeo', | ||
| 166 | + per_page: '12' | ||
| 167 | + }); | ||
| 168 | + const res = await fetch(`/api/search?${params}`); | ||
| 169 | + if (!res.ok) throw new Error(); | ||
| 170 | + const json = await res.json(); | ||
| 171 | + searchResults = (json.hits || []) | ||
| 172 | + .filter(hit => hit.document.class_ === 'ubigeo') | ||
| 173 | + .map(hit => { | ||
| 174 | + const meta = parseMetadatos(hit.document.metadatos); | ||
| 175 | + return { | ||
| 176 | + nombre: hit.document.texto, | ||
| 177 | + codigo: String(meta.municipio_ubigeo || ''), | ||
| 178 | + departamento: meta.desc_departamento, | ||
| 179 | + provincia: meta.desc_provincia, | ||
| 180 | + highlight: hit.highlights?.[0]?.snippet || hit.document.texto | ||
| 181 | + }; | ||
| 182 | + }); | ||
| 183 | + searchOpen = searchResults.length > 0; | ||
| 184 | + searchSelectedIdx = -1; | ||
| 185 | + } catch { | ||
| 186 | + searchResults = []; | ||
| 187 | + } | ||
| 188 | + searchLoading = false; | ||
| 189 | + }, 250); | ||
| 190 | + } | ||
| 191 | + | ||
| 192 | + function navigateToEntity(item) { | ||
| 193 | + window.location.href = `/ubicacion/${item.codigo}`; | ||
| 194 | + } | ||
| 195 | + | ||
| 196 | + function onSearchKeydown(e) { | ||
| 197 | + if (!searchOpen) return; | ||
| 198 | + if (e.key === 'ArrowDown') { | ||
| 199 | + e.preventDefault(); | ||
| 200 | + searchSelectedIdx = Math.min(searchSelectedIdx + 1, searchResults.length - 1); | ||
| 201 | + } else if (e.key === 'ArrowUp') { | ||
| 202 | + e.preventDefault(); | ||
| 203 | + searchSelectedIdx = Math.max(searchSelectedIdx - 1, -1); | ||
| 204 | + } else if (e.key === 'Enter' && searchSelectedIdx >= 0) { | ||
| 205 | + e.preventDefault(); | ||
| 206 | + navigateToEntity(searchResults[searchSelectedIdx]); | ||
| 207 | + } else if (e.key === 'Escape') { | ||
| 208 | + searchOpen = false; | ||
| 209 | + searchQuery = ''; | ||
| 210 | + } | ||
| 211 | + } | ||
| 212 | + | ||
| 213 | + function closeSearch() { | ||
| 214 | + setTimeout(() => { searchOpen = false; }, 150); | ||
| 215 | + } | ||
| 216 | + | ||
| 217 | + // Filtrar solo años con población (2016+) | ||
| 218 | + function filtrarConPoblacion(rows) { | ||
| 219 | + return rows.filter(d => poblacionMap[d.gestion]); | ||
| 220 | + } | ||
| 221 | + | ||
| 222 | + // Aplicar per cápita si está activo | ||
| 223 | + function aplicarPerCapita(rows) { | ||
| 224 | + const filtered = filtrarConPoblacion(rows); | ||
| 225 | + if (!modoPerCapita) return filtered; | ||
| 226 | + return filtered.map(d => ({ ...d, devengado: d.devengado / poblacionMap[d.gestion] })); | ||
| 227 | + } | ||
| 228 | + | ||
| 229 | + // Datos derivados - siempre filtrados a años con población | ||
| 230 | + let historiaGastos = $derived( | ||
| 231 | + aplicarPerCapita( | ||
| 232 | + resumenData | ||
| 233 | + .filter(d => d.tipo === 'gastos') | ||
| 234 | + .sort((a, b) => a.gestion - b.gestion) | ||
| 235 | + ) | ||
| 236 | + ); | ||
| 237 | + | ||
| 238 | + let historiaIngresos = $derived( | ||
| 239 | + aplicarPerCapita( | ||
| 240 | + resumenData | ||
| 241 | + .filter(d => d.tipo === 'ingresos') | ||
| 242 | + .sort((a, b) => a.gestion - b.gestion) | ||
| 243 | + ) | ||
| 244 | + ); | ||
| 245 | + | ||
| 246 | + let tieneIngresos = $derived(historiaIngresos.length > 0); | ||
| 247 | + | ||
| 248 | + let gestiones = $derived(() => { | ||
| 249 | + const years = [...new Set(resumenData.map(d => d.gestion))].filter(g => poblacionMap[g]).sort(); | ||
| 250 | + return years; | ||
| 251 | + }); | ||
| 252 | + | ||
| 253 | + function parseRanking(rankingStr) { | ||
| 254 | + if (!rankingStr) return null; | ||
| 255 | + const parts = String(rankingStr).split('/'); | ||
| 256 | + if (parts.length !== 2) return null; | ||
| 257 | + return { posicion: parseInt(parts[0]), total: parseInt(parts[1]) }; | ||
| 258 | + } | ||
| 259 | + | ||
| 260 | + let rankingGastos = $derived(() => { | ||
| 261 | + const row = historiaGastos.find(d => d.gestion === gestionSeleccionada); | ||
| 262 | + return row ? parseRanking(row.ranking) : null; | ||
| 263 | + }); | ||
| 264 | + | ||
| 265 | + let rankingIngresos = $derived(() => { | ||
| 266 | + const row = historiaIngresos.find(d => d.gestion === gestionSeleccionada); | ||
| 267 | + return row ? parseRanking(row.ranking) : null; | ||
| 268 | + }); | ||
| 269 | + | ||
| 270 | + let distFiltradas = $derived(distribucionesData); | ||
| 271 | + | ||
| 272 | + function prepararSegmentos(data) { | ||
| 273 | + const pob = modoPerCapita ? poblacionMap[gestionSeleccionada] : null; | ||
| 274 | + return data | ||
| 275 | + .map(d => ({ | ||
| 276 | + codigo: d.hijo, | ||
| 277 | + nombre: d.desc_hijo, | ||
| 278 | + monto: pob ? d.devengado / pob : d.devengado, | ||
| 279 | + padre: d.desc_padre | ||
| 280 | + })) | ||
| 281 | + .filter(d => d.monto > 0) | ||
| 282 | + .sort((a, b) => b.monto - a.monto); | ||
| 283 | + } | ||
| 284 | + | ||
| 285 | + let clasificadoresGasto = $derived.by(() => { | ||
| 286 | + const gastos = distFiltradas.filter(d => d.tipo === 'gastos'); | ||
| 287 | + const dims = [ | ||
| 288 | + { key: 'objeto', label: 'Objetos de gasto' }, | ||
| 289 | + { key: 'finfun', label: 'Finalidad y función' }, | ||
| 290 | + { key: 'acteco', label: 'Sectores económicos' } | ||
| 291 | + ]; | ||
| 292 | + return dims | ||
| 293 | + .map(dim => ({ | ||
| 294 | + ...dim, | ||
| 295 | + data: prepararSegmentos(gastos.filter(d => d.dimension === dim.key)) | ||
| 296 | + })) | ||
| 297 | + .filter(dim => dim.data.length > 0); | ||
| 298 | + }); | ||
| 299 | + | ||
| 300 | + let clasificadoresIngreso = $derived.by(() => { | ||
| 301 | + if (!tieneIngresos) return []; | ||
| 302 | + const ingresos = distFiltradas.filter(d => d.tipo === 'ingresos'); | ||
| 303 | + const dims = [ | ||
| 304 | + { key: 'rubro', label: 'Rubros de ingreso' }, | ||
| 305 | + { key: 'organismo', label: 'Organismos financiadores' } | ||
| 306 | + ]; | ||
| 307 | + return dims | ||
| 308 | + .map(dim => ({ | ||
| 309 | + ...dim, | ||
| 310 | + data: prepararSegmentos(ingresos.filter(d => d.dimension === dim.key)) | ||
| 311 | + })) | ||
| 312 | + .filter(dim => dim.data.length > 0); | ||
| 313 | + }); | ||
| 314 | + | ||
| 315 | + let barContainers = $state({}); | ||
| 316 | + let hoverData = $state({}); | ||
| 317 | + | ||
| 318 | + function getTotal(data) { | ||
| 319 | + return data.reduce((sum, d) => sum + d.monto, 0); | ||
| 320 | + } | ||
| 321 | + | ||
| 322 | + function formatearMonto(valor) { | ||
| 323 | + if (valor >= 1e9) return `${(valor / 1e9).toFixed(1)} mil millones`; | ||
| 324 | + if (valor >= 1e6) return `${(valor / 1e6).toFixed(0)} millones`; | ||
| 325 | + if (valor >= 1e3) return `${(valor / 1e3).toFixed(0)} mil`; | ||
| 326 | + return valor.toFixed(0); | ||
| 327 | + } | ||
| 328 | + | ||
| 329 | + function formatearPerCapita(valor) { | ||
| 330 | + if (valor >= 1e6) return `${(valor / 1e6).toFixed(1)} millones`; | ||
| 331 | + if (valor >= 1e4) return `${(valor / 1e3).toFixed(1)} mil`; | ||
| 332 | + return Math.round(valor).toLocaleString('es-BO'); | ||
| 333 | + } | ||
| 334 | + | ||
| 335 | + let unidadMonto = $derived(modoPerCapita ? 'Bs por persona al año' : 'de Bolivianos'); | ||
| 336 | + function fmt(valor) { | ||
| 337 | + return modoPerCapita ? formatearPerCapita(valor) : formatearMonto(valor); | ||
| 338 | + } | ||
| 339 | + | ||
| 340 | + // Per cápita promedio anual del periodo | ||
| 341 | + function perCapitaGlobal(tipo) { | ||
| 342 | + const rawRows = resumenData.filter(d => d.tipo === tipo && poblacionMap[d.gestion]); | ||
| 343 | + if (rawRows.length === 0) return 0; | ||
| 344 | + const perCapitaAnuales = rawRows.map(d => d.devengado / poblacionMap[d.gestion]); | ||
| 345 | + return perCapitaAnuales.reduce((s, v) => s + v, 0) / perCapitaAnuales.length; | ||
| 346 | + } | ||
| 347 | + | ||
| 348 | + function formatearMontoCorto(valor) { | ||
| 349 | + if (valor >= 1e9) return `${(valor / 1e9).toFixed(1)}B`; | ||
| 350 | + if (valor >= 1e6) return `${(valor / 1e6).toFixed(0)}M`; | ||
| 351 | + if (valor >= 1e3) return `${(valor / 1e3).toFixed(0)}K`; | ||
| 352 | + return valor.toFixed(0); | ||
| 353 | + } | ||
| 354 | + | ||
| 355 | + function getChartColors() { | ||
| 356 | + const isDark = document.documentElement.classList.contains('dark'); | ||
| 357 | + return { | ||
| 358 | + barDefault: isDark ? 'rgba(107, 159, 212, 0.2)' : 'rgba(196, 137, 125, 0.2)', | ||
| 359 | + barHighlight: isDark ? 'rgba(107, 159, 212, 0.85)' : '#c4897d', | ||
| 360 | + barStroke: isDark ? 'rgba(107, 159, 212, 0.5)' : 'rgba(196, 137, 125, 0.5)', | ||
| 361 | + barStrokeDefault: isDark ? 'transparent' : 'transparent' | ||
| 362 | + }; | ||
| 363 | + } | ||
| 364 | + | ||
| 365 | + function renderizarBarras(key, data) { | ||
| 366 | + const container = barContainers[key]; | ||
| 367 | + if (!container || data.length === 0) return; | ||
| 368 | + | ||
| 369 | + const total = getTotal(data); | ||
| 370 | + const height = 48; | ||
| 371 | + const radius = 4; | ||
| 372 | + | ||
| 373 | + d3.select(container).selectAll('*').remove(); | ||
| 374 | + | ||
| 375 | + const containerRect = container.getBoundingClientRect(); | ||
| 376 | + const width = containerRect.width || 800; | ||
| 377 | + | ||
| 378 | + const svg = d3.select(container) | ||
| 379 | + .append('svg') | ||
| 380 | + .attr('width', '100%') | ||
| 381 | + .attr('height', height) | ||
| 382 | + .attr('viewBox', `0 0 ${width} ${height}`) | ||
| 383 | + .attr('preserveAspectRatio', 'none') | ||
| 384 | + .style('display', 'block'); | ||
| 385 | + | ||
| 386 | + let cumulative = 0; | ||
| 387 | + const segments = data.map(d => { | ||
| 388 | + const segWidth = (d.monto / total) * width; | ||
| 389 | + const segment = { ...d, x: cumulative, width: segWidth }; | ||
| 390 | + cumulative += segWidth; | ||
| 391 | + return segment; | ||
| 392 | + }); | ||
| 393 | + | ||
| 394 | + const chartColors = getChartColors(); | ||
| 395 | + const colorDefault = chartColors.barDefault; | ||
| 396 | + const colorHover = chartColors.barHighlight; | ||
| 397 | + const colorFirst = chartColors.barHighlight; | ||
| 398 | + | ||
| 399 | + const bars = svg.selectAll('rect') | ||
| 400 | + .data(segments) | ||
| 401 | + .enter() | ||
| 402 | + .append('rect') | ||
| 403 | + .attr('x', d => d.x) | ||
| 404 | + .attr('y', 2) | ||
| 405 | + .attr('width', d => Math.max(d.width - 1, 1)) | ||
| 406 | + .attr('height', height - 4) | ||
| 407 | + .attr('rx', (d, i) => { | ||
| 408 | + if (i === 0) return radius; | ||
| 409 | + if (i === segments.length - 1) return radius; | ||
| 410 | + return 0; | ||
| 411 | + }) | ||
| 412 | + .attr('fill', (d, i) => i === 0 ? colorFirst : colorDefault) | ||
| 413 | + .attr('stroke', (d, i) => i === 0 ? chartColors.barStroke : chartColors.barStrokeDefault) | ||
| 414 | + .attr('stroke-width', (d, i) => i === 0 ? 1 : 0.5) | ||
| 415 | + .style('cursor', 'pointer') | ||
| 416 | + .on('mouseenter', function(event, d) { | ||
| 417 | + bars | ||
| 418 | + .attr('fill', colorDefault) | ||
| 419 | + .attr('stroke', chartColors.barStrokeDefault) | ||
| 420 | + .attr('stroke-width', 0.5); | ||
| 421 | + d3.select(this) | ||
| 422 | + .attr('fill', colorHover) | ||
| 423 | + .attr('stroke', chartColors.barStroke) | ||
| 424 | + .attr('stroke-width', 1); | ||
| 425 | + hoverData[key] = d; | ||
| 426 | + hoverData = { ...hoverData }; | ||
| 427 | + }) | ||
| 428 | + .on('mouseleave', function() { | ||
| 429 | + bars | ||
| 430 | + .attr('fill', (d, i) => i === 0 ? colorFirst : colorDefault) | ||
| 431 | + .attr('stroke', (d, i) => i === 0 ? chartColors.barStroke : chartColors.barStrokeDefault) | ||
| 432 | + .attr('stroke-width', (d, i) => i === 0 ? 1 : 0.5); | ||
| 433 | + hoverData[key] = null; | ||
| 434 | + hoverData = { ...hoverData }; | ||
| 435 | + }); | ||
| 436 | + } | ||
| 437 | + | ||
| 438 | + function getDisplayItem(key, data) { | ||
| 439 | + const hover = hoverData[key]; | ||
| 440 | + if (hover) return hover; | ||
| 441 | + return data[0] || null; | ||
| 442 | + } | ||
| 443 | + | ||
| 444 | + // Dimensiones del gráfico | ||
| 445 | + const chartMargin = { top: 10, right: 10, bottom: 24, left: 48 }; | ||
| 446 | + const chartHeight = 200; | ||
| 447 | + let chartWidth = $state(600); | ||
| 448 | + let chartRefA = $state(null); | ||
| 449 | + let chartRefB = $state(null); | ||
| 450 | + let chartContainerEl = $derived(chartRefA || chartRefB); | ||
| 451 | + | ||
| 452 | + let innerW = $derived(Math.max(chartWidth - chartMargin.left - chartMargin.right, 0)); | ||
| 453 | + let innerH = $derived(chartHeight - chartMargin.top - chartMargin.bottom); | ||
| 454 | + | ||
| 455 | + // Escalas D3 para gastos | ||
| 456 | + let xScaleGastos = $derived.by(() => { | ||
| 457 | + if (historiaGastos.length === 0) return null; | ||
| 458 | + return d3.scaleLinear() | ||
| 459 | + .domain(d3.extent(historiaGastos, d => d.gestion)) | ||
| 460 | + .range([0, innerW]); | ||
| 461 | + }); | ||
| 462 | + | ||
| 463 | + let yScaleGastos = $derived.by(() => { | ||
| 464 | + if (historiaGastos.length === 0) return null; | ||
| 465 | + return d3.scaleLinear() | ||
| 466 | + .domain([0, d3.max(historiaGastos, d => d.devengado) * 1.1]) | ||
| 467 | + .range([innerH, 0]) | ||
| 468 | + .nice(); | ||
| 469 | + }); | ||
| 470 | + | ||
| 471 | + let lineGastos = $derived.by(() => { | ||
| 472 | + if (!xScaleGastos || !yScaleGastos) return ''; | ||
| 473 | + const gen = d3.line() | ||
| 474 | + .x(d => xScaleGastos(d.gestion)) | ||
| 475 | + .y(d => yScaleGastos(d.devengado)) | ||
| 476 | + .curve(d3.curveCatmullRom); | ||
| 477 | + return gen(historiaGastos) || ''; | ||
| 478 | + }); | ||
| 479 | + | ||
| 480 | + let areaGastos = $derived.by(() => { | ||
| 481 | + if (!xScaleGastos || !yScaleGastos) return ''; | ||
| 482 | + const gen = d3.area() | ||
| 483 | + .x(d => xScaleGastos(d.gestion)) | ||
| 484 | + .y0(innerH) | ||
| 485 | + .y1(d => yScaleGastos(d.devengado)) | ||
| 486 | + .curve(d3.curveCatmullRom); | ||
| 487 | + return gen(historiaGastos) || ''; | ||
| 488 | + }); | ||
| 489 | + | ||
| 490 | + let yTicksGastos = $derived.by(() => { | ||
| 491 | + if (!yScaleGastos) return []; | ||
| 492 | + return yScaleGastos.ticks(4); | ||
| 493 | + }); | ||
| 494 | + | ||
| 495 | + // Escalas D3 para ingresos | ||
| 496 | + let xScaleIngresos = $derived.by(() => { | ||
| 497 | + if (historiaIngresos.length === 0) return null; | ||
| 498 | + return d3.scaleLinear() | ||
| 499 | + .domain(d3.extent(historiaIngresos, d => d.gestion)) | ||
| 500 | + .range([0, innerW]); | ||
| 501 | + }); | ||
| 502 | + | ||
| 503 | + let yScaleIngresos = $derived.by(() => { | ||
| 504 | + if (historiaIngresos.length === 0) return null; | ||
| 505 | + return d3.scaleLinear() | ||
| 506 | + .domain([0, d3.max(historiaIngresos, d => d.devengado) * 1.1]) | ||
| 507 | + .range([innerH, 0]) | ||
| 508 | + .nice(); | ||
| 509 | + }); | ||
| 510 | + | ||
| 511 | + let lineIngresos = $derived.by(() => { | ||
| 512 | + if (!xScaleIngresos || !yScaleIngresos) return ''; | ||
| 513 | + const gen = d3.line() | ||
| 514 | + .x(d => xScaleIngresos(d.gestion)) | ||
| 515 | + .y(d => yScaleIngresos(d.devengado)) | ||
| 516 | + .curve(d3.curveCatmullRom); | ||
| 517 | + return gen(historiaIngresos) || ''; | ||
| 518 | + }); | ||
| 519 | + | ||
| 520 | + let areaIngresos = $derived.by(() => { | ||
| 521 | + if (!xScaleIngresos || !yScaleIngresos) return ''; | ||
| 522 | + const gen = d3.area() | ||
| 523 | + .x(d => xScaleIngresos(d.gestion)) | ||
| 524 | + .y0(innerH) | ||
| 525 | + .y1(d => yScaleIngresos(d.devengado)) | ||
| 526 | + .curve(d3.curveCatmullRom); | ||
| 527 | + return gen(historiaIngresos) || ''; | ||
| 528 | + }); | ||
| 529 | + | ||
| 530 | + let yTicksIngresos = $derived.by(() => { | ||
| 531 | + if (!yScaleIngresos) return []; | ||
| 532 | + return yScaleIngresos.ticks(4); | ||
| 533 | + }); | ||
| 534 | + | ||
| 535 | + function renderizarTodasLasBarras() { | ||
| 536 | + setTimeout(() => { | ||
| 537 | + clasificadoresGasto.forEach(({ key, data }) => { | ||
| 538 | + if (barContainers[key]) renderizarBarras(key, data); | ||
| 539 | + }); | ||
| 540 | + clasificadoresIngreso.forEach(({ key, data }) => { | ||
| 541 | + if (barContainers[key]) renderizarBarras(key, data); | ||
| 542 | + }); | ||
| 543 | + }, 100); | ||
| 544 | + } | ||
| 545 | + | ||
| 546 | + $effect(() => { | ||
| 547 | + clasificadoresGasto; | ||
| 548 | + clasificadoresIngreso; | ||
| 549 | + tick().then(() => renderizarTodasLasBarras()); | ||
| 550 | + }); | ||
| 551 | + | ||
| 552 | + | ||
| 553 | + onMount(() => { | ||
| 554 | + cargarMapa(codigoSeleccionado); | ||
| 555 | + const observer = new MutationObserver(() => { renderizarTodasLasBarras(); }); | ||
| 556 | + observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] }); | ||
| 557 | + | ||
| 558 | + function handleClickOutside(e) { | ||
| 559 | + if (gestionDropdownOpen && !e.target.closest('.gestion-selector')) { | ||
| 560 | + gestionDropdownOpen = false; | ||
| 561 | + } | ||
| 562 | + } | ||
| 563 | + document.addEventListener('click', handleClickOutside); | ||
| 564 | + | ||
| 565 | + return () => { observer.disconnect(); document.removeEventListener('click', handleClickOutside); }; | ||
| 566 | + }); | ||
| 567 | +</script> | ||
| 568 | + | ||
| 569 | +<svelte:head> | ||
| 570 | + <title>{nombre} | Presupuesto Público</title> | ||
| 571 | +</svelte:head> | ||
| 572 | + | ||
| 573 | +{#if cargando} | ||
| 574 | + <div class="dashboard"> | ||
| 575 | + <p>Cargando datos...</p> | ||
| 576 | + </div> | ||
| 577 | +{:else} | ||
| 578 | +<div class="dashboard"> | ||
| 579 | + <div class="nav-spacer"></div> | ||
| 580 | + <!-- Header sticky --> | ||
| 581 | + <header class="sticky-header"> | ||
| 582 | + <div class="sticky-row"> | ||
| 583 | + <div class="sticky-left"> | ||
| 584 | + <h1 class="entidad-nombre">{nombre}</h1> | ||
| 585 | + {#if nombrePadre} | ||
| 586 | + <span class="entidad-meta-text">{nombrePadre}</span> | ||
| 587 | + {/if} | ||
| 588 | + <div class="entidad-search" class:focused={searchOpen || searchQuery.length > 0}> | ||
| 589 | + <svg class="search-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | ||
| 590 | + <circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/> | ||
| 591 | + </svg> | ||
| 592 | + <input | ||
| 593 | + type="text" | ||
| 594 | + placeholder="Buscar otra ubicación..." | ||
| 595 | + bind:value={searchQuery} | ||
| 596 | + oninput={onSearchInput} | ||
| 597 | + onkeydown={onSearchKeydown} | ||
| 598 | + onblur={closeSearch} | ||
| 599 | + onfocus={() => { if (searchResults.length > 0) searchOpen = true; }} | ||
| 600 | + /> | ||
| 601 | + {#if searchQuery} | ||
| 602 | + <button class="search-clear" onclick={() => { searchQuery = ''; searchResults = []; searchOpen = false; }}> | ||
| 603 | + <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | ||
| 604 | + <line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/> | ||
| 605 | + </svg> | ||
| 606 | + </button> | ||
| 607 | + {/if} | ||
| 608 | + {#if searchOpen || (searchQuery.length >= 2 && searchLoading)} | ||
| 609 | + <div class="search-dropdown"> | ||
| 610 | + {#if searchLoading} | ||
| 611 | + <div class="search-msg">Buscando...</div> | ||
| 612 | + {:else if searchResults.length === 0} | ||
| 613 | + <div class="search-msg">Sin resultados</div> | ||
| 614 | + {:else} | ||
| 615 | + {#each searchResults as item, i} | ||
| 616 | + <button | ||
| 617 | + class="search-item" | ||
| 618 | + class:selected={searchSelectedIdx === i} | ||
| 619 | + onmousedown={() => navigateToEntity(item)} | ||
| 620 | + onmouseenter={() => { searchSelectedIdx = i; }} | ||
| 621 | + > | ||
| 622 | + <span class="item-name">{@html item.highlight}</span> | ||
| 623 | + {#if item.departamento} | ||
| 624 | + <span class="item-meta">{item.departamento} · {item.provincia}</span> | ||
| 625 | + {/if} | ||
| 626 | + </button> | ||
| 627 | + {/each} | ||
| 628 | + {/if} | ||
| 629 | + </div> | ||
| 630 | + {/if} | ||
| 631 | + </div> | ||
| 632 | + </div> | ||
| 633 | + </div> | ||
| 634 | + </header> | ||
| 635 | + | ||
| 636 | + <!-- Breadcrumb --> | ||
| 637 | + <nav class="breadcrumb"> | ||
| 638 | + <a href="/">Inicio</a> | ||
| 639 | + <span class="sep">/</span> | ||
| 640 | + <a href="/ubicacion">Ubicación geográfica</a> | ||
| 641 | + <span class="sep">/</span> | ||
| 642 | + <span>{nombre}</span> | ||
| 643 | + </nav> | ||
| 644 | + | ||
| 645 | + <!-- Historia temporal (todos los años) --> | ||
| 646 | + <section class="seccion"> | ||
| 647 | + <div class="seccion-titulo-row"> | ||
| 648 | + <h2 class="seccion-titulo">Gasto público ejecutado en esta geografía</h2> | ||
| 649 | + <div class="percapita-toggle"> | ||
| 650 | + <button class="percapita-btn" class:active={!modoPerCapita} onclick={() => { modoPerCapita = false; }}>Total</button> | ||
| 651 | + <button class="percapita-btn" class:active={modoPerCapita} onclick={() => { modoPerCapita = true; }}>Per cápita</button> | ||
| 652 | + </div> | ||
| 653 | + </div> | ||
| 654 | + <div class="historia-mapa-grid"> | ||
| 655 | + {#if tieneIngresos} | ||
| 656 | + <div class="historia-card" bind:this={chartRefA}> | ||
| 657 | + <div class="historia-header"> | ||
| 658 | + <span class="historia-label">Ingresos</span> | ||
| 659 | + {#if historiaIngresos.length > 0} | ||
| 660 | + <span class="historia-monto ingresos"> | ||
| 661 | + {#if hoveredIngresos} | ||
| 662 | + {hoveredIngresos.gestion} · {fmt(hoveredIngresos.devengado)} {unidadMonto} | ||
| 663 | + {:else} | ||
| 664 | + {#if modoPerCapita} | ||
| 665 | + {formatearPerCapita(perCapitaGlobal('ingresos'))} Bs por persona al año · promedio del periodo | ||
| 666 | + {:else} | ||
| 667 | + {formatearMonto(historiaIngresos.reduce((s, d) => s + d.devengado, 0))} de Bolivianos | ||
| 668 | + {/if} | ||
| 669 | + {/if} | ||
| 670 | + </span> | ||
| 671 | + {/if} | ||
| 672 | + </div> | ||
| 673 | + {#if xScaleIngresos && yScaleIngresos} | ||
| 674 | + <svg class="historia-svg" viewBox="0 0 {chartWidth} {chartHeight}" preserveAspectRatio="xMidYMid meet" | ||
| 675 | + onmouseleave={() => { hoveredIngresos = null; }}> | ||
| 676 | + <g transform="translate({chartMargin.left},{chartMargin.top})"> | ||
| 677 | + {#each yTicksIngresos as tick} | ||
| 678 | + <line x1="0" x2={innerW} y1={yScaleIngresos(tick)} y2={yScaleIngresos(tick)} class="grid-line" /> | ||
| 679 | + <text x="-8" y={yScaleIngresos(tick)} class="axis-label y-label">{formatearMontoCorto(tick)}</text> | ||
| 680 | + {/each} | ||
| 681 | + <path d={areaIngresos} class="area-path" /> | ||
| 682 | + <path d={lineIngresos} class="line-path" fill="none" stroke-width="1.5" /> | ||
| 683 | + {#each historiaIngresos as d} | ||
| 684 | + <circle cx={xScaleIngresos(d.gestion)} cy={yScaleIngresos(d.devengado)} | ||
| 685 | + r={hoveredIngresos?.gestion === d.gestion ? 4 : 2} class="dot" class:active={hoveredIngresos?.gestion === d.gestion} /> | ||
| 686 | + {/each} | ||
| 687 | + {#each historiaIngresos as d} | ||
| 688 | + <rect x={xScaleIngresos(d.gestion) - innerW / historiaIngresos.length / 2} y="0" | ||
| 689 | + width={innerW / historiaIngresos.length} height={innerH} | ||
| 690 | + fill="transparent" | ||
| 691 | + onmouseenter={() => { hoveredIngresos = d; }} | ||
| 692 | + /> | ||
| 693 | + {/each} | ||
| 694 | + {#if hoveredIngresos && xScaleIngresos} | ||
| 695 | + <line x1={xScaleIngresos(hoveredIngresos.gestion)} x2={xScaleIngresos(hoveredIngresos.gestion)} | ||
| 696 | + y1="0" y2={innerH} class="hover-line" /> | ||
| 697 | + <text x={xScaleIngresos(hoveredIngresos.gestion)} y={innerH + 16} class="axis-label x-label active">{hoveredIngresos.gestion}</text> | ||
| 698 | + {:else} | ||
| 699 | + {#each historiaIngresos as d, i} | ||
| 700 | + {#if i === 0 || i === historiaIngresos.length - 1 || i === Math.floor(historiaIngresos.length / 2)} | ||
| 701 | + <text x={xScaleIngresos(d.gestion)} y={innerH + 16} class="axis-label x-label">{d.gestion}</text> | ||
| 702 | + {/if} | ||
| 703 | + {/each} | ||
| 704 | + {/if} | ||
| 705 | + </g> | ||
| 706 | + </svg> | ||
| 707 | + {/if} | ||
| 708 | + </div> | ||
| 709 | + {/if} | ||
| 710 | + <div class="historia-card" bind:this={chartRefB}> | ||
| 711 | + <div class="historia-header"> | ||
| 712 | + <span class="historia-label">Gastos</span> | ||
| 713 | + {#if historiaGastos.length > 0} | ||
| 714 | + <span class="historia-monto gastos"> | ||
| 715 | + {#if hoveredGastos} | ||
| 716 | + {hoveredGastos.gestion} · {fmt(hoveredGastos.devengado)} {unidadMonto} | ||
| 717 | + {:else} | ||
| 718 | + {#if modoPerCapita} | ||
| 719 | + {formatearPerCapita(perCapitaGlobal('gastos'))} Bs por persona al año · promedio del periodo | ||
| 720 | + {:else} | ||
| 721 | + {formatearMonto(historiaGastos.reduce((s, d) => s + d.devengado, 0))} de Bolivianos | ||
| 722 | + {/if} | ||
| 723 | + {/if} | ||
| 724 | + </span> | ||
| 725 | + {/if} | ||
| 726 | + </div> | ||
| 727 | + {#if xScaleGastos && yScaleGastos} | ||
| 728 | + <svg class="historia-svg" width="100%" height={chartHeight} viewBox="0 0 {chartWidth} {chartHeight}" | ||
| 729 | + onmouseleave={() => { hoveredGastos = null; }}> | ||
| 730 | + <g transform="translate({chartMargin.left},{chartMargin.top})"> | ||
| 731 | + {#each yTicksGastos as tick} | ||
| 732 | + <line x1="0" x2={innerW} y1={yScaleGastos(tick)} y2={yScaleGastos(tick)} class="grid-line" /> | ||
| 733 | + <text x="-8" y={yScaleGastos(tick)} class="axis-label y-label">{formatearMontoCorto(tick)}</text> | ||
| 734 | + {/each} | ||
| 735 | + <path d={areaGastos} class="area-path" /> | ||
| 736 | + <path d={lineGastos} class="line-path" fill="none" stroke-width="1.5" /> | ||
| 737 | + {#each historiaGastos as d} | ||
| 738 | + <circle cx={xScaleGastos(d.gestion)} cy={yScaleGastos(d.devengado)} | ||
| 739 | + r={hoveredGastos?.gestion === d.gestion ? 4 : 2} class="dot" class:active={hoveredGastos?.gestion === d.gestion} /> | ||
| 740 | + {/each} | ||
| 741 | + {#each historiaGastos as d} | ||
| 742 | + <rect x={xScaleGastos(d.gestion) - innerW / historiaGastos.length / 2} y="0" | ||
| 743 | + width={innerW / historiaGastos.length} height={innerH} | ||
| 744 | + fill="transparent" | ||
| 745 | + onmouseenter={() => { hoveredGastos = d; }} | ||
| 746 | + /> | ||
| 747 | + {/each} | ||
| 748 | + {#if hoveredGastos && xScaleGastos} | ||
| 749 | + <line x1={xScaleGastos(hoveredGastos.gestion)} x2={xScaleGastos(hoveredGastos.gestion)} | ||
| 750 | + y1="0" y2={innerH} class="hover-line" /> | ||
| 751 | + <text x={xScaleGastos(hoveredGastos.gestion)} y={innerH + 16} class="axis-label x-label active">{hoveredGastos.gestion}</text> | ||
| 752 | + {:else} | ||
| 753 | + {#each historiaGastos as d, i} | ||
| 754 | + {#if i === 0 || i === historiaGastos.length - 1 || i === Math.floor(historiaGastos.length / 2)} | ||
| 755 | + <text x={xScaleGastos(d.gestion)} y={innerH + 16} class="axis-label x-label">{d.gestion}</text> | ||
| 756 | + {/if} | ||
| 757 | + {/each} | ||
| 758 | + {/if} | ||
| 759 | + </g> | ||
| 760 | + </svg> | ||
| 761 | + {/if} | ||
| 762 | + </div> | ||
| 763 | + <!-- Mapa --> | ||
| 764 | + <a href="/ubicacion" class="mapa-card" style="text-decoration:none;" | ||
| 765 | + data-sveltekit-preload-data="hover" | ||
| 766 | + onmouseenter={() => { mapaHovered = true; }} | ||
| 767 | + onmouseleave={() => { mapaHovered = false; }} | ||
| 768 | + > | ||
| 769 | + {#if mapaBoliviaPath} | ||
| 770 | + <svg viewBox="0 0 200 240" class="mapa-svg" style="cursor:pointer;"> | ||
| 771 | + <path d={mapaBoliviaPath} class="mapa-pais" /> | ||
| 772 | + <g class="mapa-capas-base" class:ocultar={mapaHovered}> | ||
| 773 | + {#if mapaDepartamentoPath} | ||
| 774 | + <path d={mapaDepartamentoPath} class="mapa-departamento" /> | ||
| 775 | + {/if} | ||
| 776 | + {#if mapaMunicipioPath} | ||
| 777 | + <path d={mapaMunicipioPath} class="mapa-municipio" /> | ||
| 778 | + {/if} | ||
| 779 | + </g> | ||
| 780 | + {#if mapaAllMunisPaths.length > 0} | ||
| 781 | + <g class="mapa-munis-layer" class:visible={mapaHovered}> | ||
| 782 | + {#each mapaAllMunisPaths as m} | ||
| 783 | + <path d={m.path} class="mapa-muni-hover" /> | ||
| 784 | + {/each} | ||
| 785 | + </g> | ||
| 786 | + {/if} | ||
| 787 | + </svg> | ||
| 788 | + {/if} | ||
| 789 | + <span class="mapa-label">{nombre}</span> | ||
| 790 | + {#if poblacionMap[gestionSeleccionada]} | ||
| 791 | + <span class="mapa-poblacion">{poblacionMap[gestionSeleccionada].toLocaleString('es-BO')} hab. ({gestionSeleccionada})</span> | ||
| 792 | + {/if} | ||
| 793 | + <span class="mapa-cta"> | ||
| 794 | + <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"> | ||
| 795 | + <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/> | ||
| 796 | + </svg> | ||
| 797 | + Comparar municipios | ||
| 798 | + </span> | ||
| 799 | + </a> | ||
| 800 | + </div> | ||
| 801 | + </section> | ||
| 802 | + | ||
| 803 | + <!-- Sección por año --> | ||
| 804 | + <section class="seccion seccion-anual"> | ||
| 805 | + <h2 class="seccion-titulo">Gestión | ||
| 806 | + <div class="gestion-selector"> | ||
| 807 | + <button class="gestion-btn" onclick={() => { gestionDropdownOpen = !gestionDropdownOpen; }}> | ||
| 808 | + <span>{gestionSeleccionada}</span> | ||
| 809 | + <svg class="gestion-chevron" class:open={gestionDropdownOpen} width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | ||
| 810 | + <path d="M6 9l6 6 6-6"/> | ||
| 811 | + </svg> | ||
| 812 | + </button> | ||
| 813 | + {#if gestionDropdownOpen} | ||
| 814 | + <div class="gestion-dropdown"> | ||
| 815 | + {#each gestiones() as g} | ||
| 816 | + <button | ||
| 817 | + class="gestion-option" | ||
| 818 | + class:active={gestionSeleccionada === g} | ||
| 819 | + onclick={() => { gestionSeleccionada = g; gestionDropdownOpen = false; }} | ||
| 820 | + > | ||
| 821 | + {g} | ||
| 822 | + </button> | ||
| 823 | + {/each} | ||
| 824 | + </div> | ||
| 825 | + {/if} | ||
| 826 | + </div> | ||
| 827 | + </h2> | ||
| 828 | + | ||
| 829 | + <!-- Ranking (ancho completo, barras reales) --> | ||
| 830 | + {#if rankingGastos()} | ||
| 831 | + {@const r = rankingGastos()} | ||
| 832 | + {@const total = parseInt(r.total) || 342} | ||
| 833 | + {@const barCount = Math.min(total, 342)} | ||
| 834 | + {@const barIdx = Math.max(0, Math.min(barCount - 1, barCount - Math.round((r.posicion / total) * barCount)))} | ||
| 835 | + <div class="ranking-card ranking-card-full"> | ||
| 836 | + <div class="ranking-headline"> | ||
| 837 | + <span class="ranking-pos-big">#{r.posicion}</span> | ||
| 838 | + <span class="ranking-pos-context">de <span class="ranking-total-num">{total}</span> geografías · {r.posicion <= total / 2 ? 'entre las que más reciben gasto' : 'entre las que menos reciben gasto'}</span> | ||
| 839 | + </div> | ||
| 840 | + <div class="ranking-bars"> | ||
| 841 | + {#each Array(barCount) as _, i} | ||
| 842 | + <div class="ranking-bar-tick"></div> | ||
| 843 | + {/each} | ||
| 844 | + <div class="ranking-marker" style="left: {(barIdx / barCount) * 100}%"></div> | ||
| 845 | + </div> | ||
| 846 | + <div class="ranking-extremos"> | ||
| 847 | + <span class="ranking-extremo">Menos gasto</span> | ||
| 848 | + <span class="ranking-extremo">Más gasto</span> | ||
| 849 | + </div> | ||
| 850 | + </div> | ||
| 851 | + {/if} | ||
| 852 | + | ||
| 853 | + <!-- Composición del gasto --> | ||
| 854 | + {#if clasificadoresGasto.length > 0} | ||
| 855 | + <div class="seccion-sub"> | ||
| 856 | + <h3 class="seccion-subtitulo">¿En qué gasta?</h3> | ||
| 857 | + <div class="clasificadores-grid"> | ||
| 858 | + {#each clasificadoresGasto as { key, label, data }} | ||
| 859 | + {@const displayItem = getDisplayItem(key, data)} | ||
| 860 | + {@const totalClasif = getTotal(data)} | ||
| 861 | + {@const pct = totalClasif > 0 && displayItem ? Math.round((displayItem.monto / totalClasif) * 100) : 0} | ||
| 862 | + <div class="clasificador-card"> | ||
| 863 | + <div class="clasificador-header"> | ||
| 864 | + <div class="clasificador-info"> | ||
| 865 | + <div class="clasificador-nombres"> | ||
| 866 | + <span class="clasificador-padre">{displayItem?.padre || ''}</span> | ||
| 867 | + <span class="clasificador-nombre">{displayItem?.nombre || ''}</span> | ||
| 868 | + </div> | ||
| 869 | + <div class="clasificador-valores"> | ||
| 870 | + <span class="clasificador-monto">{fmt(displayItem?.monto || 0)} {unidadMonto}</span> | ||
| 871 | + <span class="clasificador-pct">{pct}%</span> | ||
| 872 | + </div> | ||
| 873 | + </div> | ||
| 874 | + <span class="clasificador-label">{label}</span> | ||
| 875 | + </div> | ||
| 876 | + <div class="barra-container" bind:this={barContainers[key]}></div> | ||
| 877 | + </div> | ||
| 878 | + {/each} | ||
| 879 | + </div> | ||
| 880 | + </div> | ||
| 881 | + {/if} | ||
| 882 | + | ||
| 883 | + <!-- Origen del dinero --> | ||
| 884 | + {#if tieneIngresos && clasificadoresIngreso.length > 0} | ||
| 885 | + <div class="seccion-sub"> | ||
| 886 | + <h3 class="seccion-subtitulo">Fuentes de ingreso</h3> | ||
| 887 | + <div class="clasificadores-grid"> | ||
| 888 | + {#each clasificadoresIngreso as { key, label, data }} | ||
| 889 | + {@const displayItem = getDisplayItem(key, data)} | ||
| 890 | + {@const totalClasif = getTotal(data)} | ||
| 891 | + {@const pct = totalClasif > 0 && displayItem ? Math.round((displayItem.monto / totalClasif) * 100) : 0} | ||
| 892 | + <div class="clasificador-card"> | ||
| 893 | + <div class="clasificador-header"> | ||
| 894 | + <div class="clasificador-info"> | ||
| 895 | + <div class="clasificador-nombres"> | ||
| 896 | + <span class="clasificador-padre">{displayItem?.padre || ''}</span> | ||
| 897 | + <span class="clasificador-nombre">{displayItem?.nombre || ''}</span> | ||
| 898 | + </div> | ||
| 899 | + <div class="clasificador-valores"> | ||
| 900 | + <span class="clasificador-monto">{fmt(displayItem?.monto || 0)} {unidadMonto}</span> | ||
| 901 | + <span class="clasificador-pct">{pct}%</span> | ||
| 902 | + </div> | ||
| 903 | + </div> | ||
| 904 | + <span class="clasificador-label">{label}</span> | ||
| 905 | + </div> | ||
| 906 | + <div class="barra-container" bind:this={barContainers[key]}></div> | ||
| 907 | + </div> | ||
| 908 | + {/each} | ||
| 909 | + </div> | ||
| 910 | + </div> | ||
| 911 | + {/if} | ||
| 912 | + | ||
| 913 | + </section> | ||
| 914 | +</div> | ||
| 915 | +{/if} | ||
| 916 | + | ||
| 917 | +<style> | ||
| 918 | + .dashboard { | ||
| 919 | + min-height: 100vh; | ||
| 920 | + background: var(--theme-body); | ||
| 921 | + color: var(--theme-texto); | ||
| 922 | + padding: 2rem; | ||
| 923 | + padding-top: 0; | ||
| 924 | + font-family: var(--font-sans); | ||
| 925 | + } | ||
| 926 | + | ||
| 927 | + .dashboard > :not(.nav-spacer) { | ||
| 928 | + max-width: 1200px; | ||
| 929 | + margin-left: auto; | ||
| 930 | + margin-right: auto; | ||
| 931 | + } | ||
| 932 | + | ||
| 933 | + :global(html:not(.dark)) .dashboard { | ||
| 934 | + background: #f5f5f7; | ||
| 935 | + } | ||
| 936 | + | ||
| 937 | + /* Spacer para empujar debajo del navbar fixed */ | ||
| 938 | + .nav-spacer { | ||
| 939 | + height: 3.5rem; | ||
| 940 | + } | ||
| 941 | + | ||
| 942 | + .sticky-header { | ||
| 943 | + position: sticky; | ||
| 944 | + top: 0; | ||
| 945 | + z-index: 50; | ||
| 946 | + padding: 0.75rem 2rem; | ||
| 947 | + margin: 0 -2rem 1rem -2rem; | ||
| 948 | + background: var(--theme-body); | ||
| 949 | + border-bottom: 1px solid var(--theme-borde); | ||
| 950 | + } | ||
| 951 | + | ||
| 952 | + :global(html:not(.dark)) .sticky-header { | ||
| 953 | + background: #f5f5f7; | ||
| 954 | + } | ||
| 955 | + | ||
| 956 | + .sticky-row { | ||
| 957 | + display: flex; | ||
| 958 | + align-items: center; | ||
| 959 | + gap: 1rem; | ||
| 960 | + } | ||
| 961 | + | ||
| 962 | + .sticky-left { | ||
| 963 | + display: flex; | ||
| 964 | + align-items: center; | ||
| 965 | + gap: 0.75rem; | ||
| 966 | + min-width: 0; | ||
| 967 | + flex: 1; | ||
| 968 | + } | ||
| 969 | + | ||
| 970 | + .entidad-nombre { | ||
| 971 | + font-size: 1.4rem; | ||
| 972 | + font-weight: 700; | ||
| 973 | + color: var(--theme-titulo); | ||
| 974 | + margin: 0; | ||
| 975 | + line-height: 1.2; | ||
| 976 | + white-space: nowrap; | ||
| 977 | + overflow: hidden; | ||
| 978 | + text-overflow: ellipsis; | ||
| 979 | + } | ||
| 980 | + | ||
| 981 | + .entidad-meta-text { | ||
| 982 | + font-size: 0.75rem; | ||
| 983 | + color: var(--theme-texto); | ||
| 984 | + opacity: 0.6; | ||
| 985 | + white-space: nowrap; | ||
| 986 | + } | ||
| 987 | + | ||
| 988 | + /* DA titulo jerárquico */ | ||
| 989 | + .da-titulo { | ||
| 990 | + display: flex; | ||
| 991 | + align-items: baseline; | ||
| 992 | + gap: 0.4rem; | ||
| 993 | + min-width: 0; | ||
| 994 | + } | ||
| 995 | + | ||
| 996 | + .entidad-madre-link { | ||
| 997 | + font-size: 0.85rem; | ||
| 998 | + font-weight: 500; | ||
| 999 | + color: var(--theme-texto); | ||
| 1000 | + text-decoration: none; | ||
| 1001 | + opacity: 0.6; | ||
| 1002 | + transition: opacity 0.15s; | ||
| 1003 | + white-space: nowrap; | ||
| 1004 | + } | ||
| 1005 | + | ||
| 1006 | + .entidad-madre-link:hover { | ||
| 1007 | + opacity: 1; | ||
| 1008 | + text-decoration: underline; | ||
| 1009 | + } | ||
| 1010 | + | ||
| 1011 | + .da-separador { | ||
| 1012 | + font-size: 0.85rem; | ||
| 1013 | + color: var(--theme-texto); | ||
| 1014 | + opacity: 0.35; | ||
| 1015 | + } | ||
| 1016 | + | ||
| 1017 | + .da-nombre { | ||
| 1018 | + font-size: 1.1rem; | ||
| 1019 | + font-weight: 700; | ||
| 1020 | + color: var(--theme-titulo); | ||
| 1021 | + white-space: nowrap; | ||
| 1022 | + overflow: hidden; | ||
| 1023 | + text-overflow: ellipsis; | ||
| 1024 | + } | ||
| 1025 | + | ||
| 1026 | + /* Buscador de entidades (estilo ObjetoSearch) */ | ||
| 1027 | + .entidad-search { | ||
| 1028 | + position: relative; | ||
| 1029 | + display: flex; | ||
| 1030 | + align-items: center; | ||
| 1031 | + gap: 0.5rem; | ||
| 1032 | + background: var(--search-bg, #f5f5f5); | ||
| 1033 | + border: 1px solid var(--theme-borde); | ||
| 1034 | + border-radius: 8px; | ||
| 1035 | + padding: 0.5rem 0.75rem; | ||
| 1036 | + transition: box-shadow 0.2s, border-color 0.2s; | ||
| 1037 | + width: 240px; | ||
| 1038 | + flex-shrink: 0; | ||
| 1039 | + } | ||
| 1040 | + | ||
| 1041 | + :global(html.dark) .entidad-search { | ||
| 1042 | + --search-bg: rgba(255, 255, 255, 0.08); | ||
| 1043 | + } | ||
| 1044 | + | ||
| 1045 | + .entidad-search.focused { | ||
| 1046 | + box-shadow: 0 0 0 3px rgba(107, 159, 212, 0.15); | ||
| 1047 | + } | ||
| 1048 | + | ||
| 1049 | + :global(html:not(.dark)) .entidad-search.focused { | ||
| 1050 | + box-shadow: 0 0 0 3px rgba(196, 137, 125, 0.15); | ||
| 1051 | + } | ||
| 1052 | + | ||
| 1053 | + .search-icon { | ||
| 1054 | + color: var(--theme-texto); | ||
| 1055 | + opacity: 0.5; | ||
| 1056 | + flex-shrink: 0; | ||
| 1057 | + } | ||
| 1058 | + | ||
| 1059 | + .entidad-search input { | ||
| 1060 | + flex: 1; | ||
| 1061 | + border: none; | ||
| 1062 | + background: transparent; | ||
| 1063 | + color: var(--theme-titulo); | ||
| 1064 | + font-size: 0.8125rem; | ||
| 1065 | + outline: none; | ||
| 1066 | + min-width: 100px; | ||
| 1067 | + font-family: var(--font-sans); | ||
| 1068 | + } | ||
| 1069 | + | ||
| 1070 | + .entidad-search input::placeholder { | ||
| 1071 | + color: var(--theme-texto); | ||
| 1072 | + opacity: 0.5; | ||
| 1073 | + } | ||
| 1074 | + | ||
| 1075 | + .search-clear { | ||
| 1076 | + display: flex; | ||
| 1077 | + align-items: center; | ||
| 1078 | + justify-content: center; | ||
| 1079 | + padding: 0.25rem; | ||
| 1080 | + border-radius: 4px; | ||
| 1081 | + border: none; | ||
| 1082 | + background: transparent; | ||
| 1083 | + color: var(--theme-texto); | ||
| 1084 | + cursor: pointer; | ||
| 1085 | + transition: background 0.2s; | ||
| 1086 | + } | ||
| 1087 | + | ||
| 1088 | + .search-clear:hover { | ||
| 1089 | + background: var(--theme-borde); | ||
| 1090 | + } | ||
| 1091 | + | ||
| 1092 | + .search-dropdown { | ||
| 1093 | + position: absolute; | ||
| 1094 | + top: calc(100% + 4px); | ||
| 1095 | + left: 0; | ||
| 1096 | + right: 0; | ||
| 1097 | + background: var(--theme-surface); | ||
| 1098 | + border: 1px solid var(--theme-borde); | ||
| 1099 | + border-radius: 8px; | ||
| 1100 | + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); | ||
| 1101 | + max-height: 280px; | ||
| 1102 | + overflow-y: auto; | ||
| 1103 | + z-index: 100; | ||
| 1104 | + min-width: 320px; | ||
| 1105 | + } | ||
| 1106 | + | ||
| 1107 | + .search-msg { | ||
| 1108 | + padding: 0.75rem 1rem; | ||
| 1109 | + font-size: 0.8125rem; | ||
| 1110 | + color: var(--theme-texto); | ||
| 1111 | + opacity: 0.7; | ||
| 1112 | + } | ||
| 1113 | + | ||
| 1114 | + .search-item { | ||
| 1115 | + display: flex; | ||
| 1116 | + flex-direction: column; | ||
| 1117 | + gap: 0.1rem; | ||
| 1118 | + width: 100%; | ||
| 1119 | + padding: 0.625rem 1rem; | ||
| 1120 | + border: none; | ||
| 1121 | + background: transparent; | ||
| 1122 | + text-align: left; | ||
| 1123 | + cursor: pointer; | ||
| 1124 | + transition: background 0.15s; | ||
| 1125 | + font-family: var(--font-sans); | ||
| 1126 | + } | ||
| 1127 | + | ||
| 1128 | + .search-item:hover, | ||
| 1129 | + .search-item.selected { | ||
| 1130 | + background: var(--theme-surface-hover); | ||
| 1131 | + } | ||
| 1132 | + | ||
| 1133 | + .item-name { | ||
| 1134 | + font-size: 0.8125rem; | ||
| 1135 | + color: var(--theme-titulo); | ||
| 1136 | + line-height: 1.3; | ||
| 1137 | + } | ||
| 1138 | + | ||
| 1139 | + .item-name :global(mark) { | ||
| 1140 | + background: rgba(196, 137, 125, 0.25); | ||
| 1141 | + color: inherit; | ||
| 1142 | + border-radius: 2px; | ||
| 1143 | + padding: 0 1px; | ||
| 1144 | + } | ||
| 1145 | + | ||
| 1146 | + :global(html.dark) .item-name :global(mark) { | ||
| 1147 | + background: rgba(107, 159, 212, 0.3); | ||
| 1148 | + } | ||
| 1149 | + | ||
| 1150 | + .item-meta { | ||
| 1151 | + font-size: 0.7rem; | ||
| 1152 | + color: var(--theme-texto); | ||
| 1153 | + opacity: 0.5; | ||
| 1154 | + } | ||
| 1155 | + | ||
| 1156 | + | ||
| 1157 | + /* Breadcrumb */ | ||
| 1158 | + .breadcrumb { | ||
| 1159 | + font-size: 0.75rem; | ||
| 1160 | + color: var(--theme-texto); | ||
| 1161 | + opacity: 0.6; | ||
| 1162 | + margin-bottom: 1.5rem; | ||
| 1163 | + } | ||
| 1164 | + | ||
| 1165 | + .breadcrumb a { | ||
| 1166 | + color: var(--theme-texto); | ||
| 1167 | + text-decoration: none; | ||
| 1168 | + } | ||
| 1169 | + | ||
| 1170 | + .breadcrumb a:hover { | ||
| 1171 | + text-decoration: underline; | ||
| 1172 | + } | ||
| 1173 | + | ||
| 1174 | + .breadcrumb .sep { | ||
| 1175 | + margin: 0 0.35rem; | ||
| 1176 | + } | ||
| 1177 | + | ||
| 1178 | + /* Toggle per cápita */ | ||
| 1179 | + .seccion-titulo-row { | ||
| 1180 | + display: flex; | ||
| 1181 | + align-items: center; | ||
| 1182 | + justify-content: space-between; | ||
| 1183 | + gap: 1rem; | ||
| 1184 | + margin-bottom: 1rem; | ||
| 1185 | + } | ||
| 1186 | + | ||
| 1187 | + .seccion-titulo-row .seccion-titulo { | ||
| 1188 | + margin-bottom: 0; | ||
| 1189 | + } | ||
| 1190 | + | ||
| 1191 | + .percapita-toggle { | ||
| 1192 | + display: flex; | ||
| 1193 | + gap: 2px; | ||
| 1194 | + background: rgba(255, 255, 255, 0.06); | ||
| 1195 | + border-radius: 8px; | ||
| 1196 | + padding: 3px; | ||
| 1197 | + flex-shrink: 0; | ||
| 1198 | + } | ||
| 1199 | + | ||
| 1200 | + :global(html:not(.dark)) .percapita-toggle { | ||
| 1201 | + background: rgba(0, 0, 0, 0.05); | ||
| 1202 | + } | ||
| 1203 | + | ||
| 1204 | + .percapita-btn { | ||
| 1205 | + padding: 5px 12px; | ||
| 1206 | + font-size: 0.75rem; | ||
| 1207 | + font-weight: 600; | ||
| 1208 | + font-family: var(--font-sans); | ||
| 1209 | + border: none; | ||
| 1210 | + border-radius: 6px; | ||
| 1211 | + cursor: pointer; | ||
| 1212 | + background: transparent; | ||
| 1213 | + color: var(--theme-texto); | ||
| 1214 | + opacity: 0.6; | ||
| 1215 | + transition: all 0.2s; | ||
| 1216 | + } | ||
| 1217 | + | ||
| 1218 | + .percapita-btn:hover { | ||
| 1219 | + opacity: 0.8; | ||
| 1220 | + } | ||
| 1221 | + | ||
| 1222 | + .percapita-btn.active { | ||
| 1223 | + opacity: 1; | ||
| 1224 | + background: rgba(255, 255, 255, 0.12); | ||
| 1225 | + color: var(--theme-titulo); | ||
| 1226 | + } | ||
| 1227 | + | ||
| 1228 | + :global(html:not(.dark)) .percapita-btn.active { | ||
| 1229 | + background: rgba(255, 255, 255, 0.8); | ||
| 1230 | + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08); | ||
| 1231 | + } | ||
| 1232 | + | ||
| 1233 | + /* Historia + Mapa grid */ | ||
| 1234 | + .historia-mapa-grid { | ||
| 1235 | + display: grid; | ||
| 1236 | + grid-template-columns: 1fr 0.4fr; | ||
| 1237 | + gap: 1.5rem; | ||
| 1238 | + } | ||
| 1239 | + | ||
| 1240 | + .ranking-card-full { | ||
| 1241 | + width: 100%; | ||
| 1242 | + margin-bottom: 1.5rem; | ||
| 1243 | + } | ||
| 1244 | + | ||
| 1245 | + .mapa-card { | ||
| 1246 | + background: var(--theme-surface); | ||
| 1247 | + border-radius: 16px; | ||
| 1248 | + padding: 1rem; | ||
| 1249 | + display: flex; | ||
| 1250 | + flex-direction: column; | ||
| 1251 | + align-items: center; | ||
| 1252 | + justify-content: center; | ||
| 1253 | + gap: 0.5rem; | ||
| 1254 | + cursor: pointer; | ||
| 1255 | + transition: box-shadow 0.2s; | ||
| 1256 | + } | ||
| 1257 | + | ||
| 1258 | + .mapa-card:hover { | ||
| 1259 | + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12); | ||
| 1260 | + } | ||
| 1261 | + | ||
| 1262 | + .mapa-svg { | ||
| 1263 | + width: 100%; | ||
| 1264 | + max-width: 200px; | ||
| 1265 | + height: auto; | ||
| 1266 | + margin: 0 auto; | ||
| 1267 | + } | ||
| 1268 | + | ||
| 1269 | + .mapa-pais { | ||
| 1270 | + fill: #e8e6e1; | ||
| 1271 | + stroke: #c4c0b8; | ||
| 1272 | + stroke-width: 0.5; | ||
| 1273 | + } | ||
| 1274 | + | ||
| 1275 | + :global(html.dark) .mapa-pais { | ||
| 1276 | + fill: rgba(255, 255, 255, 0.06); | ||
| 1277 | + stroke: rgba(255, 255, 255, 0.12); | ||
| 1278 | + } | ||
| 1279 | + | ||
| 1280 | + .mapa-departamento { | ||
| 1281 | + fill: #d5d0c8; | ||
| 1282 | + stroke: #b8b3aa; | ||
| 1283 | + stroke-width: 0.5; | ||
| 1284 | + } | ||
| 1285 | + | ||
| 1286 | + :global(html.dark) .mapa-departamento { | ||
| 1287 | + fill: rgba(255, 255, 255, 0.1); | ||
| 1288 | + stroke: rgba(255, 255, 255, 0.15); | ||
| 1289 | + } | ||
| 1290 | + | ||
| 1291 | + .mapa-municipio { | ||
| 1292 | + fill: #c4897d; | ||
| 1293 | + stroke: #a87068; | ||
| 1294 | + stroke-width: 1; | ||
| 1295 | + } | ||
| 1296 | + | ||
| 1297 | + :global(html.dark) .mapa-municipio { | ||
| 1298 | + fill: #D4A574; | ||
| 1299 | + stroke: #b8884e; | ||
| 1300 | + stroke-width: 1; | ||
| 1301 | + } | ||
| 1302 | + | ||
| 1303 | + .mapa-munis-layer { | ||
| 1304 | + opacity: 0; | ||
| 1305 | + transition: opacity 0.4s ease; | ||
| 1306 | + } | ||
| 1307 | + | ||
| 1308 | + .mapa-munis-layer.visible { | ||
| 1309 | + opacity: 1; | ||
| 1310 | + } | ||
| 1311 | + | ||
| 1312 | + .mapa-capas-base { | ||
| 1313 | + transition: opacity 0.3s ease; | ||
| 1314 | + } | ||
| 1315 | + | ||
| 1316 | + .mapa-capas-base.ocultar { | ||
| 1317 | + opacity: 0; | ||
| 1318 | + } | ||
| 1319 | + | ||
| 1320 | + .mapa-muni-hover { | ||
| 1321 | + fill: #e8e6e1; | ||
| 1322 | + stroke: #b8b3aa; | ||
| 1323 | + stroke-width: 0.5; | ||
| 1324 | + } | ||
| 1325 | + | ||
| 1326 | + :global(html.dark) .mapa-muni-hover { | ||
| 1327 | + fill: rgba(255, 255, 255, 0.06); | ||
| 1328 | + stroke: rgba(255, 255, 255, 0.15); | ||
| 1329 | + stroke-width: 0.5; | ||
| 1330 | + } | ||
| 1331 | + | ||
| 1332 | + .mapa-label { | ||
| 1333 | + font-size: 0.75rem; | ||
| 1334 | + font-weight: 600; | ||
| 1335 | + color: var(--theme-titulo); | ||
| 1336 | + text-align: center; | ||
| 1337 | + } | ||
| 1338 | + | ||
| 1339 | + .mapa-poblacion { | ||
| 1340 | + font-size: 0.65rem; | ||
| 1341 | + color: var(--theme-texto); | ||
| 1342 | + opacity: 0.6; | ||
| 1343 | + text-align: center; | ||
| 1344 | + } | ||
| 1345 | + | ||
| 1346 | + .mapa-cta { | ||
| 1347 | + font-size: 0.7rem; | ||
| 1348 | + color: var(--theme-accent); | ||
| 1349 | + opacity: 0.8; | ||
| 1350 | + text-align: center; | ||
| 1351 | + transition: opacity 0.2s; | ||
| 1352 | + display: flex; | ||
| 1353 | + align-items: center; | ||
| 1354 | + gap: 0.25rem; | ||
| 1355 | + } | ||
| 1356 | + | ||
| 1357 | + .mapa-card:hover .mapa-cta { | ||
| 1358 | + opacity: 1; | ||
| 1359 | + } | ||
| 1360 | + | ||
| 1361 | + /* Ranking visual - barritas con marcador animado */ | ||
| 1362 | + .ranking-bars { | ||
| 1363 | + display: flex; | ||
| 1364 | + height: 18px; | ||
| 1365 | + position: relative; | ||
| 1366 | + gap: 0; | ||
| 1367 | + } | ||
| 1368 | + | ||
| 1369 | + .ranking-bar-tick { | ||
| 1370 | + flex: 1; | ||
| 1371 | + height: 100%; | ||
| 1372 | + background: var(--theme-borde); | ||
| 1373 | + opacity: 0.5; | ||
| 1374 | + border-right: 1px solid var(--theme-surface); | ||
| 1375 | + } | ||
| 1376 | + | ||
| 1377 | + :global(html:not(.dark)) .ranking-bar-tick { | ||
| 1378 | + background: #d0cec9; | ||
| 1379 | + opacity: 0.6; | ||
| 1380 | + } | ||
| 1381 | + | ||
| 1382 | + .ranking-marker { | ||
| 1383 | + position: absolute; | ||
| 1384 | + top: -2px; | ||
| 1385 | + bottom: -2px; | ||
| 1386 | + width: 3px; | ||
| 1387 | + background: #c4897d; | ||
| 1388 | + border-radius: 1.5px; | ||
| 1389 | + transition: left 0.5s cubic-bezier(0.4, 0, 0.2, 1); | ||
| 1390 | + pointer-events: none; | ||
| 1391 | + } | ||
| 1392 | + | ||
| 1393 | + :global(html.dark) .ranking-marker { | ||
| 1394 | + background: #D4A574; | ||
| 1395 | + } | ||
| 1396 | + | ||
| 1397 | + .ranking-extremos { | ||
| 1398 | + display: flex; | ||
| 1399 | + justify-content: space-between; | ||
| 1400 | + margin-top: 0.25rem; | ||
| 1401 | + } | ||
| 1402 | + | ||
| 1403 | + .ranking-extremo { | ||
| 1404 | + font-size: 0.6rem; | ||
| 1405 | + color: var(--theme-texto); | ||
| 1406 | + opacity: 0.4; | ||
| 1407 | + } | ||
| 1408 | + | ||
| 1409 | + .ranking-headline { | ||
| 1410 | + display: flex; | ||
| 1411 | + align-items: baseline; | ||
| 1412 | + gap: 0.5rem; | ||
| 1413 | + margin-bottom: 0.75rem; | ||
| 1414 | + } | ||
| 1415 | + | ||
| 1416 | + .ranking-pos-big { | ||
| 1417 | + font-size: 1.5rem; | ||
| 1418 | + font-weight: 700; | ||
| 1419 | + color: var(--theme-titulo); | ||
| 1420 | + line-height: 1; | ||
| 1421 | + } | ||
| 1422 | + | ||
| 1423 | + .ranking-pos-context { | ||
| 1424 | + font-size: 0.8rem; | ||
| 1425 | + color: var(--theme-texto); | ||
| 1426 | + opacity: 0.7; | ||
| 1427 | + } | ||
| 1428 | + | ||
| 1429 | + .ranking-total-num { | ||
| 1430 | + font-weight: 700; | ||
| 1431 | + color: var(--theme-titulo); | ||
| 1432 | + opacity: 1; | ||
| 1433 | + } | ||
| 1434 | + | ||
| 1435 | + /* Secciones */ | ||
| 1436 | + .seccion { | ||
| 1437 | + margin-bottom: 2.5rem; | ||
| 1438 | + } | ||
| 1439 | + | ||
| 1440 | + .seccion-titulo { | ||
| 1441 | + font-size: 0.9rem; | ||
| 1442 | + font-weight: 600; | ||
| 1443 | + color: var(--theme-titulo); | ||
| 1444 | + margin: 0 0 1rem 0; | ||
| 1445 | + display: flex; | ||
| 1446 | + align-items: center; | ||
| 1447 | + gap: 0.75rem; | ||
| 1448 | + } | ||
| 1449 | + | ||
| 1450 | + /* Sección anual */ | ||
| 1451 | + .seccion-anual { | ||
| 1452 | + border-top: 1px solid var(--theme-borde); | ||
| 1453 | + padding-top: 2rem; | ||
| 1454 | + } | ||
| 1455 | + | ||
| 1456 | + /* Dropdown de gestión */ | ||
| 1457 | + .gestion-selector { | ||
| 1458 | + position: relative; | ||
| 1459 | + display: inline-flex; | ||
| 1460 | + } | ||
| 1461 | + | ||
| 1462 | + .gestion-btn { | ||
| 1463 | + display: inline-flex; | ||
| 1464 | + align-items: center; | ||
| 1465 | + gap: 0.375rem; | ||
| 1466 | + padding: 0.125rem 0; | ||
| 1467 | + background: transparent; | ||
| 1468 | + border: none; | ||
| 1469 | + border-bottom: 1.5px dotted var(--theme-texto); | ||
| 1470 | + color: var(--theme-titulo); | ||
| 1471 | + font-size: 1.1rem; | ||
| 1472 | + font-weight: 600; | ||
| 1473 | + cursor: pointer; | ||
| 1474 | + transition: border-color 0.2s; | ||
| 1475 | + font-family: var(--font-sans); | ||
| 1476 | + } | ||
| 1477 | + | ||
| 1478 | + .gestion-btn:hover { | ||
| 1479 | + border-bottom-color: var(--theme-accent); | ||
| 1480 | + } | ||
| 1481 | + | ||
| 1482 | + .gestion-chevron { | ||
| 1483 | + color: var(--theme-texto); | ||
| 1484 | + opacity: 0.5; | ||
| 1485 | + transition: transform 0.2s; | ||
| 1486 | + flex-shrink: 0; | ||
| 1487 | + } | ||
| 1488 | + | ||
| 1489 | + .gestion-chevron.open { | ||
| 1490 | + transform: rotate(180deg); | ||
| 1491 | + } | ||
| 1492 | + | ||
| 1493 | + .gestion-dropdown { | ||
| 1494 | + position: absolute; | ||
| 1495 | + top: calc(100% + 6px); | ||
| 1496 | + left: 0; | ||
| 1497 | + min-width: 100px; | ||
| 1498 | + max-height: 240px; | ||
| 1499 | + overflow-y: auto; | ||
| 1500 | + background: var(--theme-surface); | ||
| 1501 | + border: 1px solid var(--theme-borde); | ||
| 1502 | + border-radius: 10px; | ||
| 1503 | + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); | ||
| 1504 | + z-index: 100; | ||
| 1505 | + } | ||
| 1506 | + | ||
| 1507 | + .gestion-option { | ||
| 1508 | + display: block; | ||
| 1509 | + width: 100%; | ||
| 1510 | + padding: 0.5rem 0.75rem; | ||
| 1511 | + border: none; | ||
| 1512 | + background: transparent; | ||
| 1513 | + color: var(--theme-titulo); | ||
| 1514 | + font-size: 0.8125rem; | ||
| 1515 | + font-weight: 500; | ||
| 1516 | + text-align: left; | ||
| 1517 | + cursor: pointer; | ||
| 1518 | + transition: background 0.15s; | ||
| 1519 | + font-family: var(--font-sans); | ||
| 1520 | + } | ||
| 1521 | + | ||
| 1522 | + .gestion-option:hover { | ||
| 1523 | + background: var(--theme-surface-hover); | ||
| 1524 | + } | ||
| 1525 | + | ||
| 1526 | + .gestion-option.active { | ||
| 1527 | + background: rgba(107, 159, 212, 0.1); | ||
| 1528 | + color: #6B9FD4; | ||
| 1529 | + } | ||
| 1530 | + | ||
| 1531 | + :global(html:not(.dark)) .gestion-option.active { | ||
| 1532 | + background: rgba(90, 157, 191, 0.1); | ||
| 1533 | + color: #3D7A9C; | ||
| 1534 | + } | ||
| 1535 | + | ||
| 1536 | + .seccion-sub { | ||
| 1537 | + margin-top: 1.5rem; | ||
| 1538 | + } | ||
| 1539 | + | ||
| 1540 | + .seccion-subtitulo { | ||
| 1541 | + font-size: 0.9rem; | ||
| 1542 | + font-weight: 600; | ||
| 1543 | + color: var(--theme-titulo); | ||
| 1544 | + margin: 0 0 0.75rem 0; | ||
| 1545 | + opacity: 0.8; | ||
| 1546 | + } | ||
| 1547 | + | ||
| 1548 | + /* Rankings grid */ | ||
| 1549 | + .ranking-grid { | ||
| 1550 | + display: grid; | ||
| 1551 | + grid-template-columns: repeat(2, 1fr); | ||
| 1552 | + gap: 1.5rem; | ||
| 1553 | + margin-bottom: 1.5rem; | ||
| 1554 | + } | ||
| 1555 | + | ||
| 1556 | + .ranking-grid.single { | ||
| 1557 | + grid-template-columns: 1fr; | ||
| 1558 | + max-width: 50%; | ||
| 1559 | + } | ||
| 1560 | + | ||
| 1561 | + .ranking-card { | ||
| 1562 | + background: var(--theme-surface); | ||
| 1563 | + border-radius: 16px; | ||
| 1564 | + padding: 1.25rem 1.5rem; | ||
| 1565 | + } | ||
| 1566 | + | ||
| 1567 | + .ranking-card-label { | ||
| 1568 | + font-size: 0.75rem; | ||
| 1569 | + font-weight: 500; | ||
| 1570 | + color: var(--theme-texto); | ||
| 1571 | + opacity: 0.7; | ||
| 1572 | + display: block; | ||
| 1573 | + margin-bottom: 0.5rem; | ||
| 1574 | + } | ||
| 1575 | + | ||
| 1576 | + /* Historia */ | ||
| 1577 | + .historia-grid { | ||
| 1578 | + display: grid; | ||
| 1579 | + grid-template-columns: repeat(2, 1fr); | ||
| 1580 | + gap: 1.5rem; | ||
| 1581 | + } | ||
| 1582 | + | ||
| 1583 | + .historia-grid.single { | ||
| 1584 | + grid-template-columns: 1fr; | ||
| 1585 | + } | ||
| 1586 | + | ||
| 1587 | + .historia-card { | ||
| 1588 | + background: var(--theme-surface); | ||
| 1589 | + border-radius: 16px; | ||
| 1590 | + padding: 1.25rem 1.5rem; | ||
| 1591 | + display: flex; | ||
| 1592 | + flex-direction: column; | ||
| 1593 | + min-height: 200px; | ||
| 1594 | + } | ||
| 1595 | + | ||
| 1596 | + .historia-header { | ||
| 1597 | + display: flex; | ||
| 1598 | + justify-content: space-between; | ||
| 1599 | + align-items: baseline; | ||
| 1600 | + margin-bottom: 0.75rem; | ||
| 1601 | + } | ||
| 1602 | + | ||
| 1603 | + .historia-label { | ||
| 1604 | + font-size: 0.85rem; | ||
| 1605 | + font-weight: 500; | ||
| 1606 | + color: var(--theme-texto); | ||
| 1607 | + } | ||
| 1608 | + | ||
| 1609 | + .historia-monto { | ||
| 1610 | + font-size: 0.8rem; | ||
| 1611 | + font-weight: 600; | ||
| 1612 | + } | ||
| 1613 | + | ||
| 1614 | + .historia-monto.ingresos, | ||
| 1615 | + .historia-monto.gastos { | ||
| 1616 | + color: #3D7A9C; | ||
| 1617 | + } | ||
| 1618 | + | ||
| 1619 | + :global(html.dark) .historia-monto.ingresos, | ||
| 1620 | + :global(html.dark) .historia-monto.gastos { | ||
| 1621 | + color: #D4A574; | ||
| 1622 | + } | ||
| 1623 | + | ||
| 1624 | + /* SVG chart */ | ||
| 1625 | + .historia-svg { | ||
| 1626 | + display: block; | ||
| 1627 | + overflow: visible; | ||
| 1628 | + cursor: default; | ||
| 1629 | + width: 100%; | ||
| 1630 | + height: 100%; | ||
| 1631 | + flex: 1; | ||
| 1632 | + } | ||
| 1633 | + | ||
| 1634 | + .grid-line { | ||
| 1635 | + stroke: var(--theme-texto); | ||
| 1636 | + stroke-width: 0.5; | ||
| 1637 | + stroke-dasharray: 2 3; | ||
| 1638 | + opacity: 0.15; | ||
| 1639 | + } | ||
| 1640 | + | ||
| 1641 | + .axis-label { | ||
| 1642 | + font-size: 0.55rem; | ||
| 1643 | + fill: var(--theme-texto); | ||
| 1644 | + opacity: 0.6; | ||
| 1645 | + font-family: var(--font-sans); | ||
| 1646 | + } | ||
| 1647 | + | ||
| 1648 | + .y-label { | ||
| 1649 | + text-anchor: end; | ||
| 1650 | + dominant-baseline: middle; | ||
| 1651 | + } | ||
| 1652 | + | ||
| 1653 | + .x-label { | ||
| 1654 | + text-anchor: middle; | ||
| 1655 | + dominant-baseline: hanging; | ||
| 1656 | + } | ||
| 1657 | + | ||
| 1658 | + .area-path { | ||
| 1659 | + fill: rgba(61, 122, 156, 0.4); | ||
| 1660 | + } | ||
| 1661 | + | ||
| 1662 | + .line-path { | ||
| 1663 | + stroke: #3D7A9C; | ||
| 1664 | + opacity: 0.9; | ||
| 1665 | + } | ||
| 1666 | + | ||
| 1667 | + .dot { | ||
| 1668 | + fill: #3D7A9C; | ||
| 1669 | + opacity: 0.9; | ||
| 1670 | + transition: r 0.15s; | ||
| 1671 | + } | ||
| 1672 | + | ||
| 1673 | + .dot.active { | ||
| 1674 | + fill: #3D7A9C; | ||
| 1675 | + } | ||
| 1676 | + | ||
| 1677 | + .hover-line { | ||
| 1678 | + stroke: var(--theme-texto); | ||
| 1679 | + stroke-width: 0.5; | ||
| 1680 | + stroke-dasharray: 3 3; | ||
| 1681 | + opacity: 0.3; | ||
| 1682 | + } | ||
| 1683 | + | ||
| 1684 | + .x-label.active { | ||
| 1685 | + font-weight: 600; | ||
| 1686 | + opacity: 1; | ||
| 1687 | + } | ||
| 1688 | + | ||
| 1689 | + :global(html.dark) .area-path { | ||
| 1690 | + fill: rgba(212, 165, 116, 0.12); | ||
| 1691 | + } | ||
| 1692 | + | ||
| 1693 | + :global(html.dark) .line-path { | ||
| 1694 | + stroke: #D4A574; | ||
| 1695 | + } | ||
| 1696 | + | ||
| 1697 | + :global(html.dark) .dot { | ||
| 1698 | + fill: #D4A574; | ||
| 1699 | + } | ||
| 1700 | + | ||
| 1701 | + :global(html.dark) .dot.active { | ||
| 1702 | + fill: #D4A574; | ||
| 1703 | + } | ||
| 1704 | + | ||
| 1705 | + /* Clasificadores */ | ||
| 1706 | + .clasificadores-grid { | ||
| 1707 | + display: flex; | ||
| 1708 | + flex-direction: column; | ||
| 1709 | + gap: 1rem; | ||
| 1710 | + } | ||
| 1711 | + | ||
| 1712 | + .clasificador-card { | ||
| 1713 | + display: flex; | ||
| 1714 | + flex-direction: column; | ||
| 1715 | + gap: 0.5rem; | ||
| 1716 | + background: var(--theme-surface); | ||
| 1717 | + border-radius: 16px; | ||
| 1718 | + padding: 1.25rem 1.5rem; | ||
| 1719 | + } | ||
| 1720 | + | ||
| 1721 | + .clasificador-header { | ||
| 1722 | + display: flex; | ||
| 1723 | + align-items: baseline; | ||
| 1724 | + justify-content: space-between; | ||
| 1725 | + gap: 1rem; | ||
| 1726 | + } | ||
| 1727 | + | ||
| 1728 | + .clasificador-info { | ||
| 1729 | + display: flex; | ||
| 1730 | + flex-direction: column; | ||
| 1731 | + gap: 0.15rem; | ||
| 1732 | + min-width: 0; | ||
| 1733 | + overflow: hidden; | ||
| 1734 | + } | ||
| 1735 | + | ||
| 1736 | + .clasificador-nombres { | ||
| 1737 | + display: flex; | ||
| 1738 | + align-items: baseline; | ||
| 1739 | + gap: 0.4rem; | ||
| 1740 | + overflow: hidden; | ||
| 1741 | + } | ||
| 1742 | + | ||
| 1743 | + .clasificador-valores { | ||
| 1744 | + display: flex; | ||
| 1745 | + align-items: baseline; | ||
| 1746 | + gap: 0.4rem; | ||
| 1747 | + } | ||
| 1748 | + | ||
| 1749 | + .clasificador-monto { | ||
| 1750 | + font-size: 0.95rem; | ||
| 1751 | + font-weight: 600; | ||
| 1752 | + color: var(--theme-titulo); | ||
| 1753 | + white-space: nowrap; | ||
| 1754 | + flex-shrink: 0; | ||
| 1755 | + } | ||
| 1756 | + | ||
| 1757 | + .clasificador-pct { | ||
| 1758 | + font-size: 0.8rem; | ||
| 1759 | + font-weight: 600; | ||
| 1760 | + color: var(--theme-texto); | ||
| 1761 | + opacity: 0.5; | ||
| 1762 | + white-space: nowrap; | ||
| 1763 | + flex-shrink: 0; | ||
| 1764 | + } | ||
| 1765 | + | ||
| 1766 | + .clasificador-padre { | ||
| 1767 | + font-size: 0.8rem; | ||
| 1768 | + font-weight: 400; | ||
| 1769 | + color: var(--theme-texto); | ||
| 1770 | + opacity: 0.5; | ||
| 1771 | + white-space: nowrap; | ||
| 1772 | + flex-shrink: 0; | ||
| 1773 | + } | ||
| 1774 | + | ||
| 1775 | + .clasificador-nombre { | ||
| 1776 | + font-size: 0.85rem; | ||
| 1777 | + font-weight: 500; | ||
| 1778 | + color: var(--theme-titulo); | ||
| 1779 | + white-space: nowrap; | ||
| 1780 | + overflow: hidden; | ||
| 1781 | + text-overflow: ellipsis; | ||
| 1782 | + } | ||
| 1783 | + | ||
| 1784 | + .clasificador-label { | ||
| 1785 | + font-size: 0.65rem; | ||
| 1786 | + text-transform: uppercase; | ||
| 1787 | + letter-spacing: 0.05em; | ||
| 1788 | + color: var(--theme-texto); | ||
| 1789 | + opacity: 0.6; | ||
| 1790 | + flex-shrink: 0; | ||
| 1791 | + } | ||
| 1792 | + | ||
| 1793 | + .barra-container { | ||
| 1794 | + width: 100%; | ||
| 1795 | + height: 48px; | ||
| 1796 | + border-radius: 4px; | ||
| 1797 | + overflow: hidden; | ||
| 1798 | + } | ||
| 1799 | + | ||
| 1800 | + /* Responsive */ | ||
| 1801 | + @media (max-width: 768px) { | ||
| 1802 | + .dashboard { | ||
| 1803 | + padding: 1rem; | ||
| 1804 | + padding-top: 0.5rem; | ||
| 1805 | + } | ||
| 1806 | + | ||
| 1807 | + .nav-spacer { | ||
| 1808 | + height: 3rem; | ||
| 1809 | + } | ||
| 1810 | + | ||
| 1811 | + .sticky-header { | ||
| 1812 | + margin: 0 -1rem 0.75rem -1rem; | ||
| 1813 | + padding: 0.5rem 1rem; | ||
| 1814 | + } | ||
| 1815 | + | ||
| 1816 | + .sticky-row { | ||
| 1817 | + flex-direction: column; | ||
| 1818 | + align-items: flex-start; | ||
| 1819 | + gap: 0.5rem; | ||
| 1820 | + } | ||
| 1821 | + | ||
| 1822 | + .sticky-left { | ||
| 1823 | + flex-direction: column; | ||
| 1824 | + gap: 0.25rem; | ||
| 1825 | + } | ||
| 1826 | + | ||
| 1827 | + .da-titulo { | ||
| 1828 | + flex-direction: column; | ||
| 1829 | + gap: 0.15rem; | ||
| 1830 | + } | ||
| 1831 | + | ||
| 1832 | + .entidad-nombre { | ||
| 1833 | + font-size: 0.95rem; | ||
| 1834 | + } | ||
| 1835 | + | ||
| 1836 | + .da-nombre { | ||
| 1837 | + font-size: 0.95rem; | ||
| 1838 | + } | ||
| 1839 | + | ||
| 1840 | + .historia-mapa-grid { | ||
| 1841 | + grid-template-columns: 1fr; | ||
| 1842 | + } | ||
| 1843 | + | ||
| 1844 | + .entidad-search { | ||
| 1845 | + width: 100%; | ||
| 1846 | + } | ||
| 1847 | + | ||
| 1848 | + .historia-grid { | ||
| 1849 | + grid-template-columns: 1fr; | ||
| 1850 | + } | ||
| 1851 | + | ||
| 1852 | + .ranking-grid { | ||
| 1853 | + grid-template-columns: 1fr; | ||
| 1854 | + } | ||
| 1855 | + | ||
| 1856 | + .ranking-grid.single { | ||
| 1857 | + max-width: 100%; | ||
| 1858 | + } | ||
| 1859 | + } | ||
| 1860 | +</style> |
static/mapa.json
0 → 100644
This diff could not be displayed because it is too large.
static/poblacion.csv
0 → 100644
| 1 | +codigo_ine,gestion,poblacion | ||
| 2 | +10101,2016,289621 | ||
| 3 | +10101,2017,294228 | ||
| 4 | +10101,2018,298377 | ||
| 5 | +10101,2019,302052 | ||
| 6 | +10101,2020,305505 | ||
| 7 | +10101,2021,307966 | ||
| 8 | +10101,2022,309436 | ||
| 9 | +10101,2023,310903 | ||
| 10 | +10101,2024,311295 | ||
| 11 | +10101,2025,311032 | ||
| 12 | +10102,2016,10026 | ||
| 13 | +10102,2017,10060 | ||
| 14 | +10102,2018,10079 | ||
| 15 | +10102,2019,10082 | ||
| 16 | +10102,2020,10076 | ||
| 17 | +10102,2021,10033 | ||
| 18 | +10102,2022,9953 | ||
| 19 | +10102,2023,9866 | ||
| 20 | +10102,2024,9735 | ||
| 21 | +10102,2025,9578 | ||
| 22 | +10103,2016,16884 | ||
| 23 | +10103,2017,16693 | ||
| 24 | +10103,2018,16464 | ||
| 25 | +10103,2019,16201 | ||
| 26 | +10103,2020,15919 | ||
| 27 | +10103,2021,15583 | ||
| 28 | +10103,2022,15198 | ||
| 29 | +10103,2023,14817 | ||
| 30 | +10103,2024,14391 | ||
| 31 | +10103,2025,13966 | ||
| 32 | +10201,2016,10582 | ||
| 33 | +10201,2017,10355 | ||
| 34 | +10201,2018,10104 | ||
| 35 | +10201,2019,9834 | ||
| 36 | +10201,2020,9558 | ||
| 37 | +10201,2021,9258 | ||
| 38 | +10201,2022,8942 | ||
| 39 | +10201,2023,8645 | ||
| 40 | +10201,2024,8340 | ||
| 41 | +10201,2025,8056 | ||
| 42 | +10202,2016,15580 | ||
| 43 | +10202,2017,15889 | ||
| 44 | +10202,2018,16214 | ||
| 45 | +10202,2019,16541 | ||
| 46 | +10202,2020,16873 | ||
| 47 | +10202,2021,17151 | ||
| 48 | +10202,2022,17362 | ||
| 49 | +10202,2023,17547 | ||
| 50 | +10202,2024,17631 | ||
| 51 | +10202,2025,17632 | ||
| 52 | +10301,2016,12884 | ||
| 53 | +10301,2017,13152 | ||
| 54 | +10301,2018,13392 | ||
| 55 | +10301,2019,13606 | ||
| 56 | +10301,2020,13811 | ||
| 57 | +10301,2021,13975 | ||
| 58 | +10301,2022,14102 | ||
| 59 | +10301,2023,14241 | ||
| 60 | +10301,2024,14348 | ||
| 61 | +10301,2025,14438 | ||
| 62 | +10302,2016,13506 | ||
| 63 | +10302,2017,13594 | ||
| 64 | +10302,2018,13637 | ||
| 65 | +10302,2019,13640 | ||
| 66 | +10302,2020,13620 | ||
| 67 | +10302,2021,13549 | ||
| 68 | +10302,2022,13433 | ||
| 69 | +10302,2023,13323 | ||
| 70 | +10302,2024,13179 | ||
| 71 | +10302,2025,13030 | ||
| 72 | +10303,2016,8811 | ||
| 73 | +10303,2017,8944 | ||
| 74 | +10303,2018,9072 | ||
| 75 | +10303,2019,9193 | ||
| 76 | +10303,2020,9310 | ||
| 77 | +10303,2021,9397 | ||
| 78 | +10303,2022,9451 | ||
| 79 | +10303,2023,9498 | ||
| 80 | +10303,2024,9502 | ||
| 81 | +10303,2025,9473 | ||
| 82 | +10304,2016,8530 | ||
| 83 | +10304,2017,8693 | ||
| 84 | +10304,2018,8861 | ||
| 85 | +10304,2019,9027 | ||
| 86 | +10304,2020,9195 | ||
| 87 | +10304,2021,9336 | ||
| 88 | +10304,2022,9444 | ||
| 89 | +10304,2023,9544 | ||
| 90 | +10304,2024,9599 | ||
| 91 | +10304,2025,9613 | ||
| 92 | +10401,2016,10615 | ||
| 93 | +10401,2017,10557 | ||
| 94 | +10401,2018,10493 | ||
| 95 | +10401,2019,10419 | ||
| 96 | +10401,2020,10339 | ||
| 97 | +10401,2021,10224 | ||
| 98 | +10401,2022,10069 | ||
| 99 | +10401,2023,9902 | ||
| 100 | +10401,2024,9683 | ||
| 101 | +10401,2025,9436 | ||
| 102 | +10402,2016,9009 | ||
| 103 | +10402,2017,9059 | ||
| 104 | +10402,2018,9102 | ||
| 105 | +10402,2019,9134 | ||
| 106 | +10402,2020,9159 | ||
| 107 | +10402,2021,9151 | ||
| 108 | +10402,2022,9106 | ||
| 109 | +10402,2023,9050 | ||
| 110 | +10402,2024,8947 | ||
| 111 | +10402,2025,8817 | ||
| 112 | +10403,2016,7714 | ||
| 113 | +10403,2017,7722 | ||
| 114 | +10403,2018,7716 | ||
| 115 | +10403,2019,7696 | ||
| 116 | +10403,2020,7667 | ||
| 117 | +10403,2021,7609 | ||
| 118 | +10403,2022,7522 | ||
| 119 | +10403,2023,7430 | ||
| 120 | +10403,2024,7305 | ||
| 121 | +10403,2025,7167 | ||
| 122 | +10404,2016,5013 | ||
| 123 | +10404,2017,4927 | ||
| 124 | +10404,2018,4821 | ||
| 125 | +10404,2019,4698 | ||
| 126 | +10404,2020,4567 | ||
| 127 | +10404,2021,4421 | ||
| 128 | +10404,2022,4266 | ||
| 129 | +10404,2023,4121 | ||
| 130 | +10404,2024,3976 | ||
| 131 | +10404,2025,3845 | ||
| 132 | +10405,2016,4985 | ||
| 133 | +10405,2017,5112 | ||
| 134 | +10405,2018,5244 | ||
| 135 | +10405,2019,5378 | ||
| 136 | +10405,2020,5514 | ||
| 137 | +10405,2021,5637 | ||
| 138 | +10405,2022,5742 | ||
| 139 | +10405,2023,5845 | ||
| 140 | +10405,2024,5921 | ||
| 141 | +10405,2025,5971 | ||
| 142 | +10501,2016,25779 | ||
| 143 | +10501,2017,25942 | ||
| 144 | +10501,2018,26090 | ||
| 145 | +10501,2019,26210 | ||
| 146 | +10501,2020,26313 | ||
| 147 | +10501,2021,26321 | ||
| 148 | +10501,2022,26221 | ||
| 149 | +10501,2023,26084 | ||
| 150 | +10501,2024,25807 | ||
| 151 | +10501,2025,25441 | ||
| 152 | +10502,2016,8489 | ||
| 153 | +10502,2017,8422 | ||
| 154 | +10502,2018,8350 | ||
| 155 | +10502,2019,8269 | ||
| 156 | +10502,2020,8184 | ||
| 157 | +10502,2021,8071 | ||
| 158 | +10502,2022,7928 | ||
| 159 | +10502,2023,7777 | ||
| 160 | +10502,2024,7588 | ||
| 161 | +10502,2025,7380 | ||
| 162 | +10601,2016,16919 | ||
| 163 | +10601,2017,16639 | ||
| 164 | +10601,2018,16335 | ||
| 165 | +10601,2019,16011 | ||
| 166 | +10601,2020,15681 | ||
| 167 | +10601,2021,15308 | ||
| 168 | +10601,2022,14895 | ||
| 169 | +10601,2023,14493 | ||
| 170 | +10601,2024,14052 | ||
| 171 | +10601,2025,13608 | ||
| 172 | +10602,2016,10390 | ||
| 173 | +10602,2017,10288 | ||
| 174 | +10602,2018,10160 | ||
| 175 | +10602,2019,10009 | ||
| 176 | +10602,2020,9845 | ||
| 177 | +10602,2021,9647 | ||
| 178 | +10602,2022,9418 | ||
| 179 | +10602,2023,9192 | ||
| 180 | +10602,2024,8939 | ||
| 181 | +10602,2025,8684 | ||
| 182 | +10701,2016,16568 | ||
| 183 | +10701,2017,16575 | ||
| 184 | +10701,2018,16542 | ||
| 185 | +10701,2019,16471 | ||
| 186 | +10701,2020,16376 | ||
| 187 | +10701,2021,16218 | ||
| 188 | +10701,2022,15999 | ||
| 189 | +10701,2023,15773 | ||
| 190 | +10701,2024,15486 | ||
| 191 | +10701,2025,15179 | ||
| 192 | +10702,2016,33276 | ||
| 193 | +10702,2017,32888 | ||
| 194 | +10702,2018,32413 | ||
| 195 | +10702,2019,31862 | ||
| 196 | +10702,2020,31272 | ||
| 197 | +10702,2021,30575 | ||
| 198 | +10702,2022,29787 | ||
| 199 | +10702,2023,29015 | ||
| 200 | +10702,2024,28169 | ||
| 201 | +10702,2025,27334 | ||
| 202 | +10703,2016,14823 | ||
| 203 | +10703,2017,15207 | ||
| 204 | +10703,2018,15581 | ||
| 205 | +10703,2019,15939 | ||
| 206 | +10703,2020,16293 | ||
| 207 | +10703,2021,16597 | ||
| 208 | +10703,2022,16846 | ||
| 209 | +10703,2023,17090 | ||
| 210 | +10703,2024,17267 | ||
| 211 | +10703,2025,17395 | ||
| 212 | +10704,2016,17073 | ||
| 213 | +10704,2017,16987 | ||
| 214 | +10704,2018,16834 | ||
| 215 | +10704,2019,16624 | ||
| 216 | +10704,2020,16383 | ||
| 217 | +10704,2021,16081 | ||
| 218 | +10704,2022,15732 | ||
| 219 | +10704,2023,15400 | ||
| 220 | +10704,2024,15041 | ||
| 221 | +10704,2025,14696 | ||
| 222 | +10801,2016,11297 | ||
| 223 | +10801,2017,11156 | ||
| 224 | +10801,2018,10997 | ||
| 225 | +10801,2019,10819 | ||
| 226 | +10801,2020,10632 | ||
| 227 | +10801,2021,10410 | ||
| 228 | +10801,2022,10153 | ||
| 229 | +10801,2023,9897 | ||
| 230 | +10801,2024,9604 | ||
| 231 | +10801,2025,9307 | ||
| 232 | +10901,2016,4167 | ||
| 233 | +10901,2017,4355 | ||
| 234 | +10901,2018,4550 | ||
| 235 | +10901,2019,4749 | ||
| 236 | +10901,2020,4950 | ||
| 237 | +10901,2021,5139 | ||
| 238 | +10901,2022,5311 | ||
| 239 | +10901,2023,5478 | ||
| 240 | +10901,2024,5617 | ||
| 241 | +10901,2025,5732 | ||
| 242 | +10902,2016,18220 | ||
| 243 | +10902,2017,18040 | ||
| 244 | +10902,2018,17815 | ||
| 245 | +10902,2019,17549 | ||
| 246 | +10902,2020,17260 | ||
| 247 | +10902,2021,16910 | ||
| 248 | +10902,2022,16505 | ||
| 249 | +10902,2023,16104 | ||
| 250 | +10902,2024,15653 | ||
| 251 | +10902,2025,15203 | ||
| 252 | +10903,2016,4594 | ||
| 253 | +10903,2017,4701 | ||
| 254 | +10903,2018,4806 | ||
| 255 | +10903,2019,4906 | ||
| 256 | +10903,2020,5004 | ||
| 257 | +10903,2021,5087 | ||
| 258 | +10903,2022,5152 | ||
| 259 | +10903,2023,5215 | ||
| 260 | +10903,2024,5257 | ||
| 261 | +10903,2025,5284 | ||
| 262 | +11001,2016,11031 | ||
| 263 | +11001,2017,11405 | ||
| 264 | +11001,2018,11802 | ||
| 265 | +11001,2019,12210 | ||
| 266 | +11001,2020,12627 | ||
| 267 | +11001,2021,13011 | ||
| 268 | +11001,2022,13345 | ||
| 269 | +11001,2023,13662 | ||
| 270 | +11001,2024,13900 | ||
| 271 | +11001,2025,14067 | ||
| 272 | +11002,2016,2717 | ||
| 273 | +11002,2017,2730 | ||
| 274 | +11002,2018,2738 | ||
| 275 | +11002,2019,2742 | ||
| 276 | +11002,2020,2743 | ||
| 277 | +11002,2021,2736 | ||
| 278 | +11002,2022,2719 | ||
| 279 | +11002,2023,2702 | ||
| 280 | +11002,2024,2677 | ||
| 281 | +11002,2025,2644 | ||
| 282 | +11003,2016,7869 | ||
| 283 | +11003,2017,7896 | ||
| 284 | +11003,2018,7911 | ||
| 285 | +11003,2019,7912 | ||
| 286 | +11003,2020,7904 | ||
| 287 | +11003,2021,7867 | ||
| 288 | +11003,2022,7798 | ||
| 289 | +11003,2023,7722 | ||
| 290 | +11003,2024,7611 | ||
| 291 | +11003,2025,7482 | ||
| 292 | +20101,2016,807311 | ||
| 293 | +20101,2017,809186 | ||
| 294 | +20101,2018,810405 | ||
| 295 | +20101,2019,810900 | ||
| 296 | +20101,2020,811307 | ||
| 297 | +20101,2021,809537 | ||
| 298 | +20101,2022,805573 | ||
| 299 | +20101,2023,801926 | ||
| 300 | +20101,2024,795746 | ||
| 301 | +20101,2025,788297 | ||
| 302 | +20102,2016,18904 | ||
| 303 | +20102,2017,19433 | ||
| 304 | +20102,2018,19963 | ||
| 305 | +20102,2019,20489 | ||
| 306 | +20102,2020,21023 | ||
| 307 | +20102,2021,21508 | ||
| 308 | +20102,2022,21937 | ||
| 309 | +20102,2023,22374 | ||
| 310 | +20102,2024,22738 | ||
| 311 | +20102,2025,23038 | ||
| 312 | +20103,2016,18101 | ||
| 313 | +20103,2017,18586 | ||
| 314 | +20103,2018,19057 | ||
| 315 | +20103,2019,19511 | ||
| 316 | +20103,2020,19965 | ||
| 317 | +20103,2021,20368 | ||
| 318 | +20103,2022,20721 | ||
| 319 | +20103,2023,21089 | ||
| 320 | +20103,2024,21400 | ||
| 321 | +20103,2025,21665 | ||
| 322 | +20104,2016,29866 | ||
| 323 | +20104,2017,32241 | ||
| 324 | +20104,2018,34694 | ||
| 325 | +20104,2019,37179 | ||
| 326 | +20104,2020,39683 | ||
| 327 | +20104,2021,42062 | ||
| 328 | +20104,2022,44257 | ||
| 329 | +20104,2023,46363 | ||
| 330 | +20104,2024,48176 | ||
| 331 | +20104,2025,49644 | ||
| 332 | +20105,2016,924282 | ||
| 333 | +20105,2017,931574 | ||
| 334 | +20105,2018,936522 | ||
| 335 | +20105,2019,939416 | ||
| 336 | +20105,2020,941397 | ||
| 337 | +20105,2021,940467 | ||
| 338 | +20105,2022,937049 | ||
| 339 | +20105,2023,934526 | ||
| 340 | +20105,2024,930046 | ||
| 341 | +20105,2025,925014 | ||
| 342 | +20201,2016,49174 | ||
| 343 | +20201,2017,49493 | ||
| 344 | +20201,2018,49763 | ||
| 345 | +20201,2019,49977 | ||
| 346 | +20201,2020,50170 | ||
| 347 | +20201,2021,50209 | ||
| 348 | +20201,2022,50087 | ||
| 349 | +20201,2023,49958 | ||
| 350 | +20201,2024,49640 | ||
| 351 | +20201,2025,49209 | ||
| 352 | +20202,2016,14585 | ||
| 353 | +20202,2017,14982 | ||
| 354 | +20202,2018,15411 | ||
| 355 | +20202,2019,15857 | ||
| 356 | +20202,2020,16320 | ||
| 357 | +20202,2021,16747 | ||
| 358 | +20202,2022,17119 | ||
| 359 | +20202,2023,17478 | ||
| 360 | +20202,2024,17747 | ||
| 361 | +20202,2025,17934 | ||
| 362 | +20204,2016,8559 | ||
| 363 | +20204,2017,8658 | ||
| 364 | +20204,2018,8756 | ||
| 365 | +20204,2019,8850 | ||
| 366 | +20204,2020,8946 | ||
| 367 | +20204,2021,9016 | ||
| 368 | +20204,2022,9060 | ||
| 369 | +20204,2023,9101 | ||
| 370 | +20204,2024,9106 | ||
| 371 | +20204,2025,9083 | ||
| 372 | +20205,2016,9313 | ||
| 373 | +20205,2017,9422 | ||
| 374 | +20205,2018,9520 | ||
| 375 | +20205,2019,9605 | ||
| 376 | +20205,2020,9685 | ||
| 377 | +20205,2021,9734 | ||
| 378 | +20205,2022,9752 | ||
| 379 | +20205,2023,9768 | ||
| 380 | +20205,2024,9748 | ||
| 381 | +20205,2025,9708 | ||
| 382 | +20206,2016,4515 | ||
| 383 | +20206,2017,4641 | ||
| 384 | +20206,2018,4764 | ||
| 385 | +20206,2019,4881 | ||
| 386 | +20206,2020,4997 | ||
| 387 | +20206,2021,5100 | ||
| 388 | +20206,2022,5189 | ||
| 389 | +20206,2023,5281 | ||
| 390 | +20206,2024,5357 | ||
| 391 | +20206,2025,5421 | ||
| 392 | +20203,2016,5746 | ||
| 393 | +20203,2017,5969 | ||
| 394 | +20203,2018,6208 | ||
| 395 | +20203,2019,6456 | ||
| 396 | +20203,2020,6712 | ||
| 397 | +20203,2021,6953 | ||
| 398 | +20203,2022,7171 | ||
| 399 | +20203,2023,7383 | ||
| 400 | +20203,2024,7555 | ||
| 401 | +20203,2025,7687 | ||
| 402 | +20301,2016,11810 | ||
| 403 | +20301,2017,12111 | ||
| 404 | +20301,2018,12433 | ||
| 405 | +20301,2019,12767 | ||
| 406 | +20301,2020,13113 | ||
| 407 | +20301,2021,13430 | ||
| 408 | +20301,2022,13708 | ||
| 409 | +20301,2023,13979 | ||
| 410 | +20301,2024,14186 | ||
| 411 | +20301,2025,14334 | ||
| 412 | +20302,2016,16004 | ||
| 413 | +20302,2017,16153 | ||
| 414 | +20302,2018,16269 | ||
| 415 | +20302,2019,16353 | ||
| 416 | +20302,2020,16424 | ||
| 417 | +20302,2021,16444 | ||
| 418 | +20302,2022,16418 | ||
| 419 | +20302,2023,16403 | ||
| 420 | +20302,2024,16345 | ||
| 421 | +20302,2025,16269 | ||
| 422 | +20303,2016,11049 | ||
| 423 | +20303,2017,11298 | ||
| 424 | +20303,2018,11546 | ||
| 425 | +20303,2019,11790 | ||
| 426 | +20303,2020,12038 | ||
| 427 | +20303,2021,12258 | ||
| 428 | +20303,2022,12446 | ||
| 429 | +20303,2023,12640 | ||
| 430 | +20303,2024,12795 | ||
| 431 | +20303,2025,12917 | ||
| 432 | +20304,2016,4391 | ||
| 433 | +20304,2017,4525 | ||
| 434 | +20304,2018,4665 | ||
| 435 | +20304,2019,4810 | ||
| 436 | +20304,2020,4960 | ||
| 437 | +20304,2021,5102 | ||
| 438 | +20304,2022,5232 | ||
| 439 | +20304,2023,5364 | ||
| 440 | +20304,2024,5477 | ||
| 441 | +20304,2025,5569 | ||
| 442 | +20305,2016,3691 | ||
| 443 | +20305,2017,3794 | ||
| 444 | +20305,2018,3896 | ||
| 445 | +20305,2019,3997 | ||
| 446 | +20305,2020,4099 | ||
| 447 | +20305,2021,4191 | ||
| 448 | +20305,2022,4271 | ||
| 449 | +20305,2023,4352 | ||
| 450 | +20305,2024,4418 | ||
| 451 | +20305,2025,4472 | ||
| 452 | +20306,2016,5932 | ||
| 453 | +20306,2017,6076 | ||
| 454 | +20306,2018,6198 | ||
| 455 | +20306,2019,6303 | ||
| 456 | +20306,2020,6401 | ||
| 457 | +20306,2021,6484 | ||
| 458 | +20306,2022,6557 | ||
| 459 | +20306,2023,6648 | ||
| 460 | +20306,2024,6741 | ||
| 461 | +20306,2025,6840 | ||
| 462 | +20307,2016,1507 | ||
| 463 | +20307,2017,1847 | ||
| 464 | +20307,2018,2205 | ||
| 465 | +20307,2019,2572 | ||
| 466 | +20307,2020,2941 | ||
| 467 | +20307,2021,3294 | ||
| 468 | +20307,2022,3622 | ||
| 469 | +20307,2023,3925 | ||
| 470 | +20307,2024,4181 | ||
| 471 | +20307,2025,4376 | ||
| 472 | +20308,2016,7834 | ||
| 473 | +20308,2017,7934 | ||
| 474 | +20308,2018,8038 | ||
| 475 | +20308,2019,8140 | ||
| 476 | +20308,2020,8245 | ||
| 477 | +20308,2021,8326 | ||
| 478 | +20308,2022,8379 | ||
| 479 | +20308,2023,8426 | ||
| 480 | +20308,2024,8433 | ||
| 481 | +20308,2025,8408 | ||
| 482 | +20401,2016,12390 | ||
| 483 | +20401,2017,12673 | ||
| 484 | +20401,2018,12981 | ||
| 485 | +20401,2019,13302 | ||
| 486 | +20401,2020,13639 | ||
| 487 | +20401,2021,13947 | ||
| 488 | +20401,2022,14215 | ||
| 489 | +20401,2023,14477 | ||
| 490 | +20401,2024,14672 | ||
| 491 | +20401,2025,14802 | ||
| 492 | +20402,2016,16970 | ||
| 493 | +20402,2017,17234 | ||
| 494 | +20402,2018,17509 | ||
| 495 | +20402,2019,17784 | ||
| 496 | +20402,2020,18064 | ||
| 497 | +20402,2021,18293 | ||
| 498 | +20402,2022,18461 | ||
| 499 | +20402,2023,18615 | ||
| 500 | +20402,2024,18680 | ||
| 501 | +20402,2025,18679 | ||
| 502 | +20403,2016,16062 | ||
| 503 | +20403,2017,16374 | ||
| 504 | +20403,2018,16692 | ||
| 505 | +20403,2019,17007 | ||
| 506 | +20403,2020,17326 | ||
| 507 | +20403,2021,17600 | ||
| 508 | +20403,2022,17818 | ||
| 509 | +20403,2023,18031 | ||
| 510 | +20403,2024,18169 | ||
| 511 | +20403,2025,18244 | ||
| 512 | +20404,2016,5716 | ||
| 513 | +20404,2017,5768 | ||
| 514 | +20404,2018,5818 | ||
| 515 | +20404,2019,5864 | ||
| 516 | +20404,2020,5910 | ||
| 517 | +20404,2021,5939 | ||
| 518 | +20404,2022,5949 | ||
| 519 | +20404,2023,5956 | ||
| 520 | +20404,2024,5940 | ||
| 521 | +20404,2025,5906 | ||
| 522 | +20405,2016,7974 | ||
| 523 | +20405,2017,8155 | ||
| 524 | +20405,2018,8340 | ||
| 525 | +20405,2019,8525 | ||
| 526 | +20405,2020,8714 | ||
| 527 | +20405,2021,8881 | ||
| 528 | +20405,2022,9021 | ||
| 529 | +20405,2023,9158 | ||
| 530 | +20405,2024,9257 | ||
| 531 | +20405,2025,9324 | ||
| 532 | +20501,2016,13460 | ||
| 533 | +20501,2017,14093 | ||
| 534 | +20501,2018,14771 | ||
| 535 | +20501,2019,15476 | ||
| 536 | +20501,2020,16203 | ||
| 537 | +20501,2021,16892 | ||
| 538 | +20501,2022,17523 | ||
| 539 | +20501,2023,18129 | ||
| 540 | +20501,2024,18630 | ||
| 541 | +20501,2025,19019 | ||
| 542 | +20502,2016,9082 | ||
| 543 | +20502,2017,9176 | ||
| 544 | +20502,2018,9263 | ||
| 545 | +20502,2019,9340 | ||
| 546 | +20502,2020,9415 | ||
| 547 | +20502,2021,9462 | ||
| 548 | +20502,2022,9479 | ||
| 549 | +20502,2023,9496 | ||
| 550 | +20502,2024,9477 | ||
| 551 | +20502,2025,9436 | ||
| 552 | +20503,2016,6192 | ||
| 553 | +20503,2017,6325 | ||
| 554 | +20503,2018,6451 | ||
| 555 | +20503,2019,6569 | ||
| 556 | +20503,2020,6687 | ||
| 557 | +20503,2021,6788 | ||
| 558 | +20503,2022,6872 | ||
| 559 | +20503,2023,6964 | ||
| 560 | +20503,2024,7039 | ||
| 561 | +20503,2025,7100 | ||
| 562 | +20601,2016,26695 | ||
| 563 | +20601,2017,27386 | ||
| 564 | +20601,2018,28065 | ||
| 565 | +20601,2019,28727 | ||
| 566 | +20601,2020,29394 | ||
| 567 | +20601,2021,29989 | ||
| 568 | +20601,2022,30507 | ||
| 569 | +20601,2023,31044 | ||
| 570 | +20601,2024,31489 | ||
| 571 | +20601,2025,31861 | ||
| 572 | +20602,2016,16281 | ||
| 573 | +20602,2017,16489 | ||
| 574 | +20602,2018,16665 | ||
| 575 | +20602,2019,16814 | ||
| 576 | +20602,2020,16953 | ||
| 577 | +20602,2021,17045 | ||
| 578 | +20602,2022,17094 | ||
| 579 | +20602,2023,17159 | ||
| 580 | +20602,2024,17186 | ||
| 581 | +20602,2025,17194 | ||
| 582 | +20603,2016,9148 | ||
| 583 | +20603,2017,9321 | ||
| 584 | +20603,2018,9481 | ||
| 585 | +20603,2019,9631 | ||
| 586 | +20603,2020,9781 | ||
| 587 | +20603,2021,9907 | ||
| 588 | +20603,2022,10011 | ||
| 589 | +20603,2023,10128 | ||
| 590 | +20603,2024,10224 | ||
| 591 | +20603,2025,10304 | ||
| 592 | +20604,2016,3193 | ||
| 593 | +20604,2017,3346 | ||
| 594 | +20604,2018,3506 | ||
| 595 | +20604,2019,3671 | ||
| 596 | +20604,2020,3840 | ||
| 597 | +20604,2021,4000 | ||
| 598 | +20604,2022,4147 | ||
| 599 | +20604,2023,4290 | ||
| 600 | +20604,2024,4413 | ||
| 601 | +20604,2025,4511 | ||
| 602 | +20605,2016,3992 | ||
| 603 | +20605,2017,3989 | ||
| 604 | +20605,2018,3971 | ||
| 605 | +20605,2019,3943 | ||
| 606 | +20605,2020,3908 | ||
| 607 | +20605,2021,3862 | ||
| 608 | +20605,2022,3808 | ||
| 609 | +20605,2023,3760 | ||
| 610 | +20605,2024,3710 | ||
| 611 | +20605,2025,3665 | ||
| 612 | +20606,2016,10070 | ||
| 613 | +20606,2017,9879 | ||
| 614 | +20606,2018,9658 | ||
| 615 | +20606,2019,9415 | ||
| 616 | +20606,2020,9167 | ||
| 617 | +20606,2021,8899 | ||
| 618 | +20606,2022,8622 | ||
| 619 | +20606,2023,8373 | ||
| 620 | +20606,2024,8132 | ||
| 621 | +20606,2025,7925 | ||
| 622 | +20607,2016,16827 | ||
| 623 | +20607,2017,17624 | ||
| 624 | +20607,2018,18429 | ||
| 625 | +20607,2019,19234 | ||
| 626 | +20607,2020,20046 | ||
| 627 | +20607,2021,20808 | ||
| 628 | +20607,2022,21508 | ||
| 629 | +20607,2023,22206 | ||
| 630 | +20607,2024,22818 | ||
| 631 | +20607,2025,23337 | ||
| 632 | +20608,2016,10979 | ||
| 633 | +20608,2017,11391 | ||
| 634 | +20608,2018,11804 | ||
| 635 | +20608,2019,12214 | ||
| 636 | +20608,2020,12628 | ||
| 637 | +20608,2021,13008 | ||
| 638 | +20608,2022,13352 | ||
| 639 | +20608,2023,13695 | ||
| 640 | +20608,2024,13989 | ||
| 641 | +20608,2025,14238 | ||
| 642 | +20701,2016,21846 | ||
| 643 | +20701,2017,21830 | ||
| 644 | +20701,2018,21724 | ||
| 645 | +20701,2019,21545 | ||
| 646 | +20701,2020,21332 | ||
| 647 | +20701,2021,21050 | ||
| 648 | +20701,2022,20722 | ||
| 649 | +20701,2023,20436 | ||
| 650 | +20701,2024,20141 | ||
| 651 | +20701,2025,19881 | ||
| 652 | +20702,2016,7809 | ||
| 653 | +20702,2017,8044 | ||
| 654 | +20702,2018,8276 | ||
| 655 | +20702,2019,8502 | ||
| 656 | +20702,2020,8731 | ||
| 657 | +20702,2021,8938 | ||
| 658 | +20702,2022,9123 | ||
| 659 | +20702,2023,9314 | ||
| 660 | +20702,2024,9478 | ||
| 661 | +20702,2025,9618 | ||
| 662 | +20801,2016,95383 | ||
| 663 | +20801,2017,98723 | ||
| 664 | +20801,2018,101945 | ||
| 665 | +20801,2019,105056 | ||
| 666 | +20801,2020,108160 | ||
| 667 | +20801,2021,110999 | ||
| 668 | +20801,2022,113581 | ||
| 669 | +20801,2023,116282 | ||
| 670 | +20801,2024,118724 | ||
| 671 | +20801,2025,120926 | ||
| 672 | +20802,2016,7992 | ||
| 673 | +20802,2017,8145 | ||
| 674 | +20802,2018,8302 | ||
| 675 | +20802,2019,8457 | ||
| 676 | +20802,2020,8614 | ||
| 677 | +20802,2021,8747 | ||
| 678 | +20802,2022,8851 | ||
| 679 | +20802,2023,8950 | ||
| 680 | +20802,2024,9007 | ||
| 681 | +20802,2025,9032 | ||
| 682 | +20803,2016,13325 | ||
| 683 | +20803,2017,13513 | ||
| 684 | +20803,2018,13691 | ||
| 685 | +20803,2019,13855 | ||
| 686 | +20803,2020,14016 | ||
| 687 | +20803,2021,14136 | ||
| 688 | +20803,2022,14213 | ||
| 689 | +20803,2023,14290 | ||
| 690 | +20803,2024,14316 | ||
| 691 | +20803,2025,14309 | ||
| 692 | +20804,2016,7782 | ||
| 693 | +20804,2017,7900 | ||
| 694 | +20804,2018,8000 | ||
| 695 | +20804,2019,8084 | ||
| 696 | +20804,2020,8160 | ||
| 697 | +20804,2021,8211 | ||
| 698 | +20804,2022,8238 | ||
| 699 | +20804,2023,8271 | ||
| 700 | +20804,2024,8283 | ||
| 701 | +20804,2025,8288 | ||
| 702 | +20805,2016,6894 | ||
| 703 | +20805,2017,7082 | ||
| 704 | +20805,2018,7279 | ||
| 705 | +20805,2019,7479 | ||
| 706 | +20805,2020,7683 | ||
| 707 | +20805,2021,7869 | ||
| 708 | +20805,2022,8030 | ||
| 709 | +20805,2023,8188 | ||
| 710 | +20805,2024,8308 | ||
| 711 | +20805,2025,8395 | ||
| 712 | +20806,2016,16521 | ||
| 713 | +20806,2017,16766 | ||
| 714 | +20806,2018,16994 | ||
| 715 | +20806,2019,17204 | ||
| 716 | +20806,2020,17409 | ||
| 717 | +20806,2021,17566 | ||
| 718 | +20806,2022,17673 | ||
| 719 | +20806,2023,17786 | ||
| 720 | +20806,2024,17844 | ||
| 721 | +20806,2025,17860 | ||
| 722 | +20807,2016,7277 | ||
| 723 | +20807,2017,7396 | ||
| 724 | +20807,2018,7508 | ||
| 725 | +20807,2019,7611 | ||
| 726 | +20807,2020,7712 | ||
| 727 | +20807,2021,7788 | ||
| 728 | +20807,2022,7839 | ||
| 729 | +20807,2023,7889 | ||
| 730 | +20807,2024,7908 | ||
| 731 | +20807,2025,7909 | ||
| 732 | +20901,2016,11893 | ||
| 733 | +20901,2017,11903 | ||
| 734 | +20901,2018,11879 | ||
| 735 | +20901,2019,11825 | ||
| 736 | +20901,2020,11758 | ||
| 737 | +20901,2021,11654 | ||
| 738 | +20901,2022,11519 | ||
| 739 | +20901,2023,11396 | ||
| 740 | +20901,2024,11252 | ||
| 741 | +20901,2025,11108 | ||
| 742 | +20902,2016,14126 | ||
| 743 | +20902,2017,14532 | ||
| 744 | +20902,2018,14950 | ||
| 745 | +20902,2019,15374 | ||
| 746 | +20902,2020,15812 | ||
| 747 | +20902,2021,16216 | ||
| 748 | +20902,2022,16580 | ||
| 749 | +20902,2023,16949 | ||
| 750 | +20902,2024,17258 | ||
| 751 | +20902,2025,17507 | ||
| 752 | +20903,2016,8828 | ||
| 753 | +20903,2017,9323 | ||
| 754 | +20903,2018,9850 | ||
| 755 | +20903,2019,10395 | ||
| 756 | +20903,2020,10955 | ||
| 757 | +20903,2021,11488 | ||
| 758 | +20903,2022,11978 | ||
| 759 | +20903,2023,12447 | ||
| 760 | +20903,2024,12840 | ||
| 761 | +20903,2025,13149 | ||
| 762 | +20904,2016,5628 | ||
| 763 | +20904,2017,5660 | ||
| 764 | +20904,2018,5675 | ||
| 765 | +20904,2019,5676 | ||
| 766 | +20904,2020,5671 | ||
| 767 | +20904,2021,5650 | ||
| 768 | +20904,2022,5617 | ||
| 769 | +20904,2023,5593 | ||
| 770 | +20904,2024,5562 | ||
| 771 | +20904,2025,5532 | ||
| 772 | +20905,2016,12537 | ||
| 773 | +20905,2017,12790 | ||
| 774 | +20905,2018,13047 | ||
| 775 | +20905,2019,13302 | ||
| 776 | +20905,2020,13561 | ||
| 777 | +20905,2021,13783 | ||
| 778 | +20905,2022,13961 | ||
| 779 | +20905,2023,14135 | ||
| 780 | +20905,2024,14250 | ||
| 781 | +20905,2025,14317 | ||
| 782 | +21001,2016,16342 | ||
| 783 | +21001,2017,16759 | ||
| 784 | +21001,2018,17201 | ||
| 785 | +21001,2019,17656 | ||
| 786 | +21001,2020,18128 | ||
| 787 | +21001,2021,18556 | ||
| 788 | +21001,2022,18927 | ||
| 789 | +21001,2023,19287 | ||
| 790 | +21001,2024,19557 | ||
| 791 | +21001,2025,19747 | ||
| 792 | +21002,2016,9406 | ||
| 793 | +21002,2017,9679 | ||
| 794 | +21002,2018,9956 | ||
| 795 | +21002,2019,10231 | ||
| 796 | +21002,2020,10511 | ||
| 797 | +21002,2021,10764 | ||
| 798 | +21002,2022,10988 | ||
| 799 | +21002,2023,11211 | ||
| 800 | +21002,2024,11393 | ||
| 801 | +21002,2025,11537 | ||
| 802 | +21003,2016,12431 | ||
| 803 | +21003,2017,12948 | ||
| 804 | +21003,2018,13470 | ||
| 805 | +21003,2019,13989 | ||
| 806 | +21003,2020,14513 | ||
| 807 | +21003,2021,15000 | ||
| 808 | +21003,2022,15444 | ||
| 809 | +21003,2023,15886 | ||
| 810 | +21003,2024,16269 | ||
| 811 | +21003,2025,16592 | ||
| 812 | +21004,2016,21758 | ||
| 813 | +21004,2017,22197 | ||
| 814 | +21004,2018,22632 | ||
| 815 | +21004,2019,23057 | ||
| 816 | +21004,2020,23484 | ||
| 817 | +21004,2021,23848 | ||
| 818 | +21004,2022,24140 | ||
| 819 | +21004,2023,24431 | ||
| 820 | +21004,2024,24631 | ||
| 821 | +21004,2025,24760 | ||
| 822 | +21005,2016,8820 | ||
| 823 | +21005,2017,8999 | ||
| 824 | +21005,2018,9174 | ||
| 825 | +21005,2019,9342 | ||
| 826 | +21005,2020,9512 | ||
| 827 | +21005,2021,9658 | ||
| 828 | +21005,2022,9780 | ||
| 829 | +21005,2023,9908 | ||
| 830 | +21005,2024,10006 | ||
| 831 | +21005,2025,10081 | ||
| 832 | +21006,2016,6565 | ||
| 833 | +21006,2017,6811 | ||
| 834 | +21006,2018,7046 | ||
| 835 | +21006,2019,7270 | ||
| 836 | +21006,2020,7492 | ||
| 837 | +21006,2021,7694 | ||
| 838 | +21006,2022,7876 | ||
| 839 | +21006,2023,8065 | ||
| 840 | +21006,2024,8235 | ||
| 841 | +21006,2025,8392 | ||
| 842 | +21101,2016,19370 | ||
| 843 | +21101,2017,19490 | ||
| 844 | +21101,2018,19556 | ||
| 845 | +21101,2019,19576 | ||
| 846 | +21101,2020,19576 | ||
| 847 | +21101,2021,19517 | ||
| 848 | +21101,2022,19411 | ||
| 849 | +21101,2023,19330 | ||
| 850 | +21101,2024,19219 | ||
| 851 | +21101,2025,19102 | ||
| 852 | +21102,2016,20069 | ||
| 853 | +21102,2017,20687 | ||
| 854 | +21102,2018,21283 | ||
| 855 | +21102,2019,21859 | ||
| 856 | +21102,2020,22435 | ||
| 857 | +21102,2021,22957 | ||
| 858 | +21102,2022,23425 | ||
| 859 | +21102,2023,23916 | ||
| 860 | +21102,2024,24350 | ||
| 861 | +21102,2025,24736 | ||
| 862 | +21103,2016,7218 | ||
| 863 | +21103,2017,7348 | ||
| 864 | +21103,2018,7464 | ||
| 865 | +21103,2019,7567 | ||
| 866 | +21103,2020,7668 | ||
| 867 | +21103,2021,7752 | ||
| 868 | +21103,2022,7820 | ||
| 869 | +21103,2023,7903 | ||
| 870 | +21103,2024,7976 | ||
| 871 | +21103,2025,8045 | ||
| 872 | +21104,2016,27376 | ||
| 873 | +21104,2017,27703 | ||
| 874 | +21104,2018,27956 | ||
| 875 | +21104,2019,28145 | ||
| 876 | +21104,2020,28308 | ||
| 877 | +21104,2021,28389 | ||
| 878 | +21104,2022,28403 | ||
| 879 | +21104,2023,28456 | ||
| 880 | +21104,2024,28466 | ||
| 881 | +21104,2025,28470 | ||
| 882 | +21105,2016,45454 | ||
| 883 | +21105,2017,46101 | ||
| 884 | +21105,2018,46567 | ||
| 885 | +21105,2019,46892 | ||
| 886 | +21105,2020,47161 | ||
| 887 | +21105,2021,47304 | ||
| 888 | +21105,2022,47373 | ||
| 889 | +21105,2023,47568 | ||
| 890 | +21105,2024,47776 | ||
| 891 | +21105,2025,48055 | ||
| 892 | +21201,2016,36791 | ||
| 893 | +21201,2017,38759 | ||
| 894 | +21201,2018,40791 | ||
| 895 | +21201,2019,42852 | ||
| 896 | +21201,2020,44945 | ||
| 897 | +21201,2021,46923 | ||
| 898 | +21201,2022,48743 | ||
| 899 | +21201,2023,50524 | ||
| 900 | +21201,2024,52059 | ||
| 901 | +21201,2025,53318 | ||
| 902 | +21202,2016,29790 | ||
| 903 | +21202,2017,31205 | ||
| 904 | +21202,2018,32630 | ||
| 905 | +21202,2019,34050 | ||
| 906 | +21202,2020,35482 | ||
| 907 | +21202,2021,36823 | ||
| 908 | +21202,2022,38057 | ||
| 909 | +21202,2023,39290 | ||
| 910 | +21202,2024,40376 | ||
| 911 | +21202,2025,41303 | ||
| 912 | +21203,2016,18915 | ||
| 913 | +21203,2017,19281 | ||
| 914 | +21203,2018,19678 | ||
| 915 | +21203,2019,20090 | ||
| 916 | +21203,2020,20516 | ||
| 917 | +21203,2021,20887 | ||
| 918 | +21203,2022,21183 | ||
| 919 | +21203,2023,21452 | ||
| 920 | +21203,2024,21603 | ||
| 921 | +21203,2025,21651 | ||
| 922 | +21204,2016,7078 | ||
| 923 | +21204,2017,7228 | ||
| 924 | +21204,2018,7392 | ||
| 925 | +21204,2019,7565 | ||
| 926 | +21204,2020,7746 | ||
| 927 | +21204,2021,7910 | ||
| 928 | +21204,2022,8050 | ||
| 929 | +21204,2023,8183 | ||
| 930 | +21204,2024,8275 | ||
| 931 | +21204,2025,8327 | ||
| 932 | +21301,2016,34143 | ||
| 933 | +21301,2017,34519 | ||
| 934 | +21301,2018,34841 | ||
| 935 | +21301,2019,35111 | ||
| 936 | +21301,2020,35360 | ||
| 937 | +21301,2021,35504 | ||
| 938 | +21301,2022,35547 | ||
| 939 | +21301,2023,35605 | ||
| 940 | +21301,2024,35559 | ||
| 941 | +21301,2025,35457 | ||
| 942 | +21302,2016,9428 | ||
| 943 | +21302,2017,9481 | ||
| 944 | +21302,2018,9532 | ||
| 945 | +21302,2019,9577 | ||
| 946 | +21302,2020,9621 | ||
| 947 | +21302,2021,9639 | ||
| 948 | +21302,2022,9626 | ||
| 949 | +21302,2023,9612 | ||
| 950 | +21302,2024,9560 | ||
| 951 | +21302,2025,9479 | ||
| 952 | +21303,2016,9094 | ||
| 953 | +21303,2017,9445 | ||
| 954 | +21303,2018,9806 | ||
| 955 | +21303,2019,10172 | ||
| 956 | +21303,2020,10544 | ||
| 957 | +21303,2021,10891 | ||
| 958 | +21303,2022,11204 | ||
| 959 | +21303,2023,11512 | ||
| 960 | +21303,2024,11771 | ||
| 961 | +21303,2025,11981 | ||
| 962 | +21304,2016,13803 | ||
| 963 | +21304,2017,14114 | ||
| 964 | +21304,2018,14432 | ||
| 965 | +21304,2019,14751 | ||
| 966 | +21304,2020,15077 | ||
| 967 | +21304,2021,15367 | ||
| 968 | +21304,2022,15615 | ||
| 969 | +21304,2023,15863 | ||
| 970 | +21304,2024,16051 | ||
| 971 | +21304,2025,16190 | ||
| 972 | +21305,2016,24973 | ||
| 973 | +21305,2017,25283 | ||
| 974 | +21305,2018,25561 | ||
| 975 | +21305,2019,25805 | ||
| 976 | +21305,2020,26039 | ||
| 977 | +21305,2021,26197 | ||
| 978 | +21305,2022,26280 | ||
| 979 | +21305,2023,26373 | ||
| 980 | +21305,2024,26384 | ||
| 981 | +21305,2025,26345 | ||
| 982 | +21306,2016,11189 | ||
| 983 | +21306,2017,11469 | ||
| 984 | +21306,2018,11742 | ||
| 985 | +21306,2019,12008 | ||
| 986 | +21306,2020,12273 | ||
| 987 | +21306,2021,12507 | ||
| 988 | +21306,2022,12707 | ||
| 989 | +21306,2023,12913 | ||
| 990 | +21306,2024,13077 | ||
| 991 | +21306,2025,13211 | ||
| 992 | +21307,2016,5629 | ||
| 993 | +21307,2017,5699 | ||
| 994 | +21307,2018,5748 | ||
| 995 | +21307,2019,5781 | ||
| 996 | +21307,2020,5806 | ||
| 997 | +21307,2021,5813 | ||
| 998 | +21307,2022,5805 | ||
| 999 | +21307,2023,5805 | ||
| 1000 | +21307,2024,5798 | ||
| 1001 | +21307,2025,5792 | ||
| 1002 | +21401,2016,22934 | ||
| 1003 | +21401,2017,23776 | ||
| 1004 | +21401,2018,24602 | ||
| 1005 | +21401,2019,25409 | ||
| 1006 | +21401,2020,26218 | ||
| 1007 | +21401,2021,26964 | ||
| 1008 | +21401,2022,27643 | ||
| 1009 | +21401,2023,28344 | ||
| 1010 | +21401,2024,28969 | ||
| 1011 | +21401,2025,29522 | ||
| 1012 | +21402,2016,19811 | ||
| 1013 | +21402,2017,20484 | ||
| 1014 | +21402,2018,21144 | ||
| 1015 | +21402,2019,21789 | ||
| 1016 | +21402,2020,22437 | ||
| 1017 | +21402,2021,23030 | ||
| 1018 | +21402,2022,23565 | ||
| 1019 | +21402,2023,24118 | ||
| 1020 | +21402,2024,24606 | ||
| 1021 | +21402,2025,25034 | ||
| 1022 | +21501,2016,10913 | ||
| 1023 | +21501,2017,11245 | ||
| 1024 | +21501,2018,11562 | ||
| 1025 | +21501,2019,11865 | ||
| 1026 | +21501,2020,12165 | ||
| 1027 | +21501,2021,12437 | ||
| 1028 | +21501,2022,12681 | ||
| 1029 | +21501,2023,12940 | ||
| 1030 | +21501,2024,13174 | ||
| 1031 | +21501,2025,13386 | ||
| 1032 | +21502,2016,9737 | ||
| 1033 | +21502,2017,9901 | ||
| 1034 | +21502,2018,10046 | ||
| 1035 | +21502,2019,10173 | ||
| 1036 | +21502,2020,10295 | ||
| 1037 | +21502,2021,10388 | ||
| 1038 | +21502,2022,10455 | ||
| 1039 | +21502,2023,10534 | ||
| 1040 | +21502,2024,10591 | ||
| 1041 | +21502,2025,10638 | ||
| 1042 | +21601,2016,14711 | ||
| 1043 | +21601,2017,15018 | ||
| 1044 | +21601,2018,15300 | ||
| 1045 | +21601,2019,15558 | ||
| 1046 | +21601,2020,15809 | ||
| 1047 | +21601,2021,16017 | ||
| 1048 | +21601,2022,16185 | ||
| 1049 | +21601,2023,16368 | ||
| 1050 | +21601,2024,16513 | ||
| 1051 | +21601,2025,16640 | ||
| 1052 | +21602,2016,4330 | ||
| 1053 | +21602,2017,4661 | ||
| 1054 | +21602,2018,5002 | ||
| 1055 | +21602,2019,5348 | ||
| 1056 | +21602,2020,5698 | ||
| 1057 | +21602,2021,6031 | ||
| 1058 | +21602,2022,6339 | ||
| 1059 | +21602,2023,6637 | ||
| 1060 | +21602,2024,6895 | ||
| 1061 | +21602,2025,7107 | ||
| 1062 | +21701,2016,16404 | ||
| 1063 | +21701,2017,16695 | ||
| 1064 | +21701,2018,16985 | ||
| 1065 | +21701,2019,17270 | ||
| 1066 | +21701,2020,17558 | ||
| 1067 | +21701,2021,17801 | ||
| 1068 | +21701,2022,17991 | ||
| 1069 | +21701,2023,18182 | ||
| 1070 | +21701,2024,18304 | ||
| 1071 | +21701,2025,18368 | ||
| 1072 | +21702,2016,7114 | ||
| 1073 | +21702,2017,7431 | ||
| 1074 | +21702,2018,7763 | ||
| 1075 | +21702,2019,8103 | ||
| 1076 | +21702,2020,8450 | ||
| 1077 | +21702,2021,8775 | ||
| 1078 | +21702,2022,9068 | ||
| 1079 | +21702,2023,9350 | ||
| 1080 | +21702,2024,9579 | ||
| 1081 | +21702,2025,9759 | ||
| 1082 | +21703,2016,7216 | ||
| 1083 | +21703,2017,7354 | ||
| 1084 | +21703,2018,7462 | ||
| 1085 | +21703,2019,7548 | ||
| 1086 | +21703,2020,7625 | ||
| 1087 | +21703,2021,7683 | ||
| 1088 | +21703,2022,7731 | ||
| 1089 | +21703,2023,7803 | ||
| 1090 | +21703,2024,7879 | ||
| 1091 | +21703,2025,7970 | ||
| 1092 | +21801,2016,9555 | ||
| 1093 | +21801,2017,9632 | ||
| 1094 | +21801,2018,9695 | ||
| 1095 | +21801,2019,9743 | ||
| 1096 | +21801,2020,9783 | ||
| 1097 | +21801,2021,9791 | ||
| 1098 | +21801,2022,9767 | ||
| 1099 | +21801,2023,9741 | ||
| 1100 | +21801,2024,9681 | ||
| 1101 | +21801,2025,9604 | ||
| 1102 | +21802,2016,7431 | ||
| 1103 | +21802,2017,7430 | ||
| 1104 | +21802,2018,7411 | ||
| 1105 | +21802,2019,7375 | ||
| 1106 | +21802,2020,7330 | ||
| 1107 | +21802,2021,7263 | ||
| 1108 | +21802,2022,7174 | ||
| 1109 | +21802,2023,7092 | ||
| 1110 | +21802,2024,6992 | ||
| 1111 | +21802,2025,6890 | ||
| 1112 | +21803,2016,2155 | ||
| 1113 | +21803,2017,2163 | ||
| 1114 | +21803,2018,2167 | ||
| 1115 | +21803,2019,2167 | ||
| 1116 | +21803,2020,2166 | ||
| 1117 | +21803,2021,2162 | ||
| 1118 | +21803,2022,2155 | ||
| 1119 | +21803,2023,2154 | ||
| 1120 | +21803,2024,2152 | ||
| 1121 | +21803,2025,2151 | ||
| 1122 | +21901,2016,5372 | ||
| 1123 | +21901,2017,5595 | ||
| 1124 | +21901,2018,5827 | ||
| 1125 | +21901,2019,6065 | ||
| 1126 | +21901,2020,6308 | ||
| 1127 | +21901,2021,6535 | ||
| 1128 | +21901,2022,6742 | ||
| 1129 | +21901,2023,6944 | ||
| 1130 | +21901,2024,7113 | ||
| 1131 | +21901,2025,7249 | ||
| 1132 | +21902,2016,3711 | ||
| 1133 | +21902,2017,3959 | ||
| 1134 | +21902,2018,4211 | ||
| 1135 | +21902,2019,4464 | ||
| 1136 | +21902,2020,4718 | ||
| 1137 | +21902,2021,4959 | ||
| 1138 | +21902,2022,5181 | ||
| 1139 | +21902,2023,5396 | ||
| 1140 | +21902,2024,5584 | ||
| 1141 | +21902,2025,5741 | ||
| 1142 | +22001,2016,55975 | ||
| 1143 | +22001,2017,57041 | ||
| 1144 | +22001,2018,58061 | ||
| 1145 | +22001,2019,59030 | ||
| 1146 | +22001,2020,59991 | ||
| 1147 | +22001,2021,60790 | ||
| 1148 | +22001,2022,61422 | ||
| 1149 | +22001,2023,62077 | ||
| 1150 | +22001,2024,62537 | ||
| 1151 | +22001,2025,62857 | ||
| 1152 | +22002,2016,11971 | ||
| 1153 | +22002,2017,11960 | ||
| 1154 | +22002,2018,11906 | ||
| 1155 | +22002,2019,11818 | ||
| 1156 | +22002,2020,11716 | ||
| 1157 | +22002,2021,11578 | ||
| 1158 | +22002,2022,11415 | ||
| 1159 | +22002,2023,11275 | ||
| 1160 | +22002,2024,11127 | ||
| 1161 | +22002,2025,10993 | ||
| 1162 | +30101,2016,685658 | ||
| 1163 | +30101,2017,691116 | ||
| 1164 | +30101,2018,695217 | ||
| 1165 | +30101,2019,698136 | ||
| 1166 | +30101,2020,700677 | ||
| 1167 | +30101,2021,701301 | ||
| 1168 | +30101,2022,700266 | ||
| 1169 | +30101,2023,700040 | ||
| 1170 | +30101,2024,698431 | ||
| 1171 | +30101,2025,696554 | ||
| 1172 | +30201,2016,24426 | ||
| 1173 | +30201,2017,24514 | ||
| 1174 | +30201,2018,24598 | ||
| 1175 | +30201,2019,24667 | ||
| 1176 | +30201,2020,24734 | ||
| 1177 | +30201,2021,24725 | ||
| 1178 | +30201,2022,24632 | ||
| 1179 | +30201,2023,24523 | ||
| 1180 | +30201,2024,24302 | ||
| 1181 | +30201,2025,24007 | ||
| 1182 | +30202,2016,7362 | ||
| 1183 | +30202,2017,7358 | ||
| 1184 | +30202,2018,7326 | ||
| 1185 | +30202,2019,7269 | ||
| 1186 | +30202,2020,7202 | ||
| 1187 | +30202,2021,7111 | ||
| 1188 | +30202,2022,7003 | ||
| 1189 | +30202,2023,6908 | ||
| 1190 | +30202,2024,6808 | ||
| 1191 | +30202,2025,6717 | ||
| 1192 | +30203,2016,6167 | ||
| 1193 | +30203,2017,6282 | ||
| 1194 | +30203,2018,6403 | ||
| 1195 | +30203,2019,6526 | ||
| 1196 | +30203,2020,6655 | ||
| 1197 | +30203,2021,6768 | ||
| 1198 | +30203,2022,6862 | ||
| 1199 | +30203,2023,6955 | ||
| 1200 | +30203,2024,7020 | ||
| 1201 | +30203,2025,7058 | ||
| 1202 | +30301,2016,24469 | ||
| 1203 | +30301,2017,24406 | ||
| 1204 | +30301,2018,24326 | ||
| 1205 | +30301,2019,24223 | ||
| 1206 | +30301,2020,24109 | ||
| 1207 | +30301,2021,23917 | ||
| 1208 | +30301,2022,23642 | ||
| 1209 | +30301,2023,23352 | ||
| 1210 | +30301,2024,22958 | ||
| 1211 | +30301,2025,22509 | ||
| 1212 | +30302,2016,13650 | ||
| 1213 | +30302,2017,13875 | ||
| 1214 | +30302,2018,14134 | ||
| 1215 | +30302,2019,14413 | ||
| 1216 | +30302,2020,14708 | ||
| 1217 | +30302,2021,14966 | ||
| 1218 | +30302,2022,15170 | ||
| 1219 | +30302,2023,15349 | ||
| 1220 | +30302,2024,15436 | ||
| 1221 | +30302,2025,15441 | ||
| 1222 | +30303,2016,19591 | ||
| 1223 | +30303,2017,19809 | ||
| 1224 | +30303,2018,20010 | ||
| 1225 | +30303,2019,20190 | ||
| 1226 | +30303,2020,20364 | ||
| 1227 | +30303,2021,20480 | ||
| 1228 | +30303,2022,20534 | ||
| 1229 | +30303,2023,20591 | ||
| 1230 | +30303,2024,20576 | ||
| 1231 | +30303,2025,20518 | ||
| 1232 | +30401,2016,9359 | ||
| 1233 | +30401,2017,9668 | ||
| 1234 | +30401,2018,9995 | ||
| 1235 | +30401,2019,10331 | ||
| 1236 | +30401,2020,10678 | ||
| 1237 | +30401,2021,11000 | ||
| 1238 | +30401,2022,11288 | ||
| 1239 | +30401,2023,11568 | ||
| 1240 | +30401,2024,11793 | ||
| 1241 | +30401,2025,11966 | ||
| 1242 | +30402,2016,7515 | ||
| 1243 | +30402,2017,7559 | ||
| 1244 | +30402,2018,7612 | ||
| 1245 | +30402,2019,7666 | ||
| 1246 | +30402,2020,7723 | ||
| 1247 | +30402,2021,7758 | ||
| 1248 | +30402,2022,7763 | ||
| 1249 | +30402,2023,7757 | ||
| 1250 | +30402,2024,7707 | ||
| 1251 | +30402,2025,7623 | ||
| 1252 | +30403,2016,21681 | ||
| 1253 | +30403,2017,22835 | ||
| 1254 | +30403,2018,23996 | ||
| 1255 | +30403,2019,25159 | ||
| 1256 | +30403,2020,26341 | ||
| 1257 | +30403,2021,27471 | ||
| 1258 | +30403,2022,28541 | ||
| 1259 | +30403,2023,29635 | ||
| 1260 | +30403,2024,30650 | ||
| 1261 | +30403,2025,31577 | ||
| 1262 | +30404,2016,4736 | ||
| 1263 | +30404,2017,4810 | ||
| 1264 | +30404,2018,4886 | ||
| 1265 | +30404,2019,4963 | ||
| 1266 | +30404,2020,5042 | ||
| 1267 | +30404,2021,5108 | ||
| 1268 | +30404,2022,5157 | ||
| 1269 | +30404,2023,5205 | ||
| 1270 | +30404,2024,5229 | ||
| 1271 | +30404,2025,5234 | ||
| 1272 | +30501,2016,10299 | ||
| 1273 | +30501,2017,10495 | ||
| 1274 | +30501,2018,10709 | ||
| 1275 | +30501,2019,10930 | ||
| 1276 | +30501,2020,11159 | ||
| 1277 | +30501,2021,11357 | ||
| 1278 | +30501,2022,11513 | ||
| 1279 | +30501,2023,11652 | ||
| 1280 | +30501,2024,11725 | ||
| 1281 | +30501,2025,11742 | ||
| 1282 | +30502,2016,9078 | ||
| 1283 | +30502,2017,9062 | ||
| 1284 | +30502,2018,9061 | ||
| 1285 | +30502,2019,9067 | ||
| 1286 | +30502,2020,9079 | ||
| 1287 | +30502,2021,9066 | ||
| 1288 | +30502,2022,9020 | ||
| 1289 | +30502,2023,8960 | ||
| 1290 | +30502,2024,8845 | ||
| 1291 | +30502,2025,8687 | ||
| 1292 | +30601,2016,11151 | ||
| 1293 | +30601,2017,11197 | ||
| 1294 | +30601,2018,11242 | ||
| 1295 | +30601,2019,11280 | ||
| 1296 | +30601,2020,11318 | ||
| 1297 | +30601,2021,11323 | ||
| 1298 | +30601,2022,11291 | ||
| 1299 | +30601,2023,11255 | ||
| 1300 | +30601,2024,11171 | ||
| 1301 | +30601,2025,11054 | ||
| 1302 | +30602,2016,10876 | ||
| 1303 | +30602,2017,10965 | ||
| 1304 | +30602,2018,11057 | ||
| 1305 | +30602,2019,11146 | ||
| 1306 | +30602,2020,11237 | ||
| 1307 | +30602,2021,11296 | ||
| 1308 | +30602,2022,11316 | ||
| 1309 | +30602,2023,11329 | ||
| 1310 | +30602,2024,11288 | ||
| 1311 | +30602,2025,11209 | ||
| 1312 | +30701,2016,20873 | ||
| 1313 | +30701,2017,20961 | ||
| 1314 | +30701,2018,21004 | ||
| 1315 | +30701,2019,21005 | ||
| 1316 | +30701,2020,20986 | ||
| 1317 | +30701,2021,20900 | ||
| 1318 | +30701,2022,20751 | ||
| 1319 | +30701,2023,20612 | ||
| 1320 | +30701,2024,20415 | ||
| 1321 | +30701,2025,20200 | ||
| 1322 | +30702,2016,7236 | ||
| 1323 | +30702,2017,7391 | ||
| 1324 | +30702,2018,7549 | ||
| 1325 | +30702,2019,7705 | ||
| 1326 | +30702,2020,7865 | ||
| 1327 | +30702,2021,8004 | ||
| 1328 | +30702,2022,8120 | ||
| 1329 | +30702,2023,8235 | ||
| 1330 | +30702,2024,8318 | ||
| 1331 | +30702,2025,8373 | ||
| 1332 | +30703,2016,4138 | ||
| 1333 | +30703,2017,4177 | ||
| 1334 | +30703,2018,4200 | ||
| 1335 | +30703,2019,4212 | ||
| 1336 | +30703,2020,4218 | ||
| 1337 | +30703,2021,4212 | ||
| 1338 | +30703,2022,4196 | ||
| 1339 | +30703,2023,4188 | ||
| 1340 | +30703,2024,4177 | ||
| 1341 | +30703,2025,4168 | ||
| 1342 | +30801,2016,23256 | ||
| 1343 | +30801,2017,23289 | ||
| 1344 | +30801,2018,23272 | ||
| 1345 | +30801,2019,23209 | ||
| 1346 | +30801,2020,23124 | ||
| 1347 | +30801,2021,22965 | ||
| 1348 | +30801,2022,22738 | ||
| 1349 | +30801,2023,22522 | ||
| 1350 | +30801,2024,22242 | ||
| 1351 | +30801,2025,21943 | ||
| 1352 | +30802,2016,7588 | ||
| 1353 | +30802,2017,7650 | ||
| 1354 | +30802,2018,7702 | ||
| 1355 | +30802,2019,7745 | ||
| 1356 | +30802,2020,7784 | ||
| 1357 | +30802,2021,7800 | ||
| 1358 | +30802,2022,7793 | ||
| 1359 | +30802,2023,7785 | ||
| 1360 | +30802,2024,7751 | ||
| 1361 | +30802,2025,7703 | ||
| 1362 | +30803,2016,6580 | ||
| 1363 | +30803,2017,6891 | ||
| 1364 | +30803,2018,7217 | ||
| 1365 | +30803,2019,7552 | ||
| 1366 | +30803,2020,7897 | ||
| 1367 | +30803,2021,8223 | ||
| 1368 | +30803,2022,8525 | ||
| 1369 | +30803,2023,8821 | ||
| 1370 | +30803,2024,9074 | ||
| 1371 | +30803,2025,9286 | ||
| 1372 | +30901,2016,154428 | ||
| 1373 | +30901,2017,157696 | ||
| 1374 | +30901,2018,160769 | ||
| 1375 | +30901,2019,163650 | ||
| 1376 | +30901,2020,166495 | ||
| 1377 | +30901,2021,168900 | ||
| 1378 | +30901,2022,170877 | ||
| 1379 | +30901,2023,172989 | ||
| 1380 | +30901,2024,174658 | ||
| 1381 | +30901,2025,176045 | ||
| 1382 | +30902,2016,47969 | ||
| 1383 | +30902,2017,49445 | ||
| 1384 | +30902,2018,50901 | ||
| 1385 | +30902,2019,52330 | ||
| 1386 | +30902,2020,53769 | ||
| 1387 | +30902,2021,55078 | ||
| 1388 | +30902,2022,56248 | ||
| 1389 | +30902,2023,57452 | ||
| 1390 | +30902,2024,58486 | ||
| 1391 | +30902,2025,59383 | ||
| 1392 | +30903,2016,60144 | ||
| 1393 | +30903,2017,61097 | ||
| 1394 | +30903,2018,61919 | ||
| 1395 | +30903,2019,62626 | ||
| 1396 | +30903,2020,63293 | ||
| 1397 | +30903,2021,63783 | ||
| 1398 | +30903,2022,64121 | ||
| 1399 | +30903,2023,64535 | ||
| 1400 | +30903,2024,64826 | ||
| 1401 | +30903,2025,65073 | ||
| 1402 | +30904,2016,57602 | ||
| 1403 | +30904,2017,58199 | ||
| 1404 | +30904,2018,58600 | ||
| 1405 | +30904,2019,58843 | ||
| 1406 | +30904,2020,59018 | ||
| 1407 | +30904,2021,59021 | ||
| 1408 | +30904,2022,58898 | ||
| 1409 | +30904,2023,58883 | ||
| 1410 | +30904,2024,58818 | ||
| 1411 | +30904,2025,58797 | ||
| 1412 | +30905,2016,59048 | ||
| 1413 | +30905,2017,60586 | ||
| 1414 | +30905,2018,62095 | ||
| 1415 | +30905,2019,63565 | ||
| 1416 | +30905,2020,65042 | ||
| 1417 | +30905,2021,66354 | ||
| 1418 | +30905,2022,67492 | ||
| 1419 | +30905,2023,68661 | ||
| 1420 | +30905,2024,69619 | ||
| 1421 | +30905,2025,70415 | ||
| 1422 | +31001,2016,197244 | ||
| 1423 | +31001,2017,202205 | ||
| 1424 | +31001,2018,206882 | ||
| 1425 | +31001,2019,211296 | ||
| 1426 | +31001,2020,215664 | ||
| 1427 | +31001,2021,219482 | ||
| 1428 | +31001,2022,222783 | ||
| 1429 | +31001,2023,226315 | ||
| 1430 | +31001,2024,229346 | ||
| 1431 | +31001,2025,232058 | ||
| 1432 | +31002,2016,21173 | ||
| 1433 | +31002,2017,21493 | ||
| 1434 | +31002,2018,21794 | ||
| 1435 | +31002,2019,22073 | ||
| 1436 | +31002,2020,22346 | ||
| 1437 | +31002,2021,22555 | ||
| 1438 | +31002,2022,22697 | ||
| 1439 | +31002,2023,22843 | ||
| 1440 | +31002,2024,22911 | ||
| 1441 | +31002,2025,22928 | ||
| 1442 | +31003,2016,81935 | ||
| 1443 | +31003,2017,84258 | ||
| 1444 | +31003,2018,86524 | ||
| 1445 | +31003,2019,88725 | ||
| 1446 | +31003,2020,90936 | ||
| 1447 | +31003,2021,92926 | ||
| 1448 | +31003,2022,94688 | ||
| 1449 | +31003,2023,96521 | ||
| 1450 | +31003,2024,98094 | ||
| 1451 | +31003,2025,99463 | ||
| 1452 | +31101,2016,26047 | ||
| 1453 | +31101,2017,26160 | ||
| 1454 | +31101,2018,26255 | ||
| 1455 | +31101,2019,26324 | ||
| 1456 | +31101,2020,26385 | ||
| 1457 | +31101,2021,26364 | ||
| 1458 | +31101,2022,26257 | ||
| 1459 | +31101,2023,26142 | ||
| 1460 | +31101,2024,25920 | ||
| 1461 | +31101,2025,25634 | ||
| 1462 | +31201,2016,15733 | ||
| 1463 | +31201,2017,15813 | ||
| 1464 | +31201,2018,15862 | ||
| 1465 | +31201,2019,15884 | ||
| 1466 | +31201,2020,15893 | ||
| 1467 | +31201,2021,15853 | ||
| 1468 | +31201,2022,15767 | ||
| 1469 | +31201,2023,15690 | ||
| 1470 | +31201,2024,15570 | ||
| 1471 | +31201,2025,15432 | ||
| 1472 | +31202,2016,11031 | ||
| 1473 | +31202,2017,11227 | ||
| 1474 | +31202,2018,11436 | ||
| 1475 | +31202,2019,11649 | ||
| 1476 | +31202,2020,11870 | ||
| 1477 | +31202,2021,12060 | ||
| 1478 | +31202,2022,12211 | ||
| 1479 | +31202,2023,12354 | ||
| 1480 | +31202,2024,12438 | ||
| 1481 | +31202,2025,12471 | ||
| 1482 | +31203,2016,11506 | ||
| 1483 | +31203,2017,11684 | ||
| 1484 | +31203,2018,11884 | ||
| 1485 | +31203,2019,12095 | ||
| 1486 | +31203,2020,12317 | ||
| 1487 | +31203,2021,12508 | ||
| 1488 | +31203,2022,12656 | ||
| 1489 | +31203,2023,12790 | ||
| 1490 | +31203,2024,12854 | ||
| 1491 | +31203,2025,12859 | ||
| 1492 | +31204,2016,24834 | ||
| 1493 | +31204,2017,25452 | ||
| 1494 | +31204,2018,26033 | ||
| 1495 | +31204,2019,26581 | ||
| 1496 | +31204,2020,27123 | ||
| 1497 | +31204,2021,27596 | ||
| 1498 | +31204,2022,28003 | ||
| 1499 | +31204,2023,28438 | ||
| 1500 | +31204,2024,28811 | ||
| 1501 | +31204,2025,29142 | ||
| 1502 | +31205,2016,53785 | ||
| 1503 | +31205,2017,55614 | ||
| 1504 | +31205,2018,57480 | ||
| 1505 | +31205,2019,59355 | ||
| 1506 | +31205,2020,61267 | ||
| 1507 | +31205,2021,63034 | ||
| 1508 | +31205,2022,64625 | ||
| 1509 | +31205,2023,66216 | ||
| 1510 | +31205,2024,67557 | ||
| 1511 | +31205,2025,68671 | ||
| 1512 | +31206,2016,37235 | ||
| 1513 | +31206,2017,38654 | ||
| 1514 | +31206,2018,40072 | ||
| 1515 | +31206,2019,41480 | ||
| 1516 | +31206,2020,42907 | ||
| 1517 | +31206,2021,44240 | ||
| 1518 | +31206,2022,45465 | ||
| 1519 | +31206,2023,46722 | ||
| 1520 | +31206,2024,47845 | ||
| 1521 | +31206,2025,48843 | ||
| 1522 | +31301,2016,20970 | ||
| 1523 | +31301,2017,21060 | ||
| 1524 | +31301,2018,21138 | ||
| 1525 | +31301,2019,21200 | ||
| 1526 | +31301,2020,21256 | ||
| 1527 | +31301,2021,21247 | ||
| 1528 | +31301,2022,21169 | ||
| 1529 | +31301,2023,21082 | ||
| 1530 | +31301,2024,20907 | ||
| 1531 | +31301,2025,20677 | ||
| 1532 | +31302,2016,5815 | ||
| 1533 | +31302,2017,5820 | ||
| 1534 | +31302,2018,5809 | ||
| 1535 | +31302,2019,5786 | ||
| 1536 | +31302,2020,5756 | ||
| 1537 | +31302,2021,5707 | ||
| 1538 | +31302,2022,5643 | ||
| 1539 | +31302,2023,5584 | ||
| 1540 | +31302,2024,5513 | ||
| 1541 | +31302,2025,5441 | ||
| 1542 | +31303,2016,3556 | ||
| 1543 | +31303,2017,3576 | ||
| 1544 | +31303,2018,3605 | ||
| 1545 | +31303,2019,3638 | ||
| 1546 | +31303,2020,3674 | ||
| 1547 | +31303,2021,3699 | ||
| 1548 | +31303,2022,3710 | ||
| 1549 | +31303,2023,3713 | ||
| 1550 | +31303,2024,3690 | ||
| 1551 | +31303,2025,3649 | ||
| 1552 | +31304,2016,7824 | ||
| 1553 | +31304,2017,7948 | ||
| 1554 | +31304,2018,8057 | ||
| 1555 | +31304,2019,8154 | ||
| 1556 | +31304,2020,8245 | ||
| 1557 | +31304,2021,8313 | ||
| 1558 | +31304,2022,8359 | ||
| 1559 | +31304,2023,8413 | ||
| 1560 | +31304,2024,8446 | ||
| 1561 | +31304,2025,8472 | ||
| 1562 | +31401,2016,32219 | ||
| 1563 | +31401,2017,32917 | ||
| 1564 | +31401,2018,33609 | ||
| 1565 | +31401,2019,34285 | ||
| 1566 | +31401,2020,34967 | ||
| 1567 | +31401,2021,35559 | ||
| 1568 | +31401,2022,36052 | ||
| 1569 | +31401,2023,36551 | ||
| 1570 | +31401,2024,36923 | ||
| 1571 | +31401,2025,37196 | ||
| 1572 | +31402,2016,8906 | ||
| 1573 | +31402,2017,8983 | ||
| 1574 | +31402,2018,9035 | ||
| 1575 | +31402,2019,9067 | ||
| 1576 | +31402,2020,9091 | ||
| 1577 | +31402,2021,9088 | ||
| 1578 | +31402,2022,9064 | ||
| 1579 | +31402,2023,9051 | ||
| 1580 | +31402,2024,9025 | ||
| 1581 | +31402,2025,8995 | ||
| 1582 | +31403,2016,14367 | ||
| 1583 | +31403,2017,14387 | ||
| 1584 | +31403,2018,14379 | ||
| 1585 | +31403,2019,14345 | ||
| 1586 | +31403,2020,14296 | ||
| 1587 | +31403,2021,14200 | ||
| 1588 | +31403,2022,14057 | ||
| 1589 | +31403,2023,13916 | ||
| 1590 | +31403,2024,13728 | ||
| 1591 | +31403,2025,13524 | ||
| 1592 | +31404,2016,1390 | ||
| 1593 | +31404,2017,1392 | ||
| 1594 | +31404,2018,1391 | ||
| 1595 | +31404,2019,1388 | ||
| 1596 | +31404,2020,1384 | ||
| 1597 | +31404,2021,1376 | ||
| 1598 | +31404,2022,1367 | ||
| 1599 | +31404,2023,1360 | ||
| 1600 | +31404,2024,1351 | ||
| 1601 | +31404,2025,1343 | ||
| 1602 | +31405,2016,3223 | ||
| 1603 | +31405,2017,3329 | ||
| 1604 | +31405,2018,3434 | ||
| 1605 | +31405,2019,3537 | ||
| 1606 | +31405,2020,3641 | ||
| 1607 | +31405,2021,3737 | ||
| 1608 | +31405,2022,3824 | ||
| 1609 | +31405,2023,3915 | ||
| 1610 | +31405,2024,3996 | ||
| 1611 | +31405,2025,4069 | ||
| 1612 | +31501,2016,7830 | ||
| 1613 | +31501,2017,7950 | ||
| 1614 | +31501,2018,8081 | ||
| 1615 | +31501,2019,8216 | ||
| 1616 | +31501,2020,8356 | ||
| 1617 | +31501,2021,8475 | ||
| 1618 | +31501,2022,8565 | ||
| 1619 | +31501,2023,8647 | ||
| 1620 | +31501,2024,8685 | ||
| 1621 | +31501,2025,8686 | ||
| 1622 | +31601,2016,21962 | ||
| 1623 | +31601,2017,21802 | ||
| 1624 | +31601,2018,21592 | ||
| 1625 | +31601,2019,21337 | ||
| 1626 | +31601,2020,21061 | ||
| 1627 | +31601,2021,20715 | ||
| 1628 | +31601,2022,20308 | ||
| 1629 | +31601,2023,19910 | ||
| 1630 | +31601,2024,19458 | ||
| 1631 | +31601,2025,19004 | ||
| 1632 | +31602,2016,23933 | ||
| 1633 | +31602,2017,24593 | ||
| 1634 | +31602,2018,25229 | ||
| 1635 | +31602,2019,25841 | ||
| 1636 | +31602,2020,26453 | ||
| 1637 | +31602,2021,27001 | ||
| 1638 | +31602,2022,27484 | ||
| 1639 | +31602,2023,27993 | ||
| 1640 | +31602,2024,28434 | ||
| 1641 | +31602,2025,28824 | ||
| 1642 | +40101,2016,293419 | ||
| 1643 | +40101,2017,297672 | ||
| 1644 | +40101,2018,301349 | ||
| 1645 | +40101,2019,304501 | ||
| 1646 | +40101,2020,307451 | ||
| 1647 | +40101,2021,309498 | ||
| 1648 | +40101,2022,310720 | ||
| 1649 | +40101,2023,312184 | ||
| 1650 | +40101,2024,312889 | ||
| 1651 | +40101,2025,313210 | ||
| 1652 | +40102,2016,24693 | ||
| 1653 | +40102,2017,24779 | ||
| 1654 | +40102,2018,24815 | ||
| 1655 | +40102,2019,24803 | ||
| 1656 | +40102,2020,24767 | ||
| 1657 | +40102,2021,24650 | ||
| 1658 | +40102,2022,24458 | ||
| 1659 | +40102,2023,24274 | ||
| 1660 | +40102,2024,24017 | ||
| 1661 | +40102,2025,23730 | ||
| 1662 | +40103,2016,9781 | ||
| 1663 | +40103,2017,9940 | ||
| 1664 | +40103,2018,10074 | ||
| 1665 | +40103,2019,10184 | ||
| 1666 | +40103,2020,10285 | ||
| 1667 | +40103,2021,10355 | ||
| 1668 | +40103,2022,10397 | ||
| 1669 | +40103,2023,10450 | ||
| 1670 | +40103,2024,10482 | ||
| 1671 | +40103,2025,10507 | ||
| 1672 | +40104,2016,14198 | ||
| 1673 | +40104,2017,14527 | ||
| 1674 | +40104,2018,14870 | ||
| 1675 | +40104,2019,15218 | ||
| 1676 | +40104,2020,15572 | ||
| 1677 | +40104,2021,15881 | ||
| 1678 | +40104,2022,16134 | ||
| 1679 | +40104,2023,16369 | ||
| 1680 | +40104,2024,16519 | ||
| 1681 | +40104,2025,16598 | ||
| 1682 | +40201,2016,32479 | ||
| 1683 | +40201,2017,33209 | ||
| 1684 | +40201,2018,33917 | ||
| 1685 | +40201,2019,34595 | ||
| 1686 | +40201,2020,35267 | ||
| 1687 | +40201,2021,35839 | ||
| 1688 | +40201,2022,36305 | ||
| 1689 | +40201,2023,36775 | ||
| 1690 | +40201,2024,37115 | ||
| 1691 | +40201,2025,37354 | ||
| 1692 | +40202,2016,4604 | ||
| 1693 | +40202,2017,4726 | ||
| 1694 | +40202,2018,4844 | ||
| 1695 | +40202,2019,4959 | ||
| 1696 | +40202,2020,5073 | ||
| 1697 | +40202,2021,5173 | ||
| 1698 | +40202,2022,5257 | ||
| 1699 | +40202,2023,5340 | ||
| 1700 | +40202,2024,5405 | ||
| 1701 | +40202,2025,5452 | ||
| 1702 | +40301,2016,11476 | ||
| 1703 | +40301,2017,12190 | ||
| 1704 | +40301,2018,12933 | ||
| 1705 | +40301,2019,13686 | ||
| 1706 | +40301,2020,14442 | ||
| 1707 | +40301,2021,15145 | ||
| 1708 | +40301,2022,15774 | ||
| 1709 | +40301,2023,16358 | ||
| 1710 | +40301,2024,16824 | ||
| 1711 | +40301,2025,17161 | ||
| 1712 | +40302,2016,2138 | ||
| 1713 | +40302,2017,2210 | ||
| 1714 | +40302,2018,2282 | ||
| 1715 | +40302,2019,2354 | ||
| 1716 | +40302,2020,2424 | ||
| 1717 | +40302,2021,2486 | ||
| 1718 | +40302,2022,2538 | ||
| 1719 | +40302,2023,2587 | ||
| 1720 | +40302,2024,2622 | ||
| 1721 | +40302,2025,2647 | ||
| 1722 | +40401,2016,4943 | ||
| 1723 | +40401,2017,5203 | ||
| 1724 | +40401,2018,5484 | ||
| 1725 | +40401,2019,5775 | ||
| 1726 | +40401,2020,6072 | ||
| 1727 | +40401,2021,6349 | ||
| 1728 | +40401,2022,6595 | ||
| 1729 | +40401,2023,6819 | ||
| 1730 | +40401,2024,6988 | ||
| 1731 | +40401,2025,7100 | ||
| 1732 | +40402,2016,5740 | ||
| 1733 | +40402,2017,5819 | ||
| 1734 | +40402,2018,5888 | ||
| 1735 | +40402,2019,5947 | ||
| 1736 | +40402,2020,6002 | ||
| 1737 | +40402,2021,6038 | ||
| 1738 | +40402,2022,6058 | ||
| 1739 | +40402,2023,6081 | ||
| 1740 | +40402,2024,6087 | ||
| 1741 | +40402,2025,6087 | ||
| 1742 | +40501,2016,1772 | ||
| 1743 | +40501,2017,2084 | ||
| 1744 | +40501,2018,2417 | ||
| 1745 | +40501,2019,2761 | ||
| 1746 | +40501,2020,3105 | ||
| 1747 | +40501,2021,3430 | ||
| 1748 | +40501,2022,3723 | ||
| 1749 | +40501,2023,3981 | ||
| 1750 | +40501,2024,4181 | ||
| 1751 | +40501,2025,4313 | ||
| 1752 | +40502,2016,4725 | ||
| 1753 | +40502,2017,4738 | ||
| 1754 | +40502,2018,4720 | ||
| 1755 | +40502,2019,4680 | ||
| 1756 | +40502,2020,4629 | ||
| 1757 | +40502,2021,4567 | ||
| 1758 | +40502,2022,4503 | ||
| 1759 | +40502,2023,4463 | ||
| 1760 | +40502,2024,4439 | ||
| 1761 | +40502,2025,4441 | ||
| 1762 | +40503,2016,2067 | ||
| 1763 | +40503,2017,2032 | ||
| 1764 | +40503,2018,1982 | ||
| 1765 | +40503,2019,1922 | ||
| 1766 | +40503,2020,1858 | ||
| 1767 | +40503,2021,1787 | ||
| 1768 | +40503,2022,1716 | ||
| 1769 | +40503,2023,1654 | ||
| 1770 | +40503,2024,1599 | ||
| 1771 | +40503,2025,1559 | ||
| 1772 | +40504,2016,660 | ||
| 1773 | +40504,2017,699 | ||
| 1774 | +40504,2018,738 | ||
| 1775 | +40504,2019,775 | ||
| 1776 | +40504,2020,811 | ||
| 1777 | +40504,2021,843 | ||
| 1778 | +40504,2022,870 | ||
| 1779 | +40504,2023,896 | ||
| 1780 | +40504,2024,915 | ||
| 1781 | +40504,2025,930 | ||
| 1782 | +40505,2016,3265 | ||
| 1783 | +40505,2017,3386 | ||
| 1784 | +40505,2018,3499 | ||
| 1785 | +40505,2019,3605 | ||
| 1786 | +40505,2020,3708 | ||
| 1787 | +40505,2021,3800 | ||
| 1788 | +40505,2022,3885 | ||
| 1789 | +40505,2023,3974 | ||
| 1790 | +40505,2024,4058 | ||
| 1791 | +40505,2025,4134 | ||
| 1792 | +40601,2016,8513 | ||
| 1793 | +40601,2017,8694 | ||
| 1794 | +40601,2018,8866 | ||
| 1795 | +40601,2019,9029 | ||
| 1796 | +40601,2020,9190 | ||
| 1797 | +40601,2021,9326 | ||
| 1798 | +40601,2022,9434 | ||
| 1799 | +40601,2023,9546 | ||
| 1800 | +40601,2024,9628 | ||
| 1801 | +40601,2025,9683 | ||
| 1802 | +40602,2016,5909 | ||
| 1803 | +40602,2017,5754 | ||
| 1804 | +40602,2018,5580 | ||
| 1805 | +40602,2019,5392 | ||
| 1806 | +40602,2020,5202 | ||
| 1807 | +40602,2021,5006 | ||
| 1808 | +40602,2022,4810 | ||
| 1809 | +40602,2023,4639 | ||
| 1810 | +40602,2024,4483 | ||
| 1811 | +40602,2025,4355 | ||
| 1812 | +40603,2016,3542 | ||
| 1813 | +40603,2017,3592 | ||
| 1814 | +40603,2018,3642 | ||
| 1815 | +40603,2019,3691 | ||
| 1816 | +40603,2020,3740 | ||
| 1817 | +40603,2021,3780 | ||
| 1818 | +40603,2022,3807 | ||
| 1819 | +40603,2023,3834 | ||
| 1820 | +40603,2024,3845 | ||
| 1821 | +40603,2025,3843 | ||
| 1822 | +40701,2016,25445 | ||
| 1823 | +40701,2017,25086 | ||
| 1824 | +40701,2018,24628 | ||
| 1825 | +40701,2019,24097 | ||
| 1826 | +40701,2020,23540 | ||
| 1827 | +40701,2021,22926 | ||
| 1828 | +40701,2022,22286 | ||
| 1829 | +40701,2023,21722 | ||
| 1830 | +40701,2024,21186 | ||
| 1831 | +40701,2025,20733 | ||
| 1832 | +40702,2016,5451 | ||
| 1833 | +40702,2017,5592 | ||
| 1834 | +40702,2018,5732 | ||
| 1835 | +40702,2019,5869 | ||
| 1836 | +40702,2020,6006 | ||
| 1837 | +40702,2021,6127 | ||
| 1838 | +40702,2022,6229 | ||
| 1839 | +40702,2023,6331 | ||
| 1840 | +40702,2024,6409 | ||
| 1841 | +40702,2025,6466 | ||
| 1842 | +40801,2016,13776 | ||
| 1843 | +40801,2017,14214 | ||
| 1844 | +40801,2018,14641 | ||
| 1845 | +40801,2019,15053 | ||
| 1846 | +40801,2020,15460 | ||
| 1847 | +40801,2021,15820 | ||
| 1848 | +40801,2022,16129 | ||
| 1849 | +40801,2023,16435 | ||
| 1850 | +40801,2024,16679 | ||
| 1851 | +40801,2025,16868 | ||
| 1852 | +40802,2016,3208 | ||
| 1853 | +40802,2017,3242 | ||
| 1854 | +40802,2018,3275 | ||
| 1855 | +40802,2019,3305 | ||
| 1856 | +40802,2020,3334 | ||
| 1857 | +40802,2021,3352 | ||
| 1858 | +40802,2022,3359 | ||
| 1859 | +40802,2023,3365 | ||
| 1860 | +40802,2024,3356 | ||
| 1861 | +40802,2025,3338 | ||
| 1862 | +40901,2016,10568 | ||
| 1863 | +40901,2017,11343 | ||
| 1864 | +40901,2018,12130 | ||
| 1865 | +40901,2019,12914 | ||
| 1866 | +40901,2020,13693 | ||
| 1867 | +40901,2021,14416 | ||
| 1868 | +40901,2022,15068 | ||
| 1869 | +40901,2023,15681 | ||
| 1870 | +40901,2024,16189 | ||
| 1871 | +40901,2025,16576 | ||
| 1872 | +40902,2016,1089 | ||
| 1873 | +40902,2017,1139 | ||
| 1874 | +40902,2018,1189 | ||
| 1875 | +40902,2019,1239 | ||
| 1876 | +40902,2020,1290 | ||
| 1877 | +40902,2021,1338 | ||
| 1878 | +40902,2022,1383 | ||
| 1879 | +40902,2023,1428 | ||
| 1880 | +40902,2024,1469 | ||
| 1881 | +40902,2025,1502 | ||
| 1882 | +40903,2016,2227 | ||
| 1883 | +40903,2017,2271 | ||
| 1884 | +40903,2018,2314 | ||
| 1885 | +40903,2019,2354 | ||
| 1886 | +40903,2020,2392 | ||
| 1887 | +40903,2021,2422 | ||
| 1888 | +40903,2022,2443 | ||
| 1889 | +40903,2023,2462 | ||
| 1890 | +40903,2024,2470 | ||
| 1891 | +40903,2025,2471 | ||
| 1892 | +41001,2016,11403 | ||
| 1893 | +41001,2017,11637 | ||
| 1894 | +41001,2018,11854 | ||
| 1895 | +41001,2019,12056 | ||
| 1896 | +41001,2020,12254 | ||
| 1897 | +41001,2021,12417 | ||
| 1898 | +41001,2022,12547 | ||
| 1899 | +41001,2023,12683 | ||
| 1900 | +41001,2024,12785 | ||
| 1901 | +41001,2025,12861 | ||
| 1902 | +41101,2016,5604 | ||
| 1903 | +41101,2017,5636 | ||
| 1904 | +41101,2018,5664 | ||
| 1905 | +41101,2019,5686 | ||
| 1906 | +41101,2020,5704 | ||
| 1907 | +41101,2021,5703 | ||
| 1908 | +41101,2022,5682 | ||
| 1909 | +41101,2023,5657 | ||
| 1910 | +41101,2024,5607 | ||
| 1911 | +41101,2025,5541 | ||
| 1912 | +41201,2016,5829 | ||
| 1913 | +41201,2017,5955 | ||
| 1914 | +41201,2018,6077 | ||
| 1915 | +41201,2019,6194 | ||
| 1916 | +41201,2020,6310 | ||
| 1917 | +41201,2021,6408 | ||
| 1918 | +41201,2022,6485 | ||
| 1919 | +41201,2023,6562 | ||
| 1920 | +41201,2024,6614 | ||
| 1921 | +41201,2025,6647 | ||
| 1922 | +41202,2016,2402 | ||
| 1923 | +41202,2017,2506 | ||
| 1924 | +41202,2018,2612 | ||
| 1925 | +41202,2019,2718 | ||
| 1926 | +41202,2020,2826 | ||
| 1927 | +41202,2021,2928 | ||
| 1928 | +41202,2022,3022 | ||
| 1929 | +41202,2023,3116 | ||
| 1930 | +41202,2024,3198 | ||
| 1931 | +41202,2025,3261 | ||
| 1932 | +41301,2016,6158 | ||
| 1933 | +41301,2017,6284 | ||
| 1934 | +41301,2018,6407 | ||
| 1935 | +41301,2019,6526 | ||
| 1936 | +41301,2020,6644 | ||
| 1937 | +41301,2021,6743 | ||
| 1938 | +41301,2022,6822 | ||
| 1939 | +41301,2023,6901 | ||
| 1940 | +41301,2024,6955 | ||
| 1941 | +41301,2025,6986 | ||
| 1942 | +41401,2016,14389 | ||
| 1943 | +41401,2017,14471 | ||
| 1944 | +41401,2018,14513 | ||
| 1945 | +41401,2019,14521 | ||
| 1946 | +41401,2020,14513 | ||
| 1947 | +41401,2021,14460 | ||
| 1948 | +41401,2022,14367 | ||
| 1949 | +41401,2023,14289 | ||
| 1950 | +41401,2024,14182 | ||
| 1951 | +41401,2025,14067 | ||
| 1952 | +41501,2016,739 | ||
| 1953 | +41501,2017,818 | ||
| 1954 | +41501,2018,900 | ||
| 1955 | +41501,2019,983 | ||
| 1956 | +41501,2020,1065 | ||
| 1957 | +41501,2021,1141 | ||
| 1958 | +41501,2022,1209 | ||
| 1959 | +41501,2023,1270 | ||
| 1960 | +41501,2024,1317 | ||
| 1961 | +41501,2025,1353 | ||
| 1962 | +41502,2016,892 | ||
| 1963 | +41502,2017,935 | ||
| 1964 | +41502,2018,977 | ||
| 1965 | +41502,2019,1017 | ||
| 1966 | +41502,2020,1058 | ||
| 1967 | +41502,2021,1095 | ||
| 1968 | +41502,2022,1129 | ||
| 1969 | +41502,2023,1162 | ||
| 1970 | +41502,2024,1191 | ||
| 1971 | +41502,2025,1214 | ||
| 1972 | +41503,2016,981 | ||
| 1973 | +41503,2017,1008 | ||
| 1974 | +41503,2018,1033 | ||
| 1975 | +41503,2019,1056 | ||
| 1976 | +41503,2020,1079 | ||
| 1977 | +41503,2021,1102 | ||
| 1978 | +41503,2022,1124 | ||
| 1979 | +41503,2023,1151 | ||
| 1980 | +41503,2024,1179 | ||
| 1981 | +41503,2025,1206 | ||
| 1982 | +41601,2016,5920 | ||
| 1983 | +41601,2017,5988 | ||
| 1984 | +41601,2018,6056 | ||
| 1985 | +41601,2019,6121 | ||
| 1986 | +41601,2020,6187 | ||
| 1987 | +41601,2021,6236 | ||
| 1988 | +41601,2022,6264 | ||
| 1989 | +41601,2023,6291 | ||
| 1990 | +41601,2024,6292 | ||
| 1991 | +41601,2025,6271 | ||
| 1992 | +50101,2016,211612 | ||
| 1993 | +50101,2017,214659 | ||
| 1994 | +50101,2018,217365 | ||
| 1995 | +50101,2019,219794 | ||
| 1996 | +50101,2020,222208 | ||
| 1997 | +50101,2021,224130 | ||
| 1998 | +50101,2022,225644 | ||
| 1999 | +50101,2023,227552 | ||
| 2000 | +50101,2024,229153 | ||
| 2001 | +50101,2025,230710 | ||
| 2002 | +50102,2016,29542 | ||
| 2003 | +50102,2017,29783 | ||
| 2004 | +50102,2018,29967 | ||
| 2005 | +50102,2019,30106 | ||
| 2006 | +50102,2020,30236 | ||
| 2007 | +50102,2021,30294 | ||
| 2008 | +50102,2022,30294 | ||
| 2009 | +50102,2023,30346 | ||
| 2010 | +50102,2024,30358 | ||
| 2011 | +50102,2025,30374 | ||
| 2012 | +50103,2016,10292 | ||
| 2013 | +50103,2017,10624 | ||
| 2014 | +50103,2018,10964 | ||
| 2015 | +50103,2019,11306 | ||
| 2016 | +50103,2020,11657 | ||
| 2017 | +50103,2021,11981 | ||
| 2018 | +50103,2022,12273 | ||
| 2019 | +50103,2023,12568 | ||
| 2020 | +50103,2024,12816 | ||
| 2021 | +50103,2025,13024 | ||
| 2022 | +50104,2016,3058 | ||
| 2023 | +50104,2017,3101 | ||
| 2024 | +50104,2018,3138 | ||
| 2025 | +50104,2019,3170 | ||
| 2026 | +50104,2020,3201 | ||
| 2027 | +50104,2021,3223 | ||
| 2028 | +50104,2022,3238 | ||
| 2029 | +50104,2023,3258 | ||
| 2030 | +50104,2024,3272 | ||
| 2031 | +50104,2025,3285 | ||
| 2032 | +50201,2016,23705 | ||
| 2033 | +50201,2017,23845 | ||
| 2034 | +50201,2018,23941 | ||
| 2035 | +50201,2019,24001 | ||
| 2036 | +50201,2020,24053 | ||
| 2037 | +50201,2021,24045 | ||
| 2038 | +50201,2022,23986 | ||
| 2039 | +50201,2023,23961 | ||
| 2040 | +50201,2024,23895 | ||
| 2041 | +50201,2025,23829 | ||
| 2042 | +50202,2016,17191 | ||
| 2043 | +50202,2017,17235 | ||
| 2044 | +50202,2018,17246 | ||
| 2045 | +50202,2019,17229 | ||
| 2046 | +50202,2020,17204 | ||
| 2047 | +50202,2021,17134 | ||
| 2048 | +50202,2022,17026 | ||
| 2049 | +50202,2023,16941 | ||
| 2050 | +50202,2024,16826 | ||
| 2051 | +50202,2025,16711 | ||
| 2052 | +50203,2016,43890 | ||
| 2053 | +50203,2017,44066 | ||
| 2054 | +50203,2018,44174 | ||
| 2055 | +50203,2019,44223 | ||
| 2056 | +50203,2020,44263 | ||
| 2057 | +50203,2021,44196 | ||
| 2058 | +50203,2022,44037 | ||
| 2059 | +50203,2023,43938 | ||
| 2060 | +50203,2024,43761 | ||
| 2061 | +50203,2025,43566 | ||
| 2062 | +50204,2016,8622 | ||
| 2063 | +50204,2017,8659 | ||
| 2064 | +50204,2018,8679 | ||
| 2065 | +50204,2019,8684 | ||
| 2066 | +50204,2020,8689 | ||
| 2067 | +50204,2021,8674 | ||
| 2068 | +50204,2022,8647 | ||
| 2069 | +50204,2023,8638 | ||
| 2070 | +50204,2024,8624 | ||
| 2071 | +50204,2025,8612 | ||
| 2072 | +50301,2016,35244 | ||
| 2073 | +50301,2017,35165 | ||
| 2074 | +50301,2018,35057 | ||
| 2075 | +50301,2019,34918 | ||
| 2076 | +50301,2020,34778 | ||
| 2077 | +50301,2021,34549 | ||
| 2078 | +50301,2022,34231 | ||
| 2079 | +50301,2023,33935 | ||
| 2080 | +50301,2024,33539 | ||
| 2081 | +50301,2025,33112 | ||
| 2082 | +50302,2016,10376 | ||
| 2083 | +50302,2017,10354 | ||
| 2084 | +50302,2018,10315 | ||
| 2085 | +50302,2019,10260 | ||
| 2086 | +50302,2020,10200 | ||
| 2087 | +50302,2021,10112 | ||
| 2088 | +50302,2022,9998 | ||
| 2089 | +50302,2023,9892 | ||
| 2090 | +50302,2024,9763 | ||
| 2091 | +50302,2025,9630 | ||
| 2092 | +50303,2016,12960 | ||
| 2093 | +50303,2017,13217 | ||
| 2094 | +50303,2018,13491 | ||
| 2095 | +50303,2019,13775 | ||
| 2096 | +50303,2020,14076 | ||
| 2097 | +50303,2021,14351 | ||
| 2098 | +50303,2022,14593 | ||
| 2099 | +50303,2023,14843 | ||
| 2100 | +50303,2024,15041 | ||
| 2101 | +50303,2025,15194 | ||
| 2102 | +50401,2016,16491 | ||
| 2103 | +50401,2017,16642 | ||
| 2104 | +50401,2018,16774 | ||
| 2105 | +50401,2019,16889 | ||
| 2106 | +50401,2020,17003 | ||
| 2107 | +50401,2021,17075 | ||
| 2108 | +50401,2022,17107 | ||
| 2109 | +50401,2023,17156 | ||
| 2110 | +50401,2024,17165 | ||
| 2111 | +50401,2025,17162 | ||
| 2112 | +50402,2016,21437 | ||
| 2113 | +50402,2017,21075 | ||
| 2114 | +50402,2018,20660 | ||
| 2115 | +50402,2019,20206 | ||
| 2116 | +50402,2020,19745 | ||
| 2117 | +50402,2021,19246 | ||
| 2118 | +50402,2022,18726 | ||
| 2119 | +50402,2023,18264 | ||
| 2120 | +50402,2024,17809 | ||
| 2121 | +50402,2025,17411 | ||
| 2122 | +50403,2016,29685 | ||
| 2123 | +50403,2017,30343 | ||
| 2124 | +50403,2018,30974 | ||
| 2125 | +50403,2019,31579 | ||
| 2126 | +50403,2020,32190 | ||
| 2127 | +50403,2021,32731 | ||
| 2128 | +50403,2022,33205 | ||
| 2129 | +50403,2023,33721 | ||
| 2130 | +50403,2024,34169 | ||
| 2131 | +50403,2025,34577 | ||
| 2132 | +50404,2016,16479 | ||
| 2133 | +50404,2017,16373 | ||
| 2134 | +50404,2018,16261 | ||
| 2135 | +50404,2019,16140 | ||
| 2136 | +50404,2020,16024 | ||
| 2137 | +50404,2021,15872 | ||
| 2138 | +50404,2022,15682 | ||
| 2139 | +50404,2023,15505 | ||
| 2140 | +50404,2024,15284 | ||
| 2141 | +50404,2025,15047 | ||
| 2142 | +50405,2016,21511 | ||
| 2143 | +50405,2017,21668 | ||
| 2144 | +50405,2018,21791 | ||
| 2145 | +50405,2019,21888 | ||
| 2146 | +50405,2020,21981 | ||
| 2147 | +50405,2021,22023 | ||
| 2148 | +50405,2022,22021 | ||
| 2149 | +50405,2023,22051 | ||
| 2150 | +50405,2024,22043 | ||
| 2151 | +50405,2025,22027 | ||
| 2152 | +50501,2016,31579 | ||
| 2153 | +50501,2017,31531 | ||
| 2154 | +50501,2018,31422 | ||
| 2155 | +50501,2019,31261 | ||
| 2156 | +50501,2020,31084 | ||
| 2157 | +50501,2021,30825 | ||
| 2158 | +50501,2022,30497 | ||
| 2159 | +50501,2023,30209 | ||
| 2160 | +50501,2024,29867 | ||
| 2161 | +50501,2025,29529 | ||
| 2162 | +50502,2016,11484 | ||
| 2163 | +50502,2017,11503 | ||
| 2164 | +50502,2018,11509 | ||
| 2165 | +50502,2019,11500 | ||
| 2166 | +50502,2020,11491 | ||
| 2167 | +50502,2021,11452 | ||
| 2168 | +50502,2022,11387 | ||
| 2169 | +50502,2023,11333 | ||
| 2170 | +50502,2024,11254 | ||
| 2171 | +50502,2025,11165 | ||
| 2172 | +50601,2016,33685 | ||
| 2173 | +50601,2017,33555 | ||
| 2174 | +50601,2018,33310 | ||
| 2175 | +50601,2019,32975 | ||
| 2176 | +50601,2020,32605 | ||
| 2177 | +50601,2021,32144 | ||
| 2178 | +50601,2022,31625 | ||
| 2179 | +50601,2023,31178 | ||
| 2180 | +50601,2024,30724 | ||
| 2181 | +50601,2025,30331 | ||
| 2182 | +50602,2016,11117 | ||
| 2183 | +50602,2017,11112 | ||
| 2184 | +50602,2018,11097 | ||
| 2185 | +50602,2019,11073 | ||
| 2186 | +50602,2020,11046 | ||
| 2187 | +50602,2021,10990 | ||
| 2188 | +50602,2022,10904 | ||
| 2189 | +50602,2023,10822 | ||
| 2190 | +50602,2024,10705 | ||
| 2191 | +50602,2025,10575 | ||
| 2192 | +50701,2016,20265 | ||
| 2193 | +50701,2017,20102 | ||
| 2194 | +50701,2018,19894 | ||
| 2195 | +50701,2019,19651 | ||
| 2196 | +50701,2020,19398 | ||
| 2197 | +50701,2021,19094 | ||
| 2198 | +50701,2022,18752 | ||
| 2199 | +50701,2023,18440 | ||
| 2200 | +50701,2024,18104 | ||
| 2201 | +50701,2025,17788 | ||
| 2202 | +50702,2016,9142 | ||
| 2203 | +50702,2017,9152 | ||
| 2204 | +50702,2018,9154 | ||
| 2205 | +50702,2019,9146 | ||
| 2206 | +50702,2020,9136 | ||
| 2207 | +50702,2021,9100 | ||
| 2208 | +50702,2022,9038 | ||
| 2209 | +50702,2023,8978 | ||
| 2210 | +50702,2024,8889 | ||
| 2211 | +50702,2025,8790 | ||
| 2212 | +50801,2016,48080 | ||
| 2213 | +50801,2017,48301 | ||
| 2214 | +50801,2018,48430 | ||
| 2215 | +50801,2019,48482 | ||
| 2216 | +50801,2020,48512 | ||
| 2217 | +50801,2021,48417 | ||
| 2218 | +50801,2022,48217 | ||
| 2219 | +50801,2023,48082 | ||
| 2220 | +50801,2024,47863 | ||
| 2221 | +50801,2025,47641 | ||
| 2222 | +50802,2016,11638 | ||
| 2223 | +50802,2017,11519 | ||
| 2224 | +50802,2018,11364 | ||
| 2225 | +50802,2019,11181 | ||
| 2226 | +50802,2020,10988 | ||
| 2227 | +50802,2021,10768 | ||
| 2228 | +50802,2022,10532 | ||
| 2229 | +50802,2023,10323 | ||
| 2230 | +50802,2024,10114 | ||
| 2231 | +50802,2025,9929 | ||
| 2232 | +50901,2016,13960 | ||
| 2233 | +50901,2017,13982 | ||
| 2234 | +50901,2018,13961 | ||
| 2235 | +50901,2019,13906 | ||
| 2236 | +50901,2020,13839 | ||
| 2237 | +50901,2021,13733 | ||
| 2238 | +50901,2022,13601 | ||
| 2239 | +50901,2023,13497 | ||
| 2240 | +50901,2024,13382 | ||
| 2241 | +50901,2025,13287 | ||
| 2242 | +50902,2016,1245 | ||
| 2243 | +50902,2017,1295 | ||
| 2244 | +50902,2018,1346 | ||
| 2245 | +50902,2019,1399 | ||
| 2246 | +50902,2020,1456 | ||
| 2247 | +50902,2021,1513 | ||
| 2248 | +50902,2022,1569 | ||
| 2249 | +50902,2023,1630 | ||
| 2250 | +50902,2024,1690 | ||
| 2251 | +50902,2025,1743 | ||
| 2252 | +51001,2016,3580 | ||
| 2253 | +51001,2017,3570 | ||
| 2254 | +51001,2018,3548 | ||
| 2255 | +51001,2019,3517 | ||
| 2256 | +51001,2020,3482 | ||
| 2257 | +51001,2021,3439 | ||
| 2258 | +51001,2022,3391 | ||
| 2259 | +51001,2023,3350 | ||
| 2260 | +51001,2024,3310 | ||
| 2261 | +51001,2025,3277 | ||
| 2262 | +51002,2016,1253 | ||
| 2263 | +51002,2017,1244 | ||
| 2264 | +51002,2018,1228 | ||
| 2265 | +51002,2019,1208 | ||
| 2266 | +51002,2020,1185 | ||
| 2267 | +51002,2021,1158 | ||
| 2268 | +51002,2022,1129 | ||
| 2269 | +51002,2023,1104 | ||
| 2270 | +51002,2024,1079 | ||
| 2271 | +51002,2025,1061 | ||
| 2272 | +51003,2016,2383 | ||
| 2273 | +51003,2017,2357 | ||
| 2274 | +51003,2018,2321 | ||
| 2275 | +51003,2019,2279 | ||
| 2276 | +51003,2020,2233 | ||
| 2277 | +51003,2021,2182 | ||
| 2278 | +51003,2022,2128 | ||
| 2279 | +51003,2023,2081 | ||
| 2280 | +51003,2024,2038 | ||
| 2281 | +51003,2025,2003 | ||
| 2282 | +51101,2016,21659 | ||
| 2283 | +51101,2017,21164 | ||
| 2284 | +51101,2018,20628 | ||
| 2285 | +51101,2019,20066 | ||
| 2286 | +51101,2020,19509 | ||
| 2287 | +51101,2021,18923 | ||
| 2288 | +51101,2022,18326 | ||
| 2289 | +51101,2023,17792 | ||
| 2290 | +51101,2024,17274 | ||
| 2291 | +51101,2025,16825 | ||
| 2292 | +51102,2016,13040 | ||
| 2293 | +51102,2017,13160 | ||
| 2294 | +51102,2018,13267 | ||
| 2295 | +51102,2019,13361 | ||
| 2296 | +51102,2020,13456 | ||
| 2297 | +51102,2021,13521 | ||
| 2298 | +51102,2022,13557 | ||
| 2299 | +51102,2023,13609 | ||
| 2300 | +51102,2024,13633 | ||
| 2301 | +51102,2025,13645 | ||
| 2302 | +51103,2016,16306 | ||
| 2303 | +51103,2017,16218 | ||
| 2304 | +51103,2018,16113 | ||
| 2305 | +51103,2019,15990 | ||
| 2306 | +51103,2020,15866 | ||
| 2307 | +51103,2021,15701 | ||
| 2308 | +51103,2022,15499 | ||
| 2309 | +51103,2023,15310 | ||
| 2310 | +51103,2024,15081 | ||
| 2311 | +51103,2025,14844 | ||
| 2312 | +51201,2016,33431 | ||
| 2313 | +51201,2017,34035 | ||
| 2314 | +51201,2018,34566 | ||
| 2315 | +51201,2019,35039 | ||
| 2316 | +51201,2020,35502 | ||
| 2317 | +51201,2021,35886 | ||
| 2318 | +51201,2022,36211 | ||
| 2319 | +51201,2023,36612 | ||
| 2320 | +51201,2024,36984 | ||
| 2321 | +51201,2025,37366 | ||
| 2322 | +51202,2016,9188 | ||
| 2323 | +51202,2017,9116 | ||
| 2324 | +51202,2018,9022 | ||
| 2325 | +51202,2019,8911 | ||
| 2326 | +51202,2020,8796 | ||
| 2327 | +51202,2021,8660 | ||
| 2328 | +51202,2022,8512 | ||
| 2329 | +51202,2023,8383 | ||
| 2330 | +51202,2024,8251 | ||
| 2331 | +51202,2025,8131 | ||
| 2332 | +51203,2016,12689 | ||
| 2333 | +51203,2017,13069 | ||
| 2334 | +51203,2018,13427 | ||
| 2335 | +51203,2019,13769 | ||
| 2336 | +51203,2020,14110 | ||
| 2337 | +51203,2021,14422 | ||
| 2338 | +51203,2022,14710 | ||
| 2339 | +51203,2023,15025 | ||
| 2340 | +51203,2024,15322 | ||
| 2341 | +51203,2025,15604 | ||
| 2342 | +51204,2016,6339 | ||
| 2343 | +51204,2017,6347 | ||
| 2344 | +51204,2018,6339 | ||
| 2345 | +51204,2019,6318 | ||
| 2346 | +51204,2020,6294 | ||
| 2347 | +51204,2021,6253 | ||
| 2348 | +51204,2022,6200 | ||
| 2349 | +51204,2023,6159 | ||
| 2350 | +51204,2024,6112 | ||
| 2351 | +51204,2025,6071 | ||
| 2352 | +51301,2016,4230 | ||
| 2353 | +51301,2017,4192 | ||
| 2354 | +51301,2018,4152 | ||
| 2355 | +51301,2019,4111 | ||
| 2356 | +51301,2020,4071 | ||
| 2357 | +51301,2021,4024 | ||
| 2358 | +51301,2022,3968 | ||
| 2359 | +51301,2023,3917 | ||
| 2360 | +51301,2024,3856 | ||
| 2361 | +51301,2025,3793 | ||
| 2362 | +51302,2016,6128 | ||
| 2363 | +51302,2017,5977 | ||
| 2364 | +51302,2018,5806 | ||
| 2365 | +51302,2019,5622 | ||
| 2366 | +51302,2020,5437 | ||
| 2367 | +51302,2021,5242 | ||
| 2368 | +51302,2022,5047 | ||
| 2369 | +51302,2023,4875 | ||
| 2370 | +51302,2024,4715 | ||
| 2371 | +51302,2025,4584 | ||
| 2372 | +51401,2016,4492 | ||
| 2373 | +51401,2017,4509 | ||
| 2374 | +51401,2018,4511 | ||
| 2375 | +51401,2019,4503 | ||
| 2376 | +51401,2020,4491 | ||
| 2377 | +51401,2021,4467 | ||
| 2378 | +51401,2022,4437 | ||
| 2379 | +51401,2023,4416 | ||
| 2380 | +51401,2024,4395 | ||
| 2381 | +51401,2025,4381 | ||
| 2382 | +51402,2016,1723 | ||
| 2383 | +51402,2017,1713 | ||
| 2384 | +51402,2018,1704 | ||
| 2385 | +51402,2019,1695 | ||
| 2386 | +51402,2020,1688 | ||
| 2387 | +51402,2021,1677 | ||
| 2388 | +51402,2022,1662 | ||
| 2389 | +51402,2023,1647 | ||
| 2390 | +51402,2024,1626 | ||
| 2391 | +51402,2025,1602 | ||
| 2392 | +51501,2016,49092 | ||
| 2393 | +51501,2017,49632 | ||
| 2394 | +51501,2018,50094 | ||
| 2395 | +51501,2019,50490 | ||
| 2396 | +51501,2020,50878 | ||
| 2397 | +51501,2021,51148 | ||
| 2398 | +51501,2022,51317 | ||
| 2399 | +51501,2023,51565 | ||
| 2400 | +51501,2024,51732 | ||
| 2401 | +51501,2025,51888 | ||
| 2402 | +51601,2016,1885 | ||
| 2403 | +51601,2017,1932 | ||
| 2404 | +51601,2018,1980 | ||
| 2405 | +51601,2019,2029 | ||
| 2406 | +51601,2020,2078 | ||
| 2407 | +51601,2021,2122 | ||
| 2408 | +51601,2022,2160 | ||
| 2409 | +51601,2023,2198 | ||
| 2410 | +51601,2024,2226 | ||
| 2411 | +51601,2025,2248 | ||
| 2412 | +60101,2016,230705 | ||
| 2413 | +60101,2017,235146 | ||
| 2414 | +60101,2018,239101 | ||
| 2415 | +60101,2019,242551 | ||
| 2416 | +60101,2020,245694 | ||
| 2417 | +60101,2021,247910 | ||
| 2418 | +60101,2022,249195 | ||
| 2419 | +60101,2023,250344 | ||
| 2420 | +60101,2024,250495 | ||
| 2421 | +60101,2025,250057 | ||
| 2422 | +60201,2016,19727 | ||
| 2423 | +60201,2017,19774 | ||
| 2424 | +60201,2018,19790 | ||
| 2425 | +60201,2019,19768 | ||
| 2426 | +60201,2020,19719 | ||
| 2427 | +60201,2021,19587 | ||
| 2428 | +60201,2022,19367 | ||
| 2429 | +60201,2023,19115 | ||
| 2430 | +60201,2024,18758 | ||
| 2431 | +60201,2025,18344 | ||
| 2432 | +60202,2016,37516 | ||
| 2433 | +60202,2017,37981 | ||
| 2434 | +60202,2018,38400 | ||
| 2435 | +60202,2019,38757 | ||
| 2436 | +60202,2020,39070 | ||
| 2437 | +60202,2021,39226 | ||
| 2438 | +60202,2022,39211 | ||
| 2439 | +60202,2023,39135 | ||
| 2440 | +60202,2024,38847 | ||
| 2441 | +60202,2025,38427 | ||
| 2442 | +60301,2016,100453 | ||
| 2443 | +60301,2017,101584 | ||
| 2444 | +60301,2018,102539 | ||
| 2445 | +60301,2019,103292 | ||
| 2446 | +60301,2020,103909 | ||
| 2447 | +60301,2021,104104 | ||
| 2448 | +60301,2022,103861 | ||
| 2449 | +60301,2023,103488 | ||
| 2450 | +60301,2024,102606 | ||
| 2451 | +60301,2025,101432 | ||
| 2452 | +60302,2016,16954 | ||
| 2453 | +60302,2017,17076 | ||
| 2454 | +60302,2018,17125 | ||
| 2455 | +60302,2019,17109 | ||
| 2456 | +60302,2020,17053 | ||
| 2457 | +60302,2021,16927 | ||
| 2458 | +60302,2022,16744 | ||
| 2459 | +60302,2023,16569 | ||
| 2460 | +60302,2024,16358 | ||
| 2461 | +60302,2025,16151 | ||
| 2462 | +60303,2016,45186 | ||
| 2463 | +60303,2017,46021 | ||
| 2464 | +60303,2018,46711 | ||
| 2465 | +60303,2019,47268 | ||
| 2466 | +60303,2020,47748 | ||
| 2467 | +60303,2021,48046 | ||
| 2468 | +60303,2022,48182 | ||
| 2469 | +60303,2023,48328 | ||
| 2470 | +60303,2024,48335 | ||
| 2471 | +60303,2025,48291 | ||
| 2472 | +60401,2016,16235 | ||
| 2473 | +60401,2017,16441 | ||
| 2474 | +60401,2018,16613 | ||
| 2475 | +60401,2019,16748 | ||
| 2476 | +60401,2020,16859 | ||
| 2477 | +60401,2021,16903 | ||
| 2478 | +60401,2022,16880 | ||
| 2479 | +60401,2023,16841 | ||
| 2480 | +60401,2024,16728 | ||
| 2481 | +60401,2025,16574 | ||
| 2482 | +60402,2016,5723 | ||
| 2483 | +60402,2017,5691 | ||
| 2484 | +60402,2018,5642 | ||
| 2485 | +60402,2019,5577 | ||
| 2486 | +60402,2020,5502 | ||
| 2487 | +60402,2021,5404 | ||
| 2488 | +60402,2022,5283 | ||
| 2489 | +60402,2023,5158 | ||
| 2490 | +60402,2024,5013 | ||
| 2491 | +60402,2025,4861 | ||
| 2492 | +60501,2016,27085 | ||
| 2493 | +60501,2017,27833 | ||
| 2494 | +60501,2018,28575 | ||
| 2495 | +60501,2019,29293 | ||
| 2496 | +60501,2020,29998 | ||
| 2497 | +60501,2021,30598 | ||
| 2498 | +60501,2022,31078 | ||
| 2499 | +60501,2023,31520 | ||
| 2500 | +60501,2024,31800 | ||
| 2501 | +60501,2025,31954 | ||
| 2502 | +60502,2016,11754 | ||
| 2503 | +60502,2017,11653 | ||
| 2504 | +60502,2018,11516 | ||
| 2505 | +60502,2019,11346 | ||
| 2506 | +60502,2020,11156 | ||
| 2507 | +60502,2021,10919 | ||
| 2508 | +60502,2022,10642 | ||
| 2509 | +60502,2023,10362 | ||
| 2510 | +60502,2024,10045 | ||
| 2511 | +60502,2025,9723 | ||
| 2512 | +60601,2016,23457 | ||
| 2513 | +60601,2017,23503 | ||
| 2514 | +60601,2018,23484 | ||
| 2515 | +60601,2019,23402 | ||
| 2516 | +60601,2020,23277 | ||
| 2517 | +60601,2021,23053 | ||
| 2518 | +60601,2022,22735 | ||
| 2519 | +60601,2023,22398 | ||
| 2520 | +60601,2024,21965 | ||
| 2521 | +60601,2025,21496 | ||
| 2522 | +70101,2016,1589815 | ||
| 2523 | +70101,2017,1610316 | ||
| 2524 | +70101,2018,1628034 | ||
| 2525 | +70101,2019,1643322 | ||
| 2526 | +70101,2020,1658020 | ||
| 2527 | +70101,2021,1668431 | ||
| 2528 | +70101,2022,1675059 | ||
| 2529 | +70101,2023,1683733 | ||
| 2530 | +70101,2024,1689137 | ||
| 2531 | +70101,2025,1693583 | ||
| 2532 | +70102,2016,71625 | ||
| 2533 | +70102,2017,76531 | ||
| 2534 | +70102,2018,81604 | ||
| 2535 | +70102,2019,86776 | ||
| 2536 | +70102,2020,92058 | ||
| 2537 | +70102,2021,97154 | ||
| 2538 | +70102,2022,101977 | ||
| 2539 | +70102,2023,106780 | ||
| 2540 | +70102,2024,111138 | ||
| 2541 | +70102,2025,114964 | ||
| 2542 | +70103,2016,18238 | ||
| 2543 | +70103,2017,19005 | ||
| 2544 | +70103,2018,19782 | ||
| 2545 | +70103,2019,20562 | ||
| 2546 | +70103,2020,21357 | ||
| 2547 | +70103,2021,22107 | ||
| 2548 | +70103,2022,22806 | ||
| 2549 | +70103,2023,23517 | ||
| 2550 | +70103,2024,24158 | ||
| 2551 | +70103,2025,24728 | ||
| 2552 | +70104,2016,111040 | ||
| 2553 | +70104,2017,116694 | ||
| 2554 | +70104,2018,122302 | ||
| 2555 | +70104,2019,127858 | ||
| 2556 | +70104,2020,133479 | ||
| 2557 | +70104,2021,138836 | ||
| 2558 | +70104,2022,143918 | ||
| 2559 | +70104,2023,149188 | ||
| 2560 | +70104,2024,154152 | ||
| 2561 | +70104,2025,158768 | ||
| 2562 | +70105,2016,54790 | ||
| 2563 | +70105,2017,55523 | ||
| 2564 | +70105,2018,56156 | ||
| 2565 | +70105,2019,56701 | ||
| 2566 | +70105,2020,57225 | ||
| 2567 | +70105,2021,57602 | ||
| 2568 | +70105,2022,57851 | ||
| 2569 | +70105,2023,58175 | ||
| 2570 | +70105,2024,58393 | ||
| 2571 | +70105,2025,58586 | ||
| 2572 | +70201,2016,117872 | ||
| 2573 | +70201,2017,123213 | ||
| 2574 | +70201,2018,128462 | ||
| 2575 | +70201,2019,133628 | ||
| 2576 | +70201,2020,138843 | ||
| 2577 | +70201,2021,143777 | ||
| 2578 | +70201,2022,148436 | ||
| 2579 | +70201,2023,153311 | ||
| 2580 | +70201,2024,157905 | ||
| 2581 | +70201,2025,162215 | ||
| 2582 | +70202,2016,12864 | ||
| 2583 | +70202,2017,12737 | ||
| 2584 | +70202,2018,12576 | ||
| 2585 | +70202,2019,12390 | ||
| 2586 | +70202,2020,12194 | ||
| 2587 | +70202,2021,11964 | ||
| 2588 | +70202,2022,11710 | ||
| 2589 | +70202,2023,11476 | ||
| 2590 | +70202,2024,11228 | ||
| 2591 | +70202,2025,10996 | ||
| 2592 | +70301,2016,59370 | ||
| 2593 | +70301,2017,60868 | ||
| 2594 | +70301,2018,62340 | ||
| 2595 | +70301,2019,63786 | ||
| 2596 | +70301,2020,65260 | ||
| 2597 | +70301,2021,66600 | ||
| 2598 | +70301,2022,67802 | ||
| 2599 | +70301,2023,69085 | ||
| 2600 | +70301,2024,70212 | ||
| 2601 | +70301,2025,71231 | ||
| 2602 | +70302,2016,12893 | ||
| 2603 | +70302,2017,13281 | ||
| 2604 | +70302,2018,13679 | ||
| 2605 | +70302,2019,14084 | ||
| 2606 | +70302,2020,14502 | ||
| 2607 | +70302,2021,14893 | ||
| 2608 | +70302,2022,15250 | ||
| 2609 | +70302,2023,15617 | ||
| 2610 | +70302,2024,15936 | ||
| 2611 | +70302,2025,16211 | ||
| 2612 | +70303,2016,7158 | ||
| 2613 | +70303,2017,7421 | ||
| 2614 | +70303,2018,7691 | ||
| 2615 | +70303,2019,7964 | ||
| 2616 | +70303,2020,8244 | ||
| 2617 | +70303,2021,8510 | ||
| 2618 | +70303,2022,8757 | ||
| 2619 | +70303,2023,9011 | ||
| 2620 | +70303,2024,9239 | ||
| 2621 | +70303,2025,9442 | ||
| 2622 | +70401,2016,13741 | ||
| 2623 | +70401,2017,13847 | ||
| 2624 | +70401,2018,13951 | ||
| 2625 | +70401,2019,14049 | ||
| 2626 | +70401,2020,14153 | ||
| 2627 | +70401,2021,14226 | ||
| 2628 | +70401,2022,14264 | ||
| 2629 | +70401,2023,14314 | ||
| 2630 | +70401,2024,14321 | ||
| 2631 | +70401,2025,14309 | ||
| 2632 | +70402,2016,21263 | ||
| 2633 | +70402,2017,21218 | ||
| 2634 | +70402,2018,21116 | ||
| 2635 | +70402,2019,20969 | ||
| 2636 | +70402,2020,20808 | ||
| 2637 | +70402,2021,20593 | ||
| 2638 | +70402,2022,20341 | ||
| 2639 | +70402,2023,20130 | ||
| 2640 | +70402,2024,19906 | ||
| 2641 | +70402,2025,19707 | ||
| 2642 | +70403,2016,56507 | ||
| 2643 | +70403,2017,57329 | ||
| 2644 | +70403,2018,58003 | ||
| 2645 | +70403,2019,58561 | ||
| 2646 | +70403,2020,59089 | ||
| 2647 | +70403,2021,59475 | ||
| 2648 | +70403,2022,59762 | ||
| 2649 | +70403,2023,60178 | ||
| 2650 | +70403,2024,60558 | ||
| 2651 | +70403,2025,60979 | ||
| 2652 | +70404,2016,9923 | ||
| 2653 | +70404,2017,10038 | ||
| 2654 | +70404,2018,10151 | ||
| 2655 | +70404,2019,10263 | ||
| 2656 | +70404,2020,10381 | ||
| 2657 | +70404,2021,10477 | ||
| 2658 | +70404,2022,10551 | ||
| 2659 | +70404,2023,10636 | ||
| 2660 | +70404,2024,10692 | ||
| 2661 | +70404,2025,10734 | ||
| 2662 | +70501,2016,34128 | ||
| 2663 | +70501,2017,35314 | ||
| 2664 | +70501,2018,36465 | ||
| 2665 | +70501,2019,37587 | ||
| 2666 | +70501,2020,38721 | ||
| 2667 | +70501,2021,39779 | ||
| 2668 | +70501,2022,40767 | ||
| 2669 | +70501,2023,41824 | ||
| 2670 | +70501,2024,42818 | ||
| 2671 | +70501,2025,43754 | ||
| 2672 | +70502,2016,44375 | ||
| 2673 | +70502,2017,46013 | ||
| 2674 | +70502,2018,47666 | ||
| 2675 | +70502,2019,49321 | ||
| 2676 | +70502,2020,51012 | ||
| 2677 | +70502,2021,52601 | ||
| 2678 | +70502,2022,54072 | ||
| 2679 | +70502,2023,55588 | ||
| 2680 | +70502,2024,56949 | ||
| 2681 | +70502,2025,58164 | ||
| 2682 | +70503,2016,17239 | ||
| 2683 | +70503,2017,17571 | ||
| 2684 | +70503,2018,17908 | ||
| 2685 | +70503,2019,18245 | ||
| 2686 | +70503,2020,18594 | ||
| 2687 | +70503,2021,18903 | ||
| 2688 | +70503,2022,19166 | ||
| 2689 | +70503,2023,19440 | ||
| 2690 | +70503,2024,19652 | ||
| 2691 | +70503,2025,19821 | ||
| 2692 | +70601,2016,19669 | ||
| 2693 | +70601,2017,19994 | ||
| 2694 | +70601,2018,20310 | ||
| 2695 | +70601,2019,20614 | ||
| 2696 | +70601,2020,20921 | ||
| 2697 | +70601,2021,21177 | ||
| 2698 | +70601,2022,21378 | ||
| 2699 | +70601,2023,21592 | ||
| 2700 | +70601,2024,21739 | ||
| 2701 | +70601,2025,21849 | ||
| 2702 | +70602,2016,20381 | ||
| 2703 | +70602,2017,20332 | ||
| 2704 | +70602,2018,20220 | ||
| 2705 | +70602,2019,20060 | ||
| 2706 | +70602,2020,19881 | ||
| 2707 | +70602,2021,19649 | ||
| 2708 | +70602,2022,19381 | ||
| 2709 | +70602,2023,19155 | ||
| 2710 | +70602,2024,18918 | ||
| 2711 | +70602,2025,18713 | ||
| 2712 | +70603,2016,6391 | ||
| 2713 | +70603,2017,6405 | ||
| 2714 | +70603,2018,6415 | ||
| 2715 | +70603,2019,6419 | ||
| 2716 | +70603,2020,6422 | ||
| 2717 | +70603,2021,6410 | ||
| 2718 | +70603,2022,6380 | ||
| 2719 | +70603,2023,6355 | ||
| 2720 | +70603,2024,6310 | ||
| 2721 | +70603,2025,6259 | ||
| 2722 | +70701,2016,5800 | ||
| 2723 | +70701,2017,5867 | ||
| 2724 | +70701,2018,5934 | ||
| 2725 | +70701,2019,5998 | ||
| 2726 | +70701,2020,6066 | ||
| 2727 | +70701,2021,6120 | ||
| 2728 | +70701,2022,6160 | ||
| 2729 | +70701,2023,6205 | ||
| 2730 | +70701,2024,6234 | ||
| 2731 | +70701,2025,6254 | ||
| 2732 | +70702,2016,35948 | ||
| 2733 | +70702,2017,36608 | ||
| 2734 | +70702,2018,37223 | ||
| 2735 | +70702,2019,37800 | ||
| 2736 | +70702,2020,38383 | ||
| 2737 | +70702,2021,38883 | ||
| 2738 | +70702,2022,39312 | ||
| 2739 | +70702,2023,39804 | ||
| 2740 | +70702,2024,40233 | ||
| 2741 | +70702,2025,40635 | ||
| 2742 | +70703,2016,29666 | ||
| 2743 | +70703,2017,30338 | ||
| 2744 | +70703,2018,31002 | ||
| 2745 | +70703,2019,31655 | ||
| 2746 | +70703,2020,32323 | ||
| 2747 | +70703,2021,32924 | ||
| 2748 | +70703,2022,33454 | ||
| 2749 | +70703,2023,34019 | ||
| 2750 | +70703,2024,34501 | ||
| 2751 | +70703,2025,34928 | ||
| 2752 | +70704,2016,5525 | ||
| 2753 | +70704,2017,5566 | ||
| 2754 | +70704,2018,5590 | ||
| 2755 | +70704,2019,5602 | ||
| 2756 | +70704,2020,5611 | ||
| 2757 | +70704,2021,5607 | ||
| 2758 | +70704,2022,5594 | ||
| 2759 | +70704,2023,5595 | ||
| 2760 | +70704,2024,5596 | ||
| 2761 | +70704,2025,5607 | ||
| 2762 | +70705,2016,13513 | ||
| 2763 | +70705,2017,13748 | ||
| 2764 | +70705,2018,13979 | ||
| 2765 | +70705,2019,14204 | ||
| 2766 | +70705,2020,14434 | ||
| 2767 | +70705,2021,14629 | ||
| 2768 | +70705,2022,14788 | ||
| 2769 | +70705,2023,14955 | ||
| 2770 | +70705,2024,15077 | ||
| 2771 | +70705,2025,15168 | ||
| 2772 | +70706,2016,35660 | ||
| 2773 | +70706,2017,35622 | ||
| 2774 | +70706,2018,35516 | ||
| 2775 | +70706,2019,35352 | ||
| 2776 | +70706,2020,35174 | ||
| 2777 | +70706,2021,34906 | ||
| 2778 | +70706,2022,34565 | ||
| 2779 | +70706,2023,34273 | ||
| 2780 | +70706,2024,33926 | ||
| 2781 | +70706,2025,33593 | ||
| 2782 | +70707,2016,5567 | ||
| 2783 | +70707,2017,5628 | ||
| 2784 | +70707,2018,5681 | ||
| 2785 | +70707,2019,5726 | ||
| 2786 | +70707,2020,5771 | ||
| 2787 | +70707,2021,5803 | ||
| 2788 | +70707,2022,5825 | ||
| 2789 | +70707,2023,5857 | ||
| 2790 | +70707,2024,5883 | ||
| 2791 | +70707,2025,5910 | ||
| 2792 | +70801,2016,18232 | ||
| 2793 | +70801,2017,18289 | ||
| 2794 | +70801,2018,18327 | ||
| 2795 | +70801,2019,18347 | ||
| 2796 | +70801,2020,18365 | ||
| 2797 | +70801,2021,18337 | ||
| 2798 | +70801,2022,18267 | ||
| 2799 | +70801,2023,18212 | ||
| 2800 | +70801,2024,18113 | ||
| 2801 | +70801,2025,18004 | ||
| 2802 | +70802,2016,2306 | ||
| 2803 | +70802,2017,2334 | ||
| 2804 | +70802,2018,2360 | ||
| 2805 | +70802,2019,2386 | ||
| 2806 | +70802,2020,2413 | ||
| 2807 | +70802,2021,2434 | ||
| 2808 | +70802,2022,2449 | ||
| 2809 | +70802,2023,2465 | ||
| 2810 | +70802,2024,2474 | ||
| 2811 | +70802,2025,2480 | ||
| 2812 | +70803,2016,2879 | ||
| 2813 | +70803,2017,2888 | ||
| 2814 | +70803,2018,2899 | ||
| 2815 | +70803,2019,2912 | ||
| 2816 | +70803,2020,2927 | ||
| 2817 | +70803,2021,2937 | ||
| 2818 | +70803,2022,2939 | ||
| 2819 | +70803,2023,2941 | ||
| 2820 | +70803,2024,2932 | ||
| 2821 | +70803,2025,2915 | ||
| 2822 | +70804,2016,2368 | ||
| 2823 | +70804,2017,2316 | ||
| 2824 | +70804,2018,2259 | ||
| 2825 | +70804,2019,2200 | ||
| 2826 | +70804,2020,2140 | ||
| 2827 | +70804,2021,2078 | ||
| 2828 | +70804,2022,2014 | ||
| 2829 | +70804,2023,1958 | ||
| 2830 | +70804,2024,1903 | ||
| 2831 | +70804,2025,1853 | ||
| 2832 | +70805,2016,2139 | ||
| 2833 | +70805,2017,2137 | ||
| 2834 | +70805,2018,2138 | ||
| 2835 | +70805,2019,2139 | ||
| 2836 | +70805,2020,2142 | ||
| 2837 | +70805,2021,2141 | ||
| 2838 | +70805,2022,2135 | ||
| 2839 | +70805,2023,2129 | ||
| 2840 | +70805,2024,2116 | ||
| 2841 | +70805,2025,2099 | ||
| 2842 | +70901,2016,11442 | ||
| 2843 | +70901,2017,11605 | ||
| 2844 | +70901,2018,11762 | ||
| 2845 | +70901,2019,11910 | ||
| 2846 | +70901,2020,12060 | ||
| 2847 | +70901,2021,12179 | ||
| 2848 | +70901,2022,12266 | ||
| 2849 | +70901,2023,12360 | ||
| 2850 | +70901,2024,12416 | ||
| 2851 | +70901,2025,12451 | ||
| 2852 | +70902,2016,9926 | ||
| 2853 | +70902,2017,9999 | ||
| 2854 | +70902,2018,10056 | ||
| 2855 | +70902,2019,10101 | ||
| 2856 | +70902,2020,10144 | ||
| 2857 | +70902,2021,10163 | ||
| 2858 | +70902,2022,10161 | ||
| 2859 | +70902,2023,10174 | ||
| 2860 | +70902,2024,10168 | ||
| 2861 | +70902,2025,10160 | ||
| 2862 | +70903,2016,11494 | ||
| 2863 | +70903,2017,11756 | ||
| 2864 | +70903,2018,12010 | ||
| 2865 | +70903,2019,12254 | ||
| 2866 | +70903,2020,12502 | ||
| 2867 | +70903,2021,12724 | ||
| 2868 | +70903,2022,12922 | ||
| 2869 | +70903,2023,13138 | ||
| 2870 | +70903,2024,13328 | ||
| 2871 | +70903,2025,13505 | ||
| 2872 | +70904,2016,3254 | ||
| 2873 | +70904,2017,3269 | ||
| 2874 | +70904,2018,3275 | ||
| 2875 | +70904,2019,3274 | ||
| 2876 | +70904,2020,3270 | ||
| 2877 | +70904,2021,3260 | ||
| 2878 | +70904,2022,3246 | ||
| 2879 | +70904,2023,3241 | ||
| 2880 | +70904,2024,3237 | ||
| 2881 | +70904,2025,3238 | ||
| 2882 | +71001,2016,121836 | ||
| 2883 | +71001,2017,123773 | ||
| 2884 | +71001,2018,125498 | ||
| 2885 | +71001,2019,127049 | ||
| 2886 | +71001,2020,128576 | ||
| 2887 | +71001,2021,129802 | ||
| 2888 | +71001,2022,130774 | ||
| 2889 | +71001,2023,131956 | ||
| 2890 | +71001,2024,132942 | ||
| 2891 | +71001,2025,133885 | ||
| 2892 | +71002,2016,15379 | ||
| 2893 | +71002,2017,15647 | ||
| 2894 | +71002,2018,15938 | ||
| 2895 | +71002,2019,16239 | ||
| 2896 | +71002,2020,16555 | ||
| 2897 | +71002,2021,16830 | ||
| 2898 | +71002,2022,17054 | ||
| 2899 | +71002,2023,17266 | ||
| 2900 | +71002,2024,17397 | ||
| 2901 | +71002,2025,17462 | ||
| 2902 | +71003,2016,24808 | ||
| 2903 | +71003,2017,24865 | ||
| 2904 | +71003,2018,24872 | ||
| 2905 | +71003,2019,24837 | ||
| 2906 | +71003,2020,24792 | ||
| 2907 | +71003,2021,24685 | ||
| 2908 | +71003,2022,24529 | ||
| 2909 | +71003,2023,24414 | ||
| 2910 | +71003,2024,24267 | ||
| 2911 | +71003,2025,24132 | ||
| 2912 | +71004,2016,16167 | ||
| 2913 | +71004,2017,16169 | ||
| 2914 | +71004,2018,16122 | ||
| 2915 | +71004,2019,16037 | ||
| 2916 | +71004,2020,15937 | ||
| 2917 | +71004,2021,15795 | ||
| 2918 | +71004,2022,15626 | ||
| 2919 | +71004,2023,15490 | ||
| 2920 | +71004,2024,15348 | ||
| 2921 | +71004,2025,15231 | ||
| 2922 | +71005,2016,19691 | ||
| 2923 | +71005,2017,19394 | ||
| 2924 | +71005,2018,19013 | ||
| 2925 | +71005,2019,18568 | ||
| 2926 | +71005,2020,18097 | ||
| 2927 | +71005,2021,17576 | ||
| 2928 | +71005,2022,17031 | ||
| 2929 | +71005,2023,16539 | ||
| 2930 | +71005,2024,16067 | ||
| 2931 | +71005,2025,15661 | ||
| 2932 | +71101,2016,22693 | ||
| 2933 | +71101,2017,23793 | ||
| 2934 | +71101,2018,24926 | ||
| 2935 | +71101,2019,26076 | ||
| 2936 | +71101,2020,27254 | ||
| 2937 | +71101,2021,28379 | ||
| 2938 | +71101,2022,29432 | ||
| 2939 | +71101,2023,30492 | ||
| 2940 | +71101,2024,31444 | ||
| 2941 | +71101,2025,32278 | ||
| 2942 | +71102,2016,14837 | ||
| 2943 | +71102,2017,14991 | ||
| 2944 | +71102,2018,15123 | ||
| 2945 | +71102,2019,15237 | ||
| 2946 | +71102,2020,15351 | ||
| 2947 | +71102,2021,15431 | ||
| 2948 | +71102,2022,15482 | ||
| 2949 | +71102,2023,15558 | ||
| 2950 | +71102,2024,15609 | ||
| 2951 | +71102,2025,15659 | ||
| 2952 | +71104,2016,50320 | ||
| 2953 | +71104,2017,50242 | ||
| 2954 | +71104,2018,50021 | ||
| 2955 | +71104,2019,49688 | ||
| 2956 | +71104,2020,49316 | ||
| 2957 | +71104,2021,48813 | ||
| 2958 | +71104,2022,48220 | ||
| 2959 | +71104,2023,47724 | ||
| 2960 | +71104,2024,47194 | ||
| 2961 | +71104,2025,46731 | ||
| 2962 | +71105,2016,6847 | ||
| 2963 | +71105,2017,6858 | ||
| 2964 | +71105,2018,6860 | ||
| 2965 | +71105,2019,6854 | ||
| 2966 | +71105,2020,6846 | ||
| 2967 | +71105,2021,6820 | ||
| 2968 | +71105,2022,6777 | ||
| 2969 | +71105,2023,6740 | ||
| 2970 | +71105,2024,6686 | ||
| 2971 | +71105,2025,6628 | ||
| 2972 | +71103,2016,8305 | ||
| 2973 | +71103,2017,8430 | ||
| 2974 | +71103,2018,8540 | ||
| 2975 | +71103,2019,8636 | ||
| 2976 | +71103,2020,8730 | ||
| 2977 | +71103,2021,8800 | ||
| 2978 | +71103,2022,8850 | ||
| 2979 | +71103,2023,8910 | ||
| 2980 | +71103,2024,8953 | ||
| 2981 | +71103,2025,8990 | ||
| 2982 | +71106,2016,25112 | ||
| 2983 | +71106,2017,25413 | ||
| 2984 | +71106,2018,25666 | ||
| 2985 | +71106,2019,25878 | ||
| 2986 | +71106,2020,26081 | ||
| 2987 | +71106,2021,26217 | ||
| 2988 | +71106,2022,26296 | ||
| 2989 | +71106,2023,26413 | ||
| 2990 | +71106,2024,26485 | ||
| 2991 | +71106,2025,26543 | ||
| 2992 | +71201,2016,15586 | ||
| 2993 | +71201,2017,15709 | ||
| 2994 | +71201,2018,15814 | ||
| 2995 | +71201,2019,15904 | ||
| 2996 | +71201,2020,15995 | ||
| 2997 | +71201,2021,16050 | ||
| 2998 | +71201,2022,16072 | ||
| 2999 | +71201,2023,16117 | ||
| 3000 | +71201,2024,16130 | ||
| 3001 | +71201,2025,16135 | ||
| 3002 | +71301,2016,17193 | ||
| 3003 | +71301,2017,17356 | ||
| 3004 | +71301,2018,17503 | ||
| 3005 | +71301,2019,17636 | ||
| 3006 | +71301,2020,17771 | ||
| 3007 | +71301,2021,17865 | ||
| 3008 | +71301,2022,17921 | ||
| 3009 | +71301,2023,17998 | ||
| 3010 | +71301,2024,18034 | ||
| 3011 | +71301,2025,18056 | ||
| 3012 | +71302,2016,7677 | ||
| 3013 | +71302,2017,7577 | ||
| 3014 | +71302,2018,7442 | ||
| 3015 | +71302,2019,7282 | ||
| 3016 | +71302,2020,7111 | ||
| 3017 | +71302,2021,6920 | ||
| 3018 | +71302,2022,6720 | ||
| 3019 | +71302,2023,6541 | ||
| 3020 | +71302,2024,6371 | ||
| 3021 | +71302,2025,6226 | ||
| 3022 | +71401,2016,20815 | ||
| 3023 | +71401,2017,20662 | ||
| 3024 | +71401,2018,20433 | ||
| 3025 | +71401,2019,20144 | ||
| 3026 | +71401,2020,19830 | ||
| 3027 | +71401,2021,19459 | ||
| 3028 | +71401,2022,19052 | ||
| 3029 | +71401,2023,18691 | ||
| 3030 | +71401,2024,18328 | ||
| 3031 | +71401,2025,18014 | ||
| 3032 | +71402,2016,18175 | ||
| 3033 | +71402,2017,18343 | ||
| 3034 | +71402,2018,18474 | ||
| 3035 | +71402,2019,18578 | ||
| 3036 | +71402,2020,18676 | ||
| 3037 | +71402,2021,18732 | ||
| 3038 | +71402,2022,18754 | ||
| 3039 | +71402,2023,18812 | ||
| 3040 | +71402,2024,18848 | ||
| 3041 | +71402,2025,18895 | ||
| 3042 | +71403,2016,7349 | ||
| 3043 | +71403,2017,7593 | ||
| 3044 | +71403,2018,7838 | ||
| 3045 | +71403,2019,8083 | ||
| 3046 | +71403,2020,8334 | ||
| 3047 | +71403,2021,8569 | ||
| 3048 | +71403,2022,8786 | ||
| 3049 | +71403,2023,9012 | ||
| 3050 | +71403,2024,9216 | ||
| 3051 | +71403,2025,9399 | ||
| 3052 | +71501,2016,30895 | ||
| 3053 | +71501,2017,31612 | ||
| 3054 | +71501,2018,32275 | ||
| 3055 | +71501,2019,32897 | ||
| 3056 | +71501,2020,33519 | ||
| 3057 | +71501,2021,34071 | ||
| 3058 | +71501,2022,34567 | ||
| 3059 | +71501,2023,35132 | ||
| 3060 | +71501,2024,35660 | ||
| 3061 | +71501,2025,36182 | ||
| 3062 | +71502,2016,7422 | ||
| 3063 | +71502,2017,7405 | ||
| 3064 | +71502,2018,7368 | ||
| 3065 | +71502,2019,7315 | ||
| 3066 | +71502,2020,7255 | ||
| 3067 | +71502,2021,7175 | ||
| 3068 | +71502,2022,7077 | ||
| 3069 | +71502,2023,6990 | ||
| 3070 | +71502,2024,6892 | ||
| 3071 | +71502,2025,6803 | ||
| 3072 | +71503,2016,16090 | ||
| 3073 | +71503,2017,16407 | ||
| 3074 | +71503,2018,16690 | ||
| 3075 | +71503,2019,16947 | ||
| 3076 | +71503,2020,17204 | ||
| 3077 | +71503,2021,17426 | ||
| 3078 | +71503,2022,17624 | ||
| 3079 | +71503,2023,17864 | ||
| 3080 | +71503,2024,18097 | ||
| 3081 | +71503,2025,18336 | ||
| 3082 | +80101,2016,119359 | ||
| 3083 | +80101,2017,121646 | ||
| 3084 | +80101,2018,123791 | ||
| 3085 | +80101,2019,125821 | ||
| 3086 | +80101,2020,127876 | ||
| 3087 | +80101,2021,129671 | ||
| 3088 | +80101,2022,131237 | ||
| 3089 | +80101,2023,133029 | ||
| 3090 | +80101,2024,134627 | ||
| 3091 | +80101,2025,136139 | ||
| 3092 | +80102,2016,6387 | ||
| 3093 | +80102,2017,6656 | ||
| 3094 | +80102,2018,6917 | ||
| 3095 | +80102,2019,7170 | ||
| 3096 | +80102,2020,7421 | ||
| 3097 | +80102,2021,7652 | ||
| 3098 | +80102,2022,7863 | ||
| 3099 | +80102,2023,8078 | ||
| 3100 | +80102,2024,8270 | ||
| 3101 | +80102,2025,8441 | ||
| 3102 | +80201,2016,99657 | ||
| 3103 | +80201,2017,101848 | ||
| 3104 | +80201,2018,104014 | ||
| 3105 | +80201,2019,106146 | ||
| 3106 | +80201,2020,108332 | ||
| 3107 | +80201,2021,110296 | ||
| 3108 | +80201,2022,112029 | ||
| 3109 | +80201,2023,113882 | ||
| 3110 | +80201,2024,115460 | ||
| 3111 | +80201,2025,116844 | ||
| 3112 | +80202,2016,44233 | ||
| 3113 | +80202,2017,44342 | ||
| 3114 | +80202,2018,44407 | ||
| 3115 | +80202,2019,44437 | ||
| 3116 | +80202,2020,44478 | ||
| 3117 | +80202,2021,44428 | ||
| 3118 | +80202,2022,44300 | ||
| 3119 | +80202,2023,44243 | ||
| 3120 | +80202,2024,44114 | ||
| 3121 | +80202,2025,43984 | ||
| 3122 | +80301,2016,13749 | ||
| 3123 | +80301,2017,13617 | ||
| 3124 | +80301,2018,13444 | ||
| 3125 | +80301,2019,13239 | ||
| 3126 | +80301,2020,13025 | ||
| 3127 | +80301,2021,12781 | ||
| 3128 | +80301,2022,12520 | ||
| 3129 | +80301,2023,12295 | ||
| 3130 | +80301,2024,12075 | ||
| 3131 | +80301,2025,11887 | ||
| 3132 | +80302,2016,44749 | ||
| 3133 | +80302,2017,45318 | ||
| 3134 | +80302,2018,45835 | ||
| 3135 | +80302,2019,46305 | ||
| 3136 | +80302,2020,46778 | ||
| 3137 | +80302,2021,47145 | ||
| 3138 | +80302,2022,47417 | ||
| 3139 | +80302,2023,47755 | ||
| 3140 | +80302,2024,48004 | ||
| 3141 | +80302,2025,48215 | ||
| 3142 | +80303,2016,10396 | ||
| 3143 | +80303,2017,10569 | ||
| 3144 | +80303,2018,10743 | ||
| 3145 | +80303,2019,10917 | ||
| 3146 | +80303,2020,11097 | ||
| 3147 | +80303,2021,11255 | ||
| 3148 | +80303,2022,11389 | ||
| 3149 | +80303,2023,11534 | ||
| 3150 | +80303,2024,11650 | ||
| 3151 | +80303,2025,11745 | ||
| 3152 | +80304,2016,21156 | ||
| 3153 | +80304,2017,21400 | ||
| 3154 | +80304,2018,21596 | ||
| 3155 | +80304,2019,21755 | ||
| 3156 | +80304,2020,21907 | ||
| 3157 | +80304,2021,22007 | ||
| 3158 | +80304,2022,22070 | ||
| 3159 | +80304,2023,22177 | ||
| 3160 | +80304,2024,22265 | ||
| 3161 | +80304,2025,22359 | ||
| 3162 | +80401,2016,17841 | ||
| 3163 | +80401,2017,17963 | ||
| 3164 | +80401,2018,18116 | ||
| 3165 | +80401,2019,18290 | ||
| 3166 | +80401,2020,18491 | ||
| 3167 | +80401,2021,18660 | ||
| 3168 | +80401,2022,18785 | ||
| 3169 | +80401,2023,18915 | ||
| 3170 | +80401,2024,18971 | ||
| 3171 | +80401,2025,18974 | ||
| 3172 | +80402,2016,7047 | ||
| 3173 | +80402,2017,7211 | ||
| 3174 | +80402,2018,7385 | ||
| 3175 | +80402,2019,7564 | ||
| 3176 | +80402,2020,7753 | ||
| 3177 | +80402,2021,7929 | ||
| 3178 | +80402,2022,8086 | ||
| 3179 | +80402,2023,8249 | ||
| 3180 | +80402,2024,8384 | ||
| 3181 | +80402,2025,8491 | ||
| 3182 | +80501,2016,20869 | ||
| 3183 | +80501,2017,21145 | ||
| 3184 | +80501,2018,21418 | ||
| 3185 | +80501,2019,21686 | ||
| 3186 | +80501,2020,21961 | ||
| 3187 | +80501,2021,22184 | ||
| 3188 | +80501,2022,22350 | ||
| 3189 | +80501,2023,22526 | ||
| 3190 | +80501,2024,22630 | ||
| 3191 | +80501,2025,22685 | ||
| 3192 | +80601,2016,4146 | ||
| 3193 | +80601,2017,4200 | ||
| 3194 | +80601,2018,4253 | ||
| 3195 | +80601,2019,4305 | ||
| 3196 | +80601,2020,4358 | ||
| 3197 | +80601,2021,4400 | ||
| 3198 | +80601,2022,4430 | ||
| 3199 | +80601,2023,4461 | ||
| 3200 | +80601,2024,4477 | ||
| 3201 | +80601,2025,4483 | ||
| 3202 | +80602,2016,14028 | ||
| 3203 | +80602,2017,14345 | ||
| 3204 | +80602,2018,14659 | ||
| 3205 | +80602,2019,14967 | ||
| 3206 | +80602,2020,15282 | ||
| 3207 | +80602,2021,15564 | ||
| 3208 | +80602,2022,15812 | ||
| 3209 | +80602,2023,16076 | ||
| 3210 | +80602,2024,16298 | ||
| 3211 | +80602,2025,16493 | ||
| 3212 | +80701,2016,7381 | ||
| 3213 | +80701,2017,7386 | ||
| 3214 | +80701,2018,7372 | ||
| 3215 | +80701,2019,7345 | ||
| 3216 | +80701,2020,7316 | ||
| 3217 | +80701,2021,7271 | ||
| 3218 | +80701,2022,7217 | ||
| 3219 | +80701,2023,7182 | ||
| 3220 | +80701,2024,7146 | ||
| 3221 | +80701,2025,7122 | ||
| 3222 | +80702,2016,5254 | ||
| 3223 | +80702,2017,5310 | ||
| 3224 | +80702,2018,5374 | ||
| 3225 | +80702,2019,5444 | ||
| 3226 | +80702,2020,5522 | ||
| 3227 | +80702,2021,5591 | ||
| 3228 | +80702,2022,5649 | ||
| 3229 | +80702,2023,5710 | ||
| 3230 | +80702,2024,5752 | ||
| 3231 | +80702,2025,5776 | ||
| 3232 | +80703,2016,1022 | ||
| 3233 | +80703,2017,1038 | ||
| 3234 | +80703,2018,1054 | ||
| 3235 | +80703,2019,1071 | ||
| 3236 | +80703,2020,1089 | ||
| 3237 | +80703,2021,1105 | ||
| 3238 | +80703,2022,1118 | ||
| 3239 | +80703,2023,1132 | ||
| 3240 | +80703,2024,1143 | ||
| 3241 | +80703,2025,1153 | ||
| 3242 | +80801,2016,12448 | ||
| 3243 | +80801,2017,12612 | ||
| 3244 | +80801,2018,12764 | ||
| 3245 | +80801,2019,12905 | ||
| 3246 | +80801,2020,13046 | ||
| 3247 | +80801,2021,13157 | ||
| 3248 | +80801,2022,13239 | ||
| 3249 | +80801,2023,13335 | ||
| 3250 | +80801,2024,13402 | ||
| 3251 | +80801,2025,13455 | ||
| 3252 | +80802,2016,6682 | ||
| 3253 | +80802,2017,6836 | ||
| 3254 | +80802,2018,6991 | ||
| 3255 | +80802,2019,7146 | ||
| 3256 | +80802,2020,7305 | ||
| 3257 | +80802,2021,7451 | ||
| 3258 | +80802,2022,7581 | ||
| 3259 | +80802,2023,7718 | ||
| 3260 | +80802,2024,7836 | ||
| 3261 | +80802,2025,7937 | ||
| 3262 | +80803,2016,4492 | ||
| 3263 | +80803,2017,4552 | ||
| 3264 | +80803,2018,4609 | ||
| 3265 | +80803,2019,4663 | ||
| 3266 | +80803,2020,4718 | ||
| 3267 | +80803,2021,4761 | ||
| 3268 | +80803,2022,4793 | ||
| 3269 | +80803,2023,4829 | ||
| 3270 | +80803,2024,4854 | ||
| 3271 | +80803,2025,4873 | ||
| 3272 | +80502,2016,3682 | ||
| 3273 | +80502,2017,3763 | ||
| 3274 | +80502,2018,3836 | ||
| 3275 | +80502,2019,3903 | ||
| 3276 | +80502,2020,3967 | ||
| 3277 | +80502,2021,4021 | ||
| 3278 | +80502,2022,4066 | ||
| 3279 | +80502,2023,4117 | ||
| 3280 | +80502,2024,4161 | ||
| 3281 | +80502,2025,4201 | ||
| 3282 | +90101,2016,52269 | ||
| 3283 | +90101,2017,53085 | ||
| 3284 | +90101,2018,53752 | ||
| 3285 | +90101,2019,54325 | ||
| 3286 | +90101,2020,54912 | ||
| 3287 | +90101,2021,55438 | ||
| 3288 | +90101,2022,55968 | ||
| 3289 | +90101,2023,56746 | ||
| 3290 | +90101,2024,57643 | ||
| 3291 | +90101,2025,58663 | ||
| 3292 | +90102,2016,8965 | ||
| 3293 | +90102,2017,9095 | ||
| 3294 | +90102,2018,9198 | ||
| 3295 | +90102,2019,9283 | ||
| 3296 | +90102,2020,9369 | ||
| 3297 | +90102,2021,9445 | ||
| 3298 | +90102,2022,9522 | ||
| 3299 | +90102,2023,9642 | ||
| 3300 | +90102,2024,9784 | ||
| 3301 | +90102,2025,9952 | ||
| 3302 | +90103,2016,2425 | ||
| 3303 | +90103,2017,2454 | ||
| 3304 | +90103,2018,2475 | ||
| 3305 | +90103,2019,2491 | ||
| 3306 | +90103,2020,2506 | ||
| 3307 | +90103,2021,2515 | ||
| 3308 | +90103,2022,2522 | ||
| 3309 | +90103,2023,2537 | ||
| 3310 | +90103,2024,2553 | ||
| 3311 | +90103,2025,2577 | ||
| 3312 | +90104,2016,4172 | ||
| 3313 | +90104,2017,4151 | ||
| 3314 | +90104,2018,4112 | ||
| 3315 | +90104,2019,4061 | ||
| 3316 | +90104,2020,4007 | ||
| 3317 | +90104,2021,3946 | ||
| 3318 | +90104,2022,3885 | ||
| 3319 | +90104,2023,3843 | ||
| 3320 | +90104,2024,3811 | ||
| 3321 | +90104,2025,3796 | ||
| 3322 | +90201,2016,6946 | ||
| 3323 | +90201,2017,7046 | ||
| 3324 | +90201,2018,7135 | ||
| 3325 | +90201,2019,7217 | ||
| 3326 | +90201,2020,7305 | ||
| 3327 | +90201,2021,7387 | ||
| 3328 | +90201,2022,7469 | ||
| 3329 | +90201,2023,7581 | ||
| 3330 | +90201,2024,7705 | ||
| 3331 | +90201,2025,7839 | ||
| 3332 | +90202,2016,3392 | ||
| 3333 | +90202,2017,3438 | ||
| 3334 | +90202,2018,3473 | ||
| 3335 | +90202,2019,3499 | ||
| 3336 | +90202,2020,3526 | ||
| 3337 | +90202,2021,3549 | ||
| 3338 | +90202,2022,3575 | ||
| 3339 | +90202,2023,3621 | ||
| 3340 | +90202,2024,3679 | ||
| 3341 | +90202,2025,3750 | ||
| 3342 | +90203,2016,6754 | ||
| 3343 | +90203,2017,6971 | ||
| 3344 | +90203,2018,7181 | ||
| 3345 | +90203,2019,7386 | ||
| 3346 | +90203,2020,7595 | ||
| 3347 | +90203,2021,7794 | ||
| 3348 | +90203,2022,7984 | ||
| 3349 | +90203,2023,8196 | ||
| 3350 | +90203,2024,8404 | ||
| 3351 | +90203,2025,8610 | ||
| 3352 | +90301,2016,9869 | ||
| 3353 | +90301,2017,10278 | ||
| 3354 | +90301,2018,10678 | ||
| 3355 | +90301,2019,11070 | ||
| 3356 | +90301,2020,11469 | ||
| 3357 | +90301,2021,11849 | ||
| 3358 | +90301,2022,12212 | ||
| 3359 | +90301,2023,12601 | ||
| 3360 | +90301,2024,12977 | ||
| 3361 | +90301,2025,13334 | ||
| 3362 | +90302,2016,8832 | ||
| 3363 | +90302,2017,9037 | ||
| 3364 | +90302,2018,9220 | ||
| 3365 | +90302,2019,9389 | ||
| 3366 | +90302,2020,9562 | ||
| 3367 | +90302,2021,9721 | ||
| 3368 | +90302,2022,9877 | ||
| 3369 | +90302,2023,10071 | ||
| 3370 | +90302,2024,10277 | ||
| 3371 | +90302,2025,10498 | ||
| 3372 | +90303,2016,9611 | ||
| 3373 | +90303,2017,9819 | ||
| 3374 | +90303,2018,9991 | ||
| 3375 | +90303,2019,10141 | ||
| 3376 | +90303,2020,10291 | ||
| 3377 | +90303,2021,10430 | ||
| 3378 | +90303,2022,10572 | ||
| 3379 | +90303,2023,10768 | ||
| 3380 | +90303,2024,10995 | ||
| 3381 | +90303,2025,11253 | ||
| 3382 | +90401,2016,2651 | ||
| 3383 | +90401,2017,2700 | ||
| 3384 | +90401,2018,2748 | ||
| 3385 | +90401,2019,2796 | ||
| 3386 | +90401,2020,2847 | ||
| 3387 | +90401,2021,2893 | ||
| 3388 | +90401,2022,2935 | ||
| 3389 | +90401,2023,2982 | ||
| 3390 | +90401,2024,3024 | ||
| 3391 | +90401,2025,3065 | ||
| 3392 | +90402,2016,2019 | ||
| 3393 | +90402,2017,2113 | ||
| 3394 | +90402,2018,2206 | ||
| 3395 | +90402,2019,2298 | ||
| 3396 | +90402,2020,2390 | ||
| 3397 | +90402,2021,2477 | ||
| 3398 | +90402,2022,2558 | ||
| 3399 | +90402,2023,2640 | ||
| 3400 | +90402,2024,2715 | ||
| 3401 | +90402,2025,2785 | ||
| 3402 | +90501,2016,2172 | ||
| 3403 | +90501,2017,2130 | ||
| 3404 | +90501,2018,2073 | ||
| 3405 | +90501,2019,2005 | ||
| 3406 | +90501,2020,1933 | ||
| 3407 | +90501,2021,1858 | ||
| 3408 | +90501,2022,1786 | ||
| 3409 | +90501,2023,1727 | ||
| 3410 | +90501,2024,1682 | ||
| 3411 | +90501,2025,1657 | ||
| 3412 | +90502,2016,3014 | ||
| 3413 | +90502,2017,2988 | ||
| 3414 | +90502,2018,2944 | ||
| 3415 | +90502,2019,2887 | ||
| 3416 | +90502,2020,2827 | ||
| 3417 | +90502,2021,2763 | ||
| 3418 | +90502,2022,2703 | ||
| 3419 | +90502,2023,2661 | ||
| 3420 | +90502,2024,2636 | ||
| 3421 | +90502,2025,2631 | ||
| 3422 | +90503,2016,2429 | ||
| 3423 | +90503,2017,2444 | ||
| 3424 | +90503,2018,2446 | ||
| 3425 | +90503,2019,2440 | ||
| 3426 | +90503,2020,2432 | ||
| 3427 | +90503,2021,2423 | ||
| 3428 | +90503,2022,2418 | ||
| 3429 | +90503,2023,2430 | ||
| 3430 | +90503,2024,2456 | ||
| 3431 | +90503,2025,2496 |
static/screen.png
deleted
100644 → 0
52.3 KB
-
Please register or login to post a comment