feat(guias): agregar resolución paso a paso

Co-authored-by: wsf <alejandro.sartorio@gmail.com>
Signed-off-by: wsf <alejandro.sartorio@gmail.com>
This commit is contained in:
wsf
2026-08-25 06:53:49 -03:00
parent 0c55a7ce82
commit f96428cf3e
7 changed files with 117 additions and 2 deletions
+3
View File
@@ -61,6 +61,9 @@ sap_tfi_2026/
> **Practicar con ejercicios interactivos:** > **Practicar con ejercicios interactivos:**
> [https://cursos-uai.github.io/sap_tfi_2026/ejercicios.html](https://cursos-uai.github.io/sap_tfi_2026/ejercicios.html) > [https://cursos-uai.github.io/sap_tfi_2026/ejercicios.html](https://cursos-uai.github.io/sap_tfi_2026/ejercicios.html)
> **Resolver casos con hojas de ruta paso a paso:**
> [https://cursos-uai.github.io/sap_tfi_2026/guias.html](https://cursos-uai.github.io/sap_tfi_2026/guias.html)
El directorio **`prototipo/`** contiene una representación interactiva de las El directorio **`prototipo/`** contiene una representación interactiva de las
pantallas que actúan como fronteras (*boundaries*) en los diagramas de robustez. pantallas que actúan como fronteras (*boundaries*) en los diagramas de robustez.
Permite observar cómo los botones, formularios, tablas, mensajes y otros Permite observar cómo los botones, formularios, tablas, mensajes y otros
+7
View File
@@ -47,6 +47,13 @@ Los ejercicios ofrecen corrección inmediata, explicación de la respuesta y
progreso persistente en el navegador. La base teórica se encuentra citada al progreso persistente en el navegador. La base teórica se encuentra citada al
pie de cada estación. pie de cada estación.
## Guías paso a paso
Abrir `guias.html` para resolver seis casos progresivos con una hoja de ruta:
comprender el problema, anticipar cambios, descomponer, diseñar interfaces,
establecer relaciones y validar el diseño. Cada paso permite registrar el
razonamiento, solicitar una pista y comparar al final con una solución modelo.
## Alcance ## Alcance
Es un prototipo educativo, no una reproducción exacta ni conectada a Odoo. Los Es un prototipo educativo, no una reproducción exacta ni conectada a Odoo. Los
+1 -1
View File
@@ -11,7 +11,7 @@
<body class="lab-body"> <body class="lab-body">
<header class="topbar"> <header class="topbar">
<a class="brand" href="index.html"><span class="brand-mark">S</span><span><strong>SAP · TFI</strong><small>Laboratorio ICONIX</small></span></a> <a class="brand" href="index.html"><span class="brand-mark">S</span><span><strong>SAP · TFI</strong><small>Laboratorio ICONIX</small></span></a>
<nav class="course lab-links"><a href="index.html">Prototipo</a><a class="active" href="ejercicios.html">Ejercicios</a></nav> <nav class="course lab-links"><a href="index.html">Prototipo</a><a class="active" href="ejercicios.html">Ejercicios</a><a href="guias.html">Guías</a></nav>
</header> </header>
<main class="lab-layout"> <main class="lab-layout">
+2
View File
@@ -0,0 +1,2 @@
.guide-body{min-height:100vh}.guide-shell{max-width:1250px;margin:auto;padding:38px clamp(16px,4vw,52px) 60px}.guide-hero{display:flex;justify-content:space-between;align-items:end;gap:25px}.guide-hero h1{font:700 clamp(34px,5vw,58px)/1 Georgia,serif;margin:0}.guide-hero>div:first-child>p:last-child{max-width:760px;color:var(--muted);font-size:17px}.guide-progress{background:#172433;color:white;border-radius:14px;padding:18px 25px;min-width:150px}.guide-progress strong{font:700 27px Georgia,serif;display:block}.guide-progress span{color:#b7c1cb;font-size:12px}.case-picker{display:grid;grid-template-columns:repeat(6,1fr);gap:8px;margin:28px 0}.case-button{border:1px solid var(--line);background:white;border-radius:10px;padding:12px;text-align:left;cursor:pointer;color:var(--ink)}.case-button span{display:block;color:var(--muted);font-size:10px;margin-bottom:3px}.case-button.active{background:#172433;border-color:#172433;color:white}.case-button.done:not(.active){border-color:#5ab492;background:#edfaf5}.case-workspace{display:grid;grid-template-columns:245px 1fr;background:white;border:1px solid var(--line);border-radius:16px;overflow:hidden;box-shadow:var(--shadow)}.roadmap{background:#f5f7f8;padding:25px;border-right:1px solid var(--line)}.roadmap ol{padding:0;margin:0;list-style:none;display:grid;gap:6px}.roadmap li{display:grid;grid-template-columns:25px 1fr;padding:9px;border-radius:8px;color:var(--muted);font-size:12px}.roadmap li i{font-style:normal}.roadmap li.active{background:white;color:var(--blue);font-weight:800}.roadmap li.done{color:#087658}.step-panel{padding:28px min(5vw,55px);min-height:560px}.step-panel>header{display:flex;justify-content:space-between;align-items:start}.step-panel h2{font:700 32px Georgia,serif;margin:0}.step-panel>header>span{background:#edf1f4;padding:7px 10px;border-radius:18px;color:var(--muted);font-size:11px;font-weight:800}.problem-box{background:#172433;color:#d4dde5;border-radius:11px;padding:18px 20px;margin:20px 0}.problem-box b{color:white}.step-question h3{font-size:21px;margin-bottom:5px}.step-question>p{color:var(--muted);margin-top:0}.answer-area{width:100%;min-height:135px;border:1px solid var(--line);border-radius:9px;padding:14px;font:14px/1.5 ui-monospace,monospace;resize:vertical}.checklist{display:grid;gap:8px;margin:16px 0}.checklist label{display:flex;gap:9px;padding:10px;border:1px solid var(--line);border-radius:8px}.hint-box{display:none;background:#fff5df;border-left:4px solid #e19b27;padding:13px 15px;margin-top:13px;color:#704b13}.hint-box.visible{display:block}.step-actions{display:flex;gap:9px;margin-top:18px}.step-actions button,.solution-card button,dialog button{border:1px solid var(--line);background:white;padding:10px 14px;border-radius:8px;font-weight:800;cursor:pointer}.step-actions .primary,dialog button{margin-left:auto;background:var(--blue);border-color:var(--blue);color:white}.solution-card{border:1px solid #bce3d3;background:#effaf6;border-radius:10px;padding:18px}.solution-card h3{margin-top:0}.solution-card ul{padding-left:20px}.method-card{margin:28px 0;background:white;border:1px solid var(--line);border-radius:14px;padding:25px}.method-card h2{font:700 27px Georgia,serif}.method-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}.method-grid span{background:#f5f7f8;padding:13px;border-radius:8px}.method-grid b{display:inline-grid;place-items:center;width:22px;height:22px;background:var(--blue);color:white;border-radius:50%;margin-right:8px;font-size:11px}.guide-sources{display:flex;gap:15px;flex-wrap:wrap;color:var(--muted);font-size:13px}.guide-sources a{color:var(--blue)}dialog{border:0;border-radius:15px;padding:30px;max-width:650px;box-shadow:0 25px 80px rgba(0,0,0,.3)}dialog::backdrop{background:rgba(15,25,35,.68)}.solution-mark{display:grid;place-items:center;width:38px;height:38px;border-radius:50%;background:#dff5eb;color:#087658;font-weight:900}dialog h2{font:700 28px Georgia,serif}.model-modules{display:grid;grid-template-columns:repeat(2,1fr);gap:9px}.model-module{border:1px solid var(--line);border-radius:8px;padding:12px}.model-module strong{display:block;color:var(--blue)}.model-module small{color:var(--muted)}
@media(max-width:900px){.guide-hero{align-items:start}.case-picker{grid-template-columns:repeat(3,1fr)}.case-workspace{grid-template-columns:1fr}.roadmap{border-right:0;border-bottom:1px solid var(--line)}.roadmap ol{display:flex;overflow:auto}.roadmap li{min-width:140px}.method-grid{grid-template-columns:1fr 1fr}}@media(max-width:600px){.guide-shell{padding-top:24px}.guide-hero{display:block}.guide-progress{display:inline-block}.case-picker{grid-template-columns:1fr 1fr}.step-panel{padding:22px 15px}.step-actions{flex-wrap:wrap}.step-actions .primary{margin-left:0}.method-grid,.model-modules{grid-template-columns:1fr}}
+47
View File
@@ -0,0 +1,47 @@
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Hojas de ruta para resolver problemas de diseño de software paso a paso">
<title>Guías paso a paso · SAP-TFI-2026</title>
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="guias.css">
</head>
<body class="guide-body">
<header class="topbar">
<a class="brand" href="index.html"><span class="brand-mark">S</span><span><strong>SAP · TFI</strong><small>Laboratorio ICONIX</small></span></a>
<nav class="course lab-links"><a href="index.html">Prototipo</a><a href="ejercicios.html">Ejercicios</a><a class="active" href="guias.html">Guías</a></nav>
</header>
<main class="guide-shell">
<section class="guide-hero">
<div><p class="eyebrow">Práctica acompañada</p><h1>Resolver diseño, paso a paso</h1><p>No mires primero la respuesta. Registrá tus decisiones, pedí una pista cuando la necesites y compará tu propuesta recién al final.</p></div>
<div class="guide-progress"><strong id="guide-completed">0 / 6</strong><span>casos completados</span></div>
</section>
<section class="case-picker" id="case-picker" aria-label="Casos disponibles"></section>
<section class="case-workspace">
<aside class="roadmap"><p class="eyebrow">Hoja de ruta</p><ol id="roadmap"></ol></aside>
<article class="step-panel">
<header><div><p class="eyebrow" id="case-label"></p><h2 id="case-title"></h2></div><span id="step-counter"></span></header>
<div class="problem-box" id="problem-box"></div>
<div id="step-content"></div>
<div class="step-actions"><button id="previous-step">← Paso anterior</button><button id="hint-button">Ver una pista</button><button id="next-step" class="primary">Guardar y continuar →</button></div>
</article>
</section>
<section class="method-card">
<p class="eyebrow">Método reutilizable para la práctica completa</p>
<h2>La misma hoja de ruta sirve para los problemas no resueltos</h2>
<div class="method-grid"><span><b>1</b>Separar hechos de decisiones</span><span><b>2</b>Anticipar cambios</span><span><b>3</b>Un secreto por módulo</span><span><b>4</b>Diseñar interfaces</span><span><b>5</b>Elegir relaciones</span><span><b>6</b>Documentar y someter a cambios</span></div>
<p>Aplicala a Celda de producción, Museo, Balanza de camiones, Procesamiento de imágenes, Estación climatológica, Exclusa y los demás enunciados de la práctica.</p>
</section>
<footer class="guide-sources"><b>Material de estudio:</b> <a href="https://www.fceia.unr.edu.ar/ingsoft/p-diseno.pdf" target="_blank" rel="noreferrer">Práctica de Diseño ↗</a><a href="https://www.fceia.unr.edu.ar/ingsoft/problemasDiseno.pdf" target="_blank" rel="noreferrer">Problemas resueltos ↗</a></footer>
</main>
<dialog id="solution-dialog"><div class="solution-mark">✓</div><h2>Compará con la solución modelo</h2><div id="solution-content"></div><button id="close-solution">Cerrar</button></dialog>
<script src="guias.js"></script>
</body>
</html>
+56
View File
@@ -0,0 +1,56 @@
const cases=[
{id:"limite",short:"Límite",title:"Controlar extracciones",problem:"Una caja de ahorros ya permite depositar, extraer y consultar saldo. Se agrega la regla: no extraer más de $10.000.",steps:[
["Separá lo estable de lo nuevo","¿Qué responsabilidad ya existe y cuál aparece con el nuevo requisito?","No confundas operaciones básicas de la cuenta con la política de límite."],
["Detectá los ítems de cambio","¿Qué podría variar de forma independiente? Anotá al menos dos cambios futuros.","El monto puede cambiar, desaparecer o incorporar excepciones."],
["Elegí los módulos","¿Qué secreto debería ocultar cada módulo?","Un módulo conserva la cuenta básica y otro la regla de extracción."],
["Diseñá las interfaces","Escribí las operaciones públicas. ¿Conviene que ambos módulos presenten la misma interfaz?","Una interfaz compatible permite sustituir el objeto sin modificar clientes."],
["Definí la relación","¿Quién usa o delega en quién? Representalo con una flecha.","El control envuelve o compone la cuenta y delega las operaciones permitidas."],
["Someté el diseño a cambios","Probá cambiar $10.000 por un límite diario y luego eliminar la regla. ¿Qué módulos tocás?","El diseño es fuerte si CajaAhorros permanece intacta."]],solution:{idea:"Separar la política de la cuenta básica y usar delegación.",modules:[["CajaAhorros","Oculta representación y algoritmos del saldo."],["ControlExtraccion","Oculta la regla del límite y delega en CajaAhorros."]],relation:"ControlExtraccion ◇──▷ CajaAhorros; interfaces compatibles."}},
{id:"form",short:"Formulario",title:"Validar nombre y DNI",problem:"Un formulario recibe nombre y DNI. Al aceptar, el sistema verifica que el DNI exista y corresponda con el nombre usando una fuente de datos.",steps:[
["Marcá las decisiones","¿Qué decisiones distintas hay en interfaz, regla y persistencia?","Pensá qué cambia si reemplazás GUI, regla o base relacional por separado."],
["Listá cambios probables","Proponé una variación para cada decisión.","La presentación puede ser web; la regla puede pedir otro dato; la fuente puede ser una API."],
["Asigná un secreto por módulo","Nombrá tres módulos y escribí el secreto de cada uno.","Frontera, regla de negocio y acceso a datos son tres motivos de cambio."],
["Definí servicios abstractos","Diseñá interfaces sin SQL, widgets ni rutas de archivos.","La regla pregunta por una persona; no debería conocer SELECT ni tablas."],
["Trazá la estructura de uso","Ordená: actor, formulario, control, repositorio.","La dependencia avanza desde la frontera hacia servicios abstractos."],
["Revisá con ICONIX","Mapeá los módulos a Boundary, Control y Entity/Repository.","Formulario=Boundary; validación=Control; acceso a personas=servicio de datos."]],solution:{idea:"Aislar presentación, regla de validación y acceso a datos.",modules:[["FormNombreDNI","Oculta la implementación del formulario."],["ControlarNombreDNI","Oculta la regla de correspondencia."],["RepositorioPersonas","Oculta tecnología y esquema de persistencia."]],relation:"Form → Control → Repositorio; ninguna sentencia SQL en los dos primeros."}},
{id:"cuentas",short:"Cuentas",title:"Modelar tipos de cuenta",problem:"Caja de ahorro y cuenta corriente comparten operaciones, pero la primera no permite saldo negativo y la segunda sí.",steps:[
["Compará comportamiento","Armá una tabla: qué operaciones comparten y cuáles cambian.","depositar y saldo son equivalentes; extraer tiene reglas distintas."],
["Evitá banderas de tipo","Explicá el costo de una clase con tipo=CA/CC y condicionales.","Cada nuevo tipo agrega ramas y mezcla secretos."],
["Separá implementaciones","Proponé una clase para cada variación de comportamiento.","Cada cuenta puede implementar extraer sin preguntar qué tipo es."],
["Extraé una interfaz común","¿Qué abstracción permite tratar todas las cuentas de manera uniforme?","Una CuentaBancaria abstracta declara el contrato compartido."],
["Elegí herencia con criterio","Dibujá generalización y justificá por qué es herencia de interfaz.","La sustitución sirve para clientes como calcularSaldoTotal."],
["Probá extensión","Agregá una cuenta remunerada. ¿Cuántas clases existentes modificás?","El resultado deseable es agregar un heredero sin modificar los anteriores."]],solution:{idea:"Polimorfismo en lugar de una bandera de tipo y condicionales.",modules:[["CuentaBancaria","Interfaz abstracta común."],["CajaAhorros","Regla de extracción con saldo suficiente."],["CuentaCorriente","Permite saldo negativo."]],relation:"CajaAhorros y CuentaCorriente heredan la interfaz CuentaBancaria."}},
{id:"idioma",short:"Idioma",title:"Internacionalizar un formulario",problem:"El título y las etiquetas del formulario Nombre/DNI deben cambiar según el idioma configurado.",steps:[
["Localizá los condicionales","¿Dónde aparecerían sentencias case si resolvieras todo dentro de dibujar()?","Habría ramas para título, etiquetas y botones."],
["Separá motivos de cambio","¿Qué cambia por idioma y qué cambia por tecnología de interfaz?","Traducción y dibujo son decisiones independientes."],
["Proponé una abstracción","¿Qué servicio puede pedir el formulario sin conocer un idioma concreto?","Puede solicitar textos mediante claves o una interfaz de recursos."],
["Elegí implementaciones","Definí al menos dos implementaciones intercambiables.","RecursosES y RecursosEN implementan el mismo contrato."],
["Usá composición","Mostrá cómo recibe el formulario su proveedor de textos.","El proveedor se inyecta al construir el formulario."],
["Aplicá abierto/cerrado","Agregá portugués y contá qué cambia.","Se agrega una implementación y configuración; el formulario no cambia."]],solution:{idea:"Separar recursos lingüísticos de la implementación visual.",modules:[["RecursosFormulario","Interfaz de textos necesarios."],["RecursosES / RecursosEN","Implementaciones por idioma."],["FormNombreDNI","Dibuja usando un proveedor compuesto."]],relation:"FormNombreDNI ◇── RecursosFormulario; implementaciones sustituibles."}},
{id:"reuso",short:"Reuso",title:"Compartir implementación sin mezclar reglas",problem:"CajaAhorros y CuentaCorriente repiten el código de depositar y consultar saldo, pero difieren al extraer.",steps:[
["Identificá repetición","¿Qué código es realmente común y qué parte expresa una regla variable?","La representación del saldo es común; la autorización de extracción difiere."],
["Evaluá si es un ítem de cambio","¿La implementación común podría divergir entre tipos de cuenta?","Si puede divergir, heredar código crea acoplamiento evolutivo."],
["Compará alternativas","Contrastá copiar, heredar implementación y componer un colaborador.","La composición permite delegar sin forzar una jerarquía de implementación."],
["Diseñá el colaborador","Definí operaciones privadas de implementación del saldo.","Un objeto CuentaImp puede mantener y modificar el saldo."],
["Dibujá ambas relaciones","Mostrá generalización de interfaz y composición de implementación.","Son relaciones con propósitos distintos."],
["Documentá lo que UML no muestra","Escribí cómo delega cada operación y qué secreto queda oculto.","El diagrama solo no especifica interfaces, secretos ni delegación."]],solution:{idea:"Combinar herencia de interfaz con composición para compartir implementación.",modules:[["CuentaBancaria","Contrato común."],["CajaAhorros / CuentaCorriente","Reglas específicas."],["CuentaImp","Estado y operaciones básicas del saldo."]],relation:"Las cuentas heredan CuentaBancaria y cada instancia compone su propio CuentaImp."}},
{id:"secretos",short:"Secretos",title:"Un secreto por módulo",problem:"Un formulario localizado mezcla la forma de dibujarse con el idioma de sus textos. Debe poder cambiar cualquiera sin tocar la otra decisión.",steps:[
["Enumerá secretos","¿Cuántas decisiones independientes oculta hoy el formulario?","Al menos dos: presentación y traducción."],
["Refiná la granularidad","¿Conviene un módulo por título o un proveedor para todos los textos? Justificá.","La granularidad debe conservar cohesión sin crear interfaces inmanejables."],
["Abstraé la interfaz","Escribí servicios que no revelen archivos, diccionarios ni variables de entorno.","titulo(), etiquetaNombre() o texto(clave) describen necesidades, no almacenamiento."],
["Encapsulá implementaciones","Anotá qué detalles deben permanecer privados.","Codificación, archivo, locale y cache son internos al proveedor."],
["Configurá en un punto estable","Decidí dónde seleccionar el idioma concreto.","La composición se realiza en el punto de arranque, no dentro del formulario."],
["Cerrá con guía de módulos","Para cada módulo escribí Función, Secreto, Interfaz y Relaciones.","La figura UML comunica relaciones, pero no reemplaza esta documentación."]],solution:{idea:"La calidad surge de explicitar función, secreto, interfaz y delegación.",modules:[["FormNombreDNI","Oculta dibujo e interacción."],["TextosFormulario","Oculta selección y representación de textos."],["Configurador","Elige implementaciones al iniciar."]],relation:"Configurador crea Textos; Form lo recibe por composición y sólo conoce su interfaz."}}
];
const stepNames=["Comprender","Anticipar cambios","Descomponer","Diseñar interfaz","Relacionar","Validar"];
let caseIndex=0,stepIndex=0;
let state=JSON.parse(localStorage.getItem("iconix-guides")||"{}");
function key(){return`${cases[caseIndex].id}-${stepIndex}`}
function save(){localStorage.setItem("iconix-guides",JSON.stringify(state))}
function caseDone(c){return c.steps.every((_,i)=>state[`${c.id}-${i}`]?.done)}
function renderPicker(){document.querySelector("#case-picker").innerHTML=cases.map((c,i)=>`<button class="case-button ${i===caseIndex?"active":""} ${caseDone(c)?"done":""}" data-case="${i}"><span>CASO ${i+1}${caseDone(c)?" · ✓":""}</span>${c.short}</button>`).join("");document.querySelector("#guide-completed").textContent=`${cases.filter(caseDone).length} / ${cases.length}`}
function render(){const c=cases[caseIndex],s=c.steps[stepIndex],entry=state[key()]||{};document.querySelector("#case-label").textContent=`Caso ${caseIndex+1} · ${stepNames[stepIndex]}`;document.querySelector("#case-title").textContent=c.title;document.querySelector("#step-counter").textContent=`PASO ${stepIndex+1} DE 6`;document.querySelector("#problem-box").innerHTML=`<b>Enunciado de trabajo</b><br>${c.problem}`;document.querySelector("#roadmap").innerHTML=c.steps.map((x,i)=>`<li class="${i===stepIndex?"active":""} ${state[`${c.id}-${i}`]?.done?"done":""}" data-step="${i}"><i>${state[`${c.id}-${i}`]?.done?"✓":i+1}</i>${x[0]}</li>`).join("");document.querySelector("#step-content").innerHTML=stepIndex<5?`<div class="step-question"><h3>${s[0]}</h3><p>${s[1]}</p><textarea class="answer-area" id="answer" placeholder="Escribí tu razonamiento antes de pedir una pista…">${entry.answer||""}</textarea><div class="hint-box ${entry.hint?"visible":""}" id="hint-box"><b>Pista:</b> ${s[2]}</div><div class="checklist"><label><input type="checkbox" data-check="reason" ${entry.reason?"checked":""}> Justifiqué la decisión en función de cambios probables.</label><label><input type="checkbox" data-check="terms" ${entry.terms?"checked":""}> Usé vocabulario de diseño: secreto, interfaz, responsabilidad o relación.</label></div></div>`:`<div class="step-question"><h3>${s[0]}</h3><p>${s[1]}</p><textarea class="answer-area" id="answer" placeholder="Registrá el resultado de someter tu diseño a cambios…">${entry.answer||""}</textarea><div class="hint-box ${entry.hint?"visible":""}" id="hint-box"><b>Pista:</b> ${s[2]}</div><div class="solution-card"><h3>Antes de comparar</h3><p>Comprobá que cada módulo tenga un secreto, que las interfaces no revelen implementación y que un cambio afecte pocas unidades.</p><button id="reveal-solution">Comparar con la solución modelo</button></div></div>`;document.querySelector("#previous-step").disabled=stepIndex===0;document.querySelector("#next-step").textContent=stepIndex===5?"Completar caso ✓":"Guardar y continuar →";renderPicker()}
function persist(){const entry=state[key()]||{};entry.answer=document.querySelector("#answer")?.value||"";document.querySelectorAll("[data-check]").forEach(x=>entry[x.dataset.check]=x.checked);state[key()]=entry;save()}
function solutionHTML(c){return`<p>${c.solution.idea}</p><div class="model-modules">${c.solution.modules.map(m=>`<div class="model-module"><strong>${m[0]}</strong><small>${m[1]}</small></div>`).join("")}</div><p><b>Relación:</b> ${c.solution.relation}</p><p><small>Esta es una solución de referencia. Compará los criterios y no sólo los nombres.</small></p>`}
document.querySelector("#case-picker").addEventListener("click",e=>{const b=e.target.closest("[data-case]");if(b){persist();caseIndex=Number(b.dataset.case);stepIndex=0;render()}});document.querySelector("#roadmap").addEventListener("click",e=>{const li=e.target.closest("[data-step]");if(li){persist();stepIndex=Number(li.dataset.step);render()}});document.querySelector("#previous-step").addEventListener("click",()=>{persist();if(stepIndex>0)stepIndex--;render()});document.querySelector("#next-step").addEventListener("click",()=>{persist();state[key()].done=true;save();if(stepIndex<5)stepIndex++;else{document.querySelector("#solution-content").innerHTML=solutionHTML(cases[caseIndex]);document.querySelector("#solution-dialog").showModal()}render()});document.querySelector("#hint-button").addEventListener("click",()=>{const entry=state[key()]||{};entry.hint=true;state[key()]=entry;save();document.querySelector("#hint-box")?.classList.add("visible")});document.querySelector("#step-content").addEventListener("click",e=>{if(e.target.id==="reveal-solution"){persist();document.querySelector("#solution-content").innerHTML=solutionHTML(cases[caseIndex]);document.querySelector("#solution-dialog").showModal()}});document.querySelector("#close-solution").addEventListener("click",()=>document.querySelector("#solution-dialog").close());render();
+1 -1
View File
@@ -13,7 +13,7 @@
<span class="brand-mark">S</span> <span class="brand-mark">S</span>
<span><strong>SAP · TFI</strong><small>Laboratorio ICONIX</small></span> <span><strong>SAP · TFI</strong><small>Laboratorio ICONIX</small></span>
</a> </a>
<div class="course lab-links"><a class="active" href="index.html">Prototipo</a><a href="ejercicios.html">Ejercicios</a></div> <div class="course lab-links"><a class="active" href="index.html">Prototipo</a><a href="ejercicios.html">Ejercicios</a><a href="guias.html">Guías</a></div>
</header> </header>
<main class="workspace"> <main class="workspace">