|
|
|
@@ -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();
|