Rafael Lopez

descargas y diario

...@@ -1194,6 +1194,24 @@ ...@@ -1194,6 +1194,24 @@
1194 </div> 1194 </div>
1195 </div> 1195 </div>
1196 1196
1197 + <!-- Links directos -->
1198 + <div class="quick-links">
1199 + <a href="/vista-diaria" class="quick-link quick-link-diaria">
1200 + <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
1201 + <polyline points="3 12 7 12 9 6 12 18 15 9 17 12 21 12"/>
1202 + </svg>
1203 + Ver ejecución diaria
1204 + <span class="quick-link-arrow">→</span>
1205 + </a>
1206 + <a href="/descargas" class="quick-link quick-link-descargas">
1207 + <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
1208 + <path d="M12 4v12"/><polyline points="8 12 12 16 16 12"/><path d="M4 18h16"/>
1209 + </svg>
1210 + Descarga bases de datos
1211 + <span class="quick-link-arrow">→</span>
1212 + </a>
1213 + </div>
1214 +
1197 </div> 1215 </div>
1198 1216
1199 <!-- Contenedor de resultados (overlay absoluto) --> 1217 <!-- Contenedor de resultados (overlay absoluto) -->
...@@ -1531,6 +1549,7 @@ ...@@ -1531,6 +1549,7 @@
1531 1549
1532 <!-- Scroll hint --> 1550 <!-- Scroll hint -->
1533 1551
1552 +
1534 </main> 1553 </main>
1535 1554
1536 <!-- Secciones removidas - el landing es solo el buscador --> 1555 <!-- Secciones removidas - el landing es solo el buscador -->
...@@ -2801,6 +2820,23 @@ ...@@ -2801,6 +2820,23 @@
2801 :global(html:not(.dark)) .footer-org{color:#999} 2820 :global(html:not(.dark)) .footer-org{color:#999}
2802 :global(html:not(.dark)) .footer-right{color:#999} 2821 :global(html:not(.dark)) .footer-right{color:#999}
2803 2822
2823 + /* ── Quick links (debajo de mode cards) ── */
2824 + /* ── Quick links (debajo de mode cards) ── */
2825 + .quick-links{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-top:12px}
2826 + .quick-link{display:flex;align-items:center;gap:8px;font-family:var(--sans);font-size:16px;font-weight:600;letter-spacing:-0.01em;text-decoration:none;padding:14px 16px;border-radius:12px;transition:all 0.25s ease}
2827 + .page a.quick-link-diaria{color:#C2EDD9;background:rgba(74,139,110,0.18);border:none}
2828 + .page a.quick-link-diaria:hover{color:#E0F5EC;background:rgba(74,139,110,0.28)}
2829 + .page a.quick-link-descargas{color:#F5E6A8;background:rgba(201,167,81,0.18);border:none}
2830 + .page a.quick-link-descargas:hover{color:#FBF2D0;background:rgba(201,167,81,0.28)}
2831 + .quick-link svg{opacity:0.8;transition:opacity 0.25s;flex-shrink:0}
2832 + .quick-link:hover svg{opacity:1}
2833 + .quick-link-arrow{margin-left:auto;opacity:0.4;transition:all 0.25s;transform:translateX(-4px)}
2834 + .quick-link:hover .quick-link-arrow{opacity:0.8;transform:translateX(0)}
2835 + :global(html:not(.dark)) .page a.quick-link-diaria{color:#3A7A5E;background:rgba(74,139,110,0.14)}
2836 + :global(html:not(.dark)) .page a.quick-link-diaria:hover{color:#2D6B4F;background:rgba(74,139,110,0.22)}
2837 + :global(html:not(.dark)) .page a.quick-link-descargas{color:#8A7030;background:rgba(201,167,81,0.14)}
2838 + :global(html:not(.dark)) .page a.quick-link-descargas:hover{color:#7A6228;background:rgba(201,167,81,0.22)}
2839 +
2804 /* ══════════════════════════════════════════════════════════════════════════ 2840 /* ══════════════════════════════════════════════════════════════════════════
2805 RESPONSIVE - TABLET (768px) 2841 RESPONSIVE - TABLET (768px)
2806 ══════════════════════════════════════════════════════════════════════════ */ 2842 ══════════════════════════════════════════════════════════════════════════ */
...@@ -2808,6 +2844,7 @@ ...@@ -2808,6 +2844,7 @@
2808 .page{scroll-snap-type:y proximity} 2844 .page{scroll-snap-type:y proximity}
2809 main[data-section="hero"]{height:auto;min-height:100vh} 2845 main[data-section="hero"]{height:auto;min-height:100vh}
2810 main[data-section="hero"],.landing-screen,.directory,.clasificadores,.historias,.visualizaciones,.manifiesto,.descargas{scroll-snap-stop:normal} 2846 main[data-section="hero"],.landing-screen,.directory,.clasificadores,.historias,.visualizaciones,.manifiesto,.descargas{scroll-snap-stop:normal}
2847 + .quick-links{grid-template-columns:1fr}
2811 2848
2812 /* Section dots - bottom horizontal bar */ 2849 /* Section dots - bottom horizontal bar */
2813 .section-dots{position:fixed;right:auto;left:50%;top:auto;bottom:20px;transform:translateX(-50%);flex-direction:row;gap:10px;padding:12px 20px;border-radius:24px;background:rgba(28,28,26,0.7);backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);border:1px solid rgba(255,255,255,0.1);z-index:1000} 2850 .section-dots{position:fixed;right:auto;left:50%;top:auto;bottom:20px;transform:translateX(-50%);flex-direction:row;gap:10px;padding:12px 20px;border-radius:24px;background:rgba(28,28,26,0.7);backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);border:1px solid rgba(255,255,255,0.1);z-index:1000}
......
...@@ -11,13 +11,21 @@ export async function GET({ url }) { ...@@ -11,13 +11,21 @@ export async function GET({ url }) {
11 timeline: `${API_BASE}/timeline` 11 timeline: `${API_BASE}/timeline`
12 }; 12 };
13 13
14 - const apiUrl = endpoints[tipo]; 14 + let apiUrl = endpoints[tipo];
15 if (!apiUrl) { 15 if (!apiUrl) {
16 return new Response(JSON.stringify({ error: 'Invalid tipo' }), { 16 return new Response(JSON.stringify({ error: 'Invalid tipo' }), {
17 status: 400, headers: { 'Content-Type': 'application/json' } 17 status: 400, headers: { 'Content-Type': 'application/json' }
18 }); 18 });
19 } 19 }
20 20
21 + // Pasar parámetros adicionales
22 + const params = new URLSearchParams();
23 + const entidad = url.searchParams.get('entidad');
24 + const dias = url.searchParams.get('dias');
25 + if (entidad) params.set('entidad', entidad);
26 + if (dias) params.set('dias', dias);
27 + if (params.toString()) apiUrl += `?${params.toString()}`;
28 +
21 try { 29 try {
22 const response = await fetch(apiUrl); 30 const response = await fetch(apiUrl);
23 if (!response.ok) { 31 if (!response.ok) {
......
...@@ -4,9 +4,13 @@ ...@@ -4,9 +4,13 @@
4 const YEARS = Array.from({ length: 10 }, (_, i) => 2016 + i); 4 const YEARS = Array.from({ length: 10 }, (_, i) => 2016 + i);
5 5
6 let mounted = $state(false); 6 let mounted = $state(false);
7 - let dataset = $state('gasto'); 7 +
8 - let datasetMasivo = $state('gasto'); 8 + // Sección 1: Por gestión
9 + let datasetGestion = $state('gasto');
9 let formatoGestion = $state('csv'); 10 let formatoGestion = $state('csv');
11 +
12 + // Sección 2: Por entidad
13 + let datasetEntidad = $state('gasto');
10 let formatoEntidad = $state('csv'); 14 let formatoEntidad = $state('csv');
11 let entities = $state([]); 15 let entities = $state([]);
12 let entitySearch = $state(''); 16 let entitySearch = $state('');
...@@ -15,30 +19,27 @@ ...@@ -15,30 +19,27 @@
15 let selectedIdx = $state(-1); 19 let selectedIdx = $state(-1);
16 let loadingEntities = $state(true); 20 let loadingEntities = $state(true);
17 let entityError = $state(false); 21 let entityError = $state(false);
22 +
23 + // Sección 3: Base completa
24 + let datasetMasivo = $state('gasto');
25 +
26 + // Sección 4: Ejecución semanal (próximamente)
27 +
28 + // Sección 5: Desarrolladores
18 let copiedUrl = $state(null); 29 let copiedUrl = $state(null);
19 30
20 function normalizeText(val) { 31 function normalizeText(val) {
21 return val.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase().trim(); 32 return val.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase().trim();
22 } 33 }
23 34
24 - function buildUrl(partition, value, formato = 'csv') { 35 + function buildGestionUrl(year) {
25 - if (partition === 'completo') { 36 + const suffix = formatoGestion === 'xlsx' ? `_xlsx.zip` : `.zip`;
26 - if (formato === 'parquet') { 37 + return `${DOWNLOAD_BASE}${datasetGestion}/gestiones/gestion_${year}${suffix}`;
27 - return `${DOWNLOAD_BASE}${dataset}/ultimo/${dataset}.parquet`; 38 + }
28 - } 39 +
29 - return `${DOWNLOAD_BASE}${dataset}/ultimo/${dataset}.zip`; 40 + function buildEntidadUrl(codigo) {
30 - } else if (partition === 'gestion') { 41 + const suffix = formatoEntidad === 'xlsx' ? `_xlsx.zip` : `.zip`;
31 - if (formato === 'xlsx') { 42 + return `${DOWNLOAD_BASE}${datasetEntidad}/entidades/entidad_${codigo}${suffix}`;
32 - return `${DOWNLOAD_BASE}${dataset}/gestiones/gestion_${value}_xlsx.zip`;
33 - }
34 - return `${DOWNLOAD_BASE}${dataset}/gestiones/gestion_${value}.zip`;
35 - } else if (partition === 'entidad') {
36 - if (formato === 'xlsx') {
37 - return `${DOWNLOAD_BASE}${dataset}/entidades/entidad_${value}_xlsx.zip`;
38 - }
39 - return `${DOWNLOAD_BASE}${dataset}/entidades/entidad_${value}.zip`;
40 - }
41 - return `${DOWNLOAD_BASE}${dataset}/${partition}/${value}`;
42 } 43 }
43 44
44 function buildMasivoUrl(formato) { 45 function buildMasivoUrl(formato) {
...@@ -113,112 +114,125 @@ ...@@ -113,112 +114,125 @@
113 <p class="subtitle">Descarga los datos del presupuesto público boliviano en distintos formatos</p> 114 <p class="subtitle">Descarga los datos del presupuesto público boliviano en distintos formatos</p>
114 115
115 <!-- ═══════════════════════════════════════════ --> 116 <!-- ═══════════════════════════════════════════ -->
116 - <!-- SECCIÓN 1: Por gestión o entidad --> 117 + <!-- SECCIÓN 1: Por gestión -->
117 <!-- ═══════════════════════════════════════════ --> 118 <!-- ═══════════════════════════════════════════ -->
118 <section class="section"> 119 <section class="section">
119 - <h2 class="section-title">Por gestión o entidad</h2> 120 + <h2 class="section-title">Por gestión</h2>
120 - <p class="section-desc">Descarga los datos segmentados por año fiscal o por entidad pública</p> 121 + <p class="section-desc">Datos segmentados por año fiscal</p>
121 - 122 +
122 - <!-- Toggle Gastos/Ingresos --> 123 + <div class="controls-row">
123 - <div class="control-row"> 124 + <div class="control-row">
124 - <span class="control-label">Base</span> 125 + <span class="control-label">Base</span>
125 - <div class="toggle-group"> 126 + <div class="toggle-group">
126 - <button class="toggle-btn" class:active={dataset === 'gasto'} onclick={() => { dataset = 'gasto'; selectedEntity = null; }}>Gastos</button> 127 + <button class="toggle-btn" class:active={datasetGestion === 'gasto'} onclick={() => datasetGestion = 'gasto'}>Gastos</button>
127 - <button class="toggle-btn" class:active={dataset === 'ingreso'} onclick={() => { dataset = 'ingreso'; selectedEntity = null; }}>Ingresos</button> 128 + <button class="toggle-btn" class:active={datasetGestion === 'ingreso'} onclick={() => datasetGestion = 'ingreso'}>Ingresos</button>
128 - </div>
129 - </div>
130 -
131 - <!-- Por gestión -->
132 - <div class="subsection">
133 - <div class="subsection-header">
134 - <h3 class="subsection-title">Por gestión</h3>
135 - <div class="formato-toggle">
136 - <span class="formato-label">Formato:</span>
137 - <button class="fmt-btn" class:active={formatoGestion === 'csv'} onclick={() => formatoGestion = 'csv'}>CSV</button>
138 - <button class="fmt-btn" class:active={formatoGestion === 'xlsx'} onclick={() => formatoGestion = 'xlsx'}>Excel</button>
139 </div> 129 </div>
140 </div> 130 </div>
141 - <div class="year-grid"> 131 + <div class="control-row">
142 - {#each YEARS as year} 132 + <span class="control-label">Formato</span>
143 - <a href={buildUrl('gestion', year, formatoGestion)} download rel="external" class="year-link"> 133 + <div class="toggle-group">
144 - <span class="year-num">{year}</span> 134 + <button class="toggle-btn" class:active={formatoGestion === 'csv'} onclick={() => formatoGestion = 'csv'}>CSV</button>
145 - <span class="year-fmt">.{formatoGestion === 'xlsx' ? 'xlsx' : 'csv'}</span> 135 + <button class="toggle-btn" class:active={formatoGestion === 'xlsx'} onclick={() => formatoGestion = 'xlsx'}>Excel</button>
146 - </a> 136 + </div>
147 - {/each}
148 </div> 137 </div>
149 </div> 138 </div>
150 139
151 - <!-- Por entidad --> 140 + <div class="year-grid">
152 - <div class="subsection"> 141 + {#each YEARS as year}
153 - <div class="subsection-header"> 142 + <a href={buildGestionUrl(year)} download rel="external" class="year-link">
154 - <h3 class="subsection-title">Por entidad</h3> 143 + <span class="year-num">{year}</span>
155 - <div class="formato-toggle"> 144 + <span class="year-fmt">.{formatoGestion === 'xlsx' ? 'xlsx' : 'csv'}</span>
156 - <span class="formato-label">Formato:</span> 145 + </a>
157 - <button class="fmt-btn" class:active={formatoEntidad === 'csv'} onclick={() => formatoEntidad = 'csv'}>CSV</button> 146 + {/each}
158 - <button class="fmt-btn" class:active={formatoEntidad === 'xlsx'} onclick={() => formatoEntidad = 'xlsx'}>Excel</button> 147 + </div>
148 + </section>
149 +
150 + <!-- ═══════════════════════════════════════════ -->
151 + <!-- SECCIÓN 2: Por entidad -->
152 + <!-- ═══════════════════════════════════════════ -->
153 + <section class="section">
154 + <h2 class="section-title">Por entidad</h2>
155 + <p class="section-desc">Datos históricos de una entidad pública específica</p>
156 +
157 + <div class="controls-row">
158 + <div class="control-row">
159 + <span class="control-label">Base</span>
160 + <div class="toggle-group">
161 + <button class="toggle-btn" class:active={datasetEntidad === 'gasto'} onclick={() => { datasetEntidad = 'gasto'; selectedEntity = null; }}>Gastos</button>
162 + <button class="toggle-btn" class:active={datasetEntidad === 'ingreso'} onclick={() => { datasetEntidad = 'ingreso'; selectedEntity = null; }}>Ingresos</button>
159 </div> 163 </div>
160 </div> 164 </div>
161 - <div class="entity-search"> 165 + <div class="control-row">
162 - <input 166 + <span class="control-label">Formato</span>
163 - type="text" 167 + <div class="toggle-group">
164 - bind:value={entitySearch} 168 + <button class="toggle-btn" class:active={formatoEntidad === 'csv'} onclick={() => formatoEntidad = 'csv'}>CSV</button>
165 - onfocus={() => { entityFocused = true; selectedEntity = null; }} 169 + <button class="toggle-btn" class:active={formatoEntidad === 'xlsx'} onclick={() => formatoEntidad = 'xlsx'}>Excel</button>
166 - onblur={() => setTimeout(() => entityFocused = false, 150)} 170 + </div>
167 - onkeydown={handleKeydown}
168 - placeholder="Buscar entidad..."
169 - class="search-input"
170 - />
171 - {#if loadingEntities}
172 - <span class="search-status">Cargando entidades...</span>
173 - {:else if entityError}
174 - <span class="search-status error">No se pudieron cargar las entidades</span>
175 - {/if}
176 -
177 - {#if entityFocused && filteredEntities.length > 0}
178 - <div class="search-dropdown">
179 - {#each filteredEntities as entity, i}
180 - <button
181 - class="search-item"
182 - class:active={selectedIdx === i}
183 - onmouseenter={() => selectedIdx = i}
184 - onclick={() => selectEntity(entity)}
185 - >
186 - <span class="item-name">{entity.desc_entidad}</span>
187 - <span class="item-code">Código {entity.entidad}</span>
188 - </button>
189 - {/each}
190 - </div>
191 - {/if}
192 -
193 - {#if entityFocused && entitySearch.length >= 2 && filteredEntities.length === 0 && !loadingEntities}
194 - <span class="search-status error">Sin resultados</span>
195 - {/if}
196 </div> 171 </div>
172 + </div>
197 173
198 - {#if selectedEntity} 174 + <div class="entity-search">
199 - <div class="entity-result"> 175 + <input
200 - <span class="entity-name">{selectedEntity.desc_entidad}</span> 176 + type="text"
201 - <a href={buildUrl('entidad', selectedEntity.entidad, formatoEntidad)} download rel="external" class="download-link"> 177 + bind:value={entitySearch}
202 - <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg> 178 + onfocus={() => { entityFocused = true; selectedEntity = null; }}
203 - Descargar .{formatoEntidad === 'xlsx' ? 'xlsx' : 'csv'} 179 + onblur={() => setTimeout(() => entityFocused = false, 150)}
204 - </a> 180 + onkeydown={handleKeydown}
181 + placeholder="Buscar entidad..."
182 + class="search-input"
183 + />
184 + {#if loadingEntities}
185 + <span class="search-status">Cargando entidades...</span>
186 + {:else if entityError}
187 + <span class="search-status error">No se pudieron cargar las entidades</span>
188 + {/if}
189 +
190 + {#if entityFocused && filteredEntities.length > 0}
191 + <div class="search-dropdown">
192 + {#each filteredEntities as entity, i}
193 + <button
194 + class="search-item"
195 + class:active={selectedIdx === i}
196 + onmouseenter={() => selectedIdx = i}
197 + onclick={() => selectEntity(entity)}
198 + >
199 + <span class="item-name">{entity.desc_entidad}</span>
200 + <span class="item-code">Código {entity.entidad}</span>
201 + </button>
202 + {/each}
205 </div> 203 </div>
206 {/if} 204 {/if}
205 +
206 + {#if entityFocused && entitySearch.length >= 2 && filteredEntities.length === 0 && !loadingEntities}
207 + <span class="search-status error">Sin resultados</span>
208 + {/if}
207 </div> 209 </div>
210 +
211 + {#if selectedEntity}
212 + <div class="entity-result">
213 + <span class="entity-name">{selectedEntity.desc_entidad}</span>
214 + <a href={buildEntidadUrl(selectedEntity.entidad)} download rel="external" class="download-link">
215 + <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
216 + Descargar .{formatoEntidad === 'xlsx' ? 'xlsx' : 'csv'}
217 + </a>
218 + </div>
219 + {/if}
208 </section> 220 </section>
209 221
210 <!-- ═══════════════════════════════════════════ --> 222 <!-- ═══════════════════════════════════════════ -->
211 - <!-- SECCIÓN 2: Base completa --> 223 + <!-- SECCIÓN 3: Base completa -->
212 <!-- ═══════════════════════════════════════════ --> 224 <!-- ═══════════════════════════════════════════ -->
213 <section class="section"> 225 <section class="section">
214 <h2 class="section-title">Base completa</h2> 226 <h2 class="section-title">Base completa</h2>
215 <p class="section-desc">Toda la base histórica: todas las entidades y todos los años en un solo archivo</p> 227 <p class="section-desc">Toda la base histórica: todas las entidades y todos los años en un solo archivo</p>
216 228
217 - <div class="control-row"> 229 + <div class="controls-row">
218 - <span class="control-label">Base</span> 230 + <div class="control-row">
219 - <div class="toggle-group"> 231 + <span class="control-label">Base</span>
220 - <button class="toggle-btn" class:active={datasetMasivo === 'gasto'} onclick={() => datasetMasivo = 'gasto'}>Gastos</button> 232 + <div class="toggle-group">
221 - <button class="toggle-btn" class:active={datasetMasivo === 'ingreso'} onclick={() => datasetMasivo = 'ingreso'}>Ingresos</button> 233 + <button class="toggle-btn" class:active={datasetMasivo === 'gasto'} onclick={() => datasetMasivo = 'gasto'}>Gastos</button>
234 + <button class="toggle-btn" class:active={datasetMasivo === 'ingreso'} onclick={() => datasetMasivo = 'ingreso'}>Ingresos</button>
235 + </div>
222 </div> 236 </div>
223 </div> 237 </div>
224 238
...@@ -245,14 +259,13 @@ ...@@ -245,14 +259,13 @@
245 </section> 259 </section>
246 260
247 <!-- ═══════════════════════════════════════════ --> 261 <!-- ═══════════════════════════════════════════ -->
248 - <!-- SECCIÓN 3: Ejecución semanal --> 262 + <!-- SECCIÓN 4: Ejecución semanal -->
249 <!-- ═══════════════════════════════════════════ --> 263 <!-- ═══════════════════════════════════════════ -->
250 <section class="section"> 264 <section class="section">
251 <h2 class="section-title">Ejecución semanal</h2> 265 <h2 class="section-title">Ejecución semanal</h2>
252 - <p class="section-desc">Reporte semanal de ejecución presupuestaria con datos agregados</p> 266 + <p class="section-desc">Reporte semanal de ejecución presupuestaria</p>
253 267
254 <div class="semanal-downloads disabled"> 268 <div class="semanal-downloads disabled">
255 - <span class="semanal-badge">Próximamente</span>
256 <div class="masivo-downloads"> 269 <div class="masivo-downloads">
257 <span class="download-card download-card-disabled"> 270 <span class="download-card download-card-disabled">
258 <div class="download-card-icon"> 271 <div class="download-card-icon">
...@@ -277,7 +290,7 @@ ...@@ -277,7 +290,7 @@
277 </section> 290 </section>
278 291
279 <!-- ═══════════════════════════════════════════ --> 292 <!-- ═══════════════════════════════════════════ -->
280 - <!-- SECCIÓN 4: Para desarrolladores --> 293 + <!-- SECCIÓN 5: Para desarrolladores -->
281 <!-- ═══════════════════════════════════════════ --> 294 <!-- ═══════════════════════════════════════════ -->
282 <section class="section section-last"> 295 <section class="section section-last">
283 <h2 class="section-title">Para desarrolladores</h2> 296 <h2 class="section-title">Para desarrolladores</h2>
...@@ -326,9 +339,8 @@ ...@@ -326,9 +339,8 @@
326 } 339 }
327 340
328 .title { 341 .title {
329 - font-family: 'DM Serif Display', serif;
330 font-size: 2rem; 342 font-size: 2rem;
331 - font-weight: 400; 343 + font-weight: 700;
332 margin: 0 0 0.25rem; 344 margin: 0 0 0.25rem;
333 letter-spacing: -0.02em; 345 letter-spacing: -0.02em;
334 } 346 }
...@@ -364,12 +376,18 @@ ...@@ -364,12 +376,18 @@
364 line-height: 1.5; 376 line-height: 1.5;
365 } 377 }
366 378
367 - /* Control row */ 379 + /* Controls */
380 + .controls-row {
381 + display: flex;
382 + flex-wrap: wrap;
383 + gap: 1rem;
384 + margin-bottom: 1.25rem;
385 + }
386 +
368 .control-row { 387 .control-row {
369 display: flex; 388 display: flex;
370 align-items: center; 389 align-items: center;
371 gap: 0.75rem; 390 gap: 0.75rem;
372 - margin-bottom: 1.25rem;
373 } 391 }
374 392
375 .control-label { 393 .control-label {
...@@ -415,65 +433,6 @@ ...@@ -415,65 +433,6 @@
415 color: var(--theme-titulo); 433 color: var(--theme-titulo);
416 } 434 }
417 435
418 - /* Subsections */
419 - .subsection {
420 - margin-bottom: 1.5rem;
421 - }
422 - .subsection:last-child { margin-bottom: 0; }
423 -
424 - .subsection-header {
425 - display: flex;
426 - align-items: center;
427 - justify-content: space-between;
428 - margin-bottom: 0.75rem;
429 - flex-wrap: wrap;
430 - gap: 0.5rem;
431 - }
432 -
433 - .subsection-title {
434 - font-size: 0.9375rem;
435 - font-weight: 600;
436 - margin: 0;
437 - }
438 -
439 - /* Formato toggle */
440 - .formato-toggle {
441 - display: flex;
442 - align-items: center;
443 - gap: 0.375rem;
444 - }
445 -
446 - .formato-label {
447 - font-size: 0.6875rem;
448 - color: var(--theme-texto);
449 - opacity: 0.5;
450 - margin-right: 0.25rem;
451 - }
452 -
453 - .fmt-btn {
454 - font-family: 'Qanelas', var(--font-sans);
455 - font-size: 0.6875rem;
456 - font-weight: 600;
457 - padding: 0.3rem 0.75rem;
458 - border: 1px solid var(--theme-borde);
459 - border-radius: 6px;
460 - background: transparent;
461 - color: var(--theme-texto);
462 - cursor: pointer;
463 - transition: all 0.15s;
464 - }
465 - .fmt-btn:hover { border-color: var(--theme-accent); color: var(--theme-titulo); }
466 - .fmt-btn.active {
467 - background: var(--theme-titulo);
468 - border-color: var(--theme-titulo);
469 - color: var(--theme-body);
470 - }
471 - :global(.dark) .fmt-btn.active {
472 - background: var(--theme-accent);
473 - border-color: var(--theme-accent);
474 - color: var(--theme-body);
475 - }
476 -
477 /* Years */ 436 /* Years */
478 .year-grid { 437 .year-grid {
479 display: grid; 438 display: grid;
...@@ -777,10 +736,10 @@ ...@@ -777,10 +736,10 @@
777 @media (max-width: 640px) { 736 @media (max-width: 640px) {
778 .layout { padding: 1rem 1rem 2rem; } 737 .layout { padding: 1rem 1rem 2rem; }
779 .title { font-size: 1.75rem; } 738 .title { font-size: 1.75rem; }
739 + .controls-row { flex-direction: column; gap: 0.75rem; }
780 .year-grid { grid-template-columns: repeat(auto-fill, minmax(75px, 1fr)); } 740 .year-grid { grid-template-columns: repeat(auto-fill, minmax(75px, 1fr)); }
781 .masivo-downloads { grid-template-columns: 1fr; } 741 .masivo-downloads { grid-template-columns: 1fr; }
782 .search-input, .search-dropdown, .entity-result { max-width: 100%; } 742 .search-input, .search-dropdown, .entity-result { max-width: 100%; }
783 - .subsection-header { flex-direction: column; align-items: flex-start; }
784 .entity-result { flex-direction: column; align-items: flex-start; } 743 .entity-result { flex-direction: column; align-items: flex-start; }
785 } 744 }
786 </style> 745 </style>
......
...@@ -10,6 +10,8 @@ ...@@ -10,6 +10,8 @@
10 let timeline = $state([]); 10 let timeline = $state([]);
11 let loading = $state(true); 11 let loading = $state(true);
12 let error = $state(null); 12 let error = $state(null);
13 + let fechaActualizacion = $state(null);
14 + let fechaActualizacionRaw = $state('');
13 15
14 // Controles interactivos 16 // Controles interactivos
15 let diasSeleccionados = $state('total'); 17 let diasSeleccionados = $state('total');
...@@ -93,8 +95,99 @@ ...@@ -93,8 +95,99 @@
93 document.removeEventListener('mouseup', handleRangeMouseUp); 95 document.removeEventListener('mouseup', handleRangeMouseUp);
94 } 96 }
95 97
98 + // Fecha narrativa
99 + let fechaNarrativa = $derived.by(() => {
100 + if (!fechaActualizacion) return '';
101 + const dias = ['domingo', 'lunes', 'martes', 'miércoles', 'jueves', 'viernes', 'sábado'];
102 + const meses = ['enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio', 'julio', 'agosto', 'septiembre', 'octubre', 'noviembre', 'diciembre'];
103 + const dia = dias[fechaActualizacion.getDay()];
104 + const num = fechaActualizacion.getDate();
105 + const mes = meses[fechaActualizacion.getMonth()];
106 + const anio = fechaActualizacion.getFullYear();
107 + return `${dia} ${num} de ${mes} de ${anio}`;
108 + });
109 +
110 + let horaActualizacion = $derived.by(() => {
111 + if (!fechaActualizacionRaw) return '';
112 + // Extraer hora directamente del string ISO tal cual viene
113 + const match = fechaActualizacionRaw.match(/T(\d{2}):(\d{2}):(\d{2})/);
114 + if (!match) return '';
115 + return `${match[1]}:${match[2]}:${match[3]}`;
116 + });
117 +
96 // Búsqueda para timeline cards 118 // Búsqueda para timeline cards
97 let busquedaTimeline = $state(''); 119 let busquedaTimeline = $state('');
120 + let searchResults = $state([]);
121 + let searchLoading = $state(false);
122 + let searchTimer = null;
123 +
124 + let searchEntities = $state([]);
125 +
126 + async function buscarEnAPI(query) {
127 + if (!query || query.trim().length < 2) {
128 + searchResults = [];
129 + searchEntities = [];
130 + return;
131 + }
132 + searchLoading = true;
133 + try {
134 + const params = new URLSearchParams({
135 + q: query,
136 + per_page: '10',
137 + is_class: 'true',
138 + class_: 'entidad'
139 + });
140 + const res = await fetch(`/api/search?${params}`);
141 + if (!res.ok) throw new Error(`Error ${res.status}`);
142 + const data = await res.json();
143 + searchResults = data.hits || [];
144 +
145 + // Para cada entidad encontrada, cargar sus programas del timeline
146 + const entities = await Promise.all(searchResults.map(async (hit) => {
147 + const doc = hit.document;
148 + const meta = parseSearchMetadatos(doc.metadatos);
149 + const codigo = meta.entidad || '';
150 + let programas = [];
151 + try {
152 + const tlRes = await fetch(`/api/diario?tipo=timeline&entidad=${codigo}`);
153 + if (tlRes.ok) {
154 + const tlData = await tlRes.json();
155 + programas = (Array.isArray(tlData) ? tlData : []).map(item => ({
156 + programa: item.desc_programa || '',
157 + actividad: item.desc_actividad_proyecto || '',
158 + devengado: item.devengado || 0
159 + })).sort((a, b) => b.devengado - a.devengado);
160 + }
161 + } catch { /* silencioso */ }
162 + const total = programas.reduce((s, p) => s + p.devengado, 0);
163 + return {
164 + entidad: String(codigo),
165 + entidad_desc_area: meta.entidad_desc_area || '',
166 + entidad_desc_entidad: doc.texto || '',
167 + total: total || doc.devengado || 0,
168 + programas
169 + };
170 + }));
171 + searchEntities = entities;
172 + } catch (e) {
173 + console.error('Error en búsqueda:', e);
174 + searchResults = [];
175 + searchEntities = [];
176 + } finally {
177 + searchLoading = false;
178 + }
179 + }
180 +
181 + function handleSearchInput() {
182 + clearTimeout(searchTimer);
183 + searchTimer = setTimeout(() => buscarEnAPI(busquedaTimeline), 300);
184 + }
185 +
186 + function parseSearchMetadatos(meta) {
187 + if (!meta) return {};
188 + if (typeof meta === 'object') return meta;
189 + try { return JSON.parse(meta); } catch { return {}; }
190 + }
98 191
99 // Labels dinámicos para periodos de tiempo 192 // Labels dinámicos para periodos de tiempo
100 function formatearFechaCorta(fecha) { 193 function formatearFechaCorta(fecha) {
...@@ -130,11 +223,6 @@ ...@@ -130,11 +223,6 @@
130 223
131 function irASeccion(index) { 224 function irASeccion(index) {
132 seccionActiva = index; 225 seccionActiva = index;
133 - const elemento = document.getElementById(secciones[index].id);
134 - if (elemento && vistaContainer) {
135 - elemento.scrollIntoView({ behavior: 'smooth', block: 'start' });
136 - }
137 - // Actualizar URL hash
138 history.replaceState(null, '', `#${secciones[index].id}`); 226 history.replaceState(null, '', `#${secciones[index].id}`);
139 } 227 }
140 228
...@@ -192,7 +280,7 @@ ...@@ -192,7 +280,7 @@
192 // Colores del tema 280 // Colores del tema
193 const colores = { 281 const colores = {
194 base: '#363534', 282 base: '#363534',
195 - claro: '#F3EAD8', 283 + claro: '#4A8B6E',
196 rojo: '#DB281C', 284 rojo: '#DB281C',
197 amarillo: '#F4E410', 285 amarillo: '#F4E410',
198 verde: '#007C38', 286 verde: '#007C38',
...@@ -208,7 +296,7 @@ ...@@ -208,7 +296,7 @@
208 function getChartColors() { 296 function getChartColors() {
209 return { 297 return {
210 barDefault: getCSSVar('--chart-bar-default') || '#e0e0e0', 298 barDefault: getCSSVar('--chart-bar-default') || '#e0e0e0',
211 - barHighlight: getCSSVar('--chart-bar-highlight') || '#F3EAD8', 299 + barHighlight: getCSSVar('--chart-bar-highlight') || '#4A8B6E',
212 barStroke: getCSSVar('--chart-bar-stroke') || '#b0b0b0', 300 barStroke: getCSSVar('--chart-bar-stroke') || '#b0b0b0',
213 barStrokeDefault: getCSSVar('--chart-bar-stroke-default') || 'white', 301 barStrokeDefault: getCSSVar('--chart-bar-stroke-default') || 'white',
214 trackBg: getCSSVar('--chart-track-bg') || '#f5f5f5', 302 trackBg: getCSSVar('--chart-track-bg') || '#f5f5f5',
...@@ -250,11 +338,12 @@ ...@@ -250,11 +338,12 @@
250 338
251 try { 339 try {
252 // Cargar todos los datos en paralelo desde API 340 // Cargar todos los datos en paralelo desde API
253 - const [tsData, entData, clasData, tlData] = await Promise.all([ 341 + const [tsData, entData, clasData, tlData, statusData] = await Promise.all([
254 cargarDiario('timeseries'), 342 cargarDiario('timeseries'),
255 cargarDiario('entidades'), 343 cargarDiario('entidades'),
256 cargarDiario('clasificadores'), 344 cargarDiario('clasificadores'),
257 - cargarDiario('timeline') 345 + cargarDiario('timeline'),
346 + cargarDiario('status')
258 ]); 347 ]);
259 348
260 // Los datos ya vienen como objetos 349 // Los datos ya vienen como objetos
...@@ -262,6 +351,10 @@ ...@@ -262,6 +351,10 @@
262 ejecucionEntidades = entData; 351 ejecucionEntidades = entData;
263 ejecucionClasificadores = clasData; 352 ejecucionClasificadores = clasData;
264 timeline = tlData; 353 timeline = tlData;
354 + if (statusData?.fecha_actualizacion) {
355 + fechaActualizacionRaw = statusData.fecha_actualizacion;
356 + fechaActualizacion = new Date(statusData.fecha_actualizacion);
357 + }
265 358
266 loading = false; 359 loading = false;
267 360
...@@ -285,13 +378,10 @@ ...@@ -285,13 +378,10 @@
285 378
286 // Datos por clasificador para las barras apiladas (sin agrupar, cada hijo es un segmento) 379 // Datos por clasificador para las barras apiladas (sin agrupar, cada hijo es un segmento)
287 let datosPorClasificador = $derived.by(() => { 380 let datosPorClasificador = $derived.by(() => {
288 - const dias = diasSeleccionados === 'total' ? 0 : parseInt(diasSeleccionados);
289 const result = {}; 381 const result = {};
290 382
291 clasificadores.forEach(({ key }) => { 383 clasificadores.forEach(({ key }) => {
292 const filtered = ejecucionClasificadores.filter(d => 384 const filtered = ejecucionClasificadores.filter(d =>
293 - d.transferencias === incluirTransferencias &&
294 - String(d.dias) === String(dias) &&
295 d.clasificador === key 385 d.clasificador === key
296 ); 386 );
297 387
...@@ -443,12 +533,43 @@ ...@@ -443,12 +533,43 @@
443 } 533 }
444 }); 534 });
445 535
536 + // Re-renderizar gráficos al cambiar de sección (porque display:none → ancho 0)
537 + $effect(() => {
538 + const seccion = seccionActiva;
539 + if (loading) return;
540 + setTimeout(() => {
541 + if (seccion === 1) {
542 + clasificadores.forEach(({ key }) => {
543 + if (barContainers[key]) renderizarBarras(key);
544 + });
545 + } else if (seccion === 2 && entidadesBarContainer) {
546 + renderizarEntidades();
547 + }
548 + }, 50);
549 + });
550 +
551 + // Recargar datos cuando cambia el período
552 + $effect(() => {
553 + const dias = diasSeleccionados === 'total' ? '0' : diasSeleccionados;
554 + recargarPorDias(dias);
555 + });
556 +
557 + async function recargarPorDias(dias) {
558 + try {
559 + const [entData, clasData] = await Promise.all([
560 + fetch(`/api/diario?tipo=entidades&dias=${dias}`).then(r => r.ok ? r.json() : []),
561 + fetch(`/api/diario?tipo=clasificadores&dias=${dias}`).then(r => r.ok ? r.json() : [])
562 + ]);
563 + ejecucionEntidades = entData;
564 + ejecucionClasificadores = clasData;
565 + } catch (e) {
566 + console.error('Error recargando datos:', e);
567 + }
568 + }
569 +
446 // Todas las entidades filtradas (sin límite) 570 // Todas las entidades filtradas (sin límite)
447 let entidadesTodas = $derived.by(() => { 571 let entidadesTodas = $derived.by(() => {
448 - const dias = diasSeleccionados === 'total' ? 0 : parseInt(diasSeleccionados);
449 let filtered = ejecucionEntidades.filter(d => 572 let filtered = ejecucionEntidades.filter(d =>
450 - d.transferencias === incluirTransferencias &&
451 - String(d.dias) === String(dias) &&
452 d.ejecucion >= rangoEjecucionMin && 573 d.ejecucion >= rangoEjecucionMin &&
453 d.ejecucion <= rangoEjecucionMax 574 d.ejecucion <= rangoEjecucionMax
454 ); 575 );
...@@ -473,9 +594,14 @@ ...@@ -473,9 +594,14 @@
473 594
474 // Agrupar timeline por entidad con sus programas 595 // Agrupar timeline por entidad con sus programas
475 let timelineByEntity = $derived.by(() => { 596 let timelineByEntity = $derived.by(() => {
597 + // Si hay búsqueda activa, usar entidades cargadas con sus programas
598 + if (busquedaTimeline.trim().length >= 2 && searchEntities.length > 0) {
599 + return searchEntities;
600 + }
601 +
602 + // Sin búsqueda: usar datos locales de timeline
476 if (timeline.length === 0) return []; 603 if (timeline.length === 0) return [];
477 604
478 - // Agrupar por entidad
479 const grouped = {}; 605 const grouped = {};
480 timeline.forEach(item => { 606 timeline.forEach(item => {
481 const key = item.entidad; 607 const key = item.entidad;
...@@ -496,24 +622,10 @@ ...@@ -496,24 +622,10 @@
496 }); 622 });
497 }); 623 });
498 624
499 - // Convertir a array y ordenar por total 625 + return Object.values(grouped)
500 - let result = Object.values(grouped) 626 + .map(g => ({ ...g, programas: g.programas.sort((a, b) => b.devengado - a.devengado) }))
501 - .map(g => ({ 627 + .sort((a, b) => b.total - a.total)
502 - ...g, 628 + .slice(0, 10);
503 - programas: g.programas.sort((a, b) => b.devengado - a.devengado)
504 - }))
505 - .sort((a, b) => b.total - a.total);
506 -
507 - // Filtrar por búsqueda si hay texto
508 - if (busquedaTimeline.trim()) {
509 - const q = busquedaTimeline.toLowerCase();
510 - result = result.filter(e =>
511 - e.entidad_desc_entidad?.toLowerCase().includes(q) ||
512 - e.entidad_desc_area?.toLowerCase().includes(q)
513 - );
514 - }
515 -
516 - return result.slice(0, 10);
517 }); 629 });
518 630
519 // Renderizar barras de entidades con D3 631 // Renderizar barras de entidades con D3
...@@ -666,9 +778,10 @@ ...@@ -666,9 +778,10 @@
666 .attr('y1', headerHeight) 778 .attr('y1', headerHeight)
667 .attr('y2', height) 779 .attr('y2', height)
668 .attr('stroke', chartColors.grid) 780 .attr('stroke', chartColors.grid)
669 - .attr('stroke-width', 0.5) 781 + .attr('stroke-width', 0.75)
670 - .attr('stroke-dasharray', '1,4') 782 + .attr('stroke-dasharray', 'none')
671 .attr('stroke-linecap', 'round') 783 .attr('stroke-linecap', 'round')
784 + .attr('opacity', 0.6)
672 .attr('opacity', 0.7) 785 .attr('opacity', 0.7)
673 .style('pointer-events', 'none'); // para que no interfiera con hover 786 .style('pointer-events', 'none'); // para que no interfiera con hover
674 } 787 }
...@@ -773,7 +886,7 @@ ...@@ -773,7 +886,7 @@
773 const colorFuera = isDark ? '#555550' : '#d0d0d0'; 886 const colorFuera = isDark ? '#555550' : '#d0d0d0';
774 const colorFueraFill = isDark ? '#3a3a38' : '#e8e8e8'; 887 const colorFueraFill = isDark ? '#3a3a38' : '#e8e8e8';
775 // Color de fill activo = mismo que las barras highlight 888 // Color de fill activo = mismo que las barras highlight
776 - const colorActivoFill = isDark ? '#C9A751' : '#F3EAD8'; 889 + const colorActivoFill = isDark ? '#C9A751' : '#4A8B6E';
777 const colorActivoLinea = isDark ? '#888880' : '#999999'; 890 const colorActivoLinea = isDark ? '#888880' : '#999999';
778 891
779 // Gráfico 1: Ejecución diaria (diferencia) 892 // Gráfico 1: Ejecución diaria (diferencia)
...@@ -949,24 +1062,33 @@ ...@@ -949,24 +1062,33 @@
949 <link href="https://fonts.googleapis.com/css2?family=DM+Serif+Display:ital@0;1&family=Instrument+Sans:wght@400;500;600&display=swap" rel="stylesheet" /> 1062 <link href="https://fonts.googleapis.com/css2?family=DM+Serif+Display:ital@0;1&family=Instrument+Sans:wght@400;500;600&display=swap" rel="stylesheet" />
950 </svelte:head> 1063 </svelte:head>
951 1064
952 -<div class="vista-diaria" style="font-family: var(--font-sans);" bind:this={vistaContainer} onscroll={detectarSeccion}> 1065 +<!-- Botón flotante de descargas -->
953 - <!-- Navegación interna fija --> 1066 +<a href="/descargas" class="fab-descargas" title="Ir a la página de descarga de datos">
954 - <nav class="nav-interna"> 1067 + <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
955 - {#each secciones as seccion, i} 1068 + <path d="M12 4v12"/><polyline points="8 12 12 16 16 12"/><path d="M4 18h16"/>
956 - <button 1069 + </svg>
957 - class="nav-item" 1070 + <span class="fab-tooltip">Ir a la página de descarga de datos</span>
958 - class:active={seccionActiva === i} 1071 +</a>
959 - onclick={() => irASeccion(i)} 1072 +
960 - > 1073 +<!-- Navegación interna fija - barra inferior (fuera del scroll container) -->
961 - <span class="nav-dot"></span> 1074 +<nav class="nav-bottom-bar">
962 - <span class="nav-label">{seccion.titulo}</span> 1075 + <button class="nav-arrow" onclick={() => irASeccion(Math.max(0, seccionActiva - 1))} disabled={seccionActiva === 0}>
963 - </button> 1076 + <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
964 - {/each} 1077 + <path d="M15 18l-6-6 6-6"/>
1078 + </svg>
1079 + </button>
1080 + <span class="nav-section-label">{secciones[seccionActiva]?.titulo || ''}</span>
1081 + <button class="nav-arrow" onclick={() => irASeccion(Math.min(secciones.length - 1, seccionActiva + 1))} disabled={seccionActiva === secciones.length - 1}>
1082 + <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1083 + <path d="M9 18l6-6-6-6"/>
1084 + </svg>
1085 + </button>
965 </nav> 1086 </nav>
966 1087
1088 +<div class="vista-diaria" style="font-family: var(--font-sans);" bind:this={vistaContainer}>
967 {#if loading} 1089 {#if loading}
968 <div class="loading"> 1090 <div class="loading">
969 - <p>Cargando datos...</p> 1091 + <div class="loader-spinner"></div>
970 </div> 1092 </div>
971 {:else if error} 1093 {:else if error}
972 <div class="error"> 1094 <div class="error">
...@@ -974,12 +1096,17 @@ ...@@ -974,12 +1096,17 @@
974 </div> 1096 </div>
975 {:else} 1097 {:else}
976 <!-- SECCIÓN 1: Programas (timeline) --> 1098 <!-- SECCIÓN 1: Programas (timeline) -->
977 - <section class="vista-seccion" id="programas"> 1099 + <section class="vista-seccion" class:seccion-activa={seccionActiva === 0} id="programas">
978 <div class="vista-contenido"> 1100 <div class="vista-contenido">
979 <div class="contenido-centrado"> 1101 <div class="contenido-centrado">
980 <header class="seccion-header seccion-header-centrado"> 1102 <header class="seccion-header seccion-header-centrado">
981 - <h1 class="seccion-titulo-grande">¿En qué programas?</h1> 1103 + {#if fechaNarrativa}
982 - <p class="seccion-descripcion">Detalle de programas y actividades ejecutadas en la última semana</p> 1104 + <p class="vista-fecha-titulo">Ejecución presupuestaria del {fechaNarrativa}</p>
1105 + {/if}
1106 + <h1 class="seccion-titulo-sub">Por programas, proyectos y entidad</h1>
1107 + {#if horaActualizacion}
1108 + <p class="vista-fecha-hora">Actualización: {horaActualizacion}</p>
1109 + {/if}
983 </header> 1110 </header>
984 1111
985 <div class="timeline-search"> 1112 <div class="timeline-search">
...@@ -990,11 +1117,18 @@ ...@@ -990,11 +1117,18 @@
990 type="text" 1117 type="text"
991 placeholder="Buscar entidad..." 1118 placeholder="Buscar entidad..."
992 bind:value={busquedaTimeline} 1119 bind:value={busquedaTimeline}
1120 + oninput={handleSearchInput}
993 class="timeline-search-input" 1121 class="timeline-search-input"
994 /> 1122 />
1123 + {#if searchLoading}
1124 + <span class="search-spinner"></span>
1125 + {/if}
995 </div> 1126 </div>
996 1127
997 <div class="timeline-cards"> 1128 <div class="timeline-cards">
1129 + {#if busquedaTimeline.trim().length >= 2 && !searchLoading && searchResults.length === 0}
1130 + <p style="text-align:center;color:var(--theme-texto);opacity:0.5;padding:24px 0;">Sin resultados para "{busquedaTimeline}"</p>
1131 + {/if}
998 {#each timelineByEntity as entidad} 1132 {#each timelineByEntity as entidad}
999 <div class="timeline-card"> 1133 <div class="timeline-card">
1000 <div class="timeline-card-header"> 1134 <div class="timeline-card-header">
...@@ -1038,7 +1172,7 @@ ...@@ -1038,7 +1172,7 @@
1038 </section> 1172 </section>
1039 1173
1040 <!-- SECCIÓN 2: Clasificadores --> 1174 <!-- SECCIÓN 2: Clasificadores -->
1041 - <section class="vista-seccion" id="clasificadores"> 1175 + <section class="vista-seccion" class:seccion-activa={seccionActiva === 1} id="clasificadores">
1042 <div class="vista-contenido"> 1176 <div class="vista-contenido">
1043 <div class="contenido-centrado"> 1177 <div class="contenido-centrado">
1044 <!-- Título de sección --> 1178 <!-- Título de sección -->
...@@ -1080,18 +1214,6 @@ ...@@ -1080,18 +1214,6 @@
1080 {label} 1214 {label}
1081 </button> 1215 </button>
1082 {/each} 1216 {/each}
1083 - <label class="toggle-transferencias">
1084 - <input type="checkbox" bind:checked={incluirTransferencias} />
1085 - <span class="toggle-label">Incluir transferencias</span>
1086 - <span class="tooltip-trigger">
1087 - <svg class="tooltip-icon" viewBox="0 0 20 20" fill="currentColor">
1088 - <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd" />
1089 - </svg>
1090 - <span class="tooltip-content">
1091 - Las transferencias son recursos que el gobierno central envía a gobiernos territoriales o al sector privado. Al desactivar esta opción se evita la doble contabilización, mostrando el gasto consolidado.
1092 - </span>
1093 - </span>
1094 - </label>
1095 </div> 1217 </div>
1096 </div> 1218 </div>
1097 </div> 1219 </div>
...@@ -1099,7 +1221,7 @@ ...@@ -1099,7 +1221,7 @@
1099 1221
1100 1222
1101 <!-- SECCIÓN 2: Entidades --> 1223 <!-- SECCIÓN 2: Entidades -->
1102 - <section class="vista-seccion" id="entidades"> 1224 + <section class="vista-seccion" class:seccion-activa={seccionActiva === 2} id="entidades">
1103 <div class="vista-contenido"> 1225 <div class="vista-contenido">
1104 <div class="contenido-centrado"> 1226 <div class="contenido-centrado">
1105 <!-- Título de sección --> 1227 <!-- Título de sección -->
...@@ -1110,7 +1232,7 @@ ...@@ -1110,7 +1232,7 @@
1110 <!-- Info de entidad seleccionada --> 1232 <!-- Info de entidad seleccionada -->
1111 {#if displayEntidad} 1233 {#if displayEntidad}
1112 <div class="entidad-info"> 1234 <div class="entidad-info">
1113 - <span class="entidad-pct">{formatearPorcentaje(displayEntidad.ejecucion)}</span> 1235 + <span class="entidad-pct">{formatearPorcentaje(displayEntidad.ejecucion)} <span class="entidad-pct-nota">del presupuesto 2026</span></span>
1114 <span class="entidad-nombre">{displayEntidad.entidad_desc_entidad}</span> 1236 <span class="entidad-nombre">{displayEntidad.entidad_desc_entidad}</span>
1115 <span class="entidad-area">{displayEntidad.entidad_desc_area}</span> 1237 <span class="entidad-area">{displayEntidad.entidad_desc_area}</span>
1116 </div> 1238 </div>
...@@ -1119,26 +1241,11 @@ ...@@ -1119,26 +1241,11 @@
1119 <!-- Barras de ejecución --> 1241 <!-- Barras de ejecución -->
1120 <div class="entidades-bar-container" bind:this={entidadesBarContainer}></div> 1242 <div class="entidades-bar-container" bind:this={entidadesBarContainer}></div>
1121 1243
1122 - <!-- Paginación --> 1244 + <p class="entidades-nota" style="text-align:center;margin-top:0.5rem;font-size:0.75rem;color:var(--theme-texto);opacity:0.6;">
1123 - <div class="entidades-paginacion"> 1245 + Top 10 entidades por ejecución presupuestaria
1124 - <p class="entidades-nota"> 1246 + </p>
1125 - Mostrando {entidadesFiltered.length} de {entidadesTodas.length} entidades
1126 - </p>
1127 - <div class="entidades-botones">
1128 - {#if entidadesVisibles > 10}
1129 - <button class="entidades-ver-mas" onclick={() => { entidadesVisibles = Math.max(10, entidadesVisibles - 10); }}>
1130 - Ver menos
1131 - </button>
1132 - {/if}
1133 - {#if entidadesFiltered.length < entidadesTodas.length}
1134 - <button class="entidades-ver-mas" onclick={() => { entidadesVisibles += 10; }}>
1135 - Ver {Math.min(10, entidadesTodas.length - entidadesFiltered.length)} más
1136 - </button>
1137 - {/if}
1138 - </div>
1139 - </div>
1140 1247
1141 - <!-- Controles centrados debajo (igual que sección 1) --> 1248 + <!-- Controles centrados debajo -->
1142 <div class="controles-footer"> 1249 <div class="controles-footer">
1143 {#each labelsPeriodo as { value, label }} 1250 {#each labelsPeriodo as { value, label }}
1144 <button 1251 <button
...@@ -1149,57 +1256,6 @@ ...@@ -1149,57 +1256,6 @@
1149 {label} 1256 {label}
1150 </button> 1257 </button>
1151 {/each} 1258 {/each}
1152 - <label class="toggle-transferencias">
1153 - <input type="checkbox" bind:checked={incluirTransferencias} />
1154 - <span class="toggle-label">Incluir transferencias</span>
1155 - <span class="tooltip-trigger">
1156 - <svg class="tooltip-icon" viewBox="0 0 20 20" fill="currentColor">
1157 - <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd" />
1158 - </svg>
1159 - <span class="tooltip-content">
1160 - Las transferencias son recursos que el gobierno central envía a gobiernos territoriales o al sector privado. Al desactivar esta opción se evita la doble contabilización, mostrando el gasto consolidado.
1161 - </span>
1162 - </span>
1163 - </label>
1164 - <div class="separador-control"></div>
1165 - <!-- Filtro de rango -->
1166 - <div class="rango-control-inline">
1167 - <label class="rango-label">Rango: {rangoEjecucionMin}% – {rangoEjecucionMax}%</label>
1168 - <div class="dual-slider dual-slider-inline" bind:this={sliderContainer}>
1169 - <div class="slider-track"></div>
1170 - <div
1171 - class="slider-range"
1172 - style="left: {rangoEjecucionMin}%; right: {100 - rangoEjecucionMax}%;"
1173 - onmousedown={handleRangeMouseDown}
1174 - role="slider"
1175 - tabindex="0"
1176 - ></div>
1177 - <input
1178 - type="range"
1179 - min="0"
1180 - max="100"
1181 - bind:value={rangoEjecucionMin}
1182 - class="slider-thumb slider-thumb-min"
1183 - oninput={() => {
1184 - if (rangoEjecucionMin > rangoEjecucionMax - 5) {
1185 - rangoEjecucionMin = rangoEjecucionMax - 5;
1186 - }
1187 - }}
1188 - />
1189 - <input
1190 - type="range"
1191 - min="0"
1192 - max="100"
1193 - bind:value={rangoEjecucionMax}
1194 - class="slider-thumb slider-thumb-max"
1195 - oninput={() => {
1196 - if (rangoEjecucionMax < rangoEjecucionMin + 5) {
1197 - rangoEjecucionMax = rangoEjecucionMin + 5;
1198 - }
1199 - }}
1200 - />
1201 - </div>
1202 - </div>
1203 </div> 1259 </div>
1204 </div> 1260 </div>
1205 </div> 1261 </div>
...@@ -1244,18 +1300,6 @@ ...@@ -1244,18 +1300,6 @@
1244 {label} 1300 {label}
1245 </button> 1301 </button>
1246 {/each} 1302 {/each}
1247 - <label class="toggle-transferencias">
1248 - <input type="checkbox" bind:checked={incluirTransferencias} />
1249 - <span class="toggle-label">Incluir transferencias</span>
1250 - <span class="tooltip-trigger">
1251 - <svg class="tooltip-icon" viewBox="0 0 20 20" fill="currentColor">
1252 - <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd" />
1253 - </svg>
1254 - <span class="tooltip-content">
1255 - Las transferencias son recursos que el gobierno central envía a gobiernos territoriales o al sector privado. Al desactivar esta opción se evita la doble contabilización, mostrando el gasto consolidado.
1256 - </span>
1257 - </span>
1258 - </label>
1259 </div> 1303 </div>
1260 </div> 1304 </div>
1261 </div> 1305 </div>
...@@ -1267,13 +1311,14 @@ ...@@ -1267,13 +1311,14 @@
1267 /* Variables de color para gráficos - Modo claro */ 1311 /* Variables de color para gráficos - Modo claro */
1268 :global(:root) { 1312 :global(:root) {
1269 --chart-bar-default: #e0e0e0; 1313 --chart-bar-default: #e0e0e0;
1270 - --chart-bar-highlight: #F3EAD8; 1314 + --chart-bar-highlight: #4A8B6E;
1271 --chart-bar-stroke: #b0b0b0; 1315 --chart-bar-stroke: #b0b0b0;
1272 --chart-bar-stroke-default: white; 1316 --chart-bar-stroke-default: white;
1273 --chart-track-bg: #f5f5f5; 1317 --chart-track-bg: #f5f5f5;
1274 - --chart-grid: #bbb; 1318 + --chart-grid: #999;
1275 --chart-label: #888; 1319 --chart-label: #888;
1276 - --chart-card-header: #f5f0e6; 1320 + --chart-card-header: #ffffff;
1321 + --chart-card-body: #fafafa;
1277 } 1322 }
1278 1323
1279 /* Variables de color para gráficos - Modo oscuro */ 1324 /* Variables de color para gráficos - Modo oscuro */
...@@ -1283,18 +1328,43 @@ ...@@ -1283,18 +1328,43 @@
1283 --chart-bar-stroke: #5a5a55; 1328 --chart-bar-stroke: #5a5a55;
1284 --chart-bar-stroke-default: #2a2a28; 1329 --chart-bar-stroke-default: #2a2a28;
1285 --chart-track-bg: #2a2a28; 1330 --chart-track-bg: #2a2a28;
1286 - --chart-grid: #4a4a45; 1331 + --chart-grid: #6a6a65;
1287 --chart-label: #8a8a85; 1332 --chart-label: #8a8a85;
1288 - --chart-card-header: #3a3835; 1333 + --chart-card-header: #2a2825;
1334 + --chart-card-body: #222220;
1289 } 1335 }
1290 1336
1291 .vista-diaria { 1337 .vista-diaria {
1292 - background: var(--theme-body); 1338 + background: #f2f2f4;
1293 color: var(--theme-titulo); 1339 color: var(--theme-titulo);
1294 - scroll-snap-type: y mandatory;
1295 - overflow-y: scroll;
1296 height: 100vh; 1340 height: 100vh;
1297 height: 100dvh; 1341 height: 100dvh;
1342 + overflow: hidden;
1343 + }
1344 + :global(.dark) .vista-diaria {
1345 + background: #1C1C1A;
1346 + }
1347 +
1348 + /* Header fecha */
1349 + .vista-fecha-titulo {
1350 + font-family: 'Qanelas', var(--font-sans);
1351 + font-size: 2rem;
1352 + font-weight: 600;
1353 + color: var(--theme-titulo);
1354 + margin: 0;
1355 + }
1356 + .seccion-titulo-sub {
1357 + font-family: 'Qanelas', var(--font-sans);
1358 + font-size: 1.25rem;
1359 + font-weight: 600;
1360 + color: var(--theme-titulo);
1361 + margin: 0.5rem 0 0.25rem;
1362 + }
1363 + .vista-fecha-hora {
1364 + font-size: 0.75rem;
1365 + color: var(--theme-titulo);
1366 + opacity: 0.8;
1367 + margin: 0.2rem 0 0;
1298 } 1368 }
1299 1369
1300 /* Loading/Error */ 1370 /* Loading/Error */
...@@ -1305,75 +1375,152 @@ ...@@ -1305,75 +1375,152 @@
1305 justify-content: center; 1375 justify-content: center;
1306 color: var(--theme-texto); 1376 color: var(--theme-texto);
1307 } 1377 }
1378 + .loader-spinner {
1379 + width: 32px;
1380 + height: 32px;
1381 + border: 3px solid rgba(128,128,128,0.2);
1382 + border-top-color: var(--theme-titulo);
1383 + border-radius: 50%;
1384 + animation: spin 0.7s linear infinite;
1385 + }
1308 1386
1309 .error { 1387 .error {
1310 color: #DB281C; 1388 color: #DB281C;
1311 } 1389 }
1312 1390
1313 - /* Navegación interna fija */ 1391 + /* Navegación interna fija - barra inferior */
1314 - .nav-interna { 1392 + /* Botón flotante descargas */
1393 + .fab-descargas {
1315 position: fixed; 1394 position: fixed;
1316 - right: 1.5rem; 1395 + bottom: 24px;
1396 + right: 24px;
1397 + width: 48px;
1398 + height: 48px;
1399 + border-radius: 14px;
1400 + background: rgba(255,255,255,0.85);
1401 + backdrop-filter: blur(16px);
1402 + -webkit-backdrop-filter: blur(16px);
1403 + border: 1px solid rgba(0,0,0,0.08);
1404 + box-shadow: 0 4px 16px rgba(0,0,0,0.1);
1405 + display: flex;
1406 + align-items: center;
1407 + justify-content: center;
1408 + color: var(--theme-titulo);
1409 + text-decoration: none;
1410 + z-index: 101;
1411 + transition: all 0.25s ease;
1412 + cursor: pointer;
1413 + }
1414 + .fab-descargas:hover {
1415 + background: rgba(255,255,255,0.95);
1416 + box-shadow: 0 6px 24px rgba(0,0,0,0.15);
1417 + transform: translateY(-2px);
1418 + }
1419 + :global(.dark) .fab-descargas {
1420 + background: rgba(28,28,26,0.85);
1421 + border-color: rgba(255,255,255,0.1);
1422 + box-shadow: 0 4px 16px rgba(0,0,0,0.3);
1423 + }
1424 + :global(.dark) .fab-descargas:hover {
1425 + background: rgba(42,42,40,0.95);
1426 + box-shadow: 0 6px 24px rgba(0,0,0,0.4);
1427 + }
1428 + .fab-tooltip {
1429 + position: absolute;
1430 + right: calc(100% + 10px);
1317 top: 50%; 1431 top: 50%;
1318 transform: translateY(-50%); 1432 transform: translateY(-50%);
1433 + background: rgba(0,0,0,0.85);
1434 + color: #fff;
1435 + font-size: 0.75rem;
1436 + font-weight: 500;
1437 + padding: 6px 12px;
1438 + border-radius: 8px;
1439 + white-space: nowrap;
1440 + opacity: 0;
1441 + pointer-events: none;
1442 + transition: opacity 0.2s;
1443 + }
1444 + .fab-descargas:hover .fab-tooltip {
1445 + opacity: 1;
1446 + }
1447 + :global(.dark) .fab-tooltip {
1448 + background: rgba(255,255,255,0.9);
1449 + color: #1C1C1A;
1450 + }
1451 +
1452 + .nav-bottom-bar {
1453 + position: fixed;
1454 + bottom: 20px;
1455 + left: 50%;
1456 + transform: translateX(-50%);
1319 display: flex; 1457 display: flex;
1320 - flex-direction: column; 1458 + align-items: center;
1321 - gap: 1rem; 1459 + gap: 12px;
1460 + padding: 8px 16px;
1461 + border-radius: 14px;
1462 + background: rgba(255,255,255,0.7);
1463 + backdrop-filter: blur(20px);
1464 + -webkit-backdrop-filter: blur(20px);
1465 + border: 1px solid rgba(0,0,0,0.08);
1322 z-index: 100; 1466 z-index: 100;
1467 + box-shadow: 0 4px 16px rgba(0,0,0,0.08);
1468 + }
1469 + :global(.dark) .nav-bottom-bar {
1470 + background: rgba(28,28,26,0.75);
1471 + border-color: rgba(255,255,255,0.1);
1472 + box-shadow: 0 4px 16px rgba(0,0,0,0.3);
1323 } 1473 }
1324 1474
1325 - .nav-item { 1475 + .nav-arrow {
1326 display: flex; 1476 display: flex;
1327 align-items: center; 1477 align-items: center;
1328 - gap: 0.5rem; 1478 + justify-content: center;
1329 - background: none; 1479 + width: 32px;
1480 + height: 32px;
1481 + border-radius: 8px;
1330 border: none; 1482 border: none;
1483 + background: rgba(0,0,0,0.06);
1484 + color: var(--theme-titulo);
1331 cursor: pointer; 1485 cursor: pointer;
1332 - padding: 0.25rem;
1333 - flex-direction: row-reverse;
1334 - }
1335 -
1336 - .nav-dot {
1337 - width: 10px;
1338 - height: 10px;
1339 - border-radius: 50%;
1340 - background: rgba(0, 0, 0, 0.2);
1341 transition: all 0.2s; 1486 transition: all 0.2s;
1342 } 1487 }
1343 - 1488 + .nav-arrow:hover:not(:disabled) {
1344 - :global(.dark) .nav-dot { 1489 + background: rgba(0,0,0,0.12);
1345 - background: rgba(255, 255, 255, 0.2);
1346 } 1490 }
1347 - 1491 + .nav-arrow:disabled {
1348 - .nav-item.active .nav-dot { 1492 + opacity: 0.25;
1349 - background: var(--theme-accent); 1493 + cursor: default;
1350 - transform: scale(1.2);
1351 } 1494 }
1352 - 1495 + :global(.dark) .nav-arrow {
1353 - .nav-label { 1496 + background: rgba(255,255,255,0.08);
1354 - font-size: 0.7rem; 1497 + }
1355 - color: var(--theme-texto); 1498 + :global(.dark) .nav-arrow:hover:not(:disabled) {
1356 - opacity: 0; 1499 + background: rgba(255,255,255,0.15);
1357 - transition: opacity 0.2s;
1358 - white-space: nowrap;
1359 } 1500 }
1360 1501
1361 - .nav-item:hover .nav-label, 1502 + .nav-section-label {
1362 - .nav-item.active .nav-label { 1503 + font-size: 0.8rem;
1363 - opacity: 1; 1504 + font-weight: 600;
1505 + color: var(--theme-titulo);
1506 + white-space: nowrap;
1507 + min-width: 120px;
1508 + text-align: center;
1364 } 1509 }
1365 1510
1366 - /* Secciones de 100vh - el navbar tiene ~56px de alto */ 1511 + /* Secciones - solo visible la activa */
1367 .vista-seccion { 1512 .vista-seccion {
1368 - min-height: 100vh; 1513 + height: 100vh;
1369 - min-height: 100dvh; 1514 + height: 100dvh;
1370 padding-top: 56px; 1515 padding-top: 56px;
1516 + padding-bottom: 70px;
1371 box-sizing: border-box; 1517 box-sizing: border-box;
1372 - display: flex; 1518 + display: none;
1373 flex-direction: column; 1519 flex-direction: column;
1374 - border-bottom: 1px solid var(--theme-borde); 1520 + overflow-y: auto;
1375 - scroll-snap-align: start; 1521 + }
1376 - scroll-snap-stop: always; 1522 + .vista-seccion.seccion-activa {
1523 + display: flex;
1377 } 1524 }
1378 1525
1379 1526
...@@ -1643,9 +1790,9 @@ ...@@ -1643,9 +1790,9 @@
1643 left: 0.75rem; 1790 left: 0.75rem;
1644 top: 50%; 1791 top: 50%;
1645 transform: translateY(-50%); 1792 transform: translateY(-50%);
1646 - width: 16px; 1793 + width: 18px;
1647 - height: 16px; 1794 + height: 18px;
1648 - color: var(--theme-texto); 1795 + color: var(--theme-titulo);
1649 opacity: 0.5; 1796 opacity: 0.5;
1650 pointer-events: none; 1797 pointer-events: none;
1651 } 1798 }
...@@ -1655,9 +1802,9 @@ ...@@ -1655,9 +1802,9 @@
1655 padding: 0.6rem 1rem 0.6rem 2.25rem; 1802 padding: 0.6rem 1rem 0.6rem 2.25rem;
1656 font-size: 0.8rem; 1803 font-size: 0.8rem;
1657 font-family: inherit; 1804 font-family: inherit;
1658 - border: 1px solid var(--theme-borde); 1805 + border: none;
1659 - border-radius: 20px; 1806 + border-radius: 12px;
1660 - background: var(--theme-body); 1807 + background: #ffffff;
1661 color: var(--theme-titulo); 1808 color: var(--theme-titulo);
1662 outline: none; 1809 outline: none;
1663 transition: border-color 0.2s; 1810 transition: border-color 0.2s;
...@@ -1673,10 +1820,25 @@ ...@@ -1673,10 +1820,25 @@
1673 color: var(--theme-accent); 1820 color: var(--theme-accent);
1674 } 1821 }
1675 1822
1823 + :global(.dark) .timeline-search-input {
1824 + background: #252523;
1825 + }
1676 .timeline-search-input::placeholder { 1826 .timeline-search-input::placeholder {
1677 - color: var(--theme-texto); 1827 + color: var(--theme-titulo);
1678 opacity: 0.5; 1828 opacity: 0.5;
1829 + font-size: 0.85rem;
1830 + }
1831 + .search-spinner {
1832 + width: 16px;
1833 + height: 16px;
1834 + border: 2px solid rgba(128,128,128,0.3);
1835 + border-top-color: var(--theme-texto);
1836 + border-radius: 50%;
1837 + animation: spin 0.6s linear infinite;
1838 + flex-shrink: 0;
1839 + margin-right: 8px;
1679 } 1840 }
1841 + @keyframes spin { to { transform: rotate(360deg); } }
1680 1842
1681 .timeline-cards { 1843 .timeline-cards {
1682 display: flex; 1844 display: flex;
...@@ -1705,6 +1867,10 @@ ...@@ -1705,6 +1867,10 @@
1705 justify-content: space-between; 1867 justify-content: space-between;
1706 align-items: flex-start; 1868 align-items: flex-start;
1707 gap: 1rem; 1869 gap: 1rem;
1870 + border-bottom: 1px solid rgba(0,0,0,0.06);
1871 + }
1872 + :global(.dark) .timeline-card-header {
1873 + border-bottom-color: rgba(255,255,255,0.08);
1708 } 1874 }
1709 1875
1710 .timeline-card-entity { 1876 .timeline-card-entity {
...@@ -1717,8 +1883,8 @@ ...@@ -1717,8 +1883,8 @@
1717 font-size: 0.65rem; 1883 font-size: 0.65rem;
1718 text-transform: uppercase; 1884 text-transform: uppercase;
1719 letter-spacing: 0.03em; 1885 letter-spacing: 0.03em;
1720 - color: var(--theme-texto); 1886 + color: var(--theme-titulo);
1721 - opacity: 0.6; 1887 + opacity: 0.7;
1722 } 1888 }
1723 1889
1724 .timeline-card-nombre { 1890 .timeline-card-nombre {
...@@ -1755,6 +1921,7 @@ ...@@ -1755,6 +1921,7 @@
1755 .timeline-card-body { 1921 .timeline-card-body {
1756 padding: 0.5rem 1rem; 1922 padding: 0.5rem 1rem;
1757 text-align: center; 1923 text-align: center;
1924 + background: var(--chart-card-body);
1758 } 1925 }
1759 1926
1760 .timeline-prog-row { 1927 .timeline-prog-row {
...@@ -1785,8 +1952,8 @@ ...@@ -1785,8 +1952,8 @@
1785 1952
1786 .timeline-prog-programa { 1953 .timeline-prog-programa {
1787 font-size: 0.6rem; 1954 font-size: 0.6rem;
1788 - color: var(--theme-texto); 1955 + color: var(--theme-titulo);
1789 - opacity: 0.5; 1956 + opacity: 0.7;
1790 line-height: 1.2; 1957 line-height: 1.2;
1791 white-space: nowrap; 1958 white-space: nowrap;
1792 overflow: hidden; 1959 overflow: hidden;
...@@ -1848,6 +2015,12 @@ ...@@ -1848,6 +2015,12 @@
1848 color: var(--theme-titulo); 2015 color: var(--theme-titulo);
1849 line-height: 1; 2016 line-height: 1;
1850 } 2017 }
2018 + .entidad-pct-nota {
2019 + font-size: 0.85rem;
2020 + font-weight: 500;
2021 + color: var(--theme-texto);
2022 + opacity: 0.7;
2023 + }
1851 2024
1852 .entidad-nombre { 2025 .entidad-nombre {
1853 font-size: 1rem; 2026 font-size: 1rem;
...@@ -2066,25 +2239,8 @@ ...@@ -2066,25 +2239,8 @@
2066 } 2239 }
2067 2240
2068 /* Responsive */ 2241 /* Responsive */
2069 - @media (max-width: 900px) {
2070 - .nav-interna {
2071 - right: 0.75rem;
2072 - }
2073 -
2074 - .nav-label {
2075 - display: none;
2076 - }
2077 - }
2078 2242
2079 @media (max-width: 768px) { 2243 @media (max-width: 768px) {
2080 - .vista-diaria {
2081 - scroll-snap-type: y proximity;
2082 - }
2083 -
2084 - .vista-seccion {
2085 - scroll-snap-stop: normal;
2086 - }
2087 -
2088 .vista-contenido { 2244 .vista-contenido {
2089 padding: 1rem; 2245 padding: 1rem;
2090 } 2246 }
......