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 adeveloper.themoviedb.org/reference/search-moviey/reference/movie-details. No se creó una cuenta en TMDB, así que el flujo con clave real no se ejecutó.
Tabla de contenidos
- Antes de empezar: listas de favoritos
- Requisito 1: ver las películas
- Requisito 2: editar la calificación y la reseña
- Requisito 3: borrar películas
- Requisito 4: agregar películas con la API de TMDB
- Requisito 5: ordenar y numerar por calificación
- 🚀 Proyecto del día: Mis 10 películas favoritas
- 🌟 Proyecto extra: el ranking de libros del plan lector
- 🏗️ Proyecto integrador del bloque avanzado — Etapa 6
- 🧩 Comprueba lo aprendido
- 🤖 Programar con apoyo de IA
- 🛠️ Correcciones técnicas al material original
- 📋 Matriz de trazabilidad
- 📦 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
requestsy 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)
┌──────────── 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 41. Requisito 1: ver las películas
(Fuente: 453-requirement-1-be-able-to-view-movie-list-items.md)
📝 Ejercicio original del curso
- Crea una base de datos SQLite con SQLAlchemy con una tabla
Moviey los campos:id,title,year,description,rating,ranking,review,img_url. Eltitledebe ser único (una entrada por película). "Recuerda que la base de datos se guarda en la carpetainstance, así que tendrás que crearla". - Con código o con DB Browser, agrega esta película:
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.
- Agrega una segunda película, "Avatar The Way of Water" (2022, calificación 7.3, ranking 9, reseña "I liked the water.").
- 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
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
instanceautomá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:
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.
- Con WTForms crea
RateMovieFormy muéstralo enedit.htmlcomo Quick Form. No hace falta cambiaredit.html: ya tiene lo necesario para renderizar el formulario ("para que no se haga un formulario HTML simple"). - Cuando el formulario se envíe y sea válido, actualiza la película correspondiente en la base de datos.
📖 Explicación conceptual
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, devuelveNoneyget_or_404responde 404.FloatField+NumberRangegarantizan que la calificación sea un número entre 0 y 10. (Con unStringField, "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 esrender_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
@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.
- 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.
- Al enviar el título, el servidor usa
requestspara 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.htmlcon el título y el año de cada resultado, para que el usuario elija (suele haber varias películas con nombres parecidos).
- 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,yearydescription. Luego redirige a la portada ("faltarán algunos datos, no pasa nada"). - 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 adeveloper.themoviedb.org/reference/search-moviey/reference/movie-details. TMDB ofrece dos credenciales: la clave de API (parámetroapi_key) y el token de lectura (cabeceraAuthorization: Bearer ...); cualquiera sirve. Sin credencial, la API responde{"status_code":7,"status_message":"Invalid API key: You must be granted a valid key."}.
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:
@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 objetosResultyScalarResultque 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
.envy.envfuera de cualquier repositorio. FloatFieldconNumberRange(0, 10)para la calificación.get_or_404para ids inexistentes;timeouten las llamadas a la API.- Crédito a TMDB en el pie de página.
Planificación
- 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
@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
- Modelo
Librocon título, autor, año, portada, sinopsis, calificación promedio y número de votos. - 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.
- Un formulario para votar (1 a 5) y escribir una reseña corta; la calificación del libro es el promedio de los votos.
- 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):
- CRUD completo de actividades: crear, ver, editar y borrar (el borrado con POST).
- Formularios de Flask-WTF con validadores adecuados (fechas, números, URL).
- 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. - Portada con las próximas 3 actividades ordenadas por fecha.
9. 🧩 Comprueba lo aprendido
Conceptuales
- ¿Por qué
rating,rankingyreviewpueden quedar vacíos en la tabla? - ¿Por qué el requisito 4 necesita dos llamadas distintas a la API?
- ¿Qué diferencia hay entre el id de TMDB y el id de tu base de datos?
- ¿Por qué la clave de la API no debe estar en el código?
- ¿Qué pasa con los empates en el ranking del curso?
Análisis de código
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 Formcontrarender_form, calificación como texto,KeyError: 'results', póster sin URL base,release_datevacío, id de TMDB contra id propio,commit()olvidado, borrado con GET.