Rafael Lopez

general

...@@ -9,7 +9,6 @@ ...@@ -9,7 +9,6 @@
9 "version": "0.0.1", 9 "version": "0.0.1",
10 "dependencies": { 10 "dependencies": {
11 "@observablehq/plot": "^0.6.17", 11 "@observablehq/plot": "^0.6.17",
12 - "@supabase/supabase-js": "^2.98.0",
13 "@sveltejs/adapter-node": "^5.5.4", 12 "@sveltejs/adapter-node": "^5.5.4",
14 "d3": "^7.9.0", 13 "d3": "^7.9.0",
15 "fuse.js": "^7.1.0", 14 "fuse.js": "^7.1.0",
...@@ -939,86 +938,6 @@ ...@@ -939,86 +938,6 @@
939 "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", 938 "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
940 "license": "MIT" 939 "license": "MIT"
941 }, 940 },
942 - "node_modules/@supabase/auth-js": {
943 - "version": "2.98.0",
944 - "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.98.0.tgz",
945 - "integrity": "sha512-GBH361T0peHU91AQNzOlIrjUZw9TZbB9YDRiyFgk/3Kvr3/Z1NWUZ2athWTfHhwNNi8IrW00foyFxQD9IO/Trg==",
946 - "license": "MIT",
947 - "dependencies": {
948 - "tslib": "2.8.1"
949 - },
950 - "engines": {
951 - "node": ">=20.0.0"
952 - }
953 - },
954 - "node_modules/@supabase/functions-js": {
955 - "version": "2.98.0",
956 - "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.98.0.tgz",
957 - "integrity": "sha512-N/xEyiNU5Org+d+PNCpv+TWniAXRzxIURxDYsS/m2I/sfAB/HcM9aM2Dmf5edj5oWb9GxID1OBaZ8NMmPXL+Lg==",
958 - "license": "MIT",
959 - "dependencies": {
960 - "tslib": "2.8.1"
961 - },
962 - "engines": {
963 - "node": ">=20.0.0"
964 - }
965 - },
966 - "node_modules/@supabase/postgrest-js": {
967 - "version": "2.98.0",
968 - "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.98.0.tgz",
969 - "integrity": "sha512-v6e9WeZuJijzUut8HyXu6gMqWFepIbaeaMIm1uKzei4yLg9bC9OtEW9O14LE/9ezqNbSAnSLO5GtOLFdm7Bpkg==",
970 - "license": "MIT",
971 - "dependencies": {
972 - "tslib": "2.8.1"
973 - },
974 - "engines": {
975 - "node": ">=20.0.0"
976 - }
977 - },
978 - "node_modules/@supabase/realtime-js": {
979 - "version": "2.98.0",
980 - "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.98.0.tgz",
981 - "integrity": "sha512-rOWt28uGyFipWOSd+n0WVMr9kUXiWaa7J4hvyLCIHjRFqWm1z9CaaKAoYyfYMC1Exn3WT8WePCgiVhlAtWC2yw==",
982 - "license": "MIT",
983 - "dependencies": {
984 - "@types/phoenix": "^1.6.6",
985 - "@types/ws": "^8.18.1",
986 - "tslib": "2.8.1",
987 - "ws": "^8.18.2"
988 - },
989 - "engines": {
990 - "node": ">=20.0.0"
991 - }
992 - },
993 - "node_modules/@supabase/storage-js": {
994 - "version": "2.98.0",
995 - "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.98.0.tgz",
996 - "integrity": "sha512-tzr2mG+v7ILSAZSfZMSL9OPyIH4z1ikgQ8EcQTKfMRz4EwmlFt3UnJaGzSOxyvF5b+fc9So7qdSUWTqGgeLokQ==",
997 - "license": "MIT",
998 - "dependencies": {
999 - "iceberg-js": "^0.8.1",
1000 - "tslib": "2.8.1"
1001 - },
1002 - "engines": {
1003 - "node": ">=20.0.0"
1004 - }
1005 - },
1006 - "node_modules/@supabase/supabase-js": {
1007 - "version": "2.98.0",
1008 - "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.98.0.tgz",
1009 - "integrity": "sha512-Ohc97CtInLwZyiSASz7tT9/Abm/vqnIbO9REp+PivVUII8UZsuI3bngRQnYgJdFoOIwvaEII1fX1qy8x0CyNiw==",
1010 - "license": "MIT",
1011 - "dependencies": {
1012 - "@supabase/auth-js": "2.98.0",
1013 - "@supabase/functions-js": "2.98.0",
1014 - "@supabase/postgrest-js": "2.98.0",
1015 - "@supabase/realtime-js": "2.98.0",
1016 - "@supabase/storage-js": "2.98.0"
1017 - },
1018 - "engines": {
1019 - "node": ">=20.0.0"
1020 - }
1021 - },
1022 "node_modules/@sveltejs/acorn-typescript": { 941 "node_modules/@sveltejs/acorn-typescript": {
1023 "version": "1.0.9", 942 "version": "1.0.9",
1024 "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.9.tgz", 943 "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.9.tgz",
...@@ -1415,21 +1334,6 @@ ...@@ -1415,21 +1334,6 @@
1415 "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", 1334 "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
1416 "license": "MIT" 1335 "license": "MIT"
1417 }, 1336 },
1418 - "node_modules/@types/node": {
1419 - "version": "25.3.2",
1420 - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.2.tgz",
1421 - "integrity": "sha512-RpV6r/ij22zRRdyBPcxDeKAzH43phWVKEjL2iksqo1Vz3CuBUrgmPpPhALKiRfU7OMCmeeO9vECBMsV0hMTG8Q==",
1422 - "license": "MIT",
1423 - "dependencies": {
1424 - "undici-types": "~7.18.0"
1425 - }
1426 - },
1427 - "node_modules/@types/phoenix": {
1428 - "version": "1.6.7",
1429 - "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.7.tgz",
1430 - "integrity": "sha512-oN9ive//QSBkf19rfDv45M7eZPi0eEXylht2OLEXicu5b4KoQ1OzXIw+xDSGWxSxe1JmepRR/ZH283vsu518/Q==",
1431 - "license": "MIT"
1432 - },
1433 "node_modules/@types/resolve": { 1337 "node_modules/@types/resolve": {
1434 "version": "1.20.2", 1338 "version": "1.20.2",
1435 "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", 1339 "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
...@@ -1442,15 +1346,6 @@ ...@@ -1442,15 +1346,6 @@
1442 "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", 1346 "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
1443 "license": "MIT" 1347 "license": "MIT"
1444 }, 1348 },
1445 - "node_modules/@types/ws": {
1446 - "version": "8.18.1",
1447 - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
1448 - "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
1449 - "license": "MIT",
1450 - "dependencies": {
1451 - "@types/node": "*"
1452 - }
1453 - },
1454 "node_modules/acorn": { 1349 "node_modules/acorn": {
1455 "version": "8.16.0", 1350 "version": "8.16.0",
1456 "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", 1351 "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
...@@ -2114,15 +2009,6 @@ ...@@ -2114,15 +2009,6 @@
2114 "integrity": "sha512-CXcN/u6RdQqsK8IphUptpAEqY8IzgwzHY+MuXX+2wpoWTumfxPVr6JYbbywsNsiAl9aEbM5sRtxkwRBa22b49w==", 2009 "integrity": "sha512-CXcN/u6RdQqsK8IphUptpAEqY8IzgwzHY+MuXX+2wpoWTumfxPVr6JYbbywsNsiAl9aEbM5sRtxkwRBa22b49w==",
2115 "license": "MIT" 2010 "license": "MIT"
2116 }, 2011 },
2117 - "node_modules/iceberg-js": {
2118 - "version": "0.8.1",
2119 - "resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz",
2120 - "integrity": "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==",
2121 - "license": "MIT",
2122 - "engines": {
2123 - "node": ">=20.0.0"
2124 - }
2125 - },
2126 "node_modules/iconv-lite": { 2012 "node_modules/iconv-lite": {
2127 "version": "0.6.3", 2013 "version": "0.6.3",
2128 "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", 2014 "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
...@@ -2775,13 +2661,9 @@ ...@@ -2775,13 +2661,9 @@
2775 "version": "2.8.1", 2661 "version": "2.8.1",
2776 "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", 2662 "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
2777 "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", 2663 "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
2778 - "license": "0BSD" 2664 + "dev": true,
2779 - }, 2665 + "license": "0BSD",
2780 - "node_modules/undici-types": { 2666 + "optional": true
2781 - "version": "7.18.2",
2782 - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
2783 - "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
2784 - "license": "MIT"
2785 }, 2667 },
2786 "node_modules/vite": { 2668 "node_modules/vite": {
2787 "version": "7.3.1", 2669 "version": "7.3.1",
...@@ -2876,27 +2758,6 @@ ...@@ -2876,27 +2758,6 @@
2876 } 2758 }
2877 } 2759 }
2878 }, 2760 },
2879 - "node_modules/ws": {
2880 - "version": "8.19.0",
2881 - "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
2882 - "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
2883 - "license": "MIT",
2884 - "engines": {
2885 - "node": ">=10.0.0"
2886 - },
2887 - "peerDependencies": {
2888 - "bufferutil": "^4.0.1",
2889 - "utf-8-validate": ">=5.0.2"
2890 - },
2891 - "peerDependenciesMeta": {
2892 - "bufferutil": {
2893 - "optional": true
2894 - },
2895 - "utf-8-validate": {
2896 - "optional": true
2897 - }
2898 - }
2899 - },
2900 "node_modules/zimmerframe": { 2761 "node_modules/zimmerframe": {
2901 "version": "1.1.4", 2762 "version": "1.1.4",
2902 "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", 2763 "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz",
......
...@@ -20,7 +20,6 @@ ...@@ -20,7 +20,6 @@
20 }, 20 },
21 "dependencies": { 21 "dependencies": {
22 "@observablehq/plot": "^0.6.17", 22 "@observablehq/plot": "^0.6.17",
23 - "@supabase/supabase-js": "^2.98.0",
24 "@sveltejs/adapter-node": "^5.5.4", 23 "@sveltejs/adapter-node": "^5.5.4",
25 "d3": "^7.9.0", 24 "d3": "^7.9.0",
26 "fuse.js": "^7.1.0", 25 "fuse.js": "^7.1.0",
......
1 -import Fuse from 'fuse.js';
2 -import { supabase } from '$lib/supabase';
3 -import { normalizeText } from '$lib/utils/normalize';
4 -
5 -let fuseInstance = null;
6 -let searchData = [];
7 -
8 -/**
9 - * Configuración de Fuse.js
10 - */
11 -const fuseOptions = {
12 - keys: [
13 - { name: 'nombre_normalizado', weight: 0.6 },
14 - { name: 'sigla_normalizada', weight: 0.2 },
15 - { name: 'codigo_normalizado', weight: 0.2 }
16 - ],
17 - threshold: 0.4, // 0 = exacto, 1 = muy fuzzy
18 - distance: 100,
19 - includeScore: true,
20 - includeMatches: true,
21 - minMatchCharLength: 2,
22 - ignoreLocation: true
23 -};
24 -
25 -/**
26 - * Carga las entidades y objetos de gasto desde Supabase y construye el índice
27 - */
28 -export async function initSearchIndex() {
29 - if (fuseInstance) return fuseInstance;
30 -
31 - // Cargar entidades
32 - const { data: entidades, error: errorEntidades } = await supabase
33 - .schema('ppto')
34 - .from('clas_institucional')
35 - .select('entidad, desc_entidad, sigla_entidad, desc_area, n_gestiones');
36 -
37 - if (errorEntidades) {
38 - console.error('Error cargando entidades:', errorEntidades);
39 - throw errorEntidades;
40 - }
41 -
42 - // Cargar objetos de gasto
43 - const { data: objetos, error: errorObjetos } = await supabase
44 - .schema('ppto')
45 - .from('clas_objetos')
46 - .select('objeto, desc_objeto, nivel');
47 -
48 - if (errorObjetos) {
49 - console.error('Error cargando objetos:', errorObjetos);
50 - throw errorObjetos;
51 - }
52 -
53 - // Cargar rubros
54 - const { data: rubros, error: errorRubros } = await supabase
55 - .schema('ppto')
56 - .from('clas_rubros')
57 - .select('rubro, desc_rubro, nivel');
58 -
59 - if (errorRubros) {
60 - console.error('Error cargando rubros:', errorRubros);
61 - throw errorRubros;
62 - }
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 -
101 - // Transformar entidades para el índice
102 - const entidadesIndex = entidades.map(item => ({
103 - tipo: 'entidad',
104 - codigo: item.entidad,
105 - nombre: item.desc_entidad,
106 - sigla: item.sigla_entidad,
107 - contexto: item.desc_area,
108 - años: item.n_gestiones,
109 - nombre_normalizado: normalizeText(item.desc_entidad),
110 - sigla_normalizada: normalizeText(item.sigla_entidad),
111 - codigo_normalizado: item.entidad?.toString() || ''
112 - }));
113 -
114 - // Transformar objetos de gasto para el índice
115 - const nivelLabelsObjetos = { grupo: 'Grupo', subgrupo: 'Subgrupo', partida: 'Partida', subpartida: 'Subpartida' };
116 - const objetosIndex = objetos.map(item => ({
117 - tipo: 'objeto_gasto',
118 - codigo: item.objeto,
119 - nombre: item.desc_objeto,
120 - sigla: null,
121 - contexto: `Objeto del Gasto · ${nivelLabelsObjetos[item.nivel] || item.nivel}`,
122 - nivel: item.nivel,
123 - nombre_normalizado: normalizeText(item.desc_objeto),
124 - sigla_normalizada: '',
125 - codigo_normalizado: item.objeto || ''
126 - }));
127 -
128 - // Transformar rubros para el índice
129 - const nivelLabelsRubros = { tipo: 'Tipo', clase: 'Clase', cuenta: 'Cuenta', sub_cuenta: 'Subcuenta' };
130 - const rubrosIndex = rubros.map(item => ({
131 - tipo: 'rubro',
132 - codigo: item.rubro,
133 - nombre: item.desc_rubro,
134 - sigla: null,
135 - contexto: `Rubro · ${nivelLabelsRubros[item.nivel] || item.nivel}`,
136 - nivel: item.nivel,
137 - nombre_normalizado: normalizeText(item.desc_rubro),
138 - sigla_normalizada: '',
139 - codigo_normalizado: item.rubro?.toString() || ''
140 - }));
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 -
183 - // Combinar todos los conjuntos
184 - searchData = [...entidadesIndex, ...objetosIndex, ...rubrosIndex, ...finfunIndex, ...organismosIndex, ...fuentesIndex];
185 -
186 - fuseInstance = new Fuse(searchData, fuseOptions);
187 -
188 - return fuseInstance;
189 -}
190 -
191 -/**
192 - * Busca en el índice
193 - * @param {string} query - Texto de búsqueda
194 - * @param {number} limit - Máximo de resultados (default 30)
195 - * @returns {Array} Resultados de búsqueda
196 - */
197 -export function search(query, limit = 30) {
198 - if (!fuseInstance) {
199 - console.warn('Índice no inicializado. Llama initSearchIndex() primero.');
200 - return [];
201 - }
202 -
203 - if (!query || query.length < 2) {
204 - return [];
205 - }
206 -
207 - const normalizedQuery = normalizeText(query);
208 - const results = fuseInstance.search(normalizedQuery, { limit });
209 -
210 - return results.map(result => ({
211 - ...result.item,
212 - score: result.score,
213 - matches: result.matches
214 - }));
215 -}
216 -
217 -/**
218 - * Obtiene todas las entidades (sin búsqueda)
219 - */
220 -export function getAllEntidades() {
221 - return searchData.filter(item => item.tipo === 'entidad');
222 -}
223 -
224 -/**
225 - * Obtiene todos los objetos de gasto (sin búsqueda)
226 - */
227 -export function getAllObjetos() {
228 - return searchData.filter(item => item.tipo === 'objeto_gasto');
229 -}
230 -
231 -/**
232 - * Verifica si el índice está cargado
233 - */
234 -export function isIndexReady() {
235 - return fuseInstance !== null;
236 -}
1 -import { writable, derived, get } from 'svelte/store'; 1 +import { writable, derived } from 'svelte/store';
2 -import { initSearchIndex, search, isIndexReady } from '$lib/services/search';
3 2
4 -// Estado del buscador
5 export const query = writable(''); 3 export const query = writable('');
6 export const results = writable([]); 4 export const results = writable([]);
7 export const isLoading = writable(false); 5 export const isLoading = writable(false);
8 -export const indexLoaded = writable(false); 6 +export const indexLoaded = writable(true);
9 export const error = writable(null); 7 export const error = writable(null);
10 export const selectedIndex = writable(-1); 8 export const selectedIndex = writable(-1);
11 -export const filterType = writable('todos'); // todos, entidad, objeto_gasto, rubro, finfun 9 +export const filterType = writable('todos');
12 10
13 -// Estado derivado: si está mostrando resultados
14 export const showResults = derived( 11 export const showResults = derived(
15 [query, results, indexLoaded], 12 [query, results, indexLoaded],
16 ([$query, $results, $indexLoaded]) => $indexLoaded && $query.length >= 2 13 ([$query, $results, $indexLoaded]) => $indexLoaded && $query.length >= 2
17 ); 14 );
18 15
19 -/**
20 - * Inicializa el índice de búsqueda
21 - */
22 export async function initIndex() { 16 export async function initIndex() {
23 - if (isIndexReady()) { 17 + indexLoaded.set(true);
24 - indexLoaded.set(true);
25 - return;
26 - }
27 -
28 - isLoading.set(true);
29 - error.set(null);
30 -
31 - try {
32 - await initSearchIndex();
33 - indexLoaded.set(true);
34 - } catch (e) {
35 - error.set('Error al cargar el índice de búsqueda');
36 - console.error(e);
37 - } finally {
38 - isLoading.set(false);
39 - }
40 } 18 }
41 19
42 -/** 20 +export function performSearch() {}
43 - * Realiza la búsqueda
44 - */
45 -export function performSearch(searchQuery, filter = null) {
46 - query.set(searchQuery);
47 - selectedIndex.set(-1);
48 -
49 - if (searchQuery.length < 2) {
50 - results.set([]);
51 - return;
52 - }
53 -
54 - let searchResults = search(searchQuery, 30);
55 -
56 - // Filtrar por tipo si no es "todos"
57 - const currentFilter = filter !== null ? filter : get(filterType);
58 - if (currentFilter !== 'todos') {
59 - searchResults = searchResults.filter(r => r.tipo === currentFilter);
60 - }
61 -
62 - // Limitar resultados
63 - results.set(searchResults.slice(0, 15));
64 -}
65 -
66 -/**
67 - * Limpia la búsqueda
68 - */
69 export function clearSearch() { 21 export function clearSearch() {
70 query.set(''); 22 query.set('');
71 results.set([]); 23 results.set([]);
72 selectedIndex.set(-1); 24 selectedIndex.set(-1);
73 } 25 }
74 - 26 +export function setFilter() {}
75 -/** 27 +export function navigateResults() {}
76 - * Cambia el filtro de tipo
77 - */
78 -export function setFilter(type) {
79 - filterType.set(type);
80 - // Re-ejecutar búsqueda con el nuevo filtro
81 - const currentQuery = get(query);
82 - if (currentQuery.length >= 2) {
83 - performSearch(currentQuery, type);
84 - }
85 -}
86 -
87 -/**
88 - * Navegación por teclado
89 - */
90 -export function navigateResults(direction, totalResults) {
91 - selectedIndex.update(current => {
92 - if (direction === 'down') {
93 - return current < totalResults - 1 ? current + 1 : 0;
94 - } else if (direction === 'up') {
95 - return current > 0 ? current - 1 : totalResults - 1;
96 - }
97 - return current;
98 - });
99 -}
......
1 -import { createClient } from '@supabase/supabase-js';
2 -import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public';
3 -
4 -export const supabase = createClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY);
1 -import { supabase } from '$lib/supabase';
2 -import { json } from '@sveltejs/kit';
3 -
4 -export async function GET() {
5 - const { data, error } = await supabase
6 - .schema('ppto')
7 - .from('clas_institucional')
8 - .select('*')
9 - .limit(5);
10 -
11 - if (error) {
12 - return json({ error: error.message }, { status: 500 });
13 - }
14 -
15 - return json({
16 - success: true,
17 - count: data.length,
18 - columns: data.length > 0 ? Object.keys(data[0]) : [],
19 - sample: data
20 - });
21 -}
This diff is collapsed. Click to expand it.
...@@ -69,6 +69,7 @@ ...@@ -69,6 +69,7 @@
69 } 69 }
70 } catch { entityError = true; } 70 } catch { entityError = true; }
71 loadingEntities = false; 71 loadingEntities = false;
72 + console.log('[DESCARGAS] entities loaded:', entities.length);
72 }); 73 });
73 </script> 74 </script>
74 75
......
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.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff could not be displayed because it is too large.
This diff is collapsed. Click to expand it.
1 -import { supabase } from '$lib/supabase';
2 -
3 -// Hardcoded a objeto 11700 (Sueldos) para testing de layout
4 -const CODIGO = '11700';
5 -
6 -function getGrupoCode(objeto) { return objeto.charAt(0) + '0000'; }
7 -function getSubgrupoCode(objeto) { return objeto.substring(0, 2) + '000'; }
8 -function getPartidaCode(objeto) { return objeto.substring(0, 3) + '00'; }
9 -
10 -export async function load() {
11 - const { data } = await supabase.schema('ppto').from('clas_objetos').select('*').eq('objeto', CODIGO);
12 - const objeto = data[0];
13 -
14 - const grupoCode = getGrupoCode(CODIGO);
15 - const subgrupoCode = getSubgrupoCode(CODIGO);
16 - const partidaCode = getPartidaCode(CODIGO);
17 -
18 - const { data: padresData } = await supabase.schema('ppto').from('clas_objetos').select('*').in('objeto', [grupoCode, subgrupoCode, partidaCode]).order('objeto');
19 - const padres = padresData || [];
20 -
21 - // Contar entidades
22 - const nEntidades = objeto.n_entidades || 0;
23 -
24 - return { objeto, padres, hijos: [], nEntidades };
25 -}
This diff is collapsed. Click to expand it.
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.
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'; 1 +export const ssr = false;
2 2
3 export async function load({ fetch }) { 3 export async function load({ fetch }) {
4 - // Cargar resumen y población en paralelo 4 + const pobRes = await fetch('/poblacion.csv').then(r => r.text());
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 5
19 const poblacionMap = {}; 6 const poblacionMap = {};
20 pobRes.split('\n').slice(1).forEach(line => { 7 pobRes.split('\n').slice(1).forEach(line => {
...@@ -27,7 +14,7 @@ export async function load({ fetch }) { ...@@ -27,7 +14,7 @@ export async function load({ fetch }) {
27 const gestiones = [2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025]; 14 const gestiones = [2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025];
28 15
29 return { 16 return {
30 - resumen: resumenRes.data || [], 17 + resumen: [],
31 poblacionMap, 18 poblacionMap,
32 gestiones, 19 gestiones,
33 gestionInicial: 2025 20 gestionInicial: 2025
......
...@@ -4,7 +4,6 @@ ...@@ -4,7 +4,6 @@
4 import { goto } from '$app/navigation'; 4 import { goto } from '$app/navigation';
5 import { page } from '$app/stores'; 5 import { page } from '$app/stores';
6 import * as d3 from 'd3'; 6 import * as d3 from 'd3';
7 - import { supabase } from '$lib/supabase';
8 import { mapaCache } from '$lib/stores/mapaCache'; 7 import { mapaCache } from '$lib/stores/mapaCache';
9 8
10 let { data } = $props(); 9 let { data } = $props();
...@@ -38,6 +37,22 @@ ...@@ -38,6 +37,22 @@
38 let clasSearchVal = $state(''); 37 let clasSearchVal = $state('');
39 let clasSearchResults = $state([]); 38 let clasSearchResults = $state([]);
40 let clasSearchFocused = $state(false); 39 let clasSearchFocused = $state(false);
40 + let clasSelectedIdx = $state(-1);
41 +
42 + function handleClasKeydown(e) {
43 + if (e.key === 'ArrowDown') {
44 + e.preventDefault();
45 + clasSelectedIdx = clasSelectedIdx < clasSearchResults.length - 1 ? clasSelectedIdx + 1 : 0;
46 + } else if (e.key === 'ArrowUp') {
47 + e.preventDefault();
48 + clasSelectedIdx = clasSelectedIdx > 0 ? clasSelectedIdx - 1 : clasSearchResults.length - 1;
49 + } else if (e.key === 'Enter' && clasSelectedIdx >= 0) {
50 + e.preventDefault();
51 + seleccionarClasificador(clasSearchResults[clasSelectedIdx]);
52 + } else if (e.key === 'Escape') {
53 + clasSearchFocused = false;
54 + }
55 + }
41 let clasSearchLoading = $state(false); 56 let clasSearchLoading = $state(false);
42 let clasSeleccionado = $state(null); 57 let clasSeleccionado = $state(null);
43 let clasDebounce; 58 let clasDebounce;
...@@ -89,6 +104,7 @@ ...@@ -89,6 +104,7 @@
89 104
90 function handleClasInput(e) { 105 function handleClasInput(e) {
91 clasSearchVal = e.target.value; 106 clasSearchVal = e.target.value;
107 + clasSelectedIdx = -1;
92 clearTimeout(clasDebounce); 108 clearTimeout(clasDebounce);
93 if (clasSearchVal.length >= 2) { 109 if (clasSearchVal.length >= 2) {
94 clasDebounce = setTimeout(() => buscarClasificador(clasSearchVal), 200); 110 clasDebounce = setTimeout(() => buscarClasificador(clasSearchVal), 200);
...@@ -362,20 +378,9 @@ ...@@ -362,20 +378,9 @@
362 return; 378 return;
363 } 379 }
364 380
365 - // Modo total: cargar desde Supabase 381 + // Modo total: datos no disponibles sin Supabase
366 - if (!resumenCache[gestion]) { 382 + // TODO: implementar endpoint API para resumen por municipio
367 - const { data: rows } = await supabase 383 + resumen = [];
368 - .schema('ppto')
369 - .from('entidad_resumen')
370 - .select('codigo, desc, gestion, devengado, ranking')
371 - .eq('tipo', 'gastos')
372 - .eq('tipo_codigo', 'municipio_ubigeo')
373 - .eq('gestion', gestion)
374 - .order('devengado', { ascending: false })
375 - .limit(500);
376 - resumenCache[gestion] = rows || [];
377 - }
378 - resumen = resumenCache[gestion];
379 poblacionMap = await getPoblacionAno(gestion); 384 poblacionMap = await getPoblacionAno(gestion);
380 } 385 }
381 386
...@@ -588,6 +593,7 @@ ...@@ -588,6 +593,7 @@
588 type="text" 593 type="text"
589 value={clasSearchVal} 594 value={clasSearchVal}
590 oninput={handleClasInput} 595 oninput={handleClasInput}
596 + onkeydown={handleClasKeydown}
591 onfocus={() => clasSearchFocused = true} 597 onfocus={() => clasSearchFocused = true}
592 onblur={() => setTimeout(() => clasSearchFocused = false, 150)} 598 onblur={() => setTimeout(() => clasSearchFocused = false, 150)}
593 placeholder="Buscar {clasificadorSeleccionado === 'objeto' ? 'partida' : clasificadorSeleccionado === 'finfun' ? 'finalidad o función' : 'sector'}..." 599 placeholder="Buscar {clasificadorSeleccionado === 'objeto' ? 'partida' : clasificadorSeleccionado === 'finfun' ? 'finalidad o función' : 'sector'}..."
...@@ -599,8 +605,8 @@ ...@@ -599,8 +605,8 @@
599 {:else if clasSearchResults.length === 0} 605 {:else if clasSearchResults.length === 0}
600 <div class="objeto-search-msg">Sin resultados</div> 606 <div class="objeto-search-msg">Sin resultados</div>
601 {:else} 607 {:else}
602 - {#each clasSearchResults as result} 608 + {#each clasSearchResults as result, i}
603 - <button class="objeto-search-item" onclick={() => seleccionarClasificador(result)}> 609 + <button class="objeto-search-item" class:active={clasSelectedIdx === i} onclick={() => seleccionarClasificador(result)}>
604 <span class="objeto-search-code">{result.codigo}</span> 610 <span class="objeto-search-code">{result.codigo}</span>
605 <span class="objeto-search-name">{@html result.highlight}</span> 611 <span class="objeto-search-name">{@html result.highlight}</span>
606 </button> 612 </button>
......