Showing
1 changed file
with
474 additions
and
45 deletions
| ... | @@ -257,31 +257,216 @@ | ... | @@ -257,31 +257,216 @@ |
| 257 | const twTop2 = tweened(0, tweenOptions); | 257 | const twTop2 = tweened(0, tweenOptions); |
| 258 | const twTop3 = tweened(0, tweenOptions); | 258 | const twTop3 = tweened(0, tweenOptions); |
| 259 | 259 | ||
| 260 | - // Comparación (placeholder por ahora - TODO: implementar) | 260 | + // ══════════════════════════════════════════════════════════════ |
| 261 | + // COMPARACIÓN - Datos reales de Supabase | ||
| 262 | + // ══════════════════════════════════════════════════════════════ | ||
| 261 | const twMontoA = tweened(0, tweenOptions); | 263 | const twMontoA = tweened(0, tweenOptions); |
| 262 | const twMontoB = tweened(0, tweenOptions); | 264 | const twMontoB = tweened(0, tweenOptions); |
| 263 | const twPerCapitaA = tweened(0, tweenOptions); | 265 | const twPerCapitaA = tweened(0, tweenOptions); |
| 264 | const twPerCapitaB = tweened(0, tweenOptions); | 266 | const twPerCapitaB = tweened(0, tweenOptions); |
| 265 | 267 | ||
| 266 | let hoveredYearComparar = $state(null); | 268 | let hoveredYearComparar = $state(null); |
| 267 | - let entidadA = $state(0); | 269 | + let loadingComparacion = $state(false); |
| 268 | - let entidadB = $state(1); | 270 | + |
| 269 | - let displayPeriodoComparar = $derived( | 271 | + // Entidades seleccionadas para comparar |
| 270 | - hoveredYearComparar ? hoveredYearComparar : `${años[0]}-${años[años.length - 1]}` | 272 | + let entidadCompararA = $state(null); |
| 271 | - ); | 273 | + let entidadCompararB = $state(null); |
| 274 | + | ||
| 275 | + // Datos cargados de cada entidad | ||
| 276 | + let datosCompararA = $state({ anual: [], historico: null }); | ||
| 277 | + let datosCompararB = $state({ anual: [], historico: null }); | ||
| 278 | + | ||
| 279 | + // Dropdown states para comparación | ||
| 280 | + let dropdownAOpen = $state(false); | ||
| 281 | + let dropdownBOpen = $state(false); | ||
| 282 | + let searchA = $state(''); | ||
| 283 | + let searchB = $state(''); | ||
| 284 | + | ||
| 285 | + // Filtrar entidades para cada dropdown | ||
| 286 | + let filteredEntidadesA = $derived.by(() => { | ||
| 287 | + if (!searchA || searchA.length < 2) return entidades.slice(0, 20); | ||
| 288 | + const q = searchA.toLowerCase(); | ||
| 289 | + return entidades.filter(e => | ||
| 290 | + e.entidad_desc?.toLowerCase().includes(q) || | ||
| 291 | + e.entidad?.toString().includes(q) | ||
| 292 | + ).slice(0, 30); | ||
| 293 | + }); | ||
| 294 | + | ||
| 295 | + let filteredEntidadesB = $derived.by(() => { | ||
| 296 | + if (!searchB || searchB.length < 2) return entidades.slice(0, 20); | ||
| 297 | + const q = searchB.toLowerCase(); | ||
| 298 | + return entidades.filter(e => | ||
| 299 | + e.entidad_desc?.toLowerCase().includes(q) || | ||
| 300 | + e.entidad?.toString().includes(q) | ||
| 301 | + ).slice(0, 30); | ||
| 302 | + }); | ||
| 303 | + | ||
| 304 | + // Cargar datos de una entidad para comparación | ||
| 305 | + async function loadDatosEntidad(entidad) { | ||
| 306 | + const { data, error } = await supabase | ||
| 307 | + .schema('ppto') | ||
| 308 | + .from('vista_objeto_entidad') | ||
| 309 | + .select('*') | ||
| 310 | + .eq('objeto', objetoCodigo) | ||
| 311 | + .eq('nivel', objetoNivel) | ||
| 312 | + .eq('entidad', entidad.entidad) | ||
| 313 | + .order('gestion'); | ||
| 314 | + | ||
| 315 | + if (data) { | ||
| 316 | + return { | ||
| 317 | + anual: data.filter(d => d.gestion > 0), | ||
| 318 | + historico: data.find(d => d.gestion === 0) || null | ||
| 319 | + }; | ||
| 320 | + } | ||
| 321 | + return { anual: [], historico: null }; | ||
| 322 | + } | ||
| 323 | + | ||
| 324 | + // Seleccionar entidad A | ||
| 325 | + async function selectEntidadA(entidad) { | ||
| 326 | + entidadCompararA = entidad; | ||
| 327 | + dropdownAOpen = false; | ||
| 328 | + searchA = ''; | ||
| 329 | + loadingComparacion = true; | ||
| 330 | + datosCompararA = await loadDatosEntidad(entidad); | ||
| 331 | + loadingComparacion = false; | ||
| 332 | + } | ||
| 333 | + | ||
| 334 | + // Seleccionar entidad B | ||
| 335 | + async function selectEntidadB(entidad) { | ||
| 336 | + entidadCompararB = entidad; | ||
| 337 | + dropdownBOpen = false; | ||
| 338 | + searchB = ''; | ||
| 339 | + loadingComparacion = true; | ||
| 340 | + datosCompararB = await loadDatosEntidad(entidad); | ||
| 341 | + loadingComparacion = false; | ||
| 342 | + } | ||
| 343 | + | ||
| 344 | + // Selector de rango de años | ||
| 345 | + let rangoAños = $state('10'); // '5', '10', '15', 'todo' | ||
| 272 | 346 | ||
| 273 | - // Datos dummy para comparación (TODO: conectar a Supabase) | 347 | + // Años comunes entre ambas entidades |
| 274 | - const entidadesComparables = [ | 348 | + let añosComunesTotales = $derived.by(() => { |
| 275 | - { id: 0, nombre: 'Entidad A', datos: [], porcentajeObjeto: 0, ranking: 1 }, | 349 | + if (!datosCompararA.anual.length || !datosCompararB.anual.length) return []; |
| 276 | - { id: 1, nombre: 'Entidad B', datos: [], porcentajeObjeto: 0, ranking: 2 } | 350 | + const añosA = new Set(datosCompararA.anual.map(d => d.gestion)); |
| 351 | + const añosB = new Set(datosCompararB.anual.map(d => d.gestion)); | ||
| 352 | + return [...añosA].filter(a => añosB.has(a)).sort((a, b) => a - b); | ||
| 353 | + }); | ||
| 354 | + | ||
| 355 | + // Años filtrados según el rango seleccionado | ||
| 356 | + let añosComparacion = $derived.by(() => { | ||
| 357 | + if (rangoAños === 'todo') return añosComunesTotales; | ||
| 358 | + const n = parseInt(rangoAños); | ||
| 359 | + return añosComunesTotales.slice(-n); | ||
| 360 | + }); | ||
| 361 | + | ||
| 362 | + // Datos formateados para el gráfico | ||
| 363 | + let datosGraficoA = $derived.by(() => { | ||
| 364 | + const map = new Map(datosCompararA.anual.map(d => [d.gestion, d])); | ||
| 365 | + return añosComparacion.map(año => map.get(año) || { per_capita: 0, monto: 0 }); | ||
| 366 | + }); | ||
| 367 | + | ||
| 368 | + let datosGraficoB = $derived.by(() => { | ||
| 369 | + const map = new Map(datosCompararB.anual.map(d => [d.gestion, d])); | ||
| 370 | + return añosComparacion.map(año => map.get(año) || { per_capita: 0, monto: 0 }); | ||
| 371 | + }); | ||
| 372 | + | ||
| 373 | + // Máximo para escala del gráfico | ||
| 374 | + let maxComparacion = $derived.by(() => { | ||
| 375 | + const allPerCapita = [ | ||
| 376 | + ...datosGraficoA.map(d => d.per_capita || 0), | ||
| 377 | + ...datosGraficoB.map(d => d.per_capita || 0) | ||
| 277 | ]; | 378 | ]; |
| 278 | - let entA = $derived(entidadesComparables[entidadA] || entidadesComparables[0]); | 379 | + return Math.max(...allPerCapita, 1) * 1.1; |
| 279 | - let entB = $derived(entidadesComparables[entidadB] || entidadesComparables[1]); | 380 | + }); |
| 280 | - let maxComparacion = $state(100); | 381 | + |
| 281 | - let años = $state([2020, 2021, 2022, 2023, 2024]); | 382 | + // Período para mostrar |
| 383 | + let displayPeriodoComparar = $derived.by(() => { | ||
| 384 | + if (hoveredYearComparar) return hoveredYearComparar; | ||
| 385 | + if (añosComparacion.length > 0) { | ||
| 386 | + return `${añosComparacion[0]}-${añosComparacion[añosComparacion.length - 1]}`; | ||
| 387 | + } | ||
| 388 | + return 'Selecciona entidades'; | ||
| 389 | + }); | ||
| 390 | + | ||
| 391 | + // Valores reactivos para comparación (peso y ranking) | ||
| 392 | + let displayPropA = $derived.by(() => { | ||
| 393 | + if (hoveredYearComparar) { | ||
| 394 | + const idx = añosComparacion.indexOf(hoveredYearComparar); | ||
| 395 | + if (idx >= 0) return datosGraficoA[idx]?.prop || 0; | ||
| 396 | + } | ||
| 397 | + return datosCompararA.historico?.prop || 0; | ||
| 398 | + }); | ||
| 399 | + | ||
| 400 | + let displayPropB = $derived.by(() => { | ||
| 401 | + if (hoveredYearComparar) { | ||
| 402 | + const idx = añosComparacion.indexOf(hoveredYearComparar); | ||
| 403 | + if (idx >= 0) return datosGraficoB[idx]?.prop || 0; | ||
| 404 | + } | ||
| 405 | + return datosCompararB.historico?.prop || 0; | ||
| 406 | + }); | ||
| 407 | + | ||
| 408 | + let displayRankingA = $derived.by(() => { | ||
| 409 | + if (hoveredYearComparar) { | ||
| 410 | + const idx = añosComparacion.indexOf(hoveredYearComparar); | ||
| 411 | + if (idx >= 0) return datosGraficoA[idx]?.ranking || '-'; | ||
| 412 | + } | ||
| 413 | + return datosCompararA.historico?.ranking || '-'; | ||
| 414 | + }); | ||
| 415 | + | ||
| 416 | + let displayRankingB = $derived.by(() => { | ||
| 417 | + if (hoveredYearComparar) { | ||
| 418 | + const idx = añosComparacion.indexOf(hoveredYearComparar); | ||
| 419 | + if (idx >= 0) return datosGraficoB[idx]?.ranking || '-'; | ||
| 420 | + } | ||
| 421 | + return datosCompararB.historico?.ranking || '-'; | ||
| 422 | + }); | ||
| 423 | + | ||
| 424 | + let displayNEntidadesA = $derived.by(() => { | ||
| 425 | + if (hoveredYearComparar) { | ||
| 426 | + const idx = añosComparacion.indexOf(hoveredYearComparar); | ||
| 427 | + if (idx >= 0) return datosGraficoA[idx]?.n_entidades || '-'; | ||
| 428 | + } | ||
| 429 | + return datosCompararA.historico?.n_entidades || '-'; | ||
| 430 | + }); | ||
| 431 | + | ||
| 432 | + let displayNEntidadesB = $derived.by(() => { | ||
| 433 | + if (hoveredYearComparar) { | ||
| 434 | + const idx = añosComparacion.indexOf(hoveredYearComparar); | ||
| 435 | + if (idx >= 0) return datosGraficoB[idx]?.n_entidades || '-'; | ||
| 436 | + } | ||
| 437 | + return datosCompararB.historico?.n_entidades || '-'; | ||
| 438 | + }); | ||
| 439 | + | ||
| 440 | + // Colores | ||
| 282 | let colorA = '#2a9d8f'; | 441 | let colorA = '#2a9d8f'; |
| 283 | let colorB = '#f4a261'; | 442 | let colorB = '#f4a261'; |
| 284 | 443 | ||
| 444 | + // Actualizar tweened de comparación | ||
| 445 | + $effect(() => { | ||
| 446 | + const year = hoveredYearComparar; | ||
| 447 | + const idx = year ? añosComparacion.indexOf(year) : -1; | ||
| 448 | + | ||
| 449 | + let montoA, montoB, perCapitaA, perCapitaB; | ||
| 450 | + | ||
| 451 | + if (idx >= 0) { | ||
| 452 | + montoA = toNumber(datosGraficoA[idx]?.monto); | ||
| 453 | + montoB = toNumber(datosGraficoB[idx]?.monto); | ||
| 454 | + perCapitaA = toNumber(datosGraficoA[idx]?.per_capita); | ||
| 455 | + perCapitaB = toNumber(datosGraficoB[idx]?.per_capita); | ||
| 456 | + } else { | ||
| 457 | + // Histórico o suma | ||
| 458 | + montoA = toNumber(datosCompararA.historico?.monto); | ||
| 459 | + montoB = toNumber(datosCompararB.historico?.monto); | ||
| 460 | + perCapitaA = toNumber(datosCompararA.historico?.per_capita); | ||
| 461 | + perCapitaB = toNumber(datosCompararB.historico?.per_capita); | ||
| 462 | + } | ||
| 463 | + | ||
| 464 | + twMontoA.set(montoA); | ||
| 465 | + twMontoB.set(montoB); | ||
| 466 | + twPerCapitaA.set(perCapitaA); | ||
| 467 | + twPerCapitaB.set(perCapitaB); | ||
| 468 | + }); | ||
| 469 | + | ||
| 285 | // Helper para asegurar que siempre tengamos un número | 470 | // Helper para asegurar que siempre tengamos un número |
| 286 | function toNumber(val) { | 471 | function toNumber(val) { |
| 287 | const n = parseFloat(val); | 472 | const n = parseFloat(val); |
| ... | @@ -350,6 +535,13 @@ | ... | @@ -350,6 +535,13 @@ |
| 350 | if (entityDropdownOpen && !e.target.closest('.entity-dropdown-container')) { | 535 | if (entityDropdownOpen && !e.target.closest('.entity-dropdown-container')) { |
| 351 | entityDropdownOpen = false; | 536 | entityDropdownOpen = false; |
| 352 | } | 537 | } |
| 538 | + // Dropdowns de comparación | ||
| 539 | + if (dropdownAOpen && !e.target.closest('.comparador-dropdown-container')) { | ||
| 540 | + dropdownAOpen = false; | ||
| 541 | + } | ||
| 542 | + if (dropdownBOpen && !e.target.closest('.comparador-dropdown-container')) { | ||
| 543 | + dropdownBOpen = false; | ||
| 544 | + } | ||
| 353 | }; | 545 | }; |
| 354 | document.addEventListener('click', handleClickOutside); | 546 | document.addEventListener('click', handleClickOutside); |
| 355 | 547 | ||
| ... | @@ -614,26 +806,105 @@ | ... | @@ -614,26 +806,105 @@ |
| 614 | {:else} | 806 | {:else} |
| 615 | <!-- VISTA COMPARAR --> | 807 | <!-- VISTA COMPARAR --> |
| 616 | 808 | ||
| 617 | - <!-- Selectores de entidades --> | 809 | + <!-- Selectores de entidades con búsqueda --> |
| 618 | <div class="comparador-selectors"> | 810 | <div class="comparador-selectors"> |
| 619 | - <div class="selector-group"> | 811 | + <div class="selector-group comparador-dropdown-container"> |
| 620 | <span class="selector-dot" style="background: {colorA}"></span> | 812 | <span class="selector-dot" style="background: {colorA}"></span> |
| 621 | - <select bind:value={entidadA}> | 813 | + <div class="relative" style="flex: 1;"> |
| 622 | - {#each entidadesComparables as ent} | 814 | + <button class="selector-btn comparador-btn" onclick={() => { dropdownAOpen = !dropdownAOpen; dropdownBOpen = false; }}> |
| 623 | - <option value={ent.id} disabled={ent.id === entidadB}>{ent.nombre}</option> | 815 | + <span class="truncate"> |
| 816 | + {entidadCompararA ? entidadCompararA.entidad_desc : 'Seleccionar entidad'} | ||
| 817 | + </span> | ||
| 818 | + <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | ||
| 819 | + <path d="M6 9l6 6 6-6"/> | ||
| 820 | + </svg> | ||
| 821 | + </button> | ||
| 822 | + {#if dropdownAOpen} | ||
| 823 | + <div class="dropdown-panel comparador-panel"> | ||
| 824 | + <div class="dropdown-search"> | ||
| 825 | + <input type="text" bind:value={searchA} placeholder="Buscar entidad..." class="search-input" /> | ||
| 826 | + </div> | ||
| 827 | + <div class="dropdown-list"> | ||
| 828 | + {#each filteredEntidadesA as entity} | ||
| 829 | + <button | ||
| 830 | + class="dropdown-item" | ||
| 831 | + class:active={entidadCompararA?.entidad === entity.entidad} | ||
| 832 | + disabled={entidadCompararB?.entidad === entity.entidad} | ||
| 833 | + onclick={() => selectEntidadA(entity)} | ||
| 834 | + > | ||
| 835 | + <span class="entity-code">{entity.entidad}</span> | ||
| 836 | + <span class="truncate">{entity.entidad_desc}</span> | ||
| 837 | + </button> | ||
| 624 | {/each} | 838 | {/each} |
| 625 | - </select> | ||
| 626 | </div> | 839 | </div> |
| 840 | + </div> | ||
| 841 | + {/if} | ||
| 842 | + </div> | ||
| 843 | + </div> | ||
| 844 | + | ||
| 627 | <span class="vs">vs</span> | 845 | <span class="vs">vs</span> |
| 628 | - <div class="selector-group"> | 846 | + |
| 847 | + <div class="selector-group comparador-dropdown-container"> | ||
| 629 | <span class="selector-dot" style="background: {colorB}"></span> | 848 | <span class="selector-dot" style="background: {colorB}"></span> |
| 630 | - <select bind:value={entidadB}> | 849 | + <div class="relative" style="flex: 1;"> |
| 631 | - {#each entidadesComparables as ent} | 850 | + <button class="selector-btn comparador-btn" onclick={() => { dropdownBOpen = !dropdownBOpen; dropdownAOpen = false; }}> |
| 632 | - <option value={ent.id} disabled={ent.id === entidadA}>{ent.nombre}</option> | 851 | + <span class="truncate"> |
| 852 | + {entidadCompararB ? entidadCompararB.entidad_desc : 'Seleccionar entidad'} | ||
| 853 | + </span> | ||
| 854 | + <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | ||
| 855 | + <path d="M6 9l6 6 6-6"/> | ||
| 856 | + </svg> | ||
| 857 | + </button> | ||
| 858 | + {#if dropdownBOpen} | ||
| 859 | + <div class="dropdown-panel comparador-panel"> | ||
| 860 | + <div class="dropdown-search"> | ||
| 861 | + <input type="text" bind:value={searchB} placeholder="Buscar entidad..." class="search-input" /> | ||
| 862 | + </div> | ||
| 863 | + <div class="dropdown-list"> | ||
| 864 | + {#each filteredEntidadesB as entity} | ||
| 865 | + <button | ||
| 866 | + class="dropdown-item" | ||
| 867 | + class:active={entidadCompararB?.entidad === entity.entidad} | ||
| 868 | + disabled={entidadCompararA?.entidad === entity.entidad} | ||
| 869 | + onclick={() => selectEntidadB(entity)} | ||
| 870 | + > | ||
| 871 | + <span class="entity-code">{entity.entidad}</span> | ||
| 872 | + <span class="truncate">{entity.entidad_desc}</span> | ||
| 873 | + </button> | ||
| 633 | {/each} | 874 | {/each} |
| 634 | - </select> | ||
| 635 | </div> | 875 | </div> |
| 636 | </div> | 876 | </div> |
| 877 | + {/if} | ||
| 878 | + </div> | ||
| 879 | + </div> | ||
| 880 | + </div> | ||
| 881 | + | ||
| 882 | + <!-- Mensaje si no hay entidades seleccionadas --> | ||
| 883 | + {#if !entidadCompararA || !entidadCompararB} | ||
| 884 | + <div class="comparador-placeholder"> | ||
| 885 | + <p>Selecciona dos entidades para comparar su gasto en <strong>{objeto.nombre}</strong></p> | ||
| 886 | + </div> | ||
| 887 | + {:else if loadingComparacion} | ||
| 888 | + <div class="comparador-placeholder"> | ||
| 889 | + <p>Cargando datos...</p> | ||
| 890 | + </div> | ||
| 891 | + {:else if añosComparacion.length === 0} | ||
| 892 | + <div class="comparador-placeholder"> | ||
| 893 | + <p>Las entidades seleccionadas no tienen años en común para este objeto.</p> | ||
| 894 | + </div> | ||
| 895 | + {:else} | ||
| 896 | + <!-- Selector de rango de años --> | ||
| 897 | + {#if añosComunesTotales.length > 10} | ||
| 898 | + <div class="rango-selector"> | ||
| 899 | + <span class="rango-label">Mostrar:</span> | ||
| 900 | + <div class="rango-options"> | ||
| 901 | + <button class="rango-btn" class:active={rangoAños === '5'} onclick={() => rangoAños = '5'}>5 años</button> | ||
| 902 | + <button class="rango-btn" class:active={rangoAños === '10'} onclick={() => rangoAños = '10'}>10 años</button> | ||
| 903 | + <button class="rango-btn" class:active={rangoAños === '15'} onclick={() => rangoAños = '15'}>15 años</button> | ||
| 904 | + <button class="rango-btn" class:active={rangoAños === 'todo'} onclick={() => rangoAños = 'todo'}>Todo ({añosComunesTotales.length})</button> | ||
| 905 | + </div> | ||
| 906 | + </div> | ||
| 907 | + {/if} | ||
| 637 | 908 | ||
| 638 | <!-- Gráfico comparativo --> | 909 | <!-- Gráfico comparativo --> |
| 639 | <div class="chart-section"> | 910 | <div class="chart-section"> |
| ... | @@ -642,19 +913,20 @@ | ... | @@ -642,19 +913,20 @@ |
| 642 | <span class="chart-period">{displayPeriodoComparar}</span> | 913 | <span class="chart-period">{displayPeriodoComparar}</span> |
| 643 | </div> | 914 | </div> |
| 644 | 915 | ||
| 645 | - <div class="chart-wrapper"> | 916 | + <div class="chart-wrapper chart-wrapper-scroll"> |
| 646 | - <div class="y-axis"> | 917 | + <div class="y-axis y-axis-sticky"> |
| 647 | - <span class="y-label">Bs {formatMonto(maxComparacion)}</span> | 918 | + <span class="y-label">Bs {formatMonto(Math.round(maxComparacion))}</span> |
| 648 | <span class="y-label">Bs {formatMonto(Math.round(maxComparacion / 2))}</span> | 919 | <span class="y-label">Bs {formatMonto(Math.round(maxComparacion / 2))}</span> |
| 649 | <span class="y-label">Bs 0</span> | 920 | <span class="y-label">Bs 0</span> |
| 650 | </div> | 921 | </div> |
| 651 | 922 | ||
| 652 | <div | 923 | <div |
| 653 | class="chart chart-comparar" | 924 | class="chart chart-comparar" |
| 925 | + class:chart-muchos-años={añosComparacion.length > 15} | ||
| 654 | onmouseleave={() => hoveredYearComparar = null} | 926 | onmouseleave={() => hoveredYearComparar = null} |
| 655 | role="group" | 927 | role="group" |
| 656 | > | 928 | > |
| 657 | - {#each años as año, i} | 929 | + {#each añosComparacion as año, i} |
| 658 | <div | 930 | <div |
| 659 | class="bar-group" | 931 | class="bar-group" |
| 660 | class:active={hoveredYearComparar === año} | 932 | class:active={hoveredYearComparar === año} |
| ... | @@ -665,11 +937,11 @@ | ... | @@ -665,11 +937,11 @@ |
| 665 | <div class="bars-pair"> | 937 | <div class="bars-pair"> |
| 666 | <div | 938 | <div |
| 667 | class="bar bar-a" | 939 | class="bar bar-a" |
| 668 | - style="height: {(entA.datos[i].perCapita / maxComparacion) * 100}%; --bar-color: {colorA}" | 940 | + style="height: {((datosGraficoA[i]?.per_capita || 0) / maxComparacion) * 100}%; --bar-color: {colorA}" |
| 669 | ></div> | 941 | ></div> |
| 670 | <div | 942 | <div |
| 671 | class="bar bar-b" | 943 | class="bar bar-b" |
| 672 | - style="height: {(entB.datos[i].perCapita / maxComparacion) * 100}%; --bar-color: {colorB}" | 944 | + style="height: {((datosGraficoB[i]?.per_capita || 0) / maxComparacion) * 100}%; --bar-color: {colorB}" |
| 673 | ></div> | 945 | ></div> |
| 674 | </div> | 946 | </div> |
| 675 | <span class="bar-label visible" class:highlight={hoveredYearComparar === año}>{año}</span> | 947 | <span class="bar-label visible" class:highlight={hoveredYearComparar === año}>{año}</span> |
| ... | @@ -679,8 +951,8 @@ | ... | @@ -679,8 +951,8 @@ |
| 679 | </div> | 951 | </div> |
| 680 | 952 | ||
| 681 | <div class="chart-legend"> | 953 | <div class="chart-legend"> |
| 682 | - <span class="legend-item"><span class="legend-dot" style="background: {colorA}"></span>{entA.nombre}</span> | 954 | + <span class="legend-item"><span class="legend-dot" style="background: {colorA}"></span>{entidadCompararA.entidad_desc}</span> |
| 683 | - <span class="legend-item"><span class="legend-dot" style="background: {colorB}"></span>{entB.nombre}</span> | 955 | + <span class="legend-item"><span class="legend-dot" style="background: {colorB}"></span>{entidadCompararB.entidad_desc}</span> |
| 684 | </div> | 956 | </div> |
| 685 | </div> | 957 | </div> |
| 686 | 958 | ||
| ... | @@ -689,34 +961,35 @@ | ... | @@ -689,34 +961,35 @@ |
| 689 | <!-- Header --> | 961 | <!-- Header --> |
| 690 | <div class="tabla-header"> | 962 | <div class="tabla-header"> |
| 691 | <span class="tabla-label"></span> | 963 | <span class="tabla-label"></span> |
| 692 | - <span class="tabla-val" style="color: {colorA}">{entA.nombre}</span> | 964 | + <span class="tabla-val" style="color: {colorA}">{entidadCompararA.entidad_desc?.slice(0, 20)}{entidadCompararA.entidad_desc?.length > 20 ? '...' : ''}</span> |
| 693 | - <span class="tabla-val" style="color: {colorB}">{entB.nombre}</span> | 965 | + <span class="tabla-val" style="color: {colorB}">{entidadCompararB.entidad_desc?.slice(0, 20)}{entidadCompararB.entidad_desc?.length > 20 ? '...' : ''}</span> |
| 694 | </div> | 966 | </div> |
| 695 | <!-- Gasto --> | 967 | <!-- Gasto --> |
| 696 | <div class="tabla-row"> | 968 | <div class="tabla-row"> |
| 697 | - <span class="tabla-label">{hoveredYearComparar ? `Gasto ${hoveredYearComparar}` : 'Gasto 2020-2024'}</span> | 969 | + <span class="tabla-label">{hoveredYearComparar ? `Gasto ${hoveredYearComparar}` : `Gasto ${añosComparacion[0]}-${añosComparacion[añosComparacion.length-1]}`}</span> |
| 698 | <span class="tabla-val">Bs {formatMontoLargo(Math.round($twMontoA))}</span> | 970 | <span class="tabla-val">Bs {formatMontoLargo(Math.round($twMontoA))}</span> |
| 699 | <span class="tabla-val">Bs {formatMontoLargo(Math.round($twMontoB))}</span> | 971 | <span class="tabla-val">Bs {formatMontoLargo(Math.round($twMontoB))}</span> |
| 700 | </div> | 972 | </div> |
| 701 | <!-- Per cápita --> | 973 | <!-- Per cápita --> |
| 702 | <div class="tabla-row"> | 974 | <div class="tabla-row"> |
| 703 | <span class="tabla-label">{hoveredYearComparar ? `Por persona ${hoveredYearComparar}` : 'Por persona (prom.)'}</span> | 975 | <span class="tabla-label">{hoveredYearComparar ? `Por persona ${hoveredYearComparar}` : 'Por persona (prom.)'}</span> |
| 704 | - <span class="tabla-val">Bs {Math.round($twPerCapitaA).toLocaleString()}</span> | 976 | + <span class="tabla-val">Bs {formatPerCapita($twPerCapitaA)}</span> |
| 705 | - <span class="tabla-val">Bs {Math.round($twPerCapitaB).toLocaleString()}</span> | 977 | + <span class="tabla-val">Bs {formatPerCapita($twPerCapitaB)}</span> |
| 706 | </div> | 978 | </div> |
| 707 | <!-- Porcentaje --> | 979 | <!-- Porcentaje --> |
| 708 | <div class="tabla-row"> | 980 | <div class="tabla-row"> |
| 709 | - <span class="tabla-label">Peso sobre total</span> | 981 | + <span class="tabla-label">{hoveredYearComparar ? `Peso en ${hoveredYearComparar}` : 'Peso sobre total'}</span> |
| 710 | - <span class="tabla-val">{entA.porcentajeObjeto}%</span> | 982 | + <span class="tabla-val">{displayPropA.toLocaleString('es-BO', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}%</span> |
| 711 | - <span class="tabla-val">{entB.porcentajeObjeto}%</span> | 983 | + <span class="tabla-val">{displayPropB.toLocaleString('es-BO', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}%</span> |
| 712 | </div> | 984 | </div> |
| 713 | <!-- Ranking --> | 985 | <!-- Ranking --> |
| 714 | <div class="tabla-row"> | 986 | <div class="tabla-row"> |
| 715 | - <span class="tabla-label">Ranking</span> | 987 | + <span class="tabla-label">{hoveredYearComparar ? `Ranking ${hoveredYearComparar}` : 'Ranking'}</span> |
| 716 | - <span class="tabla-val">#{entA.ranking}</span> | 988 | + <span class="tabla-val">#{displayRankingA} de {displayNEntidadesA}</span> |
| 717 | - <span class="tabla-val">#{entB.ranking}</span> | 989 | + <span class="tabla-val">#{displayRankingB} de {displayNEntidadesB}</span> |
| 718 | </div> | 990 | </div> |
| 719 | </div> | 991 | </div> |
| 992 | + {/if} | ||
| 720 | 993 | ||
| 721 | {/if} | 994 | {/if} |
| 722 | 995 | ||
| ... | @@ -1070,6 +1343,43 @@ | ... | @@ -1070,6 +1343,43 @@ |
| 1070 | gap: 0.75rem; | 1343 | gap: 0.75rem; |
| 1071 | } | 1344 | } |
| 1072 | 1345 | ||
| 1346 | + .chart-wrapper-scroll { | ||
| 1347 | + overflow-x: auto; | ||
| 1348 | + overflow-y: visible; | ||
| 1349 | + scrollbar-width: thin; | ||
| 1350 | + scrollbar-color: #2E2E2C transparent; | ||
| 1351 | + } | ||
| 1352 | + | ||
| 1353 | + .chart-wrapper-scroll::-webkit-scrollbar { | ||
| 1354 | + height: 6px; | ||
| 1355 | + } | ||
| 1356 | + | ||
| 1357 | + .chart-wrapper-scroll::-webkit-scrollbar-track { | ||
| 1358 | + background: transparent; | ||
| 1359 | + } | ||
| 1360 | + | ||
| 1361 | + .chart-wrapper-scroll::-webkit-scrollbar-thumb { | ||
| 1362 | + background: #2E2E2C; | ||
| 1363 | + border-radius: 3px; | ||
| 1364 | + } | ||
| 1365 | + | ||
| 1366 | + .y-axis-sticky { | ||
| 1367 | + position: sticky; | ||
| 1368 | + left: 0; | ||
| 1369 | + z-index: 10; | ||
| 1370 | + background: #0A0A0A; | ||
| 1371 | + padding-right: 0.5rem; | ||
| 1372 | + margin-right: -0.5rem; | ||
| 1373 | + } | ||
| 1374 | + | ||
| 1375 | + .chart-muchos-años { | ||
| 1376 | + min-width: max-content; | ||
| 1377 | + } | ||
| 1378 | + | ||
| 1379 | + .chart-muchos-años .bar-group { | ||
| 1380 | + min-width: 40px; | ||
| 1381 | + } | ||
| 1382 | + | ||
| 1073 | .y-axis { | 1383 | .y-axis { |
| 1074 | display: flex; | 1384 | display: flex; |
| 1075 | flex-direction: column; | 1385 | flex-direction: column; |
| ... | @@ -1313,11 +1623,91 @@ | ... | @@ -1313,11 +1623,91 @@ |
| 1313 | /* Comparador inline - ajustado para 100vh */ | 1623 | /* Comparador inline - ajustado para 100vh */ |
| 1314 | .comparador-selectors { | 1624 | .comparador-selectors { |
| 1315 | display: flex; | 1625 | display: flex; |
| 1316 | - align-items: center; | 1626 | + align-items: flex-start; |
| 1317 | gap: 1rem; | 1627 | gap: 1rem; |
| 1318 | margin-bottom: 0.75rem; | 1628 | margin-bottom: 0.75rem; |
| 1319 | } | 1629 | } |
| 1320 | 1630 | ||
| 1631 | + .comparador-dropdown-container { | ||
| 1632 | + position: relative; | ||
| 1633 | + } | ||
| 1634 | + | ||
| 1635 | + .comparador-btn { | ||
| 1636 | + width: 100%; | ||
| 1637 | + justify-content: space-between; | ||
| 1638 | + } | ||
| 1639 | + | ||
| 1640 | + .comparador-panel { | ||
| 1641 | + width: 100%; | ||
| 1642 | + min-width: 280px; | ||
| 1643 | + } | ||
| 1644 | + | ||
| 1645 | + .comparador-placeholder { | ||
| 1646 | + display: flex; | ||
| 1647 | + align-items: center; | ||
| 1648 | + justify-content: center; | ||
| 1649 | + min-height: 200px; | ||
| 1650 | + background: rgba(255,255,255,0.02); | ||
| 1651 | + border: 1px dashed #2E2E2C; | ||
| 1652 | + border-radius: 12px; | ||
| 1653 | + padding: 2rem; | ||
| 1654 | + text-align: center; | ||
| 1655 | + } | ||
| 1656 | + | ||
| 1657 | + .comparador-placeholder p { | ||
| 1658 | + color: #5A5650; | ||
| 1659 | + font-size: 0.875rem; | ||
| 1660 | + margin: 0; | ||
| 1661 | + } | ||
| 1662 | + | ||
| 1663 | + .comparador-placeholder strong { | ||
| 1664 | + color: #8A8578; | ||
| 1665 | + } | ||
| 1666 | + | ||
| 1667 | + .rango-selector { | ||
| 1668 | + display: flex; | ||
| 1669 | + align-items: center; | ||
| 1670 | + gap: 0.75rem; | ||
| 1671 | + margin-bottom: 0.75rem; | ||
| 1672 | + } | ||
| 1673 | + | ||
| 1674 | + .rango-label { | ||
| 1675 | + font-size: 0.75rem; | ||
| 1676 | + color: #5A5650; | ||
| 1677 | + } | ||
| 1678 | + | ||
| 1679 | + .rango-options { | ||
| 1680 | + display: flex; | ||
| 1681 | + gap: 0.25rem; | ||
| 1682 | + } | ||
| 1683 | + | ||
| 1684 | + .rango-btn { | ||
| 1685 | + padding: 0.375rem 0.75rem; | ||
| 1686 | + font-size: 0.6875rem; | ||
| 1687 | + font-family: 'DM Mono', monospace; | ||
| 1688 | + color: #5A5650; | ||
| 1689 | + background: transparent; | ||
| 1690 | + border: none; | ||
| 1691 | + border-radius: 6px; | ||
| 1692 | + cursor: pointer; | ||
| 1693 | + transition: all 0.2s; | ||
| 1694 | + } | ||
| 1695 | + | ||
| 1696 | + .rango-btn:hover { | ||
| 1697 | + color: #8A8578; | ||
| 1698 | + background: rgba(255,255,255,0.05); | ||
| 1699 | + } | ||
| 1700 | + | ||
| 1701 | + .rango-btn.active { | ||
| 1702 | + color: #F5F0E8; | ||
| 1703 | + background: rgba(255,255,255,0.1); | ||
| 1704 | + } | ||
| 1705 | + | ||
| 1706 | + .dropdown-item:disabled { | ||
| 1707 | + opacity: 0.4; | ||
| 1708 | + cursor: not-allowed; | ||
| 1709 | + } | ||
| 1710 | + | ||
| 1321 | .selector-group { | 1711 | .selector-group { |
| 1322 | flex: 1; | 1712 | flex: 1; |
| 1323 | display: flex; | 1713 | display: flex; |
| ... | @@ -1730,6 +2120,14 @@ | ... | @@ -1730,6 +2120,14 @@ |
| 1730 | color: #8A8578; | 2120 | color: #8A8578; |
| 1731 | } | 2121 | } |
| 1732 | 2122 | ||
| 2123 | + .tema-claro .y-axis-sticky { | ||
| 2124 | + background: #FAFAF8; | ||
| 2125 | + } | ||
| 2126 | + | ||
| 2127 | + .tema-claro .chart-wrapper-scroll::-webkit-scrollbar-thumb { | ||
| 2128 | + background: #D5D5D0; | ||
| 2129 | + } | ||
| 2130 | + | ||
| 1733 | .tema-claro .chart { | 2131 | .tema-claro .chart { |
| 1734 | border-left-color: #E5E5E0; | 2132 | border-left-color: #E5E5E0; |
| 1735 | } | 2133 | } |
| ... | @@ -1822,6 +2220,37 @@ | ... | @@ -1822,6 +2220,37 @@ |
| 1822 | outline: none; | 2220 | outline: none; |
| 1823 | } | 2221 | } |
| 1824 | 2222 | ||
| 2223 | + .tema-claro .comparador-placeholder { | ||
| 2224 | + background: rgba(0,0,0,0.02); | ||
| 2225 | + border-color: #E5E5E0; | ||
| 2226 | + } | ||
| 2227 | + | ||
| 2228 | + .tema-claro .comparador-placeholder p { | ||
| 2229 | + color: #8A8578; | ||
| 2230 | + } | ||
| 2231 | + | ||
| 2232 | + .tema-claro .comparador-placeholder strong { | ||
| 2233 | + color: #5A5650; | ||
| 2234 | + } | ||
| 2235 | + | ||
| 2236 | + .tema-claro .rango-label { | ||
| 2237 | + color: #8A8578; | ||
| 2238 | + } | ||
| 2239 | + | ||
| 2240 | + .tema-claro .rango-btn { | ||
| 2241 | + color: #8A8578; | ||
| 2242 | + } | ||
| 2243 | + | ||
| 2244 | + .tema-claro .rango-btn:hover { | ||
| 2245 | + color: #5A5650; | ||
| 2246 | + background: rgba(0,0,0,0.05); | ||
| 2247 | + } | ||
| 2248 | + | ||
| 2249 | + .tema-claro .rango-btn.active { | ||
| 2250 | + color: #1A1A18; | ||
| 2251 | + background: rgba(0,0,0,0.08); | ||
| 2252 | + } | ||
| 2253 | + | ||
| 1825 | .tema-claro .vs { | 2254 | .tema-claro .vs { |
| 1826 | color: #8A8578; | 2255 | color: #8A8578; |
| 1827 | } | 2256 | } | ... | ... |
-
Please register or login to post a comment