Qué vas a conseguir
Un script de Python que lee la lista de equipos de un CSV, pregunta a una API REST el estado de cada uno, escribe un informe en CSV y en HTML, deja un log de lo que ha hecho y se ejecuta solo cada mañana. Es el patrón de casi cualquier automatización de operaciones: entrada, consulta, informe, programación. Y lo que hacer cuando funciona en tu equipo y falla en el servidor.
Antes de empezar
- Python 3 de python.org (en Windows, con el lanzador
py) o el del sistema en Linux. - Una carpeta para el script, por ejemplo
C:\scripts\informe-equiposo/opt/informe-equipos. - La URL de la API y un token con permisos de solo lectura, pedido a quien la administra.
- Si la empresa usa proxy con inspección TLS, el certificado de la CA corporativa en formato PEM (te lo da el equipo de red o seguridad).
1. Un entorno virtual por script
Cada script con sus propias librerías, sin tocar el Python del sistema:
cd C:\scripts\informe-equipospy -m venv .venv.\.venv\Scripts\Activate.ps1pip install requestspip freeze > requirements.txtEn Linux, python3 -m venv .venv y source .venv/bin/activate. Con requirements.txt el entorno se rehace en otro servidor (pip install -r requirements.txt); .venv no se copia ni va a Git.
2. El script
import csvimport htmlimport loggingimport osfrom datetime import date import requests API = "https://api.contoso.local/v1/devices"TOKEN = os.environ["INVENTARIO_TOKEN"] # nunca escrito en el códigologging.basicConfig(filename="informe.log", level=logging.INFO, encoding="utf-8", format="%(asctime)s %(levelname)s %(message)s") def estado(equipo): r = requests.get(f"{API}/{equipo}", headers={"Authorization": f"Bearer {TOKEN}"}, timeout=15) r.raise_for_status() return r.json() filas = []with open("equipos.csv", newline="", encoding="utf-8-sig") as f: for linea in csv.DictReader(f): nombre = linea["Equipo"] try: d = estado(nombre) filas.append({"Equipo": nombre, "SO": d.get("os"), "Ultimo contacto": d.get("lastSeen")}) except requests.RequestException as e: logging.warning("%s: %s", nombre, e) filas.append({"Equipo": nombre, "SO": "", "Ultimo contacto": "ERROR"})logging.info("%d equipos procesados", len(filas)) salida = f"informe-{date.today()}.csv"with open(salida, "w", newline="", encoding="utf-8-sig") as f: w = csv.DictWriter(f, fieldnames=["Equipo", "SO", "Ultimo contacto"]) w.writeheader() w.writerows(filas) celdas = "".join(f"<tr><td>{html.escape(str(x['Equipo']))}</td><td>{html.escape(str(x['SO']))}</td>" f"<td>{html.escape(str(x['Ultimo contacto']))}</td></tr>" for x in filas)with open(salida.replace(".csv", ".html"), "w", encoding="utf-8") as f: f.write(f"<meta charset='utf-8'><table><tr><th>Equipo</th><th>SO</th><th>Último contacto</th></tr>{celdas}</table>")Lo que importa de cada parte:
encoding="utf-8-sig"al leer: Excel guarda los CSV en UTF-8 con BOM, y sin-sigla primera columna se llama\ufeffEquipoy elKeyErrordesconcierta. Al escribir, el BOM hace que Excel abra bien las tildes.timeout=15: sin él, una API que no responde deja el script colgado para siempre, y la tarea programada nunca termina.raise_for_status()convierte un 401 o un 500 en una excepción que eltryrecoge: un equipo que falla no tumba el informe entero, y queda en el log.
3. El token, en una variable de entorno
El token es una contraseña. No va en el script, ni en el CSV, ni en Git. Se pone en una variable de entorno del usuario que ejecuta el script:
[Environment]::SetEnvironmentVariable("INVENTARIO_TOKEN", "pega-aqui-el-token", "User")# abre una consola nueva para que la veapy -c "import os; print('INVENTARIO_TOKEN' in os.environ)"TrueEn Linux, en un fichero chmod 600 de la cuenta de servicio. Con un almacén de secretos (Key Vault, CyberArk), el token se lee de ahí.
4. Programarlo
En Windows, con el Programador de tareas, usando el Python del entorno virtual y la carpeta del script como directorio de inicio:
$a = New-ScheduledTaskAction -Execute "C:\scripts\informe-equipos\.venv\Scripts\python.exe" -Argument "informe.py" -WorkingDirectory "C:\scripts\informe-equipos"$t = New-ScheduledTaskTrigger -Daily -At 07:00Register-ScheduledTask -TaskName "IT\Informe equipos" -Action $a -Trigger $t -User "CONTOSO\svc-informes" -Password (Read-Host "Contraseña")En Linux, con crontab -e del usuario de servicio:
0 7 * * 1-5 cd /opt/informe-equipos && . ./token.env && .venv/bin/python informe.pySin -WorkingDirectory (o el cd en cron), el script busca equipos.csv en C:\Windows\System32 o en el home, y falla con FileNotFoundError aunque a mano funcionara.
5. El ticket: "a mano funciona, programado falla"
El log de la tarea dice una de estas tres cosas:
KeyError: 'INVENTARIO_TOKEN': la tarea corre con otra cuenta (svc-informes) y la variable se definió para tu usuario. Se define para la cuenta de servicio, o a nivel de máquina si la política lo permite.UnicodeDecodeError: 'utf-8' codec can't decode byte 0xf1: el CSV lo ha guardado alguien como CSV (delimitado por comas) en lugar de CSV UTF-8, y viene en cp1252. La solución buena es pedir el fichero en UTF-8; mientras tanto,encoding="cp1252"para ese origen concreto. Si el error esUnicodeEncodeErroral imprimir, es la consola de Windows: la variablePYTHONUTF8=1en la tarea lo evita.ProxyErroroSSLError: CERTIFICATE_VERIFY_FAILED: en tu sesión el proxy se toma de la configuración del usuario y en la tarea no.requestsusa las variablesHTTPS_PROXYyNO_PROXY; y si el proxy inspecciona TLS, necesita la CA corporativa enREQUESTS_CA_BUNDLE.
[Environment]::SetEnvironmentVariable("HTTPS_PROXY", "http://proxy.contoso.local:8080", "Machine")[Environment]::SetEnvironmentVariable("NO_PROXY", "localhost,.contoso.local", "Machine")[Environment]::SetEnvironmentVariable("REQUESTS_CA_BUNDLE", "C:\scripts\ca\contoso-root.pem", "Machine")verify=False hace que el error desaparezca y deja la conexión abierta a cualquiera que se ponga en medio, con el token viajando dentro. No se usa, tampoco "para probar".
Siguientes pasos
- La guía de PowerShell cubre lo mismo en el lenguaje nativo de Windows; muchas tareas de IT se hacen mejor en uno u otro, y conviene conocer los dos.
- Microsoft Graph: la misma estructura de script sirve para leer equipos de Intune o usuarios de Entra ID, con un token de aplicación en lugar de uno fijo.
- Cuando tienes varios de estos scripts, el siguiente paso es una consola que los reúna. Es la idea del repositorio 05-it-ops-console de mi GitHub.
¿Falta algo o en tu entorno es distinto? Escríbeme y lo corrijo.
Tech Career Lab · newsletter
¿Te ha servido? Cada dos semanas envío una idea práctica para perfiles IT: LinkedIn, proyectos, networking y procesos de selección.
Apúntate y llévate las 33 ideas →