Rafael Lopez

ubigeo

...@@ -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 } 36 }
39 - return { entidad: true, objeto_gasto: true, rubro: true, finfun: true, organismo: true, fuente: true }; 37 + // Sync to landing store
40 - } 38 + landingSelectedClassifiers.set(selectedClassifiers);
41 -
42 - let searchFilters = $state(getInitialFilters());
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>
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>
281 - <span class="filter-desc">{typeConfig.rubro.description}</span>
282 - </button>
283 - <button
284 - class="search-filter-card"
285 - class:filter-active={searchFilters.finfun}
286 - onclick={() => toggleFilter('finfun')}
287 - >
288 - <div class="filter-card-header">
289 - <span class="filter-dot" style="background:{typeConfig.finfun.color}"></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> 283 </button>
319 </div> 284 </div>
285 + {#if searchMode === 'clasificadores'}
286 + <div class="classifier-pills">
287 + {#each CLASIFICADORES as cls}
288 + <button
289 + class="classifier-pill"
290 + class:active={selectedClassifiers.includes(cls.id)}
291 + onclick={() => toggleClassifier(cls.id)}
292 + >
293 + <span class="pill-label">{cls.label}</span>
294 + <span class="pill-tecnico">{cls.tecnico}</span>
295 + </button>
296 + {/each}
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('');
......
1 +import { writable } from 'svelte/store';
2 +
3 +// Cache global del mapa - se carga una sola vez en toda la sesión
4 +export const mapaCache = writable(null);
...@@ -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,9 +345,15 @@ ...@@ -282,9 +345,15 @@
282 } 345 }
283 346
284 function setMode(mode) { 347 function setMode(mode) {
285 - searchMode = mode; 348 + if (searchMode === mode) {
286 - if (mode === 'programas') { 349 + // Click en la misma card → deseleccionar, volver a "todo"
350 + searchMode = 'todo';
287 selectedClassifiers = []; 351 selectedClassifiers = [];
352 + } else {
353 + searchMode = mode;
354 + if (mode === 'programas') {
355 + selectedClassifiers = [];
356 + }
288 } 357 }
289 } 358 }
290 359
...@@ -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'; 844 + debounceTimer = setTimeout(() => performSearch(searchVal), DEBOUNCE_MS);
751 - if (!needsClassifiers || _classifiers.length > 0) {
752 - 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">
992 - <div class="results-hint">Escribe al menos 3 letras para buscar</div>
993 - </div>
994 - {:else if error && !results}
995 - <div class="results-message">
996 - <div class="results-error">{error}</div>
997 - </div>
998 - {:else if isLoading && !results}
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>
1006 - {/if}
1007 -
1008 - <!-- Título contextual de búsqueda -->
1009 - {#if allHits.length > 0 && searchContextText}
1010 - <div class="search-context-title">{searchContextText}</div>
1011 - {/if}
1012 -
1013 - <!-- Contenedor de resultados (siempre montado para evitar flicker) -->
1014 <div 1079 <div
1015 class="results-content results-content-filled" 1080 class="results-content results-content-filled"
1016 - class:results-content-visible={allHits.length > 0} 1081 + class:results-content-visible={allHits.length > 0 || (results && results.hits?.length === 0)}
1082 + on:mousedown|preventDefault
1017 > 1083 >
1018 - {#if stats} 1084 + {#if results && results.hits?.length === 0 && !isLoading}
1085 + <div class="results-message-inline">
1086 + <span class="results-empty">Sin resultados para "{searchVal}"</span>
1087 + </div>
1088 + {/if}
1089 +
1090 + <!-- Título contextual -->
1091 + {#if allHits.length > 0 && searchContextText}
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>
1098 + {/if}
1099 + </div>
1100 + {/if}
1101 + {#if stats && !isUbigeoSearchActive}
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,21 +1266,28 @@ ...@@ -1165,21 +1266,28 @@
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}`
1172 - : isObjetoClass 1275 + : isUbigeoClass
1173 - ? `/objeto/${getObjetoCodigo(meta)}` 1276 + ? `/ubicacion/${meta.municipio_ubigeo}`
1174 - : isFinfunClass 1277 + : isObjetoClass
1175 - ? `/finfun/${getFinfunCodigo(meta)}` 1278 + ? `/objeto/${getObjetoCodigo(meta)}`
1176 - : (isClassResult ? `/clasificador/${hit.document.class_}/${hit.document.id}` : `/proyecto/${generateCodigo(hit)}`)} 1279 + : isFinfunClass
1280 + ? `/finfun/${getFinfunCodigo(meta)}`
1281 + : (isClassResult ? `/clasificador/${hit.document.class_}/${hit.document.id}` : `/proyecto/${generateCodigo(hit)}`)}
1177 class="result-card" 1282 class="result-card"
1178 class:result-card-selected={selectedResultIndex === i} 1283 class:result-card-selected={selectedResultIndex === i}
1179 data-result-index={i} 1284 data-result-index={i}
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,12 +735,19 @@ ...@@ -712,12 +735,19 @@
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>
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>
721 </div> 751 </div>
722 <span class="clasificador-label">{label}</span> 752 <span class="clasificador-label">{label}</span>
723 </div> 753 </div>
...@@ -735,12 +765,19 @@ ...@@ -735,12 +765,19 @@
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>
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>
744 </div> 781 </div>
745 <span class="clasificador-label">{label}</span> 782 <span class="clasificador-label">{label}</span>
746 </div> 783 </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;
1096 + }
1097 +
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;
1047 } 1109 }
1048 1110
1049 - .ranking-pos { 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;
......
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>
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 +}
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>
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 +}
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>
This diff could not be displayed because it is too large.
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