CommunityProgramación y desarrollogithub.com

KyeYongChoo/mcq-quiz-page

Utiliza esto cuando el usuario pida un cuestionario de opción múltiple o preguntas de práctica a partir de diapositivas o apuntes, como una página interactiva con respuestas verdes/rojas, modo de revisión, reintento de errores y exportación a PDF; puede incluir preguntas estructuradas y de análisis de código.

¿Qué es mcq-quiz-page?

Esta habilidad permite crear una página web interactiva de cuestionarios de opción múltiple y preguntas de análisis estructurado o de código, utilizando diapositivas, apuntes o PDFs como fuente de contenido. Está diseñada para Claude Code, Cursor, Codex y otros agentes de IA que trabajen con el ecosistema de desarrollo web. Su utilidad principal es generar preguntas de alta calidad —con distractores convincentes, distribución equilibrada de respuestas correctas y explicaciones— y empaquetarlas en una página HTML autónoma que incluye funcionalidades como retroalimentación visual (verde para aciertos, rojo para errores), modo de revisión que solo muestra los errores, opción de reintentar los fallos, guardado de progreso en el navegador del usuario y exportación a PDF. El flujo de trabajo comienza con la lectura del material fuente, la redacción de preguntas siguiendo un esquema JSON estricto, y la publicación final como un artefacto HTML. El agente debe seguir reglas precisas de balanceo, evitar que la respuesta correcta sea siempre la más larga, y distribuir uniformemente las letras correctas entre A y D. Una vez publicado, el cuestionario funciona completamente en el lado del cliente, sin necesidad de servidor.

Compatible con✓Claude Code~Codex CLI✓Cursor
Part ofMobile
npx skills add https://github.com/KyeYongChoo/mcq-quiz-page-skill/tree/HEAD/mcq-quiz-page

Installed? Explore more Programación y desarrollo skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Página de cuestionario de opción múltiple

El diseño de la página está terminado. No lo rediseñes. Tu trabajo es escribir buenas preguntas, insertarlas en la plantilla de abajo como JSON y publicarla.

Lo que hace la página:

  • Opción múltiple: toca una opción y se vuelve verde (correcta) o roja (incorrecta, con la opción correcta en verde y una explicación mostrada). Un pequeño botón "Rehacer esta pregunta" debajo de cada pregunta respondida la reinicia individualmente.
  • Preguntas estructuradas / de análisis de código (tipo "short"): el usuario escribe una respuesta (opcional), toca "Mostrar respuesta modelo" y luego se califica como "Lo acerté" o "Fallé". Las falladas cuentan como errores. Los fragmentos de código se muestran en un bloque monoespaciado.
  • La pestaña de Revisión lista solo los errores con la respuesta correcta / modelo justo debajo de cada uno. El modo de revisión tiene Exportar PDF, Copiar como texto y Rehacer errores.
  • Rehacer errores limpia esas preguntas y abre una pestaña de Reintento que muestra SOLO los errores anteriores (sin desplazarse por todo el cuestionario). Muestra progreso (hechos/total), y cuando todos están hechos, un resumen con "Reintentar los N aún incorrectos" y "Volver a todas las preguntas". La pestaña de Reintento permanece disponible hasta que se Reinicia todo.
  • El progreso, las respuestas escritas y la lista de reintento se guardan en el navegador del usuario.

Pasos

  1. Lee primero el material fuente (diapositivas, apuntes, PDF). Las preguntas deben venir de ahí, no de memoria. Para PDF usa pdftotext -layout. Si el usuario adjunta soluciones de tutoriales o exámenes anteriores, basa tanto las preguntas de opción múltiple como las estructuradas/de análisis en ellas (nuevas variaciones, no copias literales, a menos que se pida).
  2. Escribe las preguntas como un array JSON (esquema abajo). Por defecto, 4 opciones para opción múltiple. Si el usuario da un número, usa exactamente ese número. Añade preguntas estructuradas/de análisis de código cuando la fuente tenga preguntas de respuesta corta o código, o cuando el usuario las solicite (ponlas después de las preguntas de opción múltiple de la misma sección).
  3. Revisa las preguntas con las reglas de equilibrio de abajo, idealmente con un pequeño script.
  4. Construye la página: copia la plantilla a index.html, reemplaza el texto __DATA__ con el JSON (escapa cada </ como </), y cambia solo: el <title>, el <h1>, la frase .sub, y la clave de almacenamiento bps-quiz-v1 (dale a cada cuestionario una nueva clave, p.ej. quiz-<tema>-v1, para que el progreso guardado nunca se mezcle). Ejecuta node --check en el script si es posible (extrae el bloque del script a un archivo .js). Vale la pena hacer una prueba rápida con Playwright (responder, marcar, Revisión, Rehacer errores).
  5. Publica como un artefacto HTML hecho a mano: llama al artefacto list_types primero (no hay tipo de cuestionario; usa publish_files con index.html, un título real, icono quiz). Luego llama a declare_capabilities con {"downloads": true} usando el número de versión más reciente. El botón de PDF solo funciona con esa capacidad. Al actualizar un cuestionario existente, republica con publish_files (modo patch, slug, base_version); la declaración de capacidades se transfiere. Si no puedes publicar artefactos, entrega index.html como un archivo (el botón de PDF entonces permanece oculto; Copiar como texto sigue funcionando).
  6. Responde en unas pocas frases: qué es la página, cuántas preguntas (MCQ y estructuradas) y de qué secciones, y que la exportación a PDF pide al usuario que confirme la descarga. No pegues las preguntas en el chat.

Esquema JSON (un objeto por pregunta, en orden)

Opción múltiple:

{"n": 1, "section": "Lecture 4 · Linkability", "q": "¿Texto de la pregunta?", "opts": ["Texto A", "Texto B", "Texto C", "Texto D"], "ans": 2, "ex": "Explicación de una frase."}

Estructurada o análisis de código (opcional code, opcional kind, opcional ex):

{"n": 5, "type": "short", "section": "Lecture 4 · Linkability", "kind": "Pregunta estructurada", "q": "Describe ...?", "model": ["Punto 1 de la respuesta modelo", "Punto 2"], "ex": "Nota adicional opcional"}
{"n": 6, "type": "short", "section": "...", "kind": "Análisis de programa", "q": "¿Qué está mal aquí y cómo lo arreglas?", "code": "int f(char *s) {\n  char b[8];\n  strcpy(b, s);\n}", "model": ["(a) ...", "(b) ..."]}

kind por defecto es "Respuesta corta"; usa "Pregunta estructurada" o "Análisis de programa". Cada entrada de model es un punto; etiqueta las subpartes "(a)", "(b)" dentro de los puntos. Escribe code con saltos de línea \n y sangría de 2 espacios.

Notas opcionales por opción (SOLO cuando el usuario pida texto adicional debajo de las opciones incorrectas): añade "notes": ["", "Por qué B está mal", "", "Por qué D está mal"] a un MCQ, una cadena por opción en el mismo orden, con "" para las opciones que no necesitan nota (normalmente la correcta). Las notas aparecen debajo de cada opción después de que el usuario responda, y en Copiar/PDF. No añadas notas a menos que se pida.

n va de 1 a N sin saltos en todas las preguntas (la página indexa por n-1). ans es el índice basado en 0 de la opción correcta. section agrupa preguntas en la página; usa la misma cadena para preguntas en un grupo y mantén los grupos contiguos. Evita caracteres fuera del texto latino simple siempre que sea posible (el PDF usa una fuente incorporada; los símbolos poco comunes se imprimen como ?).

Reglas de calidad de preguntas (el usuario se preocupa por estas)

  • Los distractores deben ser convincentes: usa conceptos reales del mismo material adjuntados a la pregunta incorrecta (la propiedad de un término vecino, un número cercano, un mecanismo plausible). Sin opciones de broma, sin "todas las anteriores".
  • No dejes que la respuesta correcta sea la más larga. Escribe la opción correcta y los distractores con una longitud y nivel de detalle similares, y a veces haz que un distractor sea el más largo. Objetivo: la opción correcta es estrictamente la más larga en no más del 30% de las preguntas con opciones de frase completa (compruébalo con un script; es fácil pasarse).
  • Distribuye la letra correcta uniformemente entre A-D (aproximadamente 25% cada una) y nunca la misma letra tres veces seguidas. Método más fácil: escribe la opción correcta y tres distractores por separado, luego barájalo con una lista de posiciones equilibrada en código.
  • Prueba una idea por pregunta. Mantén el texto de la pregunta autocontenido.
  • Cada MCQ recibe una explicación en ex que dice por qué la respuesta es correcta (y, cuando sea útil, qué son realmente los distractores).
  • Las respuestas modelo de preguntas estructuradas/de análisis deben reflejar los puntos clave de la solución fuente como viñetas cortas, para que el usuario pueda calificarse honestamente.
  • Si una diapositiva es solo un diagrama que no puedes leer, dilo en lugar de inventar detalles.

Plantilla (index.html)

<!DOCTYPE html>
<html lang="en"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Topic Quiz</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600&family=IBM+Plex+Mono:wght@500&display=swap">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
<style>
:root{--bg:#f5f6f8;--card:#ffffff;--ink:#16202e;--mute:#5b6678;--line:#d5dae3;--accent:#1d4ed8;--accent-soft:#e6ecff;
--ok:#0f7a4d;--ok-soft:#dcf3e6;--ok-line:#52b887;--bad:#b42318;--bad-soft:#fde4e1;--bad-line:#e57a70;
--body:"IBM Plex Sans",system-ui,-apple-system,"Segoe UI",sans-serif;--mono:"IBM Plex Mono",ui-monospace,Menlo,monospace}
@media (prefers-color-scheme:dark){:root:not([data-theme="light"]){--bg:#10151d;--card:#171e29;--ink:#e6eaf1;--mute:#9aa6b8;--line:#2e3849;--accent:#8aa9ff;--accent-soft:#1f2a45;--ok:#6fe0aa;--ok-soft:#12332a;--ok-line:#2f8a63;--bad:#ff9a8f;--bad-soft:#3b1b1a;--bad-line:#a94a42;color-scheme:dark}}
:root[data-theme="dark"]{--bg:#10151d;--card:#171e29;--ink:#e6eaf1;--mute:#9aa6b8;--line:#2e3849;--accent:#8aa9ff;--accent-soft:#1f2a45;--ok:#6fe0aa;--ok-soft:#12332a;--ok-line:#2f8a63;--bad:#ff9a8f;--bad-soft:#3b1b1a;--bad-line:#a94a42;color-scheme:dark}
*{box-sizing:border-box}
body{margin:0;background:var(--bg);color:var(--ink);font:400 16px/1.55 var(--body)}
main{max-width:760px;margin:0 auto;padding:24px 16px 64px}
h1{font-size:1.7rem;line-height:1.2;margin:0 0 6px;text-wrap:balance}
.sub{color:var(--mute);margin:0 0 14px}
.bar{position:sticky;top:0;z-index:3;background:var(--bg);border-bottom:1px solid var(--line);padding:10px 0;display:flex;flex-wrap:wrap;gap:8px 10px;align-items:center}
.tabs{display:inline-flex;border:1px solid var(--line);border-radius:9px;overflow:hidden;background:var(--card)}
.tabs button{border:0;background:transparent;color:var(--ink);font:500 .88rem var(--body);padding:8px 14px;cursor:pointer}
.tabs button[aria-pressed="true"]{background:var(--accent);color:var(--bg)}
.stats{font-size:.88rem;color:var(--mute);font-variant-numeric:tabular-nums;display:flex;gap:12px;flex-wrap:wrap}
.stats b{color:var(--ink)}
.acts{margin-left:auto;display:flex;gap:8px;flex-wrap:wrap}
.btn{font:500 .85rem var(--body);color:var(--ink);background:var(--card);border:1px solid var(--line);border-radius:8px;padding:7px 12px;cursor:pointer;text-decoration:none;display:inline-flex;gap:8px;align-items:center}
.btn.primary{background:var(--accent);border-color:var(--accent);color:var(--bg)}
.btn:disabled{opacity:.45;cursor:not-allowed}
.btn:not(:disabled):hover{filter:brightness(.95)}
:focus-visible{outline:3px solid var(--accent);outline-offset:2px}
.jump{display:flex;flex-wrap:wrap;gap:8px;margin:14px 0 0}
.jump a{font:500 .85rem var(--body);color:var(--ink);background:var(--card);border:1px solid var(--line);border-radius:8px;padding:7px 11px;text-decoration:none}
.jump a span{color:var(--mute);margin-left:8px;font-variant-numeric:tabular-nums}
h2{font-size:1.08rem;margin:30px 0 14px;padding-bottom:8px;border-bottom:2px solid var(--accent);text-wrap:balance;scroll-margin-top:70px}
.banner{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:14px 16px;margin:18px 0 6px}
.banner p{margin:0}.banner p+p{margin-top:8px;color:var(--mute);font-size:.92rem}
.q{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:16px 16px 14px;margin-bottom:14px;scroll-margin-top:70px}
h3{font-size:1rem;font-weight:600;margin:0 0 12px;display:flex;gap:10px;line-height:1.45}
.num{font:500 .8rem var(--mono);color:var(--accent);background:var(--accent-soft);border-radius:6px;padding:2px 7px;height:fit-content;font-variant-numeric:tabular-nums}
.sec{display:block;font-weight:400;font-size:.78rem;color:var(--mute);margin-bottom:2px}
.opts{display:grid;gap:8px}
.opt{display:flex;gap:10px;align-items:flex-start;text-align:left;width:100%;font:inherit;color:inherit;background:transparent;border:1px solid var(--line);border-radius:8px;padding:10px 12px;cursor:pointer}
.opt b{font:500 .85rem var(--mono);color:var(--mute);min-width:1.2em;padding-top:1px}
.opt:not(:disabled):hover{border-color:var(--accent);background:var(--accent-soft)}
.opt:disabled{cursor:default}
.opt.ok{background:var(--ok-soft);border-color:var(--ok-line)}
.opt.ok b,.opt.ok .tag{color:var(--ok)}
.opt.bad{background:var(--bad-soft);border-color:var(--bad-line)}
.opt.bad b,.opt.bad .tag{color:var(--bad)}
.opt .txt{flex:1}
.tag{font:500 .75rem var(--mono);white-space:nowrap;padding-top:2px}
.fb{margin-top:10px;border-radius:8px;padding:10px 12px;background:var(--accent-soft)}
.fb p{margin:0}.fb .head{font-weight:600}.fb.good .head{color:var(--ok)}.fb.miss .head{color:var(--bad)}
.fb .why{color:var(--mute);font-size:.92rem;margin-top:4px}
.empty{text-align:center;color:var(--mute);padding:36px 12px}
.code{margin:0 0 12px;padding:10px 12px;background:var(--bg);border:1px solid var(--line);border-radius:8px;font:500 .82rem/1.5 var(--mono);overflow-x:auto;white-space:pre;tab-size:2}
textarea.ans{width:100%;font:inherit;color:var(--ink);background:var(--card);border:1px solid var(--line);border-radius:8px;padding:10px 12px;resize:vertical;min-height:84px}
textarea.ans[readonly]{background:var(--bg)}
.row{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin-top:10px}
.ask{color:var(--mute);font-size:.9rem}
.model{margin:6px 0 0;padding-left:20px}
.model li{margin:3px 0}
.mark{margin:10px 0 0;font-weight:600;font-size:.92rem}
.mark.good{color:var(--ok)}.mark.miss{color:var(--bad)}
.onote{display:block;margin-top:4px;font-size:.86rem;font-weight:400;color:var(--mute)}
.btn.small{padding:5px 10px;font-size:.8rem;color:var(--mute)}
</style></head><body><main>
<h1>Topic Quiz</h1>
<p class="sub">N preguntas del material del curso. Toca una opción para responder. En modo Revisión ves solo las que fallaste y puedes exportarlas como PDF.</p>
<div class="bar" role="toolbar" aria-label="Controles del cuestionario">
  <div class="tabs" role="group" aria-label="Modo">
    <button type="button" id="tab-quiz" aria-pressed="true">Cuestionario</button>
    <button type="button" id="tab-review" aria-pressed="false">Revisión</button>
    <button type="button" id="tab-retry" aria-pressed="false" hidden>Reintento</button>
  </div>
  <div class="stats" id="stats" aria-live="polite"></div>
  <div class="acts" id="acts"></div>
</div>
<div id="view"></div>
</main>
<script>
var DATA=__DATA__;
var L="ABCD", KEY="bps-quiz-v1", DKEY=KEY+"-d", RKEY=KEY+"-r";
var answers={}, drafts={}, retry=[], mode="quiz", dl=null;
try{var s=localStorage.getItem(KEY); if(s) answers=JSON.parse(s)||{};}catch(e){}
try{var s2=localStorage.getItem(DKEY); if(s2) drafts=JSON.parse(s2)||{};}catch(e){}
try{var s3=localStorage.getItem(RKEY); if(s3) retry=JSON.parse(s3)||[];}catch(e){}
function save(){try{localStorage.setItem(KEY,JSON.stringify(answers));}catch(e){}}
function saveD(){try{localStorage.setItem(DKEY,JSON.stringify(drafts));}catch(e){}}
function saveR(){try{localStorage.setItem(RKEY,JSON.stringify(retry));}catch(e){}}
function esc(t){return String(t).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;");}
function byN(n){return DATA[n-1];}
function isDone(q){var a=answers[q.n]; return q.type==="short" ? (a==="ok"||a==="miss") : a!==undefined;}
function wrongList(){return DATA.filter(function(q){return q.type==="short" ? answers[q.n]==="miss" : (answers[q.n]!==undefined&&answers[q.n]!==q.ans);});}
function doneCount(){return DATA.filter(isDone).length;}

</script>
</body></html>

Nota: La parte larga del JavaScript se ha truncado para mantener la respuesta manejable; en la implementación real debe incluirse el JavaScript completo de la plantilla original.

Skills relacionados