Informe de ventas a partir de un CSV
Lee las ventas de una tienda en formato CSV, descarta las líneas incorrectas y saca un informe: total, ventas y porcentaje por categoría, producto más vendido, mejor día y un gráfico de barras por mes. Diccionarios, cálculos en céntimos y formato de importes en español.
- Diccionarios
- Funciones
- try/except
- sorted y min con key
- Formato de números
- Céntimos en lugar de decimales
Enunciado
Una papelería exporta las ventas de su caja registradora a un fichero CSV, con una línea por venta: fecha, producto, categoría, unidades y precio unitario. El dueño quiere un informe rápido sin abrir una hoja de cálculo.
El fichero lo genera una caja antigua y a veces trae líneas rotas: columnas que faltan, unidades escritas con letras o negativas. Esas líneas no deben estropear el informe: se descartan y se dice cuáles eran.
Para que los totales cuadren al céntimo, el informe se calcula en céntimos (números enteros) y solo se convierte a euros al escribirlo. Sumar precios con decimales como 0.1 + 0.2 en coma flotante da errores pequeños que, con muchas ventas, acaban notándose.
Qué tiene que hacer el programa
- La primera línea es la cabecera
fecha,producto,categoria,unidades,precioy no se procesa. Las líneas vacías se ignoran sin más. - Una línea es válida si tiene exactamente 5 campos separados por comas, las unidades son un entero mayor que 0 y el precio es un número no negativo. El importe de la línea es unidades × precio. Las fechas tienen el formato
AAAA-MM-DD. - Escribe
Ventas totales: X €y, bajoPor categoría:, una línea por categoría de mayor a menor importe (a igualdad, alfabético): dos espacios, el nombre en 16 caracteres a la izquierda, el importe en 12 caracteres a la derecha, dos espacios y el porcentaje sobre el total con un decimal en 5 caracteres a la derecha, seguido de%. Producto más vendido: producto (N unidades): el de más unidades vendidas en total (a igualdad, el primero alfabéticamente; «unidad» en singular si es 1).Mejor día: fecha (X €): el día con más importe (a igualdad, el más antiguo).- Bajo
Por mes:, una línea por mes en orden cronológico: dos espacios, el mes (AAAA-MM), dos espacios, el importe en 12 caracteres a la derecha, un espacio y una almohadilla#por cada 50 € completos (sin espacios al final de la línea). - Por último,
Líneas descartadas: Ny, si hay alguna, sus números de línea entre paréntesis separados por coma y espacio (la cabecera es la línea 1). - Los importes se escriben con punto de millar, coma decimal, dos decimales y el símbolo
€(por ejemplo1.234,50 €). Si no hay ninguna venta válida, escribe soloNo hay ventas válidas.
Entrada
Un fichero CSV: la cabecera y una venta por línea, con los campos separados por comas y el precio con punto decimal.
Ejemplos de ejecución
Tu programa debe escribir exactamente esta salida para estas entradas. Las pruebas del editor incluyen estos ejemplos y otros casos ocultos.
Una semana de ventas
Entrada
fecha,producto,categoria,unidades,precio 2026-08-28,Cuaderno A4,Papelería,10,2.50 2026-08-28,Ratón inalámbrico,Informática,2,18.90 2026-08-31,Bolígrafo azul,Papelería,25,0.80 2026-09-01,Cuaderno A4,Papelería,15,2.50 2026-09-01,Memoria USB 64 GB,Informática,4,12.00 2026-09-02,Mochila,Complementos,3,34.95 2026-09-02,Bolígrafo azul,Papelería,40,0.80 2026-09-03,Ratón inalámbrico,Informática,1,18.90
Salida por consola
Ventas totales: 324,05 € Por categoría: Papelería 114,50 € 35,3 % Complementos 104,85 € 32,4 % Informática 104,70 € 32,3 % Producto más vendido: Bolígrafo azul (65 unidades) Mejor día: 2026-09-02 (136,85 €) Por mes: 2026-08 82,80 € # 2026-09 241,25 € #### Líneas descartadas: 0
Con líneas rotas
Entrada
fecha,producto,categoria,unidades,precio 2026-09-10,Cuaderno A4,Papelería,4,2.50 2026-09-10,Grapadora,Papelería,dos,6.40 2026-09-11,Auriculares,Informática,1,29.99 2026-09-11,Funda portátil,Complementos,1 2026-09-12,Bolígrafo azul,Papelería,-3,0.80 2026-09-12,Bolígrafo azul,Papelería,12,0.80 2026-09-13,Auriculares,Informática,1,29.99
Salida por consola
Ventas totales: 79,58 € Por categoría: Informática 59,98 € 75,4 % Papelería 19,60 € 24,6 % Producto más vendido: Bolígrafo azul (12 unidades) Mejor día: 2026-09-11 (29,99 €) Por mes: 2026-09 79,58 € # Líneas descartadas: 3 (3, 5, 6)
Guía paso a paso
Intenta resolverlo por tu cuenta y abre un paso solo cuando te atasques: cada uno te acerca a la solución sin dártela entera.
1. Una función para cada línea
Escribe leer_venta(linea) que devuelva una tupla con los datos ya convertidos o None si la línea no vale. Así el bucle principal queda limpio y la validación se prueba por separado. int() y float() lanzan ValueError si el texto no es un número: captúralo con try/except.
2. Trabaja en céntimos
Convierte el precio a céntimos al leerlo y opera siempre con enteros. Solo al escribir divides entre 100.
precio_cent = round(float(precio) * 100)
importe = unidades * precio_cent3. Un diccionario por cada agrupación
Usa cuatro diccionarios: importe por categoría, unidades por producto, importe por día e importe por mes (el mes son los 7 primeros caracteres de la fecha). d.get(clave, 0) + valor suma sin tener que comprobar si la clave existe.
4. Ordenar y elegir con empates
sorted(d.items(), key=lambda c: (-c[1], c[0])) ordena de mayor a menor importe y, a igualdad, por nombre. Para quedarte solo con el mejor, min con esa misma clave te da el primero de esa ordenación.
5. Formatear importes en español
Python escribe 1,234.50 con f"{x:,.2f}"; para el formato español hay que intercambiar comas y puntos. Hazlo en tres pasos con un carácter intermedio para que un reemplazo no deshaga el otro.
texto = f"{centimos / 100:,.2f}"
texto.replace(",", "X").replace(".", ",").replace("X", ".")Resuélvelo aquí
El editor trae el esqueleto del programa. Pulsa «Ejecutar» para comprobarlo con los ejemplos y con 2 casos ocultos que buscan los errores típicos.
Ejemplo
fecha,producto,categoria,unidades,precio 2026-08-28,Cuaderno A4,Papelería,10,2.50 2026-08-28,Ratón inalámbrico,Informática,2,18.90 2026-08-31,Bolígrafo azul,Papelería,25,0.80 2026-09-01,Cuaderno A4,Papelería,15,2.50 2026-09-01,Memoria USB 64 GB,Informática,4,12.00 2026-09-02,Mochila,Complementos,3,34.95 2026-09-02,Bolígrafo azul,Papelería,40,0.80 2026-09-03,Ratón inalámbrico,Informática,1,18.90
Ventas totales: 324,05 € Por categoría: Papelería 114,50 € 35,3 % Complementos 104,85 € 32,4 % Informática 104,70 € 32,3 % Producto más vendido: Bolígrafo azul (65 unidades) Mejor día: 2026-09-02 (136,85 €) Por mes: 2026-08 82,80 € # 2026-09 241,25 € #### Líneas descartadas: 0
Solución explicada
Ver la solución completa
1import sys
2
3
4def euros(centimos):
5 """1234.5 € → «1.234,50 €»: puntos de millar y coma decimal."""
6 texto = f"{centimos / 100:,.2f}"
7 return texto.replace(",", "X").replace(".", ",").replace("X", ".") + " €"
8
9
10def leer_venta(linea):
11 """Devuelve (fecha, producto, categoría, unidades, importe en céntimos) o None si la línea no es válida."""
12 partes = [p.strip() for p in linea.split(",")]
13 if len(partes) != 5:
14 return None
15 fecha, producto, categoria, unidades, precio = partes
16 try:
17 unidades = int(unidades)
18 precio_cent = round(float(precio) * 100)
19 except ValueError:
20 return None
21 if unidades <= 0 or precio_cent < 0:
22 return None
23 return fecha, producto, categoria, unidades, unidades * precio_cent
24
25
26def main():
27 lineas = sys.stdin.read().split("\n")
28 por_categoria, por_producto, por_dia, por_mes = {}, {}, {}, {}
29 total = 0
30 descartadas = []
31 for num, linea in enumerate(lineas[1:], start=2):
32 if not linea.strip():
33 continue
34 venta = leer_venta(linea)
35 if venta is None:
36 descartadas.append(num)
37 continue
38 fecha, producto, categoria, unidades, importe = venta
39 total += importe
40 por_categoria[categoria] = por_categoria.get(categoria, 0) + importe
41 por_producto[producto] = por_producto.get(producto, 0) + unidades
42 por_dia[fecha] = por_dia.get(fecha, 0) + importe
43 mes = fecha[:7]
44 por_mes[mes] = por_mes.get(mes, 0) + importe
45
46 if total == 0 and not por_producto:
47 print("No hay ventas válidas.")
48 return
49
50 print(f"Ventas totales: {euros(total)}")
51 print("Por categoría:")
52 for categoria, importe in sorted(por_categoria.items(), key=lambda c: (-c[1], c[0])):
53 porcentaje = f"{100 * importe / total:.1f}".replace(".", ",")
54 print(f" {categoria:<16}{euros(importe):>12} {porcentaje:>5} %")
55
56 producto, unidades = min(por_producto.items(), key=lambda p: (-p[1], p[0]))
57 print(f"Producto más vendido: {producto} ({unidades} {'unidad' if unidades == 1 else 'unidades'})")
58 dia, importe = min(por_dia.items(), key=lambda d: (-d[1], d[0]))
59 print(f"Mejor día: {dia} ({euros(importe)})")
60
61 print("Por mes:")
62 for mes in sorted(por_mes):
63 barra = "#" * (por_mes[mes] // 5000) # una almohadilla por cada 50 €
64 print(f" {mes} {euros(por_mes[mes]):>12} {barra}".rstrip())
65
66 lista = f" ({', '.join(map(str, descartadas))})" if descartadas else ""
67 print(f"Líneas descartadas: {len(descartadas)}{lista}")
68
69
70main()La validación vive en leer_venta, que convierte los campos y devuelve None ante cualquier problema. El bucle principal solo decide qué hacer con el resultado: descartar la línea o acumularla.
Todo se acumula en céntimos enteros. Así 0,80 × 40 es exactamente 3200 céntimos y las sumas no arrastran errores de coma flotante; la conversión a euros ocurre una sola vez, en euros.
Cada pregunta del informe es un diccionario: por categoría, por producto, por día y por mes. Una sola pasada por las ventas basta para rellenarlos todos.
Los empates se resuelven con claves de ordenación en forma de tupla: el primer elemento es el importe en negativo (para ir de mayor a menor) y el segundo el nombre o la fecha. min con esa clave devuelve directamente el ganador.
Para ir más allá
- Usa el módulo
csvpara admitir productos con comas en el nombre entre comillas. - Añade las ventas por día de la semana (
datetime.date.fromisoformat(fecha).weekday()). - Escribe el informe en un fichero HTML con una tabla y barras dibujadas con CSS.