================================================================================
PROMPT 01 — AVANZADO (AGENTIC IDE)
Nombre: Contract Downloader · Track Avanzado
Herramientas: Claude Code, Cursor, Codex CLI, Windsurf, GitHub Copilot Workspace
Tiempo estimado breakout: 25 minutos
Nivel: Conocimiento basico de Python, terminal y entornos virtuales
================================================================================
OBJETIVO DEL BREAKOUT
---------------------
Al finalizar los 25 minutos, el participante debe contar con un script Python
funcional que DESCARGUE archivos PDF de tabuladores y contratos colectivos
desde el repositorio publico del Centro Federal de Conciliacion y Registro
Laboral (CFCRL), a partir de una lista de numeros de expediente CCT.
El producto a obtener es una carpeta local organizada por empresa con los
archivos descargados, no un CSV de metadata. El facilitador entrega 3 CCTs
de prueba al inicio del breakout.
REQUISITOS PREVIOS
------------------
- Python 3.10+ instalado
- Acceso a terminal con permisos para crear venv e instalar paquetes
- Editor con agente IA abierto en una carpeta vacia
- Conexion a internet estable (sin proxy corporativo bloqueando .gob.mx)
- Lista de 3 CCTs de prueba (entregada por el facilitador)
ALCANCE DEL EJERCICIO
---------------------
ESTA version replica la parte BASICA del descargador real: busca el CCT,
entra a la pagina del contrato y descarga los archivos cuyo enlace ya esta
visible en el HTML (links "/documento-publico/download/...").
NO entra en este alcance el flujo AJAX (botones "btn-generar-liga") que
requiere CSRF + token temporal — ese es el componente que diferencia un
descargador profesional y se cubre en el modulo extendido fuera del webinar.
================================================================================
PROMPT — COPIAR Y PEGAR EN EL AGENTE
================================================================================
ROL
Actua como un ingeniero de datos senior con experiencia en descarga
automatizada de documentos publicos en sitios gubernamentales mexicanos.
Eres pragmatico: priorizas que el codigo descargue archivos reales sobre
la elegancia arquitectonica.
CONTEXTO
Voy a descargar archivos PDF de contratos colectivos publicos del repositorio
del Centro Federal de Conciliacion y Registro Laboral (CFCRL). Las URLs base
del sitio son:
Busqueda: https://repositorio.centrolaboral.gob.mx
Pagina contrato: https://repositorio.centrolaboral.gob.mx/contrato/{id}
Descarga PDF: https://registro.centrolaboral.gob.mx/documento-publico/download/...
El flujo real verificado es:
1. GET https://repositorio.centrolaboral.gob.mx con query params:
search={numero_cct}
tab=contratos
form_general=1
Devuelve HTML con una tabla. Cada fila relevante tiene un
donde {id} es un entero.
2. Para cada {id} encontrado, GET
https://repositorio.centrolaboral.gob.mx/contrato/{id}
Devuelve HTML con multiples enlaces donde algunos
contienen la subcadena "/documento-publico/download/" y apuntan a
PDFs (o xlsx/docx) en el dominio registro.centrolaboral.gob.mx.
3. Para cada uno de esos enlaces, GET la URL completa con la sesion
(mantiene cookies). Stream-download el contenido a disco como PDF.
Los nombres de los archivos vienen embebidos en la URL, separados por "|":
.../download/2025|06|26|tramite-403862|contratos|nombrearchivo.pdf
El nombre real del archivo es el segmento posterior al ultimo "|".
TAREA
Genera un proyecto minimo con esta estructura:
cfcrl_basic_downloader/
|-- requirements.txt
|-- config.py
|-- downloader.py
`-- README.md
1. requirements.txt — solamente: requests, beautifulsoup4, lxml
2. config.py — exponer:
BASE_REPO = "https://repositorio.centrolaboral.gob.mx"
USER_AGENT = string realista de Chrome 120 Windows
TIMEOUT = 30
PAUSA = 2.5 # segundos entre peticiones (no abusar del sitio publico)
OUTPUT_DIR = "descargas"
CCTS_DEMO = ["REEMPLAZAR_CCT_1", "REEMPLAZAR_CCT_2", "REEMPLAZAR_CCT_3"]
# Estas tres entradas las completara el participante con los CCTs
# reales que entregue el facilitador al inicio del breakout.
3. downloader.py — funciones puras + main:
construir_sesion() -> requests.Session
Headers: User-Agent y "Accept-Language: es-MX,es;q=0.9".
buscar_ids_contrato(session, cct: str) -> list[int]
GET BASE_REPO con params {"search": cct, "tab": "contratos",
"form_general": "1"}. Parsear HTML con BeautifulSoup (parser lxml).
Encontrar todos los donde N es entero.
Devolver lista unica ordenada descendente (mas reciente primero).
obtener_links_descarga(session, contrato_id: int) -> list[str]
GET BASE_REPO + "/contrato/" + str(contrato_id). Parsear HTML.
Devolver lista de URLs absolutas que contengan
"/documento-publico/download/" en su href.
Si href empieza con "/", anteponer "https://registro.centrolaboral.gob.mx".
Si href empieza con "http", dejar tal cual.
Eliminar duplicados conservando orden.
nombre_archivo_desde_url(url: str) -> str
Si la URL contiene "|", devolver el segmento posterior al ultimo "|".
Si no, devolver la ultima parte despues del ultimo "/".
Sanitizar caracteres no validos para sistema de archivos
(mantener solo letras, digitos, guion, punto, underscore).
Truncar a 120 caracteres.
clasificar_tipo(url: str) -> str
Devuelve "tabuladores" si la URL contiene "tabulador",
"sueldos_pdf" o "sueldos_excel".
Devuelve "actas_votacion" si contiene "acta" o "resultado".
Devuelve "convocatorias" si contiene "convocatoria".
Devuelve "contratos" en cualquier otro caso (default seguro).
descargar_archivo(session, url: str, ruta_destino: Path) -> bool
GET con stream=True y timeout. Si la respuesta no es 2xx, log warning
y devolver False. Escribir el contenido en chunks de 8KB. Devolver
True si se escribio algun byte. Si el archivo ya existe en disco,
saltarlo y devolver True (idempotente).
main():
Iterar sobre CCTS_DEMO. Para cada uno:
1. Crear carpeta OUTPUT_DIR / cct.
2. buscar_ids_contrato. Loggear cuantos IDs se encontraron.
time.sleep(PAUSA) entre peticiones.
3. Para cada id, obtener_links_descarga.
4. Para cada link, clasificar_tipo, crear subcarpeta
OUTPUT_DIR/cct/{tipo}/, generar nombre, descargar.
5. Loggear: descargados, ya_existian, errores.
Al final, imprimir resumen total.
Logging con el modulo `logging` (nivel INFO), formato corto. Sin emojis
en los logs (la consola Windows usa cp1252 y truena con caracteres
especiales).
4. README.md corto: instalacion, como editar CCTS_DEMO, como ejecutar,
estructura de salida esperada, disclaimer de uso responsable.
VALIDACION
Despues de generar los archivos:
a. Crear venv, instalar dependencias.
b. Editar config.py reemplazando los 3 CCTs placeholder por los reales
que entrego el facilitador.
c. Ejecutar: python downloader.py
d. Verificar que en la carpeta `descargas/` aparezca al menos un PDF
descargado para uno de los CCTs.
CRITERIOS DE ACEPTACION
[ ] El script corre end-to-end sin excepcion no manejada.
[ ] Al menos uno de los tres CCTs de prueba produce 1 o mas PDFs reales
en disco (>10 KB, mime application/pdf).
[ ] Respeta PAUSA entre peticiones (verificable por timing del log).
[ ] La estructura de salida es: descargas/{cct}/{tipo}/{nombre.pdf}
[ ] El log muestra resumen final: total descargados, ya existian, errores.
[ ] El README incluye disclaimer de uso responsable.
RESTRICCIONES
- No uses Selenium ni Playwright. El sitio sirve HTML estatico para los
enlaces directos; con requests + BeautifulSoup es suficiente.
- No intentes implementar el flujo AJAX btn-generar-liga ni el manejo CSRF.
Si encuentras botones de ese tipo en el HTML, ignoralos y comentalo en
el log como "documento requiere flujo AJAX (fuera de alcance)".
- No agregues base de datos, GUI ni API.
- Sin emojis en codigo ni en logs.
ENTREGA
Cuando termines:
1. Muestra la estructura de archivos creada.
2. Muestra la salida de la corrida (ultimas 30 lineas del log).
3. Lista los archivos descargados con su tamanio.
================================================================================
SI ALGO SALE MAL — TROUBLESHOOTING
================================================================================
1. "buscar_ids_contrato devuelve []"
El CCT puede no existir o estar mal escrito. Pidele al agente:
"Modifica buscar_ids_contrato para que en caso de 0 resultados imprima
la URL completa que consulto y guarde la respuesta HTML en
debug_search.html. Voy a abrirlo para ver que devolvio el sitio."
2. "obtener_links_descarga devuelve [] aunque la pagina existe"
Probablemente todos los documentos requieren AJAX (btn-generar-liga).
En ese caso ese CCT no es util para esta version basica. Prueba con
otro CCT de los entregados. Pidele al agente que muestre cuantos
botones de ese tipo encontro vs cuantos enlaces directos.
3. "Descarga 200 OK pero el archivo no abre"
Verifica que la respuesta tenga Content-Type "application/pdf"
(o xlsx/docx). Algunas respuestas son HTML de error disfrazadas.
Pidele: "Antes de escribir el archivo, valida que el primer bloque de
bytes empiece con %PDF (firma de PDF). Si no, descarta y log."
4. "HTTP 403 o 429"
Sube PAUSA a 5 segundos. Verifica que el User-Agent no sea el default
de requests. Si persiste, tu red corporativa puede estar bloqueando
*.gob.mx (probar desde red personal o hotspot).
================================================================================
VARIANTES Y EXTENSIONES (mas alla del breakout)
================================================================================
A. Validacion de integridad SHA-256
Calcula el SHA-256 de cada archivo descargado y guardalo en un
manifiesto JSON. Permite detectar archivos corruptos en re-ejecuciones.
B. Persistencia de progreso
Guarda en progreso.json el set de CCTs ya completados. Al re-ejecutar,
saltarlos sin pegarle al servidor. Util para listas largas.
C. Deteccion de documentos AJAX
Antes de cada CCT, contar cuantos enlaces directos hay vs cuantos
botones btn-generar-liga. Reportar como "documentos accesibles vs
documentos protegidos" para entender que tan completa esta la
descarga basica para ese contrato.
================================================================================
DISCLAIMER DE USO RESPONSABLE
================================================================================
El portal del CFCRL es un servicio publico financiado con recursos publicos.
Usa el script con moderacion: respeta PAUSA de minimo 2 segundos entre
peticiones, no corras descargas masivas en horario laboral, y limita el
volumen a lo que realmente necesites para tu analisis. La informacion es
publica pero su tratamiento posterior esta sujeto a la Ley Federal de
Proteccion de Datos Personales en Posesion de los Particulares cuando
incluya datos de personas fisicas identificables. Cita la fuente al
publicar analisis derivados.