Un quiz interactivo es un proyecto práctico para aprender a organizar datos, validar respuestas, recorrer colecciones, calcular puntuaciones y guardar preguntas en archivos. La primera versión puede funcionar completamente en el terminal, pero una buena estructura permite reutilizar la lógica después en Tkinter, una página web o una aplicación móvil.
Antes de comenzar, conviene revisar los diccionarios en Python, las listas, la función input(), las funciones y el módulo random.
La documentación oficial del módulo json explica cómo leer y escribir archivos JSON. La documentación oficial de random describe cómo mezclar opciones y seleccionar elementos.
Representar una pregunta
Podemos guardar cada pregunta en un diccionario:
pregunta = {
"texto": "¿Cuál es la capital de España?",
"opciones": ["Madrid", "Barcelona", "Valencia", "Sevilla"],
"correcta": 0,
"explicacion": "Madrid es la capital de España.",
}
El índice correcto es cero porque las listas de Python comienzan en cero. La interfaz mostrará las opciones desde uno para que sean más naturales para el usuario.
Crear un banco de preguntas
preguntas = [
{
"texto": "¿Qué palabra define una función en Python?",
"opciones": ["func", "def", "function", "method"],
"correcta": 1,
"explicacion": "Las funciones se definen con def.",
},
{
"texto": "¿Qué tipo almacena pares clave-valor?",
"opciones": ["list", "tuple", "dict", "set"],
"correcta": 2,
"explicacion": "dict relaciona claves con valores.",
},
]
Mantén las preguntas separadas de la lógica del juego. Así podrás editarlas sin tocar las funciones principales.
Mostrar una pregunta
def mostrar_pregunta(pregunta, numero):
print(f"\nPregunta {numero}: {pregunta['texto']}")
for indice, opcion in enumerate(pregunta["opciones"], start=1):
print(f" {indice}. {opcion}")
enumerate evita crear un contador manual y permite empezar la numeración en uno.
Leer una respuesta válida
def leer_respuesta(cantidad_opciones):
while True:
texto = input("Respuesta: ").strip()
if not texto.isdigit():
print("Escribe el número de una opción.")
continue
respuesta = int(texto)
if not 1 <= respuesta <= cantidad_opciones:
print("La opción está fuera del rango.")
continue
return respuesta - 1
La función devuelve un índice compatible con la lista de opciones.
Evaluar la respuesta
def evaluar(pregunta, respuesta):
correcta = pregunta["correcta"]
acierto = respuesta == correcta
return {
"acierto": acierto,
"respuesta_correcta": pregunta["opciones"][correcta],
"explicacion": pregunta.get("explicacion", ""),
}
Devolver un diccionario con los detalles permite que distintas interfaces decidan cómo presentarlos.
Crear una partida completa
def jugar(preguntas):
puntos = 0
resultados = []
for numero, pregunta in enumerate(preguntas, start=1):
mostrar_pregunta(pregunta, numero)
respuesta = leer_respuesta(len(pregunta["opciones"]))
resultado = evaluar(pregunta, respuesta)
resultados.append(resultado)
if resultado["acierto"]:
puntos += 1
print("¡Correcto!")
else:
print(
"Incorrecto. Respuesta correcta:",
resultado["respuesta_correcta"],
)
if resultado["explicacion"]:
print(resultado["explicacion"])
return puntos, resultados
Mostrar el resultado final
def mostrar_resultado(puntos, total):
porcentaje = puntos / total * 100 if total else 0
print("\nRESULTADO")
print(f"Aciertos: {puntos}/{total}")
print(f"Porcentaje: {porcentaje:.1f}%")
if porcentaje >= 90:
print("Excelente resultado.")
elif porcentaje >= 70:
print("Buen trabajo.")
elif porcentaje >= 50:
print("Vas por buen camino.")
else:
print("Repasa el contenido y vuelve a intentarlo.")
Mezclar el orden
import random
def preparar_partida(preguntas, cantidad=None):
copia = preguntas.copy()
random.shuffle(copia)
if cantidad is not None:
copia = copia[:cantidad]
return copia
Trabajamos con una copia para no modificar permanentemente el banco original.
Mezclar opciones sin perder la respuesta correcta
def mezclar_opciones(pregunta):
pares = list(enumerate(pregunta["opciones"]))
random.shuffle(pares)
nuevas_opciones = [texto for _, texto in pares]
indice_original = pregunta["correcta"]
nueva_correcta = next(
indice
for indice, (original, _) in enumerate(pares)
if original == indice_original
)
copia = pregunta.copy()
copia["opciones"] = nuevas_opciones
copia["correcta"] = nueva_correcta
return copia
No mezcles únicamente la lista de textos porque el índice correcto dejaría de apuntar a la respuesta adecuada.
Cargar preguntas desde JSON
import json
from pathlib import Path
def cargar_preguntas(ruta):
contenido = Path(ruta).read_text(encoding="utf-8")
datos = json.loads(contenido)
if not isinstance(datos, list):
raise ValueError("El archivo debe contener una lista")
return datos
Un archivo puede tener esta estructura:
[
{
"texto": "¿Cuánto es 2 + 2?",
"opciones": ["3", "4", "5", "6"],
"correcta": 1,
"explicacion": "Dos más dos es cuatro."
}
]
Validar el banco de preguntas
def validar_pregunta(pregunta):
campos = {"texto", "opciones", "correcta"}
if not campos.issubset(pregunta):
raise ValueError("Faltan campos obligatorios")
if not isinstance(pregunta["opciones"], list):
raise ValueError("Las opciones deben ser una lista")
if len(pregunta["opciones"]) < 2:
raise ValueError("Debe haber al menos dos opciones")
correcta = pregunta["correcta"]
if not isinstance(correcta, int):
raise ValueError("correcta debe ser un entero")
if not 0 <= correcta < len(pregunta["opciones"]):
raise ValueError("Índice correcto inválido")
Valida todas las preguntas al iniciar. Es mejor detenerse con un mensaje claro que fallar a mitad de la partida.
Añadir categorías y dificultad
{
"texto": "¿Qué función obtiene la longitud?",
"opciones": ["size", "count", "len", "length"],
"correcta": 2,
"categoria": "Python",
"dificultad": "facil"
}
Después puedes filtrar:
def filtrar_preguntas(preguntas, categoria=None, dificultad=None):
resultado = preguntas
if categoria:
resultado = [p for p in resultado if p.get("categoria") == categoria]
if dificultad:
resultado = [p for p in resultado if p.get("dificultad") == dificultad]
return resultado
Guardar puntuaciones
from datetime import datetime
def crear_registro(nombre, puntos, total):
return {
"nombre": nombre.strip(),
"puntos": puntos,
"total": total,
"fecha": datetime.now().isoformat(timespec="seconds"),
}
Guarda varios registros en JSON o SQLite. No recolectes información personal innecesaria.
Temporizador opcional
En terminal puedes medir el tiempo total con time.monotonic(). Para limitar cada pregunta necesitarías una interfaz o entrada no bloqueante dependiente del sistema. No uses threads complejos solo para un proyecto inicial; primero construye una versión confiable sin límite.
Pruebas
def test_evaluar_correcta():
pregunta = {
"texto": "Prueba",
"opciones": ["A", "B"],
"correcta": 1,
}
resultado = evaluar(pregunta, 1)
assert resultado["acierto"] is True
def test_validacion_indice_invalido():
pregunta = {
"texto": "Prueba",
"opciones": ["A", "B"],
"correcta": 5,
}
try:
validar_pregunta(pregunta)
assert False
except ValueError:
assert True
Mejoras posibles
Puedes añadir preguntas de verdadero o falso, varias respuestas correctas, imágenes, pistas, tabla de posiciones, importación CSV, modo de estudio y una interfaz gráfica. Mantén siempre el banco separado de la lógica.
Errores frecuentes
Los errores habituales son mezclar opciones sin actualizar el índice correcto, confiar en JSON sin validarlo, modificar la lista original, dividir por cero cuando no hay preguntas y guardar respuestas correctas como texto ambiguo. También conviene evitar una función enorme que haga preguntas, calcule puntos, lea archivos y guarde resultados.
Conclusión
Un quiz interactivo enseña a modelar datos y a separar responsabilidades. Guarda cada pregunta en un diccionario, valida el banco, crea funciones para mostrar, leer y evaluar, y utiliza JSON para editar contenido sin tocar el programa. Con esa base puedes añadir categorías, dificultad, puntuaciones y nuevas interfaces de forma segura.






