Saltar al contenido
Inicio 100 Días de Código en Python — Día 35: Claves de API, autenticación y variables de entorno — la Alerta de Lluvia

100 Días de Código en Python — Día 35: Claves de API, autenticación y variables de entorno — la Alerta de Lluvia

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

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 requests 2.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.

  1. Una clave de API es como una contraseña: nunca la pegues en un chat, un documento compartido o un repositorio público.
  2. 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).
  3. 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

  1. Antes de empezar
  2. ¿Qué es la autenticación y por qué hay APIs de pago?
  3. Autenticarse con una clave: OpenWeatherMap
  4. ¿Lloverá en las próximas 12 horas?
  5. Enviar la alerta: SMS y WhatsApp con Twilio
  6. Variables de entorno: esconder las claves
  7. Ejecutar la alerta cada mañana
  8. 🚀 Proyecto del día: la Alerta de Lluvia
  9. 🌟 Proyecto extra: ¿Hay Educación Física afuera?
  10. 🏆 Proyecto integrador de los Días 32 a 35: el Asistente de la Salida Pedagógica
  11. 🧩 Comprueba lo aprendido
  12. 🤖 Programar con apoyo de IA
  13. 🛠️ Correcciones técnicas al material original
  14. 📋 Matriz de trazabilidad
  15. 📦 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_status y JSON anidado (Días 33 y 34); bucles for sobre 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:

  1. Registrarse en openweathermap.org.
  2. En la sección API keys, crear una clave (puedes darle un nombre, por ejemplo "Python") y copiarla.
  3. 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):

JSON
{"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

Python
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) y weather, 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 dt con datetime) y la descripción de los primeros 8 pronósticos.
  • 🔴 Reto 4: haz la petición sin appid y 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"].

Python
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 con int(). En el JSON de OpenWeatherMap, el id es un número (sin comillas), así que int() no hace daño pero no es necesario. Compruébalo tú: imprime type(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:

Python
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):

JSON
"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 i de cada lista corresponde a la hora time[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) -> bool que 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:

  1. Registrarse y verificar el correo y el número de teléfono.
  2. Obtener un número de prueba de Twilio (por defecto, de EE. UU.).
  3. Seguir el Quickstart de SMS para Python e instalar la biblioteca twilio.
  4. Copiar del panel el Account SID (identificador de la cuenta) y el Auth Token (clave secreta).
Python
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.status para confirmar que el mensaje quedó en cola (queued).

📖 ¿Por qué from_ con guion bajo? from es una palabra reservada de Python (como en from twilio.rest import Client). Usarla como nombre de argumento es un error de sintaxis: from='+1...' produce SyntaxError: invalid syntax (verificado). Por eso la biblioteca usa from_. 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í:

Python
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 haga print. Úsala en el programa.
  • 🟡 Reto 2: cambia enviar_alerta para 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:

  1. Comodidad: cambiar datos (por ejemplo, la lista de correos) sin tocar el código de una aplicación ya desplegada.
  2. 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):

Terminal
export OWM_API_KEY=tu_clave_sin_comillas
export AUTH_TOKEN=tu_token

Sin espacios alrededor del =.

En el código:

Python
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:

Terminal
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)

Python
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 biblioteca python-dotenv y agregar .env al archivo .gitignore para 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_NOMBRE y 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 con python-dotenv y un .gitignore que lo excluya. Explica a un compañero por qué es más seguro que escribir la clave en main.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:

Python
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.yml de 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

  1. Consultar el pronóstico de tu municipio (OpenWeatherMap con tu clave, o Open-Meteo sin clave).
  2. Decidir con una bandera si lloverá en las próximas 12 horas.
  3. Si va a llover, enviar un mensaje (SMS, WhatsApp o correo al servidor local).
  4. 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

  1. La clave que falta. Sin la variable de entorno, os.environ.get devuelve None y 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.
  2. Horas UTC. Si programas la alerta a las 6:00 a. m. de Colombia, ¿qué horas UTC de dt_txt cubren tus 12 horas? Escríbelas y compruébalas con los datos reales.
  3. Sin internet. Captura requests.exceptions.RequestException y usa timeout. Decide: si no se pudo consultar el pronóstico, ¿avisas "No pude revisar el clima" o no haces nada?
  4. ¿Qué cuenta como lluvia? La regla < 700 incluye 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

  1. Un horario.csv con las clases de Educación Física de la semana: dia,hora_inicio,hora_fin,grupo.
  2. Pronóstico por hora de Open-Meteo con timezone de Colombia (o de OpenWeatherMap con clave en variable de entorno).
  3. Para cada clase de hoy, calcular la probabilidad máxima de lluvia en su franja horaria y la temperatura.
  4. Clasificar cada clase: "Cancha" (probabilidad < 40 %), "Revisar" (40–70 %) o "Aula" (> 70 %). Los umbrales, como constantes.
  5. Enviar un correo (servidor local) con una tabla: grupo, hora, probabilidad, decisión.
  6. Si hoy no hay clases, no se envía nada.
  7. 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

  1. salida.json con el destino (coordenadas), la fecha de la salida y la hora de regreso prevista.
  2. Pronóstico del destino para la fecha de la salida (Open-Meteo u OpenWeatherMap con clave en variable de entorno).
  3. 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.
  4. Decisión: "Confirmada", "En riesgo" o "Reprogramar", según reglas que tú defines y documentas.
  5. 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.
  6. acudientes.csv ficticio; los correos se envían con EmailMessage.
  7. historial.json que registra cada consulta y cada aviso enviado, para no repetir avisos.
  8. Todas las funciones de decisión reciben los datos como parámetros, para probarlas con pronósticos simulados.
  9. 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 timeout y 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.json y 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

  1. ¿Por qué algunas APIs cobran y otras no? Da un ejemplo de cada tipo.
  2. ¿Qué es una clave de API y qué permite hacer al proveedor?
  3. ¿Qué significa el error 401? ¿En qué se diferencia de un 404?
  4. Menciona los dos usos de las variables de entorno según el curso.
  5. ¿Por qué Twilio usa from_ y no from?

Análisis de código

Python
will_rain = False
for hour_data in weather_data["list"]:
    condition_code = hour_data["weather"][0]["id"]
    if condition_code < 700:
        will_rain = True

Explica cada nivel de hour_data["weather"][0]["id"]. ¿Qué pasaría si will_rain = False estuviera dentro del for?

Predicción

  1. Los cuatro códigos del pronóstico son [800, 803, 500, 801]. ¿Qué vale will_rain al final?
  2. ¿Qué imprime print(os.environ.get("VARIABLE_QUE_NO_EXISTE"))?
  3. Un workflow tiene cron: '0 11 * * *'. ¿A qué hora se ejecuta en Colombia?

Depuración

Python
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? ¿Y units?
  • ¿Escribió lon o lng?
  • ¿Dijo que el id de 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, lng en lugar de lon, KeyError e IndexError en el JSON, bandera inicializada dentro del bucle, from como argumento, variable de entorno inexistente, export en PowerShell y horas UTC.