================================================================================ 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.