Rafael Lopez

arreglo descargas2

<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Descargas presupuestarias</title>
<style>
:root {
--text: #1f1f1f;
--muted: #666;
--link: #0b57d0;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: sans-serif;
color: var(--text);
line-height: 1.5;
}
main {
max-width: 800px;
margin: 0 auto;
padding: 24px 16px 40px;
}
h1,
h2,
p {
margin-top: 0;
}
h1 {
margin-bottom: 16px;
}
h2 {
margin-bottom: 8px;
font-size: 1rem;
}
section {
margin-bottom: 20px;
}
label,
input,
button {
font: inherit;
}
input {
width: 100%;
padding: 8px 10px;
border: 1px solid #ccc;
background: #fff;
}
.field {
display: grid;
gap: 6px;
}
.radio-group {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.radio-option {
display: inline-flex;
align-items: center;
gap: 8px;
}
.radio-option input {
width: auto;
padding: 0;
border: 0;
}
.links,
.results {
padding: 0;
margin: 0;
list-style: none;
}
.links {
display: flex;
flex-wrap: wrap;
gap: 8px 12px;
}
.search-area {
position: relative;
}
.results {
position: absolute;
top: calc(100% + 4px);
left: 0;
right: 0;
z-index: 10;
max-height: 240px;
overflow-y: auto;
border-top: 1px solid #ddd;
border-bottom: 1px solid #ddd;
background: #fff;
}
.results[hidden],
.entity-downloads[hidden] {
display: none;
}
.results li + li {
border-top: 1px solid #ddd;
}
.result-button {
display: block;
width: 100%;
padding: 10px 0;
border: 0;
background: transparent;
text-align: left;
cursor: pointer;
}
.result-button:hover,
.result-button.active,
.result-button:focus {
background: #f3f3f3;
}
.meta,
.status {
color: var(--muted);
}
.status {
min-height: 1.25rem;
margin: 6px 0 0;
font-size: 0.9rem;
}
.status.error {
color: #b42318;
}
.entity-downloads p {
margin-bottom: 8px;
}
a {
color: var(--link);
}
</style>
</head>
<body>
<main>
<h1>Descargas</h1>
<section>
<div class="field">
<span>Base</span>
<div class="radio-group">
<label class="radio-option">
<input type="radio" name="dataset" value="gastos" checked>
<span>Gastos</span>
</label>
<label class="radio-option">
<input type="radio" name="dataset" value="ingresos">
<span>Ingresos</span>
</label>
</div>
</div>
</section>
<section>
<h2>Completo</h2>
<ul class="links" id="full-download"></ul>
</section>
<section>
<h2>Por gestión</h2>
<ul class="links" id="year-downloads"></ul>
</section>
<section>
<h2>Por entidad</h2>
<div class="field search-area">
<label for="entity-search">Entidad</label>
<input
id="entity-search"
name="entity-search"
type="search"
placeholder="Busca entidades"
autocomplete="off"
>
<ul class="results" id="entity-results" hidden></ul>
</div>
<p class="status" id="entity-status">Cargando entidades...</p>
<div class="entity-downloads" id="entity-downloads" hidden>
<p id="entity-name"></p>
<ul class="links" id="entity-links"></ul>
</div>
</section>
</main>
<script>
const API_BASE = "http://136.112.29.74/api/";
const DOWNLOAD_BASE = "https://abierto.economiayfinanzas.gob.bo/presupuesto/descargas/";
const ENTITY_API = `${API_BASE}entidad/clasificador`;
const YEARS = Array.from({ length: 10 }, (_, index) => 2016 + index);
const MAX_ENTITY_RESULTS = 12;
let entities = [];
let activeResultIndex = -1;
function setStatus(message, type = "info") {
const status = document.getElementById("entity-status");
status.textContent = message;
status.classList.toggle("error", type === "error");
}
function currentDataset() {
return document.querySelector('input[name="dataset"]:checked').value;
}
function buildUrl(partition, value) {
return `${DOWNLOAD_BASE}${currentDataset()}/${partition}/${value}`;
}
function normalizeText(value) {
return value
.normalize("NFD")
.replace(/[\u0300-\u036f]/g, "")
.toLowerCase()
.trim();
}
function createLinkItem(label, href) {
const item = document.createElement("li");
const link = document.createElement("a");
link.href = href;
link.textContent = label;
item.appendChild(link);
return item;
}
function renderFullDownload() {
const container = document.getElementById("full-download");
container.innerHTML = "";
container.appendChild(createLinkItem("Descargar", buildUrl("completo", "ultimo")));
}
function renderYearDownloads() {
const container = document.getElementById("year-downloads");
container.innerHTML = "";
YEARS.forEach((year) => {
container.appendChild(createLinkItem(String(year), buildUrl("gestion", year)));
});
}
function renderEntityDownloads(entity) {
const wrapper = document.getElementById("entity-downloads");
const name = document.getElementById("entity-name");
const links = document.getElementById("entity-links");
name.textContent = entity.desc_entidad;
links.innerHTML = "";
links.appendChild(createLinkItem("Descargar", buildUrl("entidad", entity.entidad)));
wrapper.hidden = false;
}
function selectEntity(entity) {
document.getElementById("entity-search").value = entity.desc_entidad;
document.getElementById("entity-results").hidden = true;
activeResultIndex = -1;
renderEntityDownloads(entity);
}
function resultButtons() {
return Array.from(document.querySelectorAll("#entity-results .result-button"));
}
function setActiveResult(index) {
const buttons = resultButtons();
buttons.forEach((button, buttonIndex) => {
button.classList.toggle("active", buttonIndex === index);
});
activeResultIndex = index;
if (index >= 0 && buttons[index]) {
buttons[index].scrollIntoView({ block: "nearest" });
}
}
function renderEntityResults(matches) {
const results = document.getElementById("entity-results");
results.innerHTML = "";
activeResultIndex = -1;
if (!matches.length) {
results.hidden = true;
return;
}
matches.forEach((entity) => {
const item = document.createElement("li");
const button = document.createElement("button");
const meta = document.createElement("span");
button.type = "button";
button.className = "result-button";
button.textContent = entity.desc_entidad;
button.addEventListener("click", () => selectEntity(entity));
meta.className = "meta";
meta.textContent = `Código ${entity.entidad}`;
button.appendChild(document.createElement("br"));
button.appendChild(meta);
item.appendChild(button);
results.appendChild(item);
});
results.hidden = false;
}
function moveActiveResult(direction) {
const buttons = resultButtons();
if (!buttons.length) {
return;
}
if (activeResultIndex === -1) {
setActiveResult(direction > 0 ? 0 : buttons.length - 1);
return;
}
const nextIndex = (activeResultIndex + direction + buttons.length) % buttons.length;
setActiveResult(nextIndex);
}
function updateEntitySearch() {
const query = normalizeText(document.getElementById("entity-search").value);
document.getElementById("entity-downloads").hidden = true;
if (!entities.length) {
return;
}
if (!query) {
setStatus("");
document.getElementById("entity-results").hidden = true;
activeResultIndex = -1;
return;
}
const matches = entities
.filter((entity) => entity.normalizedName.includes(query))
.slice(0, MAX_ENTITY_RESULTS);
if (!matches.length) {
setStatus("Sin resultados.", "error");
document.getElementById("entity-results").hidden = true;
activeResultIndex = -1;
return;
}
setStatus(`${matches.length} resultado(s).`);
renderEntityResults(matches);
}
async function loadEntities() {
try {
const response = await fetch(ENTITY_API);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
entities = data
.filter((entity) => entity && entity.entidad && entity.desc_entidad)
.map((entity) => ({
entidad: entity.entidad,
desc_entidad: entity.desc_entidad,
normalizedName: normalizeText(entity.desc_entidad)
}));
setStatus("");
} catch (error) {
setStatus("No se pudieron cargar las entidades.", "error");
}
}
function rerenderDownloads() {
renderFullDownload();
renderYearDownloads();
const selectedName = document.getElementById("entity-search").value;
const selectedEntity = entities.find(
(entity) => entity.desc_entidad === selectedName
);
if (selectedEntity) {
renderEntityDownloads(selectedEntity);
}
}
document.querySelectorAll('input[name="dataset"]').forEach((input) => {
input.addEventListener("change", rerenderDownloads);
});
document.getElementById("entity-search").addEventListener("input", updateEntitySearch);
document.getElementById("entity-search").addEventListener("keydown", (event) => {
const results = document.getElementById("entity-results");
if (event.key === "ArrowDown") {
event.preventDefault();
if (!results.hidden) {
moveActiveResult(1);
}
}
if (event.key === "ArrowUp") {
event.preventDefault();
if (!results.hidden) {
moveActiveResult(-1);
}
}
if (event.key === "Enter") {
const buttons = resultButtons();
if (!results.hidden && activeResultIndex >= 0 && buttons[activeResultIndex]) {
event.preventDefault();
buttons[activeResultIndex].click();
}
}
if (event.key === "Escape") {
results.hidden = true;
activeResultIndex = -1;
}
});
renderFullDownload();
renderYearDownloads();
loadEntities();
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Descargas presupuestarias</title>
<style>
:root {
--text: #1f1f1f;
--muted: #666;
--link: #0b57d0;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: sans-serif;
color: var(--text);
line-height: 1.5;
}
main {
max-width: 800px;
margin: 0 auto;
padding: 24px 16px 40px;
}
h1,
h2,
p {
margin-top: 0;
}
h1 {
margin-bottom: 16px;
}
h2 {
margin-bottom: 8px;
font-size: 1rem;
}
section {
margin-bottom: 20px;
}
label,
input,
button {
font: inherit;
}
input {
width: 100%;
padding: 8px 10px;
border: 1px solid #ccc;
background: #fff;
}
.field {
display: grid;
gap: 6px;
}
.radio-group {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.radio-option {
display: inline-flex;
align-items: center;
gap: 8px;
}
.radio-option input {
width: auto;
padding: 0;
border: 0;
}
.links,
.results {
padding: 0;
margin: 0;
list-style: none;
}
.links {
display: flex;
flex-wrap: wrap;
gap: 8px 12px;
}
.search-area {
position: relative;
}
.results {
position: absolute;
top: calc(100% + 4px);
left: 0;
right: 0;
z-index: 10;
max-height: 240px;
overflow-y: auto;
border-top: 1px solid #ddd;
border-bottom: 1px solid #ddd;
background: #fff;
}
.results[hidden],
.entity-downloads[hidden] {
display: none;
}
.results li + li {
border-top: 1px solid #ddd;
}
.result-button {
display: block;
width: 100%;
padding: 10px 0;
border: 0;
background: transparent;
text-align: left;
cursor: pointer;
}
.result-button:hover,
.result-button.active,
.result-button:focus {
background: #f3f3f3;
}
.meta,
.status {
color: var(--muted);
}
.status {
min-height: 1.25rem;
margin: 6px 0 0;
font-size: 0.9rem;
}
.status.error {
color: #b42318;
}
.entity-downloads p {
margin-bottom: 8px;
}
a {
color: var(--link);
}
</style>
</head>
<body>
<main>
<h1>Descargas</h1>
<section>
<div class="field">
<span>Base</span>
<div class="radio-group">
<label class="radio-option">
<input type="radio" name="dataset" value="gastos" checked>
<span>Gastos</span>
</label>
<label class="radio-option">
<input type="radio" name="dataset" value="ingresos">
<span>Ingresos</span>
</label>
</div>
</div>
</section>
<section>
<h2>Completo</h2>
<ul class="links" id="full-download"></ul>
</section>
<section>
<h2>Por gestión</h2>
<ul class="links" id="year-downloads"></ul>
</section>
<section>
<h2>Por entidad</h2>
<div class="field search-area">
<label for="entity-search">Entidad</label>
<input
id="entity-search"
name="entity-search"
type="search"
placeholder="Busca entidades"
autocomplete="off"
>
<ul class="results" id="entity-results" hidden></ul>
</div>
<p class="status" id="entity-status">Cargando entidades...</p>
<div class="entity-downloads" id="entity-downloads" hidden>
<p id="entity-name"></p>
<ul class="links" id="entity-links"></ul>
</div>
</section>
</main>
<script>
const API_BASE = "http://136.112.29.74/api/";
const DOWNLOAD_BASE = "https://abierto.economiayfinanzas.gob.bo/presupuesto/descargas/";
const ENTITY_API = `${API_BASE}entidad/clasificador`;
const YEARS = Array.from({ length: 10 }, (_, index) => 2016 + index);
const MAX_ENTITY_RESULTS = 12;
let entities = [];
let activeResultIndex = -1;
function setStatus(message, type = "info") {
const status = document.getElementById("entity-status");
status.textContent = message;
status.classList.toggle("error", type === "error");
}
function currentDataset() {
return document.querySelector('input[name="dataset"]:checked').value;
}
function buildUrl(partition, value) {
return `${DOWNLOAD_BASE}${currentDataset()}/${partition}/${value}`;
}
function normalizeText(value) {
return value
.normalize("NFD")
.replace(/[\u0300-\u036f]/g, "")
.toLowerCase()
.trim();
}
function createLinkItem(label, href) {
const item = document.createElement("li");
const link = document.createElement("a");
link.href = href;
link.textContent = label;
item.appendChild(link);
return item;
}
function renderFullDownload() {
const container = document.getElementById("full-download");
container.innerHTML = "";
container.appendChild(createLinkItem("Descargar", buildUrl("completo", "ultimo")));
}
function renderYearDownloads() {
const container = document.getElementById("year-downloads");
container.innerHTML = "";
YEARS.forEach((year) => {
container.appendChild(createLinkItem(String(year), buildUrl("gestion", year)));
});
}
function renderEntityDownloads(entity) {
const wrapper = document.getElementById("entity-downloads");
const name = document.getElementById("entity-name");
const links = document.getElementById("entity-links");
name.textContent = entity.desc_entidad;
links.innerHTML = "";
links.appendChild(createLinkItem("Descargar", buildUrl("entidad", entity.entidad)));
wrapper.hidden = false;
}
function selectEntity(entity) {
document.getElementById("entity-search").value = entity.desc_entidad;
document.getElementById("entity-results").hidden = true;
activeResultIndex = -1;
renderEntityDownloads(entity);
}
function resultButtons() {
return Array.from(document.querySelectorAll("#entity-results .result-button"));
}
function setActiveResult(index) {
const buttons = resultButtons();
buttons.forEach((button, buttonIndex) => {
button.classList.toggle("active", buttonIndex === index);
});
activeResultIndex = index;
if (index >= 0 && buttons[index]) {
buttons[index].scrollIntoView({ block: "nearest" });
}
}
function renderEntityResults(matches) {
const results = document.getElementById("entity-results");
results.innerHTML = "";
activeResultIndex = -1;
if (!matches.length) {
results.hidden = true;
return;
}
matches.forEach((entity) => {
const item = document.createElement("li");
const button = document.createElement("button");
const meta = document.createElement("span");
button.type = "button";
button.className = "result-button";
button.textContent = entity.desc_entidad;
button.addEventListener("click", () => selectEntity(entity));
meta.className = "meta";
meta.textContent = `Código ${entity.entidad}`;
button.appendChild(document.createElement("br"));
button.appendChild(meta);
item.appendChild(button);
results.appendChild(item);
});
results.hidden = false;
}
function moveActiveResult(direction) {
const buttons = resultButtons();
if (!buttons.length) {
return;
}
if (activeResultIndex === -1) {
setActiveResult(direction > 0 ? 0 : buttons.length - 1);
return;
}
const nextIndex = (activeResultIndex + direction + buttons.length) % buttons.length;
setActiveResult(nextIndex);
}
function updateEntitySearch() {
const query = normalizeText(document.getElementById("entity-search").value);
document.getElementById("entity-downloads").hidden = true;
if (!entities.length) {
return;
}
if (!query) {
setStatus("");
document.getElementById("entity-results").hidden = true;
activeResultIndex = -1;
return;
}
const matches = entities
.filter((entity) => entity.normalizedName.includes(query))
.slice(0, MAX_ENTITY_RESULTS);
if (!matches.length) {
setStatus("Sin resultados.", "error");
document.getElementById("entity-results").hidden = true;
activeResultIndex = -1;
return;
}
setStatus(`${matches.length} resultado(s).`);
renderEntityResults(matches);
}
async function loadEntities() {
try {
const response = await fetch(ENTITY_API);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
entities = data
.filter((entity) => entity && entity.entidad && entity.desc_entidad)
.map((entity) => ({
entidad: entity.entidad,
desc_entidad: entity.desc_entidad,
normalizedName: normalizeText(entity.desc_entidad)
}));
setStatus("");
} catch (error) {
setStatus("No se pudieron cargar las entidades.", "error");
}
}
function rerenderDownloads() {
renderFullDownload();
renderYearDownloads();
const selectedName = document.getElementById("entity-search").value;
const selectedEntity = entities.find(
(entity) => entity.desc_entidad === selectedName
);
if (selectedEntity) {
renderEntityDownloads(selectedEntity);
}
}
document.querySelectorAll('input[name="dataset"]').forEach((input) => {
input.addEventListener("change", rerenderDownloads);
});
document.getElementById("entity-search").addEventListener("input", updateEntitySearch);
document.getElementById("entity-search").addEventListener("keydown", (event) => {
const results = document.getElementById("entity-results");
if (event.key === "ArrowDown") {
event.preventDefault();
if (!results.hidden) {
moveActiveResult(1);
}
}
if (event.key === "ArrowUp") {
event.preventDefault();
if (!results.hidden) {
moveActiveResult(-1);
}
}
if (event.key === "Enter") {
const buttons = resultButtons();
if (!results.hidden && activeResultIndex >= 0 && buttons[activeResultIndex]) {
event.preventDefault();
buttons[activeResultIndex].click();
}
}
if (event.key === "Escape") {
results.hidden = true;
activeResultIndex = -1;
}
});
renderFullDownload();
renderYearDownloads();
loadEntities();
</script>
</body>
</html>