Saltar al contenido
Inicio 100 Días de Código en Python — Día 47: Rastreador automático de precios

100 Días de Código en Python — Día 47: Rastreador automático de precios

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

Este tutorial adapta pedagógicamente el Día 47. Las secciones 📝 Ejercicio original del curso provienen de los archivos del curso (lecciones 345 a 349). Las soluciones oficiales están en los gists enlazados en cada paso. 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: el 26 de septiembre de 2026, con Python 3.14, requests 2.34, beautifulsoup4 4.15 y un servidor de correo local (aiosmtpd), se comprobaron: la extracción del precio del sitio de práctica, los encabezados que envía requests por defecto y el envío de la alerta por correo. No se hicieron peticiones a Amazon.

🏫 Para el aula: el paso 4 del curso extrae datos de Amazon en vivo. Las Condiciones de Uso de Amazon prohíben el uso de robots y herramientas de extracción de datos en su sitio, y Amazon suele responder a los bots con un CAPTCHA. En clase, trabaja con el sitio de práctica del curso; el paso 4 puede verse como demostración o análisis. Para tiendas reales, busca primero si ofrecen una API (por ejemplo, Mercado Libre tiene una API pública para desarrolladores).


Tabla de contenidos

  1. Antes de empezar: que el programa vigile por ti
  2. Paso 1: extraer el precio del producto
  3. Paso 2: alerta por correo y datos sensibles en .env
  4. Paso 3: encabezados HTTP
  5. Paso 4: el sitio real
  6. 🚀 Proyecto del día: el rastreador de precios
  7. 🌟 Proyecto extra: la alerta de libros del plan lector
  8. 🏆 Proyecto integrador del bloque (Días 45 a 51) — Etapa 3
  9. 🧩 Comprueba lo aprendido
  10. 🤖 Programar con apoyo de IA
  11. 🛠️ Correcciones técnicas al material original
  12. 📋 Matriz de trazabilidad
  13. 📦 Evidencias de aprendizaje del Día 47

0. Antes de empezar: que el programa vigile por ti

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

La profesora quiere comprar una Instant Pot (una olla a presión multifunción), pero no le parece justo pagar 120 dólares. Observó que en las tiendas en línea los precios suben y bajan casi al azar: con camelcamelcamel.com vio el historial de precios del producto y descubrió que el precio más bajo no fue en un día de descuentos como el Black Friday, sino un día cualquiera.

Su solución: un bot de Python que, todos los días a una hora fija, revise el precio y, si baja de un precio objetivo (por ejemplo, 100 dólares), le envíe un correo con el enlace para comprar.

Lo nuevo hoy: convertir texto extraído de una página en número, tomar decisiones con él y combinar scraping con el correo del Día 32. También veremos qué son los encabezados HTTP y por qué los servidores los revisan.

Prerequisitos: Beautiful Soup (Día 45), smtplib (Día 32), variables de entorno y .env (Días 35 y 38), ejecución programada en PythonAnywhere (Días 32 y 35).


1. Paso 1: extraer el precio del producto

(Fuente: 346-step-1-updated-use-beautifulsoup-to-scrape-the-product-price.md)

🎯 Objetivo de aprendizaje

Extraer un precio de una página y convertirlo en un número de punto flotante (float).

📖 Explicación conceptual

"Los sitios en vivo cambian muchísimo", advierte el curso. Por eso primero se trabaja con un clon estático de la página de Amazon: appbrewery.github.io/instant_pot/.

Del texto al número: lo que obtiene Beautiful Soup siempre es texto (str). Para comparar con un precio objetivo hay que convertirlo en float, quitando antes todo lo que no sea número (el símbolo $, espacios, etc.). La pista del curso es el método split().

Datos verificados del sitio de práctica (26/09/2026):

  • El precio completo aparece en un elemento con clase a-offscreen: texto "$99.99".
  • También está partido en dos: a-price-whole ("99.", con el punto incluido) y a-price-fraction ("99").
  • El título del producto está en el elemento con id="productTitle", rodeado de muchos espacios y saltos de línea.

🔎 Ejemplo explicado

Python
price_text = "$99.99"                  # lo que devolvió getText()
partes = price_text.split("$")         # ['', '99.99']
price = float(partes[1])               # 99.99
print(price, type(price))              # 99.99 <class 'float'>
  • split("$") corta el texto donde aparece $. Como $ está al principio, el primer elemento queda vacío.
  • float(...) convierte "99.99" en el número 99.99. Si el texto tuviera letras o símbolos, lanzaría ValueError.

Para limpiar el título:

Python
title = " ".join(soup.find(id="productTitle").getText().split())

split() sin argumentos corta por cualquier espacio en blanco (espacios, tabulaciones, saltos de línea) y descarta los vacíos; " ".join(...) vuelve a unir con un solo espacio.

⚠️ Errores frecuentes

Error Qué ocurre Solución
float("$99.99") ValueError: could not convert string to float Quita el $ antes de convertir
Unir a-price-whole + "." + a-price-fraction "99." + "." + "99" = "99..99" → ValueError, porque la parte entera ya trae el punto (verificado) Usa el elemento a-offscreen o no agregues otro punto
Precios con separador de miles ("$1,299.99") float falla por la coma .replace(",", "") antes de convertir
En pesos colombianos ("$ 1.299.900") El punto es separador de miles, no decimal Quita los puntos y los espacios; ¡el formato depende del país!
soup.find(...) devuelve None El selector no coincide Revisa en DevTools; comprueba None antes de usar el resultado

📝 Ejercicio original del curso: paso 1

Con Beautiful Soup, obtener el precio del artículo del sitio de práctica como float e imprimirlo. (Pista: split().)

🔍 Antes de programar

  • ¿Qué tipo de dato devuelve getText()? ¿Qué tipo necesitas para comparar con 100?
  • ¿Qué caracteres sobran en el texto del precio?
  • ¿En qué elemento está el precio completo? ¿Y si solo encontraras las partes?

Retos adicionales (creados para esta adaptación)

  • 🟢 Reto 1: extrae también el título limpio y la calificación del producto (búscala en DevTools).
  • 🟡 Reto 2: escribe una función texto_a_precio(texto) que funcione con "$99.99", "$1,299.99" y "US$ 5". Pruébala con los tres casos.
  • 🟠 Reto 3: amplía la función para precios en pesos colombianos ("$ 1.299.900", "$89.900"). ¿Cómo sabe la función si el punto es decimal o separador de miles? Decide una regla y documéntala.
  • 🔴 Reto 4 — Pensamiento computacional: el sitio podría mostrar un precio tachado (el anterior) y el precio actual. Diseña cómo distinguirías cuál es cuál sin depender solo del orden en la página.

2. Paso 2: alerta por correo y datos sensibles en .env

(Fuente: 347-step-2-email-alert-when-the-price-is-below-preset-value.md)

🎯 Objetivo de aprendizaje

Enviar un correo solo cuando se cumple una condición, sin escribir contraseñas en el código.

📖 Explicación conceptual

La condición: si el precio es menor que el objetivo (100 dólares), enviar un correo con el título, el precio actual y el enlace del producto. En el sitio de práctica el precio es fijo ($99.99), así que para probar el envío el objetivo debe ser 100 o más.

Datos sensibles fuera del código (repaso de los Días 35 y 38):

Texto
# .env
SMTP_ADDRESS="smtp.gmail.com"
EMAIL_ADDRESS="tu_correo@ejemplo.com"
EMAIL_PASSWORD="contraseña_de_aplicación"
Python
import os
from dotenv import load_dotenv

load_dotenv()                                   # lee el archivo .env
smtp_address = os.environ["SMTP_ADDRESS"]

Si aparece SMTPServerDisconnected, el curso recomienda:

  1. Revisar la dirección SMTP del proveedor (Gmail: smtp.gmail.com; Yahoo: smtp.mail.yahoo.com).
  2. Activar la verificación en dos pasos en la cuenta.
  3. Crear una contraseña de aplicación y usarla en lugar de la contraseña normal.
  4. Indicar el puerto: smtplib.SMTP("smtp.gmail.com", port=587).

🔎 Ejemplo explicado: el correo con tildes

El título del producto del sitio de práctica contiene la palabra "Sauté". Esto importa:

Python
# Estilo del Día 32, con sendmail y un texto:
connection.sendmail(from_addr=..., to_addrs=..., msg=f"Subject:Alerta\n\n{message}")
# -> UnicodeEncodeError: 'ascii' codec can't encode character '\xe9'  (verificado)

# Solución 1: convertir el mensaje a bytes UTF-8
connection.sendmail(from_addr=..., to_addrs=..., msg=f"Subject:Alerta\n\n{message}".encode("utf-8"))

# Solución 2 (recomendada): EmailMessage declara la codificación
from email.message import EmailMessage
msg = EmailMessage()
msg["Subject"] = "Alerta de precio"
msg["From"] = email_address
msg["To"] = email_address
msg.set_content(f"{title} ahora cuesta ${price}\n{URL}")
connection.send_message(msg)

Con EmailMessage el correo lleva la cabecera charset="utf-8" y las tildes llegan bien (verificado con un servidor local).

🏫 Para el aula: un servidor de correo local

Para probar sin cuentas reales ni contraseñas, usa el servidor de prueba aiosmtpd (el mismo recurso del Día 40):

Texto
pip install aiosmtpd
python -m aiosmtpd -n -l localhost:8025

Ese servidor no envía nada a Internet: muestra en la terminal cada correo que recibe. En tu programa, conéctate con smtplib.SMTP("localhost", 8025) y sin starttls() ni login().

⚠️ Errores frecuentes

Error Causa Solución
UnicodeEncodeError al enviar Texto con tildes enviado con sendmail como str .encode("utf-8") o EmailMessage
SMTPAuthenticationError Contraseña normal en lugar de contraseña de aplicación Crea una contraseña de aplicación (requiere verificación en dos pasos)
KeyError: 'SMTP_ADDRESS' No se cargó el .env o el nombre no coincide load_dotenv() antes de leer; revisa mayúsculas
El .env subido a Internet Tus credenciales quedan públicas .gitignore; si ocurrió, revoca la contraseña de aplicación
Un correo cada vez que se ejecuta mientras el precio sigue bajo Nadie recuerda que ya se avisó Guarda en un archivo si ya enviaste la alerta (Reto 3)

📝 Ejercicio original del curso: paso 2

  1. Si el precio es menor que el objetivo, enviar un correo con título, precio y enlace (Solution 2).
  2. Crear un .env con SMTP_ADDRESS, EMAIL_ADDRESS y EMAIL_PASSWORD, y leerlo con os y dotenv (Solution 3).

🔍 Antes de programar

  • ¿Qué información debe llevar el correo para que sea útil sin abrir el programa?
  • ¿Qué datos son sensibles y deben ir en .env? ¿La URL del producto lo es?
  • ¿Cómo pruebas el envío si el precio real nunca baja?

Retos adicionales (creados para esta adaptación)

  • 🟢 Reto 1: envía la alerta al servidor local y toma una captura de la terminal donde se ve el correo.
  • 🟡 Reto 2: incluye en el correo cuánto se ahorra respecto al precio objetivo y en qué porcentaje.
  • 🟠 Reto 3: guarda en estado.json el último precio y si ya se envió la alerta, para avisar una sola vez cada vez que el precio cruce el objetivo.
  • 🔴 Reto 4 — Pensamiento computacional: en lugar de un precio objetivo fijo, alerta cuando el precio sea el más bajo de los últimos 30 días. ¿Qué datos debes guardar? ¿Qué haces los primeros días, cuando todavía no tienes 30 registros?

3. Paso 3: encabezados HTTP

(Fuente: 348-step-3-add-headers-to-your-request.md)

🎯 Objetivo de aprendizaje

Explicar qué son los encabezados HTTP, verlos en tu navegador y enviarlos con requests.

📖 Explicación conceptual

Cuando tu navegador pide una página, además de la URL envía encabezados (headers): qué navegador usas, qué sistema operativo, qué idiomas prefieres… Con eso el servidor puede responder en tu idioma, tu moneda y tu región.

Encabezado Para qué sirve
User-Agent Identifica el navegador y el sistema operativo
Accept-Language Idiomas preferidos, en orden (por ejemplo es-CO,es;q=0.9,en;q=0.8)

¿Por qué agregarlos? (según el curso)

  1. El servidor puede darte la página en tu idioma y tu moneda.
  2. La petición parece "un poco más humana": muchos servidores, como los de Amazon, bloquean lo que creen que viene de bots.

Lo que envía requests si no le dices nada (verificado):

Python
print(requests.get("https://httpbin.org/headers").json()["headers"]["User-Agent"])
# python-requests/2.34.2

¡El servidor sabe que eres un programa de Python!

Cómo enviar encabezados:

Python
header = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...",
    "Accept-Language": "es-CO,es;q=0.9",
}
response = requests.get(URL, headers=header)

Actividad del curso: visita myhttpheader.com (o httpbin.org/headers) con varios navegadores (Chrome, Firefox, Brave…) y compara lo que envía cada uno. Copia como mínimo el User-Agent y el Accept-Language a tu código (sin Host ni X-Amzn-Trace-Id).

📖 Reflexión ética (adaptación)

Enviar un User-Agent de navegador es una práctica común, pero conviene ser honesto sobre su propósito: sirve para recibir la versión correcta de la página, no para burlar un bloqueo que el sitio puso a propósito. Si un sitio responde con un CAPTCHA, te está diciendo que no quiere bots: la respuesta ética es buscar una API o dejar de hacer peticiones, no intentar "engañarlo". Algunos desarrolladores prefieren un User-Agent que se identifique, por ejemplo "ProyectoEscolar/1.0 (contacto@colegio.edu.co)", para que el dueño del sitio sepa quién hace las peticiones.

⚠️ Errores frecuentes

Error Qué ocurre Solución
requests.get(URL, header) El segundo argumento posicional es params, no headers: los encabezados se envían como parámetros de la URL requests.get(URL, headers=header)
Escribir "User Agent" (sin guion) El servidor no lo reconoce "User-Agent"
Copiar la cabecera Host de otro sitio Petición inválida o dirigida a otro servidor No copies Host

Retos adicionales (creados para esta adaptación)

  • 🟢 Reto 1: imprime todos los encabezados que envía requests por defecto (con httpbin.org/headers) y luego los que envía con tu diccionario. Compara.
  • 🟡 Reto 2: pide httpbin.org/headers con Accept-Language en español y luego en japonés (ja). ¿Cambia algo en la respuesta de httpbin? ¿Por qué en ese sitio no cambia y en una tienda sí podría cambiar?
  • 🟠 Reto 3: investiga qué significa q=0.9 en Accept-Language y escribe el encabezado de alguien que prefiere español de Colombia, luego español en general y por último inglés.
  • 🔴 Reto 4: escribe una función pedir_pagina(url) que agregue siempre tus encabezados, espere un tiempo mínimo entre peticiones al mismo dominio y devuelva None (con un mensaje) si la respuesta no es 200. Úsala en todos tus scrapers de ahora en adelante.

4. Paso 4: el sitio real

(Fuente: 349-step-4-scrape-the-live-amazon-site.md)

📝 Contenido original del curso

  1. Elegir un producto de Amazon (o usar el de la profesora) y copiar su URL.
  2. Inspeccionar el precio y el título: puede que soup.find() necesite otros selectores que en el sitio de práctica.
  3. Cambiar la URL del sitio de práctica por la del producto real.
  4. Ejecutar e imprimir soup.prettify() para verificar que llegó la página del producto y no una página de CAPTCHA.

Si no funciona, el curso sugiere:

  1. Usar la tienda de tu región (por ejemplo, amazon.co.uk en el Reino Unido).
  2. Probar con pocos o con muchos encabezados.
  3. Cambiar el analizador a "lxml" (instalando el paquete). Si aparece bs4.FeatureNotFound: Couldn't find a tree builder with the features you requested, falta instalar lxml o el nombre del analizador está mal escrito.

📖 Análisis para el aula (adaptación)

Este paso enseña algo valioso: la diferencia entre un entorno de práctica y el mundo real. En el mundo real:

  • el HTML cambia sin avisar;
  • el sitio puede detectar el bot y responder con un CAPTCHA;
  • los términos de uso del sitio pueden prohibir la extracción automática (es el caso de Amazon).

Por eso, antes de apuntar a un sitio real, pregúntate: ¿tiene API?, ¿qué dice su robots.txt?, ¿qué dicen sus términos de uso?, ¿cuántas peticiones haré? (Día 45, sección 4).

🛠️ Nota técnica: el mensaje de error que cita el curso dice html-parser (con guion). Ese nombre de analizador no existe: el correcto es "html.parser" (con punto). Un error de escritura en el nombre produce exactamente ese FeatureNotFound, aunque lxml no tenga nada que ver.

Actividad de análisis (en lugar de ejecutar contra Amazon)

  1. Abre en el navegador la página de un producto de Amazon y mira con DevTools dónde está el precio. ¿Coincide con las clases del sitio de práctica?
  2. Abre https://www.amazon.com/robots.txt. ¿Qué rutas prohíbe?
  3. Busca en las Condiciones de Uso de Amazon el apartado sobre robots y extracción de datos. Resume en dos líneas lo que dice.
  4. Conclusión escrita: ¿harías este paso con un producto real? ¿Qué alternativa propones?

5. 🚀 Proyecto del día: el rastreador de precios

(Integra los pasos 1 a 4 del curso: 346 a 349.)

Problema

Revisar a mano el precio de un producto todos los días es aburrido y fácil de olvidar.

Contexto

Sirve para comprar útiles escolares, un computador para el colegio o el regalo de un familiar al mejor precio.

Objetivo

Un programa que revise el precio del producto del sitio de práctica y envíe una alerta por correo cuando baje del objetivo.

Requisitos funcionales

  1. Descargar la página con encabezados.
  2. Extraer título y precio (como float).
  3. Comparar con un precio objetivo definido como constante.
  4. Si el precio es menor, enviar un correo con título, precio y enlace.
  5. Credenciales en .env.

Restricciones técnicas

  • Probar el correo con el servidor local.
  • Funciones separadas: obtener_producto(url), debe_alertar(precio, objetivo), enviar_alerta(titulo, precio, url).
  • EmailMessage o mensaje codificado en UTF-8.

Planificación

  1. Comprender. 2. Entradas: URL y precio objetivo. 3. Procesos: descargar → extraer → convertir → comparar → avisar. 4. Salidas: correo o mensaje "sin cambios". 5. Dividir en funciones. 6. Diseñar. 7. Implementar. 8. Probar. 9. Corregir. 10. Mejorar.

Construcción paso a paso

  • Versión 1: imprime el precio como float.
  • Versión 2: imprime "¡Comprar!" o "Esperar" según el objetivo.
  • Versión 3: envía la alerta al servidor local.
  • Versión 4: mueve los datos sensibles a .env y agrega encabezados.
  • Versión final: programa la ejecución diaria (por ejemplo, con las tareas programadas de PythonAnywhere, Día 35) y evita alertas repetidas.

Pruebas

Caso Objetivo Resultado esperado
Precio menor que el objetivo 100 Se envía un correo con "$99.99"
Precio mayor que el objetivo 50 No se envía nada; mensaje "Esperar"
Precio igual al objetivo 99.99 Decide y documenta: ¿< o <=?
Título con tildes — El correo llega sin UnicodeEncodeError
.env sin una variable — Mensaje claro, no un KeyError misterioso

🧪 Prueba y depuración

Encuentra los cuatro errores:

Python
TARGET = "100"
response = requests.get(URL, header)
soup = BeautifulSoup(response.content, "html-parser")
price = soup.find(class_="a-offscreen").getText().split("$")[1]
if price < TARGET:
    enviar_alerta(price)

(Pista: tipos de datos, parámetros de requests, el nombre del analizador y una comparación entre textos que "funciona" pero da resultados absurdos: prueba "99.99" < "100" y "5" < "100" en la consola.)

⭐ Extensión del proyecto

  • Vigilar varios productos leídos de un archivo CSV (URL y precio objetivo).
  • Guardar el historial de precios en un CSV y graficarlo (Días 72 y 73 del curso, o una gráfica sencilla con pandas).
  • Enviar la alerta por Telegram o SMS (Días 35 y 36) en lugar de correo.

6. 🌟 Proyecto extra: la alerta de libros del plan lector

(Proyecto creado para esta adaptación; no existe en el curso original.)

Situación

El club de lectura tiene un presupuesto limitado y quiere comprar libros cuando estén por debajo de cierto precio. Para practicar, usarán la librería ficticia books.toscrape.com (Día 45).

Requisitos

  1. Un archivo deseos.csv con columnas url y precio_maximo (al menos 5 libros de la librería de práctica).
  2. Para cada libro: extraer título, precio y disponibilidad (In stock).
  3. Un solo correo (al servidor local) con todos los libros que están por debajo de su precio máximo y disponibles, ordenados de mayor a menor ahorro.
  4. Si ninguno cumple, no enviar nada.
  5. Esperar 1 segundo entre peticiones.

Decisiones que debes tomar (y justificar)

  • ¿Un correo por libro o uno con todos? (Piensa en quien lo recibe.)
  • ¿Qué haces si una URL del CSV está mal escrita o devuelve 404?
  • ¿Cómo representas el precio en libras (£) en el correo?

Casos de prueba

  • Un libro con precio máximo muy alto (siempre alerta) y otro muy bajo (nunca).
  • Una URL inválida en el CSV: el programa debe continuar con las demás.

7. 🏆 Proyecto integrador del bloque (Días 45 a 51) — Etapa 3

(Proyecto creado para esta adaptación. La situación problema está en el Día 45; la versión final, en el Día 51.)

Etapa 3 (hoy): vigilar cambios y avisar

  1. Agrega a la página de tu grupo (bloque web) un elemento con un dato que cambia, por ejemplo <p id="cupos">Cupos disponibles: 12</p> en unete.html.
  2. Escribe vigilante.py, que lea ese archivo con Beautiful Soup, extraiga el número de cupos y:
    • guarde el valor y la fecha en historial_cupos.csv;
    • envíe un correo al servidor local cuando los cupos bajen de 5 ("¡Quedan pocos cupos!") o lleguen a 0 ("Inscripciones cerradas"), una sola vez por cada cambio de estado.
  3. Prueba editando el HTML a mano (12 → 4 → 0) y ejecutando el programa después de cada cambio.
  4. Entregable: vigilante.py, el CSV del historial y la captura de los correos en el servidor local.

8. 🧩 Comprueba lo aprendido

Conceptuales

  1. ¿Por qué el curso practica primero con un clon estático de Amazon?
  2. ¿Qué son los encabezados HTTP? Da dos ejemplos y su función.
  3. ¿Qué es una contraseña de aplicación y por qué se usa?
  4. ¿Por qué "99.99" < "100" da False aunque 99.99 sea menor que 100?
  5. ¿Qué diferencia hay entre usar un encabezado para recibir la página correcta y usarlo para burlar un bloqueo?

Análisis de código

Python
precios = ["$12.50", "$7.99", "$120.00"]
numeros = [float(p.split("$")[1]) for p in precios]
baratos = [n for n in numeros if n < 20]
print(min(numeros), baratos)

¿Qué imprime?

Predicción

Python
print(" ".join("\n   Instant   Pot  \n  Duo ".split()))
print("99.".split(".") )

Depuración

Python
load_dotenv
email = os.environ["EMAIL"]
with smtplib.SMTP(os.environ["SMTP_ADDRESS"]) as c:
    c.login(email, os.environ["PASSWORD"])

El programa falla con KeyError. Encuentra dos causas posibles y un tercer problema (pista: ¿dónde está starttls() y en qué puerto se conecta?).

Modificación

Cambia el rastreador para que avise también cuando el precio suba más de un 10 % respecto a la última ejecución.

Creación

Crea un programa que lea quotes.toscrape.com una vez al día y te envíe (al servidor local) la primera cita de la portada solo si es distinta a la del día anterior.


9. 🤖 Programar con apoyo de IA

Ciclo de trabajo: formula el problema → inténtalo tú → consulta a la IA → lee y entiende → prueba → compara con la documentación oficial → modifica → explica con tus palabras.

Actividad 1 — Ética de una respuesta. Pregúntale a una IA: "¿Cómo evito que Amazon detecte mi bot de scraping?". Analiza la respuesta: ¿menciona los términos de uso de Amazon? ¿Sugiere técnicas para burlar protecciones? Escribe una respuesta alternativa que un desarrollador responsable daría, basada en la sección 4.

Actividad 2 — Comprobar datos técnicos. Pide a una IA la dirección SMTP y el puerto de Gmail y de Outlook. Compara con la documentación oficial de cada proveedor (y con la nota técnica de la sección 10). ¿Coinciden?

Actividad 3 — Casos límite. Pide a una IA una función que convierta precios de texto en números. Pruébala con "$1,299.99", "$ 1.299.900" (pesos), "Gratis" y "". ¿Cuáles falla? Mejórala tú.


10. 🛠️ Correcciones técnicas al material original

Archivo Afirmación o comportamiento del curso Observación Tratamiento
349 Mensaje de error con html-parser El analizador se llama "html.parser"; con guion se produce FeatureNotFound sin importar si lxml está instalado Nota técnica de la sección 4
349 Extraer datos de Amazon en vivo Las Condiciones de Uso de Amazon prohíben robots y herramientas de extracción; en el aula se usa el sitio de práctica Aviso inicial; sección 4
347 Hotmail: smtp.live.com; Outlook: outlook.office365.com outlook.office365.com es el servidor para recibir (IMAP/POP). Para enviar, Microsoft indica smtp-mail.outlook.com (cuentas personales) o smtp.office365.com (cuentas de trabajo o estudio). Además, Microsoft ha anunciado el retiro de la autenticación con contraseña para SMTP en cuentas personales: consulta su documentación vigente Actividad 2 de IA
347 Mensaje de correo como texto El título del producto de práctica contiene "Sauté": con sendmail y un str se produce UnicodeEncodeError (verificado) Ejemplo de la sección 2
346 Pista: usar split() El elemento a-price-whole trae el punto incluido ("99.", verificado); unir las partes con otro punto produce "99..99" Tabla de errores de la sección 1
345 Ejecutar el bot "todos los días a las 9:00" El curso no incluye ese paso en las lecciones escritas; se propone con PythonAnywhere (Día 35) Versión final del proyecto

11. 📋 Matriz de trazabilidad

Contenido original Archivo de origen Concepto desarrollado Ejercicio original Reto adicional Proyecto relacionado
Instant Pot, variación de precios, camelcamelcamel, precio objetivo, alerta por correo diaria 345 Sección 0 — — Secciones 5 y 6
Sitio de práctica instant_pot, precio como float, split() 346 Sección 1 Paso 1 Retos 1–4 de la sección 1 Secciones 5, 6 y 7
Alerta si el precio es menor al objetivo, título, precio y enlace, .env, dotenv, SMTPServerDisconnected, direcciones SMTP, verificación en dos pasos, contraseña de aplicación, puerto 587 347 Sección 2 Paso 2 (soluciones 2 y 3) Retos 1–4 de la sección 2 Secciones 5, 6 y 7
Encabezados, myhttpheader.com, User-Agent, Accept-Language, httpbin.org/headers, bloqueo de bots 348 Sección 3 Paso 3 (solución 4) Retos 1–4 de la sección 3 Secciones 5 y 6
Amazon en vivo, cambios de HTML, CAPTCHA, prettify, otra región, analizador lxml, FeatureNotFound 349 Sección 4 Paso 4 (solución 5) Actividad de análisis Sección 5
(No existe en el curso original) — — — Alerta de libros del plan lector Sección 6
(No existe en el curso original) — — — Asistente automático, etapa 3 Sección 7

12. 📦 Evidencias de aprendizaje del Día 47

  • Función texto_a_precio con sus casos de prueba (incluidos precios en pesos).
  • Captura del servidor local recibiendo la alerta con tildes correctas.
  • Tu archivo .env.ejemplo (sin datos reales) y el .gitignore.
  • Comparación de encabezados: requests por defecto contra tu diccionario.
  • La actividad de análisis del paso 4 (sitio real, robots.txt y términos de uso).
  • El rastreador de precios con la tabla de pruebas diligenciada.
  • La alerta de libros del plan lector (proyecto extra).
  • Etapa 3 del proyecto integrador: vigilante.py y su historial.
  • Cuaderno de errores: float con símbolos, "99..99", comparación de textos, UnicodeEncodeError, headers pasado como posicional, html-parser, alertas repetidas.