From f96428cf3ef085e4273a8ef69295b98a904ac7ec Mon Sep 17 00:00:00 2001 From: wsf Date: Tue, 25 Aug 2026 06:53:49 -0300 Subject: [PATCH] =?UTF-8?q?feat(guias):=20agregar=20resoluci=C3=B3n=20paso?= =?UTF-8?q?=20a=20paso?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: wsf Signed-off-by: wsf --- README.md | 3 +++ prototipo/README.md | 7 +++++ prototipo/ejercicios.html | 2 +- prototipo/guias.css | 2 ++ prototipo/guias.html | 47 ++++++++++++++++++++++++++++++++ prototipo/guias.js | 56 +++++++++++++++++++++++++++++++++++++++ prototipo/index.html | 2 +- 7 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 prototipo/guias.css create mode 100644 prototipo/guias.html create mode 100644 prototipo/guias.js diff --git a/README.md b/README.md index f196f83..6e51ec6 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,9 @@ sap_tfi_2026/ > **Practicar con ejercicios interactivos:** > [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 pantallas que actúan como fronteras (*boundaries*) en los diagramas de robustez. Permite observar cómo los botones, formularios, tablas, mensajes y otros diff --git a/prototipo/README.md b/prototipo/README.md index b7f9aba..84d44bf 100644 --- a/prototipo/README.md +++ b/prototipo/README.md @@ -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 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 Es un prototipo educativo, no una reproducción exacta ni conectada a Odoo. Los diff --git a/prototipo/ejercicios.html b/prototipo/ejercicios.html index 15432bb..246bb5a 100644 --- a/prototipo/ejercicios.html +++ b/prototipo/ejercicios.html @@ -11,7 +11,7 @@
SSAP · TFILaboratorio ICONIX - +
diff --git a/prototipo/guias.css b/prototipo/guias.css new file mode 100644 index 0000000..e8a872c --- /dev/null +++ b/prototipo/guias.css @@ -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}} diff --git a/prototipo/guias.html b/prototipo/guias.html new file mode 100644 index 0000000..07c1e1a --- /dev/null +++ b/prototipo/guias.html @@ -0,0 +1,47 @@ + + + + + + + Guías paso a paso · SAP-TFI-2026 + + + + +
+ SSAP · TFILaboratorio ICONIX + +
+ +
+
+

Práctica acompañada

Resolver diseño, paso a paso

No mires primero la respuesta. Registrá tus decisiones, pedí una pista cuando la necesites y compará tu propuesta recién al final.

+
0 / 6casos completados
+
+ +
+ +
+ +
+

+
+
+
+
+
+ +
+

Método reutilizable para la práctica completa

+

La misma hoja de ruta sirve para los problemas no resueltos

+
1Separar hechos de decisiones2Anticipar cambios3Un secreto por módulo4Diseñar interfaces5Elegir relaciones6Documentar y someter a cambios
+

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.

+
+ + +
+
✓

Compará con la solución modelo

+ + + diff --git a/prototipo/guias.js b/prototipo/guias.js new file mode 100644 index 0000000..39315e4 --- /dev/null +++ b/prototipo/guias.js @@ -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)=>``).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=`Enunciado de trabajo
${c.problem}`;document.querySelector("#roadmap").innerHTML=c.steps.map((x,i)=>`
  • ${state[`${c.id}-${i}`]?.done?"✓":i+1}${x[0]}
  • `).join("");document.querySelector("#step-content").innerHTML=stepIndex<5?`

    ${s[0]}

    ${s[1]}

    Pista: ${s[2]}
    `:`

    ${s[0]}

    ${s[1]}

    Pista: ${s[2]}

    Antes de comparar

    Comprobá que cada módulo tenga un secreto, que las interfaces no revelen implementación y que un cambio afecte pocas unidades.

    `;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`

    ${c.solution.idea}

    ${c.solution.modules.map(m=>`
    ${m[0]}${m[1]}
    `).join("")}

    Relación: ${c.solution.relation}

    Esta es una solución de referencia. Compará los criterios y no sólo los nombres.

    `} +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(); diff --git a/prototipo/index.html b/prototipo/index.html index ab39e5a..7396cc2 100644 --- a/prototipo/index.html +++ b/prototipo/index.html @@ -13,7 +13,7 @@ S SAP · TFILaboratorio ICONIX - +