noodbox commited on
Commit
b4d61a6
·
verified ·
1 Parent(s): 8da9e29

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +432 -59
app.py CHANGED
@@ -1,4 +1,3 @@
1
-
2
  import os
3
  import re
4
  import json
@@ -1267,18 +1266,14 @@ def info_html() -> str:
1267
  <div class="info-card pop-in">
1268
  <h2>Metodología de evaluación y construcción del resultado</h2>
1269
  <p>Esta aplicación está diseñada para que cualquier persona pueda entender de dónde salen los resultados. El sistema evalúa la formulación de un reto a partir de la lógica del marco Cynefin y luego muestra una lectura cuantificada que puede auditarse. El resultado no debe leerse como una verdad absoluta. Debe leerse como una operacionalización transparente de rasgos de complejidad.</p>
1270
-
1271
  <h3>1. Qué es Cynefin y por qué se usa aquí</h3>
1272
  <p>Cynefin es un marco de sentido y decisión desarrollado por Dave Snowden y colaboradores para distinguir contextos simples, complicados, complejos, caóticos y de desorden. Su utilidad central consiste en recordar que no todos los problemas admiten la misma forma de intervención. En contextos complejos no suele existir una respuesta única previa. Lo apropiado es explorar, observar patrones emergentes y ajustar. Ese principio es la base conceptual del evaluador.</p>
1273
-
1274
  <p><b>Referencias centrales</b><br>
1275
  Kurtz, C. F., & Snowden, D. J. 2003. The new dynamics of strategy. IBM Systems Journal, 42(3), 462–483.<br>
1276
  Snowden, D. J., & Boone, M. E. 2007. A Leader’s Framework for Decision Making. Harvard Business Review, 85(11), 68–76.<br>
1277
  Snowden, D. J. 2010. The Cynefin framework and naturalizing sense-making.</p>
1278
-
1279
  <h3>2. Qué intenta medir la aplicación</h3>
1280
  <p>La aplicación no intenta determinar si un reto es “bueno” en términos generales. Intenta estimar qué tan visible es su estructura de complejidad. Para ello usa siete criterios que condensan rasgos frecuentes de los problemas complejos en la literatura de Cynefin y en su uso práctico para diseño de intervención.</p>
1281
-
1282
  <table style="width:100%;border-collapse:collapse;background:white;border:1px solid #e5e7eb;">
1283
  <tr style="background:#eff6ff;">
1284
  <th style="padding:10px;text-align:left;">ID</th>
@@ -1329,10 +1324,8 @@ def info_html() -> str:
1329
  <td style="padding:10px;">Snowden 2010, sense-making adaptativo y patrones emergentes.</td>
1330
  </tr>
1331
  </table>
1332
-
1333
  <h3>3. Flujo metodológico completo</h3>
1334
  <p>La aplicación sigue una secuencia fija de procesamiento. Cada capa cumple una función distinta y puede dejar rastros visibles en la pestaña de diagnóstico.</p>
1335
-
1336
  <table style="width:100%;border-collapse:collapse;background:white;border:1px solid #e5e7eb;">
1337
  <tr style="background:#eff6ff;">
1338
  <th style="padding:10px;text-align:left;">Fase</th>
@@ -1360,13 +1353,10 @@ def info_html() -> str:
1360
  <td style="padding:10px;">Mantiene la app utilizable en escenarios mínimos.</td>
1361
  </tr>
1362
  </table>
1363
-
1364
  <h3>4. Papel del LLM dentro de la metodología</h3>
1365
  <p>El LLM no define la teoría. La teoría la define Cynefin. El LLM solo intenta producir una evaluación estructurada en formato JSON a partir de un prompt que ya está guiado por criterios, ejemplos y restricciones de salida. Si responde bien, su salida se usa. Si no responde, la aplicación cambia a métodos locales controlados. En otras palabras, el LLM es una capa de conveniencia y no la fuente conceptual del sistema.</p>
1366
-
1367
  <h3>5. Papel del motor semántico local</h3>
1368
  <p>El motor local usa un modelo multilingüe de sentence-transformers para representar texto como vectores. Luego compara el reto con dos tipos de referencia. Primero con anclas conceptuales específicas por criterio. Segundo con ejemplos guía completos. Esa doble comparación permite medir afinidad conceptual aunque la redacción del usuario no use exactamente las mismas palabras.</p>
1369
-
1370
  <h3>6. Fórmula del puntaje global</h3>
1371
  <div style="background:white;border:1px solid #e5e7eb;border-radius:20px;padding:16px;margin:14px 0;">
1372
  <div style="font-family:monospace;background:#f8fafc;border-radius:12px;padding:12px;">
@@ -1374,7 +1364,6 @@ def info_html() -> str:
1374
  </div>
1375
  </div>
1376
  <p>La aplicación usa promedio simple para preservar interpretabilidad. Todos los criterios pesan igual en la capa final. Esa decisión es metodológica y busca trazabilidad. Quien lea el resultado puede reproducir el cálculo sin una caja negra adicional.</p>
1377
-
1378
  <h3>7. Fórmula del puntaje por criterio en modo semántico local</h3>
1379
  <div style="background:white;border:1px solid #e5e7eb;border-radius:20px;padding:16px;margin:14px 0;">
1380
  <div style="font-family:monospace;background:#f8fafc;border-radius:12px;padding:12px;">
@@ -1385,7 +1374,6 @@ def info_html() -> str:
1385
  boost_actores)
1386
  </div>
1387
  </div>
1388
-
1389
  <table style="width:100%;border-collapse:collapse;background:white;border:1px solid #e5e7eb;">
1390
  <tr style="background:#eff6ff;">
1391
  <th style="padding:10px;text-align:left;">Componente</th>
@@ -1413,7 +1401,6 @@ def info_html() -> str:
1413
  <td style="padding:10px;">Compensa que la multiplicidad de actores necesita una señal específica.</td>
1414
  </tr>
1415
  </table>
1416
-
1417
  <h3>8. Cómo se transforma la similitud a un score interpretable</h3>
1418
  <div style="background:white;border:1px solid #e5e7eb;border-radius:20px;padding:16px;margin:14px 0;">
1419
  <div style="font-family:monospace;background:#f8fafc;border-radius:12px;padding:12px;">
@@ -1423,14 +1410,12 @@ def info_html() -> str:
1423
  </div>
1424
  </div>
1425
  <p>La similitud semántica cruda no se entrega directamente porque no sería intuitiva para un usuario general. Por eso se reescala a un rango más legible. El límite inferior evita falsos ceros absolutos. El límite superior evita que cualquier parecido moderado dispare máximos artificiales.</p>
1426
-
1427
  <h3>9. Calibración global posterior</h3>
1428
  <p>Después del promedio simple, la aplicación puede aplicar un piso si el reto se parece mucho a los ejemplos guía. Esto existe para corregir un problema frecuente. Algunos retos tienen buena estructura compleja, pero expresan esa estructura con vocabulario distinto y por eso podrían salir subestimados.</p>
1429
  <ul>
1430
  <li>Si la similitud con ejemplos guía es al menos 0.58 y el promedio cae debajo de 72, se eleva a 72.</li>
1431
  <li>Si la similitud con ejemplos guía es al menos 0.68 y el promedio cae debajo de 78, se eleva a 78.</li>
1432
  </ul>
1433
-
1434
  <h3>10. Cómo se interpreta cada rango</h3>
1435
  <table style="width:100%;border-collapse:collapse;background:white;border:1px solid #e5e7eb;">
1436
  <tr style="background:#eff6ff;">
@@ -1459,13 +1444,10 @@ def info_html() -> str:
1459
  <td style="padding:10px;">El texto ofrece poca evidencia para sostener ese criterio.</td>
1460
  </tr>
1461
  </table>
1462
-
1463
- <h3>11. Cómo leer la explicabilidad por criterio</h3>
1464
  <p>Para cada criterio la aplicación puede mostrar cuatro huellas. Términos detectados, fragmentos activados del texto, similitud con anclas y score final. Eso permite reconstruir por qué un criterio pudo salir más alto o más bajo que otro. La app intenta que el número no quede separado de su rastro.</p>
1465
-
1466
  <h3>12. Qué ocurre si el acceso remoto falla</h3>
1467
  <p>Si aparece un error 401 Unauthorized o no llega una respuesta válida desde Hugging Face, la aplicación cambia a motor local. La teoría evaluativa sigue siendo la misma. Lo que cambia es la capa computacional utilizada para producir el número.</p>
1468
-
1469
  <h3>13. Bibliografía básica utilizada en el diseño conceptual</h3>
1470
  <p>Snowden, D. J., & Boone, M. E. 2007. A Leader’s Framework for Decision Making. Harvard Business Review, 85(11), 68–76.<br>
1471
  Kurtz, C. F., & Snowden, D. J. 2003. The new dynamics of strategy. IBM Systems Journal, 42(3), 462–483.<br>
@@ -1482,7 +1464,7 @@ def reset_all():
1482
  [],
1483
  idx,
1484
  stepper_html(idx),
1485
- "", "", "<div></div>", "Sin diagnóstico todavía.", "", "", "", "", "Sin actores agregados todavía.", None, "", "", None, gr.update(value=None, visible=False), "🔴 Estado remoto no verificado",
1486
  *render_step_visibility(idx),
1487
  gr.update(interactive=False),
1488
  gr.update(interactive=False)
@@ -1499,34 +1481,400 @@ def build_app():
1499
  theme = gr.themes.Soft(primary_hue="blue", secondary_hue="slate", neutral_hue="slate")
1500
 
1501
  css = """
1502
- .app-shell {max-width: 1280px; margin: 0 auto;}
1503
- .hero {padding: 10px 4px 18px 4px;}
1504
- .hero h1 {font-size: 32px; font-weight: 800; margin: 0 0 6px 0;}
1505
- .hero p {color: #6b7280; margin: 0;}
1506
- .one-card, .wizard-card, .info-card, .diag-card {border-radius: 28px; border: 1px solid #e5e7eb; padding: 18px; background: #fbfbfd;}
1507
- .stepper {display:flex; gap:10px; flex-wrap:wrap; margin: 8px 0 18px 0;}
1508
- .step {padding:10px 14px; border-radius:999px; border:1px solid #e5e7eb; background:#fff; color:#6b7280; font-weight:700; transition: all .25s ease;}
1509
- .step.active {background:#dbeafe; color:#1d4ed8; border-color:#bfdbfe; transform: translateY(-1px);}
1510
- .step.done {background:#ecfdf5; color:#15803d; border-color:#bbf7d0;}
1511
- .criterion-grid {display:grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap:14px; margin-top:16px;}
1512
- .criterion-card {border-radius:22px; border:1px solid #e5e7eb; background:white; padding:16px; box-shadow: 0 1px 0 rgba(17,24,39,.03);}
1513
- .criterion-head {display:flex; justify-content:space-between; gap:12px; align-items:center;}
1514
- .criterion-id {font-weight:800; color:#2563eb;}
1515
- .criterion-label {margin-top:8px; color:#111827; font-weight:600;}
1516
- .criterion-score {font-size:20px; font-weight:800;}
1517
- .global-score-card {border-radius:24px; background:white; border:1px solid #e5e7eb; padding:18px;}
1518
- .global-score-label {color:#6b7280; font-size:14px;}
1519
- .global-score-value {font-size:34px; font-weight:800; margin-top:4px;}
1520
- .global-score-model {font-size:12px; color:#6b7280; margin-top:4px;}
1521
- .bar {height:10px; border-radius:999px; background:#e5e7eb; overflow:hidden; margin-top:10px;}
1522
- .fill {height:100%; border-radius:999px;}
1523
- .animate-fill {animation: fillin .6s ease;}
1524
- .nav-row {display:flex; gap:12px;}
1525
- .pop-in {animation: popin .28s ease;}
1526
- .tag-pill {display:inline-block; font-size:11px; padding:4px 8px; border-radius:999px; background:#eff6ff; color:#1d4ed8; margin-right:6px; margin-top:8px;}
1527
- .evidence-pills {margin-top:8px;}
1528
- @keyframes fillin {from {width:0;} to {width:100%;}}
1529
- @keyframes popin {from {opacity:0; transform:translateY(6px);} to {opacity:1; transform:translateY(0);}}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1530
  """
1531
 
1532
  with gr.Blocks(theme=theme, css=css, title=APP_NAME) as demo:
@@ -1547,13 +1895,21 @@ def build_app():
1547
  stepper = gr.HTML(stepper_html(0))
1548
 
1549
  with gr.Group(visible=True) as step0:
1550
- remote_status = gr.Markdown("🟡 Verificando disponibilidad del LLM…")
1551
- remote_ping_btn = gr.Button("Volver a probar disponibilidad del LLM")
1552
- reto_input = gr.Textbox(label="Paso 1 Escribe el reto", lines=6, placeholder="Escribe una pregunta de al menos 100 caracteres.")
 
 
 
 
 
 
1553
  gr.Examples(examples=examples, inputs=[reto_input], label="Ejemplos guía")
1554
- with gr.Row():
 
1555
  eval_btn = gr.Button("Evaluar y continuar", variant="primary")
1556
  clear_btn = gr.Button("Reiniciar")
 
1557
  eval_md = gr.Markdown()
1558
  eval_chart = gr.HTML("<div></div>")
1559
  diagnostics_md = gr.Markdown("Sin diagnóstico todavía.")
@@ -1579,10 +1935,11 @@ def build_app():
1579
  with gr.Row():
1580
  actor_name = gr.Textbox(label="Actor", scale=3)
1581
  actor_role = gr.Textbox(label="Rol", scale=3)
1582
- actor_add_btn = gr.Button("Agregar actor")
 
 
1583
  actor_md = gr.Markdown("Sin actores agregados todavía.")
1584
  actor_select_remove = gr.Dropdown(label="Selecciona un actor para eliminar", choices=[], value=None)
1585
- actor_remove_btn = gr.Button("Eliminar actor seleccionado")
1586
 
1587
  with gr.Group(visible=False) as step5:
1588
  gr.Markdown("## Paso 6 • Gobernanza")
@@ -1638,19 +1995,28 @@ def build_app():
1638
  )
1639
 
1640
  ask_def.click(
1641
- fn=lambda reto, d, r, c, g, i, actors: suggest_section_answer(reto, "definicion", {"definicion": d, "relevancia": r, "conexion": c, "gobernanza": g, "iniciativas": i, "actors": actors}),
 
 
 
1642
  inputs=[reto_hidden, definicion, relevancia, conexion, gobernanza, iniciativas, actors_state],
1643
  outputs=[definicion]
1644
  )
1645
 
1646
  ask_rel.click(
1647
- fn=lambda reto, d, r, c, g, i, actors: suggest_section_answer(reto, "relevancia", {"definicion": d, "relevancia": r, "conexion": c, "gobernanza": g, "iniciativas": i, "actors": actors}),
 
 
 
1648
  inputs=[reto_hidden, definicion, relevancia, conexion, gobernanza, iniciativas, actors_state],
1649
  outputs=[relevancia]
1650
  )
1651
 
1652
  ask_con.click(
1653
- fn=lambda reto, d, r, c, g, i, actors: suggest_section_answer(reto, "conexion", {"definicion": d, "relevancia": r, "conexion": c, "gobernanza": g, "iniciativas": i, "actors": actors}),
 
 
 
1654
  inputs=[reto_hidden, definicion, relevancia, conexion, gobernanza, iniciativas, actors_state],
1655
  outputs=[conexion]
1656
  )
@@ -1668,13 +2034,19 @@ def build_app():
1668
  )
1669
 
1670
  ask_gob.click(
1671
- fn=lambda reto, d, r, c, g, i, actors: suggest_section_answer(reto, "gobernanza", {"definicion": d, "relevancia": r, "conexion": c, "gobernanza": g, "iniciativas": i, "actors": actors}),
 
 
 
1672
  inputs=[reto_hidden, definicion, relevancia, conexion, gobernanza, iniciativas, actors_state],
1673
  outputs=[gobernanza]
1674
  )
1675
 
1676
  ask_ini.click(
1677
- fn=lambda reto, d, r, c, g, i, actors: suggest_section_answer(reto, "iniciativas", {"definicion": d, "relevancia": r, "conexion": c, "gobernanza": g, "iniciativas": i, "actors": actors}),
 
 
 
1678
  inputs=[reto_hidden, definicion, relevancia, conexion, gobernanza, iniciativas, actors_state],
1679
  outputs=[iniciativas]
1680
  )
@@ -1705,6 +2077,7 @@ def build_app():
1705
  fn=lambda: "Sin diagnóstico todavía.",
1706
  outputs=[diagnostics_tab_md]
1707
  )
 
1708
  demo.load(fn=remote_status_text, outputs=[remote_status])
1709
 
1710
  return demo
 
 
1
  import os
2
  import re
3
  import json
 
1266
  <div class="info-card pop-in">
1267
  <h2>Metodología de evaluación y construcción del resultado</h2>
1268
  <p>Esta aplicación está diseñada para que cualquier persona pueda entender de dónde salen los resultados. El sistema evalúa la formulación de un reto a partir de la lógica del marco Cynefin y luego muestra una lectura cuantificada que puede auditarse. El resultado no debe leerse como una verdad absoluta. Debe leerse como una operacionalización transparente de rasgos de complejidad.</p>
 
1269
  <h3>1. Qué es Cynefin y por qué se usa aquí</h3>
1270
  <p>Cynefin es un marco de sentido y decisión desarrollado por Dave Snowden y colaboradores para distinguir contextos simples, complicados, complejos, caóticos y de desorden. Su utilidad central consiste en recordar que no todos los problemas admiten la misma forma de intervención. En contextos complejos no suele existir una respuesta única previa. Lo apropiado es explorar, observar patrones emergentes y ajustar. Ese principio es la base conceptual del evaluador.</p>
 
1271
  <p><b>Referencias centrales</b><br>
1272
  Kurtz, C. F., & Snowden, D. J. 2003. The new dynamics of strategy. IBM Systems Journal, 42(3), 462–483.<br>
1273
  Snowden, D. J., & Boone, M. E. 2007. A Leader’s Framework for Decision Making. Harvard Business Review, 85(11), 68–76.<br>
1274
  Snowden, D. J. 2010. The Cynefin framework and naturalizing sense-making.</p>
 
1275
  <h3>2. Qué intenta medir la aplicación</h3>
1276
  <p>La aplicación no intenta determinar si un reto es “bueno” en términos generales. Intenta estimar qué tan visible es su estructura de complejidad. Para ello usa siete criterios que condensan rasgos frecuentes de los problemas complejos en la literatura de Cynefin y en su uso práctico para diseño de intervención.</p>
 
1277
  <table style="width:100%;border-collapse:collapse;background:white;border:1px solid #e5e7eb;">
1278
  <tr style="background:#eff6ff;">
1279
  <th style="padding:10px;text-align:left;">ID</th>
 
1324
  <td style="padding:10px;">Snowden 2010, sense-making adaptativo y patrones emergentes.</td>
1325
  </tr>
1326
  </table>
 
1327
  <h3>3. Flujo metodológico completo</h3>
1328
  <p>La aplicación sigue una secuencia fija de procesamiento. Cada capa cumple una función distinta y puede dejar rastros visibles en la pestaña de diagnóstico.</p>
 
1329
  <table style="width:100%;border-collapse:collapse;background:white;border:1px solid #e5e7eb;">
1330
  <tr style="background:#eff6ff;">
1331
  <th style="padding:10px;text-align:left;">Fase</th>
 
1353
  <td style="padding:10px;">Mantiene la app utilizable en escenarios mínimos.</td>
1354
  </tr>
1355
  </table>
 
1356
  <h3>4. Papel del LLM dentro de la metodología</h3>
1357
  <p>El LLM no define la teoría. La teoría la define Cynefin. El LLM solo intenta producir una evaluación estructurada en formato JSON a partir de un prompt que ya está guiado por criterios, ejemplos y restricciones de salida. Si responde bien, su salida se usa. Si no responde, la aplicación cambia a métodos locales controlados. En otras palabras, el LLM es una capa de conveniencia y no la fuente conceptual del sistema.</p>
 
1358
  <h3>5. Papel del motor semántico local</h3>
1359
  <p>El motor local usa un modelo multilingüe de sentence-transformers para representar texto como vectores. Luego compara el reto con dos tipos de referencia. Primero con anclas conceptuales específicas por criterio. Segundo con ejemplos guía completos. Esa doble comparación permite medir afinidad conceptual aunque la redacción del usuario no use exactamente las mismas palabras.</p>
 
1360
  <h3>6. Fórmula del puntaje global</h3>
1361
  <div style="background:white;border:1px solid #e5e7eb;border-radius:20px;padding:16px;margin:14px 0;">
1362
  <div style="font-family:monospace;background:#f8fafc;border-radius:12px;padding:12px;">
 
1364
  </div>
1365
  </div>
1366
  <p>La aplicación usa promedio simple para preservar interpretabilidad. Todos los criterios pesan igual en la capa final. Esa decisión es metodológica y busca trazabilidad. Quien lea el resultado puede reproducir el cálculo sin una caja negra adicional.</p>
 
1367
  <h3>7. Fórmula del puntaje por criterio en modo semántico local</h3>
1368
  <div style="background:white;border:1px solid #e5e7eb;border-radius:20px;padding:16px;margin:14px 0;">
1369
  <div style="font-family:monospace;background:#f8fafc;border-radius:12px;padding:12px;">
 
1374
  boost_actores)
1375
  </div>
1376
  </div>
 
1377
  <table style="width:100%;border-collapse:collapse;background:white;border:1px solid #e5e7eb;">
1378
  <tr style="background:#eff6ff;">
1379
  <th style="padding:10px;text-align:left;">Componente</th>
 
1401
  <td style="padding:10px;">Compensa que la multiplicidad de actores necesita una señal específica.</td>
1402
  </tr>
1403
  </table>
 
1404
  <h3>8. Cómo se transforma la similitud a un score interpretable</h3>
1405
  <div style="background:white;border:1px solid #e5e7eb;border-radius:20px;padding:16px;margin:14px 0;">
1406
  <div style="font-family:monospace;background:#f8fafc;border-radius:12px;padding:12px;">
 
1410
  </div>
1411
  </div>
1412
  <p>La similitud semántica cruda no se entrega directamente porque no sería intuitiva para un usuario general. Por eso se reescala a un rango más legible. El límite inferior evita falsos ceros absolutos. El límite superior evita que cualquier parecido moderado dispare máximos artificiales.</p>
 
1413
  <h3>9. Calibración global posterior</h3>
1414
  <p>Después del promedio simple, la aplicación puede aplicar un piso si el reto se parece mucho a los ejemplos guía. Esto existe para corregir un problema frecuente. Algunos retos tienen buena estructura compleja, pero expresan esa estructura con vocabulario distinto y por eso podrían salir subestimados.</p>
1415
  <ul>
1416
  <li>Si la similitud con ejemplos guía es al menos 0.58 y el promedio cae debajo de 72, se eleva a 72.</li>
1417
  <li>Si la similitud con ejemplos guía es al menos 0.68 y el promedio cae debajo de 78, se eleva a 78.</li>
1418
  </ul>
 
1419
  <h3>10. Cómo se interpreta cada rango</h3>
1420
  <table style="width:100%;border-collapse:collapse;background:white;border:1px solid #e5e7eb;">
1421
  <tr style="background:#eff6ff;">
 
1444
  <td style="padding:10px;">El texto ofrece poca evidencia para sostener ese criterio.</td>
1445
  </tr>
1446
  </table>
1447
+ <h3>11. Cómo se leer la explicabilidad por criterio</h3>
 
1448
  <p>Para cada criterio la aplicación puede mostrar cuatro huellas. Términos detectados, fragmentos activados del texto, similitud con anclas y score final. Eso permite reconstruir por qué un criterio pudo salir más alto o más bajo que otro. La app intenta que el número no quede separado de su rastro.</p>
 
1449
  <h3>12. Qué ocurre si el acceso remoto falla</h3>
1450
  <p>Si aparece un error 401 Unauthorized o no llega una respuesta válida desde Hugging Face, la aplicación cambia a motor local. La teoría evaluativa sigue siendo la misma. Lo que cambia es la capa computacional utilizada para producir el número.</p>
 
1451
  <h3>13. Bibliografía básica utilizada en el diseño conceptual</h3>
1452
  <p>Snowden, D. J., & Boone, M. E. 2007. A Leader’s Framework for Decision Making. Harvard Business Review, 85(11), 68–76.<br>
1453
  Kurtz, C. F., & Snowden, D. J. 2003. The new dynamics of strategy. IBM Systems Journal, 42(3), 462–483.<br>
 
1464
  [],
1465
  idx,
1466
  stepper_html(idx),
1467
+ "", "", "<div></div>", "Sin diagnóstico todavía.", "", "", "", "", "Sin actores agregados todavía.", None, "", "", None, gr.update(value=None, visible=False), "🟡 Verificando disponibilidad del LLM…",
1468
  *render_step_visibility(idx),
1469
  gr.update(interactive=False),
1470
  gr.update(interactive=False)
 
1481
  theme = gr.themes.Soft(primary_hue="blue", secondary_hue="slate", neutral_hue="slate")
1482
 
1483
  css = """
1484
+ .app-shell {
1485
+ max-width: 1280px;
1486
+ margin: 0 auto;
1487
+ padding-left: 12px;
1488
+ padding-right: 12px;
1489
+ box-sizing: border-box;
1490
+ }
1491
+
1492
+ .hero {
1493
+ padding: 10px 4px 18px 4px;
1494
+ }
1495
+
1496
+ .hero h1 {
1497
+ font-size: 32px;
1498
+ font-weight: 800;
1499
+ margin: 0 0 6px 0;
1500
+ line-height: 1.15;
1501
+ }
1502
+
1503
+ .hero p {
1504
+ color: #6b7280;
1505
+ margin: 0;
1506
+ line-height: 1.45;
1507
+ }
1508
+
1509
+ .one-card, .wizard-card, .info-card, .diag-card {
1510
+ border-radius: 28px;
1511
+ border: 1px solid #e5e7eb;
1512
+ padding: 18px;
1513
+ background: #fbfbfd;
1514
+ box-sizing: border-box;
1515
+ }
1516
+
1517
+ .wizard-card {
1518
+ overflow: hidden;
1519
+ }
1520
+
1521
+ .stepper {
1522
+ display: flex;
1523
+ gap: 10px;
1524
+ flex-wrap: wrap;
1525
+ margin: 8px 0 18px 0;
1526
+ }
1527
+
1528
+ .step {
1529
+ padding: 10px 14px;
1530
+ border-radius: 999px;
1531
+ border: 1px solid #e5e7eb;
1532
+ background: #fff;
1533
+ color: #6b7280;
1534
+ font-weight: 700;
1535
+ transition: all .25s ease;
1536
+ white-space: nowrap;
1537
+ }
1538
+
1539
+ .step.active {
1540
+ background: #dbeafe;
1541
+ color: #1d4ed8;
1542
+ border-color: #bfdbfe;
1543
+ transform: translateY(-1px);
1544
+ }
1545
+
1546
+ .step.done {
1547
+ background: #ecfdf5;
1548
+ color: #15803d;
1549
+ border-color: #bbf7d0;
1550
+ }
1551
+
1552
+ .criterion-grid {
1553
+ display: grid;
1554
+ grid-template-columns: repeat(2, minmax(0, 1fr));
1555
+ gap: 14px;
1556
+ margin-top: 16px;
1557
+ }
1558
+
1559
+ .criterion-card {
1560
+ border-radius: 22px;
1561
+ border: 1px solid #e5e7eb;
1562
+ background: white;
1563
+ padding: 16px;
1564
+ box-shadow: 0 1px 0 rgba(17,24,39,.03);
1565
+ box-sizing: border-box;
1566
+ }
1567
+
1568
+ .criterion-head {
1569
+ display: flex;
1570
+ justify-content: space-between;
1571
+ gap: 12px;
1572
+ align-items: center;
1573
+ }
1574
+
1575
+ .criterion-id {
1576
+ font-weight: 800;
1577
+ color: #2563eb;
1578
+ }
1579
+
1580
+ .criterion-label {
1581
+ margin-top: 8px;
1582
+ color: #111827;
1583
+ font-weight: 600;
1584
+ line-height: 1.35;
1585
+ }
1586
+
1587
+ .criterion-score {
1588
+ font-size: 20px;
1589
+ font-weight: 800;
1590
+ white-space: nowrap;
1591
+ }
1592
+
1593
+ .global-score-card {
1594
+ border-radius: 24px;
1595
+ background: white;
1596
+ border: 1px solid #e5e7eb;
1597
+ padding: 18px;
1598
+ box-sizing: border-box;
1599
+ }
1600
+
1601
+ .global-score-label {
1602
+ color: #6b7280;
1603
+ font-size: 14px;
1604
+ }
1605
+
1606
+ .global-score-value {
1607
+ font-size: 34px;
1608
+ font-weight: 800;
1609
+ margin-top: 4px;
1610
+ line-height: 1;
1611
+ }
1612
+
1613
+ .global-score-model {
1614
+ font-size: 12px;
1615
+ color: #6b7280;
1616
+ margin-top: 6px;
1617
+ line-height: 1.4;
1618
+ }
1619
+
1620
+ .bar {
1621
+ height: 10px;
1622
+ border-radius: 999px;
1623
+ background: #e5e7eb;
1624
+ overflow: hidden;
1625
+ margin-top: 10px;
1626
+ }
1627
+
1628
+ .fill {
1629
+ height: 100%;
1630
+ border-radius: 999px;
1631
+ }
1632
+
1633
+ .animate-fill {
1634
+ animation: fillin .6s ease;
1635
+ }
1636
+
1637
+ .nav-row {
1638
+ display: flex;
1639
+ gap: 12px;
1640
+ margin-top: 8px;
1641
+ }
1642
+
1643
+ .pop-in {
1644
+ animation: popin .28s ease;
1645
+ }
1646
+
1647
+ .tag-pill {
1648
+ display: inline-block;
1649
+ font-size: 11px;
1650
+ padding: 4px 8px;
1651
+ border-radius: 999px;
1652
+ background: #eff6ff;
1653
+ color: #1d4ed8;
1654
+ margin-right: 6px;
1655
+ margin-top: 8px;
1656
+ }
1657
+
1658
+ .evidence-pills {
1659
+ margin-top: 8px;
1660
+ }
1661
+
1662
+ .llm-status-wrap {
1663
+ display: flex;
1664
+ flex-direction: column;
1665
+ gap: 10px;
1666
+ margin-bottom: 10px;
1667
+ }
1668
+
1669
+ .llm-status-wrap .gr-button,
1670
+ .primary-action-row .gr-button,
1671
+ .nav-row .gr-button,
1672
+ .actor-actions-mobile .gr-button {
1673
+ min-height: 48px;
1674
+ }
1675
+
1676
+ .primary-action-row {
1677
+ display: flex;
1678
+ gap: 12px;
1679
+ flex-wrap: wrap;
1680
+ }
1681
+
1682
+ .actor-actions-mobile {
1683
+ display: flex;
1684
+ gap: 10px;
1685
+ flex-wrap: wrap;
1686
+ }
1687
+
1688
+ .gradio-container .gr-box,
1689
+ .gradio-container .gr-panel {
1690
+ box-sizing: border-box;
1691
+ }
1692
+
1693
+ .gradio-container textarea,
1694
+ .gradio-container input,
1695
+ .gradio-container select {
1696
+ font-size: 16px !important;
1697
+ }
1698
+
1699
+ @keyframes fillin {
1700
+ from {width: 0;}
1701
+ to {width: 100%;}
1702
+ }
1703
+
1704
+ @keyframes popin {
1705
+ from {opacity: 0; transform: translateY(6px);}
1706
+ to {opacity: 1; transform: translateY(0);}
1707
+ }
1708
+
1709
+ @media (max-width: 1024px) {
1710
+ .criterion-grid {
1711
+ grid-template-columns: 1fr;
1712
+ }
1713
+ }
1714
+
1715
+ @media (max-width: 768px) {
1716
+ .app-shell {
1717
+ max-width: 100%;
1718
+ padding-left: 10px;
1719
+ padding-right: 10px;
1720
+ }
1721
+
1722
+ .hero {
1723
+ padding: 6px 2px 14px 2px;
1724
+ }
1725
+
1726
+ .hero h1 {
1727
+ font-size: 25px;
1728
+ line-height: 1.15;
1729
+ margin-bottom: 8px;
1730
+ }
1731
+
1732
+ .hero p {
1733
+ font-size: 14px;
1734
+ line-height: 1.45;
1735
+ }
1736
+
1737
+ .one-card, .wizard-card, .info-card, .diag-card {
1738
+ padding: 12px;
1739
+ border-radius: 20px;
1740
+ }
1741
+
1742
+ .stepper {
1743
+ gap: 8px;
1744
+ margin: 6px 0 14px 0;
1745
+ overflow-x: auto;
1746
+ flex-wrap: nowrap;
1747
+ padding-bottom: 4px;
1748
+ scrollbar-width: thin;
1749
+ }
1750
+
1751
+ .step {
1752
+ padding: 9px 12px;
1753
+ font-size: 13px;
1754
+ flex: 0 0 auto;
1755
+ }
1756
+
1757
+ .global-score-card,
1758
+ .criterion-card {
1759
+ border-radius: 18px;
1760
+ padding: 14px;
1761
+ }
1762
+
1763
+ .global-score-value {
1764
+ font-size: 30px;
1765
+ }
1766
+
1767
+ .criterion-score {
1768
+ font-size: 18px;
1769
+ }
1770
+
1771
+ .criterion-label {
1772
+ font-size: 14px;
1773
+ }
1774
+
1775
+ .primary-action-row,
1776
+ .nav-row,
1777
+ .actor-actions-mobile {
1778
+ flex-direction: column;
1779
+ gap: 10px;
1780
+ }
1781
+
1782
+ .primary-action-row > *,
1783
+ .nav-row > *,
1784
+ .actor-actions-mobile > * {
1785
+ width: 100%;
1786
+ }
1787
+
1788
+ .llm-status-wrap {
1789
+ gap: 8px;
1790
+ margin-bottom: 8px;
1791
+ }
1792
+
1793
+ .llm-status-wrap .gr-button,
1794
+ .primary-action-row .gr-button,
1795
+ .nav-row .gr-button,
1796
+ .actor-actions-mobile .gr-button {
1797
+ width: 100%;
1798
+ }
1799
+
1800
+ .evidence-pills {
1801
+ display: flex;
1802
+ flex-wrap: wrap;
1803
+ gap: 6px;
1804
+ }
1805
+
1806
+ .tag-pill {
1807
+ margin-right: 0;
1808
+ margin-top: 0;
1809
+ }
1810
+
1811
+ .gradio-container .gr-markdown table {
1812
+ display: block;
1813
+ overflow-x: auto;
1814
+ white-space: nowrap;
1815
+ }
1816
+ }
1817
+
1818
+ @media (max-width: 520px) {
1819
+ .app-shell {
1820
+ padding-left: 8px;
1821
+ padding-right: 8px;
1822
+ }
1823
+
1824
+ .hero h1 {
1825
+ font-size: 22px;
1826
+ }
1827
+
1828
+ .hero p {
1829
+ font-size: 13px;
1830
+ }
1831
+
1832
+ .one-card, .wizard-card, .info-card, .diag-card {
1833
+ padding: 10px;
1834
+ border-radius: 16px;
1835
+ }
1836
+
1837
+ .step {
1838
+ padding: 8px 11px;
1839
+ font-size: 12px;
1840
+ }
1841
+
1842
+ .global-score-card,
1843
+ .criterion-card {
1844
+ padding: 12px;
1845
+ border-radius: 16px;
1846
+ }
1847
+
1848
+ .global-score-value {
1849
+ font-size: 28px;
1850
+ }
1851
+
1852
+ .global-score-model {
1853
+ font-size: 11px;
1854
+ }
1855
+
1856
+ .criterion-head {
1857
+ gap: 8px;
1858
+ }
1859
+
1860
+ .criterion-score {
1861
+ font-size: 17px;
1862
+ }
1863
+
1864
+ .criterion-label {
1865
+ font-size: 13px;
1866
+ }
1867
+
1868
+ .gradio-container .gr-button {
1869
+ font-size: 15px !important;
1870
+ }
1871
+
1872
+ .gradio-container .gr-form,
1873
+ .gradio-container .gr-box,
1874
+ .gradio-container .gr-panel {
1875
+ min-width: 0 !important;
1876
+ }
1877
+ }
1878
  """
1879
 
1880
  with gr.Blocks(theme=theme, css=css, title=APP_NAME) as demo:
 
1895
  stepper = gr.HTML(stepper_html(0))
1896
 
1897
  with gr.Group(visible=True) as step0:
1898
+ with gr.Column(elem_classes=["llm-status-wrap"]):
1899
+ remote_status = gr.Markdown("🟡 Verificando disponibilidad del LLM")
1900
+ remote_ping_btn = gr.Button("Volver a probar disponibilidad del LLM")
1901
+
1902
+ reto_input = gr.Textbox(
1903
+ label="Paso 1 • Escribe el reto",
1904
+ lines=6,
1905
+ placeholder="Escribe una pregunta de al menos 100 caracteres."
1906
+ )
1907
  gr.Examples(examples=examples, inputs=[reto_input], label="Ejemplos guía")
1908
+
1909
+ with gr.Row(elem_classes=["primary-action-row"]):
1910
  eval_btn = gr.Button("Evaluar y continuar", variant="primary")
1911
  clear_btn = gr.Button("Reiniciar")
1912
+
1913
  eval_md = gr.Markdown()
1914
  eval_chart = gr.HTML("<div></div>")
1915
  diagnostics_md = gr.Markdown("Sin diagnóstico todavía.")
 
1935
  with gr.Row():
1936
  actor_name = gr.Textbox(label="Actor", scale=3)
1937
  actor_role = gr.Textbox(label="Rol", scale=3)
1938
+ with gr.Row(elem_classes=["actor-actions-mobile"]):
1939
+ actor_add_btn = gr.Button("Agregar actor")
1940
+ actor_remove_btn = gr.Button("Eliminar actor seleccionado")
1941
  actor_md = gr.Markdown("Sin actores agregados todavía.")
1942
  actor_select_remove = gr.Dropdown(label="Selecciona un actor para eliminar", choices=[], value=None)
 
1943
 
1944
  with gr.Group(visible=False) as step5:
1945
  gr.Markdown("## Paso 6 • Gobernanza")
 
1995
  )
1996
 
1997
  ask_def.click(
1998
+ fn=lambda reto, d, r, c, g, i, actors: suggest_section_answer(
1999
+ reto, "definicion",
2000
+ {"definicion": d, "relevancia": r, "conexion": c, "gobernanza": g, "iniciativas": i, "actors": actors}
2001
+ ),
2002
  inputs=[reto_hidden, definicion, relevancia, conexion, gobernanza, iniciativas, actors_state],
2003
  outputs=[definicion]
2004
  )
2005
 
2006
  ask_rel.click(
2007
+ fn=lambda reto, d, r, c, g, i, actors: suggest_section_answer(
2008
+ reto, "relevancia",
2009
+ {"definicion": d, "relevancia": r, "conexion": c, "gobernanza": g, "iniciativas": i, "actors": actors}
2010
+ ),
2011
  inputs=[reto_hidden, definicion, relevancia, conexion, gobernanza, iniciativas, actors_state],
2012
  outputs=[relevancia]
2013
  )
2014
 
2015
  ask_con.click(
2016
+ fn=lambda reto, d, r, c, g, i, actors: suggest_section_answer(
2017
+ reto, "conexion",
2018
+ {"definicion": d, "relevancia": r, "conexion": c, "gobernanza": g, "iniciativas": i, "actors": actors}
2019
+ ),
2020
  inputs=[reto_hidden, definicion, relevancia, conexion, gobernanza, iniciativas, actors_state],
2021
  outputs=[conexion]
2022
  )
 
2034
  )
2035
 
2036
  ask_gob.click(
2037
+ fn=lambda reto, d, r, c, g, i, actors: suggest_section_answer(
2038
+ reto, "gobernanza",
2039
+ {"definicion": d, "relevancia": r, "conexion": c, "gobernanza": g, "iniciativas": i, "actors": actors}
2040
+ ),
2041
  inputs=[reto_hidden, definicion, relevancia, conexion, gobernanza, iniciativas, actors_state],
2042
  outputs=[gobernanza]
2043
  )
2044
 
2045
  ask_ini.click(
2046
+ fn=lambda reto, d, r, c, g, i, actors: suggest_section_answer(
2047
+ reto, "iniciativas",
2048
+ {"definicion": d, "relevancia": r, "conexion": c, "gobernanza": g, "iniciativas": i, "actors": actors}
2049
+ ),
2050
  inputs=[reto_hidden, definicion, relevancia, conexion, gobernanza, iniciativas, actors_state],
2051
  outputs=[iniciativas]
2052
  )
 
2077
  fn=lambda: "Sin diagnóstico todavía.",
2078
  outputs=[diagnostics_tab_md]
2079
  )
2080
+
2081
  demo.load(fn=remote_status_text, outputs=[remote_status])
2082
 
2083
  return demo