From 3ac7618047b92820641adaa0b1b07afd5b382625 Mon Sep 17 00:00:00 2001 From: wsf Date: Mon, 24 Aug 2026 22:36:10 -0300 Subject: [PATCH] feat(ejercicios): agregar laboratorio interactivo ICONIX Co-authored-by: wsf Signed-off-by: wsf --- README.md | 3 ++ prototipo/README.md | 15 ++++++++ prototipo/ejercicios.css | 2 + prototipo/ejercicios.html | 34 +++++++++++++++++ prototipo/ejercicios.js | 79 +++++++++++++++++++++++++++++++++++++++ prototipo/index.html | 2 +- prototipo/styles.css | 1 + 7 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 prototipo/ejercicios.css create mode 100644 prototipo/ejercicios.html create mode 100644 prototipo/ejercicios.js diff --git a/README.md b/README.md index c546db4..f196f83 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,9 @@ sap_tfi_2026/ > **Abrir la versión web:** > [https://cursos-uai.github.io/sap_tfi_2026/](https://cursos-uai.github.io/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) + 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 edf2ba7..b7f9aba 100644 --- a/prototipo/README.md +++ b/prototipo/README.md @@ -32,6 +32,21 @@ Luego visitar . | Reserva | CU-ENT-002 | D-ROB-ENT-002 | | Facturación | CU-FAC-001 | D-ROB-FAC-001 | +## Ejercicios interactivos + +Abrir `ejercicios.html` para recorrer seis estaciones de aprendizaje: + +1. fundamentos de diseño para el cambio y ocultamiento de información; +2. modelo de dominio; +3. casos de uso; +4. diagramas de robustez; +5. diagramas de secuencia; +6. diagramas de clases. + +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. + ## Alcance Es un prototipo educativo, no una reproducción exacta ni conectada a Odoo. Los diff --git a/prototipo/ejercicios.css b/prototipo/ejercicios.css new file mode 100644 index 0000000..3ce475e --- /dev/null +++ b/prototipo/ejercicios.css @@ -0,0 +1,2 @@ +.lab-body{min-height:100vh}.lab-layout{display:grid;grid-template-columns:290px minmax(0,1fr);min-height:calc(100vh - 72px)}.lesson-nav{padding:30px 22px;background:#f8f9fa;border-right:1px solid var(--line);display:flex;flex-direction:column}.lesson-nav h2{font:700 24px/1.15 Georgia,serif;margin:0 0 15px}.progress-meter{height:7px;background:#e1e5e8;border-radius:20px;overflow:hidden}.progress-meter i{display:block;width:0;height:100%;background:linear-gradient(90deg,var(--blue),#35a69d);transition:width .35s}.lesson-nav small{color:var(--muted)}#lesson-list{display:grid;gap:7px;margin-top:25px}.lesson-button{border:0;background:transparent;text-align:left;padding:12px;border-radius:10px;cursor:pointer;display:grid;grid-template-columns:28px 1fr;color:var(--ink)}.lesson-button span{display:grid;place-items:center;width:22px;height:22px;border-radius:50%;background:#e5e9ec;color:var(--muted);font-size:11px;font-weight:800}.lesson-button strong{font-size:13px}.lesson-button small{grid-column:2}.lesson-button.active{background:white;color:var(--blue);box-shadow:0 5px 20px rgba(25,34,45,.08)}.lesson-button.done span{background:#d9f3e8;color:#087658}.reset-button{margin-top:auto;border:1px solid var(--line);background:white;padding:10px;border-radius:8px;color:var(--muted);cursor:pointer}.lesson-stage{padding:38px clamp(22px,5vw,70px);max-width:1200px;width:100%;margin:0 auto}.lesson-header{display:flex;align-items:start;justify-content:space-between;margin-bottom:25px}.lesson-header h1{font:700 clamp(30px,4vw,46px)/1.05 Georgia,serif;margin:0 0 10px}.lesson-header>div>p:last-child{max-width:760px;color:var(--muted);margin:0}.score-chip{background:#17202a;color:white;border-radius:22px;padding:8px 13px;font-weight:800;white-space:nowrap}.theory-card{background:#172433;color:white;border-radius:14px;padding:24px;margin-bottom:18px;box-shadow:var(--shadow)}.theory-card p{color:#c3ced8;margin:5px 0 0}.theory-card ul{margin-bottom:0}.concept-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin-bottom:18px}.concept-card{background:white;border:1px solid var(--line);border-radius:11px;padding:18px}.concept-card b{display:block;margin-bottom:6px}.concept-card p{margin:0;color:var(--muted);font-size:13px}.exercise-card{background:white;border:1px solid var(--line);border-radius:14px;padding:24px;margin:14px 0}.exercise-card header{display:flex;justify-content:space-between;gap:15px}.exercise-card h3{margin:0 0 6px;font-size:18px}.exercise-card header p{margin:0;color:var(--muted)}.exercise-type{font-size:10px;text-transform:uppercase;letter-spacing:.08em;color:var(--blue);font-weight:800;white-space:nowrap}.options{display:grid;gap:9px;margin-top:18px}.option{display:flex;align-items:flex-start;gap:10px;border:1px solid var(--line);background:white;padding:12px;border-radius:9px;cursor:pointer;text-align:left;color:var(--ink)}.option:hover{border-color:#9eb9ee}.option.selected{border-color:var(--blue);background:var(--blue-soft)}.option.correct{border-color:#39a77e;background:#e7f7f0}.option.incorrect{border-color:#d7646d;background:#fff0f1}.feedback{display:none;margin-top:14px;padding:12px 14px;border-radius:8px;background:#f1f4f6;color:var(--muted)}.feedback.visible{display:block}.feedback.success{background:#e7f7f0;color:#096b50}.feedback.error{background:#fff0f1;color:#9b3039}.check-button{border:0;background:var(--blue);color:white;padding:10px 15px;border-radius:8px;font-weight:800;cursor:pointer;margin-top:15px}.check-button:disabled{opacity:.45}.sort-list{display:grid;gap:8px;margin-top:16px}.sort-item{display:grid;grid-template-columns:32px 1fr 70px;align-items:center;border:1px solid var(--line);padding:10px;border-radius:8px}.sort-item i{font-style:normal;color:var(--muted)}.sort-controls button{border:0;background:#edf1f4;width:26px;height:26px;margin-left:4px;border-radius:5px;cursor:pointer}.builder{display:grid;grid-template-columns:1fr 1.1fr;gap:16px;margin-top:18px}.palette,.canvas{border:1px solid var(--line);border-radius:10px;padding:15px;min-height:190px}.palette h4,.canvas h4{margin:0 0 10px}.token{display:inline-block;border:1px solid #c9d2da;background:#f8fafb;border-radius:7px;padding:8px 10px;margin:4px;cursor:pointer}.token.active{background:var(--blue-soft);border-color:var(--blue);color:var(--blue)}.uml-preview{display:grid;gap:9px}.uml-box{border:2px solid #33475c;border-radius:5px;background:white}.uml-box strong,.uml-box div{display:block;padding:8px 10px}.uml-box strong{background:#eaf1ff;border-bottom:1px solid #aab7c4;text-align:center}.uml-box div{font-family:ui-monospace,monospace;font-size:12px}.relationship{text-align:center;color:var(--muted);font-family:ui-monospace,monospace}.source-note{font-size:12px;color:var(--muted);border-top:1px solid var(--line);padding-top:15px;margin-top:22px}.source-note a{color:var(--blue)}.lesson-footer{display:flex;justify-content:space-between;margin-top:25px}.lesson-footer button,dialog button{border:1px solid var(--line);background:white;padding:11px 17px;border-radius:8px;font-weight:800;cursor:pointer}.lesson-footer .primary,dialog button{background:var(--blue);border-color:var(--blue);color:white}dialog{border:0;border-radius:16px;padding:34px;text-align:center;box-shadow:0 25px 80px rgba(0,0,0,.25);max-width:420px}dialog::backdrop{background:rgba(15,25,35,.65)}.celebration{font-size:40px;color:#e1a31f}dialog h2{font:700 30px Georgia,serif;margin:5px}dialog p{color:var(--muted)} +@media(max-width:850px){.lab-layout{grid-template-columns:1fr}.lesson-nav{border-right:0;border-bottom:1px solid var(--line);padding:18px}.lesson-nav h2,.lesson-nav>div>small,.reset-button{display:none}#lesson-list{display:flex;overflow:auto;margin-top:14px}.lesson-button{min-width:145px}.lesson-stage{padding:26px 14px}.concept-grid,.builder{grid-template-columns:1fr}.lesson-header{gap:10px}.score-chip{font-size:11px}.exercise-card{padding:18px}} diff --git a/prototipo/ejercicios.html b/prototipo/ejercicios.html new file mode 100644 index 0000000..15432bb --- /dev/null +++ b/prototipo/ejercicios.html @@ -0,0 +1,34 @@ + + + + + + + Ejercicios ICONIX · SAP-TFI-2026 + + + + +
+ SSAP · TFILaboratorio ICONIX + +
+ +
+ + +
+

Fundamentos

Diseñar para el cambio

0 / 0
+
+
+
+
+ +
✦

Recorrido completado

Ya conectaste fundamentos de diseño con los cinco artefactos principales de ICONIX.

+ + + diff --git a/prototipo/ejercicios.js b/prototipo/ejercicios.js new file mode 100644 index 0000000..72cd502 --- /dev/null +++ b/prototipo/ejercicios.js @@ -0,0 +1,79 @@ +const lessons=[ + {id:"fundamentos",kicker:"Fundamentos",title:"Diseñar para el cambio",summary:"Antes de dibujar clases hay que decidir por qué se divide el sistema y qué cambio intenta aislar cada parte.",theory:{title:"Idea central",text:"Diseñar es descomponer el software, asignar funciones y establecer relaciones. El criterio no es producir muchas partes: es reducir el costo de los cambios sin degradar la integridad conceptual.",concepts:[["Cambio probable","Anticipar qué requisito, algoritmo, dato o tecnología puede variar."],["Secreto del módulo","Cada módulo aísla una decisión de diseño con alta probabilidad de cambio."],["Interfaz estable","Los clientes usan servicios abstractos y no dependen de la implementación."]]},exercises:[ + {type:"choice",q:"Una empresa puede cambiar el proveedor de pagos varias veces. ¿Qué decisión sigue mejor a Parnas?",options:["Incluir condiciones del proveedor en Pedido","Aislar la integración tras una interfaz de pagos","Exponer el SDK del proveedor a todas las clases"],answer:1,why:"El proveedor es un ítem de cambio. Una interfaz estable evita propagar ese cambio."}, + {type:"choice",q:"¿Cuál es el objetivo principal de un buen diseño?",options:["Usar la mayor cantidad posible de clases","Minimizar el costo de cambio y preservar la integridad conceptual","Copiar directamente el modelo funcional"],answer:1,why:"La cantidad de clases no es el criterio; importa la descomposición frente al cambio."} + ],source:"Apuntes Una Teoría para el Diseño de Software y Diseño de Software, secciones sobre Diseño para el Cambio y DBOI."}, + {id:"dominio",kicker:"ICONIX · Modelo de dominio",title:"Encontrar conceptos, no soluciones",summary:"El modelo de dominio establece el vocabulario conceptual antes de introducir detalles técnicos.",theory:{title:"Regla de trabajo",text:"Extraé conceptos del problema, sus atributos significativos y relaciones. No confundas entidades del dominio con pantallas, controladores o tablas de una tecnología particular.",concepts:[["Concepto","Objeto reconocible del mundo del problema."],["Atributo","Propiedad significativa del concepto."],["Relación","Vínculo semántico con cardinalidad."]]},exercises:[ + {type:"multi",q:"Seleccioná sólo conceptos del dominio de ventas.",options:["Cliente","PedidoVenta","Botón Confirmar","PostgreSQL","Producto"],answer:[0,1,4],why:"Cliente, PedidoVenta y Producto pertenecen al dominio; el botón y PostgreSQL son detalles de solución."}, + {type:"choice",q:"¿Cuál relación expresa mejor el dominio?",options:["PedidoVenta 1 contiene 1..* LíneaPedido","Form llama a SQL","Controlador importa ORM"],answer:0,why:"La primera relación usa conceptos y cardinalidad sin contaminar el modelo conceptual."} + ],source:"Proceso ICONIX del repositorio: modelo de dominio conceptual y separación entre requerimientos y diseño."}, + {id:"casos",kicker:"ICONIX · Casos de uso",title:"Narrar comportamiento observable",summary:"Los casos de uso conectan objetivos de actores con respuestas del sistema y preparan el análisis de robustez.",theory:{title:"Del objetivo al flujo",text:"Nombrá el caso de uso con un verbo, identificá actor y objetivo, y redactá pasos en voz activa. Usá include para comportamiento obligatorio reutilizado y extend para comportamiento condicional.",concepts:[["Actor","Rol externo que persigue un objetivo."],["include","Subflujo obligatorio compartido."],["extend","Comportamiento opcional bajo condición."]]},exercises:[ + {type:"choice",q:"Enviar el presupuesto por email es opcional. ¿Qué relación corresponde?",options:["include","extend","generalización de actor"],answer:1,why:"El envío aparece sólo si el vendedor decide realizarlo."}, + {type:"sort",q:"Ordená el camino feliz de Crear presupuesto.",items:["El sistema calcula los totales","El vendedor selecciona el cliente","El vendedor guarda el presupuesto","El vendedor agrega productos"],answer:["El vendedor selecciona el cliente","El vendedor agrega productos","El sistema calcula los totales","El vendedor guarda el presupuesto"],why:"El orden conserva causa y efecto y luego podrá trazarse al diagrama de robustez."} + ],source:"Casos de uso CU-VEN-001 y D-CU-GEN-002 del repositorio SAP-TFI-2026."}, + {id:"robustez",kicker:"ICONIX · Robustez",title:"Separar frontera, control y entidad",summary:"El análisis de robustez verifica que el texto del caso de uso pueda convertirse en colaboraciones de objetos.",theory:{title:"Tres responsabilidades",text:"El actor interactúa con fronteras; las fronteras invocan controles; los controles coordinan entidades. Evitá que la interfaz manipule directamente la persistencia o que una entidad asuma decisiones de presentación.",concepts:[["Boundary","Pantalla, formulario, API o mensaje visible."],["Control","Coordinador de un flujo o regla de aplicación."],["Entity","Concepto persistente con estado del dominio."]]},exercises:[ + {type:"classify",q:"Clasificá cada elemento del flujo Confirmar pedido.",items:[["Botón Confirmar","Boundary"],["action_confirm()","Control"],["sale.order","Entity"]],options:["Boundary","Control","Entity"],why:"La clasificación mantiene separadas interacción, coordinación y estado."}, + {type:"choice",q:"¿Qué interacción revela mayor acoplamiento indebido?",options:["Actor → Formulario","Formulario → Control","Formulario → PostgreSQL"],answer:2,why:"La frontera conoce un detalle de persistencia y saltea el control y las entidades."} + ],source:"Diagramas D-ROB del repositorio y principio de bajo acoplamiento del apunte Diseño de Software."}, + {id:"secuencia",kicker:"ICONIX · Secuencia",title:"Asignar mensajes en el tiempo",summary:"El diagrama de secuencia refina el flujo de robustez y asigna operaciones concretas a participantes.",theory:{title:"Trazabilidad temporal",text:"Cada mensaje debe derivarse de un paso del caso de uso y de una interacción de robustez. Las alternativas se representan con condiciones explícitas y las operaciones se asignan al objeto que posee la responsabilidad.",concepts:[["Lifeline","Participante que existe durante la interacción."],["Mensaje","Solicitud de un servicio en orden temporal."],["alt / opt","Variación condicional del flujo."]]},exercises:[ + {type:"sort",q:"Ordená los mensajes de Confirmar pedido.",items:["Stock crea picking","Formulario invoca action_confirm()","Control valida pedido","sale.order cambia a sale"],answer:["Formulario invoca action_confirm()","Control valida pedido","sale.order cambia a sale","Stock crea picking"],why:"Primero se recibe la intención, luego se valida y sólo después se modifican entidades."}, + {type:"choice",q:"Si la validación puede fallar, ¿qué fragmento UML corresponde?",options:["loop","alt / else","par"],answer:1,why:"alt / else muestra caminos mutuamente excluyentes: éxito o error."} + ],source:"Diagramas D-SEC del repositorio y trazabilidad definida por el proceso ICONIX."}, + {id:"clases",kicker:"ICONIX · Diagrama de clases",title:"Construir clases que oculten decisiones",summary:"El modelo de clases asigna datos, operaciones y relaciones sin exponer decisiones que probablemente cambien.",theory:{title:"De Parnas a UML",text:"Una clase no se justifica sólo porque exista un sustantivo: debe reunir una responsabilidad coherente y proteger un secreto. La interfaz pública debe ser pequeña, abstracta e insensible a cambios anticipados.",concepts:[["Alta cohesión","Los elementos internos contribuyen a un único secreto o responsabilidad."],["Bajo acoplamiento","Las clases conocen la menor cantidad necesaria de otras clases."],["Encapsulamiento","Los parámetros y retornos no revelan la representación interna."]]},exercises:[ + {type:"choice",q:"Un catálogo puede pasar de arreglo a base de datos. ¿Cuál interfaz oculta mejor esa decisión?",options:["Producto[] getArray()","Producto buscarPorId(IdProducto id)","Producto get(int posicion)"],answer:1,why:"Buscar por identidad expresa una necesidad del dominio sin exponer arreglo, posición ni capacidad."}, + {type:"builder",q:"Construí un diseño para descuentos variables.",tokens:["Pedido","PoliticaDescuento","BaseDatosDescuento","calcularTotal()","aplicarA(Pedido)","getFila(int)"],answer:["Pedido","PoliticaDescuento","calcularTotal()","aplicarA(Pedido)"],why:"Pedido compone una Política de Descuento mediante servicios del dominio; la tabla y el acceso por fila quedan ocultos."} + ],source:"Diseño de Software, secciones DBOI, interfaz/implementación, abstracción, encapsulamiento, cohesión/acoplamiento y diseño orientado a objetos."} +]; + +let lessonIndex=0; +let progress=JSON.parse(localStorage.getItem("iconix-progress")||"{}"); +const selections={}; + +function saveProgress(){localStorage.setItem("iconix-progress",JSON.stringify(progress))} +function allCorrect(lesson){return lesson.exercises.every((_,i)=>progress[`${lesson.id}-${i}`])} +function renderNav(){ + document.querySelector("#lesson-list").innerHTML=lessons.map((lesson,i)=>``).join(""); + const completed=lessons.filter(allCorrect).length; + document.querySelector("#progress-bar").style.width=`${completed/lessons.length*100}%`; + document.querySelector("#progress-copy").textContent=`${completed} de ${lessons.length} estaciones completadas`; +} +function renderLesson(){ + const lesson=lessons[lessonIndex]; + document.querySelector("#lesson-kicker").textContent=lesson.kicker;document.querySelector("#lesson-title").textContent=lesson.title;document.querySelector("#lesson-summary").textContent=lesson.summary; + document.querySelector("#lesson-content").innerHTML=`

${lesson.theory.title}

${lesson.theory.text}

${lesson.theory.concepts.map(c=>`
${c[0]}

${c[1]}

`).join("")}
${lesson.exercises.map((e,i)=>exerciseHTML(e,i,lesson.id)).join("")}

Base conceptual: ${lesson.source} Fuentes: Introducción al diseño ↗ · Diseño y ocultamiento de información ↗

`; + document.querySelector("#previous-lesson").disabled=lessonIndex===0;document.querySelector("#next-lesson").textContent=lessonIndex===lessons.length-1?"Finalizar ✓":"Siguiente →"; + updateScore();renderNav(); +} +function exerciseHTML(e,i,id){ + const common=`

${i+1}. ${e.q}

${({choice:"Una respuesta",multi:"Selección múltiple",sort:"Ordenar",classify:"Clasificar",builder:"Construir"})[e.type]}
`; + let body=""; + if(e.type==="choice"||e.type==="multi")body=`
${e.options.map((o,n)=>``).join("")}
`; + if(e.type==="sort")body=`
${e.items.map((item,n)=>`
${n+1}${item}
`).join("")}
`; + if(e.type==="classify")body=`
${e.items.map((item,n)=>``).join("")}
`; + if(e.type==="builder")body=`

Elementos disponibles

${e.tokens.map((t,n)=>``).join("")}

Tu diagrama

Elegí clases y operaciones.

`; + return `
${common}${body}
` +} +function updateScore(){const lesson=lessons[lessonIndex];const score=lesson.exercises.filter((_,i)=>progress[`${lesson.id}-${i}`]).length;document.querySelector("#lesson-score").textContent=`${score} / ${lesson.exercises.length}`} +function checkExercise(card){ + const lesson=lessons[lessonIndex],i=Number(card.dataset.exercise),e=lesson.exercises[i];let correct=false; + if(e.type==="choice")correct=Number(card.querySelector(".option.selected")?.dataset.option)===e.answer; + if(e.type==="multi"){const got=[...card.querySelectorAll(".option.selected")].map(x=>Number(x.dataset.option)).sort();correct=JSON.stringify(got)===JSON.stringify(e.answer)} + if(e.type==="sort")correct=JSON.stringify([...card.querySelectorAll(".sort-item")].map(x=>x.dataset.value))===JSON.stringify(e.answer); + if(e.type==="classify")correct=[...card.querySelectorAll("select")].every((x,n)=>x.value===e.items[n][1]); + if(e.type==="builder"){const got=selections[`${lesson.id}-${i}`]||[];correct=e.answer.every(x=>got.includes(x))&&got.every(x=>e.answer.includes(x))} + const feedback=card.querySelector(".feedback");feedback.className=`feedback visible ${correct?"success":"error"}`;feedback.textContent=`${correct?"✓ Correcto.":"Todavía no."} ${e.why}`; + progress[`${lesson.id}-${i}`]=correct;saveProgress();updateScore();renderNav(); +} +document.querySelector("#lesson-content").addEventListener("click",event=>{ + const card=event.target.closest(".exercise-card");if(!card)return; + if(event.target.closest("[data-option]")){const option=event.target.closest("[data-option]");if(card.dataset.type==="choice")card.querySelectorAll(".option").forEach(o=>o.classList.remove("selected"));option.classList.toggle("selected")} + const mover=event.target.closest("[data-move]");if(mover){const row=mover.closest(".sort-item"),sibling=mover.dataset.move==="up"?row.previousElementSibling:row.nextElementSibling;if(sibling)row.parentElement.insertBefore(mover.dataset.move==="up"?row:sibling,mover.dataset.move==="up"?sibling:row)} + const token=event.target.closest("[data-token]");if(token){const key=`${lessons[lessonIndex].id}-${card.dataset.exercise}`,value=lessons[lessonIndex].exercises[card.dataset.exercise].tokens[token.dataset.token];selections[key]=selections[key]||[];selections[key]=selections[key].includes(value)?selections[key].filter(x=>x!==value):[...selections[key],value];token.classList.toggle("active");card.querySelector("[data-preview]").innerHTML=builderPreview(selections[key])} + if(event.target.closest("[data-check]"))checkExercise(card); +}); +function builderPreview(items){const classes=items.filter(x=>!x.includes("(")&&!x.startsWith("Base"));const ops=items.filter(x=>x.includes("("));if(!items.length)return"

Elegí clases y operaciones.

";return classes.map((c,i)=>`
${c}
${ops[i]||"+ interfaz abstracta"}
`).join('
◇── usa ──▷
')} +document.querySelector("#lesson-list").addEventListener("click",e=>{const b=e.target.closest("[data-index]");if(b){lessonIndex=Number(b.dataset.index);renderLesson()}}); +document.querySelector("#previous-lesson").addEventListener("click",()=>{if(lessonIndex>0){lessonIndex--;renderLesson();scrollTo(0,0)}}); +document.querySelector("#next-lesson").addEventListener("click",()=>{if(lessonIndex{progress={};saveProgress();renderLesson()});document.querySelector("#close-dialog").addEventListener("click",()=>document.querySelector("#completion-dialog").close()); +renderLesson(); diff --git a/prototipo/index.html b/prototipo/index.html index 1357a96..ab39e5a 100644 --- a/prototipo/index.html +++ b/prototipo/index.html @@ -13,7 +13,7 @@ S SAP · TFILaboratorio ICONIX -
UAI · IngenieríaPrototipo educativo
+
diff --git a/prototipo/styles.css b/prototipo/styles.css index 57f73d0..170889c 100644 --- a/prototipo/styles.css +++ b/prototipo/styles.css @@ -1,3 +1,4 @@ :root{--ink:#17202a;--muted:#687383;--line:#dfe4e8;--paper:#fff;--canvas:#f2f4f5;--blue:#2864dc;--blue-soft:#eaf1ff;--teal:#087e78;--amber:#b46b07;--red:#b53b45;--shadow:0 18px 55px rgba(25,34,45,.10)} *{box-sizing:border-box}body{margin:0;background:var(--canvas);color:var(--ink);font:15px/1.45 Inter,ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif}.topbar{height:72px;background:#122030;color:white;display:flex;align-items:center;justify-content:space-between;padding:0 30px;box-shadow:0 1px 0 rgba(255,255,255,.1)}.brand{display:flex;gap:12px;align-items:center;color:white;text-decoration:none}.brand-mark{display:grid;place-items:center;width:38px;height:38px;background:#f1b740;color:#17202a;font:bold 20px Georgia;border-radius:11px}.brand span:last-child{display:flex;flex-direction:column}.brand small,.course{color:#aeb9c7;font-size:12px}.course{display:flex;gap:22px}.status-dot:before{content:"";display:inline-block;width:7px;height:7px;border-radius:50%;background:#53d398;margin-right:7px}.workspace{display:grid;grid-template-columns:230px minmax(0,1fr);min-height:calc(100vh - 72px)}.sidebar{padding:30px 18px;background:#f8f9fa;border-right:1px solid var(--line)}.eyebrow{text-transform:uppercase;letter-spacing:.11em;font-size:10px;font-weight:800;color:var(--muted);margin:0 0 12px}.nav-item{width:100%;border:0;background:transparent;text-align:left;padding:12px;border-radius:10px;color:var(--ink);cursor:pointer;display:grid;grid-template-columns:28px 1fr;font-weight:700}.nav-item span{color:#9aa3ad}.nav-item small{grid-column:2;color:var(--muted);font-weight:500}.nav-item.active{background:white;color:var(--blue);box-shadow:0 5px 20px rgba(25,34,45,.08)}.legend{margin-top:36px;padding:16px 12px;border-top:1px solid var(--line);display:grid;gap:10px}.legend p{margin-bottom:2px}.legend span{font-size:12px;color:var(--muted)}.dot{display:inline-block;width:9px;height:9px;border-radius:50%;margin-right:8px}.boundary{background:var(--blue)}.control{background:var(--amber)}.entity{background:var(--teal)}.stage{padding:34px clamp(20px,4vw,60px);max-width:1400px;width:100%;margin:0 auto}.stage-heading{display:flex;align-items:end;justify-content:space-between;margin-bottom:20px}.stage-heading h1{font:700 clamp(28px,3vw,42px)/1.1 Georgia,serif;margin:0}.view-switch{padding:4px;background:#e3e7ea;border-radius:10px}.view-switch button{border:0;padding:8px 14px;border-radius:7px;background:transparent;color:var(--muted);cursor:pointer}.view-switch button.active{background:white;color:var(--ink);box-shadow:0 2px 8px rgba(0,0,0,.08)}.view{display:none}.view.active{display:block}.browser-frame{background:white;border:1px solid #d5dbe0;border-radius:14px;overflow:hidden;box-shadow:var(--shadow)}.browser-bar{height:42px;background:#edf0f2;display:flex;align-items:center;gap:7px;padding:0 14px;border-bottom:1px solid var(--line)}.browser-bar>span{width:9px;height:9px;border-radius:50%;background:#bec5cb}.browser-bar>span:first-child{background:#e2766f}.browser-bar p{margin:auto;color:#89939d;font-size:12px}.app-shell{min-height:520px}.actionbar{height:64px;display:flex;align-items:center;justify-content:space-between;padding:0 26px;border-bottom:1px solid var(--line)}.actionbar button{border:1px solid var(--line);background:white;padding:9px 14px;border-radius:7px;margin-right:8px;font-weight:700;cursor:pointer}.actionbar button.primary{background:var(--blue);border-color:var(--blue);color:white}.record-state{font-size:11px;font-weight:800;letter-spacing:.08em;color:var(--blue);background:var(--blue-soft);padding:7px 10px;border-radius:20px}.screen{display:none;padding:30px}.screen.active{display:block}.form-title{display:flex;align-items:baseline;gap:12px;margin-bottom:25px}.form-title span{font-size:13px;color:var(--muted)}.form-title strong{font:700 26px Georgia,serif}.form-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:18px 26px}.form-grid label{color:var(--muted);font-size:12px;font-weight:700}.form-grid input,.form-grid select{display:block;width:100%;margin-top:6px;padding:10px;border:1px solid var(--line);border-radius:6px;background:white;color:var(--ink)}.tabs{margin-top:30px;border-bottom:1px solid var(--line)}.tabs button{border:0;background:transparent;padding:10px 2px;margin-right:24px;color:var(--muted)}.tabs button.active{color:var(--blue);border-bottom:2px solid var(--blue);font-weight:700}.table-wrap{overflow-x:auto}table{border-collapse:collapse;width:100%;font-size:13px}th{text-align:left;color:var(--muted);font-size:11px;text-transform:uppercase;letter-spacing:.06em}th,td{padding:13px 10px;border-bottom:1px solid var(--line)}.text-button{border:0;background:transparent;color:var(--blue);padding:14px 10px;cursor:pointer}.totals{margin-left:auto;width:min(330px,100%);display:grid;gap:7px;padding-top:12px}.totals span,.totals strong{display:flex;justify-content:space-between}.totals strong{font-size:17px;border-top:1px solid var(--line);padding-top:10px}.summary-card{display:flex;align-items:center;gap:15px;padding:18px;background:#f8fafb;border:1px solid var(--line);border-radius:10px}.summary-card p,.summary-card h2{margin:0}.summary-card p{color:var(--muted);font-size:12px}.summary-card h2{font-size:17px}.summary-card>strong{margin-left:auto}.icon{display:grid;place-items:center;width:42px;height:42px;border-radius:50%;font-weight:800}.success{background:#ddf5ea;color:#14825e}.invoice{background:var(--blue-soft);color:var(--blue)}.validation-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:14px;margin:24px 0}.validation-grid article{border:1px solid var(--line);border-radius:9px;padding:16px}.validation-grid span{display:block;color:var(--muted);font-size:11px;margin-bottom:5px}.callout{border-left:4px solid var(--blue);background:var(--blue-soft);padding:14px 16px}.callout p{margin:3px 0 0;color:var(--muted)}.callout.warning{border-color:#e39a27;background:#fff5df;margin-top:24px}.progress{display:flex;margin:25px 0}.progress span{flex:1;border-top:3px solid var(--line);padding-top:9px;color:var(--muted);font-size:12px}.progress .done,.progress .active{border-color:var(--blue);color:var(--blue);font-weight:700}.pill{padding:5px 8px;border-radius:14px;font-size:11px}.waiting{background:#f0f2f4;color:#687383}.ok{background:#ddf5ea;color:#087658}.partial{background:#fff0cf;color:#94600b}.invoice-options{display:grid;gap:12px;margin:24px 0}.invoice-options label{display:grid;grid-template-columns:22px 1fr;padding:16px;border:1px solid var(--line);border-radius:9px;font-weight:700}.invoice-options input{grid-row:1/3}.invoice-options small{font-weight:400;color:var(--muted)}.model-preview{background:#182433;color:white;border-radius:10px;padding:18px}.model-preview .eyebrow{color:#9eacba}.model-preview div{display:flex;justify-content:space-between;border-top:1px solid rgba(255,255,255,.12);padding:10px 0;font-family:ui-monospace,monospace;font-size:12px}.model-preview b{color:#8fdfc5}.event-log{margin-top:16px;display:grid;grid-template-columns:220px 1fr;gap:25px;padding:17px 20px;background:#17202a;color:white;border-radius:10px}.event-log p{margin:0;color:#b2bdc8}.event-log .eyebrow{color:#7f8d9a;margin-bottom:4px}.trace-intro{display:flex;justify-content:space-between;align-items:center;background:white;padding:18px 22px;border-radius:12px;margin-bottom:15px}.trace-intro p{margin:0}.trace-intro a{color:var(--blue);font-weight:700;text-decoration:none}.trace-grid{display:grid;gap:12px}.trace-row{display:grid;grid-template-columns:1fr 1fr 1fr;background:white;border:1px solid var(--line);border-radius:10px;overflow:hidden}.trace-cell{padding:18px;border-right:1px solid var(--line)}.trace-cell:last-child{border:0}.trace-cell span{display:block;font-size:10px;text-transform:uppercase;letter-spacing:.08em;font-weight:800;margin-bottom:6px}.trace-cell.boundary-cell span{color:var(--blue)}.trace-cell.control-cell span{color:var(--amber)}.trace-cell.entity-cell span{color:var(--teal)}.trace-cell p{margin:0;color:var(--muted);font-size:13px} +.lab-links a{color:#aeb9c7;text-decoration:none;padding:8px 2px}.lab-links a.active{color:#fff;border-bottom:2px solid #f1b740} @media(max-width:850px){.workspace{grid-template-columns:1fr}.sidebar{border:0;border-bottom:1px solid var(--line);padding:16px}.sidebar nav{display:flex;overflow:auto;gap:6px}.nav-item{min-width:150px}.legend{display:none}.stage{padding:24px 14px}.form-grid,.validation-grid{grid-template-columns:1fr}.trace-row{grid-template-columns:1fr}.trace-cell{border-right:0;border-bottom:1px solid var(--line)}.event-log{grid-template-columns:1fr;gap:6px}.course{display:none}}