Cómo validar un CSV financiero con Python
ملخص
En Pixel, este anuncio explica cómo validar una exportación CSV antes de crear un resumen financiero mensual por empresa. Detalla el formato esperado, el uso de Decimal, la detección de documentos duplicados y por qué un importe vacío debe bloquear el proceso en lugar de tratarse como cero.
DEV Community
Validar un CSV antes del reporting financiero: Decimal, duplicados y datos pendientes
Nota de edición: artículo y código generados con IA para el equipo de Numio. El ejemplo se ha ejecutado localmente con datos ficticios. El recurso enlazado al final pertenece al blog de Numio. Un informe puede estar bien diseñado y sumar documentos duplicados. También puede mostrar cero donde en realidad falta un dato. Antes de construir un gráfico, conviene establecer qué archivo se admite, qué controles se aplican y qué pasa si alguno falla. Este ejemplo en Python valida una exportación pequeña antes de calcular un resumen mensual por empresa. No se conecta a un ERP, no modifica la contabilidad y no envía información a servicios externos. El contrato del archivo Trabajaremos con cinco columnas, separadas por punto y coma, en este orden: empresa;documento;fecha;moneda;importe. El archivo es UTF-8. Cada fila representa un documento ya agregado, no una línea de asiento ni una partida de factura. La clave (empresa, documento) debe ser única dentro de esta exportación. Las fechas tienen formato YYYY-MM-DD. Los importes usan punto decimal, exactamente dos decimales y ningún separador de miles. Una exportación con coma decimal necesita una conversión explícita y revisada antes de este paso; el script la rechaza en vez de adivinar su significado. empresa;documento;fecha;moneda;importe
cliente_demo_A;F001;2026-10-01;EUR;0.10
cliente_demo_A;F002;2026-10-02;EUR;0.20
cliente_demo_A;F003;2026-10-03;EUR;0.00
cliente_demo_B;F001;2026-10-01;EUR;5.00
F001 puede existir en dos empresas diferentes. Un cero explícito es válido. Un importe vacío bloquea la entrega: no significa cero. Los identificadores son ficticios y sirven únicamente para explicar el formato. Validar antes de devolver un total """Ejemplo educativo: validar un CSV de documentos antes de resumirlo."""
import csv
import re
from collections import defaultdict
from datetime import date
from decimal import Decimal, localcontext
FIELDS = ["empresa", "documento", "fecha", "moneda", "importe"]
def resumir(stream):
reader = csv.DictReader(stream, delimiter=";", strict=True)
try:
fieldnames = reader.fieldnames
except csv.Error as exc:
raise ValueError("Cabecera CSV no valida") from exc
if fieldnames != FIELDS:
raise ValueError("Cabecera distinta del contrato esperado")
seen = set()
totals = defaultdict(lambda: Decimal("0.00"))
errors = []
rows = 0
# Limites del ejemplo: 10 000 documentos, 18 digitos enteros por importe.
with localcontext() as ctx:
ctx.prec = 28
try:
for row in reader:
rows += 1
line = reader.line_num
if rows > 10_000:
raise ValueError("El ejemplo admite hasta 10 000 documentos")
try:
if None in row or any(v is None for v in row.values()):
raise ValueError("Numero de columnas incorrecto")
row = {k: v.strip() for k, v in row.items()}
if not all(row.values()):
raise ValueError("Dato pendiente o identificador vacio")
if not re.fullmatch(r"[0-9]{4}-[0-9]{2}-[0-9]{2}", row["fecha"]):
raise ValueError("Fecha fuera del formato YYYY-MM-DD")
day = date.fromisoformat(row["fecha"])
if row["moneda"] != "EUR":
raise ValueError("Este ejemplo solo agrega EUR")
if not re.fullmatch(r"-?[0-9]{1,18}\.[0-9]{2}", row["importe"]):
raise ValueError("Importe: punto decimal y dos decimales")
key = (row["empresa"], row["documento"])
if key in seen:
raise ValueError("Documento repetido en la misma empresa")
seen.add(key)
totals[(row["empresa"], day.strftime("%Y-%m"))] += Decimal(row["importe"])
except ValueError as exc:
err...

Powered by Readybot.io