26. Automatización de Reportes
Genera reportes PDF, Excel y HTML automáticamente
Los reportes manuales consumen tiempo valioso que podrías dedicar al análisis estratégico. En esta lección aprenderás a generar reportes profesionales automáticamente en PDF, Excel y HTML con datos actualizados en tiempo real.
El problema de los reportes manuales
Antes de automatizar
Analista típico pasa 15-20 horas mensuales en:
- Copiar datos entre Excel sheets
- Actualizar gráficos manualmente
- Calcular métricas repetitivas
- Formatear documentos
- Enviar reportes a stakeholders
Después de automatizar
Script Python ejecuta en 5 minutos todo lo anterior:
- Extrae datos actualizados automáticamente
- Calcula KPIs y métricas
- Genera gráficos profesionales
- Crea reportes en múltiples formatos
- Distribuye por email
Resultado: 15 horas ahorradas mensuales = tiempo para análisis de mayor valor.
Tipos de reportes a automatizar
1. Reportes operacionales diarios
- Ventas del día anterior
- Tráfico web y conversiones
- Inventario y stock crítico
- Performance de campañas publicitarias
2. Reportes tácticos semanales
- Tendencias de la semana
- Análisis de cohortes
- Top productos/categorías
- Embudo de conversión
3. Reportes estratégicos mensuales
- Crecimiento MoM y YoY
- Análisis competitivo
- Proyecciones y forecasting
- ROI de marketing
Implementación completa
Arquitectura del sistema de reportes
# Estructura del proyecto
automated_reports/
├── report_generator.py # Generador principal
├── templates/
│ ├── email_template.html
│ └── report_template.html
├── data/
│ └── ventas.csv
├── reports/
│ ├── pdf/
│ ├── excel/
│ └── html/
└── config.py
Paso 1: Generador de reportes PDF profesionales
# report_generator.py
"""
Generador automático de reportes en múltiples formatos
"""
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from reportlab.lib import colors
from reportlab.lib.pagesizes import letter, A4
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer, Image, PageBreak
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import inch
from reportlab.lib.enums import TA_CENTER, TA_RIGHT, TA_LEFT
from datetime import datetime, timedelta
import os
class ReportGenerator:
"""Generador de reportes automatizados"""
def __init__(self, data_path='data/ventas.csv'):
"""Inicializa generador con datos"""
self.df = pd.read_csv(data_path)
self.df['fecha'] = pd.to_datetime(self.df['fecha'])
self.fecha_reporte = datetime.now().strftime('%Y-%m-%d')
self.styles = getSampleStyleSheet()
# Crear directorios si no existen
os.makedirs('reports/pdf', exist_ok=True)
os.makedirs('reports/excel', exist_ok=True)
os.makedirs('reports/html', exist_ok=True)
os.makedirs('temp', exist_ok=True)
def calcular_metricas(self):
"""Calcula KPIs principales"""
# Filtrar último mes
fecha_inicio = datetime.now() - timedelta(days=30)
df_mes = self.df[self.df['fecha'] >= fecha_inicio]
# Mes anterior para comparación
fecha_inicio_anterior = fecha_inicio - timedelta(days=30)
df_mes_anterior = self.df[
(self.df['fecha'] >= fecha_inicio_anterior) &
(self.df['fecha'] < fecha_inicio)
]
metricas = {
'total_ventas': df_mes['total'].sum(),
'total_ventas_anterior': df_mes_anterior['total'].sum(),
'num_transacciones': len(df_mes),
'num_transacciones_anterior': len(df_mes_anterior),
'ticket_promedio': df_mes['total'].mean(),
'ticket_promedio_anterior': df_mes_anterior['total'].mean(),
'total_productos': df_mes['cantidad'].sum(),
'total_productos_anterior': df_mes_anterior['cantidad'].sum()
}
# Calcular variaciones porcentuales
metricas['var_ventas'] = (
(metricas['total_ventas'] - metricas['total_ventas_anterior']) /
metricas['total_ventas_anterior'] * 100
) if metricas['total_ventas_anterior'] > 0 else 0
metricas['var_transacciones'] = (
(metricas['num_transacciones'] - metricas['num_transacciones_anterior']) /
metricas['num_transacciones_anterior'] * 100
) if metricas['num_transacciones_anterior'] > 0 else 0
metricas['var_ticket'] = (
(metricas['ticket_promedio'] - metricas['ticket_promedio_anterior']) /
metricas['ticket_promedio_anterior'] * 100
) if metricas['ticket_promedio_anterior'] > 0 else 0
return metricas
def generar_graficos(self):
"""Genera gráficos para el reporte"""
# Configurar estilo
sns.set_style("whitegrid")
plt.rcParams['figure.figsize'] = (10, 6)
# 1. Ventas diarias del último mes
fecha_inicio = datetime.now() - timedelta(days=30)
df_mes = self.df[self.df['fecha'] >= fecha_inicio]
ventas_diarias = df_mes.groupby('fecha')['total'].sum().reset_index()
plt.figure(figsize=(10, 5))
plt.plot(ventas_diarias['fecha'], ventas_diarias['total'], marker='o', linewidth=2, color='#ae4291')
plt.title('Ventas Diarias - Últimos 30 Días', fontsize=14, fontweight='bold')
plt.xlabel('Fecha')
plt.ylabel('Ventas ($)')
plt.xticks(rotation=45)
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('temp/ventas_diarias.png', dpi=150, bbox_inches='tight')
plt.close()
# 2. Top 10 productos
top_productos = df_mes.groupby('producto')['total'].sum().nlargest(10).reset_index()
plt.figure(figsize=(10, 6))
plt.barh(top_productos['producto'], top_productos['total'], color='#35286b')
plt.title('Top 10 Productos por Ventas', fontsize=14, fontweight='bold')
plt.xlabel('Ventas ($)')
plt.tight_layout()
plt.savefig('temp/top_productos.png', dpi=150, bbox_inches='tight')
plt.close()
# 3. Ventas por categoría
ventas_categoria = df_mes.groupby('categoria')['total'].sum().reset_index()
plt.figure(figsize=(8, 8))
plt.pie(ventas_categoria['total'], labels=ventas_categoria['categoria'],
autopct='%1.1f%%', startangle=90, colors=sns.color_palette("viridis", len(ventas_categoria)))
plt.title('Distribución de Ventas por Categoría', fontsize=14, fontweight='bold')
plt.tight_layout()
plt.savefig('temp/ventas_categoria.png', dpi=150, bbox_inches='tight')
plt.close()
print("✅ Gráficos generados exitosamente")
def generar_reporte_pdf(self):
"""Genera reporte profesional en PDF"""
print("\n📄 Generando reporte PDF...")
# Calcular métricas
metricas = self.calcular_metricas()
# Generar gráficos
self.generar_graficos()
# Crear PDF
filename = f'reports/pdf/reporte_ventas_{self.fecha_reporte}.pdf'
doc = SimpleDocTemplate(filename, pagesize=letter)
story = []
# Estilos personalizados
titulo_style = ParagraphStyle(
'CustomTitle',
parent=self.styles['Heading1'],
fontSize=24,
textColor=colors.HexColor('#35286b'),
alignment=TA_CENTER,
spaceAfter=30
)
subtitulo_style = ParagraphStyle(
'CustomSubtitle',
parent=self.styles['Heading2'],
fontSize=16,
textColor=colors.HexColor('#ae4291'),
spaceAfter=12
)
# 1. Título
titulo = Paragraph("Reporte de Ventas Mensual", titulo_style)
story.append(titulo)
fecha = Paragraph(
f"<para align=center>Período: {self.fecha_reporte}</para>",
self.styles['Normal']
)
story.append(fecha)
story.append(Spacer(1, 0.3*inch))
# 2. Resumen ejecutivo
story.append(Paragraph("Resumen Ejecutivo", subtitulo_style))
# Tabla de métricas clave
datos_metricas = [
['Métrica', 'Valor Actual', 'Mes Anterior', 'Variación'],
[
'Ventas Totales',
f"${metricas['total_ventas']:,.2f}",
f"${metricas['total_ventas_anterior']:,.2f}",
f"{metricas['var_ventas']:+.1f}%"
],
[
'Transacciones',
f"{metricas['num_transacciones']:,}",
f"{metricas['num_transacciones_anterior']:,}",
f"{metricas['var_transacciones']:+.1f}%"
],
[
'Ticket Promedio',
f"${metricas['ticket_promedio']:,.2f}",
f"${metricas['ticket_promedio_anterior']:,.2f}",
f"{metricas['var_ticket']:+.1f}%"
],
[
'Productos Vendidos',
f"{metricas['total_productos']:,}",
f"{metricas['total_productos_anterior']:,}",
f"{((metricas['total_productos'] - metricas['total_productos_anterior']) / metricas['total_productos_anterior'] * 100):+.1f}%"
]
]
tabla_metricas = Table(datos_metricas, colWidths=[2.5*inch, 1.5*inch, 1.5*inch, 1.2*inch])
tabla_metricas.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#35286b')),
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('FONTSIZE', (0, 0), (-1, 0), 12),
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
('BACKGROUND', (0, 1), (-1, -1), colors.beige),
('GRID', (0, 0), (-1, -1), 1, colors.black),
('FONTNAME', (0, 1), (-1, -1), 'Helvetica'),
('FONTSIZE', (0, 1), (-1, -1), 10),
('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.lightgrey])
]))
story.append(tabla_metricas)
story.append(Spacer(1, 0.3*inch))
# 3. Análisis de tendencias
story.append(Paragraph("Análisis de Tendencias", subtitulo_style))
story.append(Spacer(1, 0.1*inch))
# Interpretación automática
if metricas['var_ventas'] > 0:
interpretacion = f"Las ventas han crecido un <b>{metricas['var_ventas']:.1f}%</b> respecto al mes anterior, " \
f"lo que indica una tendencia positiva en el desempeño del negocio."
else:
interpretacion = f"Las ventas han disminuido un <b>{abs(metricas['var_ventas']):.1f}%</b> respecto al mes anterior. " \
f"Se recomienda revisar estrategias de marketing y pricing."
story.append(Paragraph(interpretacion, self.styles['Normal']))
story.append(Spacer(1, 0.2*inch))
# 4. Gráficos
story.append(PageBreak())
story.append(Paragraph("Visualizaciones", subtitulo_style))
# Ventas diarias
story.append(Image('temp/ventas_diarias.png', width=6*inch, height=3*inch))
story.append(Spacer(1, 0.2*inch))
# Top productos
story.append(Image('temp/top_productos.png', width=6*inch, height=3.5*inch))
story.append(Spacer(1, 0.2*inch))
# Ventas por categoría
story.append(PageBreak())
story.append(Image('temp/ventas_categoria.png', width=5*inch, height=5*inch))
# Construir PDF
doc.build(story)
print(f"✅ Reporte PDF generado: {filename}")
return filename
def generar_reporte_excel(self):
"""Genera reporte interactivo en Excel"""
print("\n📊 Generando reporte Excel...")
filename = f'reports/excel/reporte_ventas_{self.fecha_reporte}.xlsx'
# Calcular métricas
metricas = self.calcular_metricas()
fecha_inicio = datetime.now() - timedelta(days=30)
df_mes = self.df[self.df['fecha'] >= fecha_inicio]
# Crear Excel con múltiples hojas
with pd.ExcelWriter(filename, engine='openpyxl') as writer:
# Hoja 1: Resumen ejecutivo
resumen_data = {
'Métrica': ['Ventas Totales', 'Transacciones', 'Ticket Promedio', 'Productos Vendidos'],
'Valor Actual': [
metricas['total_ventas'],
metricas['num_transacciones'],
metricas['ticket_promedio'],
metricas['total_productos']
],
'Mes Anterior': [
metricas['total_ventas_anterior'],
metricas['num_transacciones_anterior'],
metricas['ticket_promedio_anterior'],
metricas['total_productos_anterior']
],
'Variación %': [
metricas['var_ventas'],
metricas['var_transacciones'],
metricas['var_ticket'],
((metricas['total_productos'] - metricas['total_productos_anterior']) /
metricas['total_productos_anterior'] * 100) if metricas['total_productos_anterior'] > 0 else 0
]
}
df_resumen = pd.DataFrame(resumen_data)
df_resumen.to_excel(writer, sheet_name='Resumen Ejecutivo', index=False)
# Hoja 2: Ventas diarias
ventas_diarias = df_mes.groupby('fecha').agg({
'total': 'sum',
'cantidad': 'sum',
'producto': 'count'
}).reset_index()
ventas_diarias.columns = ['Fecha', 'Ventas Totales', 'Productos Vendidos', 'Transacciones']
ventas_diarias.to_excel(writer, sheet_name='Ventas Diarias', index=False)
# Hoja 3: Top productos
top_productos = df_mes.groupby('producto').agg({
'total': 'sum',
'cantidad': 'sum'
}).nlargest(20, 'total').reset_index()
top_productos.columns = ['Producto', 'Ventas', 'Unidades Vendidas']
top_productos.to_excel(writer, sheet_name='Top Productos', index=False)
# Hoja 4: Análisis por categoría
analisis_categoria = df_mes.groupby('categoria').agg({
'total': ['sum', 'mean'],
'producto': 'count'
}).reset_index()
analisis_categoria.columns = ['Categoría', 'Ventas Totales', 'Venta Promedio', 'Transacciones']
analisis_categoria.to_excel(writer, sheet_name='Por Categoría', index=False)
# Hoja 5: Datos completos
df_mes.to_excel(writer, sheet_name='Datos Completos', index=False)
print(f"✅ Reporte Excel generado: {filename}")
return filename
def generar_reporte_html(self):
"""Genera reporte HTML interactivo"""
print("\n🌐 Generando reporte HTML...")
filename = f'reports/html/reporte_ventas_{self.fecha_reporte}.html'
# Calcular métricas
metricas = self.calcular_metricas()
# Plantilla HTML
html_content = f"""
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Reporte de Ventas - {self.fecha_reporte}</title>
<style>
body {{
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
margin: 0;
padding: 20px;
background-color: #f5f5f5;
}}
.container {{
max-width: 1200px;
margin: 0 auto;
background-color: white;
padding: 40px;
border-radius: 10px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}}
h1 {{
color: #35286b;
text-align: center;
font-size: 32px;
margin-bottom: 10px;
}}
.date {{
text-align: center;
color: #666;
margin-bottom: 40px;
}}
.metrics-grid {{
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin-bottom: 40px;
}}
.metric-card {{
background: linear-gradient(135deg, #ae4291 0%, #35286b 100%);
color: white;
padding: 25px;
border-radius: 10px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}}
.metric-label {{
font-size: 14px;
opacity: 0.9;
margin-bottom: 8px;
}}
.metric-value {{
font-size: 32px;
font-weight: bold;
margin-bottom: 8px;
}}
.metric-change {{
font-size: 14px;
opacity: 0.9;
}}
.positive {{ color: #4caf50; }}
.negative {{ color: #f44336; }}
table {{
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}}
th, td {{
padding: 12px;
text-align: left;
border-bottom: 1px solid #ddd;
}}
th {{
background-color: #35286b;
color: white;
font-weight: bold;
}}
tr:hover {{
background-color: #f5f5f5;
}}
.footer {{
text-align: center;
margin-top: 40px;
color: #666;
font-size: 14px;
}}
</style>
</head>
<body>
<div class="container">
<h1>Reporte de Ventas Mensual</h1>
<p class="date">Período: {self.fecha_reporte}</p>
<div class="metrics-grid">
<div class="metric-card">
<div class="metric-label">Ventas Totales</div>
<div class="metric-value">${metricas['total_ventas']:,.0f}</div>
<div class="metric-change {'positive' if metricas['var_ventas'] > 0 else 'negative'}">
{metricas['var_ventas']:+.1f}% vs mes anterior
</div>
</div>
<div class="metric-card">
<div class="metric-label">Transacciones</div>
<div class="metric-value">{metricas['num_transacciones']:,}</div>
<div class="metric-change {'positive' if metricas['var_transacciones'] > 0 else 'negative'}">
{metricas['var_transacciones']:+.1f}% vs mes anterior
</div>
</div>
<div class="metric-card">
<div class="metric-label">Ticket Promedio</div>
<div class="metric-value">${metricas['ticket_promedio']:,.2f}</div>
<div class="metric-change {'positive' if metricas['var_ticket'] > 0 else 'negative'}">
{metricas['var_ticket']:+.1f}% vs mes anterior
</div>
</div>
<div class="metric-card">
<div class="metric-label">Productos Vendidos</div>
<div class="metric-value">{metricas['total_productos']:,}</div>
<div class="metric-change">Unidades totales</div>
</div>
</div>
<div class="footer">
<p>Reporte generado automáticamente el {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</p>
<p>Powered by Python Analytics</p>
</div>
</div>
</body>
</html>
"""
with open(filename, 'w', encoding='utf-8') as f:
f.write(html_content)
print(f"✅ Reporte HTML generado: {filename}")
return filename
Paso 2: Script de ejecución completo
# main.py
"""
Ejecuta generación de todos los reportes
"""
from report_generator import ReportGenerator
from datetime import datetime
def main():
"""Genera reportes en todos los formatos"""
print("="*60)
print("🚀 INICIANDO GENERACIÓN DE REPORTES")
print("="*60)
print(f"Fecha: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
# Crear generador
generator = ReportGenerator(data_path='data/ventas.csv')
# Generar todos los formatos
try:
pdf_file = generator.generar_reporte_pdf()
excel_file = generator.generar_reporte_excel()
html_file = generator.generar_reporte_html()
print("\n" + "="*60)
print("✅ REPORTES GENERADOS EXITOSAMENTE")
print("="*60)
print(f"\n📄 PDF: {pdf_file}")
print(f"📊 Excel: {excel_file}")
print(f"🌐 HTML: {html_file}")
print("\n✨ Proceso completado\n")
except Exception as e:
print(f"\n❌ Error generando reportes: {e}")
if __name__ == "__main__":
main()
Casos de uso reales
1. Startup SaaS
Automatiza reportes diarios de métricas clave (MRR, churn, CAC, LTV) que se envían a founders e inversionistas cada mañana.
2. E-commerce
Genera reportes de ventas por hora durante Black Friday/Cyber Monday para ajustar inventario y marketing en tiempo real.
3. Agencia de marketing
Reportes semanales automatizados para 50 clientes con métricas específicas de cada campaña (CPC, CTR, conversiones, ROI).
Ventajas de la automatización
Ahorro de tiempo
- Manual: 2-3 horas por reporte
- Automatizado: 5 minutos de ejecución
Consistencia
- Mismo formato y cálculos siempre
- Sin errores humanos de copia/pegado
- Versión controlada
Escalabilidad
- Generar 1 reporte = mismo tiempo que 100 reportes
- Agregar nuevos clientes/productos sin overhead
- Modificar todos los reportes actualizando un solo script
Resumen de la lección
✅ Generaste reportes PDF profesionales con gráficos y tablas ✅ Creaste reportes Excel interactivos con múltiples hojas ✅ Desarrollaste reportes HTML con diseño responsive ✅ Automatizaste cálculo de métricas y KPIs ✅ Implementaste sistema completo de generación de reportes
🎯 Próxima lección: 27. Envío Automático de Emails - Distribuye estos reportes por email automáticamente
En la siguiente lección conectarás estos reportes con un sistema de envío automático de emails para distribuirlos a stakeholders sin intervención manual. ¡La automatización completa está a solo una lección de distancia!
¿Completaste esta lección?
Marca esta lección como completada. Tu progreso se guardará en tu navegador.