Saltar al contenido
Volver al inicio 100 Días de Código en Python — Día 91: Proyecto de portafolio — Convertir un PDF en audiolibro

100 Días de Código en Python — Día 91: Proyecto de portafolio — Convertir un PDF en audiolibro

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 91, proyecto de portafolio profesional (categoría HTTP Requests & APIs).

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 con pypdf, 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

  1. La tarea: un audiolibro gratis a partir de un PDF
  2. Analizar el problema: un programa en tres etapas
  3. 📖 Etapa 1: extraer el texto del PDF
  4. 📖 Etapa 2: preparar el texto para la voz
  5. 📖 Etapa 3: elegir un servicio de texto a voz
  6. 🚀 Proyecto del día: construcción por versiones
  7. 🌟 Proyecto extra: guías de estudio accesibles
  8. 🏗️ Mi Portafolio Profesional — Etapa 10
  9. 🧩 Comprueba lo aprendido
  10. 🤖 Programar con apoyo de IA
  11. 🛠️ Observaciones sobre el material original
  12. 📋 Matriz de trazabilidad
  13. 📦 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)

Texto
 PDF ──► [1. Extraer texto] ──► texto crudo
                                   │
                                   ▼
                          [2. Limpiar y dividir] ──► fragmentos
                                                        │
                                                        ▼
                                             [3. Texto a voz] ──► audio

Cada 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

Python
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 paginas
  • PdfReader abre el PDF; lector.pages es la lista de páginas.
  • extract_text() devuelve el texto de una página. El or "" 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.

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

Có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 .env incluido 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)

Texto
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 audiolibro

Cada 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

  1. Recibe la ruta de un PDF (argumento, input() o diálogo de archivos).
  2. Permite elegir un rango de páginas.
  3. Avisa si el PDF no tiene texto.
  4. 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

  1. 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.md y 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
Python
fragmentos = dividir_en_fragmentos("a" * 1200, 500)
assert all(len(f) <= 500 for f in fragmentos)   # hoy falla: corrígelo

🧪 Prueba y depuración

  1. 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?
  2. Encuentra los tres problemas:
Python
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: propiedades rate y voice).
  • 🟡 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 investiga threading).
  • 🔴 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

  1. Procesar una carpeta de PDF y generar un audio por guía.
  2. Crear un indice.html con la lista de audios reproducibles (<audio controls>), accesible con teclado.
  3. 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):

  1. Repositorio con extraer.py, preparar.py, voz.py, pruebas, requirements.txt, .env.example (sin claves reales) y .gitignore con .env.
  2. README.md con el diagrama de tubería, cómo configurar la clave (si usas la nube) y un audio de muestra de dominio público.
  3. DIARIO.md del Día 91 y ficha en proyectos.json.
  4. 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

  1. ¿Por qué un PDF escaneado no tiene texto extraíble?
  2. ¿Por qué hay que dividir el texto antes de enviarlo a una API de voz?
  3. ¿Qué ventaja tiene separar el programa en tres etapas?
  4. ¿Por qué el audio se guarda con "wb"?

Análisis de código

Python
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.example y .gitignore correctos; revisión de seguridad de los repositorios.
  • DIARIO.md del 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.