Rafael Lopez

buscador

......@@ -228,3 +228,145 @@ body {
.font-display {
font-family: var(--font-display);
}
/* ============================================
FALLBACK LAYOUT UTILITIES
Respaldo para asegurar que flex siempre funcione
============================================ */
/* Forzar flex en contenedores principales */
.flex {
display: flex !important;
}
.flex-1 {
flex: 1 1 0% !important;
}
.flex-col {
flex-direction: column !important;
}
.flex-row {
flex-direction: row !important;
}
.flex-wrap {
flex-wrap: wrap !important;
}
.flex-shrink-0 {
flex-shrink: 0 !important;
}
.items-center {
align-items: center !important;
}
.items-start {
align-items: flex-start !important;
}
.justify-center {
justify-content: center !important;
}
.justify-between {
justify-content: space-between !important;
}
.gap-1 { gap: 0.25rem !important; }
.gap-2 { gap: 0.5rem !important; }
.gap-3 { gap: 0.75rem !important; }
.gap-4 { gap: 1rem !important; }
.gap-6 { gap: 1.5rem !important; }
/* Grid fallbacks */
.grid {
display: grid !important;
}
/* Hidden utilities */
.hidden {
display: none !important;
}
/* Responsive: mostrar en lg+ */
@media (min-width: 1024px) {
.lg\:flex {
display: flex !important;
}
.lg\:block {
display: block !important;
}
.lg\:hidden {
display: none !important;
}
.lg\:relative {
position: relative !important;
}
.lg\:translate-x-0 {
transform: translateX(0) !important;
}
}
@media (min-width: 1280px) {
.xl\:block {
display: block !important;
}
.xl\:hidden {
display: none !important;
}
}
/* ============================================
CLASIFICADORES LAYOUT - ESTILOS GLOBALES
Forzar layout correcto en todas las páginas
============================================ */
.clasificador-layout {
display: flex !important;
flex-direction: row !important;
}
.clasificador-layout > .sidebar-left {
position: relative !important;
transform: translateX(0) !important;
flex-shrink: 0 !important;
width: 18rem !important;
z-index: auto !important;
box-shadow: none !important;
}
.clasificador-layout > main {
flex: 1 1 0% !important;
min-width: 0 !important;
}
/* Forzar estilos correctos después del mount (para navegación cliente) */
@media (min-width: 1024px) {
.sidebar-mounted.sidebar-left {
position: relative !important;
transform: translateX(0) !important;
flex-shrink: 0 !important;
z-index: auto !important;
box-shadow: none !important;
}
}
@media (max-width: 1023px) {
.clasificador-layout {
display: block !important;
}
.clasificador-layout > .sidebar-left {
position: fixed !important;
transform: translateX(-100%) !important;
width: 20rem !important;
z-index: 50 !important;
}
.clasificador-layout > .sidebar-left.translate-x-0 {
transform: translateX(0) !important;
}
}
......
......@@ -17,7 +17,7 @@
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<body data-sveltekit-preload-data="off">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>
......
......@@ -33,7 +33,12 @@
// Dimensiones del área del gráfico
let effectiveHeight = $derived(fill ? measuredHeight : height);
let chartHeight = $derived(effectiveHeight - marginTop - marginBottom);
let chartHeight = $derived(Math.max(effectiveHeight - marginTop - marginBottom, 50));
// Máximo del eje Y (con guard para data vacía o valores undefined)
let maxPerCapita = $derived(
data.length > 0 ? Math.max(...data.map(d => d.perCapita || 0), 1) : 1
);
// Escalas D3
let xScale = $derived(
......@@ -46,7 +51,7 @@
// Para CSS bottom positioning: 0 → 0%, max → 100%
let yScale = $derived(
scaleLinear()
.domain([0, Math.max(...data.map(d => d.perCapita))])
.domain([0, maxPerCapita])
.range([0, chartHeight])
.nice()
);
......@@ -65,7 +70,8 @@
}
</script>
<div class="chart-wrapper" bind:this={wrapperEl} style="height: {fill ? '100%' : height + 'px'}">
<div class="chart-wrapper" bind:this={wrapperEl} style="height: {fill ? '100%' : height + 'px'}; min-height: {height}px">
{#if data.length > 0 && chartHeight > 0}
<!-- Contenedor principal con márgenes -->
<div class="chart-inner" style="top: {marginTop}px; bottom: {marginBottom}px; left: {marginLeft}px; right: {marginRight}px">
......@@ -118,6 +124,11 @@
</span>
{/each}
</div>
{:else}
<div class="chart-empty">
<span>Sin datos</span>
</div>
{/if}
</div>
<style>
......@@ -210,4 +221,15 @@
.x-label.visible {
opacity: 1;
}
.chart-empty {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
color: var(--theme-texto);
opacity: 0.5;
font-size: 0.875rem;
}
</style>
......
<script>
import { goto } from '$app/navigation';
import {
query,
results,
isLoading as searchLoading,
indexLoaded,
selectedIndex,
performSearch,
clearSearch,
navigateResults
} from '$lib/stores/searchStore';
import { indexLoaded } from '$lib/stores/searchStore';
import { search } from '$lib/services/search';
let {
preserveVista = false,
......@@ -19,19 +11,23 @@
let searchInputRef = $state(null);
let searchVal = $state('');
let searchFocused = $state(false);
let isSearching = $state(false);
let localResults = $state([]);
let selectedIdx = $state(-1);
let debounceTimer;
// Filtrar solo objeto_gasto
let filteredSearchResults = $derived($results.filter(r => r.tipo === 'objeto_gasto'));
function handleSearchInput(e) {
const val = e.target.value;
clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => {
if (val.length >= 2) {
performSearch(val);
isSearching = true;
// Buscar y filtrar solo objetos de gasto
const allResults = search(val, 50);
localResults = allResults.filter(r => r.tipo === 'objeto_gasto').slice(0, 15);
isSearching = false;
} else {
clearSearch();
localResults = [];
}
}, 200);
}
......@@ -39,14 +35,18 @@
function handleSearchKeydown(e) {
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
e.preventDefault();
navigateResults(e.key === 'ArrowDown' ? 1 : -1, filteredSearchResults.length);
} else if (e.key === 'Enter' && filteredSearchResults.length > 0) {
if (e.key === 'ArrowDown') {
selectedIdx = selectedIdx < localResults.length - 1 ? selectedIdx + 1 : 0;
} else {
selectedIdx = selectedIdx > 0 ? selectedIdx - 1 : localResults.length - 1;
}
} else if (e.key === 'Enter' && localResults.length > 0) {
e.preventDefault();
const selected = filteredSearchResults[$selectedIndex];
const selected = localResults[selectedIdx >= 0 ? selectedIdx : 0];
if (selected) handleSearchSelect(selected);
} else if (e.key === 'Escape') {
searchVal = '';
clearSearch();
localResults = [];
searchInputRef?.blur();
}
}
......@@ -57,7 +57,7 @@
: `/objeto/${result.codigo}`;
goto(url);
searchVal = '';
clearSearch();
localResults = [];
searchFocused = false;
}
......@@ -68,7 +68,7 @@
}
function handleClear() {
clearSearch();
localResults = [];
searchVal = '';
}
</script>
......@@ -97,17 +97,17 @@
{/if}
{#if searchFocused && searchVal.length >= 2}
<div class="search-dropdown">
{#if $searchLoading}
{#if isSearching}
<div class="search-msg">Buscando...</div>
{:else if filteredSearchResults.length === 0}
{:else if localResults.length === 0}
<div class="search-msg">Sin resultados</div>
{:else}
{#each filteredSearchResults as result, i}
{#each localResults as result, i}
<button
class="search-item"
class:selected={$selectedIndex === i}
class:selected={selectedIdx === i}
onclick={() => handleSearchSelect(result)}
onmouseenter={() => selectedIndex.set(i)}
onmouseenter={() => selectedIdx = i}
>
<span class="item-code">{result.codigo}</span>
<span class="item-name">{result.nombre}</span>
......
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
......@@ -54,7 +54,7 @@ export async function initSearchIndex() {
const { data: rubros, error: errorRubros } = await supabase
.schema('ppto')
.from('clas_rubros')
.select('rubro, desc_rubros, nivel');
.select('rubro, desc_rubro, nivel');
if (errorRubros) {
console.error('Error cargando rubros:', errorRubros);
......@@ -93,11 +93,11 @@ export async function initSearchIndex() {
const rubrosIndex = rubros.map(item => ({
tipo: 'rubro',
codigo: item.rubro,
nombre: item.desc_rubros,
nombre: item.desc_rubro,
sigla: null,
contexto: `Rubro · ${nivelLabelsRubros[item.nivel] || item.nivel}`,
nivel: item.nivel,
nombre_normalizado: normalizeText(item.desc_rubros),
nombre_normalizado: normalizeText(item.desc_rubro),
sigla_normalizada: '',
codigo_normalizado: item.rubro?.toString() || ''
}));
......
......@@ -4,6 +4,7 @@
import favicon from '$lib/assets/favicon.svg';
import Navbar from '$lib/components/ui/Navbar.svelte';
import NavigationDrawer from '$lib/components/layout/NavigationDrawer.svelte';
import SearchModal from '$lib/components/ui/SearchModal.svelte';
import { openDrawer } from '$lib/stores/drawer.js';
import Spinner from '$lib/components/ui/Spinner.svelte';
......@@ -12,6 +13,21 @@
// Don't show navbar on landing page
let isLanding = $derived($page.url.pathname === '/');
// Global search modal state
let searchModalOpen = $state(false);
function openSearchModal() {
searchModalOpen = true;
}
function handleGlobalKeydown(e) {
// Cmd+K or Ctrl+K to open search
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
e.preventDefault();
searchModalOpen = true;
}
}
// Navigation loading state
let showLoader = $state(false);
let loaderTimeout = null;
......@@ -42,12 +58,17 @@
<link href="https://fonts.googleapis.com/css2?family=DM+Serif+Display:ital@0;1&family=DM+Mono:wght@400;500&family=Instrument+Sans:wght@400;500;600&display=swap" rel="stylesheet" />
</svelte:head>
<svelte:window onkeydown={handleGlobalKeydown} />
<!-- Navigation Drawer (siempre presente) -->
<NavigationDrawer />
<!-- Global Search Modal -->
<SearchModal bind:open={searchModalOpen} />
<!-- Floating controls bar (fixed position - no afecta el flujo) -->
{#if !isLanding}
<Navbar onOpenDrawer={openDrawer} />
<Navbar onOpenDrawer={openDrawer} onOpenSearch={openSearchModal} />
{/if}
<!-- Navigation loading overlay -->
......
This diff is collapsed. Click to expand it.
......@@ -28,6 +28,11 @@
}
onMount(async () => {
// Forzar recálculo de layout para navegación cliente
requestAnimationFrame(() => {
document.body.offsetHeight; // Force reflow
});
const { data, error } = await supabase
.schema('ppto')
.from('clas_institucional')
......@@ -307,7 +312,7 @@
</button>
</div>
<div class="max-w-screen-xl mx-auto flex px-4">
<div class="clasificador-layout max-w-screen-xl mx-auto flex px-4" style="display: flex !important; flex-direction: row;">
<!-- Sidebar izquierda: Áreas -->
<!-- En móvil: overlay, en desktop: sidebar fijo -->
{#if sidebarOpen}
......@@ -611,4 +616,33 @@
background-color: transparent;
}
}
/* Layout principal - fallback nativo */
.clasificador-layout {
display: flex !important;
flex-direction: row !important;
}
/* Sidebar - fallback para responsive */
.sidebar-left {
position: fixed;
transform: translateX(-100%);
}
@media (min-width: 1024px) {
.sidebar-left {
position: relative !important;
transform: translateX(0) !important;
flex-shrink: 0;
width: 16rem;
z-index: auto !important;
box-shadow: none !important;
}
}
@media (max-width: 1023px) {
.clasificador-layout {
display: block !important;
}
}
</style>
......
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
import { supabase } from '$lib/supabase';
import { error } from '@sveltejs/kit';
// Funciones para derivar jerarquía del código finfun
// Jerarquía: Finalidad (1 dígito) → Grupo Función (2 dígitos) → Función (3+ dígitos)
function getFinalidadCode(finfun) {
return finfun.charAt(0);
}
function getGrpFuncionCode(finfun) {
return finfun.substring(0, 2);
}
function getNivel(finfun) {
if (finfun.length === 1) return 'finalidad';
if (finfun.length === 2) return 'grpfuncion';
return 'funcion';
}
export async function load({ params }) {
console.time('[SERVER] Total load finfun');
const { codigo } = params;
console.time('[SERVER] Query clas_finfun');
const { data, error: dbError } = await supabase
.schema('ppto')
.from('clas_finfun')
.select('*')
.eq('finfun', codigo);
console.timeEnd('[SERVER] Query clas_finfun');
if (dbError || !data || data.length === 0) {
throw error(404, 'Finalidad/Función no encontrada');
}
// Tomar el primer resultado
const finfun = data[0];
const nivel = finfun.nivel || getNivel(codigo);
// Obtener jerarquía (padres e hijos)
let padres = [];
let hijos = [];
console.time('[SERVER] Query padres');
// Buscar padres según nivel
if (nivel === 'funcion') {
// Padres: finalidad y grupo función
const finalidadCode = getFinalidadCode(codigo);
const grpFuncionCode = getGrpFuncionCode(codigo);
const { data: padresData } = await supabase
.schema('ppto')
.from('clas_finfun')
.select('*')
.in('finfun', [finalidadCode, grpFuncionCode])
.order('finfun');
if (padresData) padres = padresData;
} else if (nivel === 'grpfuncion') {
// Padre: finalidad
const finalidadCode = getFinalidadCode(codigo);
const { data: padresData } = await supabase
.schema('ppto')
.from('clas_finfun')
.select('*')
.eq('finfun', finalidadCode);
if (padresData) padres = padresData;
}
// finalidad no tiene padres
console.timeEnd('[SERVER] Query padres');
console.time('[SERVER] Query hijos');
// Buscar hijos según nivel
if (nivel === 'finalidad') {
// Hijos: grupos de función que empiecen con el mismo dígito
const { data: hijosData } = await supabase
.schema('ppto')
.from('clas_finfun')
.select('*')
.eq('nivel', 'grpfuncion')
.like('finfun', `${codigo}%`)
.order('finfun');
hijos = hijosData || [];
} else if (nivel === 'grpfuncion') {
// Hijos: funciones que empiecen con los mismos 2 dígitos
const { data: hijosData } = await supabase
.schema('ppto')
.from('clas_finfun')
.select('*')
.eq('nivel', 'funcion')
.like('finfun', `${codigo}%`)
.order('finfun');
hijos = hijosData || [];
}
// funcion no tiene hijos
console.timeEnd('[SERVER] Query hijos');
console.timeEnd('[SERVER] Total load finfun');
return {
finfun,
padres,
hijos
};
}
This diff could not be displayed because it is too large.
import { supabase } from '$lib/supabase';
import { error } from '@sveltejs/kit';
// Funciones para derivar jerarquía del código objeto
function getGrupoCode(objeto) {
return objeto.charAt(0) + '0000';
}
function getSubgrupoCode(objeto) {
return objeto.substring(0, 2) + '000';
}
function getPartidaCode(objeto) {
return objeto.substring(0, 3) + '00';
}
function getNivel(objeto) {
if (objeto.endsWith('0000')) return 'grupo';
if (objeto.endsWith('000')) return 'subgrupo';
if (objeto.endsWith('00')) return 'partida';
return 'subpartida';
}
export async function load({ params }) {
console.time('[SERVER] Total load objeto');
const { codigo } = params;
......@@ -17,95 +37,102 @@ export async function load({ params }) {
throw error(404, 'Objeto no encontrado');
}
// Tomar el primer resultado (puede haber duplicados)
// Tomar el primer resultado
const objeto = data[0];
const nivel = objeto.nivel || getNivel(codigo);
// Obtener jerarquía (padres e hijos)
let padres = [];
let hijos = [];
console.time('[SERVER] Query padres');
// Buscar padre según nivel
if (objeto.nivel === 'subpartida') {
// Padre es partida
const { data: padreData } = await supabase
// Buscar padres según nivel
if (nivel === 'subpartida') {
// Padres: grupo, subgrupo, partida
const grupoCode = getGrupoCode(codigo);
const subgrupoCode = getSubgrupoCode(codigo);
const partidaCode = getPartidaCode(codigo);
const { data: padresData } = await supabase
.schema('ppto')
.from('clas_objetos')
.select('*')
.eq('nivel', 'partida')
.eq('grupo', objeto.grupo)
.eq('subgrupo', objeto.subgrupo)
.eq('partida', objeto.partida);
if (padreData?.length) padres.push(padreData[0]);
}
.in('objeto', [grupoCode, subgrupoCode, partidaCode])
.order('objeto');
if (objeto.nivel === 'partida' || objeto.nivel === 'subpartida') {
// Padre es subgrupo
const { data: padreData } = await supabase
if (padresData) padres = padresData;
} else if (nivel === 'partida') {
// Padres: grupo, subgrupo
const grupoCode = getGrupoCode(codigo);
const subgrupoCode = getSubgrupoCode(codigo);
const { data: padresData } = await supabase
.schema('ppto')
.from('clas_objetos')
.select('*')
.eq('nivel', 'subgrupo')
.eq('grupo', objeto.grupo)
.eq('subgrupo', objeto.subgrupo);
if (padreData?.length) padres.unshift(padreData[0]);
}
.in('objeto', [grupoCode, subgrupoCode])
.order('objeto');
if (padresData) padres = padresData;
} else if (nivel === 'subgrupo') {
// Padre: grupo
const grupoCode = getGrupoCode(codigo);
if (objeto.nivel !== 'grupo') {
// Padre es grupo
const grupoCodigo = objeto.grupo + '0000';
const { data: padreData } = await supabase
const { data: padresData } = await supabase
.schema('ppto')
.from('clas_objetos')
.select('*')
.eq('nivel', 'grupo')
.eq('objeto', grupoCodigo);
if (padreData?.length) padres.unshift(padreData[0]);
.eq('objeto', grupoCode);
if (padresData) padres = padresData;
}
// grupo no tiene padres
console.timeEnd('[SERVER] Query padres');
console.time('[SERVER] Query hijos');
// Buscar hijos según nivel
if (objeto.nivel === 'grupo') {
if (nivel === 'grupo') {
// Hijos: subgrupos que empiecen con el mismo dígito
const prefix = codigo.charAt(0);
const { data: hijosData } = await supabase
.schema('ppto')
.from('clas_objetos')
.select('*')
.eq('nivel', 'subgrupo')
.eq('grupo', objeto.grupo)
.like('objeto', `${prefix}%`)
.order('objeto');
hijos = hijosData?.reduce((acc, item) => {
if (!acc.find(h => h.objeto === item.objeto)) acc.push(item);
return acc;
}, []) || [];
} else if (objeto.nivel === 'subgrupo') {
hijos = hijosData || [];
} else if (nivel === 'subgrupo') {
// Hijos: partidas que empiecen con los mismos 2 dígitos
const prefix = codigo.substring(0, 2);
const { data: hijosData } = await supabase
.schema('ppto')
.from('clas_objetos')
.select('*')
.eq('nivel', 'partida')
.eq('grupo', objeto.grupo)
.eq('subgrupo', objeto.subgrupo)
.like('objeto', `${prefix}%`)
.order('objeto');
hijos = hijosData?.reduce((acc, item) => {
if (!acc.find(h => h.objeto === item.objeto)) acc.push(item);
return acc;
}, []) || [];
} else if (objeto.nivel === 'partida') {
hijos = hijosData || [];
} else if (nivel === 'partida') {
// Hijos: subpartidas que empiecen con los mismos 3 dígitos
const prefix = codigo.substring(0, 3);
const { data: hijosData } = await supabase
.schema('ppto')
.from('clas_objetos')
.select('*')
.eq('nivel', 'subpartida')
.eq('grupo', objeto.grupo)
.eq('subgrupo', objeto.subgrupo)
.eq('partida', objeto.partida)
.like('objeto', `${prefix}%`)
.order('objeto');
hijos = hijosData?.reduce((acc, item) => {
if (!acc.find(h => h.objeto === item.objeto)) acc.push(item);
return acc;
}, []) || [];
hijos = hijosData || [];
}
// subpartida no tiene hijos
console.timeEnd('[SERVER] Query hijos');
console.timeEnd('[SERVER] Total load objeto');
......
......@@ -357,12 +357,16 @@
// VALORES DERIVADOS
// ══════════════════════════════════════════════════════════════
// Constante de población para cálculos per cápita
const POBLACION = 12000000;
// Datos para el gráfico (monto o per_capita según preferencia)
// Nota: vista_objeto_entidad solo tiene 'devengado', no 'per_capita'
let gastoPerCapita = $derived(
datosAnuales.map(d => ({
año: d.gestion,
monto: selectedEntity ? d.monto : d.total,
perCapita: d.per_capita
monto: selectedEntity ? (d.monto ?? d.devengado) : d.total,
perCapita: d.per_capita ?? (d.devengado ? d.devengado / POBLACION : 0)
}))
);
......@@ -991,7 +995,7 @@
<div class="page" class:mounted>
<!-- Link a clasificadores -->
<nav class="page-nav">
<a href="/clasificadores/objeto-gasto" class="back-link">
<a href="/clasificadores/objeto-gasto" class="back-link" data-sveltekit-reload>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M19 12H5M12 19l-7-7 7-7"/>
</svg>
......@@ -1149,7 +1153,13 @@
<div class="chart-header">
<span class="chart-title">Bs/habitante <span class="chart-period">· {displayPeriodo}</span></span>
</div>
{#if loading}
<div class="chart-loading" style="height: 280px;">
<span>Cargando...</span>
</div>
{:else}
<BarChart data={gastoPerCapita} bind:hoveredYear height={280} fill={!!selectedEntity} />
{/if}
<!-- KPIs abajo -->
<div class="kpis">
......@@ -2581,6 +2591,17 @@
letter-spacing: 0;
}
.chart-loading {
display: flex;
align-items: center;
justify-content: center;
color: var(--theme-texto);
opacity: 0.5;
font-size: 0.875rem;
border: 1px dashed var(--theme-borde);
border-radius: 8px;
}
.chart-controls {
display: flex;
align-items: center;
......
<script>
let institucion = "Ministerio de Economía y Finanzas Públicas";
let pais = "Bolivia";
</script>
<svelte:head>
<title>Sobre | Presupuesto Público</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link href="https://fonts.googleapis.com/css2?family=Source+Serif+4:ital,opsz,wght@0,8..60,300;0,8..60,400;0,8..60,600;1,8..60,300;1,8..60,400&family=JetBrains+Mono:wght@400&display=swap" rel="stylesheet" />
</svelte:head>
<div class="manifesto-outer">
<div class="manifesto-inner">
<div class="manifesto-label">
Presentación &nbsp;·&nbsp; Liberación de la base histórica
</div>
<div class="manifesto-fecha">
A fines de 2025 decidimos abrir los datos históricos<br>
de presupuesto del Estado boliviano.
</div>
<div class="manifesto-cuerpo">
<p class="p1">
Cada año el Estado recibe y gasta dinero a tu nombre. ¿Sabes de dónde viene,
en qué se usa y qué se logra con él? Esa información es tuya. Siempre lo fue,
pero durante décadas permaneció encerrada en archivos PDF, en sistemas que pocos
saben usar y en las paredes de un ministerio.
</p>
<div class="manifesto-hoy">Hoy cambia eso.</div>
<p class="p3">
Aquí puedes explorar más de veinte años de ejecución presupuestaria, con
actualización semanal. Puedes ver qué hizo el Estado en tu región, en tu sector
y en el año que te interesa. Puedes comparar, cuestionar y entender. Y si
encuentras algo que no cuadra o que vale la pena contar, puedes hacerlo tuyo.
</p>
<hr class="manifesto-regla" />
<p class="p4">
No publicamos esto para cumplir un trámite de transparencia. Lo hacemos porque
creemos que un Estado que trabaja de espaldas a su sociedad se vuelve impune,
y una sociedad que no entiende al Estado se desconecta de él.
</p>
<blockquote class="manifesto-apuesta">
Un presupuesto abierto es una apuesta: que la confianza se construye con
<strong>información</strong> y no con promesas.
</blockquote>
</div>
<div class="manifesto-cierre">
<p class="manifesto-cierre-texto">Abrimos para no cerrar.</p>
<p class="manifesto-cierre-sub">{institucion} &nbsp;·&nbsp; {pais}</p>
</div>
</div>
</div>
<style>
.manifesto-outer {
min-height: 100vh;
background: var(--manifesto-bg);
overflow: hidden;
}
.manifesto-inner {
max-width: 640px;
margin: 0 auto;
padding: 72px 48px 88px;
font-family: 'Source Serif 4', Georgia, 'Times New Roman', serif;
color: var(--manifesto-text);
}
/* === etiqueta superior === */
.manifesto-label {
font-family: 'JetBrains Mono', 'Courier New', monospace;
font-size: 10px;
letter-spacing: 0.18em;
color: var(--manifesto-muted);
text-transform: uppercase;
margin-bottom: 56px;
display: flex;
align-items: center;
gap: 12px;
opacity: 0;
animation: fadeUp 0.6s ease forwards;
}
.manifesto-label::after {
content: '';
flex: 1;
height: 0.5px;
background: var(--manifesto-rule);
}
/* === fecha === */
.manifesto-fecha {
font-family: 'Source Serif 4', Georgia, serif;
font-style: italic;
font-weight: 300;
font-size: 14px;
color: var(--manifesto-subtle);
letter-spacing: 0.02em;
margin-bottom: 40px;
opacity: 0;
animation: fadeUp 0.6s ease 0.1s forwards;
}
/* === cuerpo === */
.manifesto-cuerpo {
font-size: 18.5px;
line-height: 1.78;
font-weight: 300;
color: var(--manifesto-body);
letter-spacing: 0.01em;
}
.p1 {
opacity: 0;
animation: fadeUp 0.7s ease 0.2s forwards;
margin-bottom: 28px;
margin-top: 0;
}
.manifesto-hoy {
opacity: 0;
animation: fadeUp 0.7s ease 0.5s forwards;
margin: 44px 0;
font-size: 26px;
font-weight: 400;
color: var(--manifesto-text);
letter-spacing: -0.01em;
line-height: 1.3;
}
.p3 {
opacity: 0;
animation: fadeUp 0.7s ease 0.65s forwards;
margin-bottom: 28px;
margin-top: 0;
}
.manifesto-regla {
opacity: 0;
animation: fadeUp 0.6s ease 0.75s forwards;
border: none;
border-top: 0.5px solid var(--manifesto-rule);
margin: 44px 0;
}
.p4 {
opacity: 0;
animation: fadeUp 0.7s ease 0.85s forwards;
margin-bottom: 0;
margin-top: 0;
}
.manifesto-apuesta {
opacity: 0;
animation: fadeUp 0.7s ease 0.95s forwards;
margin: 44px 0;
padding: 0 0 0 24px;
border-left: 1.5px solid var(--manifesto-quote-border);
font-style: italic;
font-weight: 300;
font-size: 20px;
line-height: 1.65;
color: var(--manifesto-quote);
}
.manifesto-apuesta strong {
font-style: normal;
font-weight: 400;
color: var(--manifesto-body);
}
/* === cierre === */
.manifesto-cierre {
opacity: 0;
animation: fadeUp 0.8s ease 1.1s forwards;
margin-top: 56px;
padding-top: 36px;
border-top: 0.5px solid var(--manifesto-rule);
}
.manifesto-cierre-texto {
font-size: 36px;
font-weight: 400;
color: var(--color-gold);
letter-spacing: -0.02em;
line-height: 1.1;
margin: 0;
}
.manifesto-cierre-sub {
font-family: 'JetBrains Mono', 'Courier New', monospace;
font-size: 10px;
letter-spacing: 0.16em;
color: var(--manifesto-muted);
text-transform: uppercase;
margin-top: 20px;
margin-bottom: 0;
}
/* === animación === */
@keyframes -global-fadeUp {
from {
opacity: 0;
transform: translateY(14px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* === MODO OSCURO (default) === */
.manifesto-outer {
--manifesto-bg: #0C0C0C;
--manifesto-text: #EDE9E1;
--manifesto-body: #D9D4CB;
--manifesto-muted: #5A5650;
--manifesto-subtle: #6B6660;
--manifesto-rule: #2A2822;
--manifesto-quote: #C4BFB6;
--manifesto-quote-border: #3A3530;
}
/* === MODO CLARO === */
:global(html:not(.dark)) .manifesto-outer {
--manifesto-bg: #FAFAF8;
--manifesto-text: #1a1a1a;
--manifesto-body: #3a3a3a;
--manifesto-muted: #8a8a8a;
--manifesto-subtle: #6a6a6a;
--manifesto-rule: #e0e0e0;
--manifesto-quote: #4a4a4a;
--manifesto-quote-border: #d0d0d0;
}
/* === responsive === */
@media (max-width: 600px) {
.manifesto-inner {
padding: 48px 28px 64px;
}
.manifesto-cuerpo {
font-size: 17px;
}
.manifesto-hoy {
font-size: 22px;
}
.manifesto-apuesta {
font-size: 18px;
}
.manifesto-cierre-texto {
font-size: 28px;
}
}
</style>
This diff is collapsed. Click to expand it.
gestion,objeto,devengado
2025,11100,7010195151.280003
2025,11210,5812831318.110004
2025,11220,3960643633.5099993
2025,11310,477377380.7399999
2025,11321,611358929.5699998
2025,11322,344284069.2800001
2025,11323,8492765.11
2025,11324,119472850.73000005
2025,11331,3741918.29
2025,11332,222831.86
2025,11339,991911009.5900005
2025,11400,2598265843.6899953
2025,11510,115618445.64
2025,11520,3684768.92
2025,11600,347937539.3
2025,11700,16012422722.88999
2025,11810,1897633.19
2025,11820,5236920.13
2025,11910,418427037.15000004
2025,11920,49552831.83999999
2025,11930,141140855.32999998
2025,11940,4603197.24
2025,12100,3204535824.6000023
2025,13110,3901953807.7400007
2025,13120,619414677.6200007
2025,13131,1362025647.0700052
2025,13132,16517337.85
2025,13200,780321050.7899984
2025,14100,1139341529.1000001
2025,15100,0
2025,15200,0
2025,15300,0
2025,15400,4691146.74
2025,21100,18994647.069999997
2025,21200,1185053965.7300012
2025,21300,187133399.53000006
2025,21400,53465476.169999935
2025,21500,32025660.46999999
2025,21600,194624988.81999987
2025,22110,91267157.03
2025,22120,18054096.22
2025,22210,150388054.74000007
2025,22220,34629325.89
2025,22300,1959574229.3100035
2025,22400,2341473.77
2025,22500,701307983.6299998
2025,22600,129774059.36000001
2025,23100,177818037.08
2025,23200,705817678.630001
2025,23300,267332.29
2025,23400,102877216.77999996
2025,24110,347305124.4300007
2025,24120,495875030.0700008
2025,24130,4734068.450000001
2025,24200,186441122.7200001
2025,24300,2640074895.480003
2025,25120,968577449.3700005
2025,25130,48331129.9
2025,25210,124279655.01999998
2025,25220,1788157637.100003
2025,25230,38750297.28
2025,25300,561398734.3999997
2025,25400,878953716.1200004
2025,25500,306587141.63999945
2025,25600,313083444.33000004
2025,25700,6976563.890000001
2025,25810,119466843.19999999
2025,25820,224542509.50000012
2025,25900,237665083.13999984
2025,26200,15655261.7
2025,26300,34604066.51
2025,26610,123238100.68000004
2025,26620,65803840.900000006
2025,26630,5856917.720000001
2025,26640,12578208.32
2025,26700,14848951.62
2025,26910,663385.5700000003
2025,26920,235250
2025,26930,8978735.689999998
2025,26940,66563836.099999994
2025,26990,1503662082.0000036
2025,27110,1825790733.1499996
2025,27120,364659427.3
2025,31110,542443427.4200002
2025,31120,216381918.43999982
2025,31130,664933780.7900007
2025,31140,546467610.53
2025,31150,58934973.47
2025,31200,33094567.35000001
2025,31300,2447995585.1100035
2025,32100,143276681.52000007
2025,32200,132315063.70000002
2025,32300,5561928.509999998
2025,32400,159320
2025,32500,1136682.8700000003
2025,33100,24196989.06
2025,33200,44402437.73000002
2025,33300,140159482.78000006
2025,33400,44503112.73000003
2025,34110,1233206780.699983
2025,34120,22281523272.74
2025,34130,210854679.98
2025,34200,2971103666.2100005
2025,34300,107990911.47999994
2025,34400,7452099.93
2025,34500,643334205.2399997
2025,34600,256516535.3800006
2025,34700,4499642859.289998
2025,34800,31720752.09
2025,34900,3886088.93
2025,39100,110497000.54
2025,39200,48738034.710000016
2025,39300,4137761.2899999996
2025,39400,88709616.89000002
2025,39500,308909532.08000034
2025,39600,13789089.190000001
2025,39700,260527546.88999993
2025,39800,711478491.3899997
2025,39911,25451599.84
2025,39912,229716435.09
2025,39990,85810785.37999989
2025,41100,4018555.16
2025,41200,11738231.740000002
2025,41300,0
2025,42210,2475688.8800000004
2025,42220,5772449.3
2025,42230,3644887583.5699887
2025,42240,119884214.51000004
2025,42310,3262092590.7700067
2025,42320,162411814.5
2025,42400,748574159.99
2025,42500,368399418.97999996
2025,43110,68317124.34
2025,43120,223834030.73999995
2025,43200,145257372.71
2025,43310,18770602.6
2025,43320,7603011.960000001
2025,43330,290260838.97
2025,43340,4231983.279999999
2025,43400,398948895.01000005
2025,43500,328470832.01999986
2025,43600,51696765.46999999
2025,43700,169086594.95000005
2025,46110,125733899.86000003
2025,46120,74691013.75999999
2025,46210,57348534.56999999
2025,46220,9583654.360000001
2025,46310,30831570.299999986
2025,46320,2784494.7899999996
2025,49100,41480273
2025,49300,492862.94999999995
2025,49400,85275
2025,49900,660922.38
2025,51100,0
2025,51200,620015014.37
2025,51600,481042090
2025,51700,29014116.87
2025,53410,52050949.82
2025,53420,351090144.51
2025,53430,3378151.15
2025,53440,25716472.12
2025,54300,77289717.23
2025,54800,632675260.65
2025,55130,0
2025,56100,2147721911.8500004
2025,57100,836275.9
2025,57200,0
2025,61100,70227731033.1
2025,61200,3503973693.6899977
2025,61300,7686201.74
2025,61400,165316.78
2025,61600,4660620766.32
2025,61700,3610015023.1800003
2025,61800,578767.5200000001
2025,61900,560031.13
2025,62100,106585021.14999999
2025,62200,38756619.58
2025,62300,3500499.439999999
2025,62400,405.55
2025,62600,4871024753.510001
2025,62700,3091378138.92
2025,62800,66184743.53000001
2025,62900,236631.16
2025,63100,6480763.82
2025,63200,0
2025,63300,100360.43
2025,63400,462857.7
2025,63500,35529.2
2025,63600,0
2025,63700,2369055.65
2025,63800,0
2025,63900,1087614.1800000002
2025,64100,0
2025,64200,0
2025,65100,586447491.8299999
2025,65210,398987753.5799999
2025,65220,240387686.01999998
2025,65230,132581381.01
2025,65240,0
2025,65300,2444818356.6
2025,65400,644864385.6300001
2025,65500,797927308.79
2025,65600,622100032.9
2025,65800,6897016.98
2025,65900,66705420.480000004
2025,66100,403828926.02000004
2025,66210,2597435653.4000015
2025,66220,1829365908.85
2025,66230,646299428.4800001
2025,66240,80232240.73
2025,66250,209697820.00000003
2025,66300,239921321.82000005
2025,66400,134798065.3299999
2025,66900,1844595842.08
2025,67100,23208064.040000003
2025,68200,230881944.88000008
2025,69100,29581681.77
2025,69200,203500678.33
2025,71100,7630007944.97
2025,71210,2392898.6
2025,71220,322149243.09
2025,71230,4408152.6
2025,71300,26644234.430000003
2025,71610,1883091507.0600004
2025,71630,343800974.93
2025,71700,65080225.449999996
2025,71800,4478984170.690001
2025,72200,2046897058.8299997
2025,72420,316010589.22
2025,72520,7584584602.65001
2025,73100,2664574022.970007
2025,73200,24681254941.659996
2025,73410,4845222515.11
2025,73420,315258596.03999996
2025,73430,4314037.58
2025,73440,1652127
2025,73700,15015040108.359999
2025,73820,1364681.67
2025,75110,0
2025,75120,683772757.9200002
2025,75211,469127620.05
2025,75212,50308009.760000005
2025,75221,11649919.909999998
2025,75222,305831148.79999995
2025,75320,0
2025,77100,346626643.30999994
2025,77200,1783047515.49
2025,77410,154404059.40000007
2025,77440,13005182.149999999
2025,77520,34542653.12
2025,77530,1359464924.2300012
2025,77700,0
2025,77820,42931153.11
2025,78100,346333.4
2025,79100,20183063.04
2025,79200,74336.66
2025,79310,69600
2025,81100,189467352
2025,81200,1327189189.5399992
2025,81300,551652107.6699998
2025,81400,299412884.65
2025,81500,2442434
2025,81600,395569
2025,81950,39137
2025,81960,32790665.14
2025,81990,2007174694.16
2025,82100,879007438.5400001
2025,83110,29113391.8
2025,83120,4240482.35
2025,83210,351070.72
2025,83220,50460
2025,84100,65887753.279999994
2025,84230,394819507.59
2025,84240,723835763.5600002
2025,84250,65803251.14
2025,84900,8503359.91
2025,85100,94254502.63999999
2025,85200,10478575.300000003
2025,85400,6221225.630000003
2025,85500,3581209.3799999994
2025,85900,20640115.529999997
2025,86100,29028844.980000008
2025,91200,512102826.06
2025,92100,0
2025,94100,11526054.86
2025,94200,7279202.82
2025,94300,1551638.3499999999
2025,95100,291453722.56
2025,96100,5934986991.019999
2025,96200,614954091.44
2025,96900,2947311655.2299995
2025,97100,70099589.35000001
2025,98300,125000000
2025,99100,0
2025,99200,0
This diff is collapsed. Click to expand it.