Este tutorial adapta pedagógicamente el Día 35. 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: con Python 3.14 y
requests2.34 se comprobó, el 26 de septiembre de 2026, la respuesta de OpenWeatherMap sin clave válida (error 401) y la API Open-Meteo, que no necesita clave. No se probaron una clave real de OpenWeatherMap ni el envío de SMS con Twilio, porque requieren cuentas personales; esas partes se presentan tal como las enseña el curso.
🔐 Seguridad y ética (adaptación para el aula): hoy se trabaja con claves secretas y números de teléfono.
- Una clave de API es como una contraseña: nunca la pegues en un chat, un documento compartido o un repositorio público.
- Twilio es un servicio comercial que exige registrar y verificar un teléfono. En el aula, lo recomendable es que el docente haga la demostración, y que los estudiantes envíen la alerta por correo al servidor local del Día 32 (el propio curso dice que el correo es una alternativa válida).
- Con Open-Meteo (sección 3) todo el grupo puede construir la lógica de la alerta sin registrarse en ningún servicio.
Tabla de contenidos
- Antes de empezar
- ¿Qué es la autenticación y por qué hay APIs de pago?
- Autenticarse con una clave: OpenWeatherMap
- ¿Lloverá en las próximas 12 horas?
- Enviar la alerta: SMS y WhatsApp con Twilio
- Variables de entorno: esconder las claves
- Ejecutar la alerta cada mañana
- 🚀 Proyecto del día: la Alerta de Lluvia
- 🌟 Proyecto extra: ¿Hay Educación Física afuera?
- 🏆 Proyecto integrador de los Días 32 a 35: el Asistente de la Salida Pedagógica
- 🧩 Comprueba lo aprendido
- 🤖 Programar con apoyo de IA
- 🛠️ Correcciones técnicas al material original
- 📋 Matriz de trazabilidad
- 📦 Evidencias de aprendizaje del Día 35
0. Antes de empezar
(Fuente: 268 - Day 35 Goals- what you will make by the end of the day.md)
Hoy daremos el siguiente paso con las APIs:
| Días 33 y 34 | Día 35 |
|---|---|
| APIs abiertas, sin identificarse | APIs que exigen autenticación con una clave |
| Resultados en la consola o por correo | Alertas por SMS o WhatsApp |
| Datos en el código | Claves fuera del código, en variables de entorno |
El proyecto: una alerta de lluvia. Cada mañana, antes de salir de casa, un programa revisa el pronóstico y, si va a llover durante el día, envía un mensaje: "Hoy va a llover. Lleva paraguas ☔".
Prerequisito general: endpoints, parámetros,
raise_for_statusy JSON anidado (Días 33 y 34); buclesforsobre listas de diccionarios;smtplib, GitHub Actions y la hora UTC (Día 32).
1. ¿Qué es la autenticación y por qué hay APIs de pago?
(Fuente: 269 - What is API Authentication and Why Do We Need to Authenticate Ourselves-.md)
🎯 Objetivo de aprendizaje
Explicar por qué algunas APIs cobran y cómo una clave de API controla su uso.
📖 Explicación conceptual
Las APIs de los días anteriores eran gratuitas y abiertas: sus datos son sencillos y a nadie le preocupa que se usen. Otros datos cuestan mucho producirlos. El ejemplo del curso es el clima: OpenWeatherMap usa miles de estaciones meteorológicas, imágenes de satélite, científicos de datos, servidores y electricidad. Por eso muchas APIs son una forma de vender datos.
La mayoría ofrece un nivel gratuito para aprender y probar. Pero ¿cómo evitar que una empresa de 1000 empleados diga "solo estoy aprendiendo" y use el servicio gratis sin límite? Con una clave de API (API key):
- Es como tu número de cuenta y contraseña con el proveedor.
- Le permite saber quién hace cada petición, contar cuántas haces y negarte el acceso cuando superas el límite.
Autenticación = demostrar quién eres. Cada proveedor tiene su propia forma de hacerlo, pero casi todas usan alguna clave.
Retos adicionales (creados para esta adaptación)
- 🟢 Reto 1: con la analogía del banco del Día 33, explica qué parte corresponde a "consultar el horario" y qué parte a "retirar dinero".
- 🟡 Reto 2: busca la página de precios de OpenWeatherMap y anota cuántas peticiones permite el plan gratuito.
- 🟠 Reto 3: si alguien roba tu clave de un plan gratuito, ¿qué puede pasar? ¿Y si la clave está asociada a una tarjeta de crédito?
- 🔴 Reto 4: investiga dos formas de autenticación distintas a la clave como parámetro (por ejemplo, una clave en un encabezado HTTP, que verás en el Día 37). Explica en qué se diferencian.
2. Autenticarse con una clave: OpenWeatherMap
(Fuente: 270 - Using API Keys to Authenticate and Get the Weather from OpenWeatherMap.md)
🎯 Objetivo de aprendizaje
- Leer la documentación de una API nueva.
- Enviar una clave de API como parámetro.
- Reconocer el error 401.
📖 Primero, la documentación
La profesora lo compara con comprar una podadora y botar el manual: dos días después algo falla y te arrepientes. OpenWeatherMap tiene varios productos, cada uno con su endpoint: clima actual, pronóstico por horas, pronóstico de 5 días… Hay que leer cuál necesitas.
Pasos del curso:
- Registrarse en
openweathermap.org. - En la sección API keys, crear una clave (puedes darle un nombre, por ejemplo "Python") y copiarla.
- Probar primero en el navegador con el ejemplo de la documentación, reemplazando la ciudad y la clave.
💡 Según la ayuda de OpenWeatherMap, una clave recién creada puede tardar un tiempo en activarse. Si la acabas de crear y recibes 401, espera un rato antes de buscar otros errores.
📖 El error 401
Con una clave inventada, o sin clave, la respuesta es (verificado):
{"cod":401, "message": "Invalid API key. Please see https://openweathermap.org/faq#error401 for more info."}401 = No autorizado (Unauthorized): el problema está en tu clave o en cómo la envías.
📝 Ejercicio original del curso
Enunciado: leer la documentación del pronóstico de 5 días cada 3 horas y hacer la petición desde Python con la latitud y longitud de tu ubicación y tu clave. Encontrar el pronóstico en el JSON que llega.
🔍 Antes de programar
- ¿Cuál es el endpoint? (Todo lo que va antes del
?.) - ¿Cómo se llaman exactamente los parámetros de latitud, longitud y clave en esta API?
🔎 La solución del curso
import requests
OWM_Endpoint = "https://api.openweathermap.org/data/2.5/forecast"
api_key = "TU_CLAVE_DE_API"
weather_params = {
"lat": 4.711, # tu latitud
"lon": -74.0721, # tu longitud (¡con el signo!)
"appid": api_key,
}
response = requests.get(OWM_Endpoint, params=weather_params)
print(response.status_code)
print(response.json())⚠️ Atención a los nombres: en esta API la longitud es lon (L-O-N). El curso advierte que mucha gente escribe lng… ¡que era justamente el nombre en la API de amanecer y atardecer del Día 33! Cada API tiene sus propias reglas: siempre la documentación.
📖 Qué contiene la respuesta
El curso copia el JSON en un visor de JSON en línea para verlo como árbol:
list: 40 pronósticos (8 por día × 5 días), uno cada 3 horas.- En cada pronóstico:
dt(marca de tiempo en segundos desde el 1 de enero de 1970),dt_txt(la fecha en texto),main(temperatura en kelvin, presión, humedad) yweather, una lista con la condición del clima. - Al final,
city: el lugar más cercano a tus coordenadas.
🛠️ Nota técnica: el curso dice que los pronósticos son a las 3:00, 6:00, 9:00 a. m., etc. Esas horas están en UTC. En Colombia (UTC−5) corresponden a las 10:00 p. m., 1:00 a. m., 4:00 a. m., 7:00 a. m., 10:00 a. m., 1:00 p. m., 4:00 p. m. y 7:00 p. m. Tenlo en cuenta al interpretar
dt_txt.
💡 Mejora opcional: la documentación de OpenWeatherMap incluye un parámetro
units(por ejemplo,"metric") para recibir la temperatura en grados Celsius en lugar de kelvin. Búscalo y compruébalo tú.
⚠️ Errores frecuentes
| Error | Qué ocurre | Solución |
|---|---|---|
| Clave mal copiada o recién creada | 401 | Revisa la clave; espera si es nueva |
"APPID" o "api_key" |
401: el parámetro no se reconoce | Es appid, en minúsculas |
"lng" en lugar de "lon" |
La API no recibe la longitud | Usa el nombre de esta API |
| Olvidar el signo negativo | Pronóstico de otro lugar del mundo | Copia el signo |
Retos adicionales (creados para esta adaptación)
- 🟢 Reto 1: imprime el nombre de la ciudad que devuelve la API. ¿Coincide con tu municipio?
- 🟡 Reto 2: convierte la temperatura del primer pronóstico de kelvin a Celsius (resta 273,15). Luego obtén el mismo resultado con el parámetro
units. - 🟠 Reto 3: imprime una tabla con la hora de Colombia (convierte
dtcondatetime) y la descripción de los primeros 8 pronósticos. - 🔴 Reto 4: haz la petición sin
appidy con una clave falsa, y escribe una función que distinga el 401 de otros errores y muestre un mensaje claro para cada caso.
3. ¿Lloverá en las próximas 12 horas?
(Fuente: 271 - Challenge - Check if it Will Rain in the Next 12 Hours.md)
🎯 Objetivo de aprendizaje
Recorrer una lista de pronósticos, interpretar códigos de condición y decidir con una bandera (will_rain).
📝 Ejercicio original del curso
Enunciado: el programa se ejecuta a las 6:00 a. m. Si en las próximas 12 horas (hasta las 6:00 p. m., cuando ya volviste a casa) hay pronóstico de lluvia, debe imprimir "Bring an umbrella" (lleva paraguas).
📖 Paso 1: pedir solo lo necesario
Como hay un pronóstico cada 3 horas, 12 horas son 4 pronósticos. La documentación trae el parámetro cnt (count): con "cnt": 4 la API envía solo esos 4. Así no pedimos más datos de los necesarios. La profesora insiste: "Leer la documentación tiene pepitas de oro. Es como entrar a una casa ajena: la lavadora y la secadora funcionan distinto".
También se agrega response.raise_for_status() y se guarda weather_data = response.json().
📖 Paso 2: los códigos de condición
La documentación explica que weather[0]["id"] es un código de condición:
| Código | Condición |
|---|---|
| 2xx | Tormenta eléctrica |
| 3xx | Llovizna |
| 5xx | Lluvia |
| 6xx | Nieve |
| 7xx | Atmósfera (neblina, humo, polvo) |
| 800 | Despejado |
| 80x | Nubes (por ejemplo, 802: nubes dispersas) |
Regla del curso: si algún código es menor que 700, hay algún tipo de precipitación → lleva paraguas.
weather es una lista porque puede haber varias condiciones a la vez (nieve y neblina). Revisando la documentación, la profesora observó que el primer elemento es la condición principal.
🔎 La solución del curso, paso a paso
Explorar el JSON por partes: weather_data["list"] → [0] → ["weather"] → [0] → ["id"].
weather_params = {
"lat": MY_LAT,
"lon": MY_LONG,
"appid": api_key,
"cnt": 4,
}
response = requests.get(OWM_Endpoint, params=weather_params)
response.raise_for_status()
weather_data = response.json()
will_rain = False
for hour_data in weather_data["list"]:
condition_code = hour_data["weather"][0]["id"]
if int(condition_code) < 700:
will_rain = True
if will_rain:
print("Bring an umbrella.")¿Por qué la bandera will_rain? Si el print estuviera dentro del for, el mensaje saldría varias veces, una por cada pronóstico con lluvia. Con la bandera se revisan todos los pronósticos y se avisa una sola vez al final.
Truco del curso para probar: si en tu ciudad no llueve, busca un lugar donde sí esté lloviendo (el curso usa el mapa de precipitación de Ventusky) y usa temporalmente sus coordenadas.
🛠️ Nota técnica: el video dice que
condition_code"es una cadena" y hay que convertirlo conint(). En el JSON de OpenWeatherMap, elides un número (sin comillas), así queint()no hace daño pero no es necesario. Compruébalo tú: imprimetype(condition_code).
📖 Alternativa sin clave para el aula: Open-Meteo (adaptación)
Para que todo el grupo practique la lógica sin registrarse, la API Open-Meteo ofrece pronósticos por hora sin clave para uso no comercial:
parameters = {
"latitude": 4.711,
"longitude": -74.0721,
"hourly": "precipitation_probability,weather_code",
"forecast_hours": 12,
"timezone": "America/Bogota",
}
response = requests.get("https://api.open-meteo.com/v1/forecast", params=parameters)
response.raise_for_status()
data = response.json()Respuesta real para Bogotá el 26/09/2026 al mediodía (verificada):
"hourly": {
"time": ["2026-09-26T12:00", "2026-09-26T13:00", ...],
"precipitation_probability": [91, 98, 100, 99, ...],
"weather_code": [3, 53, 80, 51, ...]
}- Es por hora (12 datos) y ya viene en hora de Colombia gracias a
timezone. - Los códigos siguen la norma WMO, distinta a la de OpenWeatherMap: por ejemplo, 51–55 llovizna, 61–65 lluvia, 80–82 aguaceros, 95 tormenta. Consulta la tabla en la documentación de Open-Meteo.
- La estructura es diferente: en vez de una lista de diccionarios, hay listas paralelas: el dato
ide cada lista corresponde a la horatime[i].
Moraleja: la lógica (recorrer pronósticos y levantar una bandera) es la misma; lo que cambia de una API a otra es cómo vienen los datos. Por eso se lee la documentación.
⚠️ Errores frecuentes
| Error | Qué ocurre | Solución |
|---|---|---|
weather_data["weather"] |
KeyError: weather está dentro de cada elemento de list |
Recorre weather_data["list"] |
Olvidar el [0] de weather |
TypeError: una lista no se indexa con "id" |
hour_data["weather"][0]["id"] |
print dentro del for |
El mensaje sale varias veces | Usa la bandera |
Poner will_rain = False dentro del for |
Solo cuenta el último pronóstico | Inicialízala antes del bucle |
Usar la regla < 700 con los códigos de Open-Meteo |
Decisiones equivocadas | Cada API tiene su propia tabla de códigos |
Retos adicionales (creados para esta adaptación)
- 🟢 Reto 1: además de avisar, imprime a qué hora se espera la primera lluvia.
- 🟡 Reto 2: con Open-Meteo, avisa si la probabilidad de lluvia supera el 60 % en alguna hora. ¿Es mejor criterio que el código de condición?
- 🟠 Reto 3: diferencia los mensajes: "Lleva paraguas" (llovizna o lluvia) y "¡Cuidado, tormenta eléctrica!" (códigos 2xx de OpenWeatherMap o 95–99 de Open-Meteo).
- 🔴 Reto 4: escribe una sola función
va_a_llover(pronosticos) -> boolque reciba una lista estándar (por ejemplo, de diccionarios{"hora": ..., "lluvia": True/False}) y dos funciones que conviertan la respuesta de OpenWeatherMap y la de Open-Meteo a ese formato. Así puedes cambiar de API sin cambiar la lógica.
4. Enviar la alerta: SMS y WhatsApp con Twilio
(Fuentes: 272 - Sending SMS via the Twilio API.md, 273-can-t-use-sms-try-whatsapp-instead.md)
🎯 Objetivo de aprendizaje
Usar la biblioteca de un proveedor (Twilio) siguiendo su guía de inicio rápido.
📝 Contenido original del curso
Twilio es una API para enviar SMS, hacer llamadas y más. Es de pago, pero ofrece crédito de prueba sin tarjeta de crédito. El curso aclara: "Si no quieres enviarte un mensaje de texto, no pasa nada: puedes usar el correo electrónico".
Pasos del curso:
- Registrarse y verificar el correo y el número de teléfono.
- Obtener un número de prueba de Twilio (por defecto, de EE. UU.).
- Seguir el Quickstart de SMS para Python e instalar la biblioteca
twilio. - Copiar del panel el Account SID (identificador de la cuenta) y el Auth Token (clave secreta).
from twilio.rest import Client
account_sid = "TU_ACCOUNT_SID"
auth_token = "TU_AUTH_TOKEN"
if will_rain:
client = Client(account_sid, auth_token)
message = client.messages.create(
body="Hoy va a llover. Lleva paraguas ☔",
from_="+1XXXXXXXXXX", # tu número de prueba de Twilio
to="+57XXXXXXXXXX" # un número VERIFICADO en tu cuenta
)
print(message.status)- Con una cuenta de prueba solo puedes enviar a números verificados en la consola de Twilio (Verified Caller IDs). El mensaje llega con el texto "Sent from your Twilio trial account".
- El curso imprime
message.statuspara confirmar que el mensaje quedó en cola (queued).
📖 ¿Por qué
from_con guion bajo?fromes una palabra reservada de Python (como enfrom twilio.rest import Client). Usarla como nombre de argumento es un error de sintaxis:from='+1...'produceSyntaxError: invalid syntax(verificado). Por eso la biblioteca usafrom_. Es una convención común en Python.
📝 Si no puedes usar SMS: WhatsApp (lección 273)
La lección explica que, por un cambio en la ley de EE. UU. y Canadá desde el 31 de enero de 2024, no se pueden enviar SMS desde números gratuitos sin un proceso de verificación de 2 a 4 semanas. Para quienes viven en esos países, las cuentas gratuitas solo usan un teléfono virtual en el sitio de Twilio. Alternativa: el sandbox de WhatsApp de Twilio. Se conecta escaneando un código QR y se cambia el código así:
message = client.messages.create(
from_="whatsapp:NUMERO_DE_WHATSAPP_DE_TWILIO",
body="It's going to rain today. Remember to bring an umbrella",
to="whatsapp:TU_NUMERO_VERIFICADO"
)💡 Las condiciones de Twilio (países, precios, pruebas gratuitas) cambian con el tiempo. Consulta siempre su documentación actual.
🏫 Para el aula: la alerta por correo (adaptación)
Usa la función de envío con EmailMessage del Día 32 y el servidor local. La lógica es idéntica: solo cambia cómo se entrega el mensaje. Esto también muestra un buen diseño: si la función de envío está separada, cambiar de SMS a correo es cambiar una función.
⚠️ Errores frecuentes
| Error | Qué ocurre | Solución |
|---|---|---|
from= en lugar de from_= |
SyntaxError |
Usa from_ |
| Enviar a un número no verificado (cuenta de prueba) | Twilio rechaza el envío | Verifica el número en la consola |
| Número sin código de país | Error de formato | Formato internacional: +57... |
ModuleNotFoundError: twilio |
No está instalada | pip install twilio |
Retos adicionales (creados para esta adaptación)
- 🟢 Reto 1: escribe la función
enviar_alerta(texto)que por ahora solo hagaprint. Úsala en el programa. - 🟡 Reto 2: cambia
enviar_alertapara que envíe un correo al servidor local del Día 32. - 🟠 Reto 3: agrega un parámetro
canal("consola","correo"o"sms") leído de un archivo de configuración. - 🔴 Reto 4: un SMS tiene un límite de caracteres. Investiga cuál es y cómo cambia con emojis y tildes. Diseña un mensaje de alerta útil que quepa en un solo SMS.
5. Variables de entorno: esconder las claves
(Fuente: 275 - Understanding Environment Variables and Hiding API Keys.md)
🎯 Objetivo de aprendizaje
Guardar claves fuera del código y leerlas con os.environ.
📖 Explicación conceptual
Las variables de entorno son pares NOMBRE=valor (siempre texto) que existen en el entorno donde se ejecuta un programa. Para verlas en la terminal: env en Mac y Linux, set en el Símbolo del sistema de Windows y Get-ChildItem Env: en PowerShell.
Dos usos, según el curso:
- Comodidad: cambiar datos (por ejemplo, la lista de correos) sin tocar el código de una aplicación ya desplegada.
- Seguridad: separar las claves del código. Si subes el código a un sitio público como GitHub, cualquiera podría robar las claves que estén escritas en él.
🔎 La solución del curso
En la terminal (Mac/Linux o PythonAnywhere):
export OWM_API_KEY=tu_clave_sin_comillas
export AUTH_TOKEN=tu_tokenSin espacios alrededor del =.
En el código:
import os
api_key = os.environ.get("OWM_API_KEY")
auth_token = os.environ.get("AUTH_TOKEN")En la tarea programada de PythonAnywhere, el curso escribe todo en una sola línea, separado por punto y coma:
export OWM_API_KEY=...; export AUTH_TOKEN=...; python3 main.py📖 Equivalentes en Windows (adaptación)
| Terminal | Crear una variable (solo para esa ventana) |
|---|---|
| PowerShell | $env:OWM_API_KEY = "tu_clave" |
| Símbolo del sistema (cmd) | set OWM_API_KEY=tu_clave |
⚠️ Una variable creada así solo existe en esa terminal y en los programas que se ejecuten desde ella. Si ejecutas desde el botón ▶ de PyCharm o VS Code, no la verá: hay que configurarla en las opciones de ejecución del editor.
📖 get o corchetes (adaptación)
os.environ.get("OWM_API_KEY") # si no existe → None (verificado)
os.environ["OWM_API_KEY"] # si no existe → KeyError: 'OWM_API_KEY' (verificado)Con get, un nombre mal escrito produce None en silencio, y el error aparece más tarde como un 401 confuso. Con corchetes, el programa falla de inmediato y el mensaje dice qué variable falta. Para claves obligatorias, los corchetes (o un if que valide) ayudan a encontrar el error más rápido.
💡 Práctica común (no está en el curso): guardar las variables en un archivo
.env, leerlo con la bibliotecapython-dotenvy agregar.enval archivo.gitignorepara que nunca se suba a un repositorio. Es una práctica muy extendida en proyectos reales.
⚠️ Errores frecuentes
| Error | Qué ocurre | Solución |
|---|---|---|
export OWM_API_KEY = clave (con espacios) |
La terminal no crea la variable | Sin espacios alrededor del = |
export en PowerShell |
No existe ese comando | $env:NOMBRE = "valor" |
| Nombre distinto en la terminal y en el código | None → 401 |
Los nombres distinguen mayúsculas |
| Subir el código a GitHub antes de quitar la clave | La clave queda en el historial aunque la borres después | Cambia la clave en el proveedor de inmediato |
Retos adicionales (creados para esta adaptación)
- 🟢 Reto 1: crea en tu terminal la variable
MI_NOMBREy escribe un programa que salude usando su valor. - 🟡 Reto 2: haz que el programa muestre "Falta la variable MI_NOMBRE" y termine con un mensaje claro si no existe.
- 🟠 Reto 3: configura una variable de entorno en las opciones de ejecución de tu editor y comprueba que el botón ▶ la ve.
- 🔴 Reto 4: crea un archivo
.env, léelo conpython-dotenvy un.gitignoreque lo excluya. Explica a un compañero por qué es más seguro que escribir la clave enmain.py.
6. Ejecutar la alerta cada mañana
(Fuentes: 274 - Optional- Use PythonAnywhere to Automate the Python Script.md, 276-use-github-actions-again-to-automate-the-rain-alert-python-script.md)
📝 PythonAnywhere (lección 274, opcional)
El curso sube main.py, lo prueba con python3 main.py en una consola Bash y encuentra un ConnectionError ("Max retries exceeded"). Buscando el error en Google, encuentra la explicación: las cuentas gratuitas de PythonAnywhere salen a internet a través de un proxy, y hay que indicárselo a Twilio:
import os
from twilio.http.http_client import TwilioHttpClient
proxy_client = TwilioHttpClient()
proxy_client.session.proxies = {"https": os.environ["https_proxy"]}
client = Client(account_sid, auth_token, http_client=proxy_client)Luego programa la tarea diaria (en hora UTC). Recuerda: en el Día 32 la propia lección aclaró que la programación de tareas en PythonAnywhere ya no es gratuita.
📝 GitHub Actions (lección 276)
Son los mismos pasos del Día 32 (fork de appbrewery/scheduled-tasks, habilitar workflows, código en main.py, requirements.txt con requests y twilio, secretos y expresión cron), con tres secretos: OWM_API_KEY, ACCOUNT_SID y AUTH_TOKEN. El curso aclara que los números de teléfono no son secretos, solo configuración, pero hay que ponerlos en el código.
Para ejecutar a las 6:00 a. m. en Colombia (UTC−5): 6 + 5 = 11 → cron: '0 11 * * *'.
Problemas frecuentes que señala el curso: retrasos de 5 a 15 minutos; pausa tras 60 días sin actividad; No module named 'twilio' si falta en requirements.txt; nombres de secretos que distinguen mayúsculas; en cuentas de prueba, solo números verificados.
Retos adicionales (creados para esta adaptación)
- 🟢 Reto 1: escribe la expresión cron para las 5:30 a. m. de Colombia.
- 🟡 Reto 2: ¿por qué el curso guarda los números de teléfono en el código pero las claves en secretos? ¿Estás de acuerdo? Justifica.
- 🟠 Reto 3: revisa el archivo
.github/workflows/scheduled.ymlde la plantilla (si el docente lo comparte) e identifica en qué línea se pasan los secretos al programa. - 🔴 Reto 4: el curso encontró la solución del proxy buscando el mensaje de error. Provoca a propósito un error en tu programa, copia el mensaje y encuentra la explicación en la documentación oficial. Describe tu proceso de búsqueda.
7. 🚀 Proyecto del día: la Alerta de Lluvia
(Fuentes: 268 a 276)
Problema
Recibir cada mañana un aviso si va a llover durante las próximas 12 horas.
Requisitos funcionales
- Consultar el pronóstico de tu municipio (OpenWeatherMap con tu clave, o Open-Meteo sin clave).
- Decidir con una bandera si lloverá en las próximas 12 horas.
- Si va a llover, enviar un mensaje (SMS, WhatsApp o correo al servidor local).
- Ninguna clave escrita en el código.
Planificación
| Entradas | Proceso | Salidas |
|---|---|---|
| Coordenadas, clave (variable de entorno), pronóstico | Recorrer los pronósticos y levantar la bandera | Mensaje (o nada, si no llueve) |
Pruebas
| Caso | Cómo probarlo | Esperado |
|---|---|---|
| Día sin lluvia | Coordenadas de un lugar seco | No se envía nada |
| Día con lluvia | Coordenadas de un lugar lluvioso (Ventusky) | Un mensaje |
| Variable de entorno sin definir | Ejecutar en una terminal nueva | ⚠️ (ver 🧪) |
| Sin internet | Desconectar | ⚠️ (ver 🧪) |
🧪 Prueba y depuración
- La clave que falta. Sin la variable de entorno,
os.environ.getdevuelveNoney la API responde 401 (verificado: sin clave, OpenWeatherMap responde 401). El error no dice "falta la variable". Haz que el programa lo detecte antes de hacer la petición. - Horas UTC. Si programas la alerta a las 6:00 a. m. de Colombia, ¿qué horas UTC de
dt_txtcubren tus 12 horas? Escríbelas y compruébalas con los datos reales. - Sin internet. Captura
requests.exceptions.RequestExceptiony usatimeout. Decide: si no se pudo consultar el pronóstico, ¿avisas "No pude revisar el clima" o no haces nada? - ¿Qué cuenta como lluvia? La regla
< 700incluye nieve (6xx). En tu municipio, ¿tiene sentido? ¿Y la neblina (7xx)? Justifica tu regla.
⭐ Extensión del proyecto
- El mensaje incluye la hora de la primera lluvia y la temperatura máxima.
- Aviso de protector solar si no hay nubes (pista: índice UV en la documentación de Open-Meteo).
- Varias personas con distintas ubicaciones (un CSV de destinatarios).
8. 🌟 Proyecto extra: ¿Hay Educación Física afuera?
Situación
El profesor de Educación Física decide cada mañana si la clase será en la cancha o en el aula. Quiere un programa que, a las 6:00 a. m., le envíe el pronóstico para las horas exactas de sus clases.
Requisitos
- Un
horario.csvcon las clases de Educación Física de la semana:dia,hora_inicio,hora_fin,grupo. - Pronóstico por hora de Open-Meteo con
timezonede Colombia (o de OpenWeatherMap con clave en variable de entorno). - Para cada clase de hoy, calcular la probabilidad máxima de lluvia en su franja horaria y la temperatura.
- Clasificar cada clase: "Cancha" (probabilidad < 40 %), "Revisar" (40–70 %) o "Aula" (> 70 %). Los umbrales, como constantes.
- Enviar un correo (servidor local) con una tabla: grupo, hora, probabilidad, decisión.
- Si hoy no hay clases, no se envía nada.
- Manejo de errores de red y de un CSV mal escrito.
Decisiones que debes tomar
- ¿Qué pesa más: el código de condición o la probabilidad? ¿Y la tormenta eléctrica (riesgo para la seguridad)?
- ¿Cómo haces el programa fácil de probar sin esperar a que llueva? (Pista: una función que reciba el pronóstico como parámetro.)
Casos de prueba
| Caso | Esperado |
|---|---|
| Pronóstico simulado con 20 % de lluvia a las 8:00 | "Cancha" para la clase de las 8:00 |
| Pronóstico simulado con tormenta a las 10:00 | "Aula", aunque la probabilidad sea baja |
| Domingo | No se envía nada |
9. 🏆 Proyecto integrador de los Días 32 a 35: el Asistente de la Salida Pedagógica
Proyecto integrador creado para esta adaptación. Reúne correo, fechas y programación de tareas (Día 32), APIs y parámetros (Días 33 y 34), y autenticación, variables de entorno y alertas (Día 35).
Situación problema
El colegio organiza una salida pedagógica a un parque natural. La coordinación necesita decidir con anticipación si el clima lo permite, organizar la hora de salida según la luz del día y avisar a los acudientes (ficticios en este ejercicio).
Pregunta orientadora
¿Cómo combinar varias fuentes de datos en vivo, de forma segura, para apoyar una decisión real y comunicarla a tiempo?
Objetivo
Construir el Asistente de la Salida Pedagógica: un programa que, en los días previos a la salida, consulta el pronóstico del destino y las horas de sol, y envía los avisos adecuados.
Requerimientos
salida.jsoncon el destino (coordenadas), la fecha de la salida y la hora de regreso prevista.- Pronóstico del destino para la fecha de la salida (Open-Meteo u OpenWeatherMap con clave en variable de entorno).
- Horas de sol del destino ese día (API de amanecer y atardecer con
tzid, Día 33): la salida debe regresar antes del atardecer. - Decisión: "Confirmada", "En riesgo" o "Reprogramar", según reglas que tú defines y documentas.
- Avisos por correo (servidor local): 3 días antes, un aviso con el pronóstico; 1 día antes, la confirmación o reprogramación; si pasa de "Confirmada" a "Reprogramar", un aviso urgente.
acudientes.csvficticio; los correos se envían conEmailMessage.historial.jsonque registra cada consulta y cada aviso enviado, para no repetir avisos.- Todas las funciones de decisión reciben los datos como parámetros, para probarlas con pronósticos simulados.
- Propuesta de expresión cron (hora de Colombia → UTC) para ejecutarlo cada mañana.
Restricciones
- Ninguna clave en el código ni en el repositorio.
- Todas las peticiones con
timeouty manejo de errores. - Datos personales ficticios.
Etapas de desarrollo
| Etapa | Entregable parcial |
|---|---|
| 1. Diseño | Reglas de decisión escritas y diagrama de flujo diario |
| 2. Datos | Funciones de pronóstico y horas de sol, probadas en la consola |
| 3. Decisión | Función de decisión probada con 6 pronósticos simulados |
| 4. Avisos | Correos al servidor local y historial.json |
| 5. Seguridad | Clave en variable de entorno; .gitignore si se usa .env |
| 6. Presentación | Simulación de los 3 días previos a una salida |
Entregables
- Código organizado en funciones o módulos,
salida.jsony datos ficticios. - Documento de una página con las reglas de decisión y su justificación.
- 10 casos de prueba con resultados.
- Registro de errores con al menos 5 errores reales.
Casos de prueba mínimos
| Caso | Esperado |
|---|---|
| Pronóstico simulado sin lluvia, regreso antes del atardecer | "Confirmada" |
| Tormenta simulada el día de la salida | "Reprogramar" y aviso urgente |
| Hora de regreso después del atardecer | Advertencia en el aviso |
| Ejecutar dos veces el mismo día | No se duplican avisos |
| Falta la variable de entorno de la clave | Mensaje claro; no se hace la petición |
Criterios técnicos de funcionamiento
- Cambiar de destino o de fecha solo requiere editar
salida.json. - La lógica de decisión no depende de ninguna API concreta.
- El programa no se detiene por un error de red.
Posibles extensiones
Interfaz Tkinter para la coordinación, con el estado de la salida y un botón para reenviar avisos; comparación del pronóstico de dos destinos posibles.
10. 🧩 Comprueba lo aprendido
Conceptuales
- ¿Por qué algunas APIs cobran y otras no? Da un ejemplo de cada tipo.
- ¿Qué es una clave de API y qué permite hacer al proveedor?
- ¿Qué significa el error 401? ¿En qué se diferencia de un 404?
- Menciona los dos usos de las variables de entorno según el curso.
- ¿Por qué Twilio usa
from_y nofrom?
Análisis de código
will_rain = False
for hour_data in weather_data["list"]:
condition_code = hour_data["weather"][0]["id"]
if condition_code < 700:
will_rain = TrueExplica cada nivel de hour_data["weather"][0]["id"]. ¿Qué pasaría si will_rain = False estuviera dentro del for?
Predicción
- Los cuatro códigos del pronóstico son
[800, 803, 500, 801]. ¿Qué valewill_rainal final? - ¿Qué imprime
print(os.environ.get("VARIABLE_QUE_NO_EXISTE"))? - Un workflow tiene
cron: '0 11 * * *'. ¿A qué hora se ejecuta en Colombia?
Depuración
weather_params = {
"lat": 4.711,
"lng": -74.0721,
"APPID": os.environ.get("OWM_KEY"),
"cnt": 4,
}La respuesta es 401 (o datos de otro lugar). Encuentra tres posibles errores, suponiendo que la variable de entorno se llama OWM_API_KEY.
Modificación
Cambia el programa para que avise solo si llueve entre las 6:00 a. m. y las 7:00 p. m. de Colombia, aunque el pronóstico incluya horas de la noche.
Creación
Escribe resumen_pronostico(codigos: list[int]) -> str que devuelva "Despejado", "Nublado", "Lluvia" o "Tormenta" según el código más grave de la lista (usa la tabla de OpenWeatherMap). Pruébala con 4 listas distintas.
11. 🤖 Programar con apoyo de IA
Actividad 1: nunca compartas claves. Antes de pedir ayuda a una IA con tu código, reemplaza todas las claves, tokens y números de teléfono por textos como "TU_CLAVE". Pide a un compañero que revise tu mensaje antes de enviarlo. ¿Encontró algún dato sensible que se te escapó?
Actividad 2: documentación frente a IA. Pregunta a una IA: "¿Cuáles son los parámetros de la API de pronóstico de 5 días de OpenWeatherMap?". Compara con la documentación oficial:
- ¿Mencionó
cnt? ¿Yunits? - ¿Escribió
lonolng? - ¿Dijo que el
idde condición es texto o número?
Actividad 3: adaptar la lógica a otra API. Pide a una IA que convierta tu alerta de OpenWeatherMap a Open-Meteo. Revisa si cambió la regla de los códigos (de < 700 a la tabla WMO) o si la dejó igual. Si la dejó igual, el programa tomará decisiones equivocadas: explica por qué con un ejemplo.
12. 🛠️ Correcciones técnicas al material original
| Archivo | Afirmación o comportamiento del curso | Observación | Tratamiento |
|---|---|---|---|
270 |
Pronósticos a las 3:00, 6:00, 9:00… | Son horas UTC; en Colombia se corren 5 horas | Nota técnica de la sección 2 |
271 |
condition_code "es una cadena" que hay que convertir |
En el JSON de OpenWeatherMap, id es numérico; int() es innecesario pero inofensivo |
Nota técnica de la sección 3 |
271 |
Regla < 700 para "lleva paraguas" |
Incluye nieve y excluye neblina; conviene adaptarla al lugar | 🧪 del proyecto |
275 |
Crear variables con export y verlas con env |
En Windows: $env:NOMBRE (PowerShell) o set (cmd); la variable solo existe en esa terminal |
Sección 5 |
275 |
Leer claves con os.environ.get |
Si la variable falta, devuelve None en silencio y el error aparece como 401 (verificado); os.environ[...] falla de inmediato con KeyError |
Sección 5; 🧪 del proyecto |
274 |
PythonAnywhere con tarea programada gratuita | Ya no es gratuita (según la lección 251 del Día 32) |
Sección 6 |
272, 273 |
Condiciones de Twilio y ley de EE. UU. de 2024 | Pueden cambiar; verificar en la documentación actual | Sección 4 |
13. 📋 Matriz de trazabilidad
| Contenido original | Archivo de origen | Concepto desarrollado | Ejercicio original | Reto adicional | Proyecto relacionado |
|---|---|---|---|---|---|
| Objetivos: claves, autenticación, SMS, variables de entorno, alerta de lluvia | 268 |
Sección 0 | — | — | Sección 7 |
| Por qué hay APIs de pago, nivel gratuito, clave de API | 269 |
Sección 1 | — | Retos 1–4 de la sección 1 | Secciones 7 y 9 |
OpenWeatherMap: documentación, appid, 401, pronóstico de 5 días, lon |
270 |
Sección 2 | Desafío: pronóstico de 5 días | Retos 1–4 de la sección 2 | Secciones 7, 8 y 9 |
cnt, códigos de condición, < 700, bandera will_rain, Ventusky |
271 |
Sección 3 | Desafío: lluvia en 12 horas | Retos 1–4 de la sección 3; Open-Meteo | Secciones 7, 8 y 9 |
Twilio: cuenta de prueba, números verificados, Client, messages.create |
272 |
Sección 4 | Enviar un SMS | Retos 1–4 de la sección 4 | Sección 7 |
| WhatsApp con Twilio, ley de EE. UU. de 2024 | 273 |
Sección 4 | Alternativa por WhatsApp | — | Sección 7 |
PythonAnywhere, proxy TwilioHttpClient, tarea en UTC |
274 |
Sección 6 | Automatizar el script | Reto 4 de la sección 6 | Sección 9 |
Variables de entorno, export, os.environ.get, seguridad |
275 |
Sección 5 | Ocultar las claves | Retos 1–4 de la sección 5 | Secciones 7, 8 y 9 |
| GitHub Actions con tres secretos | 276 |
Sección 6 | Automatizar con GitHub | Retos 1–3 de la sección 6 | Sección 9 |
| (No existe en el curso original) | — | — | — | ¿Hay Educación Física afuera? | Sección 8 |
| (No existe en el curso original) | — | — | — | Asistente de la Salida Pedagógica (Días 32–35) | Sección 9 |
14. 📦 Evidencias de aprendizaje del Día 35
- Captura del error 401 y tu explicación de qué lo causa.
- El JSON del pronóstico (OpenWeatherMap o Open-Meteo) con la ruta hasta el código de condición señalada.
- La Alerta de Lluvia funcionando con la clave en una variable de entorno (captura de la terminal sin mostrar el valor de la clave).
- El mensaje de alerta recibido (SMS o WhatsApp en la demostración del docente, o correo en el servidor local).
- Las correcciones de la sección 🧪 y tu regla justificada de "qué cuenta como lluvia".
- ¿Hay Educación Física afuera? (proyecto extra) o el Asistente de la Salida Pedagógica (proyecto integrador).
- Cuaderno de errores: 401 por clave inválida o ausente,
lngen lugar delon,KeyErroreIndexErroren el JSON, bandera inicializada dentro del bucle,fromcomo argumento, variable de entorno inexistente,exporten PowerShell y horas UTC.