Saltar al contenido
Volver al inicio 100 Días de Código en Python — Día 64: "Mis 10 películas favoritas" — CRUD, formularios y una API externa

100 Días de Código en Python — Día 64: "Mis 10 películas favoritas" — CRUD, formularios y una API externa

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 64 (nivel avanzado).

Este tutorial adapta pedagógicamente el Día 64, un proyecto de integración de Flask, WTForms, Bootstrap-Flask, SQLAlchemy y APIs. Las secciones 📝 Ejercicio original del curso provienen de las lecciones 451 a 457; las soluciones oficiales están en los gists enlazados en cada requisito y los archivos de inicio y final (day-64-starting-files-top-movies.zip, day-64-top-movies-end.zip) en los recursos, no entre los archivos suministrados. Los retos 🟢🟡🟠🔴, las secciones sobre la clave de la API y los empates en el ranking, el proyecto extra y la etapa 6 del proyecto integrador son creaciones de esta adaptación. Las correcciones al material original aparecen como 🛠️ Nota técnica.

Nota sobre las verificaciones: el 27 de septiembre de 2026, con Python 3.14, Flask-SQLAlchemy 3.1.1 y SQLAlchemy 2.1.1, se ejecutó el algoritmo de ranking del requisito 5 (incluido un caso con una película sin calificación). Se comprobó que las dos imágenes de póster del curso (image.tmdb.org/t/p/w500/...) responden, que la API de The Movie Database (TMDB) responde "Invalid API key" sin clave, y que los enlaces de documentación del curso (developers.themoviedb.org/3/...) redirigen a developer.themoviedb.org/reference/search-movie y /reference/movie-details. No se creó una cuenta en TMDB, así que el flujo con clave real no se ejecutó.


Tabla de contenidos

  1. Antes de empezar: listas de favoritos
  2. Requisito 1: ver las películas
  3. Requisito 2: editar la calificación y la reseña
  4. Requisito 3: borrar películas
  5. Requisito 4: agregar películas con la API de TMDB
  6. Requisito 5: ordenar y numerar por calificación
  7. 🚀 Proyecto del día: Mis 10 películas favoritas
  8. 🌟 Proyecto extra: el ranking de libros del plan lector
  9. 🏗️ Proyecto integrador del bloque avanzado — Etapa 6
  10. 🧩 Comprueba lo aprendido
  11. 🤖 Programar con apoyo de IA
  12. 🛠️ Correcciones técnicas al material original
  13. 📋 Matriz de trazabilidad
  14. 📦 Evidencias de aprendizaje del Día 64

0. Antes de empezar: listas de favoritos

(Fuentes: 451-day-64-goals-what-we-ll-build.md y 452-download-the-starting-project.md)

📝 Contenido original del curso

Muchos sitios publican listas de las mejores películas de todos los tiempos (el British Film Institute, la revista Empire, el New York Times), y hay empresas enteras dedicadas a que la gente haga listas de sus cosas favoritas (listchallenges.com). Hoy construirás algo así con Flask, WTForms, SQLite, SQLAlchemy y más: un sitio atractivo con tus 10 películas favoritas, que puedes actualizar a medida que ves más películas.

Proyecto de inicio: descárgalo, crea el entorno virtual e instala requirements.txt (como en los Días 61 a 63). Contiene las plantillas HTML para usar con Jinja.

Prerequisitos: CRUD con SQLAlchemy (Día 63), Flask-WTF y Bootstrap-Flask (Día 61), APIs con requests y claves en variables de entorno (Días 33 a 35), url_for, redirecciones y rutas con variables (Días 55 a 63).

📖 El mapa del proyecto (adaptación)

Texto
         ┌──────────── requisito 1: leer ─────────────┐
 /  ◀────┤ portada con tarjetas (ranking al frente,    │◀── requisito 5: ordenar
         │ datos y botones al reverso)                 │
         └───┬───────────────────────┬─────────────────┘
             │ Edit                  │ Delete
             ▼                       ▼
 /edit?id=…  requisito 2         /delete?id=…  requisito 3
             ▲
             │ tras elegir la película
 /add ──▶ TMDB: buscar ──▶ /select (lista) ──▶ TMDB: detalles ──▶ guardar   requisito 4

1. Requisito 1: ver las películas

(Fuente: 453-requirement-1-be-able-to-view-movie-list-items.md)

📝 Ejercicio original del curso

  1. Crea una base de datos SQLite con SQLAlchemy con una tabla Movie y los campos: id, title, year, description, rating, ranking, review, img_url. El title debe ser único (una entrada por película). "Recuerda que la base de datos se guarda en la carpeta instance, así que tendrás que crearla".
  2. Con código o con DB Browser, agrega esta película:
Python
   new_movie = Movie(
       title="Phone Booth",
       year=2002,
       description="Publicist Stuart Shepard finds himself trapped in a phone booth, pinned down by an extortionist's sniper rifle. ...",
       rating=7.3,
       ranking=10,
       review="My favourite character was the caller.",
       img_url="https://image.tmdb.org/t/p/w500/tjrX2oWRCM3Tvarz38zlZM7Uc10.jpg"
   )

Pista: borra (o comenta) el código que la agrega después de ejecutarlo una vez; si no, como el título es único, la base de datos rechaza el duplicado con un error de integridad.

  1. Agrega una segunda película, "Avatar The Way of Water" (2022, calificación 7.3, ranking 9, reseña "I liked the water.").
  2. Haz que la portada muestre cada película en su tarjeta: al frente, el póster y el ranking; al reverso, título, año, calificación, descripción, reseña y botones. Consulta los datos con Flask-SQLAlchemy (pista: .scalars()).

📖 Explicación conceptual

Python
class Movie(db.Model):
    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    title: Mapped[str] = mapped_column(String(250), unique=True, nullable=False)
    year: Mapped[int] = mapped_column(Integer, nullable=False)
    description: Mapped[str] = mapped_column(String(500), nullable=False)
    rating: Mapped[float] = mapped_column(Float, nullable=True)
    ranking: Mapped[int] = mapped_column(Integer, nullable=True)
    review: Mapped[str] = mapped_column(String(250), nullable=True)
    img_url: Mapped[str] = mapped_column(String(250), nullable=False)

¿Por qué nullable=True en rating, ranking y review? Piensa en el requisito 4: al agregar una película desde la API, todavía no tienes tu calificación ni tu reseña. Si esos campos fueran obligatorios, no podrías guardarla. Diseñar la tabla pensando en todo el flujo evita tener que cambiarla después.

🛠️ Nota técnica (verificado en el Día 63): Flask-SQLAlchemy 3.1 crea la carpeta instance automáticamente; no hace falta crearla a mano.

Agregar datos de prueba sin duplicarlos: en lugar de comentar y descomentar código, comprueba primero si ya existe:

Python
with app.app_context():
    db.create_all()
    if not db.session.execute(db.select(Movie).where(Movie.title == "Phone Booth")).scalar():
        db.session.add(new_movie)
        db.session.commit()

⚠️ Errores frecuentes

Error Causa Solución
IntegrityError: UNIQUE constraint failed: movie.title El código que agrega la película se ejecutó dos veces Comprueba si existe antes, o ejecútalo solo una vez
IntegrityError: NOT NULL constraint failed Un campo obligatorio quedó vacío Revisa qué campos pueden ser nullable=True
La portada sale vacía Enviaste el ScalarResult ya recorrido, o con otro nombre .scalars().all() y el mismo nombre en la plantilla

2. Requisito 2: editar la calificación y la reseña

(Fuente: 454-requirement-2-be-able-to-edit-a-movie-s-rating-and-review.md)

📝 Ejercicio original del curso

En el reverso de cada tarjeta hay un botón Edit para cambiar la calificación y la reseña.

  1. Con WTForms crea RateMovieForm y muéstralo en edit.html como Quick Form. No hace falta cambiar edit.html: ya tiene lo necesario para renderizar el formulario ("para que no se haga un formulario HTML simple").
  2. Cuando el formulario se envíe y sea válido, actualiza la película correspondiente en la base de datos.

📖 Explicación conceptual

Python
class RateMovieForm(FlaskForm):
    rating = FloatField("Tu calificación de 0 a 10 (por ejemplo, 7.5)",
                        validators=[DataRequired(), NumberRange(min=0, max=10)])
    review = StringField("Tu reseña", validators=[DataRequired(), Length(max=250)])
    submit = SubmitField("Listo")


@app.route("/edit", methods=["GET", "POST"])
def rate_movie():
    form = RateMovieForm()
    movie_id = request.args.get("id", type=int)
    movie = db.get_or_404(Movie, movie_id)
    if form.validate_on_submit():
        movie.rating = form.rating.data
        movie.review = form.review.data
        db.session.commit()
        return redirect(url_for("home"))
    return render_template("edit.html", movie=movie, form=form)
  • El enlace de la tarjeta envía el id en la URL: url_for('rate_movie', id=movie.id) → /edit?id=3 (Día 57: un argumento que no está en la ruta se convierte en parámetro de consulta).
  • request.args.get("id", type=int) lo convierte a entero; si falta o no es número, devuelve None y get_or_404 responde 404.
  • FloatField + NumberRange garantizan que la calificación sea un número entre 0 y 10. (Con un StringField, "nueve" llegaría a la base de datos y rompería el ranking.)

🛠️ Nota técnica: el curso dice Quick Form, que era la macro de la extensión antigua Flask-Bootstrap. En Bootstrap-Flask (Día 61) la macro equivalente es render_form, y es la que enlaza la propia lección.


3. Requisito 3: borrar películas

(Fuente: 455-requirement-3-be-able-to-delete-movies-from-the-database.md)

📝 Ejercicio original del curso

En el reverso de cada tarjeta también hay un botón Delete. Haz que funcione y borre la película de la base de datos.

📖 Explicación conceptual

Python
@app.route("/delete")
def delete_movie():
    movie = db.get_or_404(Movie, request.args.get("id", type=int))
    db.session.delete(movie)
    db.session.commit()
    return redirect(url_for("home"))

🛠️ Nota técnica: como en el Día 63, borrar con un enlace (GET) funciona, pero no es buena práctica: cualquier página podría provocar el borrado con un enlace o una imagen. La versión robusta usa un formulario POST (y con Flask-WTF, además, protección CSRF). Hazlo primero como el curso y conviértelo en el Reto 3 de la sección 6.


4. Requisito 4: agregar películas con la API de TMDB

(Fuente: 456-requirement-4-be-able-to-add-new-movies-via-the-add-page.md)

🎯 Objetivo de aprendizaje

Encadenar dos llamadas a una API externa (buscar y ver detalles) con páginas intermedias, y guardar el resultado en la base de datos.

📝 Ejercicio original del curso

Hasta ahora agregabas películas con código o con DB Browser, algo que un usuario normal no puede hacer. La página Add debe permitir agregar cualquier película y obtener de una API el póster, el año y la descripción.

  1. El botón Add Movie de la portada lleva a una página con un formulario de WTForms de un solo campo: el título.
  2. Al enviar el título, el servidor usa requests para buscar en la API de The Movie Database todas las películas con ese título:
    • Crea una cuenta gratuita en TMDB.
    • En Settings → API, llena el formulario y obtén una clave de API; cópiala a tu proyecto.
    • Lee la documentación de la búsqueda de películas. Pista 1: la pestaña "Try it out" muestra la estructura de la solicitud y de la respuesta. Pista 2: repasa el Día 33.
    • Con los datos, renderiza select.html con el título y el año de cada resultado, para que el usuario elija (suele haber varias películas con nombres parecidos).
  3. Cuando el usuario elige una película, usa su id para llamar a otra ruta de la API, get-movie-details, que trae todos los datos (por ejemplo, las URL de los pósteres). Guarda en la base de datos: title, img_url, year y description. Luego redirige a la portada ("faltarán algunos datos, no pasa nada").
  4. En lugar de ir a la portada, redirige a edit.html, porque lo que falta es justamente la calificación y la reseña.

📖 Explicación conceptual

🛠️ Nota técnica (verificado): los enlaces de documentación del curso (developers.themoviedb.org/3/...) ahora redirigen a developer.themoviedb.org/reference/search-movie y /reference/movie-details. TMDB ofrece dos credenciales: la clave de API (parámetro api_key) y el token de lectura (cabecera Authorization: Bearer ...); cualquiera sirve. Sin credencial, la API responde {"status_code":7,"status_message":"Invalid API key: You must be granted a valid key."}.

Python
import os
import requests

TMDB_URL_BUSQUEDA = "https://api.themoviedb.org/3/search/movie"
TMDB_URL_PELICULA = "https://api.themoviedb.org/3/movie"
TMDB_URL_IMAGEN = "https://image.tmdb.org/t/p/w500"
TMDB_API_KEY = os.environ["TMDB_API_KEY"]          # en .env, nunca en el código


@app.route("/add", methods=["GET", "POST"])
def add_movie():
    form = FindMovieForm()
    if form.validate_on_submit():
        respuesta = requests.get(TMDB_URL_BUSQUEDA,
                                 params={"api_key": TMDB_API_KEY, "query": form.title.data, "language": "es-CO"},
                                 timeout=10)
        respuesta.raise_for_status()
        return render_template("select.html", options=respuesta.json()["results"])
    return render_template("add.html", form=form)

En select.html, cada opción enlaza a una ruta que recibe el id de TMDB: url_for('find_movie', id=movie.id). Esa ruta pide los detalles y guarda:

Python
@app.route("/find")
def find_movie():
    tmdb_id = request.args.get("id", type=int)
    datos = requests.get(f"{TMDB_URL_PELICULA}/{tmdb_id}",
                         params={"api_key": TMDB_API_KEY, "language": "es-CO"}, timeout=10).json()
    ...   # crear Movie con title, year, img_url y description; commit
    return redirect(url_for("rate_movie", id=nueva.id))

Tres detalles de los datos que debes pensar tú:

Dato de TMDB Qué hacer
release_date llega como "2002-11-14" El año es la parte antes del primer guion. Pero algunas películas tienen release_date vacío (""): int("") lanza ValueError.
poster_path llega como "/tjrX2oWRCM3Tvarz38zlZM7Uc10.jpg" La URL completa es TMDB_URL_IMAGEN + poster_path (verificado con los pósteres del curso). Puede llegar None.
overview es la descripción Con language=es-CO suele venir en español; si no existe traducción, puede venir vacía.

Ojo con el id: el id de TMDB (tmdb_id) no es el id de tu base de datos (nueva.id). Para redirigir a la edición usa el de tu base.

📖 Condiciones de uso de la API (adaptación)

TMDB es gratuita para uso no comercial, pero sus condiciones piden dar crédito: mostrar su logotipo o el aviso de que el producto usa la API de TMDB sin estar respaldado por ella. Revisa la página de condiciones de la API y agrégalo en el pie de página.

⚠️ Errores frecuentes

Error Causa Solución
KeyError: 'results' La respuesta fue un error (clave inválida) raise_for_status() y revisa la clave en .env
La portada muestra el póster roto Guardaste solo poster_path sin la URL base Concatena la base de la imagen
ValueError: invalid literal for int() release_date vacío Comprueba antes de convertir
La edición abre otra película Usaste el id de TMDB en lugar del de tu base nueva.id después del commit()
IntegrityError al agregar La película ya estaba en tu lista Revisa si existe y muestra un aviso

5. Requisito 5: ordenar y numerar por calificación

(Fuente: 457-requirement-5-be-able-to-sort-and-rank-the-movies-by-rating.md)

📝 Ejercicio original del curso

El frente de las tarjetas nuevas dice None, porque no se ha asignado ranking. Debe mostrar la posición según tu calificación. Si The Matrix tiene 9.2 y Spirited Away 9.5, Spirited Away es la 1 y The Matrix la 2. Si agregas Parasite con 9.9, pasa al primer lugar; si luego la editas a 8.9, baja al último.

  • Puedes asignar el ranking al visitar la ruta /, trabajando con los objetos Result y ScalarResult que devuelve la consulta. "Busca cómo convertir los datos en una lista de Python".
  • Pista 1: mira cómo se usa order_by() en la documentación.
  • Pista 2: no hace falta cambiar index.html.
  • Pista 3: solo cambias la función home().

🔍 Antes de programar

  • Si ordenas de menor a mayor calificación, ¿qué posición le toca a la primera película de la lista? ¿Y a la última?
  • ¿Por qué hace falta convertir el resultado en una lista? (Pista: len() y recorrerla con índices.)
  • ¿Dónde debe quedar una película que aún no tiene calificación?

📖 Verificación del algoritmo (adaptación)

Con la idea "ordenar de menor a mayor y asignar len(lista) - i", probado con Matrix 9.3, Spirited Away 9.5, Parasite 8.9 y una película sin calificación:

Película Calificación Ranking asignado
Spirited Away 9.5 1
Matrix 9.3 2
Parasite 8.9 3
(sin calificación) None 4

En SQLite, los valores NULL quedan primero al ordenar de menor a mayor, así que la película sin calificación termina en el último lugar (verificado). Otras bases de datos ordenan los NULL al revés; si cambias de base, revísalo.

Los empates: dos películas con 9.0 reciben posiciones distintas (por ejemplo, 3 y 4), según el orden en que las devuelva la consulta. ¿Es justo? Decide tú una regla y compárala con otras (Reto 4 de la sección 6).

💡 El título del proyecto dice "10 películas", pero el código no limita la cantidad. ¿Qué harías con la película número 11? (Reto 2 de la sección 6.)


6. 🚀 Proyecto del día: Mis 10 películas favoritas

(Integra los requisitos 1 a 5 del curso: 453 a 457.)

Problema

Quieres una lista personal de tus películas favoritas, ordenada por tu calificación, sin tener que escribir a mano los datos de cada película.

Objetivo

Un sitio Flask con los 5 requisitos del curso: ver, editar, borrar, agregar con TMDB y ordenar.

Requisitos funcionales

Los 5 requisitos de las secciones 1 a 5.

Restricciones técnicas

  • Clave de TMDB en .env y .env fuera de cualquier repositorio.
  • FloatField con NumberRange(0, 10) para la calificación.
  • get_or_404 para ids inexistentes; timeout en las llamadas a la API.
  • Crédito a TMDB en el pie de página.

Planificación

  1. Comprender las plantillas del proyecto de inicio. 2. Entradas: título buscado, película elegida, calificación y reseña. 3. Procesos: buscar → elegir → detallar → guardar → calificar → ordenar. 4. Salidas: tarjetas ordenadas. 5. Dividir en los 5 requisitos. 6. Diseñar la tabla (¿qué campos pueden quedar vacíos?). 7. Implementar. 8. Probar. 9. Corregir. 10. Mejorar.

Construcción paso a paso

  • Versión 1: modelo y dos películas de prueba en la portada.
  • Versión 2: edición.
  • Versión 3: borrado.
  • Versión 4: búsqueda y selección con TMDB.
  • Versión 5: guardar los detalles y redirigir a la edición.
  • Versión final: ranking automático.

Pruebas

Caso Resultado esperado
Portada con Phone Booth y Avatar Dos tarjetas con póster, frente y reverso
Editar con calificación 11 o nueve Error de validación
Buscar "Matrix" Varias opciones con título y año
Elegir una Pasa a la edición; tras guardar, aparece con su ranking
Película con release_date vacío Se guarda sin error (año vacío o 0)
Calificaciones 9.3, 9.5, 9.9 → cambiar 9.9 a 8.9 El orden cambia como en el ejemplo del curso
/edit?id=999 y /edit?id=abc 404
Sin clave de TMDB Mensaje claro, no un KeyError

🧪 Prueba y depuración

Python
@app.route("/find")
def find_movie():
    movie_id = request.args.get("id")
    data = requests.get(f"https://api.themoviedb.org/3/movie/{movie_id}?api_key=abc123def456").json()
    new_movie = Movie(title=data["title"], year=int(data["release_date"][:4]),
                      img_url=data["poster_path"], description=data["overview"])
    db.session.add(new_movie)
    return redirect(url_for("rate_movie", id=movie_id))

Encuentra cinco problemas: la clave está escrita en el código; img_url guarda solo la ruta, sin la URL base; int("") falla si release_date está vacío; falta db.session.commit(); y la redirección usa el id de TMDB en lugar del id de la base de datos.

⭐ Extensión del proyecto

  • Reto 1: mostrar en la portada tu calificación promedio.
  • Reto 2: si agregas la película número 11, avisar que debes quitar una (o borrar automáticamente la de menor calificación, preguntando antes).
  • Reto 3: borrar con un formulario POST.
  • Reto 4 — Pensamiento computacional: define una regla para los empates (por ejemplo, "misma posición para la misma calificación": 1, 2, 2, 4) e impleméntala. Compárala con la del curso.

7. 🌟 Proyecto extra: el ranking de libros del plan lector

(Proyecto creado para esta adaptación; no existe en el curso original.)

Situación

El área de Humanidades quiere que cada curso arme el ranking de los libros del plan lector del año, con reseñas escritas por los estudiantes.

Requisitos

  1. Modelo Libro con título, autor, año, portada, sinopsis, calificación promedio y número de votos.
  2. Agregar libros buscándolos en una API pública de libros (por ejemplo, Open Library, que no exige clave: investiga su documentación de búsqueda) con el mismo flujo buscar → elegir → guardar.
  3. Un formulario para votar (1 a 5) y escribir una reseña corta; la calificación del libro es el promedio de los votos.
  4. La portada muestra el ranking por promedio; con menos de 3 votos, el libro aparece como "Sin suficientes votos" y no entra al ranking.

Decisiones que debes tomar (y justificar)

  • ¿Cómo evitas que una persona vote 20 veces por su libro favorito (sin tener aún inicio de sesión, Día 68)?
  • ¿Las reseñas se publican de inmediato o las revisa el docente?

Casos de prueba

  • Votos 5, 4 y 3 → promedio 4.0.
  • Un libro con 2 votos no aparece numerado en el ranking.
  • Buscar un título inexistente → "No se encontraron libros".

8. 🏗️ Proyecto integrador del bloque avanzado — Etapa 6

(Proyecto creado para esta adaptación. Descripción completa en el tutorial del Día 70.)

La Plataforma del Club — Etapa 6 (hoy):

  1. CRUD completo de actividades: crear, ver, editar y borrar (el borrado con POST).
  2. Formularios de Flask-WTF con validadores adecuados (fechas, números, URL).
  3. Opcional: al crear una actividad al aire libre, consultar el pronóstico del clima para esa fecha en una API (Días 35 y 36), con la clave en .env.
  4. Portada con las próximas 3 actividades ordenadas por fecha.

9. 🧩 Comprueba lo aprendido

Conceptuales

  1. ¿Por qué rating, ranking y review pueden quedar vacíos en la tabla?
  2. ¿Por qué el requisito 4 necesita dos llamadas distintas a la API?
  3. ¿Qué diferencia hay entre el id de TMDB y el id de tu base de datos?
  4. ¿Por qué la clave de la API no debe estar en el código?
  5. ¿Qué pasa con los empates en el ranking del curso?

Análisis de código

Python
movies = db.session.execute(db.select(Movie).order_by(Movie.rating.desc())).scalars().all()
for i, movie in enumerate(movies, start=1):
    movie.ranking = i
db.session.commit()

¿Da el mismo resultado que el algoritmo de la sección 5? ¿Qué cambia con las películas sin calificación?

Predicción

Si release_date = "1999-03-31", ¿qué dan release_date[:4] y release_date.split("-")[0]? ¿Y si release_date = ""?

Depuración

"Agregué una película desde la búsqueda, pero al guardarle la calificación se la puse a otra". ¿Cuál es la causa más probable?

Modificación

Cambia la portada para que muestre solo las 10 películas con mejor calificación, aunque haya más en la base de datos.

Creación

Crea una ruta /estadisticas que muestre cuántas películas hay, la calificación promedio, la mejor y la peor, calculadas con consultas de SQLAlchemy.


10. 🤖 Programar con apoyo de IA

Ciclo de trabajo: formula el problema → inténtalo tú → consulta a la IA → lee y entiende → prueba → compara con la documentación oficial → modifica → explica con tus palabras.

Actividad 1 — Leer la documentación de la API. Pide a una IA la URL y los parámetros para buscar películas en TMDB. Compáralos con developer.themoviedb.org/reference/search-movie. ¿Incluyó language? ¿Usa api_key o Bearer?

Actividad 2 — El algoritmo de ranking. Pide a una IA que asigne rankings con empates ("1, 2, 2, 4"). Pruébalo con cinco películas y dos empates; corrige lo que falle.

Actividad 3 — Secretos. Pregunta a una IA cómo proteger la clave de TMDB en un proyecto Flask. Verifica que su respuesta incluya .env, no subirla a GitHub y cómo leerla en Python (Día 35).


11. 🛠️ Correcciones técnicas al material original

Archivo Afirmación o comportamiento del curso Observación Tratamiento
453 "Tendrás que crear la carpeta instance" Flask-SQLAlchemy 3.1 la crea sola (verificado en el Día 63) Nota técnica de la sección 1
454 Crear un Quick Form Era la macro de Flask-Bootstrap; en Bootstrap-Flask es render_form Nota técnica de la sección 2
455, 453 Borrar con un botón que es un enlace Acciones que modifican datos deben usar POST Nota técnica de la sección 3
456 Enlaces developers.themoviedb.org/3/... Redirigen a developer.themoviedb.org/reference/... (verificado); existen api_key y token Bearer Nota técnica de la sección 4
456 Copiar la clave de la API "en tu proyecto" Debe ir en .env, no en el código (Día 35) Sección 4
456 Guardar año, póster y descripción release_date puede venir vacío y poster_path necesita la URL base Tabla de la sección 4
457 Ordenar por calificación para asignar el ranking Funciona (verificado); los empates reciben posiciones distintas y los NULL se ordenan según la base de datos Sección 5
456 Uso de la API de TMDB Sus condiciones piden dar crédito a TMDB Sección 4

12. 📋 Matriz de trazabilidad

Contenido original Archivo de origen Concepto desarrollado Ejercicio original Reto adicional Proyecto relacionado
Listas de mejores películas (BFI, Empire, NYT), listchallenges, Flask/WTForms/SQLite/SQLAlchemy 451 Sección 0 — Mapa del proyecto Sección 6
Proyecto de inicio, entorno virtual, plantillas 452 Sección 0 Preparar el proyecto — Sección 6
Tabla Movie con 8 campos, título único, instance, Phone Booth, error de integridad, Avatar, tarjetas, .scalars() 453 Sección 1 Requisito 1 Campos que pueden quedar vacíos; datos de prueba sin duplicar Secciones 6 y 7
RateMovieForm, Quick Form en edit.html, actualizar la película 454 Sección 2 Requisito 2 FloatField y NumberRange Sección 6
Botón Delete 455 Sección 3 Requisito 3 Borrar con POST Sección 6
Formulario de título, cuenta y clave de TMDB, búsqueda, "Try it out", select.html, detalles por id, guardar título/imagen/año/descripción, redirigir a la edición 456 Sección 4 Requisito 4 Detalles de los datos; condiciones de uso Secciones 6, 7 y 8
Ranking según la calificación, ejemplos con Matrix, Spirited Away y Parasite, Result/ScalarResult, order_by, pistas 457 Sección 5 Requisito 5 Empates, NULL, límite de 10 Sección 6
(No existe en el curso original) — — — Ranking del plan lector Sección 7
(No existe en el curso original) — — — Plataforma del Club, etapa 6 Sección 8

13. 📦 Evidencias de aprendizaje del Día 64

  • El sitio "Mis 10 películas favoritas" con los 5 requisitos y capturas del flujo completo de agregar una película.
  • Tu .env.ejemplo (sin la clave real) y la línea del pie de página con el crédito a TMDB.
  • La prueba del ranking con el ejemplo del curso (9.3, 9.5, 9.9 → 8.9).
  • Tu regla para los empates (Reto 4 de la sección 6).
  • El ranking del plan lector (proyecto extra).
  • La etapa 6 de la Plataforma del Club.
  • Cuaderno de errores: datos de prueba duplicados, campos obligatorios que deberían ser opcionales, Quick Form contra render_form, calificación como texto, KeyError: 'results', póster sin URL base, release_date vacío, id de TMDB contra id propio, commit() olvidado, borrado con GET.