Saltar al contenido
Mano apartando una pila de reportes impresos junto a una laptop con una planilla abierta sin texto legible
Aprender IA

Automatizar reportes de Excel con Python: guía con código paso a paso

La automatización de reportes se resuelve en cuatro pasos: normalizar los archivos de entrada, consolidar con pandas, calcular indicadores y escribir un Excel con formato. Lo que hace que dure es el control de calidad: filas inválidas visibles, totales que cuadran y un registro de cada ejecución.

Por · Equipo de contenido y revisiónPublicado: 9 min de lectura

Puntos clave

Los puntos que más importan

  • Cuatro pasos: normalizar entradas, consolidar, calcular y escribir el Excel final.
  • Las filas con fecha o monto inválido se guardan en una hoja aparte, nunca se descartan en silencio.
  • pct_change calcula la variación contra el período anterior y deja el primer mes vacío, como corresponde.
  • Programá la ejecución con cron o el Programador de tareas y avisá solo cuando falla.
  • Si la estructura de entrada cambia, el script debe fallar con un mensaje claro en lugar de producir un reporte incorrecto.

Si cada mes dedicas horas a copiar datos de planillas, pegarlos en otra hoja, calcular totales y guardar el archivo con el nombre del mes, ese proceso se puede automatizar con Python. El resultado típico: el mismo reporte en minutos, sin errores de copiado y con la posibilidad de repetirlo tantas veces como haga falta. No necesitás ser programador: necesitás entender el proceso que hacés hoy y traducirlo a pasos.

Esta guía muestra el recorrido completo con la biblioteca pandas para consolidar datos y openpyxl para dar formato al Excel final. Incluye el código del ejemplo trabajado, los controles que hay que hacer antes de confiar en el resultado y las limitaciones honestas del enfoque.

Qué se puede automatizar y qué no

Se automatiza muy bien:

  • Leer varios archivos con la misma estructura y unirlos en una sola tabla.
  • Limpiar encabezados, espacios y tipos de datos inconsistentes.
  • Calcular totales, promedios, variaciones contra el mes anterior y rankings.
  • Escribir un Excel con hojas separadas, anchos de columna y formatos de moneda.
  • Enviar el archivo por correo o guardarlo en una carpeta compartida.
  • Programar la ejecución para el primer día hábil de cada mes.

No conviene automatizar sin revisión humana:

  • Decisiones que dependen de contexto no escrito ("este gasto va en marketing porque lo aprobó la dirección").
  • Clasificaciones ambiguas o cambiantes.
  • Datos que llegan en formatos libres y distintos cada vez.
  • Cálculos cuya fórmula nadie documentó y solo una persona entiende.

La regla práctica: automatizá lo repetitivo y predecible; dejá para una persona lo que requiere juicio.

Preparación del entorno

Instalá Python y creá un entorno virtual por proyecto. Aislar las dependencias evita conflictos entre proyectos.

python -m venv .venv
source .venv/bin/activate
pip install pandas openpyxl

pandas lee y transforma datos; openpyxl permite leer y escribir archivos de Excel con formato. Con esas dos bibliotecas se resuelve la mayoría de este tipo de trabajo.

Paso 1: leer y normalizar los archivos de entrada

El problema más común no es el cálculo: es la entrada. Antes de escribir una línea conviene inspeccionar dos o tres archivos y responder:

  • ¿Los encabezados están siempre en la misma fila?
  • ¿Qué hojas tienen los datos y qué hojas son de resumen?
  • ¿Cómo vienen las fechas: como texto, como número o como fecha real?
  • ¿Los montos usan coma o punto decimal?
  • ¿Hay filas vacías, subtotales o notas al pie?

Con esas respuestas, la lectura se vuelve explícita:

import pandas as pd
from pathlib import Path

CARPETA = Path("datos")
archivos = sorted(CARPETA.glob("ventas_*.xlsx"))

tablas = []
for archivo in archivos:
    tabla = pd.read_excel(archivo, sheet_name="Ventas", skiprows=3)
    tabla["origen"] = archivo.name
    tablas.append(tabla)

datos = pd.concat(tablas, ignore_index=True)
datos.columns = [columna.strip().lower().replace(" ", "_") for columna in datos.columns]

Dos decisiones que vale la pena notar: skiprows asume que los encabezados siempre están en la misma posición (si cambian, hay que detectarlos), y la columna origen deja registro de dónde vino cada fila, algo esencial para auditar un total que no cierra.

Paso 2: consolidar y limpiar

Con las tablas unidas, el paso siguiente es asegurar tipos de datos correctos. Un monto que llega como texto rompe cualquier suma.

datos["fecha"] = pd.to_datetime(datos["fecha"], errors="coerce", dayfirst=True)
datos["monto"] = pd.to_numeric(datos["monto"], errors="coerce")

filas_invalidas = datos[datos["fecha"].isna() | datos["monto"].isna()].copy()
datos = datos.dropna(subset=["fecha", "monto"])

if not filas_invalidas.empty:
    print(f"Revisar {len(filas_invalidas)} filas con fecha o monto inválido")
    filas_invalidas.to_excel("revisar_invalidas.xlsx", index=False)

En lugar de descartar las filas con problemas en silencio, el script las guarda en un archivo aparte. Quien revisa el reporte necesita saber qué quedó afuera y por qué.

Paso 3: calcular los indicadores

Los indicadores típicos de un reporte mensual se resuelven en pocas líneas:

por_mes = datos.groupby(datos["fecha"].dt.to_period("M"))["monto"].sum()
por_categoria = datos.groupby("categoria")["monto"].agg(["sum", "count", "mean"])
top_productos = (
    datos.groupby("producto")["monto"].sum()
    .sort_values(ascending=False)
    .head(10)
)

mensual = por_mes.reset_index()
mensual.columns = ["mes", "total"]
mensual["variacion_pct"] = mensual["total"].pct_change().mul(100).round(1)

La variación porcentual contra el período anterior es el indicador que más se pide y el que más errores tiene cuando se calcula a mano: pct_change compara filas consecutivas y deja el primer mes vacío, que es lo correcto.

Paso 4: escribir el Excel con formato

Un archivo plano con los números ya es útil, pero un reporte con formato se lee en la mitad del tiempo. Con openpyxl se ajustan anchos, se aplican formatos de moneda y se agrega una hoja de control:

from openpyxl import load_workbook
from openpyxl.styles import Font

salida = "reporte_mensual.xlsx"
with pd.ExcelWriter(salida, engine="openpyxl") as escritor:
    mensual.to_excel(escritor, sheet_name="Mensual", index=False)
    por_categoria.to_excel(escritor, sheet_name="Categorias")
    top_productos.to_excel(escritor, sheet_name="Top productos")
    filas_invalidas.to_excel(escritor, sheet_name="Revisar")

libro = load_workbook(salida)

hoja = libro["Mensual"]
for fila in hoja.iter_rows(min_row=2, min_col=2, max_col=2):
    for celda in fila:
        celda.number_format = '#,##0.00'

for columna, ancho in {"A": 12, "B": 16, "C": 14}.items():
    hoja.column_dimensions[columna].width = ancho

hoja["A1"].font = Font(bold=True)
libro.save(salida)

Ese archivo ya reemplaza el trabajo manual. Lo que sigue es programarlo: en Linux, una entrada en cron; en Windows, el Programador de tareas. El comando a programar es un script que active el entorno virtual y ejecute el proceso.

Ejemplo trabajado: reporte mensual desde tres archivos

Supongamos tres archivos de ventas con la misma estructura, un archivo por canal. El script completo, ordenado y con validaciones:

import pandas as pd
from pathlib import Path
from openpyxl import load_workbook
from openpyxl.styles import Font

CARPETA = Path("datos")
SALIDA = Path("reporte_mensual_2026-09.xlsx")

def cargar(archivo: Path) -> pd.DataFrame:
    tabla = pd.read_excel(archivo, sheet_name=0, skiprows=2)
    tabla.columns = [c.strip().lower().replace(" ", "_") for c in tabla.columns]
    tabla["canal"] = archivo.stem.split("_")[-1]
    return tabla

def normalizar(datos: pd.DataFrame) -> tuple[pd.DataFrame, pd.DataFrame]:
    datos["fecha"] = pd.to_datetime(datos["fecha"], errors="coerce", dayfirst=True)
    datos["monto"] = pd.to_numeric(datos["monto"], errors="coerce")
    invalidas = datos[datos["fecha"].isna() | datos["monto"].isna()]
    return datos.dropna(subset=["fecha", "monto"]), invalidas

datos = pd.concat([cargar(f) for f in sorted(CARPETA.glob("ventas_*.xlsx"))], ignore_index=True)
limpios, invalidas = normalizar(datos)

resumen = (
    limpios.groupby([limpios["fecha"].dt.to_period("M"), "canal"])["monto"]
    .sum()
    .unstack(fill_value=0)
    .round(2)
)
resumen["total"] = resumen.sum(axis=1)

with pd.ExcelWriter(SALIDA, engine="openpyxl") as escritor:
    resumen.to_excel(escritor, sheet_name="Resumen")
    limpios.to_excel(escritor, sheet_name="Detalle", index=False)
    invalidas.to_excel(escritor, sheet_name="Revisar", index=False)

libro = load_workbook(SALIDA)
hoja = libro["Resumen"]
hoja["A1"].font = Font(bold=True)
hoja.column_dimensions["A"].width = 12
for columnas in hoja.iter_cols(min_row=2, min_col=2):
    for celda in columnas:
        celda.number_format = '#,##0.00'
libro.save(SALIDA)

print(f"Listo: {SALIDA} | filas válidas: {len(limpios)} | a revisar: {len(invalidas)}")
print(f"Total del período: {limpios['monto'].sum():,.2f}")
VerificaciónCómo se hacePor qué
Total coincideComparar la suma del script con el total de los archivos originalesDetecta filas perdidas en la lectura
Sin duplicadosRevisar limpios.duplicated().sum()Evita contar dos veces el mismo registro
Filas inválidasAbrir la hoja "Revisar"No se descarta nada en silencio
Cierre de mesVerificar el rango de fechasUn archivo con fechas del mes siguiente distorsiona todo
IdempotenciaEjecutar dos veces y compararSi cambia el resultado, hay estado oculto

Paso 5: automatizar la ejecución y el aviso

Programá el script con cron si el servidor es Linux:

# Ejecuta el primer día de cada mes a las 7:00
0 7 1 * * cd /srv/reportes && .venv/bin/python generar_reporte.py >> registro.log 2>&1

Tres detalles que hacen la diferencia entre una automatización que dura y una que se rompe: guardar un registro de cada ejecución, avisar por correo o mensaje solo cuando algo falla y dejar el archivo con un nombre previsible que incluya el período. Si el proceso falla en silencio, nadie lo usa más.

Controles antes de confiar en el reporte

  • Cuadrá los totales contra la fuente. Si el script dice 1.240.500 y la planilla original 1.238.900, hay una diferencia que hay que explicar.
  • Buscá negativos imposibles. Un monto negativo en una columna de ventas suele ser una devolución sin registrar o un error de signo.
  • Revisá las fechas nulas. Casi siempre indican un formato distinto en un archivo.
  • Verificá la moneda. Mezclar pesos y dólares en una suma produce un número sin sentido.
  • Ejecutá dos veces. Un script idempotente da el mismo resultado sin duplicar datos.

Limitaciones

  • Si la entrada cambia, el script se rompe. Un encabezado movido o una hoja renombrada detienen el proceso. Conviene que falle con un mensaje claro en lugar de producir un reporte silenciosamente incorrecto.
  • Las macros y fórmulas complejas no se traducen solas. Si tu planilla tiene lógica en macros de Excel, primero hay que documentar qué hace y recién después reimplementarla.
  • Power Query puede ser suficiente. Si ya trabajás en Excel y el proceso es estable, la propia herramienta puede resolverlo sin programar. Python conviene cuando hay muchos archivos, lógica compleja o necesidad de dejar registro.
  • Permisos y accesos. Automatizar requiere que el script tenga acceso a las carpetas y credenciales, con el cuidado de seguridad que eso implica.
  • El mantenimiento es real. Cambios en el negocio (nuevas categorías, nuevas reglas de negocio) requieren actualizar el script. Un reporte automatizado no se mantiene solo para siempre.
  • No reemplaza el análisis. El script consolida y calcula; la interpretación de qué significan los números sigue siendo tuya.

Preguntas frecuentes

¿Necesito saber programar mucho? Basta con variables, funciones, listas y un poco de lectura de documentación. Este tipo de automatización es un excelente primer proyecto real.

¿Puedo hacer lo mismo con Google Sheets? Sí, con Apps Script y funciones propias. Si tu equipo vive en Google, esa ruta puede integrarse mejor; si los archivos son de escritorio, Python es más directo.

¿Cómo manejo archivos con estructuras distintas? Detectá la fila de encabezados buscando una palabra clave en las primeras filas y normalizá cada archivo a una estructura común antes de unirlos. Es más trabajo al principio y más robusto después.

¿Qué hago si el reporte debe llevar formato corporativo? Cargá una plantilla de Excel como base y escribí solo los datos en las celdas correspondientes. Así el formato queda en la plantilla y el código se ocupa de los números.

¿La IA puede ayudarme a escribirlo? Sí: pedile que explique cada línea, que proponga validaciones y que te ayude a interpretar un error. No le pidas que adivine la estructura de tus archivos; eso lo tenés que describir vos.

¿Cada cuánto debo revisar el script? Cada vez que cambie un archivo de entrada o una regla de negocio. Un buen control es ejecutarlo manualmente una vez al mes y comparar contra el resultado esperado.

Siguiente paso

Si querés combinar esta automatización con asistentes de IA para analizar resultados, revisá la guía de prompts para revisar código con IA y el recorrido de análisis de datos para interpretar los indicadores. Para entender qué tareas conviene automatizar antes de escribir código, sirve automatizar tareas con IA. Y si querés una ruta guiada de datos y automatización, revisá los cursos de Cursalo —empezando por ChatGPT para el trabajo y datos— y el detalle de precios.

Fuentes

Referencias externas

  1. pandas: guía de 10 minutospandas
  2. openpyxl en PyPIPython Package Index
  3. venv: entornos virtualesPython Software Foundation

Siguiente paso

Domina la IA con Cursalo

Crea tu cuenta y avanza con rutas estructuradas, proyectos reales, libros y biblioteca de prompts.

02 / LLEVAR A LA PRÁCTICA

Después de leer

Convierte una idea útil en una habilidad repetible.

Elige una ruta breve, aplícala a una tarea real y termina con algo que puedas revisar, usar o compartir.