Rafael Lopez

final

Showing 43 changed files with 111 additions and 312 deletions
......@@ -4,7 +4,7 @@
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "vite dev",
"dev": "NODE_TLS_REJECT_UNAUTHORIZED=0 vite dev",
"build": "vite build",
"preview": "vite preview",
"prepare": "svelte-kit sync || echo ''"
......
// ═══════════════════════════════════════════════════════════════
// CONFIGURACIÓN CENTRAL DE API
// Cambiar la IP aquí actualiza todas las llamadas del proyecto
// ═══════════════════════════════════════════════════════════════
export const API_HOST = 'https://abierto.economiayfinanzas.gob.bo/presupuesto';
export const DOWNLOAD_BASE = 'https://abierto.economiayfinanzas.gob.bo/presupuesto/descargas/';
This diff is collapsed. Click to expand it.
import { error } from '@sveltejs/kit';
const API_BASE = 'http://136.112.29.74/api/acteco';
import { API_HOST } from '$lib/api.js';
const API_BASE = API_HOST + '/api/acteco';
// Jerarquía acteco: códigos con punto "1" → "1.1" → "1.1.1"
// Todos tienen nivel "actividad" en la API, derivamos la profundidad del código
......
const API_BASE = 'http://136.112.29.74/api/acteco/clasificador';
import { API_HOST } from '$lib/api.js';
const API_BASE = API_HOST + '/api/acteco/clasificador';
export async function GET() {
try {
......
const API_BASE = 'http://136.112.29.74/api/acteco';
import { API_HOST } from '$lib/api.js';
const API_BASE = API_HOST + '/api/acteco';
export async function GET({ url }) {
const codigo = url.searchParams.get('codigo') || '';
......
const API_BASE = 'http://136.112.29.74/api/acteco';
import { API_HOST } from '$lib/api.js';
const API_BASE = API_HOST + '/api/acteco';
let cachedClasificador = null;
let cachedEstados = null;
......
const API_BASE = 'http://136.112.29.74/api/acteco';
import { API_HOST } from '$lib/api.js';
const API_BASE = API_HOST + '/api/acteco';
export async function GET({ url }) {
const codigo = url.searchParams.get('codigo') || '';
......
const API_BASE = 'http://136.112.29.74/api/diario';
import { API_HOST } from '$lib/api.js';
const API_BASE = API_HOST + '/api/diario';
export async function GET({ url }) {
const tipo = url.searchParams.get('tipo') || 'status';
......
const API_BASE = 'http://136.112.29.74/api/entidad/clasificador';
import { API_HOST } from '$lib/api.js';
const API_BASE = API_HOST + '/api/entidad/clasificador';
export async function GET() {
try {
......
const API_BASE = 'http://136.112.29.74/api/entidad';
import { API_HOST } from '$lib/api.js';
const API_BASE = API_HOST + '/api/entidad';
export async function GET({ url }) {
const codigo = url.searchParams.get('codigo') || '';
......
const API_BASE = 'http://136.112.29.74/api/finfun/clasificador';
import { API_HOST } from '$lib/api.js';
const API_BASE = API_HOST + '/api/finfun/clasificador';
export async function GET() {
try {
const response = await fetch(API_BASE);
if (!response.ok) {
return new Response(JSON.stringify({ error: `API error: ${response.status}` }), {
status: response.status, headers: { 'Content-Type': 'application/json' }
});
}
if (!response.ok) throw new Error();
const data = await response.json();
return new Response(JSON.stringify(data), {
headers: { 'Content-Type': 'application/json' }
......
const API_BASE = 'http://136.112.29.74/api/finfun';
import { API_HOST } from '$lib/api.js';
const API_BASE = API_HOST + '/api/finfun';
export async function GET({ url }) {
const codigo = url.searchParams.get('codigo') || '';
......
const API_BASE = 'http://136.112.29.74/api/finfun';
import { API_HOST } from '$lib/api.js';
const API_BASE = API_HOST + '/api/finfun';
// Cache en memoria
let cachedClasificador = null;
let cachedEstados = null;
let cachedTimestamp = 0;
const CACHE_TTL = 5 * 60 * 1000;
function getParent(finfun) {
const idx = finfun.lastIndexOf('.');
if (idx === -1) return null;
return finfun.substring(0, idx);
function getParent(finfun, nivel) {
const code = String(finfun);
if (nivel === 'finalidad') return null;
if (nivel === 'grp_funcion') {
return code.startsWith('10') ? '10' : code.charAt(0);
}
if (nivel === 'funcion') {
if (code.startsWith('10')) return code.substring(0, 3);
return code.substring(0, 2);
}
return null;
}
async function loadAndCacheAll() {
......@@ -58,7 +67,7 @@ export async function GET({ url }) {
nivel: item.nivel,
finfun: item.finfun,
desc_finfun: item.desc_finfun,
parent: getParent(item.finfun),
parent: getParent(item.finfun, item.nivel),
devengado: yearData.total
});
}
......@@ -89,7 +98,7 @@ export async function GET({ url }) {
nivel: item.nivel,
finfun: item.finfun,
desc_finfun: item.desc_finfun,
parent: getParent(item.finfun),
parent: getParent(item.finfun, item.nivel),
devengado: yearData.monto
};
} catch { return null; }
......
const API_BASE = 'http://136.112.29.74/api/finfun';
import { API_HOST } from '$lib/api.js';
const API_BASE = API_HOST + '/api/finfun';
export async function GET({ url }) {
const codigo = url.searchParams.get('codigo') || '';
......
const API_BASE = 'http://136.112.29.74/api/objeto/clasificador';
import { API_HOST } from '$lib/api.js';
const API_BASE = API_HOST + '/api/objeto/clasificador';
export async function GET() {
try {
......
const API_BASE = 'http://136.112.29.74/api/objeto';
import { API_HOST } from '$lib/api.js';
const API_BASE = API_HOST + '/api/objeto';
export async function GET({ url }) {
const codigo = url.searchParams.get('codigo') || '';
......
const API_BASE = 'http://136.112.29.74/api/entidad/clasificador';
import { API_HOST } from '$lib/api.js';
const API_BASE = API_HOST + '/api/entidad/clasificador';
export async function GET() {
try {
......
const API_BASE = 'http://136.112.29.74/api/objeto';
import { API_HOST } from '$lib/api.js';
const API_BASE = API_HOST + '/api/objeto';
// Cache en memoria del servidor
let cachedClasificador = null;
......
const API_BASE = 'http://136.112.29.74/api/objeto';
import { API_HOST } from '$lib/api.js';
const API_BASE = API_HOST + '/api/objeto';
export async function GET({ url }) {
const codigo = url.searchParams.get('codigo') || '';
......
const API_BASE = 'http://136.112.29.74/api/organismo/clasificador';
import { API_HOST } from '$lib/api.js';
const API_BASE = API_HOST + '/api/organismo/clasificador';
export async function GET() {
try {
......
const API_BASE = 'http://136.112.29.74/api/organismo';
import { API_HOST } from '$lib/api.js';
const API_BASE = API_HOST + '/api/organismo';
export async function GET({ url }) {
const codigo = url.searchParams.get('codigo') || '';
......
const API_BASE = 'http://136.112.29.74/api/organismo';
import { API_HOST } from '$lib/api.js';
const API_BASE = API_HOST + '/api/organismo';
let cachedClasificador = null;
let cachedEstados = null;
......
const API_BASE = 'http://136.112.29.74/api/programa_proyecto';
import { API_HOST } from '$lib/api.js';
const API_BASE = API_HOST + '/api/programa_proyecto';
export async function GET({ url }) {
const codigo = url.searchParams.get('codigo') || '';
......
const API_BASE = 'http://136.112.29.74/api/rubro/clasificador';
import { API_HOST } from '$lib/api.js';
const API_BASE = API_HOST + '/api/rubro/clasificador';
export async function GET() {
try {
......
const API_BASE = 'http://136.112.29.74/api/rubro';
import { API_HOST } from '$lib/api.js';
const API_BASE = API_HOST + '/api/rubro';
export async function GET({ url }) {
const codigo = url.searchParams.get('codigo') || '';
......
const API_BASE = 'http://136.112.29.74/api/rubro';
import { API_HOST } from '$lib/api.js';
const API_BASE = API_HOST + '/api/rubro';
let cachedClasificador = null;
let cachedEstados = null;
......
const API_BASE = 'http://136.112.29.74/api/rubro';
import { API_HOST } from '$lib/api.js';
const API_BASE = API_HOST + '/api/rubro';
export async function GET({ url }) {
const codigo = url.searchParams.get('codigo') || '';
......
// Proxy para evitar CORS
const API_BASE = 'http://136.112.29.74/api/search';
import { API_HOST } from '$lib/api.js';
const API_BASE = API_HOST + '/api/search';
export async function GET({ url }) {
const q = url.searchParams.get('q') || '';
......@@ -37,8 +38,8 @@ export async function GET({ url }) {
headers: { 'Content-Type': 'application/json' }
});
} catch (err) {
console.error('Proxy error:', err);
return new Response(JSON.stringify({ error: 'Failed to fetch from API' }), {
console.error('Proxy error:', err.message, 'URL:', apiUrl.toString());
return new Response(JSON.stringify({ error: 'Failed to fetch from API', detail: err.message, url: apiUrl.toString() }), {
status: 500,
headers: { 'Content-Type': 'application/json' }
});
......
const API_BASE = 'http://136.112.29.74/api/ubigeo/clasificador';
import { API_HOST } from '$lib/api.js';
const API_BASE = API_HOST + '/api/ubigeo/clasificador';
export async function GET() {
try {
......
const API_BASE = 'http://136.112.29.74/api/ubigeo';
import { API_HOST } from '$lib/api.js';
const API_BASE = API_HOST + '/api/ubigeo';
export async function GET({ url }) {
const codigo = url.searchParams.get('codigo') || '';
......
This diff is collapsed. Click to expand it.
<script>
import { supabase } from '$lib/supabase';
import { onMount, untrack } from 'svelte';
import { page } from '$app/stores';
import { goto } from '$app/navigation';
......@@ -154,16 +153,11 @@
treemapNodes = [];
try {
const entidadFiltro = selectedEntity?.entidad ?? 0;
const { data, error: dbError } = await supabase
.schema('ppto')
.from('treemap_finfun')
.select('gestion, nivel, finfun, desc_funcion, parent, devengado')
.eq('gestion', selectedYear)
.eq('entidad', entidadFiltro)
.gt('devengado', 0);
if (dbError) {
console.error('Error fetching treemap data:', dbError.message);
const res = await fetch(`/api/finfun-treemap?gestion=${selectedYear}&entidad=${entidadFiltro}`);
const data = await res.json();
if (data.error) {
console.error('Error fetching treemap data:', data.error);
return;
}
......@@ -171,7 +165,7 @@
gestion: d.gestion,
nivel: d.nivel,
finfun: d.finfun,
desc_finfun: d.desc_funcion,
desc_finfun: d.desc_finfun,
parent: d.parent,
devengado: d.devengado || 0
}));
......@@ -186,15 +180,11 @@
async function loadEntitiesForYear(year) {
if (!year) return;
try {
const { data, error: dbError } = await supabase
.schema('ppto')
.from('entidades_treemap')
.select('entidad, desc_entidad, sigla_entidad')
.eq('gestion', year)
.order('desc_entidad');
if (dbError) {
console.error('Error loading entities:', dbError.message);
const res = await fetch('/api/objeto-treemap-entidades');
const data = await res.json();
if (data.error) {
console.error('Error loading entities:', data.error);
return;
}
entities = data || [];
......@@ -580,13 +570,10 @@
document.body.offsetHeight; // Force reflow
});
const { data, error } = await supabase
.schema('ppto')
.from('clas_finfun')
.select('*')
.order('finfun');
const clasRes = await fetch('/api/finfun-clasificador');
const data = await clasRes.json();
if (!error && data) {
if (Array.isArray(data)) {
allItems = data;
// Extraer finalidades únicas (nivel más agregado)
......@@ -842,55 +829,6 @@
Jerarquía: Finalidad → Grupo Función → Función
</p>
<!-- Toggle de modos: Lista, Mapa, Comparar + Botón filtros móvil -->
<div class="flex items-center gap-3">
<div class="header-modes">
<button
class="header-mode-btn"
class:active={viewMode === 'lista'}
onclick={() => setMode('lista')}
>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16" />
</svg>
Lista
</button>
<button
class="header-mode-btn"
class:active={viewMode === 'mapa'}
onclick={() => setMode('mapa')}
>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 5a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM14 5a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1V5zM4 15a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1H5a1 1 0 01-1-1v-4zM14 15a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1v-4z" />
</svg>
Mapa
</button>
<button
class="header-mode-btn"
class:active={viewMode === 'comparar'}
onclick={() => setMode('comparar')}
>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
</svg>
Comparar
</button>
</div>
<!-- Botón filtros para móvil en modo mapa -->
{#if viewMode === 'mapa'}
<button
class="lg:hidden flex items-center gap-2 px-3 py-1.5 rounded-lg text-sm"
style="background-color: var(--theme-fill); color: var(--theme-titulo);"
onclick={() => mapaSidebarOpen = !mapaSidebarOpen}
>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4" />
</svg>
Filtros
</button>
{/if}
</div>
</div>
</div>
</header>
......
......@@ -1441,52 +1441,6 @@
<div class="mt-2">
<div class="flex flex-col md:flex-row gap-4 md:items-center md:justify-between">
<div class="flex flex-col sm:flex-row sm:items-center gap-3 sm:gap-4">
{#if viewMode === 'lista'}
<div class="header-modes">
<button
class="header-mode-btn active"
onclick={() => setMode('lista')}
>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16" />
</svg>
Lista
</button>
<button
class="header-mode-btn"
onclick={() => setMode('mapa')}
>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z" />
</svg>
Mapa
</button>
<button
class="header-mode-btn"
onclick={() => { setMode('comparar'); initCompareMode(); }}
>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
</svg>
Comparar
</button>
</div>
<!-- Guía inline (solo para lista) -->
<div class="hidden sm:block text-sm border-l pl-4" style="color: var(--theme-texto); border-color: var(--theme-borde);">
<span class="flex items-center gap-1.5">
Clic en
<span class="inline-flex items-center justify-center w-5 h-5 rounded" style="background-color: color-mix(in srgb, var(--theme-accent) 20%, transparent); color: var(--theme-accent);">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
</svg>
</span>
para ver el presupuesto histórico ·
<span class="underline decoration-dotted font-medium" style="color: var(--theme-accent);">N var.</span>
= número de cambios en la descripción oficial
</span>
</div>
{/if}
</div>
<div class="flex flex-wrap items-center gap-3 flex-shrink-0">
......
<script>
import { supabase } from '$lib/supabase';
import { onMount } from 'svelte';
import { page } from '$app/stores';
import { goto } from '$app/navigation';
......@@ -33,15 +32,12 @@
document.body.offsetHeight; // Force reflow
});
const { data, error } = await supabase
.schema('ppto')
.from('clas_institucional')
.select('*')
.order('desc_area')
.order('desc_entidad');
try {
const res = await fetch('/api/entidad-clasificador');
const data = await res.json();
if (!error && data) {
allEntidades = data;
if (Array.isArray(data)) {
allEntidades = data.sort((a, b) => (a.desc_area || '').localeCompare(b.desc_area || '') || (a.desc_entidad || '').localeCompare(b.desc_entidad || ''));
// Agrupar por área (segundo nivel)
const areasMap = {};
......@@ -74,6 +70,9 @@
selectedArea = areas[0];
}
}
} catch (err) {
console.error('Error loading clasificador:', err);
}
loading = false;
});
......@@ -238,49 +237,6 @@
<!-- Controles de visualización -->
<div class="mt-8">
<div class="flex flex-col sm:flex-row sm:items-center gap-3 sm:gap-4">
<!-- Toggle Vista -->
<div class="flex items-center gap-3">
<span class="text-sm font-medium" style="color: var(--theme-titulo);">Explorar como</span>
<nav class="inline-flex rounded-xl border-2 p-1 shadow-sm" style="border-color: var(--theme-titulo); background-color: var(--theme-surface);">
<a
href="?modo=lista"
class="px-4 py-2 text-sm font-semibold rounded-lg transition-all"
style="{viewMode === 'lista' ? `background-color: var(--theme-titulo); color: var(--theme-body);` : `color: var(--theme-texto);`}"
onclick={(e) => { e.preventDefault(); setMode('lista'); }}
>
Lista
</a>
<a
href="?modo=mapa"
class="px-4 py-2 text-sm font-semibold rounded-lg transition-all"
style="{viewMode === 'mapa' ? `background-color: var(--theme-titulo); color: var(--theme-body);` : `color: var(--theme-texto);`}"
onclick={(e) => { e.preventDefault(); setMode('mapa'); }}
>
Mapa
</a>
<a
href="?modo=comparar"
class="px-4 py-2 text-sm font-semibold rounded-lg transition-all"
style="{viewMode === 'comparar' ? `background-color: var(--theme-titulo); color: var(--theme-body);` : `color: var(--theme-texto);`}"
onclick={(e) => { e.preventDefault(); setMode('comparar'); }}
>
Comparar
</a>
</nav>
</div>
<!-- Guía inline -->
<div class="hidden sm:block text-sm border-l pl-4" style="color: var(--theme-texto); border-color: var(--theme-borde);">
{#if viewMode === 'lista'}
<span>Navega por áreas y subáreas</span>
{:else if viewMode === 'mapa'}
<span>Visualización geográfica del presupuesto</span>
{:else}
<span>Compara dos entidades o periodos</span>
{/if}
</div>
</div>
</div>
</div>
</header>
......
......@@ -1604,52 +1604,6 @@
<!-- Selector de modo + Guía inline (visible en sm+) -->
<div class="flex flex-col sm:flex-row sm:items-center gap-3 sm:gap-4">
<!-- Selector de modo (solo para lista, en mapa y comparar está en el sidebar) -->
{#if viewMode === 'lista'}
<div class="header-modes">
<button
class="header-mode-btn active"
onclick={() => setMode('lista')}
>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16" />
</svg>
Lista
</button>
<button
class="header-mode-btn"
onclick={() => setMode('mapa')}
>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z" />
</svg>
Mapa
</button>
<button
class="header-mode-btn"
onclick={() => { setMode('comparar'); initCompareMode(); }}
>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
</svg>
Comparar
</button>
</div>
<!-- Guía inline (solo para lista) -->
<div class="hidden sm:block text-sm border-l pl-4" style="color: var(--theme-texto); border-color: var(--theme-borde);">
<span class="flex items-center gap-1.5">
Clic en
<span class="inline-flex items-center justify-center w-5 h-5 rounded" style="background-color: color-mix(in srgb, var(--theme-accent) 20%, transparent); color: var(--theme-accent);">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
</svg>
</span>
para ver el presupuesto histórico ·
<span class="underline decoration-dotted font-medium" style="color: var(--theme-accent);">N var.</span>
= número de cambios en la descripción oficial
</span>
</div>
{/if}
</div>
<!-- Filtros (botón móvil para abrir sidebar en mapa y comparar) -->
......
......@@ -1453,52 +1453,6 @@
<div class="mt-2">
<div class="flex flex-col md:flex-row gap-4 md:items-center md:justify-between">
<div class="flex flex-col sm:flex-row sm:items-center gap-3 sm:gap-4">
{#if viewMode === 'lista'}
<div class="header-modes">
<button
class="header-mode-btn active"
onclick={() => setMode('lista')}
>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16" />
</svg>
Lista
</button>
<button
class="header-mode-btn"
onclick={() => setMode('mapa')}
>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z" />
</svg>
Mapa
</button>
<button
class="header-mode-btn"
onclick={() => { setMode('comparar'); initCompareMode(); }}
>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
</svg>
Comparar
</button>
</div>
<!-- Guía inline (solo para lista) -->
<div class="hidden sm:block text-sm border-l pl-4" style="color: var(--theme-texto); border-color: var(--theme-borde);">
<span class="flex items-center gap-1.5">
Clic en
<span class="inline-flex items-center justify-center w-5 h-5 rounded" style="background-color: color-mix(in srgb, var(--theme-accent) 20%, transparent); color: var(--theme-accent);">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
</svg>
</span>
para ver el presupuesto histórico ·
<span class="underline decoration-dotted font-medium" style="color: var(--theme-accent);">N var.</span>
= número de cambios en la descripción oficial
</span>
</div>
{/if}
</div>
<!-- Filtros (botón móvil para abrir sidebar en mapa y comparar) -->
......
This diff is collapsed. Click to expand it.
import { error } from '@sveltejs/kit';
const API_BASE = 'http://136.112.29.74/api/finfun';
import { API_HOST } from '$lib/api.js';
const API_BASE = API_HOST + '/api/finfun';
// Jerarquía finfun: Finalidad → Grupo Función → Función
// Códigos con punto: "1" (finalidad), "1.1" (grpfuncion), "1.1.1" (función)
......
import { error } from '@sveltejs/kit';
const API_BASE = 'http://136.112.29.74/api/objeto';
import { API_HOST } from '$lib/api.js';
const API_BASE = API_HOST + '/api/objeto';
export async function load({ params }) {
const { codigo } = params;
......
import { error } from '@sveltejs/kit';
const API_BASE = 'http://136.112.29.74/api/organismo';
import { API_HOST } from '$lib/api.js';
const API_BASE = API_HOST + '/api/organismo';
export async function load({ params }) {
const { codigo } = params;
......
import { error } from '@sveltejs/kit';
const API_BASE = 'http://136.112.29.74/api/rubro';
import { API_HOST } from '$lib/api.js';
const API_BASE = API_HOST + '/api/rubro';
export async function load({ params }) {
const { codigo } = params;
......
This diff is collapsed. Click to expand it.