Este tutorial adapta pedagógicamente el Día 33. Las secciones 📝 Ejercicio original del curso provienen de los archivos del curso. Los retos 🟢🟡🟠🔴, el proyecto extra y el proyecto integrador son creaciones de esta adaptación. Las correcciones al material original aparecen como 🛠️ Nota técnica.
Nota sobre las verificaciones: las API se consultaron en vivo el 26 de septiembre de 2026 con Python 3.14 y
requests2.34. Las API públicas cambian o desaparecen con el tiempo: si alguna no responde igual, ese también es un aprendizaje (sección 3). El archivo inicial del desafío de Kanye y el del proyecto de la ISS no están en la carpeta.
Tabla de contenidos
- Antes de empezar
- ¿Qué es una API?
- Endpoints y la primera petición con
requests - Respuestas: códigos HTTP, excepciones y JSON
- Desafío: la máquina de citas de Kanye
- Parámetros de una API: la hora del amanecer y del atardecer
- 🚀 Proyecto del día: el Notificador de la ISS
- 🌟 Proyecto extra: el Reloj Solar de Colombia
- 🏆 Proyecto integrador de los Días 32 y 33: la Alerta del Club de Astronomía
- 🧩 Comprueba lo aprendido
- 🤖 Programar con apoyo de IA
- 🛠️ Correcciones técnicas al material original
- 📋 Matriz de trazabilidad
- 📦 Evidencias de aprendizaje del Día 33
0. Antes de empezar
(Fuente: 252 - Day 33 Goals- what you will make by the end of the day.md)
Hoy aprenderemos a usar APIs (Application Programming Interfaces, interfaces de programación de aplicaciones) para traer datos en vivo de internet a nuestros programas.
El proyecto: la Estación Espacial Internacional (ISS) da muchas vueltas a la Tierra cada día. Nuestro programa averiguará dónde está en este momento y, si pasa sobre nosotros y es de noche (para poder verla), nos enviará un correo que diga: "¡Mira hacia arriba!".
Prerequisito general: diccionarios anidados y JSON (Días 9 y 30); excepciones y
raise(Día 30);splitde cadenas;datetimeysmtplib(Día 32); Tkinter yitemconfig(Días 27 a 31); funciones que devuelven valores (Día 10).
💡 Para el docente: el envío de correos puede probarse con el servidor local del Día 32 (sección 1 de ese tutorial), sin cuentas reales.
1. ¿Qué es una API?
(Fuente: 253 - What are Application Programming Interfaces (APIs)-.md)
🎯 Objetivo de aprendizaje
Explicar qué es una API y por qué los programas la necesitan para obtener datos de otros sistemas.
📖 Explicación conceptual
Muchos sitios web tienen datos valiosos: el pronóstico del clima, el precio de las criptomonedas, las estadísticas de los jugadores de baloncesto. ¿Cómo los usamos en nuestros programas?
Una API es un conjunto de comandos, funciones, protocolos y objetos que los programadores pueden usar para crear software o interactuar con un sistema externo. Funciona como una barrera con reglas: si tu petición cumple las reglas de la API, el sistema te responde con los datos; si no, la rechaza.
La analogía del restaurante (del curso):
| Restaurante | API |
|---|---|
| La cocina, donde están los ingredientes | Los datos del sistema externo |
| El menú: lo que puedes pedir | La API: lo que puedes solicitar |
| No puedes entrar a la cocina y tomar lo que quieras | No puedes acceder directamente a la base de datos del sitio |
Retos adicionales (creados para esta adaptación)
- 🟢 Reto 1: menciona tres aplicaciones de tu celular que probablemente usan una API (por ejemplo, una aplicación del clima). ¿Qué datos pedirían?
- 🟡 Reto 2: explica la analogía del restaurante con otra situación de tu colegio (por ejemplo, la secretaría académica y los certificados de notas).
- 🟠 Reto 3: ¿por qué una empresa querría ofrecer una API pública y gratuita? ¿Y por qué otra no querría?
- 🔴 Reto 4: investiga si alguna entidad colombiana ofrece datos abiertos por API (por ejemplo, el portal de datos abiertos del Gobierno). Anota qué datos ofrece y en qué formato.
2. Endpoints y la primera petición con requests
(Fuente: 254 - API Endpoints and Making API Calls.md)
🎯 Objetivo de aprendizaje
- Identificar el endpoint de una API.
- Hacer una petición
GETcon la bibliotecarequests.
📖 Explicación conceptual
Endpoint (punto final): es la dirección donde están los datos, normalmente una URL. Si quieres sacar dinero de un banco, necesitas saber dónde queda.
Petición (request): es como ir al banco y hablar con el cajero, que actúa como la API: no te deja entrar a la bóveda, pero atiende tus solicitudes. Algunas requieren identificarte (retirar dinero); otras no (preguntar el horario). Hoy usaremos APIs del segundo tipo: no piden identificación.
La API de la ubicación de la ISS:
http://api.open-notify.org/iss-now.jsonSi pegas esa dirección en el navegador, verás algo así (respuesta real del 26/09/2026):
{"message": "success", "iss_position": {"latitude": "29.1542", "longitude": "40.0602"}, "timestamp": 1790444272}Si recargas la página, los números cambian: la ISS se mueve muy rápido.
💡 El curso recomienda una extensión del navegador para ver el JSON en forma de árbol (JSON Viewer Awesome). Algunos navegadores, como Firefox, ya lo muestran así sin instalar nada.
¿Por qué JSON? La analogía del curso: un mueble de IKEA viene en un paquete plano para transportarlo fácil, y en casa lo armas. JSON es ese paquete plano: texto compacto que viaja rápido por internet y que en Python "armamos" de nuevo como diccionario.
🔎 Ejemplo explicado
import requests
response = requests.get(url="http://api.open-notify.org/iss-now.json")
print(response) # <Response [200]>requestsno viene con Python: hay que instalarlo (en PyCharm, con el bombillo rojo; o conpip install requests).requests.gethace una petición de tipo GET (obtener datos) al endpoint.- Al imprimir la respuesta no vemos el JSON, sino
<Response [200]>: un código de estado. Lo estudiamos en la siguiente sección.
⚠️ Errores frecuentes
| Error | Qué ocurre | Solución |
|---|---|---|
import request (sin la s) |
ModuleNotFoundError |
La biblioteca se llama requests |
ModuleNotFoundError: No module named 'requests' |
No está instalada en el intérprete del proyecto | Instálala |
| Sin conexión a internet | ConnectionError |
Revisa la conexión; maneja la excepción (sección 3) |
| Copiar mal el endpoint | Código 404 | Copia la URL exacta de la documentación |
Retos adicionales (creados para esta adaptación)
- 🟢 Reto 1: abre el endpoint en el navegador 3 veces con un minuto de diferencia y anota la latitud y longitud. ¿Hacia dónde se mueve la ISS?
- 🟡 Reto 2: haz la misma petición desde Python e imprime
response.text. ¿Qué tipo de dato es? - 🟠 Reto 3: mide cuánto tarda la respuesta (investiga
response.elapsed). - 🔴 Reto 4: el endpoint
http://api.open-notify.org/astros.jsondice quiénes están en el espacio. Consúltalo y compara la lista con una fuente de noticias reciente. ¿Coinciden? (Ver sección 3, "Datos que no son confiables".)
3. Respuestas: códigos HTTP, excepciones y JSON
(Fuente: 255 - Working with Responses- HTTP Codes, Exceptions & JSON Data.md)
🎯 Objetivo de aprendizaje
- Interpretar los códigos de estado HTTP.
- Lanzar una excepción cuando la petición falla.
- Extraer datos del JSON de la respuesta.
📖 Los códigos de estado
El primer dígito resume lo que pasó:
| Código | Significado | Ejemplo |
|---|---|---|
| 1xx | Espera, algo está en proceso (informativo) | — |
| 2xx | ¡Éxito! Aquí están tus datos | 200 OK |
| 3xx | Redirección: el recurso está en otra parte | 301 Moved Permanently |
| 4xx | Tú cometiste un error | 400 Bad Request, 401 Unauthorized, 404 Not Found |
| 5xx | El servidor cometió un error | 500 Internal Server Error |
🛠️ Nota técnica: en el video, los 3xx se describen como "no tienes permiso, vete". En realidad significan redirección: el recurso se movió a otra dirección. La falta de permiso se indica con códigos 4xx, como 401 (no autenticado) o 403 (prohibido). Consulta la referencia de códigos de estado de MDN (se abre en otra pestaña).
📖 ¿Qué hacer si la petición falla?
El curso muestra el razonamiento paso a paso:
- Tentación:
if response.status_code != 200: print("error"). Imprimir no sirve: el programa sigue como si nada. - Mejor: lanzar una excepción,
raise Exception("Bad response from ISS API"). Pero es demasiado general. - Más específico: un
if/elifpara 404, 401, … Pero hay decenas de códigos: imposible cubrirlos todos. - Solución: que
requestslance la excepción adecuada:
response = requests.get(url="http://api.open-notify.org/iss-now.json")
response.raise_for_status()Si el código no es de éxito, raise_for_status() lanza una HTTPError con un mensaje claro. Con un endpoint mal escrito (verificado):
HTTPError: 404 Client Error: Not Found for url: http://api.open-notify.org/iss-now.jsonx📖 Extraer los datos
data = response.json()
longitude = data["iss_position"]["longitude"]
latitude = data["iss_position"]["latitude"]
iss_position = (longitude, latitude)
print(iss_position)response.json()convierte el JSON en un diccionario de Python.iss_positiones un diccionario anidado: se accede con dos pares de corchetes.- ⚠️ La latitud y la longitud llegan como texto (
"29.1542"), no como números. Para compararlas hay que usarfloat(...)(sección 6).
Para ver en un mapa dónde está la ISS, el curso usa latlong.net (se abre en otra pestaña) (Lat Long to Address).
📖 Datos que no son confiables (análisis de esta adaptación)
raise_for_status() solo detecta los errores que el servidor reporta con su código. Dos casos reales, verificados el 26/09/2026:
http://api.open-notify.org/astros.jsonresponde 200, pero la lista de astronautas corresponde a una tripulación de 2024: los datos están desactualizados.https://restcountries.com/v3.1/..., una API de países muy usada en tutoriales, responde 200 pero su contenido dice"success": falsey que esa versión fue retirada.
Conclusión: un código 200 significa "el servidor respondió", no "los datos son correctos". Revisa también el contenido y compáralo con otras fuentes.
⚠️ Errores frecuentes
| Error | Qué ocurre | Solución |
|---|---|---|
response.json sin paréntesis |
Obtienes el método, no los datos | response.json() |
data["longitude"] |
KeyError: la longitud está dentro de iss_position |
data["iss_position"]["longitude"] |
Comparar "29.15" > 5 |
TypeError entre str e int |
Convierte con float() |
Olvidar raise_for_status() |
El programa sigue con datos vacíos o de error | Llámalo justo después de la petición |
Retos adicionales (creados para esta adaptación)
- 🟢 Reto 1: provoca a propósito un 404 y captúralo con
try/except requests.exceptions.HTTPError, mostrando un mensaje amable. - 🟡 Reto 2: desconecta el internet y ejecuta la petición. ¿Qué excepción aparece? Captúrala también.
- 🟠 Reto 3: convierte el
timestampde la respuesta en una fecha legible (investigadatetime.fromtimestamp). - 🔴 Reto 4: escribe
obtener_posicion_iss()que devuelva una tupla de floats(lat, lng), oNonesi hubo cualquier error (HTTP, conexión o tiempo de espera contimeout=10).
4. Desafío: la máquina de citas de Kanye
(Fuente: 256 - Challenge - Build a Kanye Quotes App using the Kanye Rest API.md)
📝 Ejercicio original del curso
Enunciado: el curso entrega una aplicación Tkinter ya construida (con background.png y kanye.png) que muestra una cita dentro de un lienzo. Al pulsar el botón con la cara de Kanye West se llama get_quote(). Tu trabajo: completar esa función para que traiga una cita aleatoria de la API https://api.kanye.rest y la muestre en quote_text.
La API devuelve un JSON con una sola clave (verificado):
{"quote": "People always tell you 'Be humble. Be humble.' When was the last time someone told you to be amazing? ..."}🔍 Antes de programar
- ¿Qué clave del JSON contiene la cita?
- ¿Cómo se cambiaba el texto de un elemento del lienzo? (Día 31)
🔎 La solución del curso
def get_quote():
response = requests.get("https://api.kanye.rest")
response.raise_for_status()
data = response.json()
quote = data["quote"]
canvas.itemconfig(quote_text, text=quote)🏫 Nota para el aula (adaptación): las citas llegan de una API externa y no podemos controlar su contenido. Si el docente prefiere otra fuente, la API
https://api.adviceslip.com/advice(consejos cortos en inglés, sin clave) funciona igual; su JSON es anidado:data["slip"]["advice"](verificado).
Si no tienes el archivo inicial: construye una ventana con un
Canvasque tenga un texto (quote_text) y un botón que llame aget_quote. Es la estructura de Flashy (Día 31), más sencilla.
Retos adicionales (creados para esta adaptación)
- 🟢 Reto 1: muestra la primera cita al abrir la aplicación, sin esperar al botón.
- 🟡 Reto 2: si la API falla (sin internet), muestra en el lienzo "No se pudo obtener la cita" en lugar de cerrar el programa.
- 🟠 Reto 3: agrega un botón Guardar que añada la cita actual a un archivo
favoritas.txt, sin repetirlas. - 🔴 Reto 4: las citas largas se salen del lienzo. Investiga el parámetro
widthdecreate_texty reduce el tamaño de la letra si la cita supera cierto número de caracteres.
5. Parámetros de una API: la hora del amanecer y del atardecer
(Fuente: 257 - Understand API Parameters- Match Sunset Times with the Current Time.md)
🎯 Objetivo de aprendizaje
- Enviar parámetros en una petición.
- Leer la documentación de una API para saber qué parámetros acepta.
- Extraer la hora de una fecha en formato ISO con
split.
📖 Explicación conceptual
Los parámetros son entradas que cambian la respuesta, igual que los argumentos de una función. En el banco: en vez de "¿cuál es su horario?", preguntas "¿a qué hora cierran el lunes?".
- Algunas APIs no tienen parámetros (la de la ISS).
- Otras tienen parámetros obligatorios y opcionales. Los opcionales tienen un valor por defecto, como los parámetros de las funciones.
La API de amanecer y atardecer: https://api.sunrise-sunset.org/json
| Parámetro | Tipo | ¿Obligatorio? | Para qué |
|---|---|---|---|
lat |
decimal | Sí | Latitud |
lng |
decimal | Sí | Longitud |
date |
fecha | No (por defecto, hoy) | Otro día |
formatted |
0 o 1 | No (por defecto, 1) | 1: reloj de 12 horas con AM/PM; 0: formato ISO 8601 |
tzid |
zona horaria | No (por defecto, UTC) | Devolver las horas en otra zona horaria |
🔎 La solución del curso, paso a paso
1. Sin parámetros: error 400 (Bad Request): ¿cómo va a saber la hora del amanecer si no le dices dónde estás? (Verificado: {"results":"","status":"INVALID_REQUEST"}.)
2. Con los parámetros en un diccionario: las claves deben ser exactamente las de la documentación. Tu latitud y longitud se buscan en latlong.net; incluye el signo negativo si lo tiene (en Colombia, la longitud es negativa):
MY_LAT = 4.711 # ejemplo: Bogotá (busca las de tu municipio)
MY_LONG = -74.0721
parameters = {
"lat": MY_LAT,
"lng": MY_LONG,
"formatted": 0,
}
response = requests.get("https://api.sunrise-sunset.org/json", params=parameters)
response.raise_for_status()
data = response.json()
sunrise = data["results"]["sunrise"]
sunset = data["results"]["sunset"]3. Los parámetros en la URL: requests los agrega así: https://api.sunrise-sunset.org/json?lat=4.711&lng=-74.0721&formatted=0. El ? inicia los parámetros y el & los separa. Puedes pegar esa URL en el navegador para ver la respuesta.
4. Extraer la hora con split: con formatted=0, la respuesta es "2026-09-26T10:43:35+00:00" (verificado):
sunrise_hour = int(sunrise.split("T")[1].split(":")[0])| Paso | Resultado |
|---|---|
sunrise.split("T") |
["2026-09-26", "10:43:35+00:00"] |
[1] |
"10:43:35+00:00" |
.split(":") |
["10", "43", "35+00", "00"] |
[0] y int(...) |
10 |
5. Comparar con la hora actual: datetime.now().hour, que ya está en formato de 24 horas.
🛠️ Nota técnica (muy importante en Colombia): las horas de esta API están en UTC (fíjate en el
+00:00). El curso se grabó en Londres, donde la hora local coincide con UTC buena parte del año, así que no se nota el problema. En Colombia (UTC−5), para Bogotá el 26/09/2026 la API dice amanecer a las 10 y atardecer a las 22 (verificado), pero en la hora local son las 5:43 y las 17:51. Si comparas esas horas condatetime.now().hour, que es la hora local, el resultado es incorrecto. Solución: agrega el parámetro"tzid": "America/Bogota"y la API devuelve"2026-09-26T05:43:35-05:00"(verificado).
⚠️ Errores frecuentes
| Error | Qué ocurre | Solución |
|---|---|---|
"lon" o "long" en lugar de "lng" |
Error 400 | Usa las claves de la documentación |
| Olvidar el signo negativo de la longitud | Obtienes los datos de un lugar en Asia | Copia el signo |
data["sunrise"] |
KeyError: está dentro de results |
data["results"]["sunrise"] |
Comparar "10" < 17 |
TypeError |
Convierte con int() |
No usar tzid en Colombia |
Horas corridas 5 horas | "tzid": "America/Bogota" |
Retos adicionales (creados para esta adaptación)
- 🟢 Reto 1: imprime la hora del amanecer y del atardecer de tu municipio en formato
05:43. - 🟡 Reto 2: usa el parámetro
datepara averiguar la hora del atardecer del día de tu cumpleaños. - 🟠 Reto 3: usa
day_length(duración del día en segundos) y muéstralo en horas y minutos. - 🔴 Reto 4: extrae la hora y los minutos para decidir si "ya se puso el sol" con precisión de minutos, no solo de horas. (Investiga
datetime.fromisoformat, que convierte el texto completo en un objetodatetime.)
6. 🚀 Proyecto del día: el Notificador de la ISS
(Fuente: 258 - ISS Overhead Notifier Project - Challenge & Solution.md)
Problema
Enviarte un correo cuando la ISS esté cerca de tu posición y sea de noche.
Contexto
La ISS solo se ve a simple vista cuando el cielo está oscuro y pasa relativamente cerca. El programa combina dos APIs (posición de la ISS y amanecer/atardecer), datetime y smtplib.
Requisitos funcionales (del curso)
- Si la ISS está cerca de tu posición (±5 grados de latitud y de longitud)…
- …y es de noche…
- …envíate un correo: "¡Mira hacia arriba! La ISS está sobre ti".
- Desafío extra del curso: repetir la comprobación cada 60 segundos.
Pistas del curso:
- Las horas del amanecer y del atardecer son texto: conviértelas con
int(). - La posición de la ISS también es texto: conviértela con
float(). - Crea una función que devuelva
Truesi la ISS está a ±5 grados.
Planificación
🔍 Antes de programar
- Entradas: tu latitud y longitud, la posición de la ISS, la hora del amanecer, del atardecer y la actual.
- Proceso: dos preguntas de sí o no. ¿Cuáles? ¿Qué función responde cada una?
- Salida: un correo.
- Si tu latitud es 4,7, ¿entre qué valores debe estar la latitud de la ISS para que cuente como "cerca"?
Construcción paso a paso (solución del curso)
Versión 1 — ¿Está cerca la ISS?
def is_iss_overhead():
response = requests.get(url="http://api.open-notify.org/iss-now.json")
response.raise_for_status()
data = response.json()
iss_latitude = float(data["iss_position"]["latitude"])
iss_longitude = float(data["iss_position"]["longitude"])
if MY_LAT - 5 <= iss_latitude <= MY_LAT + 5 and MY_LONG - 5 <= iss_longitude <= MY_LONG + 5:
return TrueLa expresión a <= x <= b comprueba que x esté entre a y b.
Versión 2 — ¿Es de noche?
def is_night():
parameters = {"lat": MY_LAT, "lng": MY_LONG, "formatted": 0}
response = requests.get("https://api.sunrise-sunset.org/json", params=parameters)
response.raise_for_status()
data = response.json()
sunrise = int(data["results"]["sunrise"].split("T")[1].split(":")[0])
sunset = int(data["results"]["sunset"].split("T")[1].split(":")[0])
time_now = datetime.now().hour
if time_now >= sunset or time_now <= sunrise:
return TrueEl curso encontró un error: primero comparaba datetime.now() (un objeto datetime) con números enteros. La corrección fue usar datetime.now().hour.
Versión 3 — El correo. Si is_iss_overhead() and is_night(), se envía el correo como en el Día 32.
Versión final — Repetir cada 60 segundos:
while True:
time.sleep(60)
if is_iss_overhead() and is_night():
# enviar el correoEl programa ya no termina: sigue funcionando mientras el computador esté encendido.
Pruebas
Es difícil esperar a que la ISS pase de noche. Para probar, simula las condiciones:
| Caso | Cómo simularlo | Esperado |
|---|---|---|
| ISS cerca y de noche | Temporalmente, pon MY_LAT y MY_LONG iguales a la posición actual de la ISS, y fuerza is_night a devolver True |
Llega el correo |
| ISS lejos | Posición real | No llega nada |
| Es de día | Fuerza is_night a False |
No llega nada |
| Sin internet | Desconecta la red | ⚠️ (ver 🧪) |
Probar is_night en Colombia a las 7:00 p. m. |
Sin tzid |
⚠️ (ver 🧪) |
🧪 Prueba y depuración
- La hora UTC (análisis de esta adaptación). Con las horas UTC de Bogotá (amanecer 10, atardecer 22) y la hora local, así responde el
is_night()del curso:
| Hora local en Bogotá | ¿Está oscuro de verdad? | is_night() del curso |
|---|---|---|
| 6:00 a. m. | No (ya amaneció) | True ❌ |
| 12:00 m. | No | False ✅ |
| 7:00 p. m. | Sí | False ❌ |
| 9:00 p. m. | Sí | False ❌ |
| 11:00 p. m. | Sí | True ✅ |
(Simulación verificada.) Corrige la función con tzid y repite la tabla.
- Correos repetidos. Con el bucle cada 60 s, mientras la ISS esté cerca llega un correo por minuto. ¿Cómo haces que llegue uno solo por paso de la ISS?
- Peticiones innecesarias. El bucle consulta la hora del amanecer cada minuto, aunque solo cambia una vez al día. Guárdala y consúltala una sola vez por día.
- Sin internet o API caída. Una sola falla lanza una excepción y detiene el bucle para siempre. Captura
requests.exceptions.RequestExceptiony agregatimeout=10a cada petición para que el programa no se quede esperando indefinidamente. - La línea de cambio de fecha. Si tu longitud fuera 178, la ISS en −179 está a solo 3 grados, pero la resta da 357. No afecta a Colombia, pero ¿cómo lo corregirías?
⭐ Extensión del proyecto
- Mostrar en el correo la posición exacta y un enlace a un mapa.
- Registrar en un CSV cada vez que la ISS pasa cerca (fecha, hora, posición).
- Usar el "crepúsculo civil" (
civil_twilight_end) en lugar del atardecer: el cielo está más oscuro.
7. 🌟 Proyecto extra: el Reloj Solar de Colombia
Situación
En clase de Ciencias Naturales se preguntan: ¿por qué en Colombia los días duran casi lo mismo todo el año, mientras que en otros países cambian mucho? Vamos a comprobarlo con datos reales.
Requisitos
- Un diccionario con al menos 6 ciudades: 4 de Colombia de distintas regiones (por ejemplo, Leticia, Riohacha, San Andrés y tu municipio) y 2 lejos del ecuador (por ejemplo, Ushuaia y Oslo). Busca tú sus coordenadas en latlong.net.
- Para cada ciudad, consultar la API de amanecer y atardecer con
formatted=0y sutzid(investiga la zona horaria de cada ciudad). - Mostrar una tabla con: ciudad, amanecer, atardecer y duración del día en horas y minutos (
day_length). - Repetir la consulta para cuatro fechas del año (21 de marzo, 21 de junio, 23 de septiembre y 21 de diciembre) usando el parámetro
date. - Guardar los resultados en
reloj_solar.csvcon pandas. - Responder con los datos: ¿cuántos minutos varía la duración del día en Bogotá entre junio y diciembre? ¿Y en Oslo?
- Manejar errores de red sin detener el programa: si una ciudad falla, se informa y se sigue con las demás.
Decisiones que debes tomar
- ¿Cómo organizas el código para no repetir la petición 24 veces (6 ciudades × 4 fechas)?
- ¿Muestras los resultados en la consola, en Tkinter o en ambos?
Casos de prueba
| Caso | Esperado |
|---|---|
| Ciudad colombiana en junio y diciembre | Diferencia de pocos minutos |
| Oslo en junio y diciembre | Diferencia de muchas horas |
| Coordenadas inválidas (latitud 200) | Mensaje de error para esa ciudad; el programa continúa |
8. 🏆 Proyecto integrador de los Días 32 y 33: la Alerta del Club de Astronomía
Proyecto integrador creado para esta adaptación. Combina correo y fechas (Día 32) con APIs, parámetros y manejo de respuestas (Día 33), y recupera archivos, CSV y JSON (Días 24 a 30).
Situación problema
El club de astronomía del colegio quiere observar la ISS. Necesita un sistema que avise a los miembros del club cuando la ISS pase cerca de noche, sin llenarlos de correos, y que lleve un registro de los pasos para planear observaciones.
Pregunta orientadora
¿Cómo construir un programa que tome decisiones con datos en vivo de varias APIs y que siga funcionando aunque internet o una API fallen?
Objetivo
Construir un servicio de consola que revise cada minuto la posición de la ISS y envíe una alerta por paso a los miembros del club.
Requerimientos
miembros.csvcon nombres y correos ficticios, y un campoactivo(sí/no).config.jsoncon la ubicación del colegio, el margen en grados, el intervalo de revisión y el servidor de correo.- Hora del amanecer y del atardecer con
tzidcorrecto, consultada una vez al día. - Detección de "paso": desde que la ISS entra en el margen hasta que sale. Un solo correo por paso.
- Correo con
EmailMessage(tildes y ñ correctas) a todos los miembros activos. pasos.csvcon cada paso detectado: fecha, hora de inicio, hora de fin y si fue de noche.- Manejo de
HTTPError,ConnectionErroryTimeout: el programa espera y vuelve a intentar, sin detenerse. - Un modo simulación (en
config.json) que use posiciones falsas de la ISS para poder probar en clase.
Restricciones
- Todas las peticiones con
timeout. - Ninguna contraseña en el código (variables de entorno, Día 32).
- Funciones separadas para: obtener posición, decidir si está cerca, decidir si es de noche, enviar correo y registrar.
Etapas de desarrollo
| Etapa | Entregable parcial |
|---|---|
| 1. Diseño | Diagrama de flujo del bucle y estados ("fuera", "pasando") |
| 2. Funciones de datos | obtener_posicion y horas_de_sol con manejo de errores |
| 3. Lógica | Detección de paso probada en modo simulación |
| 4. Correo y registro | Correos al servidor local y pasos.csv |
| 5. Robustez | Prueba desconectando internet durante la ejecución |
| 6. Presentación | Demostración en modo simulación y explicación de una ejecución real |
Entregables
- Código organizado en funciones,
config.jsony datos ficticios. - Tabla de 10 casos de prueba con resultados.
- Registro de errores con al menos 5 errores reales.
pasos.csvde al menos 24 horas de ejecución real (si es posible) o de la simulación.
Casos de prueba mínimos
| Caso | Esperado |
|---|---|
| Simulación: la ISS entra en el margen durante 5 minutos, de noche | Un correo; un registro en pasos.csv |
| Simulación: paso de día | Sin correo; registro con "de noche: no" |
| Internet desconectado 3 minutos | Mensajes de aviso; el programa sigue y se recupera |
Miembro con activo = no |
No recibe correo |
Criterios técnicos de funcionamiento
- El programa corre al menos 1 hora sin detenerse.
- La API de amanecer y atardecer se consulta una vez al día, no cada minuto.
- Cambiar la ubicación o el margen solo requiere editar
config.json.
Posibles extensiones
Interfaz Tkinter con un mapa sencillo y la posición de la ISS actualizada con after; estadística semanal de pasos nocturnos.
9. 🧩 Comprueba lo aprendido
Conceptuales
- Explica con tus palabras qué es un endpoint y qué es un parámetro.
- ¿Qué significa un código 404? ¿Y un 500? ¿De quién es la culpa en cada caso?
- ¿Por qué
raise_for_status()es mejor que escribir unifpara cada código? - ¿Por qué un código 200 no garantiza que los datos sean correctos?
Análisis de código
parameters = {"lat": MY_LAT, "lng": MY_LONG, "formatted": 0}
response = requests.get("https://api.sunrise-sunset.org/json", params=parameters)¿Qué URL completa se envía? ¿Qué pasaría si quitaras "lng"?
Predicción
- ¿Qué devuelve
"2026-09-26T17:51:28-05:00".split("T")[1].split(":")[0]? ¿De qué tipo es? - Con
MY_LAT = 4.7, ¿la ISS en latitud"9.2"está "cerca" según la función del curso? ¿Y en"10.1"?
Depuración
data = response.json()
iss_latitude = data["iss_position"]["latitude"]
if MY_LAT - 5 <= iss_latitude <= MY_LAT + 5:
print("¡Cerca!")Este código produce un error. ¿Cuál y por qué?
def is_iss_overhead():
...
if cerca:
return TrueUn compañero dice que la función "devuelve False si no está cerca". ¿Es cierto? ¿Qué devuelve realmente? ¿Importa en un if?
Modificación
Cambia el margen de ±5 grados por una constante y haz que el correo diga a cuántos grados de distancia está la ISS.
Creación
Escribe es_de_noche(hora_actual, hora_amanecer, hora_atardecer) que no haga ninguna petición (solo reciba números) y pruébala con 6 horas distintas. ¿Por qué es más fácil de probar que la versión del curso?
10. 🤖 Programar con apoyo de IA
Actividad 1: la documentación manda. Pregunta a una IA: "¿Qué parámetros acepta la API sunrise-sunset.org?". Compara su respuesta con la documentación oficial de la API. ¿Mencionó tzid? ¿Inventó algún parámetro que no existe? Pruébalo: un parámetro inventado, ¿produce error o se ignora?
Actividad 2: detectar el error de la zona horaria. Pega el is_night() del curso en una IA y pregunta: "¿Funciona correctamente en Bogotá, Colombia?". Si responde que sí, pídele que lo explique con un ejemplo a las 7:00 p. m. Compara con tu tabla de la sección 🧪. Anota si la IA encontró el problema sola o necesitó pistas.
Actividad 3: APIs que ya no existen. Las IA aprenden de textos antiguos y a veces recomiendan APIs retiradas (como REST Countries v3.1, sección 3). Pide a una IA "una API gratuita sin clave para obtener datos de países" y prueba cada una que te recomiende. Registra cuáles funcionan hoy.
11. 🛠️ Correcciones técnicas al material original
| Archivo | Afirmación o comportamiento del curso | Observación | Tratamiento |
|---|---|---|---|
255 |
Códigos 3xx = "no tienes permiso, vete" | Los 3xx son redirecciones; la falta de permiso es 401/403 | Nota técnica de la sección 3 |
255 |
Un 200 indica que "todo salió bien" | El servidor respondió, pero los datos pueden estar desactualizados o indicar error en el contenido (verificado con astros.json y REST Countries) |
Sección 3 |
257 |
Comparar las horas de la API con datetime.now().hour |
La API devuelve UTC por defecto; en Colombia la comparación falla (verificado). Solución: tzid |
Nota técnica de la sección 5; 🧪 del proyecto |
257 |
Con formatted=0, "formato Unix, que es de 12 horas" |
Es el formato ISO 8601, con reloj de 24 horas | Sección 5 |
258 |
Bucle while True con peticiones sin timeout ni try |
Un error de red detiene el programa; sin timeout puede quedarse esperando |
🧪 del proyecto |
258 |
Revisión cada 60 s | Envía un correo por minuto mientras la ISS está cerca | 🧪 del proyecto |
258 |
is_iss_overhead solo tiene return True |
Si no está cerca devuelve None (funciona en un if, pero es mejor devolver False explícitamente) |
Comprueba lo aprendido |
254 |
Extensión JSON Viewer Awesome | Opcional: algunos navegadores ya formatean JSON | Sección 2 |
12. 📋 Matriz de trazabilidad
| Contenido original | Archivo de origen | Concepto desarrollado | Ejercicio original | Reto adicional | Proyecto relacionado |
|---|---|---|---|---|---|
| Objetivos: APIs, rastreador de la ISS | 252 |
Sección 0 | — | — | Sección 6 |
| Qué es una API, analogía del restaurante | 253 |
Sección 1 | — | Retos 1–4 de la sección 1 | Secciones 6, 7 y 8 |
Endpoint, analogía del banco, JSON como paquete plano, requests.get |
254 |
Sección 2 | Primera petición a la ISS | Retos 1–4 de la sección 2 | Secciones 6 y 8 |
Códigos HTTP, raise_for_status, response.json(), JSON anidado, latlong.net |
255 |
Sección 3 | Posición de la ISS como tupla | Retos 1–4 de la sección 3 | Secciones 6, 7 y 8 |
| Máquina de citas de Kanye con Tkinter | 256 |
Sección 4 | Desafío Kanye | Retos 1–4 de la sección 4 | — |
Parámetros, sunrise-sunset, params, formatted, split |
257 |
Sección 5 | Horas en formato de 24 horas | Retos 1–4 de la sección 5 | Secciones 6, 7 y 8 |
Notificador de la ISS, ±5 grados, is_night, bucle cada 60 s |
258 |
Sección 6 | Proyecto del curso | 🧪 del proyecto | Sección 8 |
| (No existe en el curso original) | — | — | — | Reloj Solar de Colombia | Sección 7 |
| (No existe en el curso original) | — | — | — | Alerta del Club de Astronomía (Días 32–33) | Sección 8 |
13. 📦 Evidencias de aprendizaje del Día 33
- Captura del JSON de la ISS en el navegador y tu explicación de cada clave.
- La tabla de códigos HTTP con un ejemplo real de 200 y de 404 provocado por ti.
- La máquina de citas funcionando (Kanye o la alternativa de consejos), con manejo de errores.
- Las horas del amanecer y del atardecer de tu municipio, con y sin
tzid, y tu explicación de la diferencia. - El Notificador de la ISS con las correcciones de la sección 🧪 y la tabla de
is_nightcorregida. - El Reloj Solar de Colombia (proyecto extra) o la Alerta del Club de Astronomía (proyecto integrador).
- Cuaderno de errores:
ModuleNotFoundErrorderequests, 404 por un endpoint mal escrito, 400 por falta de parámetros,KeyErroren JSON anidado, comparación de texto con números,datetimecomparado conint, horas UTC, bucle detenido por un error de red y correos repetidos.