Showing
9 changed files
with
243 additions
and
57 deletions
| ... | @@ -242,6 +242,8 @@ | ... | @@ -242,6 +242,8 @@ |
| 242 | color: #B8B5AD; | 242 | color: #B8B5AD; |
| 243 | cursor: pointer; | 243 | cursor: pointer; |
| 244 | transition: all 0.2s ease; | 244 | transition: all 0.2s ease; |
| 245 | + backdrop-filter: blur(12px); | ||
| 246 | + -webkit-backdrop-filter: blur(12px); | ||
| 245 | } | 247 | } |
| 246 | 248 | ||
| 247 | .nav-search-btn:hover { | 249 | .nav-search-btn:hover { |
| ... | @@ -291,6 +293,8 @@ | ... | @@ -291,6 +293,8 @@ |
| 291 | border: 1px solid rgba(255, 255, 255, 0.1); | 293 | border: 1px solid rgba(255, 255, 255, 0.1); |
| 292 | border-radius: 10px; | 294 | border-radius: 10px; |
| 293 | color: #B8B5AD; | 295 | color: #B8B5AD; |
| 296 | + backdrop-filter: blur(12px); | ||
| 297 | + -webkit-backdrop-filter: blur(12px); | ||
| 294 | cursor: pointer; | 298 | cursor: pointer; |
| 295 | transition: all 0.2s ease; | 299 | transition: all 0.2s ease; |
| 296 | } | 300 | } |
| ... | @@ -330,6 +334,8 @@ | ... | @@ -330,6 +334,8 @@ |
| 330 | border: 1px solid rgba(255, 255, 255, 0.1); | 334 | border: 1px solid rgba(255, 255, 255, 0.1); |
| 331 | border-radius: 10px; | 335 | border-radius: 10px; |
| 332 | color: #B8B5AD; | 336 | color: #B8B5AD; |
| 337 | + backdrop-filter: blur(12px); | ||
| 338 | + -webkit-backdrop-filter: blur(12px); | ||
| 333 | cursor: pointer; | 339 | cursor: pointer; |
| 334 | transition: all 0.2s ease; | 340 | transition: all 0.2s ease; |
| 335 | text-decoration: none; | 341 | text-decoration: none; |
| ... | @@ -363,6 +369,8 @@ | ... | @@ -363,6 +369,8 @@ |
| 363 | border: 1px solid rgba(255, 255, 255, 0.08); | 369 | border: 1px solid rgba(255, 255, 255, 0.08); |
| 364 | border-radius: 8px; | 370 | border-radius: 8px; |
| 365 | color: #B8B5AD; | 371 | color: #B8B5AD; |
| 372 | + backdrop-filter: blur(12px); | ||
| 373 | + -webkit-backdrop-filter: blur(12px); | ||
| 366 | cursor: pointer; | 374 | cursor: pointer; |
| 367 | transition: all 0.2s ease; | 375 | transition: all 0.2s ease; |
| 368 | } | 376 | } | ... | ... |
| 1 | <script> | 1 | <script> |
| 2 | - import { onMount, tick } from 'svelte'; | 2 | + import { tick } from 'svelte'; |
| 3 | import { goto } from '$app/navigation'; | 3 | import { goto } from '$app/navigation'; |
| 4 | - import { | 4 | + import { get } from 'svelte/store'; |
| 5 | - query, | 5 | + import { landingSearchMode, landingSelectedClassifiers } from '$lib/stores/landingSearchState'; |
| 6 | - results, | ||
| 7 | - isLoading, | ||
| 8 | - indexLoaded, | ||
| 9 | - error, | ||
| 10 | - selectedIndex, | ||
| 11 | - initIndex, | ||
| 12 | - performSearch, | ||
| 13 | - clearSearch, | ||
| 14 | - navigateResults | ||
| 15 | - } from '$lib/stores/searchStore'; | ||
| 16 | 6 | ||
| 17 | let { open = $bindable(false) } = $props(); | 7 | let { open = $bindable(false) } = $props(); |
| 18 | 8 | ||
| 19 | let searchInput = $state(null); | 9 | let searchInput = $state(null); |
| 20 | let searchVal = $state(''); | 10 | let searchVal = $state(''); |
| 21 | - let isMac = $state(false); | 11 | + let isMac = $state(typeof navigator !== 'undefined' && /Mac|iPod|iPhone|iPad/.test(navigator.platform)); |
| 22 | - | 12 | + let searchLoading = $state(false); |
| 23 | - // Search filters | 13 | + let searchResults = $state([]); |
| 24 | - let searchFilters = $state({ | 14 | + let selectedIdx = $state(-1); |
| 25 | - entidad: true, | 15 | + let debounceTimer; |
| 26 | - objeto_gasto: true, | 16 | + |
| 27 | - rubro: true, | 17 | + // Mapeo de clasificadores landing → filtros modal |
| 28 | - finfun: true, | 18 | + const classifierToFilter = { |
| 29 | - organismo: true, | 19 | + entidad: 'entidad', |
| 30 | - fuente: true | 20 | + objeto: 'objeto', |
| 21 | + rubro: 'rubro', | ||
| 22 | + finfun: 'finfun', | ||
| 23 | + organismo: 'organismo', | ||
| 24 | + fuente: 'fuente' | ||
| 25 | + }; | ||
| 26 | + | ||
| 27 | + function getInitialFilters() { | ||
| 28 | + const mode = get(landingSearchMode); | ||
| 29 | + const classifiers = get(landingSelectedClassifiers); | ||
| 30 | + if (mode === 'clasificadores' && classifiers.length > 0) { | ||
| 31 | + const filters = { entidad: false, objeto_gasto: false, rubro: false, finfun: false, organismo: false, fuente: false }; | ||
| 32 | + classifiers.forEach(c => { | ||
| 33 | + const key = classifierToFilter[c]; | ||
| 34 | + if (key === 'objeto') filters.objeto_gasto = true; | ||
| 35 | + else if (key && filters[key] !== undefined) filters[key] = true; | ||
| 31 | }); | 36 | }); |
| 37 | + return filters; | ||
| 38 | + } | ||
| 39 | + return { entidad: true, objeto_gasto: true, rubro: true, finfun: true, organismo: true, fuente: true }; | ||
| 40 | + } | ||
| 32 | 41 | ||
| 33 | - // Filtered results based on active filters | 42 | + let searchFilters = $state(getInitialFilters()); |
| 34 | - let filteredResults = $derived( | ||
| 35 | - $results.filter(item => searchFilters[item.tipo]) | ||
| 36 | - ); | ||
| 37 | 43 | ||
| 38 | function toggleFilter(tipo) { | 44 | function toggleFilter(tipo) { |
| 39 | searchFilters[tipo] = !searchFilters[tipo]; | 45 | searchFilters[tipo] = !searchFilters[tipo]; |
| 46 | + // Re-buscar con filtros actualizados | ||
| 47 | + if (searchVal.length >= 2) doSearch(searchVal); | ||
| 48 | + } | ||
| 49 | + | ||
| 50 | + // Filtros activos → class_ param para Typesense | ||
| 51 | + function getActiveClasses() { | ||
| 52 | + const map = { entidad: 'entidad', objeto_gasto: 'objeto', finfun: 'finfun' }; | ||
| 53 | + return Object.entries(searchFilters) | ||
| 54 | + .filter(([_, v]) => v) | ||
| 55 | + .map(([k]) => map[k] || k) | ||
| 56 | + .filter(Boolean); | ||
| 40 | } | 57 | } |
| 41 | 58 | ||
| 42 | - onMount(() => { | 59 | + function parseMetadatos(meta) { |
| 43 | - isMac = /Mac|iPod|iPhone|iPad/.test(navigator.platform); | 60 | + if (!meta) return {}; |
| 44 | - initIndex(); | 61 | + if (typeof meta === 'object') return meta; |
| 62 | + try { return JSON.parse(meta); } catch { return {}; } | ||
| 63 | + } | ||
| 64 | + | ||
| 65 | + async function doSearch(query) { | ||
| 66 | + searchLoading = true; | ||
| 67 | + try { | ||
| 68 | + const classes = getActiveClasses(); | ||
| 69 | + const params = new URLSearchParams({ | ||
| 70 | + q: query, | ||
| 71 | + is_class: 'true', | ||
| 72 | + per_page: '30' | ||
| 45 | }); | 73 | }); |
| 74 | + if (classes.length > 0 && classes.length < 6) { | ||
| 75 | + params.set('class_', classes.join(',')); | ||
| 76 | + } | ||
| 77 | + const res = await fetch(`/api/search?${params}`); | ||
| 78 | + if (!res.ok) throw new Error(); | ||
| 79 | + const data = await res.json(); | ||
| 80 | + searchResults = (data.hits || []).map(hit => { | ||
| 81 | + const meta = parseMetadatos(hit.document.metadatos); | ||
| 82 | + const cls = hit.document.class_; | ||
| 83 | + let tipo = cls; | ||
| 84 | + let codigo = ''; | ||
| 85 | + let nombre = hit.document.texto; | ||
| 86 | + let highlight = hit.highlights?.[0]?.snippet || nombre; | ||
| 87 | + | ||
| 88 | + if (cls === 'entidad') { | ||
| 89 | + const esDA = !!meta.da; | ||
| 90 | + codigo = esDA ? `${meta.entidad}.${meta.da}` : String(meta.entidad); | ||
| 91 | + tipo = 'entidad'; | ||
| 92 | + } else if (cls === 'objeto') { | ||
| 93 | + codigo = meta.objeto_subpartida || meta.objeto_partida || meta.objeto_subgrupo || meta.objeto_grupo || ''; | ||
| 94 | + tipo = 'objeto_gasto'; | ||
| 95 | + } else if (cls === 'finfun') { | ||
| 96 | + const fin = String(meta.finfun_finalidad || ''); | ||
| 97 | + if (meta.finfun_funcion !== undefined && meta.finfun_grpfuncion !== undefined) { | ||
| 98 | + codigo = `${fin}.${meta.finfun_grpfuncion}.${meta.finfun_funcion}`; | ||
| 99 | + } else if (meta.finfun_grpfuncion !== undefined) { | ||
| 100 | + codigo = `${fin}.${meta.finfun_grpfuncion}`; | ||
| 101 | + } else { | ||
| 102 | + codigo = fin; | ||
| 103 | + } | ||
| 104 | + tipo = 'finfun'; | ||
| 105 | + } | ||
| 46 | 106 | ||
| 47 | - // Focus input when modal opens | 107 | + return { tipo, codigo, nombre, highlight }; |
| 108 | + }); | ||
| 109 | + selectedIdx = -1; | ||
| 110 | + } catch { | ||
| 111 | + searchResults = []; | ||
| 112 | + } | ||
| 113 | + searchLoading = false; | ||
| 114 | + } | ||
| 115 | + | ||
| 116 | + let filteredResults = $derived(searchResults); | ||
| 117 | + | ||
| 118 | + // Focus input and sync filters when modal opens | ||
| 48 | $effect(() => { | 119 | $effect(() => { |
| 49 | if (open) { | 120 | if (open) { |
| 121 | + searchFilters = getInitialFilters(); | ||
| 50 | tick().then(() => { | 122 | tick().then(() => { |
| 51 | searchInput?.focus(); | 123 | searchInput?.focus(); |
| 52 | }); | 124 | }); |
| 53 | } else { | 125 | } else { |
| 54 | - // Clear on close | ||
| 55 | searchVal = ''; | 126 | searchVal = ''; |
| 56 | - clearSearch(); | 127 | + searchResults = []; |
| 128 | + selectedIdx = -1; | ||
| 57 | } | 129 | } |
| 58 | }); | 130 | }); |
| 59 | 131 | ||
| ... | @@ -63,10 +135,11 @@ | ... | @@ -63,10 +135,11 @@ |
| 63 | 135 | ||
| 64 | function handleInput(e) { | 136 | function handleInput(e) { |
| 65 | searchVal = e.target.value; | 137 | searchVal = e.target.value; |
| 138 | + clearTimeout(debounceTimer); | ||
| 66 | if (searchVal.length >= 2) { | 139 | if (searchVal.length >= 2) { |
| 67 | - performSearch(searchVal); | 140 | + debounceTimer = setTimeout(() => doSearch(searchVal), 200); |
| 68 | } else { | 141 | } else { |
| 69 | - clearSearch(); | 142 | + searchResults = []; |
| 70 | } | 143 | } |
| 71 | } | 144 | } |
| 72 | 145 | ||
| ... | @@ -75,13 +148,13 @@ | ... | @@ -75,13 +148,13 @@ |
| 75 | closeModal(); | 148 | closeModal(); |
| 76 | } else if (e.key === 'ArrowDown') { | 149 | } else if (e.key === 'ArrowDown') { |
| 77 | e.preventDefault(); | 150 | e.preventDefault(); |
| 78 | - navigateResults('down', filteredResults.length); | 151 | + selectedIdx = selectedIdx < filteredResults.length - 1 ? selectedIdx + 1 : 0; |
| 79 | } else if (e.key === 'ArrowUp') { | 152 | } else if (e.key === 'ArrowUp') { |
| 80 | e.preventDefault(); | 153 | e.preventDefault(); |
| 81 | - navigateResults('up', filteredResults.length); | 154 | + selectedIdx = selectedIdx > 0 ? selectedIdx - 1 : filteredResults.length - 1; |
| 82 | - } else if (e.key === 'Enter' && $selectedIndex >= 0) { | 155 | + } else if (e.key === 'Enter' && selectedIdx >= 0) { |
| 83 | e.preventDefault(); | 156 | e.preventDefault(); |
| 84 | - goToResult(filteredResults[$selectedIndex]); | 157 | + goToResult(filteredResults[selectedIdx]); |
| 85 | } | 158 | } |
| 86 | } | 159 | } |
| 87 | 160 | ||
| ... | @@ -91,10 +164,10 @@ | ... | @@ -91,10 +164,10 @@ |
| 91 | goto(`/entidad/${item.codigo}`); | 164 | goto(`/entidad/${item.codigo}`); |
| 92 | } else if (item.tipo === 'objeto_gasto') { | 165 | } else if (item.tipo === 'objeto_gasto') { |
| 93 | goto(`/objeto/${item.codigo}`); | 166 | goto(`/objeto/${item.codigo}`); |
| 94 | - } else if (item.tipo === 'rubro') { | ||
| 95 | - goto(`/rubro/${item.codigo}`); | ||
| 96 | } else if (item.tipo === 'finfun') { | 167 | } else if (item.tipo === 'finfun') { |
| 97 | goto(`/finfun/${item.codigo}`); | 168 | goto(`/finfun/${item.codigo}`); |
| 169 | + } else if (item.tipo === 'rubro') { | ||
| 170 | + goto(`/rubro/${item.codigo}`); | ||
| 98 | } else if (item.tipo === 'organismo') { | 171 | } else if (item.tipo === 'organismo') { |
| 99 | goto(`/organismo/${item.codigo}`); | 172 | goto(`/organismo/${item.codigo}`); |
| 100 | } else if (item.tipo === 'fuente') { | 173 | } else if (item.tipo === 'fuente') { |
| ... | @@ -248,9 +321,7 @@ | ... | @@ -248,9 +321,7 @@ |
| 248 | 321 | ||
| 249 | <!-- Results --> | 322 | <!-- Results --> |
| 250 | <div class="search-modal-results"> | 323 | <div class="search-modal-results"> |
| 251 | - {#if !$indexLoaded} | 324 | + {#if searchLoading} |
| 252 | - <div class="search-modal-status">Cargando índice...</div> | ||
| 253 | - {:else if $isLoading} | ||
| 254 | <div class="search-modal-status">Buscando...</div> | 325 | <div class="search-modal-status">Buscando...</div> |
| 255 | {:else if searchVal.length < 2} | 326 | {:else if searchVal.length < 2} |
| 256 | <div class="search-modal-empty"></div> | 327 | <div class="search-modal-empty"></div> |
| ... | @@ -260,18 +331,15 @@ | ... | @@ -260,18 +331,15 @@ |
| 260 | {#each filteredResults as item, i} | 331 | {#each filteredResults as item, i} |
| 261 | <button | 332 | <button |
| 262 | class="search-result-item" | 333 | class="search-result-item" |
| 263 | - class:result-selected={$selectedIndex === i} | 334 | + class:result-selected={selectedIdx === i} |
| 264 | onclick={() => goToResult(item)} | 335 | onclick={() => goToResult(item)} |
| 265 | - onmouseenter={() => selectedIndex.set(i)} | 336 | + onmouseenter={() => { selectedIdx = i; }} |
| 266 | > | 337 | > |
| 267 | <div class="result-main"> | 338 | <div class="result-main"> |
| 268 | <span class="result-type" style="color:{typeConfig[item.tipo]?.color || '#888'}"> | 339 | <span class="result-type" style="color:{typeConfig[item.tipo]?.color || '#888'}"> |
| 269 | {typeConfig[item.tipo]?.label || item.tipo} | 340 | {typeConfig[item.tipo]?.label || item.tipo} |
| 270 | </span> | 341 | </span> |
| 271 | - <span class="result-name">{item.nombre}</span> | 342 | + <span class="result-name">{@html item.highlight}</span> |
| 272 | - {#if item.descripcion} | ||
| 273 | - <span class="result-desc">{item.descripcion}</span> | ||
| 274 | - {/if} | ||
| 275 | </div> | 343 | </div> |
| 276 | <span class="result-code">{item.codigo}</span> | 344 | <span class="result-code">{item.codigo}</span> |
| 277 | </button> | 345 | </button> | ... | ... |
src/lib/stores/landingSearchState.js
0 → 100644
| 1 | +import { writable } from 'svelte/store'; | ||
| 2 | + | ||
| 3 | +// Persiste el estado de búsqueda del landing entre navegaciones | ||
| 4 | +export const landingSearchMode = writable('programas'); // 'programas' | 'clasificadores' | ||
| 5 | +export const landingSelectedClassifiers = writable([]); // ['entidad', 'objeto', ...] | ||
| 6 | +export const landingSearchQuery = writable(''); |
This diff is collapsed. Click to expand it.
| ... | @@ -2,24 +2,64 @@ import { supabase } from '$lib/supabase'; | ... | @@ -2,24 +2,64 @@ import { supabase } from '$lib/supabase'; |
| 2 | import { error } from '@sveltejs/kit'; | 2 | import { error } from '@sveltejs/kit'; |
| 3 | 3 | ||
| 4 | export async function load({ params }) { | 4 | export async function load({ params }) { |
| 5 | - const codigo = parseInt(params.codigo); | 5 | + const codigo = params.codigo; |
| 6 | + const isDA = codigo.includes('.'); | ||
| 7 | + const entidadCode = isDA ? codigo.split('.')[0] : codigo; | ||
| 8 | + const entidadNum = parseInt(entidadCode); | ||
| 6 | 9 | ||
| 7 | - if (isNaN(codigo)) { | 10 | + if (isNaN(entidadNum)) { |
| 8 | throw error(400, 'Código de entidad inválido'); | 11 | throw error(400, 'Código de entidad inválido'); |
| 9 | } | 12 | } |
| 10 | 13 | ||
| 11 | - const { data, error: dbError } = await supabase | 14 | + // Cargar metadata y resumen en paralelo |
| 15 | + const [entidadRes, resumenRes] = await Promise.all([ | ||
| 16 | + supabase | ||
| 12 | .schema('ppto') | 17 | .schema('ppto') |
| 13 | .from('clas_institucional') | 18 | .from('clas_institucional') |
| 14 | .select('*') | 19 | .select('*') |
| 15 | - .eq('entidad', codigo) | 20 | + .eq('entidad', entidadNum) |
| 16 | - .single(); | 21 | + .single(), |
| 17 | 22 | ||
| 18 | - if (dbError || !data) { | 23 | + supabase |
| 24 | + .schema('ppto') | ||
| 25 | + .from('entidad_resumen') | ||
| 26 | + .select('tipo, tipo_codigo, codigo, desc, desc_padre, gestion, devengado, ranking') | ||
| 27 | + .eq('codigo', codigo) | ||
| 28 | + ]); | ||
| 29 | + | ||
| 30 | + if (entidadRes.error || !entidadRes.data) { | ||
| 19 | throw error(404, 'Entidad no encontrada'); | 31 | throw error(404, 'Entidad no encontrada'); |
| 20 | } | 32 | } |
| 21 | 33 | ||
| 34 | + // Determinar última gestión disponible | ||
| 35 | + const gestiones = [...new Set((resumenRes.data || []).map(d => d.gestion))].sort((a, b) => b - a); | ||
| 36 | + const ultimaGestion = gestiones[0] || 2025; | ||
| 37 | + | ||
| 38 | + // Cargar distribuciones solo de la última gestión | ||
| 39 | + const distRes = await supabase | ||
| 40 | + .schema('ppto') | ||
| 41 | + .from('entidad_distribuciones') | ||
| 42 | + .select('tipo, dimension, gestion, padre, desc_padre, hijo, desc_hijo, devengado') | ||
| 43 | + .eq('codigo', codigo) | ||
| 44 | + .eq('gestion', ultimaGestion); | ||
| 45 | + | ||
| 46 | + // Resolver nombre de DA desde el resumen | ||
| 47 | + let nombreDA = null; | ||
| 48 | + let nombreEntidadMadre = null; | ||
| 49 | + if (isDA && resumenRes.data?.length > 0) { | ||
| 50 | + const firstRow = resumenRes.data[0]; | ||
| 51 | + nombreDA = firstRow.desc || null; | ||
| 52 | + nombreEntidadMadre = firstRow.desc_padre || null; | ||
| 53 | + } | ||
| 54 | + | ||
| 22 | return { | 55 | return { |
| 23 | - entidad: data | 56 | + entidad: entidadRes.data, |
| 57 | + isDA, | ||
| 58 | + nombreDA, | ||
| 59 | + nombreEntidadMadre, | ||
| 60 | + codigoEntidadPadre: isDA ? entidadCode : null, | ||
| 61 | + resumenData: resumenRes.data || [], | ||
| 62 | + distribucionesData: distRes.data || [], | ||
| 63 | + gestionInicial: ultimaGestion | ||
| 24 | }; | 64 | }; |
| 25 | } | 65 | } | ... | ... |
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
static/screen.png
0 → 100644
52.3 KB
| ... | @@ -377,6 +377,70 @@ Sector → Subsector → Área → Subárea → Entidad | ... | @@ -377,6 +377,70 @@ Sector → Subsector → Área → Subárea → Entidad |
| 377 | 377 | ||
| 378 | --- | 378 | --- |
| 379 | 379 | ||
| 380 | +## Tablas de Vista de Entidad | ||
| 380 | 381 | ||
| 382 | +### 13. `entidad_resumen` - Resumen histórico por entidad/DA | ||
| 383 | + | ||
| 384 | +Alimenta los gráficos de historia (area plots) y rankings en la vista de entidad (`/entidad/[codigo]`). | ||
| 385 | + | ||
| 386 | +| Columna | Tipo | Descripción | | ||
| 387 | +|---------|------|-------------| | ||
| 388 | +| tipo | TEXT | Tipo de flujo: 'gastos' o 'ingresos' | | ||
| 389 | +| tipo_codigo | TEXT | Tipo de código: 'entidad', 'entidad_da', 'municipio_ubigeo', etc. | | ||
| 390 | +| codigo | TEXT | Código de la entidad o DA (ej: "1901", "1901.6") | | ||
| 391 | +| desc | TEXT | Nombre de la entidad o DA | | ||
| 392 | +| desc_padre | TEXT | Nombre de la entidad madre (para DAs) | | ||
| 393 | +| gestion | INTEGER | Año fiscal | | ||
| 394 | +| devengado | NUMERIC | Monto devengado en Bs | | ||
| 395 | +| ranking | INTEGER | Posición en ranking por monto dentro de su tipo y gestión | | ||
| 396 | + | ||
| 397 | +**Primary key:** `(tipo, tipo_codigo, codigo, gestion)` | ||
| 398 | + | ||
| 399 | +**Índices:** | ||
| 400 | +```sql | ||
| 401 | +CREATE INDEX idx_entidad_resumen_codigo ON ppto.entidad_resumen (codigo); | ||
| 402 | +CREATE INDEX idx_entidad_resumen_gestion ON ppto.entidad_resumen (gestion); | ||
| 403 | +CREATE INDEX idx_entidad_resumen_tipo_codigo ON ppto.entidad_resumen (tipo_codigo); | ||
| 404 | +``` | ||
| 405 | + | ||
| 406 | +**Notas:** | ||
| 407 | +- ~53K filas | ||
| 408 | +- `codigo` es TEXT para soportar entidades ("1901") y DAs ("1901.6") | ||
| 409 | +- `desc` y `desc_padre` permiten resolver nombres sin joins ni búsquedas externas | ||
| 410 | +- Una fila por combinación tipo + tipo_codigo + código + año | ||
| 411 | + | ||
| 412 | +--- | ||
| 413 | + | ||
| 414 | +### 14. `entidad_distribuciones` - Distribución del gasto/ingreso por clasificador | ||
| 415 | + | ||
| 416 | +Alimenta las barras de composición (clasificadores de gasto e ingreso) en la vista de entidad. | ||
| 417 | + | ||
| 418 | +| Columna | Tipo | Descripción | | ||
| 419 | +|---------|------|-------------| | ||
| 420 | +| id | SERIAL (PK) | Identificador autoincremental | | ||
| 421 | +| tipo | TEXT | Tipo de flujo: 'gastos' o 'ingresos' | | ||
| 422 | +| tipo_codigo | TEXT | Tipo de código: 'entidad', 'entidad_da', etc. | | ||
| 423 | +| codigo | TEXT | Código de la entidad o DA | | ||
| 424 | +| dimension | TEXT | Clasificador: 'objeto', 'finfun', 'acteco', 'rubro', 'organismo' | | ||
| 425 | +| gestion | INTEGER | Año fiscal | | ||
| 426 | +| padre | TEXT | Código del grupo padre en la dimensión | | ||
| 427 | +| desc_padre | TEXT | Descripción del grupo padre | | ||
| 428 | +| hijo | TEXT | Código del ítem hijo | | ||
| 429 | +| desc_hijo | TEXT | Descripción del ítem hijo | | ||
| 430 | +| devengado | NUMERIC | Monto devengado en Bs | | ||
| 431 | + | ||
| 432 | +**Índices:** | ||
| 433 | +```sql | ||
| 434 | +CREATE INDEX idx_entidad_dist_codigo ON ppto.entidad_distribuciones (codigo); | ||
| 435 | +CREATE INDEX idx_entidad_dist_gestion ON ppto.entidad_distribuciones (gestion); | ||
| 436 | +CREATE INDEX idx_entidad_dist_codigo_gestion ON ppto.entidad_distribuciones (codigo, gestion); | ||
| 437 | +``` | ||
| 438 | + | ||
| 439 | +**Notas:** | ||
| 440 | +- ~3.6M filas | ||
| 441 | +- Usa `id SERIAL` como PK porque un mismo `hijo` puede tener distintas descripciones dentro de la misma combinación padre/gestión | ||
| 442 | +- Cada fila es un segmento de las barras de composición: cuánto gastó/ingresó una entidad en un ítem específico de un clasificador en un año | ||
| 443 | + | ||
| 444 | +--- | ||
| 381 | 445 | ||
| 382 | *Documento generado para el equipo de sistemas - Marzo 2026* | 446 | *Documento generado para el equipo de sistemas - Marzo 2026* | ... | ... |
-
Please register or login to post a comment