Este tutorial adapta pedagógicamente el Día 91. La sección 📝 Contenido original del curso proviene del único archivo de la carpeta:
dia91-tarea10.md(enunciado y pregunta de reflexión, en inglés; aquí traducido). El curso no ofrece guía ni solución, y este tutorial no entrega el programa terminado. Todo lo que no está en ese archivo (arquitectura en etapas, extracción conpypdf, división del texto, comparación de opciones de texto a voz, derechos de autor, retos 🟢🟡🟠🔴, proyecto extra y la etapa 10 de Mi Portafolio Profesional) es creación de esta adaptación.
Nota sobre las verificaciones: el 28 de septiembre de 2026, con Python 3.14 y pypdf 6.19.0, se extrajo el texto de un PDF de 2 páginas generado para la prueba (con tildes y "ñ", que se conservaron) y se ejecutó
dividir_en_fragmentos: 4 fragmentos de 494, 494, 494 y 201 caracteres con un máximo de 500. Se comprobó también que un PDF que es solo una imagen devuelve texto vacío, y que la función falla con una "oración" más larga que el máximo (devuelve un fragmento de 1201 caracteres) y que agrega un punto de más a la última oración si ya terminaba en punto: ambos errores se dejan como ejercicios (secciones 6 y 9). No se llamó a ninguna API de texto a voz (requieren cuenta y, en general, facturación).
Tabla de contenidos
- La tarea: un audiolibro gratis a partir de un PDF
- Analizar el problema: un programa en tres etapas
- 📖 Etapa 1: extraer el texto del PDF
- 📖 Etapa 2: preparar el texto para la voz
- 📖 Etapa 3: elegir un servicio de texto a voz
- 🚀 Proyecto del día: construcción por versiones
- 🌟 Proyecto extra: guías de estudio accesibles
- 🏗️ Mi Portafolio Profesional — Etapa 10
- 🧩 Comprueba lo aprendido
- 🤖 Programar con apoyo de IA
- 🛠️ Observaciones sobre el material original
- 📋 Matriz de trazabilidad
- 📦 Evidencias de aprendizaje del Día 91
1. La tarea: un audiolibro gratis a partir de un PDF
(Fuente: dia91-tarea10.md)
📝 Contenido original del curso
¿Demasiado cansado para leer? Construye un script de Python que tome un archivo PDF, identifique el texto y lo convierta en voz. En la práctica, crearás un audiolibro gratis.
El texto a voz con IA ha avanzado mucho: puede sonar más natural que un audiolibro real.
Usando lo que aprendiste sobre peticiones HTTP, APIs y scripting en Python, crea un programa que convierta archivos PDF en voz.
Quizá quieras elegir tu propia API de texto a voz (TTS, Text-To-Speech). Algunas que puedes considerar: iSpeech, Google Cloud Text-to-Speech y Amazon Polly.
Pregunta de la tarea — Momento de reflexión: cómo abordaste el proyecto; qué fue difícil y qué fácil; cómo mejorarías; tu mayor aprendizaje; qué harías distinto.
Prerequisitos: archivos y rutas (Día 24); peticiones HTTP, parámetros y claves de API (Días 33–35); variables de entorno para las claves (Día 35); excepciones (Día 30); cadenas y listas (Días 2 y 4).
2. Analizar el problema: un programa en tres etapas
(Sección creada para esta adaptación.)
🔍 Antes de programar
| Pregunta | Respuesta que debes construir tú |
|---|---|
| ¿Qué entra? | Un archivo PDF |
| ¿Qué sale? | Un archivo de audio (MP3 o WAV), o audio reproducido al instante |
| ¿Qué pasa en el medio? | Sacar el texto, limpiarlo, enviarlo al servicio de voz, juntar el audio |
| ¿Qué puede salir mal? | PDF escaneado (sin texto), texto desordenado, límites del servicio, sin internet, clave inválida |
Arquitectura: una tubería (pipeline)
PDF ──► [1. Extraer texto] ──► texto crudo
│
▼
[2. Limpiar y dividir] ──► fragmentos
│
▼
[3. Texto a voz] ──► audioCada etapa es una función con entrada y salida claras. Ventaja: puedes probar las etapas 1 y 2 sin internet ni claves, y cambiar el servicio de voz (etapa 3) sin tocar el resto.
3. 📖 Etapa 1: extraer el texto del PDF
(Sección creada para esta adaptación. Verificado con pypdf 6.19.0.)
🎯 Objetivo de aprendizaje
Extraer el texto de un PDF y reconocer cuándo no es posible.
📖 Explicación conceptual
Un PDF no guarda "párrafos" como un documento de Word: guarda instrucciones de dibujo ("escribe esta letra en esta posición"). Las librerías reconstruyen el texto a partir de esas posiciones, así que el resultado puede traer saltos de línea en mitad de frases, encabezados y números de página mezclados, o guiones de corte de palabra.
Y si el PDF es una foto escaneada, no hay letras que extraer: solo una imagen. Haría falta OCR (reconocimiento óptico de caracteres), que es otro proyecto.
🔎 Ejemplo explicado
Instala: pip install pypdf
from pypdf import PdfReader
def extraer_texto(ruta_pdf):
lector = PdfReader(ruta_pdf)
paginas = []
for pagina in lector.pages:
texto = pagina.extract_text() or ""
paginas.append(texto)
return paginasPdfReaderabre el PDF;lector.pageses la lista de páginas.extract_text()devuelve el texto de una página. Elor ""asegura una cadena aunque venga vacía.- Devuelve una lista por página: útil para convertir solo algunas páginas o crear un audio por capítulo.
Verificado: en un PDF de prueba, "Página dos: el año 2026 y la niñez." se extrajo completo, con tildes y "ñ". En un PDF formado solo por una imagen, el resultado fue [''].
⚠️ Errores frecuentes
| Error | Qué ocurre | Solución |
|---|---|---|
ModuleNotFoundError: No module named 'PyPDF2' o avisos de obsolescencia |
Tutoriales antiguos usan PyPDF2 | PyPDF2 se integró en pypdf; usa pypdf (su documentación explica la migración) |
| Texto vacío | PDF escaneado | Avisa al usuario: "Este PDF no tiene texto seleccionable" |
Símbolos raros (�) al imprimir en la consola de Windows |
La consola no muestra ciertos caracteres | El texto está bien: guárdalo en un archivo con encoding="utf-8" para comprobarlo |
FileNotFoundError |
Ruta incorrecta | Usa pathlib.Path y comprueba exists() (Día 24) |
| PDF protegido con contraseña | Error al leer | lector.is_encrypted y lector.decrypt(...) solo si tienes derecho a abrirlo |
4. 📖 Etapa 2: preparar el texto para la voz
(Sección creada para esta adaptación. Función verificada, con un error intencional para corregir.)
¿Por qué dividir?
Los servicios de voz en la nube tienen un límite de caracteres por solicitud (del orden de unos pocos miles; consulta el límite exacto del servicio que elijas en su documentación). Un libro tiene cientos de miles de caracteres. Solución: dividir en fragmentos, convertir cada uno y unir los audios.
Cortar en cualquier punto partiría palabras y frases, y la voz sonaría rara. Mejor cortar al final de una oración.
def dividir_en_fragmentos(texto, maximo=4500):
fragmentos, actual = [], ""
for oracion in texto.replace("\n", " ").split(". "):
oracion = oracion.strip()
if not oracion:
continue
if len(actual) + len(oracion) + 2 > maximo and actual:
fragmentos.append(actual.strip())
actual = ""
actual += oracion + ". "
if actual.strip():
fragmentos.append(actual.strip())
return fragmentosCómo funciona: reemplaza saltos de línea por espacios, separa por ". ", y va acumulando oraciones en actual hasta que la siguiente no cabe; entonces guarda el fragmento y empieza otro.
Verificado: con maximo=500, un texto de unos 1700 caracteres se dividió en 4 fragmentos de 494, 494, 494 y 201 caracteres.
🐞 Esta función tiene un error (verificado): si una "oración" es más larga que
maximo(por ejemplo, una tabla sin puntos), el fragmento la incluye entera y supera el límite. Lo corregirás en la sección 6.
Limpieza adicional (ideas para ti)
- Quitar números de página y encabezados que se repiten en cada página.
- Unir palabras cortadas con guion al final de línea (
"electro-\nencefalograma"). - Decidir qué hacer con URLs, tablas y notas al pie (¿leerlas? ¿omitirlas?).
5. 📖 Etapa 3: elegir un servicio de texto a voz
(Sección creada para esta adaptación. No se ejecutó ninguna de estas opciones.)
El enunciado propone APIs en la nube. Para un proyecto escolar conviene comparar todas las opciones:
| Opción | Tipo | Ventajas | Desventajas | ¿Cuenta y pago? |
|---|---|---|---|---|
| Google Cloud Text-to-Speech (sugerida) | API en la nube | Voces muy naturales, muchas en español | Límite por solicitud; requiere proyecto en la nube | Sí: cuenta con facturación (tiene capa gratuita mensual) |
| Amazon Polly (sugerida) | API en la nube | Voces naturales | Igual que la anterior | Sí: cuenta de AWS con facturación |
| iSpeech (sugerida) | API en la nube | — | Su documentación está desactualizada | Verificar antes de usar |
| pyttsx3 | Librería local (usa las voces del sistema operativo) | Gratis, sin internet, sin cuenta | Voces menos naturales; depende de las voces instaladas | No |
| gTTS | Librería que usa un servicio web de Google no oficial | Fácil, sin clave | Puede dejar de funcionar sin aviso; límites no documentados | No, pero revisa los términos |
🏫 Para el aula: los servicios en la nube exigen edad mínima y una tarjeta para la facturación. Si eres menor de edad, usa una opción local (
pyttsx3) o que el docente gestione la cuenta institucional. Nunca subas tu clave de API a GitHub (Días 35 y 71): guárdala en una variable de entorno o en un.envincluido en.gitignore.
Diseño recomendado: escribe la etapa 3 como una función texto_a_audio(fragmento, ruta_salida) y crea dos versiones intercambiables (una local y una en la nube). El resto del programa no debe saber cuál se usa. Es la base del principio de sustitución que verás en programación avanzada.
Esquema para una API en la nube (patrón general, no ejecutado)
para cada fragmento (numerado):
preparar la solicitud: texto, idioma ("es-..."), voz, formato de audio
enviar la solicitud con la clave (leída de una variable de entorno)
si la respuesta es un error: registrar cuál fragmento falló y seguir o detener
guardar el audio como parte_001.mp3, parte_002.mp3...
unir las partes o dejarlas como "pistas" del audiolibroCada proveedor tiene su librería oficial (por ejemplo, google-cloud-texttospeech o boto3 para Polly) y su propio formato: sigue la documentación oficial del que elijas.
6. 🚀 Proyecto del día: construcción por versiones
(Desarrolla la tarea dia91-tarea10.md. Las versiones son una propuesta de esta adaptación.)
Problema
Construir un programa que convierta un PDF en un archivo de audio.
Contexto
Un compañero con baja visión, o que tiene un trayecto largo en bus, quiere escuchar las lecturas del plan lector.
Objetivo
Un script (o una pequeña GUI) que reciba un PDF y produzca audio, con manejo de errores y sin exponer claves.
Requisitos funcionales
- Recibe la ruta de un PDF (argumento,
input()o diálogo de archivos). - Permite elegir un rango de páginas.
- Avisa si el PDF no tiene texto.
- Genera uno o varios archivos de audio numerados.
Restricciones técnicas
- Las tres etapas en funciones separadas; las dos primeras con pruebas.
- Claves solo en variables de entorno.
- Solo PDF con derechos para convertir (ver abajo).
Derechos de autor
Convertir en audio un libro con derechos de autor para compartirlo es ilegal en la mayoría de países. Para practicar, usa textos de dominio público (por ejemplo, obras clásicas de autores fallecidos hace décadas, según la ley de cada país) o documentos que tú escribiste.
Planificación
- Comprender (escucha un audiolibro de dominio público). 2. Entradas: PDF y rango. 3. Procesos: las tres etapas. 4. Salidas: audios. 5. Dividir en funciones. 6. Elegir el servicio de voz. 7. Implementar. 8. Probar. 9. Corregir. 10. Mejorar.
Construcción paso a paso
- Versión 1 — Extraer: imprimir el texto de un PDF y guardarlo en
texto.txt(encoding="utf-8"). - Versión 2 — Preparar: dividir en fragmentos; corregir el error de las oraciones largas (ver depuración).
- Versión 3 — Voz local: convertir un fragmento con una opción local.
- Versión 4 — Libro completo: todos los fragmentos, archivos numerados, progreso en pantalla.
- Versión 5 — Nube (opcional, con autorización): la misma interfaz con una API.
- Versión final: rango de páginas, manejo de errores,
README.mdy diario.
Pruebas
| # | Caso | Resultado esperado |
|---|---|---|
| 1 | PDF de 2 páginas con tildes | extraer_texto devuelve 2 cadenas con las tildes |
| 2 | PDF escaneado | Aviso "sin texto", no se llama al servicio de voz |
| 3 | dividir_en_fragmentos("") |
[] |
| 4 | Texto largo con maximo=500 |
Ningún fragmento supera 500 |
| 5 | Una "oración" de 1200 caracteres con maximo=500 |
Ningún fragmento supera 500 (hoy falla) |
| 6 | Sin internet (versión en la nube) | Mensaje claro, sin traza de error |
| 7 | Clave de API inválida | Mensaje claro; la clave no se imprime |
fragmentos = dividir_en_fragmentos("a" * 1200, 500)
assert all(len(f) <= 500 for f in fragmentos) # hoy falla: corrígelo🧪 Prueba y depuración
- Haz pasar la prueba 5. Pistas: si una oración es más larga que
maximo, pártela por espacios (palabras); si una palabra sola es más larga, córtala por caracteres. ¿Dónde agregas esa lógica sin romper las pruebas 3 y 4? - Encuentra los tres problemas:
import requests
CLAVE = "AIzaSy...mi_clave_real"
texto = open("libro.pdf").read()
respuesta = requests.post(URL, json={"texto": texto, "clave": CLAVE})
open("libro.mp3", "w").write(respuesta.text)(La clave está escrita en el código; un PDF no se lee como texto plano con open().read(); y el audio es binario: se guarda con "wb" y respuesta.content. Además, no se revisa respuesta.status_code ni el límite de caracteres.)
⭐ Extensión del proyecto
- 🟢 Reto 1 — Aplicación: elegir la velocidad de lectura y la voz (en
pyttsx3: propiedadesrateyvoice). - 🟡 Reto 2 — Ampliación: un audio por capítulo, detectando títulos como "Capítulo" en el texto.
- 🟠 Reto 3 — Desafío: interfaz Tkinter con barra de progreso que no se congele (convierte por partes con
after, o investigathreading). - 🔴 Reto 4 — Pensamiento computacional: si la conversión se interrumpe en el fragmento 57 de 120, ¿cómo continúas sin repetir los 56 anteriores ni pagar dos veces? Diseña un archivo de "estado" y pruébalo interrumpiendo el programa.
7. 🌟 Proyecto extra: guías de estudio accesibles
(Proyecto creado para esta adaptación; no existe en el curso original.)
Situación
El área de inclusión del colegio necesita convertir las guías de estudio (PDF hechos por los docentes, con su autorización) en audios para estudiantes con dificultades de lectura o baja visión.
Requisitos
- Procesar una carpeta de PDF y generar un audio por guía.
- Crear un
indice.htmlcon la lista de audios reproducibles (<audio controls>), accesible con teclado. - Omitir de la lectura los encabezados repetidos y los números de página.
Decisiones que debes tomar (y justificar)
- ¿Cómo describes las imágenes y tablas de la guía, que no se pueden "leer"?
- ¿Cómo validas con un usuario real que el audio es útil?
Casos de prueba
- Una guía escaneada aparece en un reporte de "no convertibles".
- El índice funciona sin ratón.
8. 🏗️ Mi Portafolio Profesional — Etapa 10
(Proyecto integrador creado para esta adaptación; etapas anteriores en los Días 82 a 90.)
Etapa 10 (hoy):
- Repositorio con
extraer.py,preparar.py,voz.py, pruebas,requirements.txt,.env.example(sin claves reales) y.gitignorecon.env. README.mdcon el diagrama de tubería, cómo configurar la clave (si usas la nube) y un audio de muestra de dominio público.DIARIO.mddel Día 91 y ficha enproyectos.json.- Revisión de seguridad de todos tus repositorios: busca claves o contraseñas escritas por error (en los archivos y en el historial de commits).
9. 🧩 Comprueba lo aprendido
Conceptuales
- ¿Por qué un PDF escaneado no tiene texto extraíble?
- ¿Por qué hay que dividir el texto antes de enviarlo a una API de voz?
- ¿Qué ventaja tiene separar el programa en tres etapas?
- ¿Por qué el audio se guarda con
"wb"?
Análisis de código
texto = pagina.extract_text() or ""¿Qué pasa si extract_text() devuelve None? ¿Y si devuelve "Hola"?
Predicción
¿Cuántos fragmentos produce dividir_en_fragmentos("Hola. Chao. Bien.", 8)? Razónalo y luego compruébalo. Fíjate bien en el último fragmento: el resultado verificado es ['Hola.', 'Chao.', 'Bien..']. ¿De dónde sale el punto doble y cómo lo corriges?
Depuración
"Mi programa imprime Cap�tulo en la consola." ¿El texto extraído está mal? ¿Cómo lo compruebas?
Modificación
Cambia extraer_texto para que reciba desde y hasta (números de página empezando en 1) y extraiga solo ese rango.
Creación
Escribe limpiar(texto) que una las palabras cortadas con guion al final de línea ("electro-\nencefalograma" → "electroencefalograma").
10. 🤖 Programar con apoyo de IA
Ciclo de trabajo: formula el problema → inténtalo tú primero → consulta a la IA sobre un punto concreto → lee y entiende → prueba → compara con la documentación oficial → modifica → explica con tus palabras.
Actividad 1 — Librería actual. Pide a una IA código para leer un PDF en Python. Si usa PyPDF2 o PdfFileReader, busca en la documentación de pypdf su guía de migración y actualiza el código.
Actividad 2 — Límites reales. Pregunta a una IA el límite de caracteres por solicitud de la API de voz que elegiste. Compruébalo en la documentación oficial (las IA suelen dar cifras desactualizadas) y anota la fuente y la fecha.
Actividad 3 — Corregir con pruebas. Pide a una IA que corrija dividir_en_fragmentos para las oraciones largas. Ejecuta las pruebas 3, 4 y 5 con su propuesta antes de aceptarla.
Declara el uso: sección "Uso de IA" en el README.md.
11. 🛠️ Observaciones sobre el material original
| Archivo | Observación | Tratamiento |
|---|---|---|
dia91-tarea10.md |
Sugiere APIs en la nube que requieren cuenta y facturación | Sección 5: comparación con opciones locales y advertencia para menores de edad |
dia91-tarea10.md |
Enlace a iSpeech con documentación antigua | Se indica verificar antes de usar |
dia91-tarea10.md |
Dice "You right want" (errata por "You might want") | Se traduce según el sentido: "quizá quieras" |
dia91-tarea10.md |
No menciona PDF escaneados, límites por solicitud ni derechos de autor | Secciones 3, 4 y 6 |
| Tutoriales en línea | Muchos usan PyPDF2, hoy integrado en pypdf | Se usa pypdf y se advierte en errores frecuentes |
12. 📋 Matriz de trazabilidad
| Contenido original | Archivo de origen | Concepto desarrollado | Ejercicio original | Reto adicional | Proyecto relacionado |
|---|---|---|---|---|---|
| Script que toma un PDF e identifica el texto | dia91-tarea10.md |
Secciones 2 y 3 | PDF a voz | Reto 2 | Secciones 6 y 8 |
| Convertir el texto a voz (audiolibro gratis) | dia91-tarea10.md |
Secciones 4 y 5 | PDF a voz | Retos 1, 3 y 4 | Sección 6 |
| Usar peticiones HTTP, APIs y scripting | dia91-tarea10.md |
Sección 5 | PDF a voz | Versión 5 | Sección 6 |
| APIs sugeridas: iSpeech, Google Cloud TTS, Amazon Polly | dia91-tarea10.md |
Sección 5 | — | — | — |
| Momento de reflexión | dia91-tarea10.md |
Sección 6 (versión final) | Pregunta de reflexión | — | Sección 8 |
| (No existe en el curso original) | — | Tubería, fragmentos, derechos de autor | — | Guías de estudio accesibles | Sección 7 |
13. 📦 Evidencias de aprendizaje del Día 91
- El programa funcionando y un audio de muestra de un texto de dominio público.
- Las pruebas de las etapas 1 y 2, incluida la prueba 5 corregida.
.env.exampley.gitignorecorrectos; revisión de seguridad de los repositorios.DIARIO.mddel Día 91 y ficha en el portafolio.- Cuaderno de errores: PDF escaneado, PyPDF2 obsoleto, caracteres raros en la consola, límites por solicitud, oración más larga que el máximo, audio guardado como texto, clave en el código.