Rafael Lopez

vista diaria

...@@ -8,9 +8,11 @@ ...@@ -8,9 +8,11 @@
8 "name": "ppto", 8 "name": "ppto",
9 "version": "0.0.1", 9 "version": "0.0.1",
10 "dependencies": { 10 "dependencies": {
11 + "@observablehq/plot": "^0.6.17",
11 "@supabase/supabase-js": "^2.98.0", 12 "@supabase/supabase-js": "^2.98.0",
12 "d3": "^7.9.0", 13 "d3": "^7.9.0",
13 "fuse.js": "^7.1.0", 14 "fuse.js": "^7.1.0",
15 + "hyparquet": "^1.25.1",
14 "marked": "^17.0.4" 16 "marked": "^17.0.4"
15 }, 17 },
16 "devDependencies": { 18 "devDependencies": {
...@@ -515,6 +517,20 @@ ...@@ -515,6 +517,20 @@
515 "@jridgewell/sourcemap-codec": "^1.4.14" 517 "@jridgewell/sourcemap-codec": "^1.4.14"
516 } 518 }
517 }, 519 },
520 + "node_modules/@observablehq/plot": {
521 + "version": "0.6.17",
522 + "resolved": "https://registry.npmjs.org/@observablehq/plot/-/plot-0.6.17.tgz",
523 + "integrity": "sha512-/qaXP/7mc4MUS0s4cPPFASDRjtsWp85/TbfsciqDgU1HwYixbSbbytNuInD8AcTYC3xaxACgVX06agdfQy9W+g==",
524 + "license": "ISC",
525 + "dependencies": {
526 + "d3": "^7.9.0",
527 + "interval-tree-1d": "^1.0.0",
528 + "isoformat": "^0.2.0"
529 + },
530 + "engines": {
531 + "node": ">=12"
532 + }
533 + },
518 "node_modules/@polka/url": { 534 "node_modules/@polka/url": {
519 "version": "1.0.0-next.29", 535 "version": "1.0.0-next.29",
520 "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", 536 "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz",
...@@ -1410,6 +1426,12 @@ ...@@ -1410,6 +1426,12 @@
1410 "node": ">= 0.4" 1426 "node": ">= 0.4"
1411 } 1427 }
1412 }, 1428 },
1429 + "node_modules/binary-search-bounds": {
1430 + "version": "2.0.5",
1431 + "resolved": "https://registry.npmjs.org/binary-search-bounds/-/binary-search-bounds-2.0.5.tgz",
1432 + "integrity": "sha512-H0ea4Fd3lS1+sTEB2TgcLoK21lLhwEJzlQv3IN47pJS976Gx4zoWe0ak3q+uYh60ppQxg9F16Ri4tS1sfD4+jA==",
1433 + "license": "MIT"
1434 + },
1413 "node_modules/clsx": { 1435 "node_modules/clsx": {
1414 "version": "2.1.1", 1436 "version": "2.1.1",
1415 "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", 1437 "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
...@@ -1998,6 +2020,12 @@ ...@@ -1998,6 +2020,12 @@
1998 "dev": true, 2020 "dev": true,
1999 "license": "ISC" 2021 "license": "ISC"
2000 }, 2022 },
2023 + "node_modules/hyparquet": {
2024 + "version": "1.25.1",
2025 + "resolved": "https://registry.npmjs.org/hyparquet/-/hyparquet-1.25.1.tgz",
2026 + "integrity": "sha512-CXcN/u6RdQqsK8IphUptpAEqY8IzgwzHY+MuXX+2wpoWTumfxPVr6JYbbywsNsiAl9aEbM5sRtxkwRBa22b49w==",
2027 + "license": "MIT"
2028 + },
2001 "node_modules/iceberg-js": { 2029 "node_modules/iceberg-js": {
2002 "version": "0.8.1", 2030 "version": "0.8.1",
2003 "resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz", 2031 "resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz",
...@@ -2028,6 +2056,15 @@ ...@@ -2028,6 +2056,15 @@
2028 "node": ">=12" 2056 "node": ">=12"
2029 } 2057 }
2030 }, 2058 },
2059 + "node_modules/interval-tree-1d": {
2060 + "version": "1.0.4",
2061 + "resolved": "https://registry.npmjs.org/interval-tree-1d/-/interval-tree-1d-1.0.4.tgz",
2062 + "integrity": "sha512-wY8QJH+6wNI0uh4pDQzMvl+478Qh7Rl4qLmqiluxALlNvl+I+o5x38Pw3/z7mDPTPS1dQalZJXsmbvxx5gclhQ==",
2063 + "license": "MIT",
2064 + "dependencies": {
2065 + "binary-search-bounds": "^2.0.0"
2066 + }
2067 + },
2031 "node_modules/is-reference": { 2068 "node_modules/is-reference": {
2032 "version": "3.0.3", 2069 "version": "3.0.3",
2033 "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", 2070 "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz",
...@@ -2038,6 +2075,12 @@ ...@@ -2038,6 +2075,12 @@
2038 "@types/estree": "^1.0.6" 2075 "@types/estree": "^1.0.6"
2039 } 2076 }
2040 }, 2077 },
2078 + "node_modules/isoformat": {
2079 + "version": "0.2.1",
2080 + "resolved": "https://registry.npmjs.org/isoformat/-/isoformat-0.2.1.tgz",
2081 + "integrity": "sha512-tFLRAygk9NqrRPhJSnNGh7g7oaVWDwR0wKh/GM2LgmPa50Eg4UfyaCO4I8k6EqJHl1/uh2RAD6g06n5ygEnrjQ==",
2082 + "license": "ISC"
2083 + },
2041 "node_modules/jiti": { 2084 "node_modules/jiti": {
2042 "version": "2.6.1", 2085 "version": "2.6.1",
2043 "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", 2086 "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
......
...@@ -19,9 +19,11 @@ ...@@ -19,9 +19,11 @@
19 "vite": "^7.3.1" 19 "vite": "^7.3.1"
20 }, 20 },
21 "dependencies": { 21 "dependencies": {
22 + "@observablehq/plot": "^0.6.17",
22 "@supabase/supabase-js": "^2.98.0", 23 "@supabase/supabase-js": "^2.98.0",
23 "d3": "^7.9.0", 24 "d3": "^7.9.0",
24 "fuse.js": "^7.1.0", 25 "fuse.js": "^7.1.0",
26 + "hyparquet": "^1.25.1",
25 "marked": "^17.0.4" 27 "marked": "^17.0.4"
26 } 28 }
27 } 29 }
......
...@@ -32,8 +32,14 @@ ...@@ -32,8 +32,14 @@
32 }); 32 });
33 </script> 33 </script>
34 34
35 -<!-- MÓVIL: Búsqueda + Theme + hamburguesa arriba a la derecha --> 35 +<!-- MÓVIL: Home + Búsqueda + Theme + hamburguesa arriba a la derecha -->
36 <div class="navbar-mobile"> 36 <div class="navbar-mobile">
37 + <a href="/" class="nav-home-link" aria-label="Ir al inicio">
38 + <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
39 + <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
40 + <polyline points="9 22 9 12 15 12 15 22"/>
41 + </svg>
42 + </a>
37 <button 43 <button
38 onclick={onOpenSearch} 44 onclick={onOpenSearch}
39 class="nav-btn-icon-mobile" 45 class="nav-btn-icon-mobile"
...@@ -70,10 +76,25 @@ ...@@ -70,10 +76,25 @@
70 </button> 76 </button>
71 </div> 77 </div>
72 78
73 -<!-- DESKTOP: Barra completa con Search + Theme + Menu --> 79 +<!-- DESKTOP: Barra completa con Home + Search + Theme + Menu -->
74 <div class="navbar-desktop"> 80 <div class="navbar-desktop">
75 <div class="navbar-desktop-inner"> 81 <div class="navbar-desktop-inner">
76 82
83 + <!-- BOTÓN HOME -->
84 + <div class="relative group/home">
85 + <a href="/" class="nav-btn-icon" aria-label="Ir al inicio">
86 + <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
87 + <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
88 + <polyline points="9 22 9 12 15 12 15 22"/>
89 + </svg>
90 + </a>
91 + <!-- Tooltip -->
92 + <div class="absolute right-0 top-full mt-2 px-3 py-1.5 bg-gray-900/90 text-white text-xs rounded-lg whitespace-nowrap opacity-0 invisible group-hover/home:opacity-100 group-hover/home:visible transition-all duration-200 pointer-events-none backdrop-blur-sm">
93 + Inicio
94 + <div class="absolute right-3 bottom-full mb-[-4px] w-2 h-2 bg-gray-900/90 rotate-45"></div>
95 + </div>
96 + </div>
97 +
77 <!-- BOTÓN BÚSQUEDA GLOBAL --> 98 <!-- BOTÓN BÚSQUEDA GLOBAL -->
78 <div class="relative group/search"> 99 <div class="relative group/search">
79 <button 100 <button
...@@ -152,6 +173,13 @@ ...@@ -152,6 +173,13 @@
152 z-index: 110; 173 z-index: 110;
153 align-items: center; 174 align-items: center;
154 gap: 0.25rem; 175 gap: 0.25rem;
176 + padding: 6px;
177 + background: rgba(40, 40, 38, 0.7);
178 + backdrop-filter: blur(20px) saturate(180%);
179 + -webkit-backdrop-filter: blur(20px) saturate(180%);
180 + border: 1px solid rgba(255, 255, 255, 0.1);
181 + border-radius: 14px;
182 + box-shadow: 0 4px 24px rgba(0, 0, 0, 0.15);
155 } 183 }
156 184
157 .navbar-desktop { 185 .navbar-desktop {
...@@ -160,6 +188,20 @@ ...@@ -160,6 +188,20 @@
160 top: 1.5rem; 188 top: 1.5rem;
161 right: 1.5rem; 189 right: 1.5rem;
162 z-index: 110; 190 z-index: 110;
191 + padding: 6px;
192 + background: rgba(40, 40, 38, 0.7);
193 + backdrop-filter: blur(20px) saturate(180%);
194 + -webkit-backdrop-filter: blur(20px) saturate(180%);
195 + border: 1px solid rgba(255, 255, 255, 0.1);
196 + border-radius: 14px;
197 + box-shadow: 0 4px 24px rgba(0, 0, 0, 0.15);
198 + }
199 +
200 + :global(html:not(.dark)) .navbar-mobile,
201 + :global(html:not(.dark)) .navbar-desktop {
202 + background: rgba(255, 255, 255, 0.7);
203 + border-color: rgba(0, 0, 0, 0.08);
204 + box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08);
163 } 205 }
164 206
165 .navbar-desktop-inner { 207 .navbar-desktop-inner {
...@@ -201,16 +243,16 @@ ...@@ -201,16 +243,16 @@
201 align-items: center; 243 align-items: center;
202 gap: 10px; 244 gap: 10px;
203 padding: 7px 12px 7px 12px; 245 padding: 7px 12px 7px 12px;
204 - background: rgba(255, 255, 255, 0.05); 246 + background: rgba(255, 255, 255, 0.08);
205 - border: 1px solid rgba(255, 255, 255, 0.12); 247 + border: 1px solid rgba(255, 255, 255, 0.1);
206 border-radius: 10px; 248 border-radius: 10px;
207 - color: #9B9890; 249 + color: #B8B5AD;
208 cursor: pointer; 250 cursor: pointer;
209 transition: all 0.2s ease; 251 transition: all 0.2s ease;
210 } 252 }
211 253
212 .nav-search-btn:hover { 254 .nav-search-btn:hover {
213 - background: rgba(255, 255, 255, 0.1); 255 + background: rgba(255, 255, 255, 0.15);
214 border-color: rgba(201, 167, 81, 0.4); 256 border-color: rgba(201, 167, 81, 0.4);
215 color: #F5F0E8; 257 color: #F5F0E8;
216 } 258 }
...@@ -252,28 +294,28 @@ ...@@ -252,28 +294,28 @@
252 align-items: center; 294 align-items: center;
253 justify-content: center; 295 justify-content: center;
254 padding: 8px; 296 padding: 8px;
255 - background: rgba(255, 255, 255, 0.05); 297 + background: rgba(255, 255, 255, 0.08);
256 - border: 1px solid rgba(255, 255, 255, 0.12); 298 + border: 1px solid rgba(255, 255, 255, 0.1);
257 border-radius: 10px; 299 border-radius: 10px;
258 - color: #9B9890; 300 + color: #B8B5AD;
259 cursor: pointer; 301 cursor: pointer;
260 transition: all 0.2s ease; 302 transition: all 0.2s ease;
261 } 303 }
262 304
263 .nav-btn-icon:hover { 305 .nav-btn-icon:hover {
264 - background: rgba(255, 255, 255, 0.1); 306 + background: rgba(255, 255, 255, 0.15);
265 border-color: rgba(201, 167, 81, 0.4); 307 border-color: rgba(201, 167, 81, 0.4);
266 color: #F5F0E8; 308 color: #F5F0E8;
267 } 309 }
268 310
269 :global(html:not(.dark)) .nav-btn-icon { 311 :global(html:not(.dark)) .nav-btn-icon {
270 - background: rgba(0, 0, 0, 0.04); 312 + background: rgba(0, 0, 0, 0.05);
271 - border-color: rgba(0, 0, 0, 0.12); 313 + border-color: rgba(0, 0, 0, 0.08);
272 - color: #666; 314 + color: #555;
273 } 315 }
274 316
275 :global(html:not(.dark)) .nav-btn-icon:hover { 317 :global(html:not(.dark)) .nav-btn-icon:hover {
276 - background: rgba(0, 0, 0, 0.08); 318 + background: rgba(0, 0, 0, 0.1);
277 border-color: rgba(201, 167, 81, 0.5); 319 border-color: rgba(201, 167, 81, 0.5);
278 color: #1A1A18; 320 color: #1A1A18;
279 } 321 }
...@@ -285,23 +327,66 @@ ...@@ -285,23 +327,66 @@
285 box-shadow: 0 1px 2px rgba(0,0,0,0.1), inset 0 1px 0 rgba(255,255,255,0.8); 327 box-shadow: 0 1px 2px rgba(0,0,0,0.1), inset 0 1px 0 rgba(255,255,255,0.8);
286 } 328 }
287 329
330 + /* Home link - hereda estilos de nav-btn-icon */
331 + .nav-home-link {
332 + display: flex;
333 + align-items: center;
334 + justify-content: center;
335 + padding: 8px;
336 + background: rgba(255, 255, 255, 0.08);
337 + border: 1px solid rgba(255, 255, 255, 0.1);
338 + border-radius: 10px;
339 + color: #B8B5AD;
340 + cursor: pointer;
341 + transition: all 0.2s ease;
342 + text-decoration: none;
343 + }
344 +
345 + .nav-home-link:hover {
346 + background: rgba(255, 255, 255, 0.15);
347 + border-color: rgba(201, 167, 81, 0.4);
348 + color: #F5F0E8;
349 + }
350 +
351 + :global(html:not(.dark)) .nav-home-link {
352 + background: rgba(0, 0, 0, 0.05);
353 + border-color: rgba(0, 0, 0, 0.08);
354 + color: #555;
355 + }
356 +
357 + :global(html:not(.dark)) .nav-home-link:hover {
358 + background: rgba(0, 0, 0, 0.1);
359 + border-color: rgba(201, 167, 81, 0.5);
360 + color: #1A1A18;
361 + }
362 +
288 /* Mobile icon buttons */ 363 /* Mobile icon buttons */
289 .nav-btn-icon-mobile { 364 .nav-btn-icon-mobile {
290 display: flex; 365 display: flex;
291 align-items: center; 366 align-items: center;
292 justify-content: center; 367 justify-content: center;
293 padding: 8px; 368 padding: 8px;
294 - background: rgba(255, 255, 255, 0.05); 369 + background: rgba(255, 255, 255, 0.1);
295 - border: 1px solid rgba(255, 255, 255, 0.12); 370 + border: 1px solid rgba(255, 255, 255, 0.08);
296 border-radius: 8px; 371 border-radius: 8px;
297 - color: #9B9890; 372 + color: #B8B5AD;
298 cursor: pointer; 373 cursor: pointer;
299 transition: all 0.2s ease; 374 transition: all 0.2s ease;
300 } 375 }
301 376
377 + .nav-btn-icon-mobile:hover {
378 + background: rgba(255, 255, 255, 0.18);
379 + color: #F5F0E8;
380 + }
381 +
302 :global(html:not(.dark)) .nav-btn-icon-mobile { 382 :global(html:not(.dark)) .nav-btn-icon-mobile {
303 - background: rgba(0, 0, 0, 0.04); 383 + background: rgba(0, 0, 0, 0.06);
304 - border-color: rgba(0, 0, 0, 0.12); 384 + border-color: rgba(0, 0, 0, 0.06);
305 - color: #666; 385 + color: #555;
386 + }
387 +
388 + :global(html:not(.dark)) .nav-btn-icon-mobile:hover {
389 + background: rgba(0, 0, 0, 0.12);
390 + color: #1A1A18;
306 } 391 }
307 </style> 392 </style>
......
This diff is collapsed. Click to expand it.
...@@ -61,6 +61,43 @@ export async function initSearchIndex() { ...@@ -61,6 +61,43 @@ export async function initSearchIndex() {
61 throw errorRubros; 61 throw errorRubros;
62 } 62 }
63 63
64 + // Cargar finalidad y función
65 + const { data: finfun, error: errorFinfun } = await supabase
66 + .schema('ppto')
67 + .from('clas_finfun')
68 + .select('finfun, desc_finfun, nivel');
69 +
70 + if (errorFinfun) {
71 + console.error('Error cargando finalidad/función:', errorFinfun);
72 + throw errorFinfun;
73 + }
74 +
75 + // Cargar organismos (no bloquea si falla)
76 + let organismos = [];
77 + const { data: organismosData, error: errorOrganismos } = await supabase
78 + .schema('ppto')
79 + .from('clas_organismos')
80 + .select('organismo, organismo_desc_organismo, organismo_grupo, organismo_sub_grupo');
81 +
82 + if (errorOrganismos) {
83 + console.warn('Tabla clas_organismos no disponible:', errorOrganismos.message);
84 + } else {
85 + organismos = organismosData || [];
86 + }
87 +
88 + // Cargar fuentes (no bloquea si falla)
89 + let fuentes = [];
90 + const { data: fuentesData, error: errorFuentes } = await supabase
91 + .schema('ppto')
92 + .from('clas_fuentes')
93 + .select('fuente, desc_fuente, grupo_fuente, desc_grupo_fuente');
94 +
95 + if (errorFuentes) {
96 + console.warn('Tabla clas_fuentes no disponible:', errorFuentes.message);
97 + } else {
98 + fuentes = fuentesData || [];
99 + }
100 +
64 // Transformar entidades para el índice 101 // Transformar entidades para el índice
65 const entidadesIndex = entidades.map(item => ({ 102 const entidadesIndex = entidades.map(item => ({
66 tipo: 'entidad', 103 tipo: 'entidad',
...@@ -102,8 +139,49 @@ export async function initSearchIndex() { ...@@ -102,8 +139,49 @@ export async function initSearchIndex() {
102 codigo_normalizado: item.rubro?.toString() || '' 139 codigo_normalizado: item.rubro?.toString() || ''
103 })); 140 }));
104 141
142 + // Transformar finalidad y función para el índice
143 + const nivelLabelsFinfun = { finalidad: 'Finalidad', grpfuncion: 'Grupo Función', funcion: 'Función' };
144 + const finfunIndex = finfun.map(item => ({
145 + tipo: 'finfun',
146 + codigo: item.finfun,
147 + nombre: item.desc_finfun,
148 + sigla: null,
149 + contexto: `Finalidad y Función · ${nivelLabelsFinfun[item.nivel] || item.nivel}`,
150 + nivel: item.nivel,
151 + nombre_normalizado: normalizeText(item.desc_finfun),
152 + sigla_normalizada: '',
153 + codigo_normalizado: item.finfun?.toString() || ''
154 + }));
155 +
156 + // Transformar organismos para el índice
157 + // Todos los registros en la tabla son organismos (nivel hoja)
158 + const organismosIndex = organismos.map(item => ({
159 + tipo: 'organismo',
160 + codigo: item.organismo,
161 + nombre: item.organismo_desc_organismo,
162 + sigla: null,
163 + contexto: `Organismo · Grupo ${item.organismo_grupo}`,
164 + nivel: 'organismo',
165 + nombre_normalizado: normalizeText(item.organismo_desc_organismo),
166 + sigla_normalizada: '',
167 + codigo_normalizado: item.organismo?.toString() || ''
168 + }));
169 +
170 + // Transformar fuentes para el índice
171 + const fuentesIndex = fuentes.map(item => ({
172 + tipo: 'fuente',
173 + codigo: item.fuente,
174 + nombre: item.desc_fuente,
175 + sigla: null,
176 + contexto: `Fuente · ${item.desc_grupo_fuente}`,
177 + nivel: 'fuente',
178 + nombre_normalizado: normalizeText(item.desc_fuente),
179 + sigla_normalizada: '',
180 + codigo_normalizado: item.fuente?.toString() || ''
181 + }));
182 +
105 // Combinar todos los conjuntos 183 // Combinar todos los conjuntos
106 - searchData = [...entidadesIndex, ...objetosIndex, ...rubrosIndex]; 184 + searchData = [...entidadesIndex, ...objetosIndex, ...rubrosIndex, ...finfunIndex, ...organismosIndex, ...fuentesIndex];
107 185
108 fuseInstance = new Fuse(searchData, fuseOptions); 186 fuseInstance = new Fuse(searchData, fuseOptions);
109 187
......
...@@ -8,7 +8,7 @@ export const isLoading = writable(false); ...@@ -8,7 +8,7 @@ export const isLoading = writable(false);
8 export const indexLoaded = writable(false); 8 export const indexLoaded = writable(false);
9 export const error = writable(null); 9 export const error = writable(null);
10 export const selectedIndex = writable(-1); 10 export const selectedIndex = writable(-1);
11 -export const filterType = writable('todos'); // todos, entidad, objeto_gasto, rubro 11 +export const filterType = writable('todos'); // todos, entidad, objeto_gasto, rubro, finfun
12 12
13 // Estado derivado: si está mostrando resultados 13 // Estado derivado: si está mostrando resultados
14 export const showResults = derived( 14 export const showResults = derived(
......
This diff is collapsed. Click to expand it.
1 -<script> 1 +me pare<script>
2 import { supabase } from '$lib/supabase'; 2 import { supabase } from '$lib/supabase';
3 import { onMount, untrack } from 'svelte'; 3 import { onMount, untrack } from 'svelte';
4 import { page } from '$app/stores'; 4 import { page } from '$app/stores';
...@@ -79,17 +79,24 @@ ...@@ -79,17 +79,24 @@
79 '10': 'Protección Social', 79 '10': 'Protección Social',
80 }; 80 };
81 81
82 - // Función para obtener color según la finalidad (primer dígito del código finfun) 82 + // Helper para obtener la finalidad de un código (maneja caso especial de "10")
83 + function getFinalidadKey(finfun) {
84 + const code = String(finfun);
85 + if (code.startsWith('10')) return '10';
86 + return code.charAt(0);
87 + }
88 +
89 + // Función para obtener color según la finalidad
83 function getNodeColor(finfun, opacity = 1) { 90 function getNodeColor(finfun, opacity = 1) {
84 - const finalidadDigit = String(finfun).charAt(0); 91 + const finalidadKey = getFinalidadKey(finfun);
85 - const baseColor = FINALIDAD_COLORS[finalidadDigit] || '#888888'; 92 + const baseColor = FINALIDAD_COLORS[finalidadKey] || '#888888';
86 if (opacity === 1) return baseColor; 93 if (opacity === 1) return baseColor;
87 return d3.color(baseColor).copy({opacity}).formatRgb(); 94 return d3.color(baseColor).copy({opacity}).formatRgb();
88 } 95 }
89 96
90 function getTextColor(finfun) { 97 function getTextColor(finfun) {
91 - const finalidadDigit = String(finfun).charAt(0); 98 + const finalidadKey = getFinalidadKey(finfun);
92 - const baseColor = FINALIDAD_COLORS[finalidadDigit] || '#888888'; 99 + const baseColor = FINALIDAD_COLORS[finalidadKey] || '#888888';
93 const color = d3.color(baseColor); 100 const color = d3.color(baseColor);
94 const r = color.r / 255; 101 const r = color.r / 255;
95 const g = color.g / 255; 102 const g = color.g / 255;
...@@ -99,8 +106,8 @@ ...@@ -99,8 +106,8 @@
99 } 106 }
100 107
101 function getTextColorSecondary(finfun) { 108 function getTextColorSecondary(finfun) {
102 - const finalidadDigit = String(finfun).charAt(0); 109 + const finalidadKey = getFinalidadKey(finfun);
103 - const baseColor = FINALIDAD_COLORS[finalidadDigit] || '#888888'; 110 + const baseColor = FINALIDAD_COLORS[finalidadKey] || '#888888';
104 const color = d3.color(baseColor); 111 const color = d3.color(baseColor);
105 const r = color.r / 255; 112 const r = color.r / 255;
106 const g = color.g / 255; 113 const g = color.g / 255;
...@@ -610,13 +617,26 @@ ...@@ -610,13 +617,26 @@
610 }); 617 });
611 618
612 // Helpers para derivar jerarquía desde código finfun 619 // Helpers para derivar jerarquía desde código finfun
613 - // Jerarquía: Finalidad (1 dígito) → Grupo Función (2 dígitos) → Función (3+ dígitos) 620 + // Jerarquía: Finalidad (1-9 o 10) → Grupo Función (2-3 dígitos) → Función (3+ dígitos)
621 + // Nota: Finalidad 10 es especial - tiene 2 dígitos, sus hijos empiezan con "10"
614 function getFinalidadFromFinfun(finfun) { 622 function getFinalidadFromFinfun(finfun) {
615 - return finfun.charAt(0); 623 + const code = String(finfun);
624 + // Si empieza con "10", la finalidad es "10"
625 + if (code.startsWith('10')) {
626 + return '10';
627 + }
628 + // Si no, la finalidad es el primer dígito
629 + return code.charAt(0);
616 } 630 }
617 631
618 function getGrpFuncionFromFinfun(finfun) { 632 function getGrpFuncionFromFinfun(finfun) {
619 - return finfun.substring(0, 2); 633 + const code = String(finfun);
634 + // Si empieza con "10", el grupo función son los primeros 3 dígitos (ej: "101", "102")
635 + if (code.startsWith('10')) {
636 + return code.substring(0, 3);
637 + }
638 + // Si no, son los primeros 2 dígitos (ej: "11", "21", "93")
639 + return code.substring(0, 2);
620 } 640 }
621 641
622 function getItemsForFinalidad(finalidadCode) { 642 function getItemsForFinalidad(finalidadCode) {
......
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
...@@ -48,16 +48,20 @@ ...@@ -48,16 +48,20 @@
48 }); 48 });
49 49
50 // Helpers para derivar jerarquía desde código rubro 50 // Helpers para derivar jerarquía desde código rubro
51 + // Jerarquía: Tipo (XX000) → Clase (XXX00) → Cuenta (XXXX0) → Subcuenta (XXXXX)
51 function getTipoFromRubro(rubro) { 52 function getTipoFromRubro(rubro) {
52 - return rubro.charAt(0); 53 + // Tipo = primeros 2 dígitos (XX de XX000)
54 + return rubro.substring(0, 2);
53 } 55 }
54 56
55 function getClaseFromRubro(rubro) { 57 function getClaseFromRubro(rubro) {
56 - return rubro.substring(0, 2); 58 + // Clase = primeros 3 dígitos (XXX de XXX00)
59 + return rubro.substring(0, 3);
57 } 60 }
58 61
59 function getCuentaFromRubro(rubro) { 62 function getCuentaFromRubro(rubro) {
60 - return rubro.substring(0, 3); 63 + // Cuenta = primeros 4 dígitos (XXXX de XXXX0)
64 + return rubro.substring(0, 4);
61 } 65 }
62 66
63 function getItemsForTipo(tipoCode) { 67 function getItemsForTipo(tipoCode) {
......
...@@ -2,18 +2,40 @@ import { supabase } from '$lib/supabase'; ...@@ -2,18 +2,40 @@ import { supabase } from '$lib/supabase';
2 import { error } from '@sveltejs/kit'; 2 import { error } from '@sveltejs/kit';
3 3
4 // Funciones para derivar jerarquía del código finfun 4 // Funciones para derivar jerarquía del código finfun
5 -// Jerarquía: Finalidad (1 dígito) → Grupo Función (2 dígitos) → Función (3+ dígitos) 5 +// Jerarquía: Finalidad (1-9 o 10) → Grupo Función (2-3 dígitos) → Función (3+ dígitos)
6 +// Nota: Finalidad 10 es especial - tiene 2 dígitos, sus hijos empiezan con "10"
7 +
6 function getFinalidadCode(finfun) { 8 function getFinalidadCode(finfun) {
7 - return finfun.charAt(0); 9 + const code = String(finfun);
10 + // Si empieza con "10", la finalidad es "10"
11 + if (code.startsWith('10')) {
12 + return '10';
13 + }
14 + // Si no, la finalidad es el primer dígito
15 + return code.charAt(0);
8 } 16 }
9 17
10 function getGrpFuncionCode(finfun) { 18 function getGrpFuncionCode(finfun) {
11 - return finfun.substring(0, 2); 19 + const code = String(finfun);
20 + // Si empieza con "10", el grupo función son los primeros 3 dígitos (ej: "101", "102")
21 + if (code.startsWith('10')) {
22 + return code.substring(0, 3);
23 + }
24 + // Si no, son los primeros 2 dígitos (ej: "11", "21", "93")
25 + return code.substring(0, 2);
12 } 26 }
13 27
14 function getNivel(finfun) { 28 function getNivel(finfun) {
15 - if (finfun.length === 1) return 'finalidad'; 29 + const code = String(finfun);
16 - if (finfun.length === 2) return 'grpfuncion'; 30 + // Finalidades 1-9 tienen longitud 1, finalidad 10 tiene longitud 2
31 + if (code.length === 1) return 'finalidad';
32 + if (code === '10') return 'finalidad';
33 + // GrpFuncion: 2 dígitos para 1-9, 3 dígitos para 10
34 + if (code.startsWith('10')) {
35 + if (code.length === 3) return 'grpfuncion';
36 + return 'funcion';
37 + }
38 + if (code.length === 2) return 'grpfuncion';
17 return 'funcion'; 39 return 'funcion';
18 } 40 }
19 41
...@@ -77,27 +99,40 @@ export async function load({ params }) { ...@@ -77,27 +99,40 @@ export async function load({ params }) {
77 99
78 // Buscar hijos según nivel 100 // Buscar hijos según nivel
79 if (nivel === 'finalidad') { 101 if (nivel === 'finalidad') {
80 - // Hijos: grupos de función que empiecen con el mismo dígito 102 + // Hijos: grupos de función de esta finalidad
81 const { data: hijosData } = await supabase 103 const { data: hijosData } = await supabase
82 .schema('ppto') 104 .schema('ppto')
83 .from('clas_finfun') 105 .from('clas_finfun')
84 .select('*') 106 .select('*')
85 .eq('nivel', 'grpfuncion') 107 .eq('nivel', 'grpfuncion')
86 - .like('finfun', `${codigo}%`)
87 .order('finfun'); 108 .order('finfun');
88 109
89 - hijos = hijosData || []; 110 + // Filtrar solo los hijos directos de esta finalidad
111 + // Para finalidad "1": hijos son "11", "12", ..., "19" (NO "10x")
112 + // Para finalidad "10": hijos son "101", "102", ..., "109"
113 + if (hijosData) {
114 + hijos = hijosData.filter(h => {
115 + const hijoPadre = getFinalidadCode(h.finfun);
116 + return hijoPadre === codigo;
117 + });
118 + }
90 } else if (nivel === 'grpfuncion') { 119 } else if (nivel === 'grpfuncion') {
91 - // Hijos: funciones que empiecen con los mismos 2 dígitos 120 + // Hijos: funciones de este grupo
121 + const grpCode = getGrpFuncionCode(codigo);
92 const { data: hijosData } = await supabase 122 const { data: hijosData } = await supabase
93 .schema('ppto') 123 .schema('ppto')
94 .from('clas_finfun') 124 .from('clas_finfun')
95 .select('*') 125 .select('*')
96 .eq('nivel', 'funcion') 126 .eq('nivel', 'funcion')
97 - .like('finfun', `${codigo}%`)
98 .order('finfun'); 127 .order('finfun');
99 128
100 - hijos = hijosData || []; 129 + // Filtrar solo los hijos directos de este grupo
130 + if (hijosData) {
131 + hijos = hijosData.filter(h => {
132 + const hijoGrp = getGrpFuncionCode(h.finfun);
133 + return hijoGrp === grpCode;
134 + });
135 + }
101 } 136 }
102 // funcion no tiene hijos 137 // funcion no tiene hijos
103 138
......
...@@ -1029,9 +1029,9 @@ ...@@ -1029,9 +1029,9 @@
1029 <p class="context-hint">Funciones relacionadas. <span class="desktop-hint">Haz click</span><span class="mobile-hint">Toca</span> para explorar.</p> 1029 <p class="context-hint">Funciones relacionadas. <span class="desktop-hint">Haz click</span><span class="mobile-hint">Toca</span> para explorar.</p>
1030 <div class="tree"> 1030 <div class="tree">
1031 {#each padres as padre, i} 1031 {#each padres as padre, i}
1032 - <a href="/finfun/{padre.objeto}" class="tree-node has-tooltip" style="--indent: {i}" data-tooltip="{padre.desc_finfun}"> 1032 + <a href="/finfun/{padre.finfun}" class="tree-node has-tooltip" style="--indent: {i}" data-tooltip="{padre.desc_finfun}">
1033 <span class="tree-dot"></span> 1033 <span class="tree-dot"></span>
1034 - <span class="tree-code">{padre.objeto}</span> 1034 + <span class="tree-code">{padre.finfun}</span>
1035 <span class="tree-name">{padre.desc_finfun}</span> 1035 <span class="tree-name">{padre.desc_finfun}</span>
1036 </a> 1036 </a>
1037 {/each} 1037 {/each}
...@@ -1045,9 +1045,9 @@ ...@@ -1045,9 +1045,9 @@
1045 {#if hijos.length > 0} 1045 {#if hijos.length > 0}
1046 <div class="tree-children" style="--indent: {padres.length + 1}"> 1046 <div class="tree-children" style="--indent: {padres.length + 1}">
1047 {#each hijos as hijo} 1047 {#each hijos as hijo}
1048 - <a href="/finfun/{hijo.objeto}" class="tree-node tree-node-child has-tooltip" data-tooltip="{hijo.desc_finfun}"> 1048 + <a href="/finfun/{hijo.finfun}" class="tree-node tree-node-child has-tooltip" data-tooltip="{hijo.desc_finfun}">
1049 <span class="tree-dot"></span> 1049 <span class="tree-dot"></span>
1050 - <span class="tree-code">{hijo.objeto}</span> 1050 + <span class="tree-code">{hijo.finfun}</span>
1051 <span class="tree-name">{hijo.desc_finfun}</span> 1051 <span class="tree-name">{hijo.desc_finfun}</span>
1052 </a> 1052 </a>
1053 {/each} 1053 {/each}
......
1 +import { supabase } from '$lib/supabase';
2 +import { error } from '@sveltejs/kit';
3 +
4 +export async function load({ params }) {
5 + console.time('[SERVER] Total load fuente');
6 + const { codigo } = params;
7 +
8 + console.time('[SERVER] Query clas_fuentes');
9 + const { data, error: dbError } = await supabase
10 + .schema('ppto')
11 + .from('clas_fuentes')
12 + .select('*')
13 + .eq('fuente', codigo);
14 + console.timeEnd('[SERVER] Query clas_fuentes');
15 +
16 + if (dbError || !data || data.length === 0) {
17 + throw error(404, 'Fuente no encontrada');
18 + }
19 +
20 + const fuente = data[0];
21 +
22 + // Obtener hermanos (otras fuentes del mismo grupo)
23 + let hermanos = [];
24 +
25 + console.time('[SERVER] Query hermanos');
26 + const { data: hermanosData } = await supabase
27 + .schema('ppto')
28 + .from('clas_fuentes')
29 + .select('*')
30 + .eq('grupo_fuente', fuente.grupo_fuente)
31 + .neq('fuente', codigo)
32 + .order('fuente');
33 +
34 + if (hermanosData) {
35 + hermanos = hermanosData;
36 + }
37 + console.timeEnd('[SERVER] Query hermanos');
38 +
39 + console.timeEnd('[SERVER] Total load fuente');
40 + return {
41 + fuente,
42 + hermanos
43 + };
44 +}
This diff is collapsed. Click to expand it.
1 +import { supabase } from '$lib/supabase';
2 +import { error } from '@sveltejs/kit';
3 +
4 +export async function load({ params }) {
5 + console.time('[SERVER] Total load organismo');
6 + const { codigo } = params;
7 +
8 + console.time('[SERVER] Query clas_organismos');
9 + const { data, error: dbError } = await supabase
10 + .schema('ppto')
11 + .from('clas_organismos')
12 + .select('*')
13 + .eq('organismo', codigo);
14 + console.timeEnd('[SERVER] Query clas_organismos');
15 +
16 + if (dbError || !data || data.length === 0) {
17 + throw error(404, 'Organismo no encontrado');
18 + }
19 +
20 + const organismo = data[0];
21 +
22 + // Obtener hermanos (otros organismos del mismo subgrupo)
23 + let hermanos = [];
24 +
25 + console.time('[SERVER] Query hermanos');
26 + const { data: hermanosData } = await supabase
27 + .schema('ppto')
28 + .from('clas_organismos')
29 + .select('*')
30 + .eq('organismo_grupo', organismo.organismo_grupo)
31 + .eq('organismo_sub_grupo', organismo.organismo_sub_grupo)
32 + .neq('organismo', codigo)
33 + .order('organismo');
34 +
35 + if (hermanosData) {
36 + hermanos = hermanosData;
37 + }
38 + console.timeEnd('[SERVER] Query hermanos');
39 +
40 + console.timeEnd('[SERVER] Total load organismo');
41 + return {
42 + organismo,
43 + hermanos
44 + };
45 +}
This diff is collapsed. Click to expand it.
1 import { supabase } from '$lib/supabase'; 1 import { supabase } from '$lib/supabase';
2 import { error } from '@sveltejs/kit'; 2 import { error } from '@sveltejs/kit';
3 3
4 +// Funciones para derivar jerarquía del código rubro
5 +// Jerarquía: Tipo (XX000) → Clase (XXX00) → Cuenta (XXXX0) → Subcuenta (XXXXX)
6 +
7 +function getTipoCode(rubro) {
8 + // Primeros 2 dígitos + 000 → ej: 11000, 12000
9 + return rubro.substring(0, 2) + '000';
10 +}
11 +
12 +function getClaseCode(rubro) {
13 + // Primeros 3 dígitos + 00 → ej: 11100, 11200
14 + return rubro.substring(0, 3) + '00';
15 +}
16 +
17 +function getCuentaCode(rubro) {
18 + // Primeros 4 dígitos + 0 → ej: 11110, 11120
19 + return rubro.substring(0, 4) + '0';
20 +}
21 +
22 +function getNivelFromCode(rubro) {
23 + // Determinar nivel basado en el patrón de ceros
24 + if (rubro.endsWith('000')) return 'tipo'; // XX000
25 + if (rubro.endsWith('00')) return 'clase'; // XXX00
26 + if (rubro.endsWith('0')) return 'cuenta'; // XXXX0
27 + return 'sub_cuenta'; // XXXXX
28 +}
29 +
30 +function getNextNivel(nivel) {
31 + const niveles = { tipo: 'clase', clase: 'cuenta', cuenta: 'sub_cuenta' };
32 + return niveles[nivel] || null;
33 +}
34 +
4 export async function load({ params }) { 35 export async function load({ params }) {
5 const { codigo } = params; 36 const { codigo } = params;
6 37
...@@ -15,88 +46,81 @@ export async function load({ params }) { ...@@ -15,88 +46,81 @@ export async function load({ params }) {
15 } 46 }
16 47
17 const rubro = data[0]; 48 const rubro = data[0];
49 + const nivel = rubro.nivel || getNivelFromCode(codigo);
18 50
19 // Obtener jerarquía (padres e hijos) 51 // Obtener jerarquía (padres e hijos)
20 let padres = []; 52 let padres = [];
21 let hijos = []; 53 let hijos = [];
22 54
23 - // Buscar padre según nivel 55 + // Buscar padres según nivel
24 - if (rubro.nivel === 'sub_cuenta') { 56 + if (nivel === 'sub_cuenta') {
25 - // Padre es cuenta 57 + // Padres: tipo, clase, cuenta
26 - const { data: padreData } = await supabase 58 + const tipoCode = getTipoCode(codigo);
59 + const claseCode = getClaseCode(codigo);
60 + const cuentaCode = getCuentaCode(codigo);
61 +
62 + const { data: padresData } = await supabase
27 .schema('ppto') 63 .schema('ppto')
28 .from('clas_rubros') 64 .from('clas_rubros')
29 .select('*') 65 .select('*')
30 - .eq('nivel', 'cuenta') 66 + .in('rubro', [tipoCode, claseCode, cuentaCode])
31 - .eq('tipo', rubro.tipo) 67 + .order('rubro');
32 - .eq('clase', rubro.clase)
33 - .eq('cuenta', rubro.cuenta);
34 - if (padreData?.length) padres.push(padreData[0]);
35 - }
36 68
37 - if (rubro.nivel === 'cuenta' || rubro.nivel === 'sub_cuenta') { 69 + if (padresData) padres = padresData;
38 - // Padre es clase 70 + } else if (nivel === 'cuenta') {
39 - const { data: padreData } = await supabase 71 + // Padres: tipo, clase
72 + const tipoCode = getTipoCode(codigo);
73 + const claseCode = getClaseCode(codigo);
74 +
75 + const { data: padresData } = await supabase
40 .schema('ppto') 76 .schema('ppto')
41 .from('clas_rubros') 77 .from('clas_rubros')
42 .select('*') 78 .select('*')
43 - .eq('nivel', 'clase') 79 + .in('rubro', [tipoCode, claseCode])
44 - .eq('tipo', rubro.tipo) 80 + .order('rubro');
45 - .eq('clase', rubro.clase); 81 +
46 - if (padreData?.length) padres.unshift(padreData[0]); 82 + if (padresData) padres = padresData;
47 - } 83 + } else if (nivel === 'clase') {
84 + // Padre: tipo
85 + const tipoCode = getTipoCode(codigo);
48 86
49 - if (rubro.nivel !== 'tipo') { 87 + const { data: padresData } = await supabase
50 - // Padre es tipo
51 - const { data: padreData } = await supabase
52 .schema('ppto') 88 .schema('ppto')
53 .from('clas_rubros') 89 .from('clas_rubros')
54 .select('*') 90 .select('*')
55 - .eq('nivel', 'tipo') 91 + .eq('rubro', tipoCode);
56 - .eq('tipo', rubro.tipo); 92 +
57 - if (padreData?.length) padres.unshift(padreData[0]); 93 + if (padresData) padres = padresData;
58 } 94 }
95 + // tipo no tiene padres
59 96
60 // Buscar hijos según nivel 97 // Buscar hijos según nivel
61 - if (rubro.nivel === 'tipo') { 98 + const nextNivel = getNextNivel(nivel);
62 - const { data: hijosData } = await supabase 99 + if (nextNivel) {
63 - .schema('ppto')
64 - .from('clas_rubros')
65 - .select('*')
66 - .eq('nivel', 'clase')
67 - .eq('tipo', rubro.tipo)
68 - .order('rubro');
69 - hijos = hijosData?.reduce((acc, item) => {
70 - if (!acc.find(h => h.rubro === item.rubro)) acc.push(item);
71 - return acc;
72 - }, []) || [];
73 - } else if (rubro.nivel === 'clase') {
74 const { data: hijosData } = await supabase 100 const { data: hijosData } = await supabase
75 .schema('ppto') 101 .schema('ppto')
76 .from('clas_rubros') 102 .from('clas_rubros')
77 .select('*') 103 .select('*')
78 - .eq('nivel', 'cuenta') 104 + .eq('nivel', nextNivel)
79 - .eq('tipo', rubro.tipo) 105 + .like('rubro', `${codigo.replace(/0+$/, '')}%`)
80 - .eq('clase', rubro.clase)
81 .order('rubro'); 106 .order('rubro');
82 - hijos = hijosData?.reduce((acc, item) => { 107 +
83 - if (!acc.find(h => h.rubro === item.rubro)) acc.push(item); 108 + if (hijosData) {
84 - return acc; 109 + // Filtrar solo hijos directos (siguiente nivel)
85 - }, []) || []; 110 + hijos = hijosData.filter(h => {
86 - } else if (rubro.nivel === 'cuenta') { 111 + if (nivel === 'tipo') {
87 - const { data: hijosData } = await supabase 112 + // Hijos de tipo son clase: XXX00 donde XX = tipo
88 - .schema('ppto') 113 + return h.rubro.startsWith(codigo.substring(0, 2)) && h.nivel === 'clase';
89 - .from('clas_rubros') 114 + } else if (nivel === 'clase') {
90 - .select('*') 115 + // Hijos de clase son cuenta: XXXX0 donde XXX = clase
91 - .eq('nivel', 'sub_cuenta') 116 + return h.rubro.startsWith(codigo.substring(0, 3)) && h.nivel === 'cuenta';
92 - .eq('tipo', rubro.tipo) 117 + } else if (nivel === 'cuenta') {
93 - .eq('clase', rubro.clase) 118 + // Hijos de cuenta son sub_cuenta: XXXXX donde XXXX = cuenta
94 - .eq('cuenta', rubro.cuenta) 119 + return h.rubro.startsWith(codigo.substring(0, 4)) && h.nivel === 'sub_cuenta';
95 - .order('rubro'); 120 + }
96 - hijos = hijosData?.reduce((acc, item) => { 121 + return false;
97 - if (!acc.find(h => h.rubro === item.rubro)) acc.push(item); 122 + });
98 - return acc; 123 + }
99 - }, []) || [];
100 } 124 }
101 125
102 return { 126 return {
......
1 +// Desactivar SSR para esta página porque hyparquet y @observablehq/plot
2 +// requieren APIs del navegador (fetch con ArrayBuffer, DOM)
3 +export const ssr = false;
This diff is collapsed. Click to expand it.
No preview for this file type
No preview for this file type
No preview for this file type
No preview for this file type