Saltar al contenido
Inicio 100 Días de Código en Python — Día 38: Registro de Ejercicio con Google Sheets

100 Días de Código en Python — Día 38: Registro de Ejercicio con Google Sheets

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 38 (nivel intermedio+).

Este tutorial adapta pedagógicamente el Día 38. 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 la API de práctica del curso sin clave (error 401), la autenticación Basic y Bearer con httpbin.org y los formatos de fecha. Las soluciones que se citan son las que el curso publica en sus enlaces (gists) de solución. No se crearon cuentas en la API de práctica, en Sheety ni en Google.

🔐 Datos personales y de salud (adaptación para el aula):

  1. La API de ejercicio pide género, peso, estatura y edad para calcular calorías. Son datos de salud. Los estudiantes no deben enviar sus datos reales a un servicio externo: usen valores ficticios o de referencia que defina el docente.
  2. Sheety pide permiso para acceder a las hojas de cálculo de Google de la cuenta. Usen una cuenta de prueba o institucional, nunca la personal ni la de la familia, y al terminar revoquen el acceso en la configuración de seguridad de la cuenta de Google.
  3. La sección 8 ofrece una versión 100 % local, sin cuentas, con la que todo el grupo puede lograr los mismos aprendizajes.

Tabla de contenidos

  1. Antes de empezar
  2. Paso 1: credenciales y hoja de cálculo
  3. Paso 2: estadísticas de ejercicio a partir de una frase
  4. Paso 3: conectar la hoja con Sheety
  5. Paso 4: guardar filas en Google Sheets
  6. Paso 5: proteger la hoja con autenticación Basic o Bearer
  7. Paso 6: variables de entorno
  8. 🚀 Proyecto del día: el Registro de Ejercicio
  9. 🌟 Proyecto extra: la Bitácora de Educación Física, versión local
  10. 🏆 Proyecto integrador de los Días 36 a 38: el Registro del Club Deportivo
  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 38

0. Antes de empezar

(Fuente: 288 - Day 38 Goals- what you will make by the end of the day.md)

La profesora llevaba el registro de sus entrenamientos con lápiz y papel. Hoy construiremos un programa que:

  1. Recibe una frase normal, por ejemplo "Ran 5k and cycled for 20 minutes" ("corrí 5 km y monté bicicleta 20 minutos").
  2. Usa una API de procesamiento del lenguaje natural para entender qué ejercicios hiciste, cuánto duraron y cuántas calorías gastaste. En el ejemplo, calculó que correr 5 km toma en promedio unos 31 minutos.
  3. Guarda cada ejercicio como una fila nueva en una hoja de cálculo de Google Sheets, con la fecha y la hora automáticas.

Procesamiento del lenguaje natural (PLN): la profesora cuenta que la idea surgió de las noticias sobre el modelo de IA GPT-3 de OpenAI (el curso se grabó en 2020), capaz de entender preguntas escritas como las haría una persona. Nuestra API hace algo parecido, pero solo con frases sobre ejercicio.

Lo que vas a repasar: strftime, APIs, peticiones POST, encabezados de autorización y variables de entorno. La profesora aclara que este proyecto lo construyes tú, paso a paso.

Nota sobre el material: el video de objetivos menciona que al final mostrará cómo ejecutar el programa en un editor en línea desde el celular. Esa parte no está en los archivos de la carpeta.

Prerequisito general: POST y encabezados (Día 37); variables de entorno (Día 35); strftime (Días 32 y 37); JSON anidado y bucles for sobre listas de diccionarios.


1. Paso 1: credenciales y hoja de cálculo

(Fuente: 289-step-1-setup-api-credentials-and-google-spreadsheet.md)

📝 Contenido original del curso

  1. Hacer una copia de la hoja de cálculo "My Workouts" del curso (o crear una propia).
  2. Registrarse en la API de práctica del curso, app.100DaysOfPython.dev. El curso la creó para imitar servicios comerciales como Nutritionix u OpenAI, que suelen ser de pago, y así no tener que registrarse en ellos. El correo de verificación puede llegar a spam o a la pestaña de promociones.
  3. Entrar a Nutrition & Exercise API y pulsar Generate API Key para obtener el App ID y la API Key.
  4. En un proyecto nuevo, guardar ambos valores en dos constantes: APP_ID y API_KEY.

💡 Que el curso haya creado su propia API de práctica es un ejemplo de algo que ya viste: las APIs cambian, cobran o desaparecen (Días 32, 33 y 35). Crear una versión de práctica protege a los estudiantes de esos cambios.

🏫 Para el aula

  • La hoja de cálculo puede crearla el docente en una cuenta institucional y compartir el proyecto con el grupo.
  • Si no se usan cuentas, pasa directamente a la sección 8 (versión local) después de leer las secciones 2 a 6.

2. Paso 2: estadísticas de ejercicio a partir de una frase

(Fuente: 290-step-2-get-exercise-stats-with-natural-language-queries.md)

🎯 Objetivo de aprendizaje

Leer una guía de API y construir por tu cuenta una petición POST con encabezados de autenticación.

📝 Ejercicio original del curso

Enunciado: con la guía de la API de práctica, imprimir las estadísticas de ejercicio de una frase escrita por el usuario. Por ahora la clave puede ir escrita en el código; en el paso 6 pasará a variables de entorno.

Pistas del curso:

  1. Usar lo aprendido sobre encabezados de autenticación.
  2. Usar lo aprendido sobre peticiones POST con los parámetros requeridos.

🔍 Antes de programar

  • En la guía, ¿cómo se llaman los dos encabezados de autenticación?
  • ¿Qué datos van en el cuerpo? ¿Cuáles son obligatorios?

🔎 La solución del curso

Python
import requests

GENDER = "..."         # valores ficticios en el aula (ver nota de datos personales)
WEIGHT_KG = ...
HEIGHT_CM = ...
AGE = ...

APP_ID = "TU_APP_ID"
API_KEY = "TU_API_KEY"

exercise_endpoint = "https://app.100daysofpython.dev/v1/nutrition/natural/exercise"

exercise_text = input("Tell me which exercises you did: ")

headers = {
    "x-app-id": APP_ID,
    "x-app-key": API_KEY,
}

parameters = {
    "query": exercise_text,
    "gender": GENDER,
    "weight_kg": WEIGHT_KG,
    "height_cm": HEIGHT_CM,
    "age": AGE,
}

response = requests.post(exercise_endpoint, json=parameters, headers=headers)
result = response.json()
print(result)
  • La autenticación va en dos encabezados: x-app-id y x-app-key. Sin ellos, la respuesta es (verificado):
JSON
{"error":{"code":"UNAUTHENTICATED","message":"Missing x-app-id or x-app-key header"}}
  • El cuerpo lleva la frase (query) y los datos de la persona, que la API usa para estimar las calorías: una persona más pesada gasta más energía haciendo el mismo ejercicio.
  • La respuesta trae una lista exercises; cada elemento tiene, entre otros, name, duration_min y nf_calories (los usa la solución del paso 4).

🔍 Análisis: la solución del curso no llama a raise_for_status(). Si la clave está mal, result es un diccionario de error, y el paso 4 fallará con KeyError: 'exercises', un mensaje que no explica la causa. Agrégalo.

⚠️ Errores frecuentes

Error Qué ocurre Solución
Enviar la clave en el cuerpo en lugar del encabezado 401 Encabezados x-app-id y x-app-key
params= en lugar de json= La API no encuentra query json=parameters
Escribir la frase en español Puede que la API no la entienda Pruébalo y anota el resultado (Reto 2)

Retos adicionales (creados para esta adaptación)

  • 🟢 Reto 1: imprime solo el nombre, la duración y las calorías de cada ejercicio, uno por línea.
  • 🟡 Reto 2: prueba la API con frases en español ("corrí 5 km") y en inglés. Registra en una tabla cuáles entiende.
  • 🟠 Reto 3: cambia solo el peso ficticio (60 kg y 80 kg) con la misma frase. ¿Cómo cambian las calorías? ¿Es proporcional?
  • 🔴 Reto 4: escribe obtener_ejercicios(frase) -> list[dict] que devuelva una lista vacía y un mensaje claro si la API responde con error o si no encontró ejercicios.

3. Paso 3: conectar la hoja con Sheety

(Fuente: 291-step-3-setup-your-google-sheet-with-sheety.md)

📖 ¿Qué es Sheety?

Google Sheets tiene su propia API, pero configurarla es complejo. Sheety es un servicio que convierte una hoja de cálculo en una API sencilla: cada hoja (pestaña) se vuelve un endpoint donde puedes leer filas (GET), agregarlas (POST), cambiarlas (PUT) y borrarlas (DELETE). Son los mismos métodos del Día 37.

📝 Contenido original del curso

  1. Iniciar sesión en Sheety con la misma cuenta de Google dueña de la hoja, y darle permiso para acceder a las hojas de cálculo. Si se omite, cerrar sesión y volver a entrar.
  2. Comprobar en la configuración de seguridad de la cuenta de Google que Sheety aparece como aplicación autorizada. Si no aparece, el código no podrá acceder a la hoja.
  3. En Sheety, crear un proyecto nuevo llamado "Workout Tracking" y pegar la URL de tu hoja "My Workouts".
  4. Abrir el endpoint workouts y habilitar GET y POST.

🔐 Reflexión (adaptación): al dar permiso a Sheety, un servicio externo puede leer y modificar tus hojas de cálculo. Antes de autorizar cualquier aplicación, pregúntate: ¿qué permisos pide?, ¿los necesita todos?, ¿cómo los quito después? Es la misma pregunta que harías antes de instalar una aplicación en tu celular.

Retos adicionales (creados para esta adaptación)

  • 🟢 Reto 1: con GET habilitado, abre el endpoint de Sheety en el navegador. ¿Qué forma tiene el JSON? ¿Cómo se llaman las claves?
  • 🟡 Reto 2: haz el mismo GET desde Python e imprime cada fila.
  • 🟠 Reto 3: compara los nombres de las columnas de la hoja ("Date", "Exercise") con las claves del JSON. ¿Qué transformación hace Sheety?
  • 🔴 Reto 4: busca en la configuración de tu cuenta de Google (de prueba) la lista de aplicaciones con acceso. Haz una captura y describe cómo revocarías el acceso de Sheety.

4. Paso 4: guardar filas en Google Sheets

(Fuente: 292-step-4-saving-data-into-google-sheets.md)

📝 Ejercicio original del curso

Enunciado: con la documentación de Sheety, crear una fila nueva en la hoja por cada ejercicio que devuelva la API. Las columnas de fecha y hora deben tener la fecha y hora actuales (datetime).

Advertencia del curso: si en una sola frase mencionas dos tipos de ejercicio, la API puede elegir solo uno. Para calorías exactas, es mejor hacer una consulta por ejercicio ("Ran 5k" y luego "Cycled for 20 minutes").

Pistas del curso:

  1. Los parámetros deben ir en camelCase y hay que leer con atención una parte específica de la documentación (ver abajo).
  2. .title() convierte un texto a formato título: "running".title() → "Running".
  3. strftime para dar formato a la fecha y la hora.

Consejo de depuración del curso: si algo falla, revisa que hayas iniciado sesión en Sheety con la misma cuenta de Google dueña de la hoja.

📖 La parte de la documentación que hay que leer con atención

Sheety espera que los datos de la fila vayan dentro de una clave raíz con el nombre de la hoja en singular y en camelCase. La hoja se llama workouts, así que la clave es "workout". Las columnas "Date", "Time", "Exercise", "Duration" y "Calories" se escriben como date, time, exercise, duration, calories.

🔎 La solución del curso

Python
from datetime import datetime

today_date = datetime.now().strftime("%d/%m/%Y")
now_time = datetime.now().strftime("%X")

for exercise in result["exercises"]:
    sheet_inputs = {
        "workout": {
            "date": today_date,
            "time": now_time,
            "exercise": exercise["name"].title(),
            "duration": exercise["duration_min"],
            "calories": exercise["nf_calories"],
        }
    }

    sheet_response = requests.post(sheet_endpoint, json=sheet_inputs)
    print(sheet_response.text)
  • %d/%m/%Y → 26/09/2026; %X → la hora con el formato local, por ejemplo 14:05:33 (verificado).
  • Un for recorre los ejercicios y hace un POST por ejercicio: una fila por cada uno.

⚠️ Errores frecuentes

Error Qué ocurre Solución
Enviar los datos sin la clave "workout" Sheety no crea la fila Envuelve la fila en la clave singular
"Workout" o "workouts" Error de Sheety Singular y en minúscula inicial: "workout"
Claves "Exercise", "Calories" con mayúscula Las celdas quedan vacías camelCase: exercise, calories
Cuenta de Sheety distinta a la dueña de la hoja Error de permisos Misma cuenta de Google
No habilitar POST en Sheety Error de método no permitido Habilítalo en el endpoint

Retos adicionales (creados para esta adaptación)

  • 🟢 Reto 1: cambia el formato de la fecha a 2026-09-26 y observa cómo lo muestra la hoja.
  • 🟡 Reto 2: redondea las calorías a un decimal antes de enviarlas.
  • 🟠 Reto 3: agrega una columna "Notes" en la hoja y envía una nota escrita por el usuario. ¿Qué nombre de clave debes usar?
  • 🔴 Reto 4: después de guardar, haz un GET y muestra el total de calorías de la semana actual, calculado en Python.

5. Paso 5: proteger la hoja con autenticación Basic o Bearer

(Fuente: 293-step-5-authenticate-your-sheety-api.md)

🎯 Objetivo de aprendizaje

Distinguir dos esquemas estándar de autenticación HTTP y usarlos con requests.

📖 El problema

Tal como está, cualquiera que conozca la URL de tu endpoint de Sheety puede leer y escribir en tu hoja. Sheety permite protegerla con Basic Authentication o con Bearer Token.

📖 Los dos esquemas

Esquema Qué envías Cómo se ve el encabezado
Basic Usuario y contraseña Authorization: Basic cHJvZmU6Y2xhdmUxMjM=
Bearer Un token secreto Authorization: Bearer mi_token_secreto

Bearer significa "portador": el curso lo explica como "dar acceso a quien porte este token". El token es una cadena larga y difícil de adivinar.

🔎 La solución del curso

Python
# Autenticación Basic: requests arma el encabezado por ti
sheet_response = requests.post(
    sheet_endpoint,
    json=sheet_inputs,
    auth=(USERNAME, PASSWORD),
)

# Autenticación Bearer: el encabezado se arma a mano
bearer_headers = {
    "Authorization": f"Bearer {TOKEN}"
}
sheet_response = requests.post(
    sheet_endpoint,
    json=sheet_inputs,
    headers=bearer_headers,
)

Practica sin cuenta con httpbin (verificado):

  • requests.get("https://httpbin.org/basic-auth/profe/clave123", auth=("profe", "clave123")) → 200 {"authenticated": true, "user": "profe"}; con una contraseña equivocada → 401.
  • requests.get("https://httpbin.org/bearer", headers={"Authorization": "Bearer mi_token_secreto"}) → 200; sin el encabezado → 401.

📖 Basic no es cifrado (análisis de esta adaptación)

Con auth=("profe", "clave123"), requests envía el encabezado Authorization: Basic cHJvZmU6Y2xhdmUxMjM= (verificado con httpbin). Parece secreto, pero es solo Base64, una forma de codificar texto que cualquiera puede revertir:

Python
import base64
print(base64.b64decode("cHJvZmU6Y2xhdmUxMjM=").decode())   # profe:clave123

(Verificado.) Codificar no es cifrar. Por eso la autenticación Basic solo es segura sobre HTTPS, que sí cifra toda la comunicación. Lo mismo vale para el token Bearer: quien lo intercepte o lo vea en tu código, lo puede usar.

⚠️ Errores frecuentes

Error Qué ocurre Solución
"Authorization": TOKEN sin la palabra Bearer 401 f"Bearer {TOKEN}", con un espacio
"Bearer" + TOKEN sin espacio 401 Deja el espacio
Activar la autenticación en Sheety y no actualizar el código 401 Agrega auth= o headers=
Creer que Basic cifra la contraseña Falsa sensación de seguridad Solo es seguro sobre HTTPS

Retos adicionales (creados para esta adaptación)

  • 🟢 Reto 1: reproduce las cuatro pruebas de httpbin (Basic bien y mal, Bearer con y sin token).
  • 🟡 Reto 2: codifica en Base64 tu propio usuario:contraseña inventado con base64.b64encode y compáralo con lo que envía requests.
  • 🟠 Reto 3: investiga la clase HTTPBasicAuth de requests y reescribe el ejemplo con ella.
  • 🔴 Reto 4: escribe una función crear_autenticacion(config) que, según un diccionario de configuración, devuelva lo necesario para Basic, para Bearer o para ninguna autenticación.

6. Paso 6: variables de entorno

(Fuente: 294-step-6-environment-variables.md)

📝 Contenido original del curso

Mover todos los datos sensibles a variables de entorno: APP_ID, API_KEY, SHEET_ENDPOINT, USERNAME, PASSWORD y TOKEN. Incluso el endpoint de Sheety es sensible: con él, cualquiera podría intentar acceder a tu hoja.

Tres formas de leer una variable (lección 294):

Python
APP_ID = os.environ["APP_ID"]                  # lanza excepción si no existe
APP_ID = os.environ.get("APP_ID")              # devuelve None si no existe
APP_ID = os.environ.get("APP_ID", "Message")   # devuelve "Message" si no existe

En PyCharm: Run → Edit Configurations → Environment variables: se agregan los pares nombre-valor (también se pueden pegar todos juntos). Sin espacios alrededor del =.

La solución final del curso usa os.environ["NT_APP_ID"], os.environ["NT_API_KEY"], os.environ["SHEET_ENDPOINT"] y os.environ['TOKEN']: la forma con corchetes, que falla de inmediato si falta alguna (Día 35).

🛠️ Nota técnica 1: en la pista de la lección, el tercer ejemplo aparece con comillas tipográficas: “Message”. Si lo copias tal cual, Python da SyntaxError: invalid character '“' (U+201C) (verificado). Escribe comillas rectas: "Message". Es un error frecuente al copiar código de documentos o páginas web.

🛠️ Nota técnica 2: la lección muestra también cómo asignar una variable desde el código: os.environ["APP_ID"] = APP_ID. Eso no esconde nada: el valor sigue escrito en tu archivo .py. Solo sirve para pruebas; el objetivo del paso es que las claves no estén en el código.

Retos adicionales (creados para esta adaptación)

  • 🟢 Reto 1: configura las variables en tu editor y comprueba que el programa funciona sin ninguna clave en el código.
  • 🟡 Reto 2: al iniciar, verifica que existan las 6 variables y muestra la lista de las que faltan (sin mostrar los valores).
  • 🟠 Reto 3: pide a un compañero que revise tu código buscando cualquier dato sensible. ¿Encontró algo?
  • 🔴 Reto 4: explica en un párrafo por qué el endpoint de Sheety se considera sensible, aunque no sea una contraseña.

7. 🚀 Proyecto del día: el Registro de Ejercicio

(Fuentes: 288 a 294)

Problema

Registrar entrenamientos escribiendo una frase normal y guardarlos automáticamente en una hoja de cálculo protegida.

Requisitos funcionales

  1. Pedir al usuario una frase sobre su ejercicio.
  2. Obtener nombre, duración y calorías de cada ejercicio (API de práctica).
  3. Guardar una fila por ejercicio con fecha y hora (Sheety).
  4. Endpoint protegido con Bearer o Basic.
  5. Todos los datos sensibles en variables de entorno.

Planificación

Texto
frase del usuario ──POST (x-app-id, x-app-key)──► API de ejercicio ──► lista de ejercicios
                                                                            │
                          para cada ejercicio: POST {"workout": {...}} ◄─────┘
                                   (Authorization: Bearer …)
                                            │
                                            ▼
                                     Fila en Google Sheets

Pruebas

Caso Esperado
"Ran 5k" Una fila con Running, duración y calorías
"Swam for 30 minutes" Una fila con Swimming
Frase sin ejercicio ("hello") ⚠️ (ver 🧪)
Clave de la API equivocada ⚠️ (ver 🧪)
Token de Sheety equivocado Error 401 visible y claro

🧪 Prueba y depuración

  1. Sin ejercicios. Con una frase que no describe ejercicio, ¿qué devuelve la API? Si la lista exercises está vacía, el programa no guarda nada ni avisa. Muestra "No reconocí ningún ejercicio".
  2. Error de autenticación. Con una clave equivocada, result es un diccionario de error y el for falla con KeyError: 'exercises'. Agrega raise_for_status() o revisa el código de estado y muestra un mensaje útil.
  3. Dos veces datetime.now(). La solución llama now() una vez para la fecha y otra para la hora. Si el programa se ejecuta justo a medianoche, ¿podrían quedar la fecha de un día y la hora del siguiente? Corrígelo llamando now() una sola vez.
  4. Varios ejercicios en una frase. Prueba la advertencia del curso: "Ran 5k and cycled for 20 minutes". ¿Obtienes una o dos filas? Si solo una, divide la frase por " and " y haz una consulta por parte.

⭐ Extensión del proyecto

  • Pedir varios ejercicios en un bucle hasta que el usuario escriba "fin".
  • Resumen semanal con un GET a Sheety.
  • Una interfaz Tkinter con una entrada y un botón "Registrar".

8. 🌟 Proyecto extra: la Bitácora de Educación Física, versión local

Situación

El área de Educación Física quiere que cada estudiante lleve una bitácora de su actividad física, pero sin enviar datos a servicios externos. Construiremos nuestra propia versión del "procesamiento de lenguaje" y del cálculo de calorías, y guardaremos todo en un archivo local.

Conceptos nuevos

  • MET (equivalente metabólico): mide cuánta energía gasta una actividad comparada con estar en reposo. Una fórmula de estimación muy usada es: calorías ≈ MET × peso (kg) × tiempo (horas).
  • Los valores MET de cada actividad deben investigarse en una fuente confiable (por ejemplo, el Compendio de Actividades Físicas, una referencia científica) y citarse en el proyecto.

Requisitos

  1. Un archivo actividades.json con al menos 8 actividades en español, sus sinónimos (por ejemplo, "correr", "corrí", "trotar") y su valor MET, con la fuente citada.
  2. Una función interpretar(frase) -> list[dict] que reconozca frases como "corrí 20 minutos y jugué fútbol 1 hora": encuentre las actividades por sus sinónimos y extraiga la duración (minutos u horas). Pista: split, lower, y buscar números cerca de la actividad.
  3. Cálculo de calorías con un peso de referencia definido por el docente (no el peso real del estudiante).
  4. Guardado en bitacora.csv con pandas: fecha (%d/%m/%Y), hora, actividad (con .title()), duración y calorías.
  5. Una clase AlmacenBitacora con métodos agregar(fila) y listar(desde, hasta), con la misma idea de "clave raíz" que Sheety: cada fila se recibe como {"registro": {...}} y se valida que tenga todas las columnas.
  6. Mensajes claros si la frase no tiene actividades reconocidas o no tiene duración.

Decisiones que debes tomar

  • ¿Qué haces con frases ambiguas como "corrí un rato"?
  • ¿Tu "API casera" debería devolver lo mismo que la API del curso (name, duration_min, nf_calories) para poder intercambiarlas? Justifica.

Casos de prueba

Frase Esperado
"corrí 30 minutos" 1 registro: Correr, 30 min
"monté bici 1 hora y nadé 20 minutos" 2 registros
"hoy estudié" Mensaje: no se reconoció actividad física
"troté" Mensaje: falta la duración

9. 🏆 Proyecto integrador de los Días 36 a 38: el Registro del Club Deportivo

Proyecto integrador creado para esta adaptación. Reúne combinación de varias APIs y caché (Día 36), POST, PUT, DELETE y encabezados (Día 37), y hojas de cálculo como API, autenticación Basic y Bearer, y variables de entorno (Día 38).

Situación problema

El club deportivo del colegio registra en papel los entrenamientos de sus integrantes. El entrenador quiere un sistema digital con un resumen semanal y la posibilidad de corregir o borrar registros, y que funcione con o sin Google Sheets.

Pregunta orientadora

¿Cómo diseñar un programa cuyos datos puedan vivir en distintos lugares (un archivo local o una hoja de cálculo en la nube) sin cambiar la lógica, y que proteja el acceso a esos datos?

Objetivo

Construir el Registro del Club Deportivo: un programa de consola o Tkinter para registrar, consultar, corregir y borrar entrenamientos.

Requerimientos

  1. Una clase base AlmacenEntrenamientos con crear, listar, actualizar y borrar.
  2. Dos implementaciones:
    • AlmacenCSV: archivo local con pandas.
    • AlmacenSheety: la hoja del docente vía Sheety (GET, POST, PUT, DELETE), con Bearer.
  3. Interpretación de frases: la API de práctica del curso o la versión local de la sección 8, elegida en config.json.
  4. Integrantes ficticios en integrantes.csv (nombre, deporte, peso de referencia).
  5. Resumen semanal: minutos y calorías por integrante y por deporte; quién no entrenó en la semana. Enviado por correo (servidor local del Día 32).
  6. Corregir (PUT) y borrar (DELETE) un registro por su identificador, con confirmación.
  7. requests-cache desactivado para los POST (¿por qué?) y opcional para los GET de desarrollo.
  8. Todas las credenciales en variables de entorno; el programa verifica al iniciar que existan las necesarias según config.json.

Restricciones

  • Ningún dato personal real.
  • La lógica del resumen no sabe qué almacén se está usando.

Etapas de desarrollo

Etapa Entregable parcial
1. Diseño Diagrama de clases y columnas de los datos
2. AlmacenCSV Las cuatro operaciones probadas
3. Interpretación Frases de 5 deportes interpretadas correctamente
4. Resumen Resumen semanal con datos simulados de 3 semanas
5. AlmacenSheety Demostración con la hoja del docente
6. Presentación Cambio de almacén solo con config.json

Entregables

  • Código organizado en módulos, config.json y datos ficticios.
  • Diagrama de clases.
  • 12 casos de prueba con resultados.
  • Registro de errores con al menos 5 errores reales.
  • Un párrafo sobre qué datos del club son sensibles y cómo los protegiste.

Casos de prueba mínimos

Caso Esperado
Registrar con AlmacenCSV y listar El registro aparece
Corregir la duración de un registro El resumen cambia
Borrar sin confirmar No se borra nada
Token de Sheety equivocado Mensaje de error de autenticación claro
Cambiar de CSV a Sheety en config.json El resto del programa funciona igual

Criterios técnicos de funcionamiento

  • Solo las clases de almacén hacen peticiones o leen archivos.
  • Las funciones de resumen se pueden probar con listas de diccionarios simuladas.
  • Ninguna credencial aparece en el código ni en config.json.

Posibles extensiones

Gráficas de progreso (se estudian más adelante en el curso), exportar el resumen a Excel, ranking semanal del club.


10. 🧩 Comprueba lo aprendido

Conceptuales

  1. ¿Qué es el procesamiento del lenguaje natural? Da un ejemplo de este proyecto.
  2. ¿Qué hace Sheety y por qué facilita el trabajo con Google Sheets?
  3. ¿Qué diferencia hay entre la autenticación Basic y la Bearer?
  4. ¿Por qué se dice que Base64 no es cifrado?
  5. ¿Por qué el endpoint de Sheety debe ir en una variable de entorno?

Análisis de código

Python
sheet_inputs = {
    "workout": {
        "date": today_date,
        "exercise": exercise["name"].title(),
    }
}

¿Por qué la clave raíz es "workout" y no "workouts"? ¿De dónde sale exercise["name"]?

Predicción

  1. ¿Qué imprime "cycling hard".title()?
  2. ¿Qué imprime datetime(2026, 3, 7).strftime("%d/%m/%Y")?
  3. ¿Qué encabezado envía requests con auth=("ana", "1234")? (Pista: calcula Base64 de ana:1234 con Python.)

Depuración

Python
headers = {"Authorization": TOKEN}
sheet_inputs = {"Workouts": {"Date": today, "Exercise": name}}
requests.post(sheet_endpoint, params=sheet_inputs, headers=headers)

Encuentra cuatro errores.

Modificación

Cambia el programa para que pregunte "¿Guardar este ejercicio? (s/n)" antes de cada POST, mostrando nombre, duración y calorías.

Creación

Escribe calorias_estimadas(met: float, peso_kg: float, minutos: float) -> float con la fórmula MET de la sección 8, y pruébala con 3 casos calculados a mano.


11. 🤖 Programar con apoyo de IA

Actividad 1: la IA como "API de lenguaje natural". La inspiración del proyecto fue un modelo de IA. Pide a una IA: "Convierte esta frase en JSON con ejercicio y minutos: 'corrí 20 minutos y nadé media hora'". Luego:

  1. Compara su JSON con lo que devuelve tu función interpretar de la sección 8.
  2. Prueba 5 frases difíciles (con errores de ortografía, números en letras, varias actividades).
  3. ¿En qué casos es mejor la IA? ¿En cuáles tu función? ¿Cuál es más predecible?

Actividad 2: verificar cálculos de salud. Pregunta a una IA cuántas calorías gasta una persona de 60 kg corriendo 30 minutos. Compara con la fórmula MET y un valor MET de una fuente confiable. ¿La IA citó su fuente? ¿Coinciden los resultados? Recuerda: las calorías son estimaciones, no medidas exactas.

Actividad 3: revisar permisos. Pide a una IA que explique qué permisos pide una aplicación como Sheety a una cuenta de Google. Luego compara con lo que muestra la pantalla de autorización real (en la demostración del docente). ¿La IA acertó?


12. 🛠️ Correcciones técnicas al material original

Archivo Afirmación o comportamiento del curso Observación Tratamiento
288 Mostrar cómo ejecutar el programa en un editor en línea Esa parte no está en los archivos de la carpeta Sección 0
290 Enviar género, peso, estatura y edad reales Datos de salud; en el aula, usar valores ficticios Nota inicial; sección 8
290, 292 La solución no verifica el código de estado Con una clave equivocada, el error aparece como KeyError: 'exercises' Análisis de la sección 2; 🧪 del proyecto
292 datetime.now() llamado dos veces Fecha y hora podrían no corresponder si se ejecuta a medianoche 🧪 del proyecto
293 Basic y Bearer como formas de "asegurar" la hoja Basic es solo Base64 (verificado): seguro únicamente sobre HTTPS Análisis de la sección 5
294 os.environ.get("APP_ID", “Message”) Las comillas tipográficas producen SyntaxError (verificado) Nota técnica 1 de la sección 6
294 os.environ["APP_ID"] = APP_ID para "establecer" variables No esconde nada: el valor sigue en el código Nota técnica 2 de la sección 6

13. 📋 Matriz de trazabilidad

Contenido original Archivo de origen Concepto desarrollado Ejercicio original Reto adicional Proyecto relacionado
Objetivos, lenguaje natural, GPT-3, hoja de entrenamientos 288 Sección 0 — Actividad 1 de IA Sección 7
Copia de la hoja, API de práctica, App ID y API Key 289 Sección 1 Paso 1 — Secciones 7 y 9
POST con encabezados x-app-id/x-app-key, parámetros de la persona 290 Sección 2 Paso 2 Retos 1–4 de la sección 2 Secciones 7, 8 y 9
Sheety, permisos de Google, proyecto, GET y POST 291 Sección 3 Paso 3 Retos 1–4 de la sección 3 Secciones 7 y 9
Una fila por ejercicio, clave raíz workout, camelCase, .title(), strftime 292 Sección 4 Paso 4 Retos 1–4 de la sección 4 Secciones 7, 8 y 9
Basic y Bearer, encabezado Authorization 293 Sección 5 Paso 5 Retos 1–4 de la sección 5 Secciones 7 y 9
Variables de entorno en PyCharm, os.environ 294 Sección 6 Paso 6 Retos 1–4 de la sección 6 Secciones 7 y 9
(No existe en el curso original) — — — Bitácora de Educación Física local Sección 8
(No existe en el curso original) — — — Registro del Club Deportivo (Días 36–38) Sección 9

14. 📦 Evidencias de aprendizaje del Día 38

  • La respuesta 401 de la API sin encabezados y la respuesta exitosa con ellos (con datos ficticios).
  • La tabla del Reto 2 de la sección 2: frases en español e inglés que entiende la API.
  • La hoja de cálculo con filas creadas por tu programa (en la cuenta del docente o de prueba), o el bitacora.csv de la versión local.
  • Las cuatro pruebas de autenticación con httpbin y tu decodificación de Base64.
  • El programa sin ninguna credencial en el código.
  • La Bitácora de Educación Física local (proyecto extra) o el Registro del Club Deportivo (proyecto integrador).
  • Cuaderno de errores: encabezados de autenticación ausentes, params frente a json, clave raíz de Sheety, camelCase, cuenta de Google distinta, Bearer sin espacio, comillas tipográficas, KeyError: 'exercises' y variables de entorno que faltan.