Saltar al contenido
Inicio 100 Días de Código en Python — Día 17: Crear Clases Propias y el Proyecto Quiz

100 Días de Código en Python — Día 17: Crear Clases Propias y el Proyecto Quiz

Nivel: Grado décimo Área: Tecnología e Informática Modalidad: Tutorial práctico Fuente: Curso "100 Days of Code: The Complete Python Pro Bootcamp" (Angela Yu, App Brewery). Contenido del Día 17 (nivel intermedio).

Este tutorial adapta pedagógicamente el Día 17. Las secciones 📝 Ejercicio original del curso provienen de los archivos del curso. Los retos 🟢🟡🟠🔴, el proyecto extra, el proyecto integrador y el banco de preguntas en español son creaciones de esta adaptación. Las correcciones al material original aparecen como 🛠️ Nota técnica.


Tabla de contenidos

  1. Antes de empezar: de usar clases a crearlas
  2. Crear una clase: class, pass y las convenciones de nombres
  3. Atributos y el constructor __init__
  4. Métodos y el parámetro self
  5. 🚀 Proyecto del día: el Juego de Preguntas (Quiz)
  6. Los beneficios de la POO: cambiar los datos sin tocar la lógica
  7. 🌟 Proyecto extra: Banco de Preguntas del Colegio
  8. 🏆 Proyecto integrador del Día 17: Tarjetas de Estudio de Selección Múltiple
  9. 🤖 Programar con apoyo de IA
  10. 🛠️ Correcciones técnicas al material original
  11. 📋 Matriz de trazabilidad
  12. 📦 Evidencias de aprendizaje del Día 17

0. Antes de empezar

(Fuentes: 116 - Day 17 Goals- what we will make by the end of the day.md, 126 - Run for that Bus!.md)

En el Día 16 usamos clases hechas por otros (Turtle, PrettyTable y la máquina de café). Hoy aprenderemos a crear nuestras propias clases, y con ellas construiremos un cuestionario de Verdadero o Falso que muestra preguntas, revisa las respuestas y lleva el puntaje, programado completamente con objetos.

Mensaje de cierre del curso: "¡corre para alcanzar el bus!". La instructora cuenta que de niña corría para alcanzar el bus del colegio aunque no supiera si llegaría. A veces llegaba y a veces no, pero siempre lo intentaba. "Corre hacia ese bus, porque la mitad de las veces lo alcanzarás."

Prerequisito general: Día 16 (objetos, atributos, métodos y notación de punto), funciones con parámetros y return (Días 8 y 10), listas de diccionarios (Día 9).


1. Crear una clase

(Fuente: 117 - How to create your own Class in Python.md)

📖 Explicación conceptual

Python
class User:
    pass

user_1 = User()     # construir un objeto a partir de la clase
  • class + el nombre + dos puntos :. Todo lo que va dentro de la clase lleva sangría.
  • Una clase (o función) vacía produce un error de sangría. La palabra clave pass significa "no hago nada por ahora; sigue".

🛠️ Nota técnica: la transcripción dice que la declaración termina con "un punto y coma". Es un error de traducción: se trata de dos puntos (:). En Python, el punto y coma (;) casi nunca se usa.

Convenciones de nombres:

Estilo Forma En Python se usa para…
PascalCase NombreDeClase Clases
camelCase nombreDeVariable Casi no se usa en Python (es común en JavaScript o Java)
snake_case nombre_de_variable Variables, funciones, métodos, atributos

El nombre PascalCase viene de Blaise Pascal: como en el nombre de una persona, cada palabra empieza con mayúscula.

Retos adicionales (creados para esta adaptación)

  • 🟢 Reto 1: crea las clases vacías Estudiante, Curso y Materia y construye un objeto de cada una.
  • 🟡 Reto 2: escribe cada identificador con la convención correcta: "número de lista", "calcular promedio", "boletín académico", "nota final" (clase o variable, según el caso).
  • 🟠 Reto 3: imprime un objeto de tu clase vacía. ¿Qué ves? Compáralo con print(timmy) del Día 16.
  • 🔴 Reto 4: busca en la guía de estilo PEP 8 (se abre en otra pestaña) la sección de convenciones de nombres y resume en 5 líneas qué dice sobre las clases, las funciones y las constantes (Día 12).

2. Atributos y el constructor __init__

(Fuente: 118 - Working with Attributes, Class Constructors and the __init__() Function.md)

🎯 Objetivo de aprendizaje

  • Definir atributos al construir el objeto, usando __init__.
  • Dar valores por defecto a los atributos que no se reciben como parámetro.

📖 Explicación conceptual

Forma 1: agregar atributos "desde fuera" (funciona, pero es propensa a errores):

Python
user_1 = User()
user_1.id = "001"
user_1.username = "angela"
user_2 = User()
user_2.id = "002"
user_2.usernme = "jack"    # ¡error de tipeo! Crea un atributo distinto y Python no avisa

Forma 2: el constructor __init__ (recomendada):

Python
class User:
    def __init__(self, user_id, username):
        print("Nuevo usuario creado...")
        self.id = user_id
        self.username = username
        self.followers = 0          # valor por defecto: no se recibe como parámetro

user_1 = User("001", "angela")      # 1 línea en lugar de 3
print(user_1.username)              # angela
print(user_1.followers)             # 0
  • __init__ (con dos guiones bajos a cada lado) es un método especial que Python ejecuta automáticamente cada vez que se construye un objeto. Se le llama constructor, y el proceso se llama inicializar.
  • self es el objeto que se está construyendo. self.id = user_id significa "el atributo id de este objeto toma el valor recibido".
  • El parámetro (user_id) y el atributo (self.id) pueden llamarse distinto, aunque la convención es que se llamen igual.
  • Valores por defecto: un atributo como followers empieza en 0 para todos los usuarios. No tiene sentido pedirlo al construir el objeto.

Si la clase pide parámetros, hay que darlos:

Python
User()   # ❌ TypeError: User.__init__() missing 2 required positional arguments: 'user_id' and 'username' (verificado)

Retos adicionales (creados para esta adaptación)

  • 🟢 Reto 1: clase Estudiante con nombre y curso recibidos como parámetros, y notas = [] por defecto.
  • 🟡 Reto 2: clase Carro con marca y asientos (el curso usa seats = 5). Crea dos carros y verifica que sus atributos son independientes.
  • 🟠 Reto 3: demuestra con código el problema de la "Forma 1": crea un atributo con un error de tipeo y explica por qué Python no avisa.
  • 🔴 Reto 4: investiga cómo dar un valor por defecto a un parámetro del constructor (def __init__(self, nombre, curso="10A"):). ¿En qué se diferencia de un atributo con valor por defecto?

3. Métodos y el parámetro self

(Fuente: 119 - Adding Methods to a Class.md)

📖 Explicación conceptual

Un método es una función definida dentro de la clase. Siempre recibe self como primer parámetro, para saber qué objeto lo está llamando.

Ejemplo del curso: modo carreras. Los autos de carreras quitan asientos para pesar menos:

Python
class Car:
    def __init__(self):
        self.seats = 5

    def enter_race_mode(self):
        self.seats = 2

Ejemplo del curso: seguir usuarios en una red social:

Python
class User:
    def __init__(self, user_id, username):
        self.id = user_id
        self.username = username
        self.followers = 0
        self.following = 0

    def follow(self, user):
        user.followers += 1      # el OTRO usuario gana un seguidor
        self.following += 1      # YO sigo a una persona más

user_1 = User("001", "angela")
user_2 = User("002", "jack")
user_1.follow(user_2)
print(user_1.followers, user_1.following)   # 0 1
print(user_2.followers, user_2.following)   # 1 0

(Resultados verificados.)

Sobre self: "Nunca verás self cuando usas objetos, pero lo verás mucho cuando escribes tus clases." Al llamar user_1.follow(user_2), Python pasa user_1 como self automáticamente.

⚠️ Errores frecuentes

  • Olvidar self en la definición: def follow(user): produce TypeError: follow() takes 1 positional argument but 2 were given.
  • Olvidar self. al usar un atributo: followers += 1 dentro del método produce un UnboundLocalError o un NameError, porque crea o busca una variable local, no el atributo (Día 12).
  • Confundir el objeto con la clase: User.follow(user_2) no funciona como se espera, porque falta el objeto que sigue.

Retos adicionales (creados para esta adaptación)

  • 🟢 Reto 1: agrega a Estudiante el método agregar_nota(nota) y el método promedio(), que devuelva el promedio (Día 10).
  • 🟡 Reto 2: agrega a User el método unfollow(user). ¿Qué validación necesita?
  • 🟠 Reto 3: crea una clase CuentaAhorro con los métodos depositar, retirar (sin permitir saldo negativo) y consultar.
  • 🔴 Reto 4: en tu User, ¿qué pasa si un usuario se sigue a sí mismo, o sigue dos veces al mismo usuario? Diseña atributos (por ejemplo, una lista seguidos) y validaciones para evitarlo.

4. 🚀 Proyecto del día: el Juego de Preguntas (Quiz)

(Fuentes: 120 a 124, partes 1 a 5 del proyecto oficial del curso original)

Problema

Construir un cuestionario de Verdadero o Falso que muestre las preguntas numeradas, revise cada respuesta, informe la respuesta correcta y el puntaje acumulado, y al final muestre el puntaje final.

Estructura del proyecto (4 archivos)

Archivo Contenido Responsabilidad
data.py question_data: una lista de diccionarios con "text" y "answer" Los datos
question_model.py La clase Question Modelar una pregunta
quiz_brain.py La clase QuizBrain La lógica del cuestionario
main.py La unión de todo Programa principal

El código inicial del curso (quiz-game-start, con 12 preguntas en inglés) no está incluido entre los materiales suministrados. Este tutorial usa un banco de preguntas en español creado para esta adaptación:

Python
question_data = [
    {"text": "En Python, el primer índice de una lista es 0.", "answer": "True"},
    {"text": "El operador % de Python calcula porcentajes.", "answer": "False"},
    {"text": "len('Python') es igual a 6.", "answer": "True"},
    {"text": "Un byte está formado por 8 bits.", "answer": "True"},
]
Texto
classDiagram
    class Question {
        text
        answer
    }
    class QuizBrain {
        question_number = 0
        score = 0
        question_list
        still_has_questions()
        next_question()
        check_answer(user_answer, correct_answer)
    }
    QuizBrain --> Question : usa una lista de

📝 Parte 1: la clase Question

Enunciado: en question_model.py, crea Question con un __init__ que reciba el texto y la respuesta y los guarde en los atributos text y answer. Prueba: new_q = Question("2 + 3 = 5", "True") y luego print(new_q.text).

📝 Parte 2: el banco de preguntas

Enunciado: en main.py, convierte la lista de diccionarios en una lista de objetos Question llamada question_bank.

🔍 Antes de programar

  • ¿Cómo recorres question_data? ¿Qué clave usas para obtener el texto y cuál para la respuesta?
  • ¿Por qué conviene tener objetos en lugar de diccionarios? (El curso lo explica: con claves de texto es fácil cometer errores de tipeo; con atributos, el editor autocompleta y avisa.)

Estructura de la solución: una lista vacía → un for → construir Question(texto, respuesta) → append. Con los datos del curso quedan 12 objetos.

Consejo de formato del curso: en PyCharm, Code → Auto-Indent Lines organiza la sangría de los datos, y las líneas de más de 120 caracteres se pueden dividir presionando Enter dentro del string.

📝 Parte 3: QuizBrain y next_question()

Enunciado:

  1. Crea QuizBrain con question_number = 0 (valor por defecto) y question_list, que se recibe al construir el objeto.
  2. Crea next_question(): toma la pregunta en la posición question_number, suma 1 al número y pide la respuesta con el formato P.1: texto (True/False):.

Detalle que resuelve el curso: question_number empieza en 0 para acceder al índice 0 de la lista, pero el usuario debe ver "P.1". La solución es sumar 1 justo después de tomar la pregunta y antes de mostrarla.

📝 Parte 4: repetir con still_has_questions()

Enunciado: crea still_has_questions(), que devuelve True mientras queden preguntas, y úsalo en main.py con while quiz.still_has_questions():.

Simplificación que muestra el curso:

Python
# Versión larga
if self.question_number < len(self.question_list):
    return True
else:
    return False

# Versión corta: la comparación YA ES True o False
return self.question_number < len(self.question_list)

📝 Parte 5: revisar las respuestas y el puntaje

Enunciado:

  1. Agrega el atributo score = 0.
  2. Crea check_answer(user_answer, correct_answer). Compara en minúsculas (.lower()), suma un punto si acierta, muestra "¡Correcto!" o "Incorrecto" y, en ambos casos, la respuesta correcta y el puntaje score/question_number.
  3. Al terminar, main.py muestra "Completaste el cuestionario" y el puntaje final.

Pruebas (con el banco de preguntas en español)

Respuestas Resultado esperado (verificado)
true, FALSE, false, verdadero Correcto, Correcto, Incorrecto, Incorrecto → puntaje final 2/4

🛠️ Nota técnica (verificada): la prueba 4 muestra un problema de la versión en español: un estudiante que responde "verdadero" recibe "Incorrecto", porque la respuesta guardada es "True". Mejoras: (a) aceptar v/f y verdadero/falso traduciéndolos antes de comparar, o (b) guardar las respuestas como True/False (booleanos) y convertir la entrada. Además, una entrada distinta de verdadero o falso debería volver a preguntarse, no contar como error (kit del Día 13).

🧪 Prueba y depuración

Este QuizBrain tiene cuatro errores:

Python
class QuizBrain:
    def __init__(self, q_list):
        self.question_number = 0
        self.question_list = q_list

    def still_has_questions(self):
        return self.question_number <= len(self.question_list)

    def next_question(self):
        current_question = self.question_list[self.question_number]
        user_answer = input(f"P.{self.question_number}: {current_question.text}: ")
        self.check_answer(user_answer, current_question.answer)

    def check_answer(user_answer, correct_answer):
        if user_answer.lower() == correct_answer.lower():
            self.score += 1

(Pistas: un <= que produce IndexError, un número que nunca aumenta, un método sin self y un atributo que no se inicializó.)

⭐ Extensión del proyecto

  • Mezclar las preguntas al inicio con random.shuffle (Día 5).
  • Mostrar el porcentaje de aciertos al final.
  • Aceptar las respuestas en español (ver la nota técnica).

5. Los beneficios de la POO

(Fuente: 125 - The Benefits of OOP- Use Open Trivia DB to Get New Questions.md)

📖 Explicación conceptual

El curso cambia todas las preguntas por otras de Open Trivia DB (se abre en otra pestaña), una base de datos gratuita con miles de preguntas de trivia aportadas y verificadas por su comunidad. Allí se genera una URL con el número de preguntas, la categoría, la dificultad y el tipo (verdadero o falso), y el resultado viene en formato JSON (JavaScript Object Notation), que se parece mucho a un diccionario de Python.

Lo que cambió en el código: solo dos claves en main.py, porque los nuevos datos usan "question" y "correct_answer" en lugar de "text" y "answer". QuizBrain no se tocó.

"Esto es modularidad en su máxima expresión." Podrías cambiar los datos a otro idioma, tema o formato, y a QuizBrain no le importaría: solo sabe llevar el número de pregunta, mostrar la siguiente y revisar la respuesta. Un compañero podría trabajar en QuizBrain mientras otro consigue los datos, sin estorbarse.

🛠️ Nota técnica: las preguntas de Open Trivia DB suelen incluir entidades HTML, como &quot; en lugar de " y &#039; en lugar de '. Si se pegan tal cual, el cuestionario las muestra así. Solución: import html y html.unescape(texto). Verificado: html.unescape("The &quot;Windows&quot; OS&#039;s logo") → The "Windows" OS's logo. Además, las preguntas están en inglés.

📝 Ejercicio original del curso: nuevos datos

Enunciado: genera 10 preguntas de Verdadero o Falso en Open Trivia DB (el curso usa la categoría de computación, dificultad fácil), reemplaza question_data con la lista "results" y ajusta main.py para que funcione sin tocar QuizBrain.

Retos adicionales (creados para esta adaptación)

  • 🟢 Reto 1: genera preguntas de otra categoría y verifica que el cuestionario funciona cambiando solo los datos.
  • 🟡 Reto 2: aplica html.unescape al construir cada Question.
  • 🟠 Reto 3: agrega a Question el atributo category, llénalo con los datos de Open Trivia DB y muéstralo antes de cada pregunta. ¿Tuviste que tocar QuizBrain?
  • 🔴 Reto 4: escribe un párrafo que explique, con este proyecto como ejemplo, dos ventajas de la POO frente a la programación procedimental del Día 15.

6. 🌟 Proyecto extra: Banco de Preguntas del Colegio

Situación

Los docentes de distintas áreas quieren usar el Quiz para repasar antes de las evaluaciones.

Requisitos

  1. Cada estudiante escribe 5 preguntas de Verdadero o Falso de una materia (Ciencias, Sociales, Matemáticas…), verificando las respuestas en una fuente confiable, y las agrega a un data.py común.
  2. Agregar a cada pregunta las claves "materia" y "autor".
  3. Ampliar Question con esos atributos.
  4. Al inicio, el usuario elige la materia (o "todas") y solo se construyen los objetos Question de esa materia.
  5. Sin modificar la lógica de QuizBrain, salvo lo estrictamente necesario. Documenta qué cambiaste y por qué.

Revisión por pares

Cada pregunta la revisa otro estudiante, que confirma la respuesta con una fuente. Registra en un comentario quién la revisó.


7. 🏆 Proyecto integrador del Día 17: Tarjetas de Estudio de Selección Múltiple

Proyecto integrador creado para esta adaptación. Integra la creación de clases (Día 17), el diseño con objetos (Día 16), random (Día 4), listas y diccionarios (Día 9), validación (Día 13) y ámbito (Día 12).

Situación problema

El Verdadero o Falso tiene un 50 % de probabilidad de acertar al azar. Para estudiar de verdad, los estudiantes quieren preguntas de selección múltiple y un informe de las preguntas en las que fallaron.

Pregunta orientadora

¿Cómo diseñar tus propias clases para un problema nuevo, repartiendo bien las responsabilidades entre ellas?

Requerimientos

Clase Atributos Métodos
PreguntaMultiple texto, opciones (lista), correcta (índice o texto), tema mostrar() devuelve el texto con las opciones A, B, C, D mezcladas; es_correcta(respuesta) devuelve True/False
Jugador nombre, puntaje = 0, falladas = [] sumar_punto(), registrar_fallo(pregunta)
Sesion preguntas, jugador, numero = 0 quedan_preguntas(), siguiente(), informe() devuelve un texto con el puntaje, el porcentaje y los temas por repasar
  1. Datos en data.py como lista de diccionarios; main.py los convierte en objetos.
  2. Validar que la respuesta sea A, B, C o D (en mayúscula o minúscula) sin gastar la pregunta.
  3. Al final, el informe lista las preguntas falladas agrupadas por tema.
  4. Ninguna clase usa variables globales; todo viaja por atributos, parámetros y return.

Entregables

  • Un diagrama de clases (puede ser en Mermaid, como el de la Sección 4, o en papel).
  • El código en 4 archivos o más.
  • Una tabla de pruebas.
  • Una explicación de por qué repartiste así las responsabilidades.

Criterios técnicos

  • PreguntaMultiple sigue siendo correcta aunque se mezclen las opciones: la letra correcta cambia, pero la validación funciona.
  • Cambiar el archivo de datos no requiere tocar ninguna clase.
  • Todas las clases y métodos tienen docstring.

8. 🤖 Programar con apoyo de IA

Actividad 1: preguntas generadas por IA. Pide a una IA 10 preguntas de Verdadero o Falso sobre la historia de Colombia. Luego verifica cada respuesta en una fuente confiable. ¿Cuántas eran incorrectas o ambiguas? (Las IA pueden producir afirmaciones falsas con total seguridad.) Solo las verificadas entran a tu data.py.

Actividad 2: revisión de diseño. Muestra tu diagrama de clases del integrador a una IA y pregúntale si las responsabilidades están bien repartidas. Evalúa críticamente sus sugerencias: ¿mejoran tu diseño o solo lo complican?


9. 🛠️ Correcciones técnicas al material original

Archivo Afirmación o comportamiento del curso Observación Tratamiento
117 La declaración de la clase termina con "punto y coma" Error de traducción: son dos puntos (:) Nota técnica del Tema 1
124 La comparación de respuestas con "True"/"False" En una versión en español, "verdadero" cuenta como incorrecto (verificado) Nota técnica del proyecto
125 Pegar los datos de Open Trivia DB tal cual Suelen traer entidades HTML (&quot;, &#039;); se corrigen con html.unescape (verificado) Nota técnica del Tema 5
120, 121 Código inicial quiz-game-start con 12 preguntas No está incluido entre los materiales suministrados Banco de preguntas en español creado para la adaptación

10. 📋 Matriz de trazabilidad

Contenido original Archivo de origen Concepto desarrollado Ejercicio original Reto adicional Proyecto relacionado
Objetivos: Quiz con POO 116 Sección 0 — — Sección 4
class, pass, PascalCase/camelCase/snake_case 117 Tema 1 Clase User vacía Retos 1–4 del Tema 1 Todos
Atributos, __init__, self, valores por defecto 118 Tema 2 User(user_id, username) con followers = 0 Retos 1–4 del Tema 2 Secciones 4, 6 y 7
Métodos, enter_race_mode, follow 119 Tema 3 follow entre usuarios Retos 1–4 del Tema 3 Secciones 4 y 7
Clase Question 120 Sección 4, parte 1 Crear Question — Sección 7
Lista de objetos a partir de los datos 121 Sección 4, parte 2 question_bank — Secciones 6 y 7
QuizBrain, next_question 122 Sección 4, parte 3 QuizBrain y next_question — Sección 7
still_has_questions, while 123 Sección 4, parte 4 Repetir las preguntas — Sección 7
check_answer, puntaje 124 Sección 4, parte 5 Revisar respuestas y puntaje Depuración y extensiones Sección 7
Open Trivia DB, JSON, modularidad 125 Tema 5 Reemplazar los datos Retos 1–4 del Tema 5 Sección 6
¡Corre para alcanzar el bus! 126 Sección 0 — — —
(No existe en el curso original) — — — Banco de Preguntas del Colegio Sección 6
(No existe en el curso original) — — — Tarjetas de Selección Múltiple Sección 7

11. 📦 Evidencias de aprendizaje del Día 17

  • Clases propias Estudiante y User con __init__, atributos por defecto y métodos (Temas 2 y 3).
  • Quiz completo en 4 archivos, con las pruebas y la mejora para aceptar respuestas en español.
  • Versión del Quiz con datos de Open Trivia DB y html.unescape.
  • Proyecto extra (Banco del Colegio, con revisión por pares) o integrador (Selección Múltiple, con su diagrama de clases).
  • Cuaderno de errores: TypeError por faltar argumentos en __init__, TypeError por olvidar self en un método, IndexError por <= en still_has_questions y un atributo con error de tipeo creado "desde fuera".