From 19f0883a70b2909eeaa6c71a7e619f5ab3729bc9 Mon Sep 17 00:00:00 2001 From: wsf Date: Tue, 25 Aug 2026 15:43:17 -0300 Subject: [PATCH] feat: generar informe markdown desde checklist Co-authored-by: wsf Signed-off-by: wsf --- README.md | 2 +- prototipo/README.md | 5 ++++- prototipo/checklist.css | 1 + prototipo/checklist.html | 5 ++++- prototipo/checklist.js | 28 ++++++++++++++++++++-------- 5 files changed, 30 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 24197b4..4c8945c 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ sap_tfi_2026/ | **Prototipo ICONIX** | Explorar pantallas, acciones y trazabilidad `Boundary → Control → Entity`. | [Abrir prototipo](https://cursos-uai.github.io/sap_tfi_2026/) | | **Ejercicios interactivos** | Practicar fundamentos de diseño y los diagramas principales de ICONIX. | [Abrir ejercicios](https://cursos-uai.github.io/sap_tfi_2026/ejercicios.html) | | **Guías paso a paso** | Resolver casos de diseño con una hoja de ruta, pistas y soluciones modelo. | [Abrir guías](https://cursos-uai.github.io/sap_tfi_2026/guias.html) | -| **Checklist del TP** | Controlar los 59 criterios, registrar evidencias y verificar la trazabilidad de la entrega. | [Abrir checklist](https://cursos-uai.github.io/sap_tfi_2026/checklist.html) | +| **Checklist del TP** | Referenciar evidencias del borrador, generar el informe `.md` y preparar su envío al profesor. | [Abrir checklist](https://cursos-uai.github.io/sap_tfi_2026/checklist.html) | Se recomienda comenzar por las **guías paso a paso** durante la elaboración del trabajo y utilizar la **checklist del TP** antes de cada revisión y de la diff --git a/prototipo/README.md b/prototipo/README.md index ee54f60..4d4fe1b 100644 --- a/prototipo/README.md +++ b/prototipo/README.md @@ -59,7 +59,10 @@ razonamiento, solicitar una pista y comparar al final con una solución modelo. Abrir `checklist.html` para controlar los diez grupos de entregables del TP: requerimientos, casos de uso, dominio, prototipo, arquitectura, robustez, secuencia, clases, patrones y testing estructural. Cada criterio admite una -evidencia y el progreso queda guardado localmente en el navegador. +referencia precisa al borrador —página, sección, tabla, figura, párrafo u otra— +y el progreso queda guardado localmente en el navegador. Al finalizar se puede +generar un informe `.md`, descargarlo y preparar el correo de seguimiento para +el profesor Alejandro Sartorio. ## Alcance diff --git a/prototipo/checklist.css b/prototipo/checklist.css index 1176cef..088381d 100644 --- a/prototipo/checklist.css +++ b/prototipo/checklist.css @@ -1 +1,2 @@ :root{--ci:#17233b;--cm:#667085;--cl:#dce3ee;--ct:#087f73;--cs:#eef8f6;--cy:#ffd65a}.checklist-body{background:#f4f7fb;color:var(--ci);min-height:100vh}.checklist-shell{max-width:1240px;margin:auto;padding:46px 28px 70px}.checklist-hero{display:flex;justify-content:space-between;gap:32px;align-items:center;padding:38px 42px;color:#fff;border-radius:28px;background:linear-gradient(125deg,#14203a,#173b54 58%,#087f73 140%);box-shadow:0 20px 60px #15233b20}.checklist-hero h1{max-width:760px;margin:6px 0 12px;font-size:clamp(2rem,4vw,3.7rem);line-height:1.02;letter-spacing:-.045em}.checklist-hero p:not(.eyebrow){max-width:680px;margin:0;color:#d9e6ee}.hero-score{width:166px;aspect-ratio:1;flex:none;display:grid;place-content:center;text-align:center;border:1px solid #ffffff42;border-radius:50%;background:#ffffff10}.hero-score strong{font-size:2.45rem;line-height:1;color:var(--cy)}.hero-score span{font-size:.78rem;margin-top:8px;color:#d9e6ee}.quality-gates{display:flex;align-items:center;justify-content:center;gap:14px;margin:24px 0}.quality-gates article{display:flex;align-items:center;gap:10px;min-width:170px;padding:13px 15px;border:1px solid var(--cl);border-radius:14px;background:#fff}.quality-gates article>span{display:grid;place-content:center;width:30px;height:30px;border-radius:9px;background:var(--cs);color:var(--ct);font-weight:800}.quality-gates b,.quality-gates small{display:block}.quality-gates small{color:var(--cm);font-size:.7rem}.quality-gates>i{color:#98a2b3}.toolbar{position:sticky;z-index:5;top:0;display:flex;gap:10px;align-items:center;padding:14px;margin-bottom:22px;border:1px solid var(--cl);border-radius:16px;background:#ffffffed;backdrop-filter:blur(12px)}.search-box{display:flex;align-items:center;gap:8px;flex:1;padding:0 13px;border:1px solid var(--cl);border-radius:10px;background:#fff}.search-box input{width:100%;padding:10px 0;border:0;outline:0;background:transparent}.filters{display:flex;padding:3px;background:#eef2f7;border-radius:10px}.filters button,.quiet-button{border:0;border-radius:8px;padding:9px 12px;background:transparent;color:var(--cm);cursor:pointer}.filters button.active{background:#fff;color:var(--ci);box-shadow:0 2px 8px #15233b14}.quiet-button{border:1px solid var(--cl);background:#fff}.danger{color:#b42318}.checklist-layout{display:grid;grid-template-columns:245px minmax(0,1fr);gap:24px;align-items:start}.section-index{position:sticky;top:86px;padding:20px;border:1px solid var(--cl);border-radius:18px;background:#fff}.section-index nav{display:grid;gap:4px;margin-top:10px}.section-index a{display:flex;justify-content:space-between;padding:9px 10px;border-radius:9px;color:var(--cm);font-size:.86rem;text-decoration:none}.section-index a:hover{background:#f2f6fa}.count{color:var(--ct)}.evidence-key{margin-top:16px;padding:14px;border-radius:12px;background:#fff8df;font-size:.8rem}.evidence-key p{margin:5px 0 0;color:#695b2d;line-height:1.4}.checklist-sections{display:grid;gap:15px}.check-section{overflow:hidden;border:1px solid var(--cl);border-radius:18px;background:#fff;box-shadow:0 8px 24px #17233b0a}.check-section[hidden]{display:none}.section-head{width:100%;display:grid;grid-template-columns:44px 1fr auto 24px;gap:13px;align-items:center;padding:20px;border:0;background:#fff;text-align:left;cursor:pointer;color:var(--ci)}.section-number{display:grid;place-content:center;width:42px;height:42px;border-radius:12px;background:var(--cs);color:var(--ct);font-weight:850}.section-head h2{margin:0;font-size:1.12rem}.section-head p{margin:4px 0 0;color:var(--cm);font-size:.82rem}.section-progress{text-align:right}.section-progress b,.section-progress small{display:block}.section-progress b{color:var(--ct)}.section-progress small{color:var(--cm);font-size:.7rem}.chevron{transition:.2s}.check-section.open .chevron{transform:rotate(180deg)}.section-body{display:none;border-top:1px solid var(--cl)}.check-section.open .section-body{display:block}.check-item{display:grid;grid-template-columns:28px minmax(0,1fr);gap:12px;padding:16px 20px;border-bottom:1px solid #edf1f6}.check-item[hidden]{display:none}.check-item input[type=checkbox]{appearance:none;width:23px;height:23px;margin:1px 0;border:2px solid #b7c1cf;border-radius:7px;cursor:pointer}.check-item input[type=checkbox]:checked{border-color:var(--ct);background:var(--ct) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='white' stroke-width='3' d='m5 12 4 4 10-10'/%3E%3C/svg%3E") center/17px}.item-copy label{display:block;font-weight:680;line-height:1.35;cursor:pointer}.item-copy p{margin:4px 0;color:var(--cm);font-size:.82rem;line-height:1.45}.evidence-input{width:100%;box-sizing:border-box;margin-top:7px;padding:9px 11px;border:1px dashed #bcc7d5;border-radius:9px;background:#fafbfd}.check-item.done{background:#f7fbfa}.check-item.done label{text-decoration:line-through;color:#5d736f}.empty-state{padding:36px;text-align:center;color:var(--cm)}.traceability-card{display:flex;justify-content:space-between;align-items:center;gap:30px;margin-top:24px;padding:28px;border-radius:20px;background:#17233b;color:#fff}.traceability-card h2{margin:5px 0}.traceability-card p{margin:0;color:#cdd7e3}.trace-chain{display:flex;align-items:center;gap:8px;flex-wrap:wrap;justify-content:center}.trace-chain span{padding:9px 10px;border-radius:9px;background:#ffffff12;border:1px solid #ffffff20;color:var(--cy);font-weight:800;font-size:.77rem}.trace-chain i{color:#8db6b0}.checklist-sources{display:flex;flex-wrap:wrap;gap:12px 18px;margin-top:20px;padding:20px;border:1px solid var(--cl);border-radius:16px;background:#fff;font-size:.84rem}.checklist-sources a{color:var(--ct)}@media(max-width:900px){.quality-gates{display:grid;grid-template-columns:1fr 1fr}.quality-gates>i{display:none}.checklist-layout{grid-template-columns:1fr}.section-index{position:static}.section-index nav{grid-template-columns:1fr 1fr}.toolbar{flex-wrap:wrap}.search-box{flex-basis:100%}.traceability-card{display:block}.trace-chain{margin-top:20px}}@media(max-width:620px){.checklist-shell{padding:24px 14px}.checklist-hero{display:block;padding:28px 22px}.hero-score{width:auto;aspect-ratio:auto;margin-top:24px;padding:18px;border-radius:16px}.quality-gates,.section-index nav{grid-template-columns:1fr}.section-head{grid-template-columns:38px 1fr 20px;padding:16px}.section-progress{display:none}.trace-chain i{display:none}} +.step-badge{display:inline-block;padding:5px 9px;border-radius:999px;background:#dff3ef;color:var(--ct);font-size:.72rem;font-weight:850;text-transform:uppercase;letter-spacing:.05em}.project-card{display:grid;grid-template-columns:auto 260px 1fr;gap:20px;align-items:start;margin:24px 0;padding:24px;border:1px solid var(--cl);border-radius:20px;background:#fff}.project-intro h2,.submission-card h2{margin:4px 0 6px}.project-intro p:not(.eyebrow),.submission-card p:not(.eyebrow){margin:0;color:var(--cm);line-height:1.45}.project-fields{display:grid;grid-template-columns:1fr 1fr;gap:13px}.project-fields label{font-size:.78rem;font-weight:750;color:#475467}.project-fields label.wide{grid-column:1/-1}.project-fields input{width:100%;box-sizing:border-box;margin-top:6px;padding:11px 12px;border:1px solid #bfc9d7;border-radius:9px;color:var(--ci)}.project-fields small{display:block;margin-top:5px;color:var(--cm);font-weight:400}.evidence-fields{display:grid;grid-template-columns:180px 1fr;gap:8px;margin-top:10px}.evidence-type,.evidence-location{width:100%;box-sizing:border-box;padding:9px 11px;border:1px solid #bcc7d5;border-radius:9px;background:#fafbfd;color:var(--ci)}.check-item.invalid{background:#fff7f5}.check-item.invalid .evidence-type,.check-item.invalid .evidence-location{border-color:#e0796f}.reference-help{display:block;margin-top:5px;color:var(--cm);font-size:.72rem}.submission-card{display:grid;grid-template-columns:1fr 430px;gap:30px;align-items:center;margin-top:24px;padding:30px;border:1px solid #b8d8d2;border-radius:22px;background:linear-gradient(125deg,#f4fbfa,#fff)}.submission-actions{display:grid;grid-template-columns:1fr 1fr;gap:10px}.submission-actions button{padding:12px;border-radius:10px;cursor:pointer;font-weight:750}.primary-action{border:0;background:var(--ct);color:#fff}.email-action{grid-column:1/-1;border:0;background:var(--ci);color:#fff}.submission-actions small{grid-column:1/-1;color:var(--cm);line-height:1.4}.readiness{margin-top:14px;padding:10px 12px;border-radius:10px;background:#eef2f7;color:#475467;font-size:.82rem}.readiness.ready{background:#e8f7f1;color:#08734f}.readiness.warning{background:#fff3e8;color:#9a3412}#report-dialog{width:min(860px,90vw);max-height:82vh;padding:0;border:0;border-radius:18px;box-shadow:0 30px 90px #1018284d}#report-dialog::backdrop{background:#10182899}.dialog-head{display:flex;justify-content:space-between;align-items:center;padding:20px 24px;border-bottom:1px solid var(--cl)}.dialog-head h2{margin:3px 0}.dialog-head button{border:0;background:transparent;font-size:1.8rem;cursor:pointer}#report-preview{max-height:62vh;margin:0;padding:24px;overflow:auto;white-space:pre-wrap;font:13px/1.55 ui-monospace,monospace;background:#f8fafc;color:#344054}@media(max-width:900px){.project-card{grid-template-columns:auto 1fr}.project-fields{grid-column:1/-1}.submission-card{grid-template-columns:1fr}}@media(max-width:620px){.project-card{grid-template-columns:1fr}.project-fields{grid-template-columns:1fr}.project-fields label.wide{grid-column:auto}.evidence-fields,.submission-actions{grid-template-columns:1fr}.email-action{grid-column:auto}} diff --git a/prototipo/checklist.html b/prototipo/checklist.html index 22a6455..874a481 100644 --- a/prototipo/checklist.html +++ b/prototipo/checklist.html @@ -2,9 +2,12 @@
SSAP · TFILaboratorio ICONIX

Control de entrega

¿Tu TP está completo y es trazable?

Marcá cada criterio sólo cuando puedas señalar su evidencia. El progreso se guarda en este navegador.

0%0 de 0 criterios
+
Paso 1

Identificar la entrega

Datos del trabajo

Esta información encabezará el informe que recibirá el profesor.

1
RRRRequerimientos
→
2
PDRDiseño preliminar
→
3
CDRDiseño crítico
→
4
V&VTesting y evidencias
-
+
+
Paso 3

Preparar el seguimiento

Generá y enviá el informe

Revisá los datos, descargá el archivo Markdown y enviáselo al profesor para tutoría y evaluación.

El correo se abrirá dirigido a alejandro.sartorio@gmail.com. Adjuntá el `.md` descargado antes de enviarlo.
+

Vista previa

Informe de seguimiento

Chequeo transversal

Cadena mínima de trazabilidad

Elegí un caso de uso central y seguí la misma historia de extremo a extremo.

REQ→CU→UI→ROB→SEC→CLA→TEST
diff --git a/prototipo/checklist.js b/prototipo/checklist.js index aa6b296..bfcb301 100644 --- a/prototipo/checklist.js +++ b/prototipo/checklist.js @@ -9,12 +9,24 @@ const S=[ ['class','Diagrama de clases','Modelo estático listo para implementar',[['Clases con responsabilidades, atributos y operaciones','Las operaciones surgen de secuencia.'],['Tipos, visibilidad y firmas especificados','Los contratos permiten comprender la colaboración.'],['Relaciones y multiplicidades correctas','Herencia y composición tienen necesidad demostrable.'],['Alta cohesión y bajo acoplamiento revisados','Cada clase protege un secreto coherente.'],['Interfaces aíslan decisiones que pueden cambiar','Se aplica ocultamiento de información.'],['Consistencia con dominio, secuencias y código','No faltan ni sobran elementos sin trazabilidad.'],['Hito CDR aprobado','Diseño completo, correcto, realizable y alineado.']]], ['patterns','Patrones de diseño','Problema, decisión y consecuencias',[['Cada patrón resuelve un problema concreto','Se explica contexto y necesidad.'],['Participantes y colaboraciones identificados','Las clases del TP se mapean a roles.'],['Clases reflejan la estructura del patrón','No es sólo un ejemplo genérico copiado.'],['Secuencias reflejan su comportamiento','Los mensajes muestran un escenario real.'],['Alternativas, beneficios y costos documentados','Evalúa acoplamiento, extensión y complejidad.']]], ['test','Testing estructural','Pruebas derivadas del código',[['Unidad, especificación y dominio de entrada definidos','Cada caso usa valores concretos y salida esperada.'],['Grafo de flujo de control construido','Nodos, decisiones y flechas corresponden al código.'],['Criterio de cobertura declarado','Distingue sentencias, ramas, condiciones y caminos.'],['Conjunto de pruebas satisface el criterio','Vincula entrada, camino, esperado y obtenido.'],['Cobertura medida y evidencia adjunta','Incluye reporte o cálculo verificable.'],['Casos normales, límites y fallas incluidos','La selección busca revelar defectos.'],['Pruebas re-ejecutables y resultado registrado','Documenta ambiente, versión, fecha y estado.'],['Trazabilidad hasta TEST comprobada','Una prueba demuestra cada requisito del alcance.']]]]; -const K='sap-tfi-checklist-v1',root=document.querySelector('#checklist-sections'),index=document.querySelector('#section-index');let st=JSON.parse(localStorage.getItem(K)||'{}'),filter='all',query=''; -const items=S.flatMap(([sid,,,a])=>a.map((x,i)=>[`${sid}-${i}`, ...x])); -const get=id=>st[id]||{done:false,evidence:''},esc=v=>(v||'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])),save=()=>localStorage.setItem(K,JSON.stringify(st)); +const K='sap-tfi-checklist-v2',OLD='sap-tfi-checklist-v1',META='sap-tfi-checklist-meta-v1'; +const root=document.querySelector('#checklist-sections'),index=document.querySelector('#section-index'); +let st=JSON.parse(localStorage.getItem(K)||localStorage.getItem(OLD)||'{}'),meta=JSON.parse(localStorage.getItem(META)||'{}'),filter='all',query=''; +const items=S.flatMap(([sid,,,a])=>a.map((x,i)=>[`${sid}-${i}`,...x])); +const types=['Página','Sección','Título o subtítulo','Tabla','Figura o diagrama','Párrafo','Anexo','Link o archivo','Otro']; +const get=id=>{const x=st[id]||{};return{done:!!x.done,type:x.type||'',location:x.location||x.evidence||''}}; +const esc=v=>(v||'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); +const md=v=>(v||'').replace(/\|/g,'\\|').replace(/\r?\n/g,' '); +function save(){localStorage.setItem(K,JSON.stringify(st));localStorage.setItem(META,JSON.stringify(meta))} function match(id,t,h){const x=get(id);return(filter==='all'||(filter==='done'?x.done:!x.done))&&`${t} ${h}`.toLowerCase().includes(query)} -function render(){root.innerHTML=S.map(([sid,title,sub,a],n)=>{const done=a.filter((_,i)=>get(`${sid}-${i}`).done).length,vis=a.map((x,i)=>[`${sid}-${i}`,...x]).filter(x=>match(...x));return`
${vis.map(row).join('')}
`}).join('');if(!root.querySelector('.check-section:not([hidden])'))root.innerHTML='
No hay criterios que coincidan con este filtro.
';index.innerHTML=S.map(([sid,t,,a])=>`${t}${a.filter((_,i)=>get(`${sid}-${i}`).done).length}/${a.length}`).join('');bind();total()} -function row([id,t,h]){const x=get(id);return`

${h}

`} -function bind(){document.querySelectorAll('.section-head').forEach(b=>b.onclick=()=>{const c=b.closest('.check-section');c.classList.toggle('open');b.setAttribute('aria-expanded',c.classList.contains('open'))});document.querySelectorAll('.check-item').forEach(r=>{const id=r.dataset.id,c=r.querySelector('[type=checkbox]'),e=r.querySelector('.evidence-input');c.onchange=()=>{st[id]={done:c.checked,evidence:e.value};save();render()};e.oninput=()=>{st[id]={done:c.checked,evidence:e.value};save()}})} -function total(){const done=items.filter(([id])=>get(id).done).length;document.querySelector('#total-percent').textContent=`${Math.round(done/items.length*100)}%`;document.querySelector('#total-copy').textContent=`${done} de ${items.length} criterios`} -document.querySelector('#search').oninput=e=>{query=e.target.value.trim().toLowerCase();render()};document.querySelectorAll('[data-filter]').forEach(b=>b.onclick=()=>{filter=b.dataset.filter;document.querySelectorAll('[data-filter]').forEach(x=>x.classList.toggle('active',x===b));render()});document.querySelector('#expand-all').onclick=e=>{const a=[...document.querySelectorAll('.check-section')],open=a.some(c=>!c.classList.contains('open'));a.forEach(c=>c.classList.toggle('open',open));e.target.textContent=open?'Contraer todo':'Expandir todo'};document.querySelector('#reset-checklist').onclick=()=>{if(confirm('¿Borrar todos los tildes y evidencias?')){st={};save();render()}};render(); +function render(){root.innerHTML=S.map(([sid,title,sub,a],n)=>{const done=a.filter((_,i)=>get(`${sid}-${i}`).done).length,vis=a.map((x,i)=>[`${sid}-${i}`,...x]).filter(x=>match(...x));return`
${vis.map(row).join('')}
`}).join('');if(!root.querySelector('.check-section:not([hidden])'))root.innerHTML='
No hay criterios que coincidan con este filtro.
';index.innerHTML=S.map(([sid,t,,a])=>`${t}${a.filter((_,i)=>get(`${sid}-${i}`).done).length}/${a.length}`).join('');bind();total()} +function row([id,t,h]){const x=get(id),invalid=x.done&&(!x.type||!x.location.trim());return`

${h}

Indicá dónde puede encontrar el profesor la evidencia dentro del borrador.
`} +function bind(){document.querySelectorAll('.section-head').forEach(b=>b.onclick=()=>{const c=b.closest('.check-section');c.classList.toggle('open');b.setAttribute('aria-expanded',c.classList.contains('open'))});document.querySelectorAll('.check-item').forEach(r=>{const id=r.dataset.id,c=r.querySelector('[type=checkbox]'),t=r.querySelector('.evidence-type'),l=r.querySelector('.evidence-location');const update=()=>{st[id]={done:c.checked,type:t.value,location:l.value};save();total()};c.onchange=()=>{update();render()};t.onchange=update;l.oninput=update})} +function total(){const done=items.filter(([id])=>get(id).done).length,valid=items.filter(([id])=>{const x=get(id);return x.done&&x.type&&x.location.trim()}).length,missing=done-valid;document.querySelector('#total-percent').textContent=`${Math.round(valid/items.length*100)}%`;document.querySelector('#total-copy').textContent=`${valid} de ${items.length} referenciados`;const box=document.querySelector('#readiness');box.className=`readiness ${missing?'warning':valid?'ready':''}`;box.textContent=missing?`${missing} ítem${missing===1?'':'s'} marcado${missing===1?'':'s'} sin referencia completa.`:valid?`El informe tiene ${valid} evidencias listas para compartir.`:'Completá criterios para construir el informe.'} +function syncMeta(){meta={student:document.querySelector('#student-name').value.trim(),group:document.querySelector('#course-group').value.trim(),draft:document.querySelector('#draft-link').value.trim()};save()} +function report(){syncMeta();const done=items.filter(([id])=>get(id).done);let out=`# Informe de seguimiento del Trabajo Práctico\n\n- **Alumno/a o equipo:** ${md(meta.student)||'Sin completar'}\n- **Comisión:** ${md(meta.group)||'Sin completar'}\n- **Documento borrador:** ${meta.draft?`[Abrir borrador](${meta.draft})`:'Sin completar'}\n- **Fecha de generación:** ${new Date().toLocaleDateString('es-AR')}\n- **Avance referenciado:** ${done.filter(([id])=>get(id).type&&get(id).location.trim()).length}/${items.length} criterios\n\n`;S.forEach(([sid,title,,a])=>{out+=`## ${title}\n\n| Estado | Criterio | Tipo de referencia | Ubicación en el documento |\n|---|---|---|---|\n`;a.forEach(([t],i)=>{const x=get(`${sid}-${i}`);out+=`| ${x.done?'✅ Cumple':'⬜ Pendiente'} | ${md(t)} | ${md(x.type)||'—'} | ${md(x.location)||'—'} |\n`});out+='\n'});out+='---\n\nInforme generado con la checklist interactiva SAP-TFI-2026.\n';return out} +function validateForShare(){syncMeta();const errors=[];if(!meta.student)errors.push('el nombre del alumno o equipo');if(!meta.draft||!/^https?:\/\//i.test(meta.draft))errors.push('un link válido al borrador');const incomplete=items.filter(([id])=>{const x=get(id);return x.done&&(!x.type||!x.location.trim())}).length;if(incomplete)errors.push(`la referencia de ${incomplete} ítem${incomplete===1?'':'s'} marcado${incomplete===1?'':'s'}`);if(errors.length){alert(`Antes de compartir, completá ${errors.join(', ')}.`);return false}return true} +function download(){if(!validateForShare())return false;const blob=new Blob([report()],{type:'text/markdown;charset=utf-8'}),url=URL.createObjectURL(blob),a=document.createElement('a'),slug=(meta.student||'alumno').toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/[^a-z0-9]+/g,'-').replace(/^-|-$/g,'');a.href=url;a.download=`seguimiento-tp-${slug||'alumno'}.md`;a.click();setTimeout(()=>URL.revokeObjectURL(url),1000);return true} +['student-name','course-group','draft-link'].forEach(id=>{const el=document.querySelector(`#${id}`),key=id==='student-name'?'student':id==='course-group'?'group':'draft';el.value=meta[key]||'';el.oninput=syncMeta}); +document.querySelector('#search').oninput=e=>{query=e.target.value.trim().toLowerCase();render()};document.querySelectorAll('[data-filter]').forEach(b=>b.onclick=()=>{filter=b.dataset.filter;document.querySelectorAll('[data-filter]').forEach(x=>x.classList.toggle('active',x===b));render()});document.querySelector('#expand-all').onclick=e=>{const a=[...document.querySelectorAll('.check-section')],open=a.some(c=>!c.classList.contains('open'));a.forEach(c=>c.classList.toggle('open',open));e.target.textContent=open?'Contraer todo':'Expandir todo'};document.querySelector('#reset-checklist').onclick=()=>{if(confirm('¿Borrar todos los datos, tildes y referencias?')){st={};meta={};localStorage.removeItem(K);localStorage.removeItem(META);location.reload()}}; +const dialog=document.querySelector('#report-dialog');document.querySelector('#preview-report').onclick=()=>{document.querySelector('#report-preview').textContent=report();dialog.showModal()};document.querySelector('#close-report').onclick=()=>dialog.close();document.querySelector('#download-report').onclick=download;document.querySelector('#email-report').onclick=()=>{if(!download())return;const subject=encodeURIComponent(`Seguimiento TP SAP-TFI-2026 · ${meta.student}`),body=encodeURIComponent(`Profesor Alejandro Sartorio:\n\nAdjunto el informe Markdown de seguimiento de nuestro Trabajo Práctico.\n\nDocumento borrador: ${meta.draft}\n\nAlumno/a o equipo: ${meta.student}\nComisión: ${meta.group||'Sin completar'}\n\nImportante: adjuntar el archivo .md que acaba de descargarse antes de enviar este correo.`);window.location.href=`mailto:alejandro.sartorio@gmail.com?subject=${subject}&body=${body}`};render();