Proyectos · Python · Intermedio

Control de gastos con CSV

Un programa que lee tus gastos de una hoja de cálculo exportada en CSV y saca un informe por categorías y por mes, desde la terminal y con exportación a JSON.

  • 5 pasos
  • 1 hora aproximadamente
  • 55 líneas de código

Lo que vas a construir

Muchas apps de bancos y casi cualquier hoja de cálculo exportan los movimientos en CSV. Con unas pocas decenas de líneas de Python puedes sacar de ese archivo el informe que quieras. Vas a leer el CSV, agrupar por categorías, filtrar por mes y convertirlo en una herramienta de terminal con sus opciones.

Lo que vas a practicar

  • Leer CSV con csv.DictReader
  • Agrupar con defaultdict
  • Filtrar con comprensiones de listas
  • Dar formato a una tabla con f-strings
  • Opciones de terminal con argparse

Cómo ejecutarlo: Crea gastos.csv con los datos de ejemplo (o exporta los tuyos con las mismas columnas), guarda el programa como gastos.py y ejecútalo con python gastos.py. En el último paso necesita la opción del mes: python gastos.py --mes 2026-05.

gastos.csv· crea este archivo junto al programa
fecha,concepto,categoria,importe
2026-04-28,Supermercado,Comida,62.40
2026-05-02,Luz,Casa,48.15
2026-05-03,Gasolina,Transporte,55.00
2026-05-06,Supermercado,Comida,71.85
2026-05-09,Cine,Ocio,16.50
2026-05-12,Internet,Casa,30.00
2026-05-15,Restaurante,Ocio,42.00
2026-05-20,Supermercado,Comida,58.30
2026-05-27,Abono transporte,Transporte,20.00
2026-06-01,Alquiler,Casa,650.00
Así funciona al final
$ python gastos.py --mes 2026-05 --json resumen.json
Gastos de 2026-05
------------------------------------
Comida          130.15 €   38.1 %
Casa             78.15 €   22.9 %
Transporte       75.00 €   21.9 %
Ocio             58.50 €   17.1 %
------------------------------------
Total           341.80 €
Resumen guardado en resumen.json

Antes de empezar, conviene haber hecho estas lecciones

Paso 1 de 5

Leer el archivo CSV

csv.DictReader lee el archivo y convierte cada fila en un diccionario cuyas claves son los títulos de la primera línea: gasto["importe"]. Abre el archivo con newline="" (lo pide el módulo csv) y encoding="utf-8".

with cierra el archivo solo al terminar el bloque, aunque haya un error.

gastos.py
import csv

with open("gastos.csv", newline="", encoding="utf-8") as archivo:
    gastos = list(csv.DictReader(archivo))

for gasto in gastos:
    print(gasto["fecha"], gasto["concepto"], gasto["importe"])

print(f"{len(gastos)} gastos leídos")
Al ejecutarlo
2026-04-28 Supermercado 62.40
2026-05-02 Luz 48.15
2026-05-03 Gasolina 55.00
2026-05-06 Supermercado 71.85
2026-05-09 Cine 16.50
2026-05-12 Internet 30.00
2026-05-15 Restaurante 42.00
2026-05-20 Supermercado 58.30
2026-05-27 Abono transporte 20.00
2026-06-01 Alquiler 650.00
10 gastos leídos

Paso 2 de 5

Totales por categoría

Del CSV todo llega como texto: float() convierte el importe en número para poder sumarlo.

defaultdict(float) es un diccionario que, si pides una clave que no existe, la crea con 0.0. Así por_categoria[categoria] += importe funciona igual la primera vez que aparece una categoría que las siguientes.

gastos.pyLo nuevo, resaltado
import csv
from collections import defaultdict

with open("gastos.csv", newline="", encoding="utf-8") as archivo:
    gastos = list(csv.DictReader(archivo))

total = 0.0
por_categoria = defaultdict(float)  # si la categoría no existe, empieza en 0.0

for gasto in gastos:
    importe = float(gasto["importe"])
    total += importe
    por_categoria[gasto["categoria"]] += importe

print(f"Total: {total:.2f} €")
for categoria, importe in por_categoria.items():
    print(f"  {categoria}: {importe:.2f} €")
Al ejecutarlo
Total: 1054.20 €
  Comida: 192.55 €
  Casa: 728.15 €
  Transporte: 75.00 €
  Ocio: 58.50 €

Paso 3 de 5

Funciones y gastos de un mes

El programa se reparte en funciones con un nombre claro: leer, filtrar y resumir. Cada una se puede probar y reutilizar por separado.

Como las fechas tienen el formato año-mes-día, los gastos de mayo son los que empiezan por «2026-05»: una comprensión de listas con startswith los filtra en una línea.

gastos.pyLo nuevo, resaltado
import csv
from collections import defaultdict


def leer_gastos(ruta):
    with open(ruta, newline="", encoding="utf-8") as archivo:
        return list(csv.DictReader(archivo))


def del_mes(gastos, mes):
    """Solo los gastos de un mes, con el formato «2026-05»."""
    return [g for g in gastos if g["fecha"].startswith(mes)]


def resumir(gastos):
    por_categoria = defaultdict(float)
    for gasto in gastos:
        por_categoria[gasto["categoria"]] += float(gasto["importe"])
    return dict(por_categoria)


gastos = del_mes(leer_gastos("gastos.csv"), "2026-05")
resumen = resumir(gastos)
print(f"Mayo de 2026: {len(gastos)} gastos, {sum(resumen.values()):.2f} €")
for categoria, importe in resumen.items():
    print(f"  {categoria}: {importe:.2f} €")
Al ejecutarlo
Mayo de 2026: 8 gastos, 341.80 €
  Casa: 78.15 €
  Transporte: 75.00 €
  Comida: 130.15 €
  Ocio: 58.50 €

Paso 4 de 5

Un informe ordenado

sorted con key=lambda par: par[1] ordena las categorías por su importe y reverse=True pone primero la mayor.

En las f-strings, :<12 alinea a la izquierda en 12 caracteres, :>10.2f a la derecha con dos decimales: así los números quedan en columna como en una tabla.

gastos.pyLo nuevo, resaltado
import csv
from collections import defaultdict


def leer_gastos(ruta):
    with open(ruta, newline="", encoding="utf-8") as archivo:
        return list(csv.DictReader(archivo))


def del_mes(gastos, mes):
    """Solo los gastos de un mes, con el formato «2026-05»."""
    return [g for g in gastos if g["fecha"].startswith(mes)]


def resumir(gastos):
    por_categoria = defaultdict(float)
    for gasto in gastos:
        por_categoria[gasto["categoria"]] += float(gasto["importe"])
    return dict(por_categoria)


def informe(resumen, titulo):
    total = sum(resumen.values())
    print(titulo)
    print("-" * 36)
    # De mayor a menor gasto
    for categoria, importe in sorted(resumen.items(), key=lambda par: par[1], reverse=True):
        porcentaje = importe / total * 100
        print(f"{categoria:<12}{importe:>10.2f} € {porcentaje:>6.1f} %")
    print("-" * 36)
    print(f"{'Total':<12}{total:>10.2f} €")


gastos = del_mes(leer_gastos("gastos.csv"), "2026-05")
informe(resumir(gastos), "Gastos de mayo de 2026")
Al ejecutarlo
Gastos de mayo de 2026
------------------------------------
Comida          130.15 €   38.1 %
Casa             78.15 €   22.9 %
Transporte       75.00 €   21.9 %
Ocio             58.50 €   17.1 %
------------------------------------
Total           341.80 €

Paso 5 de 5

Una herramienta de terminal

argparse convierte el programa en una herramienta con opciones: --mes es obligatoria, --archivo tiene un valor por defecto y --json es opcional. Además genera solo la ayuda con python gastos.py --help.

Si el archivo no existe o el mes no tiene gastos, parser.exit termina con un mensaje claro y un código de error. Con --json resumen.json se guarda también el resumen, con ensure_ascii=False para que las tildes se escriban tal cual.

gastos.pyLo nuevo, resaltado
import argparse
import csv
import json
from collections import defaultdict


def leer_gastos(ruta):
    with open(ruta, newline="", encoding="utf-8") as archivo:
        return list(csv.DictReader(archivo))


def del_mes(gastos, mes):
    """Solo los gastos de un mes, con el formato «2026-05»."""
    return [g for g in gastos if g["fecha"].startswith(mes)]


def resumir(gastos):
    por_categoria = defaultdict(float)
    for gasto in gastos:
        por_categoria[gasto["categoria"]] += float(gasto["importe"])
    return dict(por_categoria)


def informe(resumen, titulo):
    total = sum(resumen.values())
    print(titulo)
    print("-" * 36)
    for categoria, importe in sorted(resumen.items(), key=lambda par: par[1], reverse=True):
        porcentaje = importe / total * 100
        print(f"{categoria:<12}{importe:>10.2f} € {porcentaje:>6.1f} %")
    print("-" * 36)
    print(f"{'Total':<12}{total:>10.2f} €")


parser = argparse.ArgumentParser(description="Resumen de gastos por categoría")
parser.add_argument("--archivo", default="gastos.csv", help="CSV con los gastos")
parser.add_argument("--mes", required=True, help="mes con el formato AAAA-MM, por ejemplo 2026-05")
parser.add_argument("--json", help="guardar también el resumen en este archivo JSON")
args = parser.parse_args()

try:
    gastos = del_mes(leer_gastos(args.archivo), args.mes)
except FileNotFoundError:
    parser.exit(1, f"No encuentro el archivo {args.archivo}\n")

if not gastos:
    parser.exit(1, f"No hay gastos en {args.mes}\n")

resumen = resumir(gastos)
informe(resumen, f"Gastos de {args.mes}")

if args.json:
    with open(args.json, "w", encoding="utf-8") as salida:
        json.dump({"mes": args.mes, "categorias": resumen}, salida, ensure_ascii=False, indent=2)
    print(f"Resumen guardado en {args.json}")
Al ejecutarlo
$ python gastos.py --mes 2026-05 --json resumen.json
Gastos de 2026-05
------------------------------------
Comida          130.15 €   38.1 %
Casa             78.15 €   22.9 %
Transporte       75.00 €   21.9 %
Ocio             58.50 €   17.1 %
------------------------------------
Total           341.80 €
Resumen guardado en resumen.json

Retos para seguir

Has terminado el proyecto. Si quieres ir más allá, prueba a añadirle esto por tu cuenta:

  1. Añade una opción --desde y --hasta para elegir un rango de fechas.
  2. Dibuja un gráfico de barras de las categorías con la biblioteca matplotlib.
  3. Compara el mes elegido con el anterior y muestra en qué categorías has gastado más.

Conceptos clave

Repasa la ruta de Python o elige otro proyecto.