← Indice del curso

Repaso: ejercicios 1-7

Resoluciones sueltas de los ejercicios 1 a 7 de la guia de clase 4, resaltadas para releer sin abrir un editor. No son las soluciones de referencia de Ejercicios Clase 4/soluciones/, son intentos propios guardados en la raiz del repo.

1.py Ejercicio 1. Consolidacion mensual con validacion

"""Ejercicio 1. Consolidacion mensual con validacion."""
import json
import os

import pandas as pd

BASE = os.path.dirname(__file__)

# Cargar y concatenar
enero = pd.read_csv(os.path.join(BASE, "ventas_enero.csv"))
febrero = pd.read_json(os.path.join(BASE, "ventas_febrero.json"))
ventas = pd.concat([enero, febrero], ignore_index=True)

# Validar tipos y convertir fecha
ventas["fecha"] = pd.to_datetime(ventas["fecha"], errors="coerce")
ventas["unidades"] = pd.to_numeric(ventas["unidades"], errors="coerce")
ventas["precio"] = pd.to_numeric(ventas["precio"], errors="coerce")
if ventas[["fecha", "unidades", "precio"]].isna().any().any():
    print("Aviso: hay filas con datos invalidos, se descartan del calculo")
    ventas = ventas.dropna(subset=["fecha", "unidades", "precio"])

# Calcular importe
ventas["importe"] = ventas["unidades"] * ventas["precio"]
ventas["mes"] = ventas["fecha"].dt.to_period("M").astype(str)

print("Ventas consolidadas:")
print(ventas)

# Resumen por mes y sucursal
resumen = (
    ventas.groupby(["mes", "sucursal"], as_index=False)["importe"]
    .sum()
    .rename(columns={"importe": "importe_total"})
)
print("\nResumen por mes/sucursal:")
print(resumen)

# Exportar
ventas.to_excel(os.path.join(BASE, "consolidado.xlsx"), index=False)
with open(os.path.join(BASE, "resumen.json"), "w", encoding="utf-8") as f:
    json.dump(resumen.to_dict(orient="records"), f, ensure_ascii=False, indent=2)

print("\nArchivos generados: consolidado.xlsx, resumen.json")

2.py Ejercicio 2. Ingresos vectorizados y precios invalidos

"""Ejercicio 2. Ingresos vectorizados y precios invalidos."""
import os

import numpy as np
import pandas as pd

BASE = os.path.dirname(__file__)

precios_df = pd.read_csv(os.path.join(BASE, "precios.csv"))
unidades_df = pd.read_csv(os.path.join(BASE, "unidades.csv"))

sucursales = unidades_df["Sucursal"].to_numpy()
productos = precios_df["producto"].to_numpy()
unidades = unidades_df[productos].to_numpy()
precios = precios_df["precio"].to_numpy()

# Validar que los precios sean numericos y positivos
try:
    precios_validados = np.array(precios, dtype=float)
    if np.any(precios_validados <= 0):
        raise ValueError("Hay precios menores o iguales a cero")
except (TypeError, ValueError) as exc:
    raise SystemExit(f"Precios invalidos: {exc}")

# Ingresos por celda (sucursal x producto) y totales por sucursal
ingresos = unidades * precios_validados
ingresos_df = pd.DataFrame(ingresos, index=sucursales, columns=productos)
ingresos_df["total_sucursal"] = ingresos_df.sum(axis=1)

print("Ingresos por sucursal y producto:")
print(ingresos_df)

3.py Ejercicio 3. Integracion de cuatro fuentes comerciales

"""Ejercicio 3. Integracion de cuatro fuentes comerciales."""
import json
import os

import pandas as pd

BASE = os.path.dirname(__file__)

clientes = pd.read_csv(os.path.join(BASE, "clientes.csv"))
pedidos = pd.read_json(os.path.join(BASE, "pedidos.json"))
pagos = pd.read_csv(os.path.join(BASE, "pagos.csv"))
devoluciones = pd.read_json(os.path.join(BASE, "devoluciones.json"))

# Union conservando inconsistencias (outer joins)
datos = pedidos.merge(clientes, on="cliente_id", how="outer")
datos = datos.merge(pagos, on="pedido_id", how="outer")
datos = datos.merge(devoluciones, on="pedido_id", how="outer")

datos["pagado"] = datos["pagado"].fillna(0)
datos["devolucion"] = datos["devolucion"].fillna(0)
datos["saldo"] = datos["total"] - datos["pagado"] - datos["devolucion"]


def clasificar(row):
    if pd.isna(row["total"]) or pd.isna(row["cliente_id"]) or pd.isna(row["nombre"]):
        return "Inconsistente"
    if row["saldo"] <= 0:
        return "Pagado"
    if row["pagado"] > 0:
        return "Parcial"
    return "Pendiente"


datos["estado"] = datos.apply(clasificar, axis=1)

print("Datos integrados:")
print(datos)

# Reporte por segmento (excluye registros inconsistentes que no tienen segmento)
reporte = (
    datos[datos["estado"] != "Inconsistente"]
    .groupby("segmento", as_index=False)
    .agg(total=("total", "sum"), pagado=("pagado", "sum"), saldo=("saldo", "sum"))
)
print("\nReporte por segmento:")
print(reporte)

excepciones = datos[datos["estado"] == "Inconsistente"]
print("\nExcepciones:")
print(excepciones)

reporte.to_excel(os.path.join(BASE, "reporte.xlsx"), index=False)
with open(os.path.join(BASE, "excepciones.json"), "w", encoding="utf-8") as f:
    json.dump(excepciones.to_dict(orient="records"), f, ensure_ascii=False, indent=2, default=str)

print("\nArchivos generados: reporte.xlsx, excepciones.json")

4.py Ejercicio 4. Barras agrupadas y variacion porcentual

"""Ejercicio 4. Barras agrupadas y variacion porcentual."""
import os

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns

BASE = os.path.dirname(__file__)

datos = pd.read_csv(os.path.join(BASE, "ventas_regiones.csv"))
datos["variacion_pct"] = (
    (datos["ventas_2026"] - datos["ventas_2025"]) / datos["ventas_2025"] * 100
)
print(datos)

x = np.arange(len(datos))
ancho = 0.35

fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(8, 8))

ax1.bar(x - ancho / 2, datos["ventas_2025"], ancho, label="2025", color=sns.color_palette()[0])
ax1.bar(x + ancho / 2, datos["ventas_2026"], ancho, label="2026", color=sns.color_palette()[1])
for i, var in enumerate(datos["variacion_pct"]):
    altura = max(datos["ventas_2025"][i], datos["ventas_2026"][i])
    ax1.annotate(f"{var:+.1f}%", (x[i], altura + 2), ha="center")
ax1.set_xticks(x)
ax1.set_xticklabels(datos["region"])
ax1.set_ylabel("Ventas")
ax1.set_title("Ventas por region 2025 vs 2026")
ax1.legend()

ax2.bar(x, datos["variacion_pct"], color=sns.color_palette()[2])
ax2.axhline(0, color="black", linewidth=1)
ax2.set_xticks(x)
ax2.set_xticklabels(datos["region"])
ax2.set_ylabel("Variacion %")
ax2.set_title("Variacion porcentual por region")

fig.tight_layout()
fig.savefig(os.path.join(BASE, "grafico.png"))
print("\nArchivo generado: grafico.png")

5.py Ejercicio 5. MultiIndex y tabla dinamica segura

"""Ejercicio 5. MultiIndex y tabla dinamica segura."""
import os

import pandas as pd

BASE = os.path.dirname(__file__)

ventas = pd.read_csv(os.path.join(BASE, "ventas.csv"))

try:
    columnas_requeridas = ["Región", "Vendedor", "Producto", "Unidades", "Precio", "Costo"]
    faltantes = [c for c in columnas_requeridas if c not in ventas.columns]
    if faltantes:
        raise KeyError(f"Columnas faltantes: {faltantes}")

    ventas["Unidades"] = pd.to_numeric(ventas["Unidades"], errors="raise")
    ventas["Precio"] = pd.to_numeric(ventas["Precio"], errors="raise")
    ventas["Costo"] = pd.to_numeric(ventas["Costo"], errors="raise")
except (KeyError, ValueError) as exc:
    raise SystemExit(f"Error de datos: {exc}")

ventas["Importe"] = ventas["Unidades"] * ventas["Precio"]
ventas["Margen"] = ventas["Importe"] - ventas["Unidades"] * ventas["Costo"]

ventas_mi = ventas.set_index(["Región", "Vendedor"])
print("DataFrame con MultiIndex Región-Vendedor:")
print(ventas_mi)

pivot = pd.pivot_table(
    ventas, values=["Importe", "Margen"], index="Región", columns="Producto", aggfunc="sum", fill_value=0
)
print("\nTabla dinamica por region y producto:")
print(pivot)

6.py Ejercicio 6. Consolidacion mensual (con manejo explicito de errores)

"""Ejercicio 6. Consolidacion mensual (con manejo explicito de errores)."""
import json
import os

import pandas as pd

BASE = os.path.dirname(__file__)


def cargar_datos():
    try:
        enero = pd.read_csv(os.path.join(BASE, "ventas_enero.csv"))
        febrero = pd.read_json(os.path.join(BASE, "ventas_febrero.json"))
    except FileNotFoundError as exc:
        raise SystemExit(f"Archivo no encontrado: {exc}")
    return pd.concat([enero, febrero], ignore_index=True)


ventas = cargar_datos()

try:
    ventas["fecha"] = pd.to_datetime(ventas["fecha"], errors="raise")
    ventas["unidades"] = pd.to_numeric(ventas["unidades"], errors="raise")
    ventas["precio"] = pd.to_numeric(ventas["precio"], errors="raise")
except ValueError as exc:
    raise SystemExit(f"Error de conversion de datos: {exc}")

ventas["importe"] = ventas["unidades"] * ventas["precio"]
ventas["mes"] = ventas["fecha"].dt.to_period("M").astype(str)

print("Ventas consolidadas:")
print(ventas)

resumen = (
    ventas.groupby("mes", as_index=False)["importe"].sum().rename(columns={"importe": "importe_total"})
)
print("\nResumen por mes:")
print(resumen)

try:
    ventas.to_excel(os.path.join(BASE, "consolidado.xlsx"), index=False)
    with open(os.path.join(BASE, "resumen.json"), "w", encoding="utf-8") as f:
        json.dump(resumen.to_dict(orient="records"), f, ensure_ascii=False, indent=2)
except OSError as exc:
    raise SystemExit(f"Error al exportar archivos: {exc}")

print("\nArchivos generados: consolidado.xlsx, resumen.json")

7.py Ejercicio 7. Lotes y auditoria

"""Ejercicio 7. Lotes y auditoria."""
import json
import os

import pandas as pd

BASE = os.path.dirname(__file__)

lote_a = pd.read_csv(os.path.join(BASE, "lote_a.csv"))
lote_a["origen"] = "lote_a"
lote_b = pd.read_json(os.path.join(BASE, "lote_b.json"))
lote_b["origen"] = "lote_b"

lotes = pd.concat([lote_a, lote_b], ignore_index=True)

# Resolver duplicados por id: se conserva el ultimo registro
lotes = lotes.drop_duplicates(subset="id", keep="last").reset_index(drop=True)

maestros = pd.read_csv(os.path.join(BASE, "maestros.csv"))
lotes = lotes.merge(maestros, on="cuenta", how="left")

lotes["importe_numerico"] = pd.to_numeric(lotes["importe"], errors="coerce")
cuenta_valida = lotes["activa"].fillna(False).astype(bool)
importe_valido = lotes["importe_numerico"].notna()
lotes["valido"] = cuenta_valida & importe_valido

validos = lotes[lotes["valido"]].copy()
rechazados = lotes[~lotes["valido"]].copy()

print("Registros validos:")
print(validos)
print("\nRegistros rechazados:")
print(rechazados)

# Importe firmado y saldo por cuenta
validos["importe_firmado"] = validos.apply(
    lambda r: r["importe_numerico"] if r["tipo"] == "credito" else -r["importe_numerico"], axis=1
)
saldos = validos.groupby("cuenta", as_index=False)["importe_firmado"].sum().rename(
    columns={"importe_firmado": "saldo"}
)
print("\nSaldos por cuenta:")
print(saldos)

# Exportar saldos a Excel
saldos.to_excel(os.path.join(BASE, "saldos.xlsx"), index=False)

# Exportar rechazados a JSON
with open(os.path.join(BASE, "rechazados.json"), "w", encoding="utf-8") as f:
    json.dump(
        rechazados.drop(columns=["valido"]).to_dict(orient="records"),
        f,
        ensure_ascii=False,
        indent=2,
        default=str,
    )

# Exportar validos como sentencias SQL (INSERT)
with open(os.path.join(BASE, "validos.sql"), "w", encoding="utf-8") as f:
    f.write(
        "CREATE TABLE IF NOT EXISTS movimientos_validos (\n"
        "    id INT, cuenta VARCHAR(10), importe_firmado DECIMAL(12,2), tipo VARCHAR(10), origen VARCHAR(10)\n"
        ");\n\n"
    )
    for _, row in validos.iterrows():
        f.write(
            "INSERT INTO movimientos_validos (id, cuenta, importe_firmado, tipo, origen) "
            f"VALUES ({int(row['id'])}, '{row['cuenta']}', {row['importe_firmado']}, "
            f"'{row['tipo']}', '{row['origen']}');\n"
        )

print("\nArchivos generados: saldos.xlsx, rechazados.json, validos.sql")