Showing
9 changed files
with
2980 additions
and
724 deletions
src/lib/components/documentacion/doc.md
0 → 100644
| 1 | + | ||
| 2 | +A fines de 2025 decidimos abrir los datos históricos de presupuesto del Estado boliviano. | ||
| 3 | + | ||
| 4 | +Cada año el Estado recibe y gasta dinero a tu nombre. ¿Sabes de dónde viene, en qué se usa y qué se logra con él? Esa información es tuya. Siempre lo fue, pero durante décadas permaneció encerrada en archivos PDF, en sistemas que pocos saben usar y en las paredes de un ministerio. | ||
| 5 | + | ||
| 6 | +Hoy cambia eso. | ||
| 7 | + | ||
| 8 | +Aquí puedes explorar más de veinte años de ejecución presupuestaria, que serán actualizados semanalmente. Puedes ver qué hizo el Estado en tu región, en tu sector y en el año que te interesa. Puedes comparar, cuestionar y entender. Y si encuentras algo que no cuadra o que vale la pena contar, puedes hacerlo tuyo. | ||
| 9 | + | ||
| 10 | +No publicamos ésto para cumplir un trámite de transparencia. Lo hacemos porque creemos que un Estado que trabaja de espaldas a su sociedad se vuelve impune, y una sociedad que no entiende al Estado se desconecta de él. | ||
| 11 | + | ||
| 12 | +Un presupuesto abierto es una apuesta: que la confianza se construye con información y no con promesas. | ||
| 13 | + | ||
| 14 | +Abrimos para no cerrar. | ||
| 15 | + | ||
| 16 | +# La forma | ||
| 17 | + | ||
| 18 | +Existen 2 bases del presupuesto: gastos e ingresos, que registran el destino y origen de los recursos públicos respectivamente. Ambas bases cubren información sobre todas las entidades del Estado entre 2005 y 2025. | ||
| 19 | + | ||
| 20 | +Cada registro de gasto o ingreso está compuesto de atributos que permiten identificarlo y de montos en bolivianos nominales. Los atributos que identifican a cada gasto son: | ||
| 21 | + | ||
| 22 | +- Clasificador institucional: la entidad pública y dirección administrativa que registra el gasto. | ||
| 23 | +- Categoría programática: el programa, proyecto o actividad al que se dirige. | ||
| 24 | +- Objeto de gasto: el tipo de gasto según un clasificador estandarizado. | ||
| 25 | +- Financiamiento: las fuentes de los recursos utilzados. | ||
| 26 | + | ||
| 27 | +Mientras que aquellos que identifican a un ingreso son: | ||
| 28 | + | ||
| 29 | +- Clasificador institucional: la entidad pública que registra el ingreso. | ||
| 30 | +- Rubro de ingreso: el tipo de ingreso según un clasificador estandarizado. | ||
| 31 | +- Financiamiento: las fuentes del ingreso. | ||
| 32 | + | ||
| 33 | +Cada registro incluye 4 montos: | ||
| 34 | + | ||
| 35 | +- Inicial: lo registrado en el presupuesto a inicio de la gestión. | ||
| 36 | +- Vigente: el presupuesto luego de modificaciones durante la gestión. | ||
| 37 | +- Devengado: el monto devengado para ejecución. | ||
| 38 | +- Ejecutado: el monto pagado o percibido. | ||
| 39 | + | ||
| 40 | +Además, cuando están disponibles, contamos con atributos adicionales para describir mejor el destino de los gastos. Éstos son finalidad y función, sector económico y ubicación geográfica. | ||
| 41 | + | ||
| 42 | +# El detalle | ||
| 43 | + | ||
| 44 | +## Gastos | ||
| 45 | + | ||
| 46 | +La base de gastos nos permite comprender el destino de los recursos públicos. | ||
| 47 | + | ||
| 48 | +- **Quién**: entidad y dirección administrativa responsable. | ||
| 49 | +- **Qué**: programas, proyectos y actividades a los que se dirigen recursos. | ||
| 50 | +- **Para qué**: finalidad y función pública. | ||
| 51 | +- **En qué**: objetos de gasto. | ||
| 52 | +- **Con qué recursos**: fuentes de las que se toman recursos. | ||
| 53 | +- **Cuánto**: montos de presupuesto y ejecución. | ||
| 54 | + | ||
| 55 | +Los campos en esta base son los siguientes. | ||
| 56 | + | ||
| 57 | +### Gestión | ||
| 58 | + | ||
| 59 | +#### gestion | ||
| 60 | + | ||
| 61 | +Año fiscal del registro presupuestario. | ||
| 62 | + | ||
| 63 | +Tipo: Número entero de 4 dígitos. | ||
| 64 | + | ||
| 65 | +Ejemplo: `2005`. | ||
| 66 | + | ||
| 67 | +### Clasificador institucional | ||
| 68 | + | ||
| 69 | +Información sobre qué entidad y dirección administrativa registra el gasto. Una dirección administrativa pertenece a una entidad que a su vez está posicionada dentro de una jerarquía en el aparato estatal: | ||
| 70 | + | ||
| 71 | +``` | ||
| 72 | +Sector | ||
| 73 | +└─ Subsector | ||
| 74 | + └─ Área | ||
| 75 | + └─ Subárea | ||
| 76 | + └─ Entidad | ||
| 77 | + └─ Dirección Administrativa | ||
| 78 | +``` | ||
| 79 | + | ||
| 80 | +#### entidad | ||
| 81 | + | ||
| 82 | +Código de la entidad pública a la que pertenece el registro. Cada entidad ocupa una posición en el Sector Público que puede ser descrita en una jerarquía de 4 niveles: sector, subsector, área y subárea. Para cada nivel contamos con un código, sigla y descripción. Dentro de cada entidad, el gasto es registrado en una dirección administrativa, a la que podemos describir con un código y descripción. | ||
| 83 | + | ||
| 84 | +Tipo: Número entero de 1 a 4 dígitos. | ||
| 85 | + | ||
| 86 | +Ejemplo: `10`. | ||
| 87 | + | ||
| 88 | +#### entidad_desc_entidad | ||
| 89 | + | ||
| 90 | +Descripción de la entidad pública. | ||
| 91 | + | ||
| 92 | +Tipo: Texto descriptivo. | ||
| 93 | + | ||
| 94 | +Ejemplo: `Ministerio De Relaciones Exteriores Y Culto`. | ||
| 95 | + | ||
| 96 | +#### entidad_sigla_entidad | ||
| 97 | + | ||
| 98 | +Sigla de la entidad pública. | ||
| 99 | + | ||
| 100 | +Tipo: Texto corto (sigla). | ||
| 101 | + | ||
| 102 | +Ejemplo: `MIN-RREE`. | ||
| 103 | + | ||
| 104 | +#### entidad_sector | ||
| 105 | + | ||
| 106 | +Código del sector al que pertenece la entidad pública. | ||
| 107 | + | ||
| 108 | +Tipo: Número entero de 1 dígito. | ||
| 109 | + | ||
| 110 | +Ejemplo: `1`. | ||
| 111 | + | ||
| 112 | +#### entidad_subsector | ||
| 113 | + | ||
| 114 | +Código del subsector al que pertenece la entidad pública. | ||
| 115 | + | ||
| 116 | +Tipo: Secuencia de números enteros separados por puntos. | ||
| 117 | + | ||
| 118 | +Ejemplo: `1.10`. | ||
| 119 | + | ||
| 120 | +#### entidad_area | ||
| 121 | + | ||
| 122 | +Código del área al que pertenece la entidad pública. | ||
| 123 | + | ||
| 124 | +Tipo: Secuencia de números enteros separados por puntos. | ||
| 125 | + | ||
| 126 | +Ejemplo: `1.1.1`. | ||
| 127 | + | ||
| 128 | +#### entidad_subarea | ||
| 129 | + | ||
| 130 | +Código de la subárea al que pertenece la entidad pública. | ||
| 131 | + | ||
| 132 | +Tipo: Secuencia de números enteros separados por puntos. | ||
| 133 | + | ||
| 134 | +Ejemplo: `1.1.1.1.1`. | ||
| 135 | + | ||
| 136 | +#### entidad_desc_sector | ||
| 137 | + | ||
| 138 | +Descripción del sector al que pertenece la entidad pública. | ||
| 139 | + | ||
| 140 | +Tipo: Texto descriptivo. | ||
| 141 | + | ||
| 142 | +Ejemplo: `Sector Público`. | ||
| 143 | + | ||
| 144 | +#### entidad_desc_subsector | ||
| 145 | + | ||
| 146 | +Descripción del subsector al que pertenece la entidad pública. | ||
| 147 | + | ||
| 148 | +Tipo: Texto descriptivo. | ||
| 149 | + | ||
| 150 | +Ejemplo: `Sector Público No Financiero`. | ||
| 151 | + | ||
| 152 | +#### entidad_desc_area | ||
| 153 | + | ||
| 154 | +Descripción del área al que pertenece la entidad pública. | ||
| 155 | + | ||
| 156 | +Tipo: Texto descriptivo. | ||
| 157 | + | ||
| 158 | +Ejemplo: `Administraciones Públicas (Gob. General)`. | ||
| 159 | + | ||
| 160 | +#### entidad_desc_subarea | ||
| 161 | + | ||
| 162 | +Descripción de la subárea a la que pertenece la entidad pública. | ||
| 163 | + | ||
| 164 | +Tipo: Texto descriptivo. | ||
| 165 | + | ||
| 166 | +Ejemplo: `Administración Central`. | ||
| 167 | + | ||
| 168 | +#### entidad_sigla_sector | ||
| 169 | + | ||
| 170 | +Sigla del sector al que pertenece la entidad pública. | ||
| 171 | + | ||
| 172 | +Tipo: Texto corto (sigla). | ||
| 173 | + | ||
| 174 | +Ejemplo: `SP`. | ||
| 175 | + | ||
| 176 | +#### entidad_sigla_subsector | ||
| 177 | + | ||
| 178 | +Sigla del subsector al que pertenece la entidad pública. | ||
| 179 | + | ||
| 180 | +Tipo: Texto corto (sigla). | ||
| 181 | + | ||
| 182 | +Ejemplo: `SPNF`. | ||
| 183 | + | ||
| 184 | +#### entidad_sigla_area | ||
| 185 | + | ||
| 186 | +Sigla del área al que pertenece la entidad pública. | ||
| 187 | + | ||
| 188 | +Tipo: Texto corto (sigla). | ||
| 189 | + | ||
| 190 | +Ejemplo: `GOBGEN`. | ||
| 191 | + | ||
| 192 | +#### entidad_sigla_subarea | ||
| 193 | + | ||
| 194 | +Sigla de la subárea a la que pertenece la entidad pública. | ||
| 195 | + | ||
| 196 | +Tipo: Texto corto (sigla). | ||
| 197 | + | ||
| 198 | +Ejemplo: `ADMCEN`. | ||
| 199 | + | ||
| 200 | +#### da | ||
| 201 | + | ||
| 202 | +Código de la dirección administrativa que registra el gasto dentro de la entidad pública. | ||
| 203 | + | ||
| 204 | +Tipo: Número entero de 1 a 2 dígitos. | ||
| 205 | + | ||
| 206 | +Ejemplo: `1`. | ||
| 207 | + | ||
| 208 | +#### desc_da | ||
| 209 | + | ||
| 210 | +Descripción de la dirección administrativa que registra el gasto dentro de la entidad pública. | ||
| 211 | + | ||
| 212 | +Tipo: Texto descriptivo. | ||
| 213 | + | ||
| 214 | +Ejemplo: `DIRECCION SUPERIOR Y ADMINISTRACION CENTRAL`. | ||
| 215 | + | ||
| 216 | +### Categoría programática | ||
| 217 | + | ||
| 218 | +Información sobre los programas, proyectos y actividades a los que se dedica el gasto. Un programa puede contener múltiples proyectos o actividades. | ||
| 219 | + | ||
| 220 | +``` | ||
| 221 | +Programa | ||
| 222 | +├─ Proyecto | ||
| 223 | +└─ Actividad | ||
| 224 | +``` | ||
| 225 | + | ||
| 226 | +#### programa | ||
| 227 | + | ||
| 228 | +Código del programa al que se dedica el gasto. Cada programa puede tener múltiples proyectos y/o actividades. | ||
| 229 | + | ||
| 230 | +Tipo: Número entero de 1 a 3 dígitos. | ||
| 231 | + | ||
| 232 | +Ejemplo: `52`. | ||
| 233 | + | ||
| 234 | +#### proyecto | ||
| 235 | + | ||
| 236 | +Código del proyecto al que se dedica el gasto. Este código puede corresponder al código SISIN asignado por el Viceministerio de Inversión Pública y Financiamiento Externo, o un código corto asignado por la entidad pública. Cuando el código es 0, la partida corresponde a una actividad. | ||
| 237 | + | ||
| 238 | +Tipo: Código alfanumérico. | ||
| 239 | + | ||
| 240 | +Ejemplo: `000100000200000`. | ||
| 241 | + | ||
| 242 | +#### actividad | ||
| 243 | + | ||
| 244 | +Código de la actividad a la que se dedica el gasto. Cuando es 0, la partida corresponde a un proyecto. | ||
| 245 | + | ||
| 246 | +Tipo: Número entero de 1 dígito. | ||
| 247 | + | ||
| 248 | +Ejemplo: `0`. | ||
| 249 | + | ||
| 250 | +#### desc_programa | ||
| 251 | + | ||
| 252 | +Descripción del programa al que se dedica el gasto. | ||
| 253 | + | ||
| 254 | +Tipo: Texto descriptivo. | ||
| 255 | + | ||
| 256 | +Ejemplo: `PROYECTO DE FORTALECIMIENTO INSTITUCIONAL - CONSTRUCCIÓN DEL EDIFICIO INTELIGENTE DE LA CANCILLERÍA`. | ||
| 257 | + | ||
| 258 | +#### desc_actividad_proyecto | ||
| 259 | + | ||
| 260 | +Descripción del proyecto o actividad al que se dedica el gasto, según corresponda. | ||
| 261 | + | ||
| 262 | +Tipo: Texto descriptivo. | ||
| 263 | + | ||
| 264 | +Ejemplo: `CONST. EDIFICIO INTELIGENTE MRE LA PAZ`. | ||
| 265 | + | ||
| 266 | +### Financiamiento | ||
| 267 | + | ||
| 268 | +Información sobre el origen del financiamiento para el gasto. Consiste en una fuente de financiamiento, organismo financiador y entidad de transferencia. | ||
| 269 | + | ||
| 270 | +Cada fuente es descrita en 2 niveles: grupo y fuente. | ||
| 271 | + | ||
| 272 | +``` | ||
| 273 | +Grupo | ||
| 274 | +└─ Fuente | ||
| 275 | +``` | ||
| 276 | + | ||
| 277 | +Cada organismo incluye un código, sigla y descripción. Cada entidad de transferencia consiste en un código de entidad similar a los códigos de las entidades que realizan el gasto. | ||
| 278 | + | ||
| 279 | +#### grupo_fuente | ||
| 280 | + | ||
| 281 | +Código del grupo de la fuente de financiamiento. | ||
| 282 | + | ||
| 283 | +Tipo: Número entero de 1 dígito. | ||
| 284 | + | ||
| 285 | +Ejemplo: `1`. | ||
| 286 | + | ||
| 287 | +#### fuente | ||
| 288 | + | ||
| 289 | +Código de la fuente de financiamiento. | ||
| 290 | + | ||
| 291 | +Tipo: Número entero de 2 dígitos. | ||
| 292 | + | ||
| 293 | +Ejemplo: `10`. | ||
| 294 | + | ||
| 295 | +#### desc_grupo_fuente | ||
| 296 | + | ||
| 297 | +Descripción del grupo de la fuente de financiamiento. | ||
| 298 | + | ||
| 299 | +Tipo: Texto descriptivo. | ||
| 300 | + | ||
| 301 | +Ejemplo: `TESORO GENERAL`. | ||
| 302 | + | ||
| 303 | +#### desc_fuente | ||
| 304 | + | ||
| 305 | +Descripción de la fuente de financiamiento. | ||
| 306 | + | ||
| 307 | +Tipo: Texto descriptivo. | ||
| 308 | + | ||
| 309 | +Ejemplo: `Tesoro General de la Nación`. | ||
| 310 | + | ||
| 311 | +#### organismo | ||
| 312 | + | ||
| 313 | +Código del organismo financiador del gasto. | ||
| 314 | + | ||
| 315 | +Tipo: Número entero de 3 dígitos. | ||
| 316 | + | ||
| 317 | +Ejemplo: `111`. | ||
| 318 | + | ||
| 319 | +#### desc_organismo | ||
| 320 | + | ||
| 321 | +Descripción del organismo financiador. | ||
| 322 | + | ||
| 323 | +Tipo: Texto descriptivo. | ||
| 324 | + | ||
| 325 | +Ejemplo: `Tesoro General de la Nación`. | ||
| 326 | + | ||
| 327 | +#### sigla_organismo | ||
| 328 | + | ||
| 329 | +Sigla del organismo financiador. | ||
| 330 | + | ||
| 331 | +Tipo: Texto corto (sigla). | ||
| 332 | + | ||
| 333 | +Ejemplo: `TGN`. | ||
| 334 | + | ||
| 335 | +#### entidad_transferencia | ||
| 336 | + | ||
| 337 | +Código de la entidad de la que se transfieren recursos de financiamiento cuando corresponde. | ||
| 338 | + | ||
| 339 | +Tipo: Número entero de 1 a 4 dígitos. | ||
| 340 | + | ||
| 341 | +Ejemplo: `2351`. | ||
| 342 | + | ||
| 343 | +### Objeto del gasto | ||
| 344 | + | ||
| 345 | +Información sobre la naturaleza económica del gasto. Cada objeto de gasto es descrito en 4 niveles: grupo, subgrupo, partida y subpartida, y cada nivel cuenta con un código y una descripción. | ||
| 346 | + | ||
| 347 | +``` | ||
| 348 | +Grupo | ||
| 349 | +└─ Subgrupo | ||
| 350 | + └─ Partida | ||
| 351 | + └─ Subpartida | ||
| 352 | +``` | ||
| 353 | + | ||
| 354 | +#### objeto_grupo | ||
| 355 | + | ||
| 356 | +Código del grupo del objeto de gasto. | ||
| 357 | + | ||
| 358 | +Tipo: Número entero de 1 dígito. | ||
| 359 | + | ||
| 360 | +Ejemplo: `1`. | ||
| 361 | + | ||
| 362 | +#### objeto_subgrupo | ||
| 363 | + | ||
| 364 | +Código del subgrupo del objeto de gasto. | ||
| 365 | + | ||
| 366 | +Tipo: Número entero de 1 dígito. | ||
| 367 | + | ||
| 368 | +Ejemplo: `1`. | ||
| 369 | + | ||
| 370 | +#### objeto_partida | ||
| 371 | + | ||
| 372 | +Código de la partida del objeto de gasto. | ||
| 373 | + | ||
| 374 | +Tipo: Número entero de 1 dígito. | ||
| 375 | + | ||
| 376 | +Ejemplo: `2`. | ||
| 377 | + | ||
| 378 | +#### objeto_subpartida | ||
| 379 | + | ||
| 380 | +Código de la subpartida del objeto de gasto. | ||
| 381 | + | ||
| 382 | +Tipo: Número entero de 2 dígitos. | ||
| 383 | + | ||
| 384 | +Ejemplo: `20`. | ||
| 385 | + | ||
| 386 | +#### objeto_desc_grupo | ||
| 387 | + | ||
| 388 | +Descripción del grupo del objeto de gasto. | ||
| 389 | + | ||
| 390 | +Tipo: Texto descriptivo. | ||
| 391 | + | ||
| 392 | +Ejemplo: `SERVICIOS PERSONALES`. | ||
| 393 | + | ||
| 394 | +#### objeto_desc_subgrupo | ||
| 395 | + | ||
| 396 | +Descripción del subgrupo del objeto de gasto. | ||
| 397 | + | ||
| 398 | +Tipo: Texto descriptivo. | ||
| 399 | + | ||
| 400 | +Ejemplo: `Empleados Permanentes`. | ||
| 401 | + | ||
| 402 | +#### objeto_desc_partida | ||
| 403 | + | ||
| 404 | +Descripción de la partida del objeto de gasto. | ||
| 405 | + | ||
| 406 | +Tipo: Texto descriptivo. | ||
| 407 | + | ||
| 408 | +Ejemplo: `Bono de Antiguedad`. | ||
| 409 | + | ||
| 410 | +#### objeto_desc_subpartida | ||
| 411 | + | ||
| 412 | +Descripción de la subpartida del objeto de gasto. | ||
| 413 | + | ||
| 414 | +Tipo: Texto descriptivo. | ||
| 415 | + | ||
| 416 | +Ejemplo: `Bono de Antigûedad`. | ||
| 417 | + | ||
| 418 | +### Finalidad y función | ||
| 419 | + | ||
| 420 | +Información sobre el propósito público del gasto. Consiste en clasificaciones según la finalidad y función, el sector económico y la ubicación geográfica. | ||
| 421 | + | ||
| 422 | +La finalidad y función es descrita en 3 niveles: finalidad, grupo de función y función. | ||
| 423 | + | ||
| 424 | +``` | ||
| 425 | +Finalidad | ||
| 426 | +└─ Grupo de Función | ||
| 427 | + └─ Función | ||
| 428 | +``` | ||
| 429 | + | ||
| 430 | +El sector económico describe el sector donde el gasto tiene impacto directo y consiste en 3 niveles: sector, subsector y actividad. | ||
| 431 | + | ||
| 432 | +``` | ||
| 433 | +Sector | ||
| 434 | +└─ Subsector | ||
| 435 | + └─ Actividad | ||
| 436 | +``` | ||
| 437 | + | ||
| 438 | +La ubicación geográfica describe el lugar donde el gasto tiene impacto directo y tiene 3 niveles: departamento, provincia y municipio. | ||
| 439 | + | ||
| 440 | +``` | ||
| 441 | +Departamento | ||
| 442 | +└─ Provincia | ||
| 443 | + └─ Municipio | ||
| 444 | +``` | ||
| 445 | + | ||
| 446 | +#### finfun_finalidad | ||
| 447 | + | ||
| 448 | +Código de finalidad del gasto. | ||
| 449 | + | ||
| 450 | +Tipo: Número entero de 1 a 2 dígitos. | ||
| 451 | + | ||
| 452 | +Ejemplo: `8`. | ||
| 453 | + | ||
| 454 | +#### finfun_grpfuncion | ||
| 455 | + | ||
| 456 | +Código del grupo de función del gasto. | ||
| 457 | + | ||
| 458 | +Tipo: Número entero de 1 a 2 dígitos. | ||
| 459 | + | ||
| 460 | +Ejemplo: `2`. | ||
| 461 | + | ||
| 462 | +#### finfun_funcion | ||
| 463 | + | ||
| 464 | +Código de la función del gasto. | ||
| 465 | + | ||
| 466 | +Tipo: Número entero de 1 dígito. | ||
| 467 | + | ||
| 468 | +Ejemplo: `0`. | ||
| 469 | + | ||
| 470 | +#### finfun_desc_finalidad | ||
| 471 | + | ||
| 472 | +Descripción de la finalidad del gasto. | ||
| 473 | + | ||
| 474 | +Tipo: Texto descriptivo. | ||
| 475 | + | ||
| 476 | +Ejemplo: `ACTIVIDADES RECREATIVAS, CULTURA Y RELIGION`. | ||
| 477 | + | ||
| 478 | +#### finfun_desc_grpfuncion | ||
| 479 | + | ||
| 480 | +Descripción del grupo de la función del gasto. | ||
| 481 | + | ||
| 482 | +Tipo: Texto descriptivo. | ||
| 483 | + | ||
| 484 | +Ejemplo: `Servicios culturales`. | ||
| 485 | + | ||
| 486 | +#### finfun_desc_funcion | ||
| 487 | + | ||
| 488 | +Descripción de la función del gasto. | ||
| 489 | + | ||
| 490 | +Tipo: Texto descriptivo. | ||
| 491 | + | ||
| 492 | +Ejemplo: `Servicios culturales`. | ||
| 493 | + | ||
| 494 | +#### acteco_sector | ||
| 495 | + | ||
| 496 | +Código del sector económico donde tiene impacto directo el gasto. | ||
| 497 | + | ||
| 498 | +Tipo: Número entero de 1 a 2 dígitos. | ||
| 499 | + | ||
| 500 | +Ejemplo: `9`. | ||
| 501 | + | ||
| 502 | +#### acteco_subsector | ||
| 503 | + | ||
| 504 | +Código del subsector económico donde tiene impacto directo el gasto. | ||
| 505 | + | ||
| 506 | +Tipo: Número entero de 1 dígito. | ||
| 507 | + | ||
| 508 | +Ejemplo: `4`. | ||
| 509 | + | ||
| 510 | +#### acteco_actividad | ||
| 511 | + | ||
| 512 | +Código de la actividad económica donde tiene impacto directo el gasto. | ||
| 513 | + | ||
| 514 | +Tipo: Número entero de 1 a 2 dígitos. | ||
| 515 | + | ||
| 516 | +Ejemplo: `1`. | ||
| 517 | + | ||
| 518 | +#### acteco_desc_sector | ||
| 519 | + | ||
| 520 | +Descripción del sector económico donde tiene impacto directo el gasto. | ||
| 521 | + | ||
| 522 | +Tipo: Texto descriptivo. | ||
| 523 | + | ||
| 524 | +Ejemplo: `EDUCACION Y CULTURA`. | ||
| 525 | + | ||
| 526 | +#### acteco_desc_subsector | ||
| 527 | + | ||
| 528 | +Descripción del subsector económico donde tiene impacto directo el gasto. | ||
| 529 | + | ||
| 530 | +Tipo: Texto descriptivo. | ||
| 531 | + | ||
| 532 | +Ejemplo: `CULTURA`. | ||
| 533 | + | ||
| 534 | +#### acteco_desc_actividad | ||
| 535 | + | ||
| 536 | +Descripción de la actividad económica donde tiene impacto directo el gasto. | ||
| 537 | + | ||
| 538 | +Tipo: Texto descriptivo. | ||
| 539 | + | ||
| 540 | +Ejemplo: `CONSTRUCCION, REFACCION Y EQUIP. DE CENTROS CULTURALES`. | ||
| 541 | + | ||
| 542 | +#### departamento | ||
| 543 | + | ||
| 544 | +Código del departamento donde tiene impacto directo el gasto. | ||
| 545 | + | ||
| 546 | +Tipo: Número entero de 1 a 2 dígitos. | ||
| 547 | + | ||
| 548 | +Ejemplo: `2`. | ||
| 549 | + | ||
| 550 | +#### provincia | ||
| 551 | + | ||
| 552 | +Código de la provincia donde tiene impacto directo el gasto. | ||
| 553 | + | ||
| 554 | +Tipo: Número entero de 1 a 2 dígitos. | ||
| 555 | + | ||
| 556 | +Ejemplo: `6`. | ||
| 557 | + | ||
| 558 | +#### municipio | ||
| 559 | + | ||
| 560 | +Código del municipio donde tiene impacto directo el gasto. | ||
| 561 | + | ||
| 562 | +Tipo: Número entero de 1 a 2 dígitos. | ||
| 563 | + | ||
| 564 | +Ejemplo: `1`. | ||
| 565 | + | ||
| 566 | +#### desc_departamento | ||
| 567 | + | ||
| 568 | +Descripción del departamento donde tiene impacto directo el gasto. | ||
| 569 | + | ||
| 570 | +Tipo: Texto descriptivo. | ||
| 571 | + | ||
| 572 | +Ejemplo: `LA PAZ`. | ||
| 573 | + | ||
| 574 | +#### desc_provincia | ||
| 575 | + | ||
| 576 | +Descripción de la provincia donde tiene impacto directo el gasto. | ||
| 577 | + | ||
| 578 | +Tipo: Texto descriptivo. | ||
| 579 | + | ||
| 580 | +Ejemplo: `OMASUYOS`. | ||
| 581 | + | ||
| 582 | +#### desc_municipio | ||
| 583 | + | ||
| 584 | +Descripción del municipio donde tiene impacto directo el gasto. | ||
| 585 | + | ||
| 586 | +Tipo: Texto descriptivo. | ||
| 587 | + | ||
| 588 | +Ejemplo: `Achacachi`. | ||
| 589 | + | ||
| 590 | +### Montos | ||
| 591 | + | ||
| 592 | +Montos en bolivianos corrientes que hacen al presupuesto y ejecución de cada gasto. Incluye 2 montos de presupuesto: al inicio de la gestión y luego de modificaciones, y 2 montos de ejecución: devengado y pagado. | ||
| 593 | + | ||
| 594 | +#### inicial | ||
| 595 | + | ||
| 596 | +Monto presupuestado al inicio de la gestión, en bolivianos corrientes. | ||
| 597 | + | ||
| 598 | +Tipo: Monto en bolivianos corrientes con dos decimales. | ||
| 599 | + | ||
| 600 | +Ejemplo: `1411136`. | ||
| 601 | + | ||
| 602 | +#### vigente | ||
| 603 | + | ||
| 604 | +Monto presupuestado luego de modificaciones durante la gestión, en bolivianos corrientes. | ||
| 605 | + | ||
| 606 | +Tipo: Monto en bolivianos corrientes con dos decimales. | ||
| 607 | + | ||
| 608 | +Ejemplo: `514479`. | ||
| 609 | + | ||
| 610 | +#### devengado | ||
| 611 | + | ||
| 612 | +Monto devengado para la ejecución, en bolivianos corrientes. | ||
| 613 | + | ||
| 614 | +Tipo: Monto en bolivianos corrientes con dos decimales. | ||
| 615 | + | ||
| 616 | +Ejemplo: `493141.14`. | ||
| 617 | + | ||
| 618 | +#### pagado | ||
| 619 | + | ||
| 620 | +Monto pagado, en bolivianos corrientes. | ||
| 621 | + | ||
| 622 | +Tipo: Monto en bolivianos corrientes con dos decimales. | ||
| 623 | + | ||
| 624 | +Ejemplo: `493141.14`. | ||
| 625 | + | ||
| 626 | +## Ingresos | ||
| 627 | + | ||
| 628 | +La tabla de ingresos nos permite comprender el origen de los recursos públicos. | ||
| 629 | + | ||
| 630 | +- **Quién**: entidad que registra el ingreso. | ||
| 631 | +- **Qué recursos**: rubros del ingreso. | ||
| 632 | +- **De dónde**: fuentes de financiamiento. | ||
| 633 | +- **Cuánto**: montos de presupuesto y ejecución. | ||
| 634 | + | ||
| 635 | +### Gestión | ||
| 636 | + | ||
| 637 | +#### gestion | ||
| 638 | + | ||
| 639 | +Año fiscal del registro presupuestario. | ||
| 640 | + | ||
| 641 | +Tipo: Número entero de 4 dígitos. | ||
| 642 | + | ||
| 643 | +Ejemplo: `2005`. | ||
| 644 | + | ||
| 645 | +### Clasificador institucional | ||
| 646 | + | ||
| 647 | +Información sobre qué entidad percibe el ingreso. Una entidad ocupa una posición dentro de la jerarquía del aparato estatal: | ||
| 648 | + | ||
| 649 | +``` | ||
| 650 | +Sector | ||
| 651 | +└─ Subsector | ||
| 652 | + └─ Área | ||
| 653 | + └─ Subárea | ||
| 654 | + └─ Entidad | ||
| 655 | +``` | ||
| 656 | + | ||
| 657 | +#### entidad | ||
| 658 | + | ||
| 659 | +Código de la entidad pública a la que pertenece el registro. Cada entidad ocupa una posición en el Sector Público que puede describirse en una jerarquía de 4 niveles: sector, subsector, área y subárea. Para cada nivel se cuenta con un código, sigla y descripción. | ||
| 660 | + | ||
| 661 | +Tipo: Número entero de 1 a 4 dígitos. | ||
| 662 | + | ||
| 663 | +Ejemplo: `10`. | ||
| 664 | + | ||
| 665 | +#### entidad_desc_entidad | ||
| 666 | + | ||
| 667 | +Descripción de la entidad pública. | ||
| 668 | + | ||
| 669 | +Tipo: Texto descriptivo. | ||
| 670 | + | ||
| 671 | +Ejemplo: `Ministerio De Relaciones Exteriores Y Culto`. | ||
| 672 | + | ||
| 673 | +#### entidad_sigla_entidad | ||
| 674 | + | ||
| 675 | +Sigla de la entidad pública. | ||
| 676 | + | ||
| 677 | +Tipo: Texto corto (sigla). | ||
| 678 | + | ||
| 679 | +Ejemplo: `MIN-RREE`. | ||
| 680 | + | ||
| 681 | +#### entidad_sector | ||
| 682 | + | ||
| 683 | +Código del sector al que pertenece la entidad pública. | ||
| 684 | + | ||
| 685 | +Tipo: Número entero de 1 dígito. | ||
| 686 | + | ||
| 687 | +Ejemplo: `1`. | ||
| 688 | + | ||
| 689 | +#### entidad_subsector | ||
| 690 | + | ||
| 691 | +Código del subsector al que pertenece la entidad pública. | ||
| 692 | + | ||
| 693 | +Tipo: Secuencia de números enteros separados por puntos. | ||
| 694 | + | ||
| 695 | +Ejemplo: `1.10`. | ||
| 696 | + | ||
| 697 | +#### entidad_area | ||
| 698 | + | ||
| 699 | +Código del área al que pertenece la entidad pública. | ||
| 700 | + | ||
| 701 | +Tipo: Secuencia de números enteros separados por puntos. | ||
| 702 | + | ||
| 703 | +Ejemplo: `1.1.1`. | ||
| 704 | + | ||
| 705 | +#### entidad_subarea | ||
| 706 | + | ||
| 707 | +Código de la subárea al que pertenece la entidad pública. | ||
| 708 | + | ||
| 709 | +Tipo: Secuencia de números enteros separados por puntos. | ||
| 710 | + | ||
| 711 | +Ejemplo: `1.1.1.1.1`. | ||
| 712 | + | ||
| 713 | +#### entidad_desc_sector | ||
| 714 | + | ||
| 715 | +Descripción del sector al que pertenece la entidad pública. | ||
| 716 | + | ||
| 717 | +Tipo: Texto descriptivo. | ||
| 718 | + | ||
| 719 | +Ejemplo: `Sector Público`. | ||
| 720 | + | ||
| 721 | +#### entidad_desc_subsector | ||
| 722 | + | ||
| 723 | +Descripción del subsector al que pertenece la entidad pública. | ||
| 724 | + | ||
| 725 | +Tipo: Texto descriptivo. | ||
| 726 | + | ||
| 727 | +Ejemplo: `Sector Público No Financiero`. | ||
| 728 | + | ||
| 729 | +#### entidad_desc_area | ||
| 730 | + | ||
| 731 | +Descripción del área al que pertenece la entidad pública. | ||
| 732 | + | ||
| 733 | +Tipo: Texto descriptivo. | ||
| 734 | + | ||
| 735 | +Ejemplo: `Administraciones Públicas (Gob. General)`. | ||
| 736 | + | ||
| 737 | +#### entidad_desc_subarea | ||
| 738 | + | ||
| 739 | +Descripción de la subárea a la que pertenece la entidad pública. | ||
| 740 | + | ||
| 741 | +Tipo: Texto descriptivo. | ||
| 742 | + | ||
| 743 | +Ejemplo: `Administración Central`. | ||
| 744 | + | ||
| 745 | +#### entidad_sigla_sector | ||
| 746 | + | ||
| 747 | +Sigla del sector al que pertenece la entidad pública. | ||
| 748 | + | ||
| 749 | +Tipo: Texto corto (sigla). | ||
| 750 | + | ||
| 751 | +Ejemplo: `SP`. | ||
| 752 | + | ||
| 753 | +#### entidad_sigla_subsector | ||
| 754 | + | ||
| 755 | +Sigla del subsector al que pertenece la entidad pública. | ||
| 756 | + | ||
| 757 | +Tipo: Texto corto (sigla). | ||
| 758 | + | ||
| 759 | +Ejemplo: `SPNF`. | ||
| 760 | + | ||
| 761 | +#### entidad_sigla_area | ||
| 762 | + | ||
| 763 | +Sigla del área al que pertenece la entidad pública. | ||
| 764 | + | ||
| 765 | +Tipo: Texto corto (sigla). | ||
| 766 | + | ||
| 767 | +Ejemplo: `GOBGEN`. | ||
| 768 | + | ||
| 769 | +#### entidad_sigla_subarea | ||
| 770 | + | ||
| 771 | +Sigla de la subárea a la que pertenece la entidad pública. | ||
| 772 | + | ||
| 773 | +Tipo: Texto corto (sigla). | ||
| 774 | + | ||
| 775 | +Ejemplo: `ADMCEN`. | ||
| 776 | + | ||
| 777 | +### Financiamiento | ||
| 778 | + | ||
| 779 | +Información sobre el origen del financiamiento del ingreso. Consiste en una fuente de financiamiento, un organismo financiador y una entidad otorgante. Cada fuente se describe en 2 niveles: grupo y fuente. | ||
| 780 | + | ||
| 781 | +``` | ||
| 782 | +Grupo | ||
| 783 | +└─ Fuente | ||
| 784 | +``` | ||
| 785 | + | ||
| 786 | +Cada organismo incluye un código, sigla y descripción. La entidad otorgante se registra con un código de entidad. | ||
| 787 | + | ||
| 788 | +#### grupo_fuente | ||
| 789 | + | ||
| 790 | +Código del grupo de la fuente de financiamiento. | ||
| 791 | + | ||
| 792 | +Tipo: Número entero de 1 dígito. | ||
| 793 | + | ||
| 794 | +Ejemplo: `2`. | ||
| 795 | + | ||
| 796 | +#### fuente | ||
| 797 | + | ||
| 798 | +Código de la fuente de financiamiento. | ||
| 799 | + | ||
| 800 | +Tipo: Número entero de 2 dígitos. | ||
| 801 | + | ||
| 802 | +Ejemplo: `11`. | ||
| 803 | + | ||
| 804 | +#### desc_grupo_fuente | ||
| 805 | + | ||
| 806 | +Descripción del grupo de la fuente de financiamiento. | ||
| 807 | + | ||
| 808 | +Tipo: Texto descriptivo. | ||
| 809 | + | ||
| 810 | +Ejemplo: `T.G.N. OTROS INGRESOS`. | ||
| 811 | + | ||
| 812 | +#### desc_fuente | ||
| 813 | + | ||
| 814 | +Descripción de la fuente de financiamiento. | ||
| 815 | + | ||
| 816 | +Tipo: Texto descriptivo. | ||
| 817 | + | ||
| 818 | +Ejemplo: `T.G.N. Otros Ingresos`. | ||
| 819 | + | ||
| 820 | +#### organismo | ||
| 821 | + | ||
| 822 | +Código del organismo financiador del ingreso. | ||
| 823 | + | ||
| 824 | +Tipo: Número entero de 3 dígitos. | ||
| 825 | + | ||
| 826 | +Ejemplo: `0`. | ||
| 827 | + | ||
| 828 | +#### desc_organismo | ||
| 829 | + | ||
| 830 | +Descripción del organismo financiador. | ||
| 831 | + | ||
| 832 | +Tipo: Texto descriptivo. | ||
| 833 | + | ||
| 834 | +Ejemplo: `NC`. | ||
| 835 | + | ||
| 836 | +#### sigla_organismo | ||
| 837 | + | ||
| 838 | +Sigla del organismo financiador. | ||
| 839 | + | ||
| 840 | +Tipo: Texto corto (sigla). | ||
| 841 | + | ||
| 842 | +Ejemplo: `NC`. | ||
| 843 | + | ||
| 844 | +#### entidad_otorgante | ||
| 845 | + | ||
| 846 | +Código de la entidad de la que se transfieren recursos de financiamiento cuando corresponde. | ||
| 847 | + | ||
| 848 | +Tipo: Número entero de 1 a 4 dígitos. | ||
| 849 | + | ||
| 850 | +Ejemplo: `0`. | ||
| 851 | + | ||
| 852 | +### Rubro del ingreso | ||
| 853 | + | ||
| 854 | +Información sobre el origen de los recursos. Cada rubro se describe en 4 niveles: tipo, clase, cuenta y subcuenta, y cada nivel cuenta con un código y una descripción. | ||
| 855 | + | ||
| 856 | +``` | ||
| 857 | +Tipo | ||
| 858 | +└─ Clase | ||
| 859 | + └─ Cuenta | ||
| 860 | + └─ Subcuenta | ||
| 861 | +``` | ||
| 862 | + | ||
| 863 | +#### rubro_tipo | ||
| 864 | + | ||
| 865 | +Código del tipo de rubro. | ||
| 866 | + | ||
| 867 | +Tipo: Número entero de 2 dígitos. | ||
| 868 | + | ||
| 869 | +Ejemplo: `18`. | ||
| 870 | + | ||
| 871 | +#### rubro_clase | ||
| 872 | + | ||
| 873 | +Código de la clase de rubro. | ||
| 874 | + | ||
| 875 | +Tipo: Número entero de 1 dígito. | ||
| 876 | + | ||
| 877 | +Ejemplo: `2`. | ||
| 878 | + | ||
| 879 | +#### rubro_cuenta | ||
| 880 | + | ||
| 881 | +Código de la cuenta de rubro. | ||
| 882 | + | ||
| 883 | +Tipo: Número entero de 1 dígito. | ||
| 884 | + | ||
| 885 | +Ejemplo: `1`. | ||
| 886 | + | ||
| 887 | +#### rubro_sub_cuenta | ||
| 888 | + | ||
| 889 | +Código de la subcuenta de rubro. | ||
| 890 | + | ||
| 891 | +Tipo: Número entero de 1 dígito. | ||
| 892 | + | ||
| 893 | +Ejemplo: `1`. | ||
| 894 | + | ||
| 895 | +#### rubro_desc_tipo | ||
| 896 | + | ||
| 897 | +Descripción del tipo de rubro. | ||
| 898 | + | ||
| 899 | +Tipo: Texto descriptivo. | ||
| 900 | + | ||
| 901 | +Ejemplo: `DONACIONES CORRIENTES`. | ||
| 902 | + | ||
| 903 | +#### rubro_desc_clase | ||
| 904 | + | ||
| 905 | +Descripción de la clase de rubro. | ||
| 906 | + | ||
| 907 | +Tipo: Texto descriptivo. | ||
| 908 | + | ||
| 909 | +Ejemplo: `Donaciones Corrientes del Exterior`. | ||
| 910 | + | ||
| 911 | +#### rubro_desc_cuenta | ||
| 912 | + | ||
| 913 | +Descripción de la cuenta de rubro. | ||
| 914 | + | ||
| 915 | +Tipo: Texto descriptivo. | ||
| 916 | + | ||
| 917 | +Ejemplo: `Del Sector Privado del Exterior`. | ||
| 918 | + | ||
| 919 | +#### rubro_desc_sub_cuenta | ||
| 920 | + | ||
| 921 | +Descripción de la subcuenta de rubro. | ||
| 922 | + | ||
| 923 | +Tipo: Texto descriptivo. | ||
| 924 | + | ||
| 925 | +Ejemplo: `Monetizable`. | ||
| 926 | + | ||
| 927 | +### Montos | ||
| 928 | + | ||
| 929 | +Montos en bolivianos corrientes que hacen al presupuesto y ejecución de cada ingreso. Incluye 2 montos de presupuesto: al inicio de la gestión y luego de modificaciones, y 2 montos de ejecución: devengado y percibido. | ||
| 930 | + | ||
| 931 | +#### inicial | ||
| 932 | + | ||
| 933 | +Monto presupuestado al inicio de la gestión, en bolivianos corrientes. | ||
| 934 | + | ||
| 935 | +Tipo: Monto en bolivianos corrientes con dos decimales. | ||
| 936 | + | ||
| 937 | +Ejemplo: `2014000`. | ||
| 938 | + | ||
| 939 | +#### vigente | ||
| 940 | + | ||
| 941 | +Monto presupuestado luego de modificaciones durante la gestión, en bolivianos corrientes. | ||
| 942 | + | ||
| 943 | +Tipo: Monto en bolivianos corrientes con dos decimales. | ||
| 944 | + | ||
| 945 | +Ejemplo: `5653320`. | ||
| 946 | + | ||
| 947 | +#### devengado | ||
| 948 | + | ||
| 949 | +Monto devengado para la ejecución, en bolivianos corrientes. | ||
| 950 | + | ||
| 951 | +Tipo: Monto en bolivianos corrientes con dos decimales. | ||
| 952 | + | ||
| 953 | +Ejemplo: `5637823.65`. | ||
| 954 | + | ||
| 955 | +#### percibido | ||
| 956 | + | ||
| 957 | +Monto percibido, en bolivianos corrientes. | ||
| 958 | + | ||
| 959 | +Tipo: Monto en bolivianos corrientes con dos decimales. | ||
| 960 | + | ||
| 961 | +Ejemplo: `5637823.65`. | ||
| 962 | + | ||
| 963 | +# Origen y calidad | ||
| 964 | + | ||
| 965 | +La calidad de estas bases depende principalmente de los sistemas dónde se registran y procesan. En la historia reciente del presupuesto la información pudo ser registrada de manera central o descentralizada. Un registro central se almacena directamente en un servidor controlado por el Ministerio de Economía, que tiende a facilitar la coherencia, completitud y resguardo de la base. Mientras que un registro descentralizado se almacena en una computadora de la entidad responsable y sólo es comunicado periódicamente al Ministerio. La probabilidad de errores en el llenado, envío, validación y resguardo histórico de registros descentralizados es mucho mayor. Prácticamente toda la dificultad de reconstruir bases históricas recae en cómo integrar registros descentralizados ruidosos con registros centralizados en buen estado. Estos registros son particularmente comunes antes de 2016, cuando el SIGEP, un sistema centralizado, es implementado en entidades del nivel central del Estado. | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
src/lib/components/objeto/BarChart.svelte
0 → 100644
| 1 | +<script> | ||
| 2 | + import { scaleLinear, scaleBand } from 'd3-scale'; | ||
| 3 | + | ||
| 4 | + let { | ||
| 5 | + data = [], | ||
| 6 | + hoveredYear = $bindable(null), | ||
| 7 | + height = 220, | ||
| 8 | + fill = false, | ||
| 9 | + marginTop = 20, | ||
| 10 | + marginRight = 10, | ||
| 11 | + marginBottom = 30, | ||
| 12 | + marginLeft = 50 | ||
| 13 | + } = $props(); | ||
| 14 | + | ||
| 15 | + // Medir altura real del contenedor cuando fill=true | ||
| 16 | + let wrapperEl = $state(null); | ||
| 17 | + let measuredHeight = $state(height); | ||
| 18 | + | ||
| 19 | + $effect(() => { | ||
| 20 | + if (fill && wrapperEl) { | ||
| 21 | + const updateHeight = () => { | ||
| 22 | + const h = wrapperEl.clientHeight; | ||
| 23 | + if (h > 0) measuredHeight = h; | ||
| 24 | + }; | ||
| 25 | + updateHeight(); | ||
| 26 | + const observer = new ResizeObserver(updateHeight); | ||
| 27 | + observer.observe(wrapperEl); | ||
| 28 | + return () => observer.disconnect(); | ||
| 29 | + } else { | ||
| 30 | + measuredHeight = height; | ||
| 31 | + } | ||
| 32 | + }); | ||
| 33 | + | ||
| 34 | + // Dimensiones del área del gráfico | ||
| 35 | + let effectiveHeight = $derived(fill ? measuredHeight : height); | ||
| 36 | + let chartHeight = $derived(effectiveHeight - marginTop - marginBottom); | ||
| 37 | + | ||
| 38 | + // Escalas D3 | ||
| 39 | + let xScale = $derived( | ||
| 40 | + scaleBand() | ||
| 41 | + .domain(data.map(d => d.año)) | ||
| 42 | + .range([0, 100]) // porcentaje | ||
| 43 | + .padding(0.2) | ||
| 44 | + ); | ||
| 45 | + | ||
| 46 | + // Para CSS bottom positioning: 0 → 0%, max → 100% | ||
| 47 | + let yScale = $derived( | ||
| 48 | + scaleLinear() | ||
| 49 | + .domain([0, Math.max(...data.map(d => d.perCapita))]) | ||
| 50 | + .range([0, chartHeight]) | ||
| 51 | + .nice() | ||
| 52 | + ); | ||
| 53 | + | ||
| 54 | + // Ticks para el eje Y | ||
| 55 | + let yTicks = $derived(yScale.ticks(3)); | ||
| 56 | + | ||
| 57 | + // Índices para mostrar en eje X | ||
| 58 | + let midIndex = $derived(Math.floor(data.length / 2)); | ||
| 59 | + | ||
| 60 | + // Formato para valores del eje Y | ||
| 61 | + function formatValue(v) { | ||
| 62 | + if (v >= 1e6) return (v / 1e6).toLocaleString('es-BO', { maximumFractionDigits: 1 }) + ' M'; | ||
| 63 | + if (v >= 1e3) return Math.round(v / 1e3).toLocaleString('es-BO') + ' K'; | ||
| 64 | + return v.toLocaleString('es-BO'); | ||
| 65 | + } | ||
| 66 | +</script> | ||
| 67 | + | ||
| 68 | +<div class="chart-wrapper" bind:this={wrapperEl} style="height: {fill ? '100%' : height + 'px'}"> | ||
| 69 | + <!-- Contenedor principal con márgenes --> | ||
| 70 | + <div class="chart-inner" style="top: {marginTop}px; bottom: {marginBottom}px; left: {marginLeft}px; right: {marginRight}px"> | ||
| 71 | + | ||
| 72 | + <!-- Y-axis labels (fuera del área de barras, a la izquierda) --> | ||
| 73 | + {#each yTicks as tick} | ||
| 74 | + {@const pct = (yScale(tick) / chartHeight) * 100} | ||
| 75 | + <span class="y-label" style="bottom: {pct}%; left: -{marginLeft}px"> | ||
| 76 | + Bs {formatValue(tick)} | ||
| 77 | + </span> | ||
| 78 | + {/each} | ||
| 79 | + | ||
| 80 | + <!-- Grid lines --> | ||
| 81 | + {#each yTicks as tick} | ||
| 82 | + {@const pct = (yScale(tick) / chartHeight) * 100} | ||
| 83 | + <div class="grid-line" style="bottom: {pct}%"></div> | ||
| 84 | + {/each} | ||
| 85 | + | ||
| 86 | + <!-- Barras --> | ||
| 87 | + <div class="bars" onmouseleave={() => hoveredYear = null} role="group"> | ||
| 88 | + {#each data as d} | ||
| 89 | + {@const barPct = (yScale(d.perCapita) / chartHeight) * 100} | ||
| 90 | + <div | ||
| 91 | + class="bar-container" | ||
| 92 | + onmouseenter={() => hoveredYear = d.año} | ||
| 93 | + role="button" | ||
| 94 | + tabindex="0" | ||
| 95 | + > | ||
| 96 | + <div | ||
| 97 | + class="bar" | ||
| 98 | + class:hovered={hoveredYear === d.año} | ||
| 99 | + style="height: {barPct}%" | ||
| 100 | + ></div> | ||
| 101 | + </div> | ||
| 102 | + {/each} | ||
| 103 | + </div> | ||
| 104 | + </div> | ||
| 105 | + | ||
| 106 | + <!-- Eje X --> | ||
| 107 | + <div class="x-axis" style="height: {marginBottom}px; left: {marginLeft}px; right: {marginRight}px"> | ||
| 108 | + {#each data as d, i} | ||
| 109 | + {@const isFirst = i === 0} | ||
| 110 | + {@const isLast = i === data.length - 1} | ||
| 111 | + {@const isMid = i === midIndex && data.length > 5} | ||
| 112 | + {@const showLabel = isFirst || isLast || isMid || hoveredYear === d.año} | ||
| 113 | + <span | ||
| 114 | + class="x-label" | ||
| 115 | + class:visible={showLabel} | ||
| 116 | + > | ||
| 117 | + {d.año} | ||
| 118 | + </span> | ||
| 119 | + {/each} | ||
| 120 | + </div> | ||
| 121 | +</div> | ||
| 122 | + | ||
| 123 | +<style> | ||
| 124 | + .chart-wrapper { | ||
| 125 | + position: relative; | ||
| 126 | + width: 100%; | ||
| 127 | + transition: height 0.3s ease; | ||
| 128 | + } | ||
| 129 | + | ||
| 130 | + .chart-inner { | ||
| 131 | + position: absolute; | ||
| 132 | + } | ||
| 133 | + | ||
| 134 | + .y-label { | ||
| 135 | + position: absolute; | ||
| 136 | + width: 45px; | ||
| 137 | + text-align: right; | ||
| 138 | + transform: translateY(50%); | ||
| 139 | + font-family: 'Qanelas', var(--font-sans); | ||
| 140 | + font-size: 0.6875rem; | ||
| 141 | + color: var(--theme-texto); | ||
| 142 | + white-space: nowrap; | ||
| 143 | + } | ||
| 144 | + | ||
| 145 | + .grid-line { | ||
| 146 | + position: absolute; | ||
| 147 | + left: 0; | ||
| 148 | + right: 0; | ||
| 149 | + border-top: 0.5px dotted var(--theme-texto); | ||
| 150 | + opacity: 0.15; | ||
| 151 | + pointer-events: none; | ||
| 152 | + } | ||
| 153 | + | ||
| 154 | + .bars { | ||
| 155 | + position: absolute; | ||
| 156 | + top: 0; | ||
| 157 | + left: 0; | ||
| 158 | + right: 0; | ||
| 159 | + bottom: 0; | ||
| 160 | + display: flex; | ||
| 161 | + align-items: flex-end; | ||
| 162 | + gap: 2px; | ||
| 163 | + } | ||
| 164 | + | ||
| 165 | + .bar-container { | ||
| 166 | + flex: 1; | ||
| 167 | + height: 100%; | ||
| 168 | + display: flex; | ||
| 169 | + align-items: flex-end; | ||
| 170 | + cursor: default; | ||
| 171 | + } | ||
| 172 | + | ||
| 173 | + .bar { | ||
| 174 | + width: 100%; | ||
| 175 | + background: #c4897d; | ||
| 176 | + border-radius: 4px 4px 0 0; | ||
| 177 | + transition: opacity 0.1s ease; | ||
| 178 | + min-height: 2px; | ||
| 179 | + } | ||
| 180 | + | ||
| 181 | + .bars:hover .bar { | ||
| 182 | + opacity: 0.35; | ||
| 183 | + } | ||
| 184 | + | ||
| 185 | + .bars:hover .bar.hovered { | ||
| 186 | + opacity: 1; | ||
| 187 | + } | ||
| 188 | + | ||
| 189 | + .x-axis { | ||
| 190 | + position: absolute; | ||
| 191 | + bottom: 0; | ||
| 192 | + display: flex; | ||
| 193 | + align-items: center; | ||
| 194 | + } | ||
| 195 | + | ||
| 196 | + .x-label { | ||
| 197 | + flex: 1; | ||
| 198 | + text-align: center; | ||
| 199 | + font-family: 'Qanelas', var(--font-sans); | ||
| 200 | + font-size: 0.6875rem; | ||
| 201 | + color: var(--theme-texto); | ||
| 202 | + opacity: 0; | ||
| 203 | + transition: opacity 0.2s ease; | ||
| 204 | + } | ||
| 205 | + | ||
| 206 | + .x-label.visible { | ||
| 207 | + opacity: 1; | ||
| 208 | + } | ||
| 209 | +</style> |
| 1 | +<script> | ||
| 2 | + let { | ||
| 3 | + entidades = [], | ||
| 4 | + selectedEntity = $bindable(null), | ||
| 5 | + nEntidades = 0 | ||
| 6 | + } = $props(); | ||
| 7 | + | ||
| 8 | + let dropdownOpen = $state(false); | ||
| 9 | + let searchQuery = $state(''); | ||
| 10 | + let entityLimit = $state(30); | ||
| 11 | + | ||
| 12 | + let filteredEntities = $derived.by(() => { | ||
| 13 | + if (!searchQuery) return entidades.slice(0, entityLimit); | ||
| 14 | + const q = searchQuery.toLowerCase(); | ||
| 15 | + return entidades | ||
| 16 | + .filter(e => | ||
| 17 | + e.entidad_desc?.toLowerCase().includes(q) || | ||
| 18 | + e.entidad?.toString().includes(q) | ||
| 19 | + ) | ||
| 20 | + .slice(0, entityLimit); | ||
| 21 | + }); | ||
| 22 | + | ||
| 23 | + function selectEntity(entity) { | ||
| 24 | + selectedEntity = entity; | ||
| 25 | + dropdownOpen = false; | ||
| 26 | + searchQuery = ''; | ||
| 27 | + } | ||
| 28 | + | ||
| 29 | + function clearEntity() { | ||
| 30 | + selectedEntity = null; | ||
| 31 | + dropdownOpen = false; | ||
| 32 | + searchQuery = ''; | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + function handleScroll(e) { | ||
| 36 | + const { scrollTop, scrollHeight, clientHeight } = e.target; | ||
| 37 | + if (scrollTop + clientHeight >= scrollHeight - 50) { | ||
| 38 | + entityLimit += 20; | ||
| 39 | + } | ||
| 40 | + } | ||
| 41 | +</script> | ||
| 42 | + | ||
| 43 | +<div class="entity-selector"> | ||
| 44 | + <button class="entity-btn" onclick={() => dropdownOpen = !dropdownOpen}> | ||
| 45 | + <span class="entity-label"> | ||
| 46 | + {#if selectedEntity} | ||
| 47 | + {selectedEntity.entidad_desc} | ||
| 48 | + {:else} | ||
| 49 | + Todo el sector público | ||
| 50 | + {/if} | ||
| 51 | + </span> | ||
| 52 | + <svg class="chevron" class:open={dropdownOpen} width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | ||
| 53 | + <path d="M6 9l6 6 6-6"/> | ||
| 54 | + </svg> | ||
| 55 | + </button> | ||
| 56 | + | ||
| 57 | + {#if dropdownOpen} | ||
| 58 | + <div class="entity-dropdown"> | ||
| 59 | + <div class="dropdown-search"> | ||
| 60 | + <input | ||
| 61 | + type="text" | ||
| 62 | + bind:value={searchQuery} | ||
| 63 | + placeholder="Buscar entidad..." | ||
| 64 | + /> | ||
| 65 | + </div> | ||
| 66 | + <button | ||
| 67 | + class="dropdown-item" | ||
| 68 | + class:active={!selectedEntity} | ||
| 69 | + onclick={clearEntity} | ||
| 70 | + > | ||
| 71 | + Todo el sector público | ||
| 72 | + <span class="item-count">({nEntidades})</span> | ||
| 73 | + </button> | ||
| 74 | + <div class="dropdown-list" onscroll={handleScroll}> | ||
| 75 | + {#each filteredEntities as entity} | ||
| 76 | + <button | ||
| 77 | + class="dropdown-item" | ||
| 78 | + class:active={selectedEntity?.entidad === entity.entidad} | ||
| 79 | + onclick={() => selectEntity(entity)} | ||
| 80 | + > | ||
| 81 | + <span class="item-code">{entity.entidad}</span> | ||
| 82 | + <span class="item-name">{entity.entidad_desc}</span> | ||
| 83 | + </button> | ||
| 84 | + {/each} | ||
| 85 | + </div> | ||
| 86 | + </div> | ||
| 87 | + {/if} | ||
| 88 | +</div> | ||
| 89 | + | ||
| 90 | +<style> | ||
| 91 | + .entity-selector { | ||
| 92 | + position: relative; | ||
| 93 | + flex: 1; | ||
| 94 | + min-width: 0; | ||
| 95 | + } | ||
| 96 | + | ||
| 97 | + .entity-btn { | ||
| 98 | + display: inline-flex; | ||
| 99 | + align-items: center; | ||
| 100 | + gap: 0.375rem; | ||
| 101 | + padding: 0.125rem 0; | ||
| 102 | + background: transparent; | ||
| 103 | + border: none; | ||
| 104 | + border-bottom: 1px dotted #333333; | ||
| 105 | + color: var(--theme-titulo); | ||
| 106 | + font-size: 1rem; | ||
| 107 | + font-weight: 500; | ||
| 108 | + cursor: pointer; | ||
| 109 | + transition: border-color 0.2s; | ||
| 110 | + } | ||
| 111 | + | ||
| 112 | + .entity-btn:hover { | ||
| 113 | + border-bottom: 1px dotted #666666; | ||
| 114 | + } | ||
| 115 | + | ||
| 116 | + .entity-label { | ||
| 117 | + text-align: left; | ||
| 118 | + } | ||
| 119 | + | ||
| 120 | + .chevron { | ||
| 121 | + color: var(--theme-texto); | ||
| 122 | + opacity: 0.5; | ||
| 123 | + transition: transform 0.2s; | ||
| 124 | + flex-shrink: 0; | ||
| 125 | + } | ||
| 126 | + | ||
| 127 | + .chevron.open { | ||
| 128 | + transform: rotate(180deg); | ||
| 129 | + } | ||
| 130 | + | ||
| 131 | + .entity-dropdown { | ||
| 132 | + position: absolute; | ||
| 133 | + top: calc(100% + 4px); | ||
| 134 | + left: 0; | ||
| 135 | + min-width: 280px; | ||
| 136 | + background: var(--theme-surface); | ||
| 137 | + border: 1px solid var(--theme-borde); | ||
| 138 | + border-radius: 10px; | ||
| 139 | + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); | ||
| 140 | + z-index: 100; | ||
| 141 | + overflow: hidden; | ||
| 142 | + } | ||
| 143 | + | ||
| 144 | + .dropdown-search { | ||
| 145 | + padding: 0.5rem; | ||
| 146 | + border-bottom: 1px solid var(--theme-borde); | ||
| 147 | + } | ||
| 148 | + | ||
| 149 | + .dropdown-search input { | ||
| 150 | + width: 100%; | ||
| 151 | + padding: 0.5rem 0.75rem; | ||
| 152 | + border: 1px solid var(--theme-borde); | ||
| 153 | + border-radius: 6px; | ||
| 154 | + background: var(--theme-body); | ||
| 155 | + color: var(--theme-titulo); | ||
| 156 | + font-size: 0.8125rem; | ||
| 157 | + outline: none; | ||
| 158 | + } | ||
| 159 | + | ||
| 160 | + .dropdown-search input:focus { | ||
| 161 | + border-color: #6B9FD4; | ||
| 162 | + } | ||
| 163 | + | ||
| 164 | + .dropdown-item { | ||
| 165 | + display: flex; | ||
| 166 | + align-items: center; | ||
| 167 | + gap: 0.5rem; | ||
| 168 | + width: 100%; | ||
| 169 | + padding: 0.625rem 0.75rem; | ||
| 170 | + border: none; | ||
| 171 | + background: transparent; | ||
| 172 | + color: var(--theme-titulo); | ||
| 173 | + font-size: 0.8125rem; | ||
| 174 | + text-align: left; | ||
| 175 | + cursor: pointer; | ||
| 176 | + transition: background 0.15s; | ||
| 177 | + } | ||
| 178 | + | ||
| 179 | + .dropdown-item:hover { | ||
| 180 | + background: var(--theme-surface-hover); | ||
| 181 | + } | ||
| 182 | + | ||
| 183 | + .dropdown-item.active { | ||
| 184 | + background: rgba(107, 159, 212, 0.1); | ||
| 185 | + color: #6B9FD4; | ||
| 186 | + } | ||
| 187 | + | ||
| 188 | + .item-count { | ||
| 189 | + margin-left: auto; | ||
| 190 | + font-size: 0.75rem; | ||
| 191 | + color: var(--theme-texto); | ||
| 192 | + opacity: 0.6; | ||
| 193 | + } | ||
| 194 | + | ||
| 195 | + .item-code { | ||
| 196 | + font-family: 'DM Mono', monospace; | ||
| 197 | + font-size: 0.6875rem; | ||
| 198 | + color: var(--theme-texto); | ||
| 199 | + min-width: 2rem; | ||
| 200 | + } | ||
| 201 | + | ||
| 202 | + .item-name { | ||
| 203 | + flex: 1; | ||
| 204 | + overflow: hidden; | ||
| 205 | + text-overflow: ellipsis; | ||
| 206 | + white-space: nowrap; | ||
| 207 | + } | ||
| 208 | + | ||
| 209 | + .dropdown-list { | ||
| 210 | + max-height: 240px; | ||
| 211 | + overflow-y: auto; | ||
| 212 | + } | ||
| 213 | + | ||
| 214 | + :global(html.dark) .dropdown-item.active { | ||
| 215 | + background: rgba(201, 167, 81, 0.1); | ||
| 216 | + color: var(--theme-accent); | ||
| 217 | + } | ||
| 218 | +</style> |
| 1 | +<script> | ||
| 2 | + import { goto } from '$app/navigation'; | ||
| 3 | + import { | ||
| 4 | + query, | ||
| 5 | + results, | ||
| 6 | + isLoading as searchLoading, | ||
| 7 | + indexLoaded, | ||
| 8 | + selectedIndex, | ||
| 9 | + performSearch, | ||
| 10 | + clearSearch, | ||
| 11 | + navigateResults | ||
| 12 | + } from '$lib/stores/searchStore'; | ||
| 13 | + | ||
| 14 | + let searchInputRef = $state(null); | ||
| 15 | + let searchVal = $state(''); | ||
| 16 | + let searchFocused = $state(false); | ||
| 17 | + let debounceTimer; | ||
| 18 | + | ||
| 19 | + // Filtrar solo objeto_gasto | ||
| 20 | + let filteredSearchResults = $derived($results.filter(r => r.tipo === 'objeto_gasto')); | ||
| 21 | + | ||
| 22 | + function handleSearchInput(e) { | ||
| 23 | + const val = e.target.value; | ||
| 24 | + clearTimeout(debounceTimer); | ||
| 25 | + debounceTimer = setTimeout(() => { | ||
| 26 | + if (val.length >= 2) { | ||
| 27 | + performSearch(val); | ||
| 28 | + } else { | ||
| 29 | + clearSearch(); | ||
| 30 | + } | ||
| 31 | + }, 200); | ||
| 32 | + } | ||
| 33 | + | ||
| 34 | + function handleSearchKeydown(e) { | ||
| 35 | + if (e.key === 'ArrowDown' || e.key === 'ArrowUp') { | ||
| 36 | + e.preventDefault(); | ||
| 37 | + navigateResults(e.key === 'ArrowDown' ? 1 : -1, filteredSearchResults.length); | ||
| 38 | + } else if (e.key === 'Enter' && filteredSearchResults.length > 0) { | ||
| 39 | + e.preventDefault(); | ||
| 40 | + const selected = filteredSearchResults[$selectedIndex]; | ||
| 41 | + if (selected) handleSearchSelect(selected); | ||
| 42 | + } else if (e.key === 'Escape') { | ||
| 43 | + searchVal = ''; | ||
| 44 | + clearSearch(); | ||
| 45 | + searchInputRef?.blur(); | ||
| 46 | + } | ||
| 47 | + } | ||
| 48 | + | ||
| 49 | + function handleSearchSelect(result) { | ||
| 50 | + goto(`/objeto/${result.codigo}`); | ||
| 51 | + searchVal = ''; | ||
| 52 | + clearSearch(); | ||
| 53 | + searchFocused = false; | ||
| 54 | + } | ||
| 55 | + | ||
| 56 | + function handleSearchBlur() { | ||
| 57 | + setTimeout(() => { | ||
| 58 | + searchFocused = false; | ||
| 59 | + }, 150); | ||
| 60 | + } | ||
| 61 | + | ||
| 62 | + function handleClear() { | ||
| 63 | + clearSearch(); | ||
| 64 | + searchVal = ''; | ||
| 65 | + } | ||
| 66 | +</script> | ||
| 67 | + | ||
| 68 | +<div class="objeto-search" class:focused={searchFocused}> | ||
| 69 | + <svg class="search-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | ||
| 70 | + <circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/> | ||
| 71 | + </svg> | ||
| 72 | + <input | ||
| 73 | + type="text" | ||
| 74 | + bind:this={searchInputRef} | ||
| 75 | + bind:value={searchVal} | ||
| 76 | + oninput={handleSearchInput} | ||
| 77 | + onkeydown={handleSearchKeydown} | ||
| 78 | + onfocus={() => searchFocused = true} | ||
| 79 | + onblur={handleSearchBlur} | ||
| 80 | + placeholder="Buscar objeto..." | ||
| 81 | + disabled={!$indexLoaded} | ||
| 82 | + /> | ||
| 83 | + {#if searchVal} | ||
| 84 | + <button class="search-clear" onclick={handleClear}> | ||
| 85 | + <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | ||
| 86 | + <line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/> | ||
| 87 | + </svg> | ||
| 88 | + </button> | ||
| 89 | + {/if} | ||
| 90 | + {#if searchFocused && searchVal.length >= 2} | ||
| 91 | + <div class="search-dropdown"> | ||
| 92 | + {#if $searchLoading} | ||
| 93 | + <div class="search-msg">Buscando...</div> | ||
| 94 | + {:else if filteredSearchResults.length === 0} | ||
| 95 | + <div class="search-msg">Sin resultados</div> | ||
| 96 | + {:else} | ||
| 97 | + {#each filteredSearchResults as result, i} | ||
| 98 | + <button | ||
| 99 | + class="search-item" | ||
| 100 | + class:selected={$selectedIndex === i} | ||
| 101 | + onclick={() => handleSearchSelect(result)} | ||
| 102 | + onmouseenter={() => selectedIndex.set(i)} | ||
| 103 | + > | ||
| 104 | + <span class="item-code">{result.codigo}</span> | ||
| 105 | + <span class="item-name">{result.nombre}</span> | ||
| 106 | + </button> | ||
| 107 | + {/each} | ||
| 108 | + {/if} | ||
| 109 | + </div> | ||
| 110 | + {/if} | ||
| 111 | +</div> | ||
| 112 | + | ||
| 113 | +<style> | ||
| 114 | + .objeto-search { | ||
| 115 | + position: relative; | ||
| 116 | + display: flex; | ||
| 117 | + align-items: center; | ||
| 118 | + gap: 0.5rem; | ||
| 119 | + background: #f5f5f5; | ||
| 120 | + border: none; | ||
| 121 | + border-radius: 8px; | ||
| 122 | + padding: 0.625rem 1rem; | ||
| 123 | + transition: box-shadow 0.2s; | ||
| 124 | + width: 100%; | ||
| 125 | + } | ||
| 126 | + | ||
| 127 | + .objeto-search.focused { | ||
| 128 | + border-color: #6B9FD4; | ||
| 129 | + box-shadow: 0 0 0 3px rgba(107, 159, 212, 0.15); | ||
| 130 | + } | ||
| 131 | + | ||
| 132 | + .search-icon { | ||
| 133 | + color: var(--theme-texto); | ||
| 134 | + opacity: 0.5; | ||
| 135 | + flex-shrink: 0; | ||
| 136 | + } | ||
| 137 | + | ||
| 138 | + input { | ||
| 139 | + flex: 1; | ||
| 140 | + border: none; | ||
| 141 | + background: transparent; | ||
| 142 | + color: var(--theme-titulo); | ||
| 143 | + font-size: 0.8125rem; | ||
| 144 | + outline: none; | ||
| 145 | + min-width: 120px; | ||
| 146 | + } | ||
| 147 | + | ||
| 148 | + input::placeholder { | ||
| 149 | + color: var(--theme-texto); | ||
| 150 | + opacity: 0.5; | ||
| 151 | + } | ||
| 152 | + | ||
| 153 | + .search-clear { | ||
| 154 | + display: flex; | ||
| 155 | + align-items: center; | ||
| 156 | + justify-content: center; | ||
| 157 | + padding: 0.25rem; | ||
| 158 | + border-radius: 4px; | ||
| 159 | + border: none; | ||
| 160 | + background: transparent; | ||
| 161 | + color: var(--theme-texto); | ||
| 162 | + cursor: pointer; | ||
| 163 | + transition: background 0.2s; | ||
| 164 | + } | ||
| 165 | + | ||
| 166 | + .search-clear:hover { | ||
| 167 | + background: var(--theme-borde); | ||
| 168 | + } | ||
| 169 | + | ||
| 170 | + .search-dropdown { | ||
| 171 | + position: absolute; | ||
| 172 | + top: calc(100% + 4px); | ||
| 173 | + left: 0; | ||
| 174 | + right: 0; | ||
| 175 | + background: var(--theme-surface); | ||
| 176 | + border: 1px solid var(--theme-borde); | ||
| 177 | + border-radius: 8px; | ||
| 178 | + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); | ||
| 179 | + max-height: 280px; | ||
| 180 | + overflow-y: auto; | ||
| 181 | + z-index: 100; | ||
| 182 | + } | ||
| 183 | + | ||
| 184 | + .search-msg { | ||
| 185 | + padding: 0.75rem 1rem; | ||
| 186 | + font-size: 0.8125rem; | ||
| 187 | + color: var(--theme-texto); | ||
| 188 | + opacity: 0.7; | ||
| 189 | + } | ||
| 190 | + | ||
| 191 | + .search-item { | ||
| 192 | + display: flex; | ||
| 193 | + align-items: center; | ||
| 194 | + gap: 0.75rem; | ||
| 195 | + width: 100%; | ||
| 196 | + padding: 0.625rem 1rem; | ||
| 197 | + border: none; | ||
| 198 | + background: transparent; | ||
| 199 | + text-align: left; | ||
| 200 | + cursor: pointer; | ||
| 201 | + transition: background 0.15s; | ||
| 202 | + } | ||
| 203 | + | ||
| 204 | + .search-item:hover, | ||
| 205 | + .search-item.selected { | ||
| 206 | + background: var(--theme-surface-hover); | ||
| 207 | + } | ||
| 208 | + | ||
| 209 | + .item-code { | ||
| 210 | + font-family: 'DM Mono', monospace; | ||
| 211 | + font-size: 0.75rem; | ||
| 212 | + color: var(--theme-texto); | ||
| 213 | + min-width: 3.5rem; | ||
| 214 | + } | ||
| 215 | + | ||
| 216 | + .item-name { | ||
| 217 | + font-size: 0.8125rem; | ||
| 218 | + color: var(--theme-titulo); | ||
| 219 | + } | ||
| 220 | +</style> |
src/lib/components/objeto/VistaToggle.svelte
0 → 100644
| 1 | +<script> | ||
| 2 | + let { vista = $bindable('agregado') } = $props(); | ||
| 3 | + | ||
| 4 | + function toggle() { | ||
| 5 | + vista = vista === 'agregado' ? 'comparar' : 'agregado'; | ||
| 6 | + } | ||
| 7 | +</script> | ||
| 8 | + | ||
| 9 | +<button class="vista-btn" onclick={toggle}> | ||
| 10 | + {#if vista === 'agregado'} | ||
| 11 | + Comparar entre entidades | ||
| 12 | + <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | ||
| 13 | + <path d="M5 12h14M12 5l7 7-7 7"/> | ||
| 14 | + </svg> | ||
| 15 | + {:else} | ||
| 16 | + <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | ||
| 17 | + <path d="M19 12H5M12 19l-7-7 7-7"/> | ||
| 18 | + </svg> | ||
| 19 | + Ver todo | ||
| 20 | + {/if} | ||
| 21 | +</button> | ||
| 22 | + | ||
| 23 | +<style> | ||
| 24 | + .vista-btn { | ||
| 25 | + display: inline-flex; | ||
| 26 | + align-items: center; | ||
| 27 | + gap: 0.375rem; | ||
| 28 | + font-family: 'Qanelas', var(--font-sans); | ||
| 29 | + font-size: 0.75rem; | ||
| 30 | + font-weight: 500; | ||
| 31 | + padding: 0; | ||
| 32 | + border: none; | ||
| 33 | + background: transparent; | ||
| 34 | + color: #666666; | ||
| 35 | + cursor: pointer; | ||
| 36 | + transition: color 0.15s ease; | ||
| 37 | + white-space: nowrap; | ||
| 38 | + text-decoration: underline dotted; | ||
| 39 | + text-underline-offset: 3px; | ||
| 40 | + text-decoration-color: #666666; | ||
| 41 | + } | ||
| 42 | + | ||
| 43 | + .vista-btn svg { | ||
| 44 | + flex-shrink: 0; | ||
| 45 | + opacity: 0.7; | ||
| 46 | + } | ||
| 47 | + | ||
| 48 | + .vista-btn:hover { | ||
| 49 | + color: #999999; | ||
| 50 | + text-decoration-color: #666666; | ||
| 51 | + } | ||
| 52 | + | ||
| 53 | + .vista-btn:hover svg { | ||
| 54 | + opacity: 1; | ||
| 55 | + } | ||
| 56 | + | ||
| 57 | + .vista-btn:active { | ||
| 58 | + color: #555555; | ||
| 59 | + } | ||
| 60 | +</style> |
| ... | @@ -60,13 +60,25 @@ | ... | @@ -60,13 +60,25 @@ |
| 60 | </div> | 60 | </div> |
| 61 | {/if} | 61 | {/if} |
| 62 | 62 | ||
| 63 | -<div class="min-h-screen" style="background-color: var(--theme-body);"> | 63 | +<div class="layout-wrapper"> |
| 64 | - <main> | 64 | + <main class="layout-main"> |
| 65 | {@render children()} | 65 | {@render children()} |
| 66 | </main> | 66 | </main> |
| 67 | </div> | 67 | </div> |
| 68 | 68 | ||
| 69 | <style> | 69 | <style> |
| 70 | + .layout-wrapper { | ||
| 71 | + min-height: 100vh; | ||
| 72 | + width: 100%; | ||
| 73 | + background-color: var(--theme-body); | ||
| 74 | + } | ||
| 75 | + | ||
| 76 | + .layout-main { | ||
| 77 | + width: 100%; | ||
| 78 | + max-width: 100%; | ||
| 79 | + overflow-x: hidden; | ||
| 80 | + } | ||
| 81 | + | ||
| 70 | .nav-loader { | 82 | .nav-loader { |
| 71 | position: fixed; | 83 | position: fixed; |
| 72 | top: 0; | 84 | top: 0; | ... | ... |
| ... | @@ -37,6 +37,7 @@ | ... | @@ -37,6 +37,7 @@ |
| 37 | let gearMenuOpen = false; | 37 | let gearMenuOpen = false; |
| 38 | let searchInput; | 38 | let searchInput; |
| 39 | let debounceTimer; | 39 | let debounceTimer; |
| 40 | + let autoFocused = false; | ||
| 40 | 41 | ||
| 41 | // Search filters - which types to include | 42 | // Search filters - which types to include |
| 42 | let searchFilters = { | 43 | let searchFilters = { |
| ... | @@ -68,6 +69,14 @@ | ... | @@ -68,6 +69,14 @@ |
| 68 | warmupSupabase(); | 69 | warmupSupabase(); |
| 69 | }); | 70 | }); |
| 70 | 71 | ||
| 72 | + // Auto-focus en el buscador cuando el índice esté listo (solo una vez) | ||
| 73 | + $: if ($indexLoaded && searchInput && !autoFocused) { | ||
| 74 | + autoFocused = true; | ||
| 75 | + setTimeout(() => { | ||
| 76 | + searchInput?.focus(); | ||
| 77 | + }, 50); | ||
| 78 | + } | ||
| 79 | + | ||
| 71 | function toggleMobileSettings() { | 80 | function toggleMobileSettings() { |
| 72 | mobileSettingsOpen = !mobileSettingsOpen; | 81 | mobileSettingsOpen = !mobileSettingsOpen; |
| 73 | } | 82 | } |
| ... | @@ -124,8 +133,12 @@ | ... | @@ -124,8 +133,12 @@ |
| 124 | break; | 133 | break; |
| 125 | case 'Enter': | 134 | case 'Enter': |
| 126 | e.preventDefault(); | 135 | e.preventDefault(); |
| 136 | + // Si hay un resultado seleccionado, navegar a él | ||
| 127 | if ($selectedIndex >= 0 && filteredResults[$selectedIndex]) { | 137 | if ($selectedIndex >= 0 && filteredResults[$selectedIndex]) { |
| 128 | handleSelect(filteredResults[$selectedIndex]); | 138 | handleSelect(filteredResults[$selectedIndex]); |
| 139 | + } else if (filteredResults.length > 0) { | ||
| 140 | + // Si no hay selección pero hay resultados, ir al primero | ||
| 141 | + handleSelect(filteredResults[0]); | ||
| 129 | } | 142 | } |
| 130 | break; | 143 | break; |
| 131 | case 'Escape': | 144 | case 'Escape': |
| ... | @@ -323,11 +336,11 @@ | ... | @@ -323,11 +336,11 @@ |
| 323 | on:blur={handleBlur} | 336 | on:blur={handleBlur} |
| 324 | disabled={!$indexLoaded} | 337 | disabled={!$indexLoaded} |
| 325 | /> | 338 | /> |
| 326 | - {#if !searchVal && !searchFocused} | 339 | + {#if !searchVal} |
| 327 | <div class="search-placeholder"> | 340 | <div class="search-placeholder"> |
| 328 | <span class="blink-cursor" style="background:{accent}"></span> | 341 | <span class="blink-cursor" style="background:{accent}"></span> |
| 329 | - <span class="placeholder-text placeholder-desktop">{$indexLoaded ? placeholder : 'Cargando...'}</span> | 342 | + <span class="placeholder-text placeholder-desktop">{placeholder}</span> |
| 330 | - <span class="placeholder-text placeholder-mobile">{$indexLoaded ? (isD ? 'MEFP, Min. Salud, GAM...' : 'BOA, Publicidad, IDH...') : 'Cargando...'}</span> | 343 | + <span class="placeholder-text placeholder-mobile">{isD ? 'MEFP, Min. Salud, GAM...' : 'BOA, Publicidad, IDH...'}</span> |
| 331 | </div> | 344 | </div> |
| 332 | {/if} | 345 | {/if} |
| 333 | {#if searchVal && !$isLoading} | 346 | {#if searchVal && !$isLoading} | ... | ... |
| ... | @@ -16,19 +16,6 @@ | ... | @@ -16,19 +16,6 @@ |
| 16 | let highlightedItem = $state(null); | 16 | let highlightedItem = $state(null); |
| 17 | let sidebarOpen = $state(false); | 17 | let sidebarOpen = $state(false); |
| 18 | 18 | ||
| 19 | - // Modo embed (cuando está dentro de un iframe en /objeto/[codigo]) | ||
| 20 | - let isEmbed = $derived($page.url.searchParams.get('embed') === 'true'); | ||
| 21 | - let initialObjeto = $derived($page.url.searchParams.get('objeto')); | ||
| 22 | - | ||
| 23 | - // Función para navegar a un objeto (en modo embed envía mensaje al padre) | ||
| 24 | - function navigateToObjeto(codigo) { | ||
| 25 | - if (isEmbed && window.parent !== window) { | ||
| 26 | - window.parent.postMessage({ type: 'navigate-objeto', codigo }, '*'); | ||
| 27 | - } else { | ||
| 28 | - goto(`/objeto/${codigo}`); | ||
| 29 | - } | ||
| 30 | - } | ||
| 31 | - | ||
| 32 | // Modo de visualización desde URL | 19 | // Modo de visualización desde URL |
| 33 | let viewMode = $derived($page.url.searchParams.get('modo') || 'lista'); | 20 | let viewMode = $derived($page.url.searchParams.get('modo') || 'lista'); |
| 34 | 21 | ||
| ... | @@ -1459,7 +1446,6 @@ | ... | @@ -1459,7 +1446,6 @@ |
| 1459 | class="transition-colors hover:text-[var(--theme-accent)]" | 1446 | class="transition-colors hover:text-[var(--theme-accent)]" |
| 1460 | style="color: var(--theme-texto);" | 1447 | style="color: var(--theme-texto);" |
| 1461 | title="Ver detalle" | 1448 | title="Ver detalle" |
| 1462 | - onclick={(e) => { if (isEmbed) { e.preventDefault(); navigateToObjeto(selectedGrupo.objeto); }}} | ||
| 1463 | > | 1449 | > |
| 1464 | <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | 1450 | <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| 1465 | <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /> | 1451 | <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /> |
| ... | @@ -1510,7 +1496,6 @@ | ... | @@ -1510,7 +1496,6 @@ |
| 1510 | class="transition-colors hover:text-[var(--theme-accent)]" | 1496 | class="transition-colors hover:text-[var(--theme-accent)]" |
| 1511 | style="color: var(--theme-texto);" | 1497 | style="color: var(--theme-texto);" |
| 1512 | title="Ver página de detalle" | 1498 | title="Ver página de detalle" |
| 1513 | - onclick={(e) => { if (isEmbed) { e.preventDefault(); navigateToObjeto(subgrupo.objeto); }}} | ||
| 1514 | > | 1499 | > |
| 1515 | <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | 1500 | <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| 1516 | <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /> | 1501 | <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /> |
| ... | @@ -1557,7 +1542,6 @@ | ... | @@ -1557,7 +1542,6 @@ |
| 1557 | class="transition-colors hover:text-[var(--theme-accent)]" | 1542 | class="transition-colors hover:text-[var(--theme-accent)]" |
| 1558 | style="color: var(--theme-texto);" | 1543 | style="color: var(--theme-texto);" |
| 1559 | title="Ver página de detalle" | 1544 | title="Ver página de detalle" |
| 1560 | - onclick={(e) => { if (isEmbed) { e.preventDefault(); navigateToObjeto(partida.objeto); }}} | ||
| 1561 | > | 1545 | > |
| 1562 | <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | 1546 | <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| 1563 | <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /> | 1547 | <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /> |
| ... | @@ -1600,7 +1584,6 @@ | ... | @@ -1600,7 +1584,6 @@ |
| 1600 | class="transition-colors hover:text-[var(--theme-accent)]" | 1584 | class="transition-colors hover:text-[var(--theme-accent)]" |
| 1601 | style="color: var(--theme-texto);" | 1585 | style="color: var(--theme-texto);" |
| 1602 | title="Ver página de detalle" | 1586 | title="Ver página de detalle" |
| 1603 | - onclick={(e) => { if (isEmbed) { e.preventDefault(); navigateToObjeto(subpartida.objeto); }}} | ||
| 1604 | > | 1587 | > |
| 1605 | <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | 1588 | <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| 1606 | <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /> | 1589 | <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /> | ... | ... |
| ... | @@ -2,9 +2,14 @@ | ... | @@ -2,9 +2,14 @@ |
| 2 | import { onMount } from 'svelte'; | 2 | import { onMount } from 'svelte'; |
| 3 | import { tweened } from 'svelte/motion'; | 3 | import { tweened } from 'svelte/motion'; |
| 4 | import { cubicOut } from 'svelte/easing'; | 4 | import { cubicOut } from 'svelte/easing'; |
| 5 | - import { supabase } from '$lib/supabase'; | ||
| 6 | import { goto } from '$app/navigation'; | 5 | import { goto } from '$app/navigation'; |
| 6 | + import { supabase } from '$lib/supabase'; | ||
| 7 | import Spinner from '$lib/components/ui/Spinner.svelte'; | 7 | import Spinner from '$lib/components/ui/Spinner.svelte'; |
| 8 | + import ObjetoSearch from '$lib/components/objeto/ObjetoSearch.svelte'; | ||
| 9 | + import VistaToggle from '$lib/components/objeto/VistaToggle.svelte'; | ||
| 10 | + import EntitySelector from '$lib/components/objeto/EntitySelector.svelte'; | ||
| 11 | + import BarChart from '$lib/components/objeto/BarChart.svelte'; | ||
| 12 | + import { initIndex } from '$lib/stores/searchStore'; | ||
| 8 | 13 | ||
| 9 | // Datos del servidor (reactivos) | 14 | // Datos del servidor (reactivos) |
| 10 | let { data } = $props(); | 15 | let { data } = $props(); |
| ... | @@ -23,9 +28,12 @@ | ... | @@ -23,9 +28,12 @@ |
| 23 | let loading = $state(true); | 28 | let loading = $state(true); |
| 24 | let hoveredYear = $state(null); | 29 | let hoveredYear = $state(null); |
| 25 | let showInfo = $state(false); | 30 | let showInfo = $state(false); |
| 31 | + let showDefinicionesModal = $state(false); | ||
| 32 | + let showMobileSidebar = $state(false); | ||
| 26 | let vista = $state('agregado'); // 'agregado' | 'comparar' | 33 | let vista = $state('agregado'); // 'agregado' | 'comparar' |
| 27 | let isDark = $state(true); | 34 | let isDark = $state(true); |
| 28 | 35 | ||
| 36 | + | ||
| 29 | // Parsear descripciones del objeto | 37 | // Parsear descripciones del objeto |
| 30 | function parseDescripciones(descripcionesStr) { | 38 | function parseDescripciones(descripcionesStr) { |
| 31 | if (!descripcionesStr) return []; | 39 | if (!descripcionesStr) return []; |
| ... | @@ -82,8 +90,41 @@ | ... | @@ -82,8 +90,41 @@ |
| 82 | return labels[nivel] || nivel; | 90 | return labels[nivel] || nivel; |
| 83 | } | 91 | } |
| 84 | 92 | ||
| 93 | + function getNivelPlural(nivel) { | ||
| 94 | + const labels = { grupo: 'grupos', subgrupo: 'subgrupos', partida: 'partidas', subpartida: 'subpartidas' }; | ||
| 95 | + return labels[nivel] || nivel; | ||
| 96 | + } | ||
| 97 | + | ||
| 98 | + function getNivelGastado(nivel) { | ||
| 99 | + const gastado = { grupo: 'gastado', subgrupo: 'gastado', partida: 'gastada', subpartida: 'gastada' }; | ||
| 100 | + return gastado[nivel] || 'gastado'; | ||
| 101 | + } | ||
| 102 | + | ||
| 103 | + function getNivelSingular(nivel) { | ||
| 104 | + const labels = { grupo: 'grupo', subgrupo: 'subgrupo', partida: 'partida', subpartida: 'subpartida' }; | ||
| 105 | + return labels[nivel] || nivel; | ||
| 106 | + } | ||
| 107 | + | ||
| 108 | + function getNivelArticulo(nivel) { | ||
| 109 | + const articulos = { grupo: 'el', subgrupo: 'el', partida: 'la', subpartida: 'la' }; | ||
| 110 | + return articulos[nivel] || 'el'; | ||
| 111 | + } | ||
| 112 | + | ||
| 113 | + function getNivelArticuloContracto(nivel) { | ||
| 114 | + const articulos = { grupo: 'del', subgrupo: 'del', partida: 'de la', subpartida: 'de la' }; | ||
| 115 | + return articulos[nivel] || 'del'; | ||
| 116 | + } | ||
| 117 | + | ||
| 118 | + function getHijosNivelLabel(nivel) { | ||
| 119 | + const labels = { grupo: 'subgrupos', subgrupo: 'partidas', partida: 'subpartidas' }; | ||
| 120 | + return labels[nivel] || ''; | ||
| 121 | + } | ||
| 122 | + | ||
| 85 | let descripciones = $derived(parseDescripciones(objetoData.descripciones)); | 123 | let descripciones = $derived(parseDescripciones(objetoData.descripciones)); |
| 86 | 124 | ||
| 125 | + // Texto narrativo para el nivel | ||
| 126 | + let nivelNarrativo = $derived(`${getNivelArticulo(objetoData.nivel)} ${getNivelLabel(objetoData.nivel)} de Gasto`); | ||
| 127 | + | ||
| 87 | // Info del objeto (reactivo) | 128 | // Info del objeto (reactivo) |
| 88 | let objeto = $derived({ | 129 | let objeto = $derived({ |
| 89 | codigo: objetoData.objeto, | 130 | codigo: objetoData.objeto, |
| ... | @@ -153,31 +194,6 @@ | ... | @@ -153,31 +194,6 @@ |
| 153 | } | 194 | } |
| 154 | 195 | ||
| 155 | // ══════════════════════════════════════════════════════════════ | 196 | // ══════════════════════════════════════════════════════════════ |
| 156 | - // SPLIT VIEW - CLASIFICADOR LADO A LADO | ||
| 157 | - // ══════════════════════════════════════════════════════════════ | ||
| 158 | - let splitOpen = $state(false); | ||
| 159 | - let splitWidth = $state(45); // porcentaje del ancho para el clasificador | ||
| 160 | - let isResizing = $state(false); | ||
| 161 | - | ||
| 162 | - function startResize(e) { | ||
| 163 | - isResizing = true; | ||
| 164 | - document.body.style.cursor = 'col-resize'; | ||
| 165 | - document.body.style.userSelect = 'none'; | ||
| 166 | - } | ||
| 167 | - | ||
| 168 | - function handleResize(e) { | ||
| 169 | - if (!isResizing) return; | ||
| 170 | - const newWidth = (e.clientX / window.innerWidth) * 100; | ||
| 171 | - splitWidth = Math.max(25, Math.min(65, newWidth)); // entre 25% y 65% | ||
| 172 | - } | ||
| 173 | - | ||
| 174 | - function stopResize() { | ||
| 175 | - isResizing = false; | ||
| 176 | - document.body.style.cursor = ''; | ||
| 177 | - document.body.style.userSelect = ''; | ||
| 178 | - } | ||
| 179 | - | ||
| 180 | - // ══════════════════════════════════════════════════════════════ | ||
| 181 | // DATOS DE SUPABASE | 197 | // DATOS DE SUPABASE |
| 182 | // ══════════════════════════════════════════════════════════════ | 198 | // ══════════════════════════════════════════════════════════════ |
| 183 | let datosAnuales = $state([]); // Datos por año | 199 | let datosAnuales = $state([]); // Datos por año |
| ... | @@ -344,21 +360,21 @@ | ... | @@ -344,21 +360,21 @@ |
| 344 | ); | 360 | ); |
| 345 | 361 | ||
| 346 | let displayMonto = $derived(currentData ? currentData.monto : totalHistorico); | 362 | let displayMonto = $derived(currentData ? currentData.monto : totalHistorico); |
| 347 | - let displayMontoLabel = $derived(currentData ? `gasto en ${currentData.año}` : `gasto ${primerAño}-${ultimoAño}`); | 363 | + let displayMontoLabel = $derived(currentData ? `gastados en ${currentData.año}` : `gastados ${primerAño}-${ultimoAño}`); |
| 348 | let displayPerCapita = $derived(currentData ? currentData.perCapita : promedioPerCapita); | 364 | let displayPerCapita = $derived(currentData ? currentData.perCapita : promedioPerCapita); |
| 349 | - let displayPerCapitaLabel = $derived(currentData ? `por persona en ${currentData.año}` : 'por persona (promedio)'); | 365 | + let displayPerCapitaLabel = $derived(currentData ? `por cada boliviano en ${currentData.año}` : 'por cada boliviano'); |
| 350 | let displayPorcentaje = $derived( | 366 | let displayPorcentaje = $derived( |
| 351 | currentData && totalHistorico > 0 | 367 | currentData && totalHistorico > 0 |
| 352 | ? (totalPorcentaje * (currentData.monto / (totalHistorico / gastoPerCapita.length))).toFixed(1) | 368 | ? (totalPorcentaje * (currentData.monto / (totalHistorico / gastoPerCapita.length))).toFixed(1) |
| 353 | : totalPorcentaje | 369 | : totalPorcentaje |
| 354 | ); | 370 | ); |
| 355 | - let displayPorcentajeLabel = $derived(currentData ? `del gasto ${currentData.año}` : 'del gasto total'); | 371 | + let displayPorcentajeLabel = $derived(currentData ? `del gasto en ${currentData.año}` : 'del gasto total'); |
| 356 | let displayPeriodo = $derived(currentData ? currentData.año : `${primerAño}-${ultimoAño}`); | 372 | let displayPeriodo = $derived(currentData ? currentData.año : `${primerAño}-${ultimoAño}`); |
| 357 | let displayRanking = $derived(`${displayRankingValue} de ${rankingDenominador}`); | 373 | let displayRanking = $derived(`${displayRankingValue} de ${rankingDenominador}`); |
| 358 | let displayRankingLabel = $derived( | 374 | let displayRankingLabel = $derived( |
| 359 | selectedEntity | 375 | selectedEntity |
| 360 | ? (currentData ? `entre las que más gastan en ${currentData.año}` : 'entre las que más gastan') | 376 | ? (currentData ? `entre las que más gastan en ${currentData.año}` : 'entre las que más gastan') |
| 361 | - : (currentData ? `ranking en ${currentData.año}` : 'ranking entre grupos') | 377 | + : (currentData ? `${getNivelPlural(objetoData.nivel)} más ${getNivelGastado(objetoData.nivel)} en ${currentData.año}` : `${getNivelPlural(objetoData.nivel)} más ${getNivelGastado(objetoData.nivel)}`) |
| 362 | ); | 378 | ); |
| 363 | 379 | ||
| 364 | // Top entidades reactivo según hover | 380 | // Top entidades reactivo según hover |
| ... | @@ -642,6 +658,9 @@ | ... | @@ -642,6 +658,9 @@ |
| 642 | const mountStart = performance.now(); | 658 | const mountStart = performance.now(); |
| 643 | setTimeout(() => { mounted = true; }, 50); | 659 | setTimeout(() => { mounted = true; }, 50); |
| 644 | 660 | ||
| 661 | + // Inicializar índice de búsqueda | ||
| 662 | + initIndex(); | ||
| 663 | + | ||
| 645 | // Leer estado del tema | 664 | // Leer estado del tema |
| 646 | isDark = document.documentElement.classList.contains('dark'); | 665 | isDark = document.documentElement.classList.contains('dark'); |
| 647 | 666 | ||
| ... | @@ -660,7 +679,7 @@ | ... | @@ -660,7 +679,7 @@ |
| 660 | console.log(`[CLIENT] Promise.all: ${(performance.now() - loadStart).toFixed(0)}ms`); | 679 | console.log(`[CLIENT] Promise.all: ${(performance.now() - loadStart).toFixed(0)}ms`); |
| 661 | console.log(`[CLIENT] onMount total: ${(performance.now() - mountStart).toFixed(0)}ms`); | 680 | console.log(`[CLIENT] onMount total: ${(performance.now() - mountStart).toFixed(0)}ms`); |
| 662 | 681 | ||
| 663 | - // Cerrar dropdown al hacer click fuera | 682 | + // Cerrar dropdowns al hacer click fuera |
| 664 | const handleClickOutside = (e) => { | 683 | const handleClickOutside = (e) => { |
| 665 | if (entityDropdownOpen && !e.target.closest('.entity-dropdown-container')) { | 684 | if (entityDropdownOpen && !e.target.closest('.entity-dropdown-container')) { |
| 666 | entityDropdownOpen = false; | 685 | entityDropdownOpen = false; |
| ... | @@ -675,24 +694,9 @@ | ... | @@ -675,24 +694,9 @@ |
| 675 | }; | 694 | }; |
| 676 | document.addEventListener('click', handleClickOutside); | 695 | document.addEventListener('click', handleClickOutside); |
| 677 | 696 | ||
| 678 | - // Resize handlers para split view | ||
| 679 | - document.addEventListener('mousemove', handleResize); | ||
| 680 | - document.addEventListener('mouseup', stopResize); | ||
| 681 | - | ||
| 682 | - // Escuchar mensajes del iframe (clasificador en modo embed) | ||
| 683 | - const handleIframeMessage = (e) => { | ||
| 684 | - if (e.data?.type === 'navigate-objeto' && e.data?.codigo) { | ||
| 685 | - goto(`/objeto/${e.data.codigo}`); | ||
| 686 | - } | ||
| 687 | - }; | ||
| 688 | - window.addEventListener('message', handleIframeMessage); | ||
| 689 | - | ||
| 690 | return () => { | 697 | return () => { |
| 691 | observer.disconnect(); | 698 | observer.disconnect(); |
| 692 | document.removeEventListener('click', handleClickOutside); | 699 | document.removeEventListener('click', handleClickOutside); |
| 693 | - document.removeEventListener('mousemove', handleResize); | ||
| 694 | - document.removeEventListener('mouseup', stopResize); | ||
| 695 | - window.removeEventListener('message', handleIframeMessage); | ||
| 696 | }; | 700 | }; |
| 697 | }); | 701 | }); |
| 698 | 702 | ||
| ... | @@ -714,20 +718,29 @@ | ... | @@ -714,20 +718,29 @@ |
| 714 | } | 718 | } |
| 715 | }); | 719 | }); |
| 716 | 720 | ||
| 721 | + // Recargar datos cuando cambia la entidad seleccionada | ||
| 722 | + let previousEntity = selectedEntity; | ||
| 723 | + $effect(() => { | ||
| 724 | + if (mounted && selectedEntity !== previousEntity) { | ||
| 725 | + previousEntity = selectedEntity; | ||
| 726 | + loadData(); | ||
| 727 | + } | ||
| 728 | + }); | ||
| 729 | + | ||
| 717 | // ══════════════════════════════════════════════════════════════ | 730 | // ══════════════════════════════════════════════════════════════ |
| 718 | // HELPERS | 731 | // HELPERS |
| 719 | // ══════════════════════════════════════════════════════════════ | 732 | // ══════════════════════════════════════════════════════════════ |
| 720 | function formatMonto(m) { | 733 | function formatMonto(m) { |
| 721 | - if (m >= 1e9) return (m / 1e9).toFixed(1) + 'MM'; | 734 | + if (m >= 1e9) return (m / 1e9).toLocaleString('es-BO', { minimumFractionDigits: 1, maximumFractionDigits: 1 }) + ' MM'; |
| 722 | - if (m >= 1e6) return (m / 1e6).toFixed(1) + 'M'; | 735 | + if (m >= 1e6) return (m / 1e6).toLocaleString('es-BO', { minimumFractionDigits: 1, maximumFractionDigits: 1 }) + ' M'; |
| 723 | - if (m >= 1e3) return Math.round(m / 1e3) + 'K'; | 736 | + if (m >= 1e3) return Math.round(m / 1e3).toLocaleString('es-BO') + ' K'; |
| 724 | - return m?.toLocaleString() || '0'; | 737 | + return m?.toLocaleString('es-BO') || '0'; |
| 725 | } | 738 | } |
| 726 | 739 | ||
| 727 | function formatMontoLargo(m) { | 740 | function formatMontoLargo(m) { |
| 728 | - if (m >= 1e9) return Math.round(m / 1e9).toLocaleString('es-BO') + ' mil Mill.'; | 741 | + if (m >= 1e9) return Math.round(m / 1e9).toLocaleString('es-BO') + ' mil mill. de Bs.'; |
| 729 | - if (m >= 1e6) return (m / 1e6).toLocaleString('es-BO', { minimumFractionDigits: 1, maximumFractionDigits: 1 }) + ' Mill.'; | 742 | + if (m >= 1e6) return (m / 1e6).toLocaleString('es-BO', { minimumFractionDigits: 1, maximumFractionDigits: 1 }) + ' mill. de Bs.'; |
| 730 | - return m?.toLocaleString('es-BO', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) || '0'; | 743 | + return m?.toLocaleString('es-BO', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + ' Bs.' || '0'; |
| 731 | } | 744 | } |
| 732 | 745 | ||
| 733 | function formatPerCapita(n) { | 746 | function formatPerCapita(n) { |
| ... | @@ -744,100 +757,79 @@ | ... | @@ -744,100 +757,79 @@ |
| 744 | <link href="https://fonts.googleapis.com/css2?family=DM+Serif+Display:ital@0;1&family=DM+Mono:wght@400;500&family=Instrument+Sans:wght@400;500;600&display=swap" rel="stylesheet" /> | 757 | <link href="https://fonts.googleapis.com/css2?family=DM+Serif+Display:ital@0;1&family=DM+Mono:wght@400;500&family=Instrument+Sans:wght@400;500;600&display=swap" rel="stylesheet" /> |
| 745 | </svelte:head> | 758 | </svelte:head> |
| 746 | 759 | ||
| 747 | -<!-- Contenedor split view --> | 760 | +<div class="page" class:mounted> |
| 748 | -<div class="split-container" class:split-active={splitOpen}> | 761 | + <!-- Link a clasificadores --> |
| 749 | - <!-- Panel izquierdo: Clasificador --> | 762 | + <nav class="page-nav"> |
| 750 | - {#if splitOpen} | 763 | + <a href="/clasificadores/objeto-gasto" class="back-link"> |
| 751 | - <div class="split-left" style="width: {splitWidth}%"> | 764 | + <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| 752 | - <div class="split-header"> | 765 | + <path d="M19 12H5M12 19l-7-7 7-7"/> |
| 753 | - <span>Clasificador por Objeto</span> | ||
| 754 | - <button class="split-close" onclick={() => splitOpen = false}> | ||
| 755 | - <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | ||
| 756 | - <path d="M18 6L6 18M6 6l12 12"/> | ||
| 757 | </svg> | 766 | </svg> |
| 758 | - </button> | 767 | + Clasificador |
| 759 | - </div> | 768 | + </a> |
| 760 | - <iframe | ||
| 761 | - src="/clasificadores/objeto-gasto?embed=true&objeto={objetoCodigo}" | ||
| 762 | - title="Clasificador" | ||
| 763 | - class="split-iframe" | ||
| 764 | - ></iframe> | ||
| 765 | - </div> | ||
| 766 | - | ||
| 767 | - <!-- Resizer --> | ||
| 768 | - <div | ||
| 769 | - class="split-resizer" | ||
| 770 | - onmousedown={startResize} | ||
| 771 | - role="separator" | ||
| 772 | - ></div> | ||
| 773 | - {/if} | ||
| 774 | - | ||
| 775 | - <!-- Panel derecho: Contenido principal --> | ||
| 776 | - <div class="split-right" class:mounted> | ||
| 777 | - <!-- Header compacto --> | ||
| 778 | - <header> | ||
| 779 | - <nav class="breadcrumb"> | ||
| 780 | - <a href="/clasificadores/objeto-gasto">← Objeto del Gasto</a> | ||
| 781 | - {#each padres as padre} | ||
| 782 | - <span class="breadcrumb-sep">/</span> | ||
| 783 | - <a href="/objeto/{padre.objeto}" class="breadcrumb-link">{padre.desc_objeto}</a> | ||
| 784 | - {/each} | ||
| 785 | </nav> | 769 | </nav> |
| 786 | 770 | ||
| 787 | - <div class="title-row"> | 771 | + <!-- DASHBOARD LAYOUT --> |
| 788 | - <div class="title-main"> | 772 | + {#if vista === 'agregado'} |
| 789 | - <span class="codigo">{objeto.codigo}</span> | 773 | + <div class="dashboard-layout"> |
| 790 | - <h1> | 774 | + <!-- CONTEXTO IZQUIERDO --> |
| 791 | - {objeto.nombre} | 775 | + <aside class="dashboard-context" class:mobile-open={showMobileSidebar}> |
| 792 | - <button class="help-icon" onclick={() => showInfo = !showInfo} class:active={showInfo}> | 776 | + <!-- Botón móvil para abrir/cerrar --> |
| 793 | - <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | 777 | + <button class="mobile-sidebar-toggle" onclick={() => showMobileSidebar = !showMobileSidebar}> |
| 794 | - <circle cx="12" cy="12" r="10"/> | 778 | + <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| 795 | - <path d="M9.5 9a3 3 0 1 1 3.5 2.95c-.5.17-1 .62-1 1.3V15"/> | 779 | + <line x1="4" y1="21" x2="4" y2="14"/> |
| 796 | - <circle cx="12" cy="18" r="0.5" fill="currentColor"/> | 780 | + <line x1="4" y1="10" x2="4" y2="3"/> |
| 781 | + <line x1="12" y1="21" x2="12" y2="12"/> | ||
| 782 | + <line x1="12" y1="8" x2="12" y2="3"/> | ||
| 783 | + <line x1="20" y1="21" x2="20" y2="16"/> | ||
| 784 | + <line x1="20" y1="12" x2="20" y2="3"/> | ||
| 785 | + <circle cx="4" cy="12" r="2"/> | ||
| 786 | + <circle cx="12" cy="10" r="2"/> | ||
| 787 | + <circle cx="20" cy="14" r="2"/> | ||
| 797 | </svg> | 788 | </svg> |
| 789 | + <span class="toggle-label">{showMobileSidebar ? 'Ocultar' : 'Herramientas'}</span> | ||
| 798 | </button> | 790 | </button> |
| 799 | - </h1> | ||
| 800 | - </div> | ||
| 801 | - </div> | ||
| 802 | - | ||
| 803 | - <div class="meta"> | ||
| 804 | - <span class="badge">{objeto.nivel}</span> | ||
| 805 | - <span class="years">{formatYearsAsRanges(objeto.años)}</span> | ||
| 806 | - </div> | ||
| 807 | - </header> | ||
| 808 | 791 | ||
| 809 | - <!-- Panel de info (colapsable) --> | 792 | + <div class="context-card"> |
| 810 | - {#if showInfo} | 793 | + <!-- Título principal --> |
| 811 | - <div class="info-panel"> | 794 | + <h2 class="context-main-title">Herramientas</h2> |
| 812 | - <div class="info-content"> | 795 | + |
| 813 | - <h4>Definición vigente</h4> | 796 | + <!-- Buscador --> |
| 814 | - <p>{objeto.descripcion}</p> | 797 | + <div class="context-section"> |
| 798 | + <h3 class="context-subtitle">Buscar</h3> | ||
| 799 | + <p class="context-hint">Buscar otro grupo, subgrupo, partida o subpartida</p> | ||
| 800 | + <ObjetoSearch /> | ||
| 801 | + </div> | ||
| 802 | + | ||
| 803 | + <!-- Separador --> | ||
| 804 | + <div class="context-separator"></div> | ||
| 805 | + | ||
| 806 | + <!-- Contexto: Árbol de jerarquía --> | ||
| 807 | + {#if padres.length > 0 || hijos.length > 0} | ||
| 808 | + <div class="context-section"> | ||
| 809 | + <h3 class="context-subtitle">Navegación</h3> | ||
| 810 | + <p class="context-hint">Partidas relacionadas. <span class="desktop-hint">Haz click</span><span class="mobile-hint">Toca</span> para explorar.</p> | ||
| 811 | + <div class="tree"> | ||
| 812 | + {#each padres as padre, i} | ||
| 813 | + <a href="/objeto/{padre.objeto}" class="tree-node has-tooltip" style="--indent: {i}" data-tooltip="{padre.desc_objeto}"> | ||
| 814 | + <span class="tree-dot"></span> | ||
| 815 | + <span class="tree-code">{padre.objeto}</span> | ||
| 816 | + <span class="tree-name">{padre.desc_objeto}</span> | ||
| 817 | + </a> | ||
| 818 | + {/each} | ||
| 815 | 819 | ||
| 816 | - {#if objeto.variaciones.length > 1} | 820 | + <div class="tree-node tree-node-current has-tooltip" style="--indent: {padres.length}" data-tooltip="{objetoData.desc_objeto}"> |
| 817 | - <h4>Variaciones históricas</h4> | 821 | + <span class="tree-dot"></span> |
| 818 | - {#each objeto.variaciones as v} | 822 | + <span class="tree-code">{objetoData.objeto}</span> |
| 819 | - <div class="variacion"> | 823 | + <span class="tree-name">{objetoData.desc_objeto}</span> |
| 820 | - <span class="variacion-rango">{formatYearsAsRanges(v.rango)}</span> | ||
| 821 | - <p>{v.texto}</p> | ||
| 822 | </div> | 824 | </div> |
| 823 | - {/each} | ||
| 824 | - {/if} | ||
| 825 | 825 | ||
| 826 | {#if hijos.length > 0} | 826 | {#if hijos.length > 0} |
| 827 | - <h4> | 827 | + <div class="tree-children" style="--indent: {padres.length + 1}"> |
| 828 | - {#if objetoNivel === 'grupo'} | ||
| 829 | - Subgrupos ({hijos.length}) | ||
| 830 | - {:else if objetoNivel === 'subgrupo'} | ||
| 831 | - Partidas ({hijos.length}) | ||
| 832 | - {:else if objetoNivel === 'partida'} | ||
| 833 | - Subpartidas ({hijos.length}) | ||
| 834 | - {/if} | ||
| 835 | - </h4> | ||
| 836 | - <div class="hijos-list"> | ||
| 837 | {#each hijos as hijo} | 828 | {#each hijos as hijo} |
| 838 | - <a href="/objeto/{hijo.objeto}" class="hijo-link"> | 829 | + <a href="/objeto/{hijo.objeto}" class="tree-node tree-node-child has-tooltip" data-tooltip="{hijo.desc_objeto}"> |
| 839 | - <span class="hijo-codigo">{hijo.objeto}</span> | 830 | + <span class="tree-dot"></span> |
| 840 | - <span class="hijo-nombre">{hijo.desc_objeto}</span> | 831 | + <span class="tree-code">{hijo.objeto}</span> |
| 832 | + <span class="tree-name">{hijo.desc_objeto}</span> | ||
| 841 | </a> | 833 | </a> |
| 842 | {/each} | 834 | {/each} |
| 843 | </div> | 835 | </div> |
| ... | @@ -846,184 +838,165 @@ | ... | @@ -846,184 +838,165 @@ |
| 846 | </div> | 838 | </div> |
| 847 | {/if} | 839 | {/if} |
| 848 | 840 | ||
| 849 | - <!-- Dashboard principal --> | 841 | + <!-- Separador --> |
| 850 | - <main> | 842 | + <div class="context-separator"></div> |
| 851 | 843 | ||
| 852 | - <!-- Toggle de vista --> | 844 | + <!-- Contexto: Vigencia temporal --> |
| 853 | - <div class="vista-toggle"> | 845 | + <div class="context-section"> |
| 854 | - <button | 846 | + <h3 class="context-subtitle">Comparabilidad</h3> |
| 855 | - class="toggle-btn" | 847 | + <div class="vigencia-compact"> |
| 856 | - class:active={vista === 'agregado'} | 848 | + {#if gastoPerCapita.length === 21 && primerAño === 2005 && ultimoAño === 2025} |
| 857 | - onclick={() => vista = 'agregado'} | 849 | + <div class="vigencia-status-icon vigencia-full"> |
| 858 | - > | 850 | + <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| 859 | - Agregado | 851 | + <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/> |
| 860 | - </button> | 852 | + <polyline points="22 4 12 14.01 9 11.01"/> |
| 861 | - <button | 853 | + </svg> |
| 862 | - class="toggle-btn" | 854 | + </div> |
| 863 | - class:active={vista === 'comparar'} | 855 | + <div class="vigencia-text"> |
| 864 | - onclick={() => vista = 'comparar'} | 856 | + <span class="vigencia-main">Comparable en todo el período</span> |
| 865 | - > | 857 | + <span class="vigencia-sub">{primerAño} – {ultimoAño}</span> |
| 866 | - Comparar | ||
| 867 | - </button> | ||
| 868 | </div> | 858 | </div> |
| 869 | - | ||
| 870 | - <!-- Selector de entidad (solo en vista agregado) --> | ||
| 871 | - {#if vista === 'agregado'} | ||
| 872 | - <div class="entity-selector entity-dropdown-container"> | ||
| 873 | - <span class="selector-label">Viendo</span> | ||
| 874 | - <div class="relative"> | ||
| 875 | - <button | ||
| 876 | - class="selector-btn" | ||
| 877 | - onclick={() => entityDropdownOpen = !entityDropdownOpen} | ||
| 878 | - > | ||
| 879 | - <span class="truncate"> | ||
| 880 | - {#if selectedEntity} | ||
| 881 | - {selectedEntity.entidad_desc?.slice(0, 30)}{selectedEntity.entidad_desc?.length > 30 ? '...' : ''} | ||
| 882 | {:else} | 859 | {:else} |
| 883 | - Todo el sector público | 860 | + <div class="vigencia-status-icon vigencia-partial"> |
| 884 | - {/if} | 861 | + <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| 885 | - </span> | 862 | + <circle cx="12" cy="12" r="10"/> |
| 886 | - <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | 863 | + <line x1="12" y1="8" x2="12" y2="12"/> |
| 887 | - <path d="M6 9l6 6 6-6"/> | 864 | + <line x1="12" y1="16" x2="12.01" y2="16"/> |
| 888 | </svg> | 865 | </svg> |
| 889 | - </button> | ||
| 890 | - | ||
| 891 | - {#if entityDropdownOpen} | ||
| 892 | - <div class="dropdown-panel"> | ||
| 893 | - <div class="dropdown-search"> | ||
| 894 | - <input | ||
| 895 | - type="text" | ||
| 896 | - bind:value={entitySearchQuery} | ||
| 897 | - placeholder="Buscar entidad..." | ||
| 898 | - class="search-input" | ||
| 899 | - /> | ||
| 900 | </div> | 866 | </div> |
| 901 | - <button | 867 | + <div class="vigencia-text"> |
| 902 | - class="dropdown-item" | 868 | + <span class="vigencia-main">Comparable en {gastoPerCapita.length} años</span> |
| 903 | - class:active={!selectedEntity} | 869 | + <span class="vigencia-sub">{primerAño} – {ultimoAño}</span> |
| 904 | - onclick={() => clearEntity()} | ||
| 905 | - > | ||
| 906 | - <span class="font-medium">Todo el sector público</span> | ||
| 907 | - <span class="text-xs opacity-60 ml-2">({nEntidades} entidades)</span> | ||
| 908 | - </button> | ||
| 909 | - <div class="dropdown-list" onscroll={(e) => handleEntityScroll(e, 'main')}> | ||
| 910 | - {#each filteredEntities as entity} | ||
| 911 | - <button | ||
| 912 | - class="dropdown-item" | ||
| 913 | - class:active={selectedEntity?.entidad === entity.entidad} | ||
| 914 | - onclick={() => selectEntity(entity)} | ||
| 915 | - > | ||
| 916 | - <span class="entity-code">{entity.entidad}</span> | ||
| 917 | - <span class="truncate">{entity.entidad_desc}</span> | ||
| 918 | - </button> | ||
| 919 | - {/each} | ||
| 920 | - {#if entityLimit < entidades.length} | ||
| 921 | - <div class="text-center py-2 text-xs opacity-60"> | ||
| 922 | - Scroll para ver más ({entidades.length - entityLimit} restantes) | ||
| 923 | </div> | 870 | </div> |
| 924 | {/if} | 871 | {/if} |
| 925 | </div> | 872 | </div> |
| 926 | </div> | 873 | </div> |
| 874 | + | ||
| 875 | + <!-- Separador --> | ||
| 876 | + <div class="context-separator"></div> | ||
| 877 | + | ||
| 878 | + <!-- Contexto: Definición oficial --> | ||
| 879 | + <div class="context-section"> | ||
| 880 | + <h3 class="context-subtitle">Definición</h3> | ||
| 881 | + <p class="context-desc">Descripción oficial {getNivelArticuloContracto(objetoData.nivel)} {objetoData.nivel} según el clasificador presupuestario.</p> | ||
| 882 | + <div class="definicion-compact"> | ||
| 883 | + {#if objeto.variaciones.length === 1} | ||
| 884 | + <span class="def-status">Sin cambios en el período</span> | ||
| 885 | + {:else} | ||
| 886 | + <span class="def-status def-changed">{objeto.variaciones.length} versiones en el período</span> | ||
| 927 | {/if} | 887 | {/if} |
| 888 | + <button class="def-btn" onclick={() => showDefinicionesModal = true}> | ||
| 889 | + Ver | ||
| 890 | + </button> | ||
| 928 | </div> | 891 | </div> |
| 929 | - <span class="interaction-hint desktop-hint">Pasa el cursor sobre las barras para ver valores por año</span> | ||
| 930 | - <span class="interaction-hint mobile-hint">Toca las barras para ver valores por año</span> | ||
| 931 | </div> | 892 | </div> |
| 932 | - {/if} | 893 | + </div> |
| 894 | + </aside> | ||
| 933 | 895 | ||
| 934 | - {#if vista === 'agregado'} | 896 | + <!-- CONTENIDO PRINCIPAL --> |
| 935 | - <!-- VISTA AGREGADO --> | 897 | + <main class="dashboard-main" class:expanded={selectedEntity}> |
| 898 | + <!-- Gráfico principal con KPIs integrados --> | ||
| 899 | + <div class="chart-section chart-protagonista" class:expanded={selectedEntity}> | ||
| 900 | + <!-- Header: pills + botón --> | ||
| 901 | + <div class="chart-top-row"> | ||
| 902 | + <div class="main-title-pills"> | ||
| 903 | + <span class="pill pill-nivel">{objeto.nivel}</span> | ||
| 904 | + <span class="pill pill-codigo">{objeto.codigo}</span> | ||
| 905 | + </div> | ||
| 906 | + <VistaToggle bind:vista /> | ||
| 907 | + </div> | ||
| 908 | + | ||
| 909 | + <!-- Título principal --> | ||
| 910 | + <h2 class="main-title-name">{objeto.nombre}</h2> | ||
| 911 | + | ||
| 912 | + <!-- Selector de entidad --> | ||
| 913 | + <div class="chart-controls"> | ||
| 914 | + <EntitySelector {entidades} bind:selectedEntity {nEntidades} /> | ||
| 915 | + </div> | ||
| 936 | 916 | ||
| 937 | - <!-- KPIs arriba --> | 917 | + <!-- Título del gráfico --> |
| 918 | + <div class="chart-header"> | ||
| 919 | + <span class="chart-title">Bs/habitante <span class="chart-period">· {displayPeriodo}</span></span> | ||
| 920 | + </div> | ||
| 921 | + <BarChart data={gastoPerCapita} bind:hoveredYear height={280} fill={!!selectedEntity} /> | ||
| 922 | + | ||
| 923 | + <!-- KPIs abajo --> | ||
| 938 | <div class="kpis"> | 924 | <div class="kpis"> |
| 939 | <div class="kpi"> | 925 | <div class="kpi"> |
| 940 | - <span class="kpi-value">Bs {formatMontoLargo(Math.round($twMonto))}</span> | 926 | + <span class="kpi-value">{formatMontoLargo(Math.round($twMonto))}</span> |
| 941 | <span class="kpi-label">{displayMontoLabel}</span> | 927 | <span class="kpi-label">{displayMontoLabel}</span> |
| 942 | </div> | 928 | </div> |
| 943 | - | ||
| 944 | <div class="kpi"> | 929 | <div class="kpi"> |
| 945 | - <span class="kpi-value">Bs {formatPerCapita($twPerCapita)}</span> | 930 | + <span class="kpi-value">{formatPerCapita($twPerCapita)} Bs.</span> |
| 946 | <span class="kpi-label">{displayPerCapitaLabel}</span> | 931 | <span class="kpi-label">{displayPerCapitaLabel}</span> |
| 947 | </div> | 932 | </div> |
| 948 | - | ||
| 949 | <div class="kpi"> | 933 | <div class="kpi"> |
| 950 | <span class="kpi-value">{$twPorcentaje.toLocaleString('es-BO', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}%</span> | 934 | <span class="kpi-value">{$twPorcentaje.toLocaleString('es-BO', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}%</span> |
| 951 | <span class="kpi-label">{displayPorcentajeLabel}</span> | 935 | <span class="kpi-label">{displayPorcentajeLabel}</span> |
| 952 | </div> | 936 | </div> |
| 953 | - | ||
| 954 | <div class="kpi"> | 937 | <div class="kpi"> |
| 955 | <span class="kpi-value">{displayRanking}</span> | 938 | <span class="kpi-value">{displayRanking}</span> |
| 956 | <span class="kpi-label">{displayRankingLabel}</span> | 939 | <span class="kpi-label">{displayRankingLabel}</span> |
| 957 | </div> | 940 | </div> |
| 958 | </div> | 941 | </div> |
| 959 | - | ||
| 960 | - <!-- Gráfico de barras (per cápita) - protagonista al medio --> | ||
| 961 | - <div class="chart-section chart-protagonista"> | ||
| 962 | - <div class="chart-header"> | ||
| 963 | - <span class="chart-title">Gasto por persona</span> | ||
| 964 | - <span class="chart-period">{displayPeriodo}</span> | ||
| 965 | - </div> | ||
| 966 | - | ||
| 967 | - <div class="chart-wrapper"> | ||
| 968 | - <!-- Y-axis labels --> | ||
| 969 | - <div class="y-axis"> | ||
| 970 | - <span class="y-label">Bs {formatMonto(maxPerCapita)}</span> | ||
| 971 | - <span class="y-label">Bs {formatMonto(Math.round(maxPerCapita / 2))}</span> | ||
| 972 | - <span class="y-label">Bs 0</span> | ||
| 973 | </div> | 942 | </div> |
| 974 | 943 | ||
| 975 | - <div | 944 | + <!-- Ranking simplificado --> |
| 976 | - class="chart" | 945 | + {#if !selectedEntity && displayTopEntidades.length > 0} |
| 977 | - onmouseleave={() => hoveredYear = null} | 946 | + <div class="ranking-section"> |
| 978 | - role="group" | 947 | + <h3 class="ranking-title">Concentración del gasto</h3> |
| 979 | - > | 948 | + <p class="ranking-desc">Porcentaje del total gastado en {objeto.nombre.toLowerCase()} por todo el Estado {hoveredYear ? `en ${hoveredYear}` : `(${primerAño}-${ultimoAño})`}</p> |
| 980 | - {#each gastoPerCapita as g} | 949 | + <div class="ranking-cards"> |
| 981 | - <div | 950 | + {#each displayTopEntidades as ent, i} |
| 982 | - class="bar-container" | 951 | + {@const pct = i === 0 ? $twTop1 : i === 1 ? $twTop2 : $twTop3} |
| 983 | - onmouseenter={() => hoveredYear = g.año} | 952 | + <div class="ranking-card"> |
| 984 | - role="button" | 953 | + <span class="ranking-position">#{i + 1}</span> |
| 985 | - tabindex="0" | 954 | + <span class="ranking-name">{ent.nombre}</span> |
| 986 | - > | 955 | + <span class="ranking-pct">{pct.toFixed(1)}%</span> |
| 987 | - <div | ||
| 988 | - class="bar" | ||
| 989 | - class:hovered={hoveredYear === g.año} | ||
| 990 | - style="height: {(g.perCapita / maxPerCapita) * 100}%" | ||
| 991 | - ></div> | ||
| 992 | - <span class="bar-label" class:visible={hoveredYear === g.año || g.año % 5 === 0}> | ||
| 993 | - {g.año.toString().slice(-2)} | ||
| 994 | - </span> | ||
| 995 | </div> | 956 | </div> |
| 996 | {/each} | 957 | {/each} |
| 997 | </div> | 958 | </div> |
| 998 | </div> | 959 | </div> |
| 960 | + {/if} | ||
| 961 | + </main> | ||
| 999 | </div> | 962 | </div> |
| 1000 | 963 | ||
| 1001 | - <!-- Top entidades (solo visible cuando es "Todo el Estado") --> | 964 | + <!-- Modal de definiciones (fuera del layout) --> |
| 1002 | - {#if !selectedEntity} | 965 | + {#if showDefinicionesModal} |
| 1003 | - <div class="top-section"> | 966 | + <div class="modal-overlay" onclick={() => showDefinicionesModal = false}> |
| 1004 | - <div class="top-header"> | 967 | + <div class="modal-content" onclick={(e) => e.stopPropagation()}> |
| 1005 | - <h3>Mayores ejecutores</h3> | 968 | + <div class="modal-header"> |
| 1006 | - <span class="top-context">{totalEntidades} entidades · {displayTopLabel}</span> | 969 | + <h3>Definiciones de "{objetoData.desc_objeto}"</h3> |
| 1007 | - </div> | 970 | + <button class="modal-close" onclick={() => showDefinicionesModal = false}> |
| 1008 | - | 971 | + <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| 1009 | - <div class="top-list"> | 972 | + <line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/> |
| 1010 | - {#each displayTopEntidades as ent, i (ent.nombre)} | 973 | + </svg> |
| 1011 | - <div class="top-item"> | 974 | + </button> |
| 1012 | - <span class="top-rank">#{i + 1}</span> | ||
| 1013 | - <div class="top-info"> | ||
| 1014 | - <span class="top-name">{ent.nombre}</span> | ||
| 1015 | - <div class="top-bar-bg"> | ||
| 1016 | - <div class="top-bar" style="width: {((i === 0 ? $twTop1 : i === 1 ? $twTop2 : $twTop3) / $twTop1) * 100}%"></div> | ||
| 1017 | </div> | 975 | </div> |
| 976 | + <div class="modal-body"> | ||
| 977 | + {#each objeto.variaciones as v, i} | ||
| 978 | + <div class="def-item" class:def-current={i === 0}> | ||
| 979 | + <div class="def-header"> | ||
| 980 | + <span class="def-period">{v.rango}</span> | ||
| 981 | + {#if i === 0} | ||
| 982 | + <span class="def-badge">Vigente</span> | ||
| 983 | + {/if} | ||
| 1018 | </div> | 984 | </div> |
| 1019 | - <span class="top-pct">{(i === 0 ? $twTop1 : i === 1 ? $twTop2 : $twTop3).toFixed(1)}%</span> | 985 | + <p class="def-text">{v.texto}</p> |
| 1020 | </div> | 986 | </div> |
| 1021 | {/each} | 987 | {/each} |
| 1022 | </div> | 988 | </div> |
| 1023 | </div> | 989 | </div> |
| 990 | + </div> | ||
| 1024 | {/if} | 991 | {/if} |
| 1025 | 992 | ||
| 1026 | {:else} | 993 | {:else} |
| 994 | + <!-- VISTA COMPARAR (mantiene layout original) --> | ||
| 995 | + <div class="toolbar"> | ||
| 996 | + <ObjetoSearch /> | ||
| 997 | + <VistaToggle bind:vista /> | ||
| 998 | + </div> | ||
| 999 | + <main> | ||
| 1027 | <!-- VISTA COMPARAR --> | 1000 | <!-- VISTA COMPARAR --> |
| 1028 | 1001 | ||
| 1029 | <!-- Selectores de entidades con búsqueda --> | 1002 | <!-- Selectores de entidades con búsqueda --> |
| ... | @@ -1139,7 +1112,7 @@ | ... | @@ -1139,7 +1112,7 @@ |
| 1139 | <!-- Gráfico comparativo --> | 1112 | <!-- Gráfico comparativo --> |
| 1140 | <div class="chart-section"> | 1113 | <div class="chart-section"> |
| 1141 | <div class="chart-header"> | 1114 | <div class="chart-header"> |
| 1142 | - <span class="chart-title">Gasto por persona</span> | 1115 | + <span class="chart-title">Bs/habitante</span> |
| 1143 | <span class="chart-period">{displayPeriodoComparar}</span> | 1116 | <span class="chart-period">{displayPeriodoComparar}</span> |
| 1144 | </div> | 1117 | </div> |
| 1145 | 1118 | ||
| ... | @@ -1221,354 +1194,845 @@ | ... | @@ -1221,354 +1194,845 @@ |
| 1221 | </div> | 1194 | </div> |
| 1222 | {/if} | 1195 | {/if} |
| 1223 | 1196 | ||
| 1224 | - {/if} | ||
| 1225 | - | ||
| 1226 | </main> | 1197 | </main> |
| 1227 | - </div><!-- end split-right --> | 1198 | + {/if} |
| 1228 | - | 1199 | +</div> |
| 1229 | - <!-- Botón para abrir split view --> | ||
| 1230 | - <button | ||
| 1231 | - class="split-toggle" | ||
| 1232 | - onclick={() => splitOpen = !splitOpen} | ||
| 1233 | - title={splitOpen ? "Cerrar clasificador" : "Abrir clasificador"} | ||
| 1234 | - class:active={splitOpen} | ||
| 1235 | - > | ||
| 1236 | - <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | ||
| 1237 | - <rect x="3" y="3" width="7" height="18" rx="1"/> | ||
| 1238 | - <rect x="14" y="3" width="7" height="18" rx="1"/> | ||
| 1239 | - </svg> | ||
| 1240 | - <span class="split-toggle-label">{splitOpen ? 'Cerrar' : 'Clasificador'}</span> | ||
| 1241 | - </button> | ||
| 1242 | -</div><!-- end split-container --> | ||
| 1243 | 1200 | ||
| 1244 | <style> | 1201 | <style> |
| 1245 | - /* Split container */ | 1202 | + /* ═══════════════════════════════════════════════════════════════ |
| 1246 | - .split-container { | 1203 | + PAGE & HEADER - Modern Compact Design |
| 1247 | - display: flex; | 1204 | + ═══════════════════════════════════════════════════════════════ */ |
| 1205 | + .page { | ||
| 1248 | min-height: 100vh; | 1206 | min-height: 100vh; |
| 1207 | + width: 100%; | ||
| 1208 | + max-width: 100vw; | ||
| 1209 | + overflow-x: hidden; | ||
| 1249 | background: var(--theme-body); | 1210 | background: var(--theme-body); |
| 1211 | + color: var(--theme-titulo); | ||
| 1212 | + font-family: var(--font-sans), -apple-system, sans-serif; | ||
| 1213 | + opacity: 0; | ||
| 1214 | + transition: opacity 0.4s ease; | ||
| 1250 | } | 1215 | } |
| 1216 | + .page.mounted { opacity: 1; } | ||
| 1251 | 1217 | ||
| 1252 | - .split-left { | 1218 | + /* Fondo claro específico para objeto */ |
| 1253 | - flex-shrink: 0; | 1219 | + :global(html:not(.dark)) .page { |
| 1220 | + background: #f5f5f7; | ||
| 1221 | + } | ||
| 1222 | + | ||
| 1223 | + /* ═══════════════════════════════════════════════════════════════ | ||
| 1224 | + DASHBOARD LAYOUT - Sidebar + Main Content | ||
| 1225 | + ═══════════════════════════════════════════════════════════════ */ | ||
| 1226 | + .dashboard-layout { | ||
| 1227 | + display: grid; | ||
| 1228 | + grid-template-columns: 320px minmax(auto, 900px); | ||
| 1229 | + gap: 16px; | ||
| 1230 | + padding: 1rem 2rem 2rem; | ||
| 1231 | + max-width: 1600px; | ||
| 1232 | + margin: 0 auto; | ||
| 1233 | + justify-content: center; | ||
| 1234 | + } | ||
| 1235 | + | ||
| 1236 | + /* Contexto */ | ||
| 1237 | + .dashboard-context { | ||
| 1254 | display: flex; | 1238 | display: flex; |
| 1255 | flex-direction: column; | 1239 | flex-direction: column; |
| 1256 | - height: 100vh; | 1240 | + min-height: 0; |
| 1257 | - position: sticky; | 1241 | + } |
| 1258 | - top: 0; | 1242 | + |
| 1259 | - border-right: 1px solid var(--theme-borde); | 1243 | + /* Mobile sidebar toggle - oculto en desktop */ |
| 1244 | + .mobile-sidebar-toggle { | ||
| 1245 | + display: none; | ||
| 1260 | } | 1246 | } |
| 1261 | 1247 | ||
| 1262 | - .split-right { | 1248 | + .context-card { |
| 1249 | + background: var(--theme-surface); | ||
| 1250 | + border-radius: 16px; | ||
| 1251 | + padding: 1.25rem 1.5rem; | ||
| 1252 | + display: flex; | ||
| 1253 | + flex-direction: column; | ||
| 1254 | + gap: 1rem; | ||
| 1263 | flex: 1; | 1255 | flex: 1; |
| 1264 | - min-width: 0; | 1256 | + min-height: 0; |
| 1265 | - background: var(--theme-body); | 1257 | + } |
| 1258 | + | ||
| 1259 | + .context-main-title { | ||
| 1260 | + font-size: 1.375rem; | ||
| 1261 | + font-weight: 600; | ||
| 1266 | color: var(--theme-titulo); | 1262 | color: var(--theme-titulo); |
| 1267 | - font-family: var(--font-sans), -apple-system, sans-serif; | 1263 | + margin: 0; |
| 1268 | - opacity: 0; | 1264 | + line-height: 1.2; |
| 1269 | - transition: opacity 0.4s ease; | ||
| 1270 | } | 1265 | } |
| 1271 | - .split-right.mounted { opacity: 1; } | ||
| 1272 | 1266 | ||
| 1273 | - /* Header */ | 1267 | + .context-subtitle { |
| 1274 | - header { | 1268 | + font-family: 'Qanelas', var(--font-sans); |
| 1275 | - padding: 1rem 2rem; | 1269 | + font-size: 0.875rem; |
| 1276 | - border-bottom: 1px solid var(--theme-borde); | 1270 | + font-weight: 600; |
| 1271 | + color: var(--theme-titulo); | ||
| 1272 | + margin: 0; | ||
| 1277 | } | 1273 | } |
| 1278 | 1274 | ||
| 1279 | - .breadcrumb { | 1275 | + .context-desc { |
| 1280 | - display: flex; | 1276 | + font-size: 0.75rem; |
| 1281 | - align-items: center; | 1277 | + color: #666666; |
| 1282 | - gap: 0.5rem; | 1278 | + margin: 0.375rem 0 0.5rem; |
| 1283 | - flex-wrap: wrap; | 1279 | + line-height: 1.4; |
| 1284 | } | 1280 | } |
| 1285 | 1281 | ||
| 1286 | - .breadcrumb a { | 1282 | + .context-hint { |
| 1287 | - font-family: 'DM Mono', monospace; | ||
| 1288 | font-size: 0.75rem; | 1283 | font-size: 0.75rem; |
| 1289 | - color: var(--theme-texto); | 1284 | + color: #666666; |
| 1290 | - text-decoration: none; | 1285 | + margin: 0; |
| 1291 | - transition: color 0.2s; | 1286 | + line-height: 1.4; |
| 1292 | } | 1287 | } |
| 1293 | - .breadcrumb a:hover { color: var(--theme-titulo); } | ||
| 1294 | 1288 | ||
| 1295 | - .breadcrumb-sep { | 1289 | + .context-label { |
| 1296 | - color: var(--theme-borde); | 1290 | + font-family: 'Qanelas', var(--font-sans); |
| 1297 | font-size: 0.75rem; | 1291 | font-size: 0.75rem; |
| 1292 | + font-weight: 600; | ||
| 1293 | + color: var(--theme-texto); | ||
| 1294 | + text-transform: uppercase; | ||
| 1295 | + letter-spacing: 0.03em; | ||
| 1296 | + margin: 0; | ||
| 1298 | } | 1297 | } |
| 1299 | 1298 | ||
| 1300 | - .breadcrumb-link { | 1299 | + .desktop-hint { |
| 1301 | - max-width: 150px; | 1300 | + display: inline; |
| 1302 | - overflow: hidden; | ||
| 1303 | - text-overflow: ellipsis; | ||
| 1304 | - white-space: nowrap; | ||
| 1305 | } | 1301 | } |
| 1306 | 1302 | ||
| 1307 | - .title-row { | 1303 | + .mobile-hint { |
| 1308 | - display: flex; | 1304 | + display: none; |
| 1309 | - align-items: flex-start; | 1305 | + } |
| 1310 | - justify-content: space-between; | 1306 | + |
| 1311 | - margin-top: 0.5rem; | 1307 | + @media (max-width: 768px) { |
| 1312 | - gap: 1rem; | 1308 | + .desktop-hint { |
| 1309 | + display: none; | ||
| 1310 | + } | ||
| 1311 | + .mobile-hint { | ||
| 1312 | + display: inline; | ||
| 1313 | + } | ||
| 1313 | } | 1314 | } |
| 1314 | 1315 | ||
| 1315 | - .title-main { | 1316 | + .context-section { |
| 1316 | display: flex; | 1317 | display: flex; |
| 1317 | - align-items: baseline; | 1318 | + flex-direction: column; |
| 1318 | - gap: 1rem; | 1319 | + gap: 0.5rem; |
| 1319 | - flex-wrap: wrap; | ||
| 1320 | } | 1320 | } |
| 1321 | 1321 | ||
| 1322 | - .codigo { | 1322 | + .context-separator { |
| 1323 | - font-family: 'DM Mono', monospace; | 1323 | + border-top: 1px solid var(--theme-borde); |
| 1324 | - font-size: 1.5rem; | 1324 | + margin: 0.25rem 0; |
| 1325 | - color: var(--theme-texto); | ||
| 1326 | - font-weight: 400; | ||
| 1327 | } | 1325 | } |
| 1328 | 1326 | ||
| 1329 | - h1 { | 1327 | + .sidebar-card { |
| 1330 | - font-family: var(--font-display), Georgia, serif; | 1328 | + background: var(--theme-surface); |
| 1331 | - font-size: 1.5rem; | 1329 | + border-radius: 12px; |
| 1332 | - font-weight: 400; | 1330 | + padding: 1rem; |
| 1333 | - color: var(--theme-titulo); | ||
| 1334 | - margin: 0; | ||
| 1335 | - display: inline-flex; | ||
| 1336 | - align-items: center; | ||
| 1337 | - gap: 0.5rem; | ||
| 1338 | } | 1331 | } |
| 1339 | 1332 | ||
| 1340 | - .help-icon { | 1333 | + .sidebar-card-header { |
| 1341 | - background: transparent; | 1334 | + font-family: 'DM Mono', monospace; |
| 1342 | - border: none; | 1335 | + font-size: 0.5625rem; |
| 1343 | - padding: 0; | 1336 | + letter-spacing: 0.1em; |
| 1344 | color: var(--theme-texto); | 1337 | color: var(--theme-texto); |
| 1345 | - cursor: pointer; | 1338 | + opacity: 0.6; |
| 1346 | - transition: all 0.2s; | 1339 | + margin-bottom: 0.75rem; |
| 1347 | - display: inline-flex; | 1340 | + text-transform: uppercase; |
| 1348 | - align-items: center; | ||
| 1349 | - justify-content: center; | ||
| 1350 | - vertical-align: middle; | ||
| 1351 | } | 1341 | } |
| 1352 | - .help-icon:hover, .help-icon.active { | 1342 | + |
| 1353 | - color: var(--theme-accent); | 1343 | + .sidebar-separator { |
| 1344 | + height: 1px; | ||
| 1345 | + background: var(--theme-borde); | ||
| 1346 | + margin: 0.5rem 0; | ||
| 1354 | } | 1347 | } |
| 1355 | 1348 | ||
| 1356 | - .meta { | 1349 | + .sidebar-section { |
| 1357 | display: flex; | 1350 | display: flex; |
| 1358 | - align-items: center; | 1351 | + flex-direction: column; |
| 1359 | gap: 0.75rem; | 1352 | gap: 0.75rem; |
| 1360 | - margin-top: 0.5rem; | ||
| 1361 | } | 1353 | } |
| 1362 | 1354 | ||
| 1363 | - .badge { | 1355 | + .sidebar-selectors { |
| 1364 | - font-family: 'DM Mono', monospace; | 1356 | + display: flex; |
| 1365 | - font-size: 0.625rem; | 1357 | + flex-direction: column; |
| 1366 | - letter-spacing: 0.1em; | 1358 | + gap: 0.5rem; |
| 1367 | - text-transform: uppercase; | ||
| 1368 | - padding: 0.25rem 0.5rem; | ||
| 1369 | - background: var(--theme-surface); | ||
| 1370 | - border: 1px solid var(--theme-borde); | ||
| 1371 | - border-radius: 4px; | ||
| 1372 | - color: var(--theme-texto); | ||
| 1373 | } | 1359 | } |
| 1374 | 1360 | ||
| 1375 | - .years { | 1361 | + /* Tree compacto para sidebar */ |
| 1376 | - font-family: 'DM Mono', monospace; | 1362 | + .dashboard-context .tree { |
| 1377 | font-size: 0.75rem; | 1363 | font-size: 0.75rem; |
| 1378 | - color: var(--theme-texto); | 1364 | + line-height: 1.6; |
| 1365 | + } | ||
| 1366 | + | ||
| 1367 | + .dashboard-context .tree-node { | ||
| 1368 | + padding: 0.375rem 0.5rem; | ||
| 1369 | + margin-left: calc(var(--indent, 0) * 1rem); | ||
| 1370 | + border-left-width: 2px; | ||
| 1371 | + border-radius: 0 6px 6px 0; | ||
| 1379 | } | 1372 | } |
| 1380 | 1373 | ||
| 1381 | - /* Split View - Header y controles */ | 1374 | + .dashboard-context .tree-name { |
| 1382 | - .split-header { | 1375 | + font-size: 0.75rem; |
| 1376 | + white-space: nowrap; | ||
| 1377 | + overflow: hidden; | ||
| 1378 | + text-overflow: ellipsis; | ||
| 1379 | + max-width: 140px; | ||
| 1380 | + } | ||
| 1381 | + | ||
| 1382 | + .dashboard-context .tree-code { | ||
| 1383 | + font-size: 0.6875rem; | ||
| 1384 | + min-width: 2.5rem; | ||
| 1385 | + } | ||
| 1386 | + | ||
| 1387 | + .dashboard-context .tree-children { | ||
| 1388 | + margin-left: calc(var(--indent, 0) * 1rem); | ||
| 1389 | + } | ||
| 1390 | + | ||
| 1391 | + .dashboard-context .tree-node-child { | ||
| 1392 | + padding-left: 0.75rem; | ||
| 1393 | + } | ||
| 1394 | + | ||
| 1395 | + /* Vigencia compacta */ | ||
| 1396 | + .vigencia-compact { | ||
| 1383 | display: flex; | 1397 | display: flex; |
| 1384 | align-items: center; | 1398 | align-items: center; |
| 1385 | - justify-content: space-between; | 1399 | + gap: 0.75rem; |
| 1386 | - padding: 0.75rem 1rem; | ||
| 1387 | - background: var(--theme-surface); | ||
| 1388 | - border-bottom: 1px solid var(--theme-borde); | ||
| 1389 | - font-size: 0.75rem; | ||
| 1390 | - font-weight: 600; | ||
| 1391 | - color: var(--theme-titulo); | ||
| 1392 | } | 1400 | } |
| 1393 | 1401 | ||
| 1394 | - .split-close { | 1402 | + .vigencia-status-icon { |
| 1395 | - padding: 0.25rem; | 1403 | + width: 32px; |
| 1396 | - border: none; | 1404 | + height: 32px; |
| 1397 | - background: transparent; | 1405 | + border-radius: 8px; |
| 1398 | - color: var(--theme-texto); | ||
| 1399 | - cursor: pointer; | ||
| 1400 | - border-radius: 4px; | ||
| 1401 | display: flex; | 1406 | display: flex; |
| 1402 | align-items: center; | 1407 | align-items: center; |
| 1403 | justify-content: center; | 1408 | justify-content: center; |
| 1409 | + flex-shrink: 0; | ||
| 1404 | } | 1410 | } |
| 1405 | 1411 | ||
| 1406 | - .split-close:hover { | 1412 | + .vigencia-status-icon.vigencia-full { |
| 1407 | - background: var(--theme-borde); | 1413 | + background: rgba(74, 158, 107, 0.15); |
| 1414 | + color: #4A9E6B; | ||
| 1415 | + } | ||
| 1416 | + | ||
| 1417 | + .vigencia-status-icon.vigencia-partial { | ||
| 1418 | + background: rgba(212, 137, 61, 0.15); | ||
| 1419 | + color: #D4893D; | ||
| 1420 | + } | ||
| 1421 | + | ||
| 1422 | + .vigencia-text { | ||
| 1423 | + display: flex; | ||
| 1424 | + flex-direction: column; | ||
| 1425 | + gap: 0.125rem; | ||
| 1426 | + } | ||
| 1427 | + | ||
| 1428 | + .vigencia-main { | ||
| 1429 | + font-size: 0.875rem; | ||
| 1430 | + font-weight: 500; | ||
| 1408 | color: var(--theme-titulo); | 1431 | color: var(--theme-titulo); |
| 1409 | } | 1432 | } |
| 1410 | 1433 | ||
| 1411 | - .split-iframe { | 1434 | + .vigencia-sub { |
| 1412 | - flex: 1; | 1435 | + font-size: 0.6875rem; |
| 1413 | - width: 100%; | 1436 | + color: var(--theme-texto); |
| 1414 | - border: none; | 1437 | + opacity: 0.7; |
| 1415 | - background: var(--theme-body); | ||
| 1416 | } | 1438 | } |
| 1417 | 1439 | ||
| 1418 | - .split-resizer { | 1440 | + /* Definición compacta */ |
| 1419 | - width: 8px; | 1441 | + .definicion-compact { |
| 1420 | - background: var(--theme-borde); | 1442 | + display: flex; |
| 1421 | - cursor: col-resize; | 1443 | + align-items: center; |
| 1422 | - flex-shrink: 0; | 1444 | + justify-content: space-between; |
| 1423 | - position: relative; | 1445 | + gap: 0.5rem; |
| 1424 | - transition: background 0.2s; | ||
| 1425 | } | 1446 | } |
| 1426 | 1447 | ||
| 1427 | - .split-resizer::after { | 1448 | + .def-status { |
| 1428 | - content: ''; | 1449 | + font-size: 0.8125rem; |
| 1429 | - position: absolute; | 1450 | + color: #555555; |
| 1430 | - left: 50%; | 1451 | + } |
| 1431 | - top: 50%; | 1452 | + |
| 1432 | - transform: translate(-50%, -50%); | 1453 | + .def-status.def-changed { |
| 1433 | - width: 4px; | 1454 | + color: #555555; |
| 1434 | - height: 48px; | 1455 | + } |
| 1435 | - background: var(--theme-texto); | 1456 | + |
| 1457 | + .def-btn { | ||
| 1458 | + font-size: 0.6875rem; | ||
| 1459 | + font-weight: 500; | ||
| 1460 | + color: #555555; | ||
| 1461 | + background: rgba(0, 0, 0, 0.06); | ||
| 1462 | + border: none; | ||
| 1463 | + padding: 0.25rem 0.625rem; | ||
| 1436 | border-radius: 4px; | 1464 | border-radius: 4px; |
| 1437 | - opacity: 0.3; | 1465 | + cursor: pointer; |
| 1438 | - transition: opacity 0.2s, height 0.2s; | 1466 | + transition: background 0.2s; |
| 1439 | } | 1467 | } |
| 1440 | 1468 | ||
| 1441 | - .split-resizer:hover { | 1469 | + .def-btn:hover { |
| 1442 | - background: var(--theme-accent); | 1470 | + background: rgba(0, 0, 0, 0.1); |
| 1443 | } | 1471 | } |
| 1444 | 1472 | ||
| 1445 | - .split-resizer:hover::after { | 1473 | + /* Main content */ |
| 1446 | - opacity: 0.8; | 1474 | + .dashboard-main { |
| 1447 | - height: 64px; | 1475 | + display: flex; |
| 1476 | + flex-direction: column; | ||
| 1477 | + gap: 0.75rem; | ||
| 1478 | + min-width: 0; | ||
| 1479 | + width: 100%; | ||
| 1480 | + align-self: start; | ||
| 1481 | + padding-top: 0; | ||
| 1482 | + padding-bottom: 0; | ||
| 1483 | + transition: all 0.3s ease; | ||
| 1448 | } | 1484 | } |
| 1449 | 1485 | ||
| 1450 | - /* Botón para abrir/cerrar split view */ | 1486 | + .dashboard-main.expanded { |
| 1451 | - .split-toggle { | 1487 | + align-self: stretch; |
| 1452 | - position: fixed; | 1488 | + } |
| 1453 | - left: 1rem; | 1489 | + |
| 1454 | - bottom: 1.5rem; | 1490 | + /* Header compacto */ |
| 1455 | - display: none; | 1491 | + .page-nav { |
| 1492 | + padding: 1rem 2rem; | ||
| 1493 | + } | ||
| 1494 | + | ||
| 1495 | + .back-link { | ||
| 1496 | + display: inline-flex; | ||
| 1456 | align-items: center; | 1497 | align-items: center; |
| 1457 | - gap: 0.5rem; | 1498 | + gap: 0.375rem; |
| 1458 | - padding: 0.625rem 1rem; | 1499 | + font-family: 'DM Mono', monospace; |
| 1500 | + font-size: 0.6875rem; | ||
| 1501 | + color: var(--theme-texto); | ||
| 1502 | + text-decoration: none; | ||
| 1503 | + transition: color 0.2s; | ||
| 1504 | + } | ||
| 1505 | + .back-link:hover { color: var(--theme-accent); } | ||
| 1506 | + | ||
| 1507 | + /* ═══════════════════════════════════════════════════════════════ | ||
| 1508 | + TOOLBAR - Barra de acciones moderna | ||
| 1509 | + ═══════════════════════════════════════════════════════════════ */ | ||
| 1510 | + .toolbar { | ||
| 1511 | + display: flex; | ||
| 1512 | + align-items: center; | ||
| 1513 | + gap: 0.75rem; | ||
| 1514 | + padding: 0.75rem 2rem; | ||
| 1459 | background: var(--theme-surface); | 1515 | background: var(--theme-surface); |
| 1516 | + border-bottom: 1px solid var(--theme-borde); | ||
| 1517 | + flex-wrap: wrap; | ||
| 1518 | + } | ||
| 1519 | + | ||
| 1520 | + .entity-btn { | ||
| 1521 | + display: flex; | ||
| 1522 | + align-items: center; | ||
| 1523 | + gap: 0.5rem; | ||
| 1524 | + padding: 0.5rem 0.75rem; | ||
| 1525 | + background: var(--theme-body); | ||
| 1460 | border: 1px solid var(--theme-borde); | 1526 | border: 1px solid var(--theme-borde); |
| 1461 | border-radius: 8px; | 1527 | border-radius: 8px; |
| 1462 | - color: var(--theme-texto); | 1528 | + font-size: 0.8125rem; |
| 1463 | - font-size: 0.75rem; | 1529 | + color: var(--theme-titulo); |
| 1464 | cursor: pointer; | 1530 | cursor: pointer; |
| 1465 | - z-index: 60; | 1531 | + transition: all 0.2s; |
| 1466 | - transition: all 0.2s, left 0.3s ease; | 1532 | + max-width: 280px; |
| 1467 | - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); | ||
| 1468 | } | 1533 | } |
| 1469 | 1534 | ||
| 1470 | - .split-active .split-toggle { | 1535 | + .entity-btn:hover { |
| 1471 | - /* Se ajusta automáticamente con margin-left del contenedor */ | 1536 | + border-color: var(--theme-accent); |
| 1472 | } | 1537 | } |
| 1473 | 1538 | ||
| 1474 | - @media (min-width: 1024px) { | 1539 | + .entity-btn .entity-label { |
| 1475 | - .split-toggle { | 1540 | + flex: 1; |
| 1476 | - display: flex; | 1541 | + overflow: hidden; |
| 1542 | + text-overflow: ellipsis; | ||
| 1543 | + white-space: nowrap; | ||
| 1477 | } | 1544 | } |
| 1545 | + | ||
| 1546 | + .entity-dropdown { | ||
| 1547 | + position: absolute; | ||
| 1548 | + top: calc(100% + 4px); | ||
| 1549 | + left: 0; | ||
| 1550 | + min-width: 300px; | ||
| 1551 | + background: var(--theme-surface); | ||
| 1552 | + border: 1px solid var(--theme-borde); | ||
| 1553 | + border-radius: 8px; | ||
| 1554 | + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.25); | ||
| 1555 | + z-index: 200; | ||
| 1478 | } | 1556 | } |
| 1479 | 1557 | ||
| 1480 | - .split-toggle:hover { | 1558 | + .entity-dropdown .dropdown-search { |
| 1481 | - background: var(--theme-fill); | 1559 | + padding: 0.5rem; |
| 1560 | + border-bottom: 1px solid var(--theme-borde); | ||
| 1561 | + } | ||
| 1562 | + | ||
| 1563 | + .entity-dropdown .dropdown-search input { | ||
| 1564 | + width: 100%; | ||
| 1565 | + padding: 0.5rem 0.75rem; | ||
| 1566 | + background: var(--theme-body); | ||
| 1567 | + border: 1px solid var(--theme-borde); | ||
| 1568 | + border-radius: 6px; | ||
| 1569 | + font-size: 0.8125rem; | ||
| 1482 | color: var(--theme-titulo); | 1570 | color: var(--theme-titulo); |
| 1483 | - border-color: var(--theme-accent); | 1571 | + outline: none; |
| 1484 | } | 1572 | } |
| 1485 | 1573 | ||
| 1486 | - .split-toggle.active { | 1574 | + .entity-dropdown .dropdown-search input:focus { |
| 1487 | - background: var(--theme-accent); | ||
| 1488 | - color: white; | ||
| 1489 | border-color: var(--theme-accent); | 1575 | border-color: var(--theme-accent); |
| 1490 | } | 1576 | } |
| 1491 | 1577 | ||
| 1492 | - .split-toggle-label { | 1578 | + .entity-dropdown .dropdown-item { |
| 1493 | - font-weight: 500; | 1579 | + display: flex; |
| 1580 | + align-items: center; | ||
| 1581 | + gap: 0.5rem; | ||
| 1582 | + width: 100%; | ||
| 1583 | + padding: 0.625rem 0.75rem; | ||
| 1584 | + background: transparent; | ||
| 1585 | + border: none; | ||
| 1586 | + text-align: left; | ||
| 1587 | + font-size: 0.8125rem; | ||
| 1588 | + color: var(--theme-titulo); | ||
| 1589 | + cursor: pointer; | ||
| 1590 | + transition: background 0.15s; | ||
| 1494 | } | 1591 | } |
| 1495 | 1592 | ||
| 1496 | - /* Info panel */ | 1593 | + .entity-dropdown .dropdown-item:hover, |
| 1497 | - .info-panel { | 1594 | + .entity-dropdown .dropdown-item.active { |
| 1498 | - background: var(--theme-fill); | 1595 | + background: rgba(201, 167, 81, 0.1); |
| 1499 | - border-bottom: 1px solid var(--theme-borde); | ||
| 1500 | - padding: 1.5rem 2rem; | ||
| 1501 | } | 1596 | } |
| 1502 | 1597 | ||
| 1503 | - .info-content h4 { | 1598 | + .entity-dropdown .dropdown-item .item-code { |
| 1504 | font-family: 'DM Mono', monospace; | 1599 | font-family: 'DM Mono', monospace; |
| 1505 | font-size: 0.625rem; | 1600 | font-size: 0.625rem; |
| 1506 | - letter-spacing: 0.15em; | ||
| 1507 | - text-transform: uppercase; | ||
| 1508 | color: var(--theme-texto); | 1601 | color: var(--theme-texto); |
| 1509 | - margin: 0 0 0.5rem 0; | 1602 | + flex-shrink: 0; |
| 1510 | } | 1603 | } |
| 1511 | 1604 | ||
| 1512 | - .info-content p { | 1605 | + .entity-dropdown .dropdown-item .item-name { |
| 1513 | - font-size: 0.875rem; | 1606 | + flex: 1; |
| 1514 | - line-height: 1.6; | 1607 | + overflow: hidden; |
| 1608 | + text-overflow: ellipsis; | ||
| 1609 | + white-space: nowrap; | ||
| 1610 | + } | ||
| 1611 | + | ||
| 1612 | + .entity-dropdown .dropdown-item .item-count { | ||
| 1613 | + font-size: 0.6875rem; | ||
| 1515 | color: var(--theme-texto); | 1614 | color: var(--theme-texto); |
| 1516 | - margin: 0 0 1.25rem 0; | 1615 | + opacity: 0.6; |
| 1517 | } | 1616 | } |
| 1518 | 1617 | ||
| 1519 | - .variacion { | 1618 | + .entity-dropdown .dropdown-list { |
| 1520 | - padding-left: 1rem; | 1619 | + max-height: 240px; |
| 1521 | - border-left: 2px solid var(--theme-borde); | 1620 | + overflow-y: auto; |
| 1621 | + } | ||
| 1622 | + | ||
| 1623 | + /* ═══════════════════════════════════════════════════════════════ | ||
| 1624 | + SIDEBAR | ||
| 1625 | + ═══════════════════════════════════════════════════════════════ */ | ||
| 1626 | + /* Tree card */ | ||
| 1627 | + .tree-card { | ||
| 1628 | + background: var(--theme-surface); | ||
| 1629 | + border-radius: 16px; | ||
| 1630 | + padding: 1.25rem 1.5rem; | ||
| 1522 | margin-bottom: 1rem; | 1631 | margin-bottom: 1rem; |
| 1523 | } | 1632 | } |
| 1524 | 1633 | ||
| 1525 | - .variacion-rango { | 1634 | + .tree-card-header { |
| 1526 | font-family: 'DM Mono', monospace; | 1635 | font-family: 'DM Mono', monospace; |
| 1527 | - font-size: 0.688rem; | 1636 | + font-size: 0.625rem; |
| 1637 | + letter-spacing: 0.12em; | ||
| 1528 | color: var(--theme-texto); | 1638 | color: var(--theme-texto); |
| 1639 | + margin-bottom: 1rem; | ||
| 1640 | + text-transform: uppercase; | ||
| 1529 | } | 1641 | } |
| 1530 | 1642 | ||
| 1531 | - .variacion p { | 1643 | + /* Tree hierarchy */ |
| 1532 | - margin-top: 0.25rem; | 1644 | + .tree { |
| 1533 | - font-size: 0.813rem; | ||
| 1534 | - } | ||
| 1535 | - | ||
| 1536 | - /* Hijos list in info panel */ | ||
| 1537 | - .hijos-list { | ||
| 1538 | display: flex; | 1645 | display: flex; |
| 1539 | flex-direction: column; | 1646 | flex-direction: column; |
| 1540 | - gap: 0.25rem; | 1647 | + gap: 0.125rem; |
| 1541 | - margin-bottom: 1rem; | ||
| 1542 | } | 1648 | } |
| 1543 | 1649 | ||
| 1544 | - .hijo-link { | 1650 | + .tree-node { |
| 1545 | display: flex; | 1651 | display: flex; |
| 1546 | - align-items: baseline; | 1652 | + align-items: center; |
| 1547 | gap: 0.75rem; | 1653 | gap: 0.75rem; |
| 1548 | - padding: 0.375rem 0.5rem; | 1654 | + padding: 0.5rem 0.75rem; |
| 1549 | - border-radius: 4px; | 1655 | + margin-left: calc(var(--indent, 0) * 1.5rem); |
| 1656 | + border-left: 2px solid #e8f4fa; | ||
| 1550 | text-decoration: none; | 1657 | text-decoration: none; |
| 1551 | - transition: background 0.2s; | 1658 | + border-radius: 0 8px 8px 0; |
| 1659 | + transition: background 0.2s, border-color 0.2s; | ||
| 1552 | } | 1660 | } |
| 1553 | 1661 | ||
| 1554 | - .hijo-link:hover { | 1662 | + .tree-node:hover { |
| 1555 | - background: var(--theme-fill); | 1663 | + background: rgba(214, 234, 245, 0.4); |
| 1664 | + } | ||
| 1665 | + | ||
| 1666 | + .tree-node-current { | ||
| 1667 | + background: #ddf0f8; | ||
| 1668 | + border-left-color: #ddf0f8; | ||
| 1669 | + border-radius: 8px; | ||
| 1670 | + margin-left: calc(var(--indent, 0) * 1.5rem); | ||
| 1671 | + } | ||
| 1672 | + | ||
| 1673 | + .tree-node-current:hover { | ||
| 1674 | + background: #ddf0f8; | ||
| 1675 | + } | ||
| 1676 | + | ||
| 1677 | + .tree-children { | ||
| 1678 | + display: flex; | ||
| 1679 | + flex-direction: column; | ||
| 1680 | + gap: 0.125rem; | ||
| 1681 | + margin-left: calc(var(--indent, 0) * 1.5rem); | ||
| 1682 | + border-left: 2px solid #f0f7fb; | ||
| 1683 | + padding-left: 0; | ||
| 1684 | + } | ||
| 1685 | + | ||
| 1686 | + .tree-node-child { | ||
| 1687 | + margin-left: 0; | ||
| 1688 | + border-left: none; | ||
| 1689 | + padding-left: 1rem; | ||
| 1690 | + } | ||
| 1691 | + | ||
| 1692 | + .tree-node-child:hover { | ||
| 1693 | + background: rgba(214, 234, 245, 0.4); | ||
| 1556 | } | 1694 | } |
| 1557 | 1695 | ||
| 1558 | - .hijo-codigo { | 1696 | + .tree-dot { |
| 1697 | + width: 6px; | ||
| 1698 | + height: 6px; | ||
| 1699 | + border-radius: 50%; | ||
| 1700 | + background: #ddf0f8; | ||
| 1701 | + flex-shrink: 0; | ||
| 1702 | + } | ||
| 1703 | + | ||
| 1704 | + .tree-node-current .tree-dot { | ||
| 1705 | + background: var(--theme-titulo); | ||
| 1706 | + } | ||
| 1707 | + | ||
| 1708 | + .tree-code { | ||
| 1559 | font-family: 'DM Mono', monospace; | 1709 | font-family: 'DM Mono', monospace; |
| 1560 | - font-size: 0.688rem; | 1710 | + font-size: 0.75rem; |
| 1561 | color: var(--theme-texto); | 1711 | color: var(--theme-texto); |
| 1562 | min-width: 3rem; | 1712 | min-width: 3rem; |
| 1563 | } | 1713 | } |
| 1564 | 1714 | ||
| 1565 | - .hijo-nombre { | 1715 | + .tree-node-current .tree-code { |
| 1566 | - font-size: 0.813rem; | ||
| 1567 | color: var(--theme-texto); | 1716 | color: var(--theme-texto); |
| 1568 | } | 1717 | } |
| 1569 | 1718 | ||
| 1570 | - .hijo-link:hover .hijo-nombre { | 1719 | + .tree-name { |
| 1720 | + font-size: 0.8125rem; | ||
| 1721 | + color: var(--theme-titulo); | ||
| 1722 | + flex: 1; | ||
| 1723 | + } | ||
| 1724 | + | ||
| 1725 | + .tree-node-current .tree-name { | ||
| 1571 | color: var(--theme-titulo); | 1726 | color: var(--theme-titulo); |
| 1727 | + font-weight: 600; | ||
| 1728 | + } | ||
| 1729 | + | ||
| 1730 | + .tree-badge { | ||
| 1731 | + font-family: 'DM Mono', monospace; | ||
| 1732 | + font-size: 0.5625rem; | ||
| 1733 | + text-transform: uppercase; | ||
| 1734 | + letter-spacing: 0.05em; | ||
| 1735 | + color: var(--theme-texto); | ||
| 1736 | + opacity: 0.5; | ||
| 1737 | + padding: 0.125rem 0.375rem; | ||
| 1738 | + background: var(--theme-borde); | ||
| 1739 | + border-radius: 4px; | ||
| 1740 | + } | ||
| 1741 | + | ||
| 1742 | + .tree-node-current .tree-badge { | ||
| 1743 | + background: rgba(255, 255, 255, 0.2); | ||
| 1744 | + color: white; | ||
| 1745 | + opacity: 1; | ||
| 1746 | + } | ||
| 1747 | + | ||
| 1748 | + /* Custom Tooltip */ | ||
| 1749 | + .has-tooltip { | ||
| 1750 | + position: relative; | ||
| 1751 | + } | ||
| 1752 | + | ||
| 1753 | + .has-tooltip::after { | ||
| 1754 | + content: attr(data-tooltip); | ||
| 1755 | + position: absolute; | ||
| 1756 | + left: 0; | ||
| 1757 | + top: 100%; | ||
| 1758 | + margin-top: 4px; | ||
| 1759 | + padding: 0.5rem 0.75rem; | ||
| 1760 | + background: #1a1a1a; | ||
| 1761 | + color: #ffffff; | ||
| 1762 | + font-size: 0.75rem; | ||
| 1763 | + font-weight: 400; | ||
| 1764 | + line-height: 1.4; | ||
| 1765 | + border-radius: 6px; | ||
| 1766 | + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); | ||
| 1767 | + white-space: normal; | ||
| 1768 | + max-width: 280px; | ||
| 1769 | + width: max-content; | ||
| 1770 | + z-index: 1000; | ||
| 1771 | + opacity: 0; | ||
| 1772 | + visibility: hidden; | ||
| 1773 | + transform: translateY(-4px); | ||
| 1774 | + transition: opacity 0.15s ease, transform 0.15s ease, visibility 0.15s; | ||
| 1775 | + pointer-events: none; | ||
| 1776 | + } | ||
| 1777 | + | ||
| 1778 | + .has-tooltip:hover::after { | ||
| 1779 | + opacity: 1; | ||
| 1780 | + visibility: visible; | ||
| 1781 | + transform: translateY(0); | ||
| 1782 | + } | ||
| 1783 | + | ||
| 1784 | + /* Tooltip arrow */ | ||
| 1785 | + .has-tooltip::before { | ||
| 1786 | + content: ''; | ||
| 1787 | + position: absolute; | ||
| 1788 | + left: 12px; | ||
| 1789 | + top: 100%; | ||
| 1790 | + border: 5px solid transparent; | ||
| 1791 | + border-bottom-color: #1a1a1a; | ||
| 1792 | + z-index: 1001; | ||
| 1793 | + opacity: 0; | ||
| 1794 | + visibility: hidden; | ||
| 1795 | + transition: opacity 0.15s ease, visibility 0.15s; | ||
| 1796 | + pointer-events: none; | ||
| 1797 | + } | ||
| 1798 | + | ||
| 1799 | + .has-tooltip:hover::before { | ||
| 1800 | + opacity: 1; | ||
| 1801 | + visibility: visible; | ||
| 1802 | + } | ||
| 1803 | + | ||
| 1804 | + /* Hide tooltip on touch devices to avoid sticky tooltips */ | ||
| 1805 | + @media (hover: none) { | ||
| 1806 | + .has-tooltip::after, | ||
| 1807 | + .has-tooltip::before { | ||
| 1808 | + display: none; | ||
| 1809 | + } | ||
| 1810 | + } | ||
| 1811 | + | ||
| 1812 | + /* Tree - modo oscuro */ | ||
| 1813 | + :global(html.dark) .tree-node { | ||
| 1814 | + border-left-color: var(--theme-accent); | ||
| 1815 | + } | ||
| 1816 | + | ||
| 1817 | + :global(html.dark) .tree-node:hover { | ||
| 1818 | + background: rgba(201, 167, 81, 0.1); | ||
| 1819 | + } | ||
| 1820 | + | ||
| 1821 | + :global(html.dark) .tree-node-current { | ||
| 1822 | + background: var(--theme-accent); | ||
| 1823 | + border-left-color: var(--theme-accent); | ||
| 1824 | + } | ||
| 1825 | + | ||
| 1826 | + :global(html.dark) .tree-children { | ||
| 1827 | + border-left-color: rgba(201, 167, 81, 0.3); | ||
| 1828 | + } | ||
| 1829 | + | ||
| 1830 | + :global(html.dark) .tree-node-child:hover { | ||
| 1831 | + background: rgba(201, 167, 81, 0.1); | ||
| 1832 | + } | ||
| 1833 | + | ||
| 1834 | + :global(html.dark) .tree-dot { | ||
| 1835 | + background: var(--theme-accent); | ||
| 1836 | + } | ||
| 1837 | + | ||
| 1838 | + /* Vigencia temporal card */ | ||
| 1839 | + .vigencia-card { | ||
| 1840 | + background: var(--theme-surface); | ||
| 1841 | + border-radius: 16px; | ||
| 1842 | + padding: 1rem 1.25rem; | ||
| 1843 | + margin-bottom: 1rem; | ||
| 1844 | + display: flex; | ||
| 1845 | + flex-direction: column; | ||
| 1846 | + gap: 0.75rem; | ||
| 1847 | + } | ||
| 1848 | + | ||
| 1849 | + .vigencia-row { | ||
| 1850 | + display: flex; | ||
| 1851 | + align-items: center; | ||
| 1852 | + gap: 0.875rem; | ||
| 1853 | + padding: 0.5rem 0; | ||
| 1854 | + } | ||
| 1855 | + | ||
| 1856 | + .vigencia-row:not(:last-child) { | ||
| 1857 | + border-bottom: 1px solid var(--theme-borde); | ||
| 1858 | + padding-bottom: 0.875rem; | ||
| 1859 | + } | ||
| 1860 | + | ||
| 1861 | + .vigencia-icon { | ||
| 1862 | + color: var(--theme-texto); | ||
| 1863 | + opacity: 0.5; | ||
| 1864 | + flex-shrink: 0; | ||
| 1865 | + } | ||
| 1866 | + | ||
| 1867 | + .vigencia-content { | ||
| 1868 | + flex: 1; | ||
| 1869 | + display: flex; | ||
| 1870 | + flex-direction: column; | ||
| 1871 | + gap: 0.125rem; | ||
| 1872 | + } | ||
| 1873 | + | ||
| 1874 | + .vigencia-status { | ||
| 1875 | + font-size: 0.875rem; | ||
| 1876 | + font-weight: 500; | ||
| 1877 | + color: var(--theme-titulo); | ||
| 1878 | + } | ||
| 1879 | + | ||
| 1880 | + .vigencia-full { | ||
| 1881 | + color: #4A9E6B; | ||
| 1882 | + } | ||
| 1883 | + | ||
| 1884 | + .vigencia-partial { | ||
| 1885 | + color: #D4893D; | ||
| 1886 | + } | ||
| 1887 | + | ||
| 1888 | + .vigencia-changes { | ||
| 1889 | + color: #6B9FD4; | ||
| 1890 | + } | ||
| 1891 | + | ||
| 1892 | + .vigencia-detail { | ||
| 1893 | + font-size: 0.75rem; | ||
| 1894 | + color: var(--theme-texto); | ||
| 1895 | + opacity: 0.7; | ||
| 1896 | + } | ||
| 1897 | + | ||
| 1898 | + .vigencia-btn { | ||
| 1899 | + font-size: 0.75rem; | ||
| 1900 | + font-weight: 500; | ||
| 1901 | + color: #6B9FD4; | ||
| 1902 | + background: rgba(107, 159, 212, 0.1); | ||
| 1903 | + border: none; | ||
| 1904 | + padding: 0.375rem 0.75rem; | ||
| 1905 | + border-radius: 6px; | ||
| 1906 | + cursor: pointer; | ||
| 1907 | + transition: background 0.2s; | ||
| 1908 | + white-space: nowrap; | ||
| 1909 | + } | ||
| 1910 | + | ||
| 1911 | + .vigencia-btn:hover { | ||
| 1912 | + background: rgba(107, 159, 212, 0.2); | ||
| 1913 | + } | ||
| 1914 | + | ||
| 1915 | + /* Modal */ | ||
| 1916 | + .modal-overlay { | ||
| 1917 | + position: fixed; | ||
| 1918 | + inset: 0; | ||
| 1919 | + background: rgba(0, 0, 0, 0.6); | ||
| 1920 | + backdrop-filter: blur(4px); | ||
| 1921 | + display: flex; | ||
| 1922 | + align-items: center; | ||
| 1923 | + justify-content: center; | ||
| 1924 | + z-index: 1000; | ||
| 1925 | + padding: 1rem; | ||
| 1926 | + } | ||
| 1927 | + | ||
| 1928 | + .modal-content { | ||
| 1929 | + background: var(--theme-surface); | ||
| 1930 | + border-radius: 16px; | ||
| 1931 | + max-width: 560px; | ||
| 1932 | + width: 100%; | ||
| 1933 | + max-height: 80vh; | ||
| 1934 | + overflow: hidden; | ||
| 1935 | + display: flex; | ||
| 1936 | + flex-direction: column; | ||
| 1937 | + box-shadow: 0 24px 48px rgba(0, 0, 0, 0.2); | ||
| 1938 | + } | ||
| 1939 | + | ||
| 1940 | + .modal-header { | ||
| 1941 | + display: flex; | ||
| 1942 | + align-items: center; | ||
| 1943 | + justify-content: space-between; | ||
| 1944 | + padding: 1.25rem 1.5rem; | ||
| 1945 | + border-bottom: 1px solid var(--theme-borde); | ||
| 1946 | + } | ||
| 1947 | + | ||
| 1948 | + .modal-header h3 { | ||
| 1949 | + font-size: 1rem; | ||
| 1950 | + font-weight: 600; | ||
| 1951 | + color: var(--theme-titulo); | ||
| 1952 | + margin: 0; | ||
| 1953 | + } | ||
| 1954 | + | ||
| 1955 | + .modal-close { | ||
| 1956 | + color: var(--theme-texto); | ||
| 1957 | + background: transparent; | ||
| 1958 | + border: none; | ||
| 1959 | + padding: 0.25rem; | ||
| 1960 | + cursor: pointer; | ||
| 1961 | + border-radius: 6px; | ||
| 1962 | + transition: background 0.2s; | ||
| 1963 | + } | ||
| 1964 | + | ||
| 1965 | + .modal-close:hover { | ||
| 1966 | + background: var(--theme-borde); | ||
| 1967 | + } | ||
| 1968 | + | ||
| 1969 | + .modal-body { | ||
| 1970 | + padding: 1.5rem; | ||
| 1971 | + overflow-y: auto; | ||
| 1972 | + display: flex; | ||
| 1973 | + flex-direction: column; | ||
| 1974 | + gap: 1rem; | ||
| 1975 | + } | ||
| 1976 | + | ||
| 1977 | + .def-item { | ||
| 1978 | + padding: 1rem; | ||
| 1979 | + background: var(--theme-body); | ||
| 1980 | + border-radius: 10px; | ||
| 1981 | + border-left: 3px solid var(--theme-borde); | ||
| 1982 | + } | ||
| 1983 | + | ||
| 1984 | + .def-current { | ||
| 1985 | + border-left-color: #6B9FD4; | ||
| 1986 | + } | ||
| 1987 | + | ||
| 1988 | + .def-header { | ||
| 1989 | + display: flex; | ||
| 1990 | + align-items: center; | ||
| 1991 | + gap: 0.5rem; | ||
| 1992 | + margin-bottom: 0.5rem; | ||
| 1993 | + } | ||
| 1994 | + | ||
| 1995 | + .def-period { | ||
| 1996 | + font-family: 'DM Mono', monospace; | ||
| 1997 | + font-size: 0.75rem; | ||
| 1998 | + font-weight: 500; | ||
| 1999 | + color: #6B9FD4; | ||
| 2000 | + } | ||
| 2001 | + | ||
| 2002 | + .def-badge { | ||
| 2003 | + font-size: 0.625rem; | ||
| 2004 | + font-weight: 600; | ||
| 2005 | + text-transform: uppercase; | ||
| 2006 | + letter-spacing: 0.05em; | ||
| 2007 | + color: white; | ||
| 2008 | + background: #4A9E6B; | ||
| 2009 | + padding: 0.125rem 0.5rem; | ||
| 2010 | + border-radius: 4px; | ||
| 2011 | + } | ||
| 2012 | + | ||
| 2013 | + .def-text { | ||
| 2014 | + font-size: 0.875rem; | ||
| 2015 | + color: var(--theme-texto); | ||
| 2016 | + line-height: 1.6; | ||
| 2017 | + margin: 0; | ||
| 2018 | + } | ||
| 2019 | + | ||
| 2020 | + /* Modal - modo oscuro */ | ||
| 2021 | + :global(html.dark) .vigencia-btn { | ||
| 2022 | + color: var(--theme-accent); | ||
| 2023 | + background: rgba(201, 167, 81, 0.1); | ||
| 2024 | + } | ||
| 2025 | + | ||
| 2026 | + :global(html.dark) .vigencia-btn:hover { | ||
| 2027 | + background: rgba(201, 167, 81, 0.2); | ||
| 2028 | + } | ||
| 2029 | + | ||
| 2030 | + :global(html.dark) .def-current { | ||
| 2031 | + border-left-color: var(--theme-accent); | ||
| 2032 | + } | ||
| 2033 | + | ||
| 2034 | + :global(html.dark) .def-period { | ||
| 2035 | + color: var(--theme-accent); | ||
| 1572 | } | 2036 | } |
| 1573 | 2037 | ||
| 1574 | /* Main */ | 2038 | /* Main */ |
| ... | @@ -1610,51 +2074,7 @@ | ... | @@ -1610,51 +2074,7 @@ |
| 1610 | } | 2074 | } |
| 1611 | } | 2075 | } |
| 1612 | 2076 | ||
| 1613 | - .selector-label { | 2077 | + /* Old dropdown styles - keeping for comparar view */ |
| 1614 | - font-size: 0.813rem; | ||
| 1615 | - color: var(--theme-texto); | ||
| 1616 | - } | ||
| 1617 | - | ||
| 1618 | - .selector-btn { | ||
| 1619 | - display: flex; | ||
| 1620 | - align-items: center; | ||
| 1621 | - gap: 0.5rem; | ||
| 1622 | - padding: 0.5rem 1rem; | ||
| 1623 | - background: transparent; | ||
| 1624 | - border: none; | ||
| 1625 | - box-shadow: inset 0 0 0 1px var(--theme-borde); | ||
| 1626 | - border-radius: 8px; | ||
| 1627 | - color: var(--theme-titulo); | ||
| 1628 | - font-size: 0.875rem; | ||
| 1629 | - cursor: pointer; | ||
| 1630 | - transition: all 0.2s; | ||
| 1631 | - } | ||
| 1632 | - .selector-btn:hover { | ||
| 1633 | - box-shadow: inset 0 0 0 1px var(--theme-accent); | ||
| 1634 | - } | ||
| 1635 | - .selector-btn svg { | ||
| 1636 | - color: var(--theme-texto); | ||
| 1637 | - } | ||
| 1638 | - | ||
| 1639 | - .relative { | ||
| 1640 | - position: relative; | ||
| 1641 | - } | ||
| 1642 | - | ||
| 1643 | - .dropdown-panel { | ||
| 1644 | - position: absolute; | ||
| 1645 | - top: 100%; | ||
| 1646 | - left: 0; | ||
| 1647 | - margin-top: 0.25rem; | ||
| 1648 | - width: min(500px, 90vw); | ||
| 1649 | - max-height: 360px; | ||
| 1650 | - overflow: hidden; | ||
| 1651 | - background: var(--theme-surface); | ||
| 1652 | - border: 1px solid var(--theme-borde); | ||
| 1653 | - border-radius: 8px; | ||
| 1654 | - box-shadow: 0 10px 40px rgba(0,0,0,0.4); | ||
| 1655 | - z-index: 50; | ||
| 1656 | - } | ||
| 1657 | - | ||
| 1658 | .dropdown-search { | 2078 | .dropdown-search { |
| 1659 | padding: 0.5rem; | 2079 | padding: 0.5rem; |
| 1660 | border-bottom: 1px solid var(--theme-borde); | 2080 | border-bottom: 1px solid var(--theme-borde); |
| ... | @@ -1721,53 +2141,120 @@ | ... | @@ -1721,53 +2141,120 @@ |
| 1721 | margin-bottom: 1rem; | 2141 | margin-bottom: 1rem; |
| 1722 | } | 2142 | } |
| 1723 | 2143 | ||
| 1724 | - .chart-protagonista { | 2144 | + .chart-protagonista { |
| 1725 | - margin-bottom: 1.25rem; | 2145 | + background: var(--theme-surface); |
| 2146 | + border-radius: 16px; | ||
| 2147 | + padding: 1.25rem 1.5rem; | ||
| 2148 | + display: flex; | ||
| 2149 | + flex-direction: column; | ||
| 2150 | + min-width: 0; | ||
| 2151 | + width: 100%; | ||
| 2152 | + transition: flex 0.3s ease; | ||
| 2153 | + } | ||
| 2154 | + | ||
| 2155 | + .chart-protagonista.expanded { | ||
| 2156 | + flex: 1; | ||
| 2157 | + } | ||
| 2158 | + | ||
| 2159 | + .chart-protagonista .chart-wrapper { | ||
| 2160 | + flex: 1; | ||
| 2161 | + display: flex; | ||
| 2162 | + align-items: stretch; | ||
| 2163 | + min-height: 220px; | ||
| 2164 | + transition: min-height 0.3s ease; | ||
| 2165 | + } | ||
| 2166 | + | ||
| 2167 | + .chart-protagonista.expanded .chart-wrapper { | ||
| 2168 | + min-height: 320px; | ||
| 2169 | + } | ||
| 2170 | + | ||
| 2171 | + .chart-protagonista .chart-container { | ||
| 2172 | + height: 100%; | ||
| 2173 | + } | ||
| 2174 | + | ||
| 2175 | + .chart-protagonista .y-axis { | ||
| 2176 | + padding-bottom: 1.5rem; | ||
| 2177 | + } | ||
| 2178 | + | ||
| 2179 | + .chart-section:has(.chart-comparar) { | ||
| 2180 | + margin-bottom: 0; | ||
| 2181 | + } | ||
| 2182 | + | ||
| 2183 | + .chart-section:has(.chart-comparar) .y-axis { | ||
| 2184 | + height: calc(100vh - 380px); | ||
| 2185 | + min-height: 140px; | ||
| 2186 | + max-height: 280px; | ||
| 2187 | + } | ||
| 2188 | + | ||
| 2189 | + .chart-top-row { | ||
| 2190 | + display: flex; | ||
| 2191 | + justify-content: space-between; | ||
| 2192 | + align-items: center; | ||
| 2193 | + margin-bottom: 0.375rem; | ||
| 2194 | + } | ||
| 2195 | + | ||
| 2196 | + .main-title-pills { | ||
| 2197 | + display: flex; | ||
| 2198 | + gap: 0.5rem; | ||
| 1726 | } | 2199 | } |
| 1727 | 2200 | ||
| 1728 | - .chart-protagonista .chart { | 2201 | + .pill { |
| 1729 | - height: calc(100vh - 480px); | 2202 | + font-family: 'DM Mono', monospace; |
| 1730 | - min-height: 120px; | 2203 | + font-size: 0.75rem; |
| 1731 | - max-height: 200px; | 2204 | + font-weight: 500; |
| 2205 | + padding: 0.375rem 0.75rem; | ||
| 2206 | + border-radius: 6px; | ||
| 2207 | + letter-spacing: 0.03em; | ||
| 1732 | } | 2208 | } |
| 1733 | 2209 | ||
| 1734 | - .chart-protagonista .y-axis { | 2210 | + .pill-nivel { |
| 1735 | - height: calc(100vh - 480px); | 2211 | + background: rgba(166, 138, 62, 0.12); |
| 1736 | - min-height: 120px; | 2212 | + color: #C9A751; |
| 1737 | - max-height: 200px; | ||
| 1738 | } | 2213 | } |
| 1739 | 2214 | ||
| 1740 | - .chart-section:has(.chart-comparar) { | 2215 | + .pill-codigo { |
| 1741 | - margin-bottom: 0; | 2216 | + background: rgba(107, 159, 212, 0.12); |
| 2217 | + color: #6B9FD4; | ||
| 1742 | } | 2218 | } |
| 1743 | 2219 | ||
| 1744 | - .chart-section:has(.chart-comparar) .y-axis { | 2220 | + .main-title-name { |
| 1745 | - height: calc(100vh - 380px); | 2221 | + font-size: 1.375rem; |
| 1746 | - min-height: 140px; | 2222 | + font-weight: 600; |
| 1747 | - max-height: 280px; | 2223 | + color: var(--theme-titulo); |
| 2224 | + margin: 0 0 0.5rem 0; | ||
| 2225 | + line-height: 1.2; | ||
| 1748 | } | 2226 | } |
| 1749 | 2227 | ||
| 1750 | .chart-header { | 2228 | .chart-header { |
| 1751 | - display: flex; | ||
| 1752 | - justify-content: space-between; | ||
| 1753 | - align-items: baseline; | ||
| 1754 | margin-bottom: 0.5rem; | 2229 | margin-bottom: 0.5rem; |
| 1755 | } | 2230 | } |
| 1756 | 2231 | ||
| 1757 | .chart-title { | 2232 | .chart-title { |
| 2233 | + font-family: 'Qanelas', var(--font-sans); | ||
| 1758 | font-size: 0.75rem; | 2234 | font-size: 0.75rem; |
| 2235 | + font-weight: 500; | ||
| 1759 | color: var(--theme-texto); | 2236 | color: var(--theme-texto); |
| 1760 | text-transform: uppercase; | 2237 | text-transform: uppercase; |
| 1761 | - letter-spacing: 0.1em; | 2238 | + letter-spacing: 0.08em; |
| 1762 | } | 2239 | } |
| 1763 | 2240 | ||
| 1764 | .chart-period { | 2241 | .chart-period { |
| 1765 | - font-family: 'DM Mono', monospace; | 2242 | + font-family: 'Qanelas', var(--font-sans); |
| 1766 | - font-size: 0.875rem; | 2243 | + font-size: 0.75rem; |
| 2244 | + font-weight: 400; | ||
| 1767 | color: var(--theme-texto); | 2245 | color: var(--theme-texto); |
| 1768 | - font-variant-numeric: tabular-nums; | 2246 | + opacity: 0.7; |
| 1769 | - min-width: 5rem; | 2247 | + text-transform: none; |
| 1770 | - text-align: right; | 2248 | + letter-spacing: 0; |
| 2249 | + } | ||
| 2250 | + | ||
| 2251 | + .chart-controls { | ||
| 2252 | + display: flex; | ||
| 2253 | + align-items: center; | ||
| 2254 | + justify-content: space-between; | ||
| 2255 | + gap: 1rem; | ||
| 2256 | + margin-bottom: 0.75rem; | ||
| 2257 | + min-width: 0; | ||
| 1771 | } | 2258 | } |
| 1772 | 2259 | ||
| 1773 | .chart-wrapper { | 2260 | .chart-wrapper { |
| ... | @@ -1817,24 +2304,46 @@ | ... | @@ -1817,24 +2304,46 @@ |
| 1817 | flex-direction: column; | 2304 | flex-direction: column; |
| 1818 | justify-content: space-between; | 2305 | justify-content: space-between; |
| 1819 | align-items: flex-end; | 2306 | align-items: flex-end; |
| 1820 | - height: 160px; | 2307 | + padding-right: 0.5rem; |
| 1821 | - padding-bottom: 1.5rem; | 2308 | + width: 4rem; |
| 2309 | + flex-shrink: 0; | ||
| 1822 | } | 2310 | } |
| 1823 | 2311 | ||
| 1824 | .y-label { | 2312 | .y-label { |
| 1825 | - font-family: 'DM Mono', monospace; | 2313 | + font-family: 'Qanelas', var(--font-sans); |
| 1826 | - font-size: 0.625rem; | 2314 | + font-size: 0.6875rem; |
| 1827 | color: var(--theme-texto); | 2315 | color: var(--theme-texto); |
| 2316 | + font-weight: 400; | ||
| 2317 | + line-height: 1; | ||
| 2318 | + white-space: nowrap; | ||
| 1828 | } | 2319 | } |
| 1829 | 2320 | ||
| 1830 | - .chart { | 2321 | + .chart-container { |
| 1831 | flex: 1; | 2322 | flex: 1; |
| 2323 | + position: relative; | ||
| 2324 | + min-height: 160px; | ||
| 2325 | + overflow: hidden; | ||
| 2326 | + } | ||
| 2327 | + | ||
| 2328 | + .grid-line { | ||
| 2329 | + position: absolute; | ||
| 2330 | + left: 0; | ||
| 2331 | + right: 0; | ||
| 2332 | + border-top: 1px dashed var(--theme-texto); | ||
| 2333 | + opacity: 0.3; | ||
| 2334 | + pointer-events: none; | ||
| 2335 | + } | ||
| 2336 | + | ||
| 2337 | + .chart { | ||
| 2338 | + position: absolute; | ||
| 2339 | + top: 0; | ||
| 2340 | + left: 0; | ||
| 2341 | + right: 0; | ||
| 2342 | + bottom: 1.5rem; | ||
| 1832 | display: flex; | 2343 | display: flex; |
| 1833 | align-items: flex-end; | 2344 | align-items: flex-end; |
| 1834 | gap: 4px; | 2345 | gap: 4px; |
| 1835 | - height: 160px; | ||
| 1836 | padding: 0 0.5rem; | 2346 | padding: 0 0.5rem; |
| 1837 | - border-left: 1px solid var(--theme-borde); | ||
| 1838 | } | 2347 | } |
| 1839 | 2348 | ||
| 1840 | .bar-container { | 2349 | .bar-container { |
| ... | @@ -1849,20 +2358,19 @@ | ... | @@ -1849,20 +2358,19 @@ |
| 1849 | 2358 | ||
| 1850 | .bar { | 2359 | .bar { |
| 1851 | width: 100%; | 2360 | width: 100%; |
| 1852 | - background: #2a9d8f; | 2361 | + background: #555555; |
| 1853 | border-radius: 4px 4px 0 0; | 2362 | border-radius: 4px 4px 0 0; |
| 1854 | transition: background 0.3s cubic-bezier(0.4, 0, 0.2, 1), transform 0.25s cubic-bezier(0.4, 0, 0.2, 1); | 2363 | transition: background 0.3s cubic-bezier(0.4, 0, 0.2, 1), transform 0.25s cubic-bezier(0.4, 0, 0.2, 1); |
| 1855 | - min-height: 4px; | 2364 | + min-height: 6px; |
| 1856 | } | 2365 | } |
| 1857 | 2366 | ||
| 1858 | .bar.hovered { | 2367 | .bar.hovered { |
| 1859 | background: var(--theme-accent); | 2368 | background: var(--theme-accent); |
| 1860 | - transform: scaleX(1.1); | ||
| 1861 | } | 2369 | } |
| 1862 | 2370 | ||
| 1863 | .bar-label { | 2371 | .bar-label { |
| 1864 | - font-family: 'DM Mono', monospace; | 2372 | + font-family: 'Qanelas', var(--font-sans); |
| 1865 | - font-size: 0.625rem; | 2373 | + font-size: 0.6875rem; |
| 1866 | color: var(--theme-texto); | 2374 | color: var(--theme-texto); |
| 1867 | margin-top: 0.5rem; | 2375 | margin-top: 0.5rem; |
| 1868 | opacity: 0; | 2376 | opacity: 0; |
| ... | @@ -1875,133 +2383,128 @@ | ... | @@ -1875,133 +2383,128 @@ |
| 1875 | display: grid; | 2383 | display: grid; |
| 1876 | grid-template-columns: repeat(4, 1fr); | 2384 | grid-template-columns: repeat(4, 1fr); |
| 1877 | gap: 0.5rem; | 2385 | gap: 0.5rem; |
| 1878 | - margin-bottom: 1rem; | 2386 | + margin-top: 1rem; |
| 1879 | } | 2387 | } |
| 1880 | 2388 | ||
| 1881 | .kpi { | 2389 | .kpi { |
| 1882 | - background: transparent; | ||
| 1883 | - box-shadow: inset 0 0 0 1px rgba(255,255,255,0.08); | ||
| 1884 | - padding: 0.75rem 0.75rem; | ||
| 1885 | text-align: center; | 2390 | text-align: center; |
| 2391 | + min-width: 0; | ||
| 2392 | + padding: 0.625rem 0.5rem; | ||
| 2393 | + background: var(--theme-body); | ||
| 1886 | border-radius: 8px; | 2394 | border-radius: 8px; |
| 1887 | - transition: box-shadow 0.3s ease; | 2395 | + border: 1px solid var(--theme-borde); |
| 1888 | } | 2396 | } |
| 1889 | 2397 | ||
| 1890 | .kpi-value { | 2398 | .kpi-value { |
| 1891 | display: block; | 2399 | display: block; |
| 1892 | - font-family: 'DM Mono', monospace; | 2400 | + font-family: 'Qanelas', var(--font-sans); |
| 1893 | - font-size: 1.125rem; | 2401 | + font-size: 0.9375rem; |
| 1894 | - font-weight: 500; | 2402 | + font-weight: 600; |
| 1895 | - color: var(--theme-titulo); | 2403 | + color: #666666; |
| 1896 | - margin-bottom: 0; | 2404 | + margin-bottom: 0.125rem; |
| 1897 | transition: opacity 0.2s ease; | 2405 | transition: opacity 0.2s ease; |
| 1898 | font-variant-numeric: tabular-nums; | 2406 | font-variant-numeric: tabular-nums; |
| 1899 | - min-height: 1.5rem; | 2407 | + min-height: auto; |
| 2408 | + white-space: nowrap; | ||
| 2409 | + overflow: hidden; | ||
| 2410 | + text-overflow: ellipsis; | ||
| 1900 | } | 2411 | } |
| 1901 | 2412 | ||
| 1902 | .kpi-label { | 2413 | .kpi-label { |
| 1903 | font-size: 0.5625rem; | 2414 | font-size: 0.5625rem; |
| 1904 | - color: var(--theme-texto); | 2415 | + font-weight: 600; |
| 2416 | + color: #666666; | ||
| 1905 | text-transform: uppercase; | 2417 | text-transform: uppercase; |
| 1906 | letter-spacing: 0.03em; | 2418 | letter-spacing: 0.03em; |
| 1907 | line-height: 1.3; | 2419 | line-height: 1.3; |
| 1908 | transition: opacity 0.2s ease; | 2420 | transition: opacity 0.2s ease; |
| 1909 | - min-height: 1.25rem; | 2421 | + min-height: auto; |
| 1910 | display: flex; | 2422 | display: flex; |
| 1911 | align-items: center; | 2423 | align-items: center; |
| 1912 | justify-content: center; | 2424 | justify-content: center; |
| 1913 | } | 2425 | } |
| 1914 | 2426 | ||
| 1915 | - /* Top entidades */ | 2427 | + /* Ranking Simple */ |
| 1916 | - .top-header { | 2428 | + .ranking-section { |
| 1917 | - display: flex; | 2429 | + background: var(--theme-surface); |
| 1918 | - justify-content: space-between; | 2430 | + border-radius: 16px; |
| 1919 | - align-items: baseline; | 2431 | + padding: 1.25rem 1.5rem; |
| 1920 | - margin-bottom: 0.625rem; | ||
| 1921 | } | 2432 | } |
| 1922 | 2433 | ||
| 1923 | - .top-section h3 { | 2434 | + .ranking-title { |
| 1924 | - font-family: 'DM Mono', monospace; | 2435 | + font-size: 1.375rem; |
| 1925 | - font-size: 0.75rem; | 2436 | + font-weight: 600; |
| 1926 | - color: var(--theme-texto); | 2437 | + color: var(--theme-titulo); |
| 1927 | - text-transform: uppercase; | 2438 | + margin: 0 0 0.5rem 0; |
| 1928 | - letter-spacing: 0.1em; | 2439 | + line-height: 1.2; |
| 1929 | - margin: 0; | ||
| 1930 | } | 2440 | } |
| 1931 | 2441 | ||
| 1932 | - .top-context { | 2442 | + .ranking-desc { |
| 1933 | - font-family: 'DM Mono', monospace; | 2443 | + font-size: 0.8125rem; |
| 1934 | - font-size: 0.688rem; | 2444 | + color: #666666; |
| 1935 | - color: var(--theme-texto); | 2445 | + margin: 0 0 1.25rem 0; |
| 1936 | - font-variant-numeric: tabular-nums; | ||
| 1937 | } | 2446 | } |
| 1938 | 2447 | ||
| 1939 | - .top-list { | 2448 | + .ranking-cards { |
| 1940 | display: flex; | 2449 | display: flex; |
| 1941 | flex-direction: column; | 2450 | flex-direction: column; |
| 1942 | - gap: 0.5rem; | 2451 | + gap: 0.25rem; |
| 1943 | } | 2452 | } |
| 1944 | 2453 | ||
| 1945 | - .top-item { | 2454 | + .ranking-card { |
| 1946 | display: flex; | 2455 | display: flex; |
| 1947 | align-items: center; | 2456 | align-items: center; |
| 1948 | gap: 0.75rem; | 2457 | gap: 0.75rem; |
| 1949 | - padding: 0.625rem 0.875rem; | 2458 | + padding: 0.25rem 0; |
| 1950 | - background: transparent; | 2459 | + min-width: 0; |
| 1951 | - box-shadow: inset 0 0 0 1px rgba(255,255,255,0.08); | ||
| 1952 | - border-radius: 8px; | ||
| 1953 | - transition: box-shadow 0.25s ease; | ||
| 1954 | } | 2460 | } |
| 1955 | 2461 | ||
| 1956 | - .top-item:hover { | 2462 | + .ranking-position { |
| 1957 | - box-shadow: inset 0 0 0 1px rgba(201, 167, 81, 0.4); | 2463 | + font-family: 'Qanelas', var(--font-sans); |
| 2464 | + font-size: 1rem; | ||
| 2465 | + font-weight: 600; | ||
| 2466 | + color: #555555; | ||
| 2467 | + flex-shrink: 0; | ||
| 1958 | } | 2468 | } |
| 1959 | 2469 | ||
| 1960 | - .top-rank { | 2470 | + .ranking-list { |
| 1961 | - font-family: 'DM Mono', monospace; | 2471 | + display: flex; |
| 1962 | - font-size: 0.875rem; | 2472 | + flex-direction: column; |
| 1963 | - color: var(--theme-texto); | 2473 | + gap: 0.75rem; |
| 1964 | - width: 24px; | ||
| 1965 | } | 2474 | } |
| 1966 | 2475 | ||
| 1967 | - .top-info { | 2476 | + .ranking-item { |
| 1968 | - flex: 1; | 2477 | + display: flex; |
| 2478 | + align-items: center; | ||
| 2479 | + gap: 0.75rem; | ||
| 2480 | + padding: 0.5rem 0; | ||
| 2481 | + border-bottom: 1px solid var(--theme-borde); | ||
| 2482 | + min-width: 0; | ||
| 1969 | } | 2483 | } |
| 1970 | 2484 | ||
| 1971 | - .top-name { | 2485 | + .ranking-item:last-child { |
| 1972 | - display: block; | 2486 | + border-bottom: none; |
| 1973 | - font-size: 0.8125rem; | ||
| 1974 | - color: var(--theme-titulo); | ||
| 1975 | - margin-bottom: 0.375rem; | ||
| 1976 | } | 2487 | } |
| 1977 | 2488 | ||
| 1978 | - .top-bar-bg { | 2489 | + .ranking-name { |
| 1979 | - height: 4px; | 2490 | + flex: 1; |
| 1980 | - background: var(--theme-surface); | 2491 | + font-size: 0.8125rem; |
| 1981 | - border-radius: 2px; | 2492 | + font-weight: 400; |
| 2493 | + color: #666666; | ||
| 2494 | + line-height: 1.3; | ||
| 2495 | + white-space: nowrap; | ||
| 1982 | overflow: hidden; | 2496 | overflow: hidden; |
| 2497 | + text-overflow: ellipsis; | ||
| 1983 | } | 2498 | } |
| 1984 | 2499 | ||
| 1985 | - .top-bar { | 2500 | + .ranking-pct { |
| 1986 | - height: 100%; | 2501 | + font-family: 'Qanelas', var(--font-sans); |
| 1987 | - background: var(--theme-accent); | 2502 | + font-size: 1rem; |
| 1988 | - border-radius: 2px; | 2503 | + font-weight: 600; |
| 1989 | - transition: width 0.35s cubic-bezier(0.4, 0, 0.2, 1), background 0.25s ease; | 2504 | + color: #555555; |
| 1990 | - } | 2505 | + flex-shrink: 0; |
| 1991 | - | 2506 | + min-width: 3.5rem; |
| 1992 | - .top-item:hover .top-bar { | ||
| 1993 | - background: var(--theme-accent); | ||
| 1994 | - } | ||
| 1995 | - | ||
| 1996 | - .top-pct { | ||
| 1997 | - font-family: 'DM Mono', monospace; | ||
| 1998 | - font-size: 0.875rem; | ||
| 1999 | - color: var(--theme-texto); | ||
| 2000 | - min-width: 52px; | ||
| 2001 | - width: 52px; | ||
| 2002 | text-align: right; | 2507 | text-align: right; |
| 2003 | - transition: opacity 0.2s ease; | ||
| 2004 | - font-variant-numeric: tabular-nums; | ||
| 2005 | } | 2508 | } |
| 2006 | 2509 | ||
| 2007 | .see-all { | 2510 | .see-all { |
| ... | @@ -2021,38 +2524,7 @@ | ... | @@ -2021,38 +2524,7 @@ |
| 2021 | color: var(--theme-accent); | 2524 | color: var(--theme-accent); |
| 2022 | } | 2525 | } |
| 2023 | 2526 | ||
| 2024 | - /* Vista Toggle */ | 2527 | + /* Comparador inline */ |
| 2025 | - .vista-toggle { | ||
| 2026 | - display: inline-flex; | ||
| 2027 | - background: transparent; | ||
| 2028 | - box-shadow: inset 0 0 0 1px rgba(255,255,255,0.08); | ||
| 2029 | - border-radius: 10px; | ||
| 2030 | - padding: 4px; | ||
| 2031 | - margin-bottom: 0.75rem; | ||
| 2032 | - } | ||
| 2033 | - | ||
| 2034 | - .toggle-btn { | ||
| 2035 | - padding: 0.5rem 1.25rem; | ||
| 2036 | - font-size: 0.813rem; | ||
| 2037 | - font-weight: 500; | ||
| 2038 | - color: var(--theme-texto); | ||
| 2039 | - background: transparent; | ||
| 2040 | - border: none; | ||
| 2041 | - border-radius: 7px; | ||
| 2042 | - cursor: pointer; | ||
| 2043 | - transition: all 0.2s; | ||
| 2044 | - } | ||
| 2045 | - | ||
| 2046 | - .toggle-btn:hover { | ||
| 2047 | - color: var(--theme-texto); | ||
| 2048 | - } | ||
| 2049 | - | ||
| 2050 | - .toggle-btn.active { | ||
| 2051 | - background: var(--theme-titulo); | ||
| 2052 | - color: var(--theme-body); | ||
| 2053 | - } | ||
| 2054 | - | ||
| 2055 | - /* Comparador inline - ajustado para 100vh */ | ||
| 2056 | .comparador-selectors { | 2528 | .comparador-selectors { |
| 2057 | display: flex; | 2529 | display: flex; |
| 2058 | align-items: flex-start; | 2530 | align-items: flex-start; |
| ... | @@ -2333,22 +2805,120 @@ | ... | @@ -2333,22 +2805,120 @@ |
| 2333 | } | 2805 | } |
| 2334 | 2806 | ||
| 2335 | /* Responsive */ | 2807 | /* Responsive */ |
| 2808 | + @media (max-width: 900px) { | ||
| 2809 | + .dashboard-layout { | ||
| 2810 | + grid-template-columns: 1fr; | ||
| 2811 | + padding: 1rem; | ||
| 2812 | + } | ||
| 2813 | + | ||
| 2814 | + .dashboard-context { | ||
| 2815 | + height: auto; | ||
| 2816 | + } | ||
| 2817 | + | ||
| 2818 | + /* Mobile sidebar toggle */ | ||
| 2819 | + .mobile-sidebar-toggle { | ||
| 2820 | + display: flex; | ||
| 2821 | + align-items: center; | ||
| 2822 | + gap: 0.5rem; | ||
| 2823 | + padding: 0.625rem 1rem; | ||
| 2824 | + background: var(--theme-surface); | ||
| 2825 | + border: 1px solid var(--theme-borde); | ||
| 2826 | + border-radius: 10px; | ||
| 2827 | + color: var(--theme-titulo); | ||
| 2828 | + font-family: 'Qanelas', var(--font-sans); | ||
| 2829 | + font-size: 0.875rem; | ||
| 2830 | + font-weight: 500; | ||
| 2831 | + cursor: pointer; | ||
| 2832 | + transition: all 0.2s ease; | ||
| 2833 | + margin-bottom: 0.5rem; | ||
| 2834 | + } | ||
| 2835 | + | ||
| 2836 | + .mobile-sidebar-toggle:hover { | ||
| 2837 | + background: var(--theme-surface-hover); | ||
| 2838 | + } | ||
| 2839 | + | ||
| 2840 | + .mobile-sidebar-toggle svg { | ||
| 2841 | + color: var(--theme-texto); | ||
| 2842 | + opacity: 0.7; | ||
| 2843 | + } | ||
| 2844 | + | ||
| 2845 | + .toggle-label { | ||
| 2846 | + flex: 1; | ||
| 2847 | + text-align: left; | ||
| 2848 | + } | ||
| 2849 | + | ||
| 2850 | + /* Ocultar contenido del sidebar en mobile por defecto */ | ||
| 2851 | + .dashboard-context .context-card { | ||
| 2852 | + display: none; | ||
| 2853 | + height: auto; | ||
| 2854 | + } | ||
| 2855 | + | ||
| 2856 | + /* Mostrar cuando está abierto */ | ||
| 2857 | + .dashboard-context.mobile-open .context-card { | ||
| 2858 | + display: flex; | ||
| 2859 | + animation: slideDown 0.25s ease; | ||
| 2860 | + } | ||
| 2861 | + | ||
| 2862 | + @keyframes slideDown { | ||
| 2863 | + from { | ||
| 2864 | + opacity: 0; | ||
| 2865 | + transform: translateY(-8px); | ||
| 2866 | + } | ||
| 2867 | + to { | ||
| 2868 | + opacity: 1; | ||
| 2869 | + transform: translateY(0); | ||
| 2870 | + } | ||
| 2871 | + } | ||
| 2872 | + | ||
| 2873 | + .context-card { | ||
| 2874 | + height: auto; | ||
| 2875 | + } | ||
| 2876 | + | ||
| 2877 | + .sidebar-card { | ||
| 2878 | + flex: 1; | ||
| 2879 | + min-width: 200px; | ||
| 2880 | + } | ||
| 2881 | + | ||
| 2882 | + .sidebar-separator { | ||
| 2883 | + display: none; | ||
| 2884 | + } | ||
| 2885 | + | ||
| 2886 | + .sidebar-section { | ||
| 2887 | + width: 100%; | ||
| 2888 | + } | ||
| 2889 | + | ||
| 2890 | + .sidebar-selectors { | ||
| 2891 | + flex-direction: row; | ||
| 2892 | + flex-wrap: wrap; | ||
| 2893 | + } | ||
| 2894 | + } | ||
| 2895 | + | ||
| 2336 | @media (max-width: 640px) { | 2896 | @media (max-width: 640px) { |
| 2337 | - header, main { | 2897 | + .page-nav { |
| 2338 | - padding: 1.25rem 1rem; | 2898 | + padding: 0.75rem 1rem; |
| 2339 | } | 2899 | } |
| 2340 | 2900 | ||
| 2341 | - .title-main { | 2901 | + .toolbar { |
| 2902 | + padding: 0.75rem 1rem; | ||
| 2342 | flex-direction: column; | 2903 | flex-direction: column; |
| 2343 | - gap: 0.25rem; | 2904 | + align-items: stretch; |
| 2905 | + gap: 0.5rem; | ||
| 2344 | } | 2906 | } |
| 2345 | 2907 | ||
| 2346 | - .codigo { | 2908 | + .dashboard-layout { |
| 2347 | - font-size: 1.25rem; | 2909 | + padding: 0.75rem; |
| 2348 | } | 2910 | } |
| 2349 | 2911 | ||
| 2350 | - h1 { | 2912 | + .context-card { |
| 2351 | - font-size: 1.375rem; | 2913 | + height: auto; |
| 2914 | + } | ||
| 2915 | + | ||
| 2916 | + .sidebar-selectors { | ||
| 2917 | + flex-direction: column; | ||
| 2918 | + } | ||
| 2919 | + | ||
| 2920 | + main, .dashboard-main { | ||
| 2921 | + padding: 0; | ||
| 2352 | } | 2922 | } |
| 2353 | 2923 | ||
| 2354 | .y-axis { | 2924 | .y-axis { |
| ... | @@ -2360,21 +2930,21 @@ | ... | @@ -2360,21 +2930,21 @@ |
| 2360 | height: 120px; | 2930 | height: 120px; |
| 2361 | } | 2931 | } |
| 2362 | 2932 | ||
| 2933 | + .chart-protagonista .chart-container { | ||
| 2934 | + min-height: 160px; | ||
| 2935 | + max-height: 200px; | ||
| 2936 | + } | ||
| 2937 | + | ||
| 2363 | .kpis { | 2938 | .kpis { |
| 2364 | grid-template-columns: repeat(2, 1fr); | 2939 | grid-template-columns: repeat(2, 1fr); |
| 2365 | } | 2940 | } |
| 2366 | 2941 | ||
| 2367 | .kpi-value { | 2942 | .kpi-value { |
| 2368 | - font-size: 1.125rem; | 2943 | + font-size: 0.75rem; |
| 2369 | - } | ||
| 2370 | - | ||
| 2371 | - .top-summary { | ||
| 2372 | - flex-direction: column; | ||
| 2373 | - gap: 0.25rem; | ||
| 2374 | } | 2944 | } |
| 2375 | 2945 | ||
| 2376 | - .comparador-modal { | 2946 | + .kpi-label { |
| 2377 | - padding: 1.5rem; | 2947 | + font-size: 0.45rem; |
| 2378 | } | 2948 | } |
| 2379 | 2949 | ||
| 2380 | .comparador-selectors { | 2950 | .comparador-selectors { |
| ... | @@ -2386,21 +2956,27 @@ | ... | @@ -2386,21 +2956,27 @@ |
| 2386 | display: none; | 2956 | display: none; |
| 2387 | } | 2957 | } |
| 2388 | 2958 | ||
| 2389 | - .kpi-values { | 2959 | + /* Ranking responsive */ |
| 2390 | - gap: 1rem; | 2960 | + .ranking-title { |
| 2961 | + font-size: 1.25rem; | ||
| 2391 | } | 2962 | } |
| 2392 | 2963 | ||
| 2393 | - .kpi-val { | 2964 | + .ranking-cards { |
| 2394 | - font-size: 0.75rem; | 2965 | + grid-template-columns: 1fr; |
| 2395 | - min-width: 60px; | ||
| 2396 | } | 2966 | } |
| 2397 | 2967 | ||
| 2398 | - .comp-bar { | 2968 | + .ranking-card { |
| 2399 | - width: 12px; | 2969 | + padding: 0.75rem; |
| 2400 | } | 2970 | } |
| 2401 | 2971 | ||
| 2402 | - .grouped-chart { | 2972 | + .ranking-name { |
| 2403 | - gap: 0.5rem; | 2973 | + font-size: 0.8125rem; |
| 2974 | + -webkit-line-clamp: 1; | ||
| 2975 | + } | ||
| 2976 | + | ||
| 2977 | + .ranking-position, | ||
| 2978 | + .ranking-pct { | ||
| 2979 | + font-size: 0.875rem; | ||
| 2404 | } | 2980 | } |
| 2405 | } | 2981 | } |
| 2406 | 2982 | ... | ... |
-
Please register or login to post a comment