Rafael Lopez

objetos

...@@ -45,7 +45,7 @@ ...@@ -45,7 +45,7 @@
45 scaleBand() 45 scaleBand()
46 .domain(data.map(d => d.año)) 46 .domain(data.map(d => d.año))
47 .range([0, 100]) // porcentaje 47 .range([0, 100]) // porcentaje
48 - .padding(0.2) 48 + .padding(0.6)
49 ); 49 );
50 50
51 // Para CSS bottom positioning: 0 → 0%, max → 100% 51 // Para CSS bottom positioning: 0 → 0%, max → 100%
...@@ -112,15 +112,11 @@ ...@@ -112,15 +112,11 @@
112 <!-- Eje X --> 112 <!-- Eje X -->
113 <div class="x-axis" style="height: {marginBottom}px; left: {marginLeft}px; right: {marginRight}px"> 113 <div class="x-axis" style="height: {marginBottom}px; left: {marginLeft}px; right: {marginRight}px">
114 {#each data as d, i} 114 {#each data as d, i}
115 - {@const isFirst = i === 0}
116 - {@const isLast = i === data.length - 1}
117 - {@const isMid = i === midIndex && data.length > 5}
118 - {@const showLabel = isFirst || isLast || isMid || hoveredYear === d.año}
119 <span 115 <span
120 - class="x-label" 116 + class="x-label visible"
121 - class:visible={showLabel} 117 + class:hovered={hoveredYear === d.año}
122 > 118 >
123 - {d.año} 119 + {String(d.año).slice(-2)}
124 </span> 120 </span>
125 {/each} 121 {/each}
126 </div> 122 </div>
...@@ -170,7 +166,7 @@ ...@@ -170,7 +166,7 @@
170 bottom: 0; 166 bottom: 0;
171 display: flex; 167 display: flex;
172 align-items: flex-end; 168 align-items: flex-end;
173 - gap: 2px; 169 + gap: 4px;
174 } 170 }
175 171
176 .bar-container { 172 .bar-container {
...@@ -178,11 +174,12 @@ ...@@ -178,11 +174,12 @@
178 height: 100%; 174 height: 100%;
179 display: flex; 175 display: flex;
180 align-items: flex-end; 176 align-items: flex-end;
177 + justify-content: center;
181 cursor: default; 178 cursor: default;
182 } 179 }
183 180
184 .bar { 181 .bar {
185 - width: 100%; 182 + width: 80%;
186 background: #c4897d; 183 background: #c4897d;
187 border-radius: 4px 4px 0 0; 184 border-radius: 4px 4px 0 0;
188 transition: opacity 0.1s ease; 185 transition: opacity 0.1s ease;
...@@ -206,20 +203,27 @@ ...@@ -206,20 +203,27 @@
206 bottom: 0; 203 bottom: 0;
207 display: flex; 204 display: flex;
208 align-items: center; 205 align-items: center;
206 + gap: 4px;
209 } 207 }
210 208
211 .x-label { 209 .x-label {
212 flex: 1; 210 flex: 1;
213 text-align: center; 211 text-align: center;
214 - font-family: 'Qanelas', var(--font-sans); 212 + font-family: 'DM Mono', monospace;
215 - font-size: 0.6875rem; 213 + font-size: 0.5625rem;
216 color: var(--theme-texto); 214 color: var(--theme-texto);
217 opacity: 0; 215 opacity: 0;
218 transition: opacity 0.2s ease; 216 transition: opacity 0.2s ease;
219 } 217 }
220 218
221 .x-label.visible { 219 .x-label.visible {
220 + opacity: 0.5;
221 + }
222 +
223 + .x-label.hovered {
222 opacity: 1; 224 opacity: 1;
225 + color: var(--theme-titulo);
226 + font-weight: 600;
223 } 227 }
224 228
225 .chart-empty { 229 .chart-empty {
......
...@@ -101,12 +101,14 @@ ...@@ -101,12 +101,14 @@
101 padding: 0.125rem 0; 101 padding: 0.125rem 0;
102 background: transparent; 102 background: transparent;
103 border: none; 103 border: none;
104 - border-bottom: 1px dotted #333333; 104 + border-bottom: 1px dotted #666666;
105 color: var(--theme-titulo); 105 color: var(--theme-titulo);
106 font-size: 1rem; 106 font-size: 1rem;
107 font-weight: 500; 107 font-weight: 500;
108 cursor: pointer; 108 cursor: pointer;
109 transition: border-color 0.2s; 109 transition: border-color 0.2s;
110 + max-width: 100%;
111 + min-width: 0;
110 } 112 }
111 113
112 .entity-btn:hover { 114 .entity-btn:hover {
...@@ -115,6 +117,9 @@ ...@@ -115,6 +117,9 @@
115 117
116 .entity-label { 118 .entity-label {
117 text-align: left; 119 text-align: left;
120 + overflow: hidden;
121 + text-overflow: ellipsis;
122 + white-space: nowrap;
118 } 123 }
119 124
120 .chevron { 125 .chevron {
...@@ -132,7 +137,8 @@ ...@@ -132,7 +137,8 @@
132 position: absolute; 137 position: absolute;
133 top: calc(100% + 4px); 138 top: calc(100% + 4px);
134 left: 0; 139 left: 0;
135 - min-width: 280px; 140 + right: 0;
141 + min-width: min(280px, calc(100vw - 2rem));
136 background: var(--theme-surface); 142 background: var(--theme-surface);
137 border: 1px solid var(--theme-borde); 143 border: 1px solid var(--theme-borde);
138 border-radius: 10px; 144 border-radius: 10px;
......
1 <script> 1 <script>
2 import { goto } from '$app/navigation'; 2 import { goto } from '$app/navigation';
3 - import { indexLoaded } from '$lib/stores/searchStore';
4 - import { search } from '$lib/services/search';
5 3
6 let { 4 let {
7 preserveVista = false, 5 preserveVista = false,
...@@ -16,20 +14,46 @@ ...@@ -16,20 +14,46 @@
16 let selectedIdx = $state(-1); 14 let selectedIdx = $state(-1);
17 let debounceTimer; 15 let debounceTimer;
18 16
17 + function parseMetadatos(meta) {
18 + if (!meta) return {};
19 + if (typeof meta === 'object') return meta;
20 + try { return JSON.parse(meta); } catch { return {}; }
21 + }
22 +
23 + async function doSearch(query) {
24 + isSearching = true;
25 + try {
26 + const params = new URLSearchParams({
27 + q: query,
28 + per_page: '20',
29 + is_class: 'true',
30 + class_: 'objeto'
31 + });
32 + const res = await fetch(`/api/search?${params}`);
33 + if (!res.ok) throw new Error();
34 + const data = await res.json();
35 + localResults = (data.hits || []).map(hit => {
36 + const meta = parseMetadatos(hit.document.metadatos);
37 + const codigo = meta.objeto_subpartida || meta.objeto_partida || meta.objeto_subgrupo || meta.objeto_grupo || '';
38 + const nombre = hit.document.texto;
39 + const highlight = hit.highlights?.[0]?.snippet || nombre;
40 + return { codigo, nombre, highlight };
41 + });
42 + selectedIdx = -1;
43 + } catch {
44 + localResults = [];
45 + }
46 + isSearching = false;
47 + }
48 +
19 function handleSearchInput(e) { 49 function handleSearchInput(e) {
20 - const val = e.target.value; 50 + searchVal = e.target.value;
21 clearTimeout(debounceTimer); 51 clearTimeout(debounceTimer);
22 - debounceTimer = setTimeout(() => { 52 + if (searchVal.length >= 2) {
23 - if (val.length >= 2) { 53 + debounceTimer = setTimeout(() => doSearch(searchVal), 200);
24 - isSearching = true; 54 + } else {
25 - // Buscar y filtrar solo objetos de gasto 55 + localResults = [];
26 - const allResults = search(val, 50); 56 + }
27 - localResults = allResults.filter(r => r.tipo === 'objeto_gasto').slice(0, 15);
28 - isSearching = false;
29 - } else {
30 - localResults = [];
31 - }
32 - }, 200);
33 } 57 }
34 58
35 function handleSearchKeydown(e) { 59 function handleSearchKeydown(e) {
...@@ -86,7 +110,6 @@ ...@@ -86,7 +110,6 @@
86 onfocus={() => searchFocused = true} 110 onfocus={() => searchFocused = true}
87 onblur={handleSearchBlur} 111 onblur={handleSearchBlur}
88 placeholder="Buscar objeto..." 112 placeholder="Buscar objeto..."
89 - disabled={!$indexLoaded}
90 /> 113 />
91 {#if searchVal} 114 {#if searchVal}
92 <button class="search-clear" onclick={handleClear}> 115 <button class="search-clear" onclick={handleClear}>
...@@ -110,7 +133,7 @@ ...@@ -110,7 +133,7 @@
110 onmouseenter={() => selectedIdx = i} 133 onmouseenter={() => selectedIdx = i}
111 > 134 >
112 <span class="item-code">{result.codigo}</span> 135 <span class="item-code">{result.codigo}</span>
113 - <span class="item-name">{result.nombre}</span> 136 + <span class="item-name">{@html result.highlight}</span>
114 </button> 137 </button>
115 {/each} 138 {/each}
116 {/if} 139 {/if}
...@@ -229,4 +252,11 @@ ...@@ -229,4 +252,11 @@
229 font-size: 0.8125rem; 252 font-size: 0.8125rem;
230 color: var(--theme-titulo); 253 color: var(--theme-titulo);
231 } 254 }
255 +
256 + :global(.item-name mark) {
257 + background: rgba(201, 167, 81, 0.3);
258 + color: inherit;
259 + border-radius: 2px;
260 + padding: 0 1px;
261 + }
232 </style> 262 </style>
......
...@@ -72,13 +72,13 @@ ...@@ -72,13 +72,13 @@
72 72
73 // Detectar tipos de clasificadores presentes en los resultados 73 // Detectar tipos de clasificadores presentes en los resultados
74 // Mapeo inverso: API class_ → id interno del clasificador 74 // Mapeo inverso: API class_ → id interno del clasificador
75 - const CLASS_REVERSE_MAP = { ubigeo: 'geografico', acteco: 'sectores' }; 75 + const CLASS_REVERSE_MAP = { ubigeo: 'geografico', acteco: 'sectores', finalidad: 'finfun' };
76 76
77 // Filtrar hits según clasificadores seleccionados (la API puede devolver tipos extra) 77 // Filtrar hits según clasificadores seleccionados (la API puede devolver tipos extra)
78 $: filteredHits = (() => { 78 $: filteredHits = (() => {
79 if (searchMode !== 'clasificadores' || selectedClassifiers.length === 0) return allHits; 79 if (searchMode !== 'clasificadores' || selectedClassifiers.length === 0) return allHits;
80 const allowedClasses = new Set(selectedClassifiers.map(c => { 80 const allowedClasses = new Set(selectedClassifiers.map(c => {
81 - const map = { geografico: 'ubigeo', sectores: 'acteco' }; 81 + const map = { geografico: 'ubigeo', sectores: 'acteco', finfun: 'finalidad' };
82 return map[c] || c; 82 return map[c] || c;
83 })); 83 }));
84 return allHits.filter(h => allowedClasses.has(h.document.class_)); 84 return allHits.filter(h => allowedClasses.has(h.document.class_));
...@@ -86,7 +86,12 @@ ...@@ -86,7 +86,12 @@
86 86
87 $: activeClassTypes = (() => { 87 $: activeClassTypes = (() => {
88 const types = new Set(); 88 const types = new Set();
89 - filteredHits.forEach(h => { if (h.document.class_) types.add(h.document.class_); }); 89 + filteredHits.forEach(h => {
90 + if (h.document.class_) {
91 + const normalized = CLASS_REVERSE_MAP[h.document.class_] || h.document.class_;
92 + types.add(normalized);
93 + }
94 + });
90 return types; 95 return types;
91 })(); 96 })();
92 $: isMixedClassSearch = activeClassTypes.size > 1; 97 $: isMixedClassSearch = activeClassTypes.size > 1;
...@@ -154,14 +159,14 @@ ...@@ -154,14 +159,14 @@
154 if (isMixedClassSearch || searchMode === 'todo') { 159 if (isMixedClassSearch || searchMode === 'todo') {
155 // Filtrar duplicados de finfun 160 // Filtrar duplicados de finfun
156 const filtered = filteredHits.filter(hit => { 161 const filtered = filteredHits.filter(hit => {
157 - if (hit.document.class_ !== 'finfun') return true; 162 + if (hit.document.class_ !== 'finfun' && hit.document.class_ !== 'finalidad') return true;
158 const m = parseMetadatos(hit.document.metadatos); 163 const m = parseMetadatos(hit.document.metadatos);
159 return !(m.finfun_funcion === 0 && m.finfun_grpfuncion !== undefined); 164 return !(m.finfun_funcion === 0 && m.finfun_grpfuncion !== undefined);
160 }); 165 });
161 166
162 const groups = {}; 167 const groups = {};
163 filtered.forEach(hit => { 168 filtered.forEach(hit => {
164 - const type = hit.document.class_ || 'otros'; 169 + const type = CLASS_REVERSE_MAP[hit.document.class_] || hit.document.class_ || 'otros';
165 const groupName = CLASS_LABELS[type] || type; 170 const groupName = CLASS_LABELS[type] || type;
166 if (!groups[groupName]) { 171 if (!groups[groupName]) {
167 groups[groupName] = { name: groupName, hits: [], totalMonto: 0, classType: type }; 172 groups[groupName] = { name: groupName, hits: [], totalMonto: 0, classType: type };
...@@ -692,7 +697,7 @@ ...@@ -692,7 +697,7 @@
692 const isClassResult = hit.document.is_class === true; 697 const isClassResult = hit.document.is_class === true;
693 698
694 let url; 699 let url;
695 - const isFinfunClass = hit.document.class_ === 'finfun'; 700 + const isFinfunClass = hit.document.class_ === 'finfun' || hit.document.class_ === 'finalidad';
696 701
697 const isUbigeoClass = hit.document.class_ === 'ubigeo'; 702 const isUbigeoClass = hit.document.class_ === 'ubigeo';
698 703
...@@ -1207,7 +1212,7 @@ ...@@ -1207,7 +1212,7 @@
1207 {#each group.hits.slice(0, maxVisible) as hit, i (getHitKey(hit, gi * 1000 + i))} 1212 {#each group.hits.slice(0, maxVisible) as hit, i (getHitKey(hit, gi * 1000 + i))}
1208 {@const meta = parseMetadatos(hit.document.metadatos)} 1213 {@const meta = parseMetadatos(hit.document.metadatos)}
1209 {@const globalIdx = flatHitsForNav.indexOf(hit)} 1214 {@const globalIdx = flatHitsForNav.indexOf(hit)}
1210 - {@const hitClass = hit.document.class_} 1215 + {@const hitClass = CLASS_REVERSE_MAP[hit.document.class_] || hit.document.class_}
1211 {#if hitClass === 'entidad'} 1216 {#if hitClass === 'entidad'}
1212 {@const isDA = meta.da && meta.entidad_desc_entidad} 1217 {@const isDA = meta.da && meta.entidad_desc_entidad}
1213 {@const parentEntity = isDA ? extractAfterHyphen(meta.entidad_desc_entidad) : null} 1218 {@const parentEntity = isDA ? extractAfterHyphen(meta.entidad_desc_entidad) : null}
...@@ -1323,10 +1328,10 @@ ...@@ -1323,10 +1328,10 @@
1323 {@const subarea = isEntidadClass ? extractAfterHyphen(meta.entidad_desc_subarea) : null} 1328 {@const subarea = isEntidadClass ? extractAfterHyphen(meta.entidad_desc_subarea) : null}
1324 {@const parentEntity = isDA ? extractAfterHyphen(meta.entidad_desc_entidad) : null} 1329 {@const parentEntity = isDA ? extractAfterHyphen(meta.entidad_desc_entidad) : null}
1325 {@const isObjetoClass = hit.document.class_ === 'objeto'} 1330 {@const isObjetoClass = hit.document.class_ === 'objeto'}
1326 - {@const isFinfunClass = hit.document.class_ === 'finfun'} 1331 + {@const isFinfunClass = hit.document.class_ === 'finfun' || hit.document.class_ === 'finalidad'}
1327 {@const isUbigeoClass = hit.document.class_ === 'ubigeo'} 1332 {@const isUbigeoClass = hit.document.class_ === 'ubigeo'}
1328 {@const isClassResult = hit.document.is_class === true} 1333 {@const isClassResult = hit.document.is_class === true}
1329 - {@const hitType = hit.document.class_} 1334 + {@const hitType = CLASS_REVERSE_MAP[hit.document.class_] || hit.document.class_}
1330 <a 1335 <a
1331 href={isEntidadClass 1336 href={isEntidadClass
1332 ? `/entidad/${meta.da ? `${meta.entidad}.${meta.da}` : meta.entidad}` 1337 ? `/entidad/${meta.da ? `${meta.entidad}.${meta.da}` : meta.entidad}`
......
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
...@@ -28,8 +28,8 @@ ...@@ -28,8 +28,8 @@
28 let topEntidadesPorAño = $state({}); 28 let topEntidadesPorAño = $state({});
29 29
30 const POBLACION = 12000000; 30 const POBLACION = 12000000;
31 - const objetoCodigo = '11700'; 31 + let objetoCodigo = $derived(objetoData.objeto);
32 - const objetoNivel = 'subpartida'; 32 + let objetoNivel = $derived(objetoData.nivel);
33 33
34 let gastoPerCapita = $derived( 34 let gastoPerCapita = $derived(
35 datosAnuales.map(d => ({ 35 datosAnuales.map(d => ({
......
1 +import { supabase } from '$lib/supabase';
2 +import { error } from '@sveltejs/kit';
3 +
4 +// Funciones para derivar jerarquía del código objeto
5 +function getGrupoCode(objeto) {
6 + return objeto.charAt(0) + '0000';
7 +}
8 +
9 +function getSubgrupoCode(objeto) {
10 + return objeto.substring(0, 2) + '000';
11 +}
12 +
13 +function getPartidaCode(objeto) {
14 + return objeto.substring(0, 3) + '00';
15 +}
16 +
17 +function getNivel(objeto) {
18 + if (objeto.endsWith('0000')) return 'grupo';
19 + if (objeto.endsWith('000')) return 'subgrupo';
20 + if (objeto.endsWith('00')) return 'partida';
21 + return 'subpartida';
22 +}
23 +
24 +export async function load({ params }) {
25 + console.time('[SERVER] Total load objeto');
26 + const { codigo } = params;
27 +
28 + console.time('[SERVER] Query clas_objetos');
29 + const { data, error: dbError } = await supabase
30 + .schema('ppto')
31 + .from('clas_objetos')
32 + .select('*')
33 + .eq('objeto', codigo);
34 + console.timeEnd('[SERVER] Query clas_objetos');
35 +
36 + if (dbError || !data || data.length === 0) {
37 + throw error(404, 'Objeto no encontrado');
38 + }
39 +
40 + // Tomar el primer resultado
41 + const objeto = data[0];
42 + const nivel = objeto.nivel || getNivel(codigo);
43 +
44 + // Obtener jerarquía (padres e hijos)
45 + let padres = [];
46 + let hijos = [];
47 +
48 + console.time('[SERVER] Query padres');
49 +
50 + // Buscar padres según nivel
51 + if (nivel === 'subpartida') {
52 + // Padres: grupo, subgrupo, partida
53 + const grupoCode = getGrupoCode(codigo);
54 + const subgrupoCode = getSubgrupoCode(codigo);
55 + const partidaCode = getPartidaCode(codigo);
56 +
57 + const { data: padresData } = await supabase
58 + .schema('ppto')
59 + .from('clas_objetos')
60 + .select('*')
61 + .in('objeto', [grupoCode, subgrupoCode, partidaCode])
62 + .order('objeto');
63 +
64 + if (padresData) padres = padresData;
65 + } else if (nivel === 'partida') {
66 + // Padres: grupo, subgrupo
67 + const grupoCode = getGrupoCode(codigo);
68 + const subgrupoCode = getSubgrupoCode(codigo);
69 +
70 + const { data: padresData } = await supabase
71 + .schema('ppto')
72 + .from('clas_objetos')
73 + .select('*')
74 + .in('objeto', [grupoCode, subgrupoCode])
75 + .order('objeto');
76 +
77 + if (padresData) padres = padresData;
78 + } else if (nivel === 'subgrupo') {
79 + // Padre: grupo
80 + const grupoCode = getGrupoCode(codigo);
81 +
82 + const { data: padresData } = await supabase
83 + .schema('ppto')
84 + .from('clas_objetos')
85 + .select('*')
86 + .eq('objeto', grupoCode);
87 +
88 + if (padresData) padres = padresData;
89 + }
90 + // grupo no tiene padres
91 +
92 + console.timeEnd('[SERVER] Query padres');
93 +
94 + console.time('[SERVER] Query hijos');
95 +
96 + // Buscar hijos según nivel
97 + if (nivel === 'grupo') {
98 + // Hijos: subgrupos que empiecen con el mismo dígito
99 + const prefix = codigo.charAt(0);
100 + const { data: hijosData } = await supabase
101 + .schema('ppto')
102 + .from('clas_objetos')
103 + .select('*')
104 + .eq('nivel', 'subgrupo')
105 + .like('objeto', `${prefix}%`)
106 + .order('objeto');
107 +
108 + hijos = hijosData || [];
109 + } else if (nivel === 'subgrupo') {
110 + // Hijos: partidas que empiecen con los mismos 2 dígitos
111 + const prefix = codigo.substring(0, 2);
112 + const { data: hijosData } = await supabase
113 + .schema('ppto')
114 + .from('clas_objetos')
115 + .select('*')
116 + .eq('nivel', 'partida')
117 + .like('objeto', `${prefix}%`)
118 + .order('objeto');
119 +
120 + hijos = hijosData || [];
121 + } else if (nivel === 'partida') {
122 + // Hijos: subpartidas que empiecen con los mismos 3 dígitos
123 + const prefix = codigo.substring(0, 3);
124 + const { data: hijosData } = await supabase
125 + .schema('ppto')
126 + .from('clas_objetos')
127 + .select('*')
128 + .eq('nivel', 'subpartida')
129 + .like('objeto', `${prefix}%`)
130 + .order('objeto');
131 +
132 + hijos = hijosData || [];
133 + }
134 + // subpartida no tiene hijos
135 +
136 + console.timeEnd('[SERVER] Query hijos');
137 +
138 + console.timeEnd('[SERVER] Total load objeto');
139 + return {
140 + objeto,
141 + padres,
142 + hijos
143 + };
144 +}
This diff is collapsed. Click to expand it.