Ordenando el Caos con CSV - De archivos de texto a datos estructurados
Introducir el concepto de datos estructurados utilizando archivos CSV (valores separados por comas), entendiendo cómo las filas y columnas de un archivo plano son los cimientos de las tablas en una base de datos relacional, desarrollando habilidades técnicas para manipular información de manera organizada, pensamiento lógico para comprender la estructura de datos, y actitudes de precisión y orden que permitan transformar información caótica en conocimiento accesible y útil para la toma de decisiones en contextos reales.
Imagina que eres el dueño de una pequeña tienda de barrio en Ecuador. Cada mañana, cuando abres las puertas, te enfrentas a un desafío: ¿cuántos productos tienes? ¿cuáles se están agotando? ¿cuánto dinero tienes invertido en el inventario?
Al principio, anotabas todo en cuadernos: "Arroz 50 bolsas", "Frijol 30 libras", "Aceite 20 botellas"... Pero con el tiempo, esos cuadernos se acumularon. Perdiste la cuenta de cuántas latas de atún quedan. No sabes si el precio del azúcar cambió. Y lo peor: un cliente te pide café y no recuerdas si te queda o no.
El caos se apoderó de tu inventario.
Pero hoy es diferente. Hoy vas a descubrir cómo organizar toda esa información de manera que puedas encontrar lo que necesitas en segundos. Hoy vas a aprender a hablar el lenguaje que usan las grandes empresas, las bases de datos, pero empezando por algo simple, accesible, poderoso: los archivos CSV.
¿Estás listo para ordenar el caos?
En los capítulos anteriores trabajamos con archivos de texto simples. Cada línea era independiente, y nosotros decidíamos cómo separar la información (con comas, espacios, etc.). Pero hay una forma estándar y universal de organizar datos en filas y columnas: los archivos CSV.
CSV significa Comma-Separated Values (Valores Separados por Comas). Es un archivo de texto donde:
De Excel a CSV: la misma información, diferente formato
Imagina una hoja de cálculo de Excel:
| Producto | Precio | Cantidad |
|---|---|---|
| Arroz 1kg | $1.25 | 50 |
| Frijol 1kg | $1.80 | 30 |
| Aceite 1L | $2.50 | 20 |
Si guardas esto como CSV, se ve así:
Producto,Precio,Cantidad
Arroz 1kg,1.25,50
Frijol 1kg,1.80,30
Aceite 1L,2.50,20
Python tiene un módulo integrado llamado csv que hace todo el trabajo pesado por ti. No necesitas separar manualmente con split(",").
import csv
import csv
with open("productos.csv", "r") as archivo:
lector = csv.reader(archivo)
for fila in lector:
print(fila)
Salida:
['Producto', 'Precio', 'Cantidad']
['Arroz 1kg', '1.25', '50']
['Frijol 1kg', '1.80', '30']
['Aceite 1L', '2.50', '20']
import csv
with open("productos.csv", "r") as archivo:
lector = csv.DictReader(archivo)
for fila in lector:
print(fila)
Salida:
{'Producto': 'Arroz 1kg', 'Precio': '1.25', 'Cantidad': '50'}
{'Producto': 'Frijol 1kg', 'Precio': '1.80', 'Cantidad': '30'}
{'Producto': 'Aceite 1L', 'Precio': '2.50', 'Cantidad': '20'}
import csv
with open("productos.csv", "r") as archivo:
lector = csv.DictReader(archivo)
for producto in lector:
nombre = producto['Producto']
precio = float(producto['Precio']) # Convertir a número
cantidad = int(producto['Cantidad']) # Convertir a entero
valor_total = precio * cantidad
print(f"{nombre}: ${valor_total:.2f}")
Salida:
Arroz 1kg: $62.50
Frijol 1kg: $54.00
Aceite 1L: $50.00
import csv
datos = [
['Pan', 0.50, 100],
['Leche 1L', 1.20, 40],
['Huevos (docena)', 2.80, 25]
]
with open("nuevos_productos.csv", "w", newline='') as archivo:
escritor = csv.writer(archivo)
# Escribir encabezado
escritor.writerow(['Producto', 'Precio', 'Cantidad'])
# Escribir datos
for fila in datos:
escritor.writerow(fila)
import csv
productos = [
{'Producto': 'Pan', 'Precio': 0.50, 'Cantidad': 100},
{'Producto': 'Leche 1L', 'Precio': 1.20, 'Cantidad': 40},
{'Producto': 'Huevos (docena)', 'Precio': 2.80, 'Cantidad': 25}
]
with open("nuevos_productos.csv", "w", newline='') as archivo:
# Definir los nombres de las columnas
campos = ['Producto', 'Precio', 'Cantidad']
escritor = csv.DictWriter(archivo, fieldnames=campos)
# Escribir encabezado
escritor.writeheader()
# Escribir cada producto
for producto in productos:
escritor.writerow(producto)
Vamos a construir un sistema completo de inventario para una tienda de barrio en Ecuador. El sistema permitirá:
Crea un archivo llamado inventario.csv con este contenido:
Producto,Precio,Cantidad
Arroz 1kg,1.25,50
Frijol 1kg,1.80,30
Aceite 1L,2.50,20
Azúcar 1kg,1.10,45
Sal 1kg,0.60,60
Atún (lata),1.50,35
Gaseosa 1L,1.00,40
Pan (unidad),0.25,100
Leche 1L,1.20,38
Huevos (docena),2.80,25
Crea un archivo llamado inventario.py:
# inventario.py - Sistema de Inventario para Tiendita del Barrio
import csv
import os
ARCHIVO_INVENTARIO = "inventario.csv"
STOCK_MINIMO = 10 # Alertar cuando haya menos de 10 unidades
def cargar_inventario():
"""Lee el inventario desde el archivo CSV"""
inventario = []
try:
with open(ARCHIVO_INVENTARIO, "r", newline='') as archivo:
lector = csv.DictReader(archivo)
for fila in lector:
# Convertir strings a números
fila['Precio'] = float(fila['Precio'])
fila['Cantidad'] = int(fila['Cantidad'])
inventario.append(fila)
except FileNotFoundError:
print("⚠️ No se encontró el archivo de inventario. Se creará uno nuevo.")
return inventario
def guardar_inventario(inventario):
"""Guarda el inventario en el archivo CSV"""
campos = ['Producto', 'Precio', 'Cantidad']
with open(ARCHIVO_INVENTARIO, "w", newline='') as archivo:
escritor = csv.DictWriter(archivo, fieldnames=campos)
escritor.writeheader()
escritor.writerows(inventario)
def mostrar_inventario(inventario):
"""Muestra todos los productos en formato de tabla"""
if not inventario:
print("\n📦 El inventario está vacío.")
return
print("\n" + "=" * 60)
print(f"{'PRODUCTO':<25} {'PRECIO':>10} {'CANTIDAD':>10} {'VALOR TOTAL':>12}")
print("=" * 60)
for producto in inventario:
valor = producto['Precio'] * producto['Cantidad']
print(f"{producto['Producto']:<25} ${producto['Precio']:>8.2f} {producto['Cantidad']:>10} ${valor:>10.2f}")
print("=" * 60)
def agregar_producto(inventario):
"""Agrega un nuevo producto al inventario"""
print("\n Agregar nuevo producto")
nombre = input("Nombre del producto: ").strip()
# Verificar si ya existe
for producto in inventario:
if producto['Producto'].lower() == nombre.lower():
print(f" El producto '{nombre}' ya existe. Usa la opción de actualizar.")
return
try:
precio = float(input("Precio: $"))
cantidad = int(input("Cantidad: "))
nuevo_producto = {
'Producto': nombre,
'Precio': precio,
'Cantidad': cantidad
}
inventario.append(nuevo_producto)
guardar_inventario(inventario)
print(f"✅ Producto '{nombre}' agregado correctamente.")
except ValueError:
print("❌ Error: Precio debe ser un número decimal y cantidad un número entero.")
def actualizar_precio(inventario):
"""Actualiza el precio de un producto existente"""
print("\n💲 Actualizar precio")
nombre = input("Nombre del producto: ").strip()
for producto in inventario:
if producto['Producto'].lower() == nombre.lower():
print(f"Producto actual: {producto['Producto']} - ${producto['Precio']:.2f}")
try:
nuevo_precio = float(input("Nuevo precio: $"))
producto['Precio'] = nuevo_precio
guardar_inventario(inventario)
print(f"✅ Precio actualizado correctamente.")
except ValueError:
print("❌ Error: El precio debe ser un número.")
return
print(f"❌ No se encontró el producto '{nombre}'.")
def calcular_valor_total(inventario):
"""Calcula y muestra el valor total del inventario"""
if not inventario:
print("\n📦 El inventario está vacío.")
return
total = sum(p['Precio'] * p['Cantidad'] for p in inventario)
print(f"\n💰 Valor total del inventario: ${total:.2f}")
def alertas_stock_bajo(inventario):
"""Muestra productos con stock bajo"""
print("\n⚠️ Productos con stock bajo (menos de 10 unidades):")
encontrados = False
for producto in inventario:
if producto['Cantidad'] < STOCK_MINIMO:
print(f" - {producto['Producto']}: {producto['Cantidad']} unidades")
encontrados = True
if not encontrados:
print(" ✅ Todos los productos tienen stock suficiente.")
def menu_principal():
"""Menú principal del sistema"""
inventario = cargar_inventario()
while True:
print("\n" + "=" * 60)
print("🏪 SISTEMA DE INVENTARIO - TIENDITA DEL BARRIO 🏪")
print("=" * 60)
print("1. Ver inventario completo")
print("2. Agregar nuevo producto")
print("3. Actualizar precio")
print("4. Calcular valor total")
print("5. Alertas de stock bajo")
print("6. Salir")
opcion = input("\nElige una opción (1-6): ").strip()
if opcion == "1":
mostrar_inventario(inventario)
elif opcion == "2":
agregar_producto(inventario)
elif opcion == "3":
actualizar_precio(inventario)
elif opcion == "4":
calcular_valor_total(inventario)
elif opcion == "5":
alertas_stock_bajo(inventario)
elif opcion == "6":
print("\n👋 ¡Hasta pronto! Gracias por usar el sistema.")
break
else:
print("❌ Opción no válida. Intenta de nuevo.")
# Ejecutar el programa
if __name__ == "__main__":
menu_principal()
Ejecuta el programa:
python inventario.py
Prueba todas las opciones:
fila['Precio'] = float(fila['Precio'])
fila['Cantidad'] = int(fila['Cantidad'])
escritor.writerow(producto) # Escribe UNA fila
escritor.writerows(inventario) # Escribe TODAS las filas de una lista
with open(ARCHIVO_INVENTARIO, "w", newline='') as archivo:
Vamos a agregar una función más: registrar una venta (reducir la cantidad de un producto).
def registrar_venta(inventario):
"""Registra una venta reduciendo la cantidad del producto"""
print("\n🛒 Registrar venta")
nombre = input("Nombre del producto vendido: ").strip()
for producto in inventario:
if producto['Producto'].lower() == nombre.lower():
print(f"Stock actual: {producto['Cantidad']} unidades")
try:
cantidad_vendida = int(input("Cantidad vendida: "))
if cantidad_vendida > producto['Cantidad']:
print(f"❌ No hay suficiente stock. Solo hay {producto['Cantidad']} unidades.")
return
producto['Cantidad'] -= cantidad_vendida
guardar_inventario(inventario)
print(f"✅ Venta registrada. Nuevo stock: {producto['Cantidad']} unidades")
# Alerta si el stock quedó bajo
if producto['Cantidad'] < STOCK_MINIMO:
print(f"⚠️ ALERTA: Stock bajo de {producto['Producto']}")
except ValueError:
print("❌ Error: La cantidad debe ser un número entero.")
return
print(f"❌ No se encontró el producto '{nombre}'.")
Agrega esta función al menú:
print("7. Registrar venta")
# ... en el if/elif:
elif opcion == "7":
registrar_venta(inventario)
Los archivos CSV pueden tener problemas. Veamos los más comunes:
def cargar_inventario_seguro():
inventario = []
try:
with open(ARCHIVO_INVENTARIO, "r", newline='') as archivo:
lector = csv.DictReader(archivo)
# Verificar que haya encabezado
if lector.fieldnames is None:
print("⚠️ El archivo está vacío o no tiene encabezado.")
return inventario
for fila in lector:
try:
fila['Precio'] = float(fila['Precio'])
fila['Cantidad'] = int(fila['Cantidad'])
inventario.append(fila)
except (ValueError, KeyError) as e:
print(f"⚠️ Fila con datos inválidos: {fila}. Error: {e}")
except FileNotFoundError:
print("⚠️ No se encontró el archivo de inventario.")
return inventario
Si un producto se llama "Arroz, premium 1kg", la coma causará problemas. Solución: el módulo csv maneja esto automáticamente si usas comillas:
Producto,Precio,Cantidad
"Arroz, premium 1kg",1.50,30
Ahora que tienes un sistema de inventario funcional, elige el reto que más te motive. Cada camino desarrolla diferentes habilidades y te permite expresarte de manera única.
Para los creativos
Agrega una función que genere un gráfico de barras ASCII mostrando la cantidad de cada producto:
Para los analíticos
Agrega funciones avanzadas de análisis:
Para los emprendedores
Transforma el sistema para un negocio real de tu comunidad:
Adapta los campos (agrega "Proveedor", "Fecha de vencimiento", "Código de barras", etc.).
Para los empáticos
Haz el sistema más amigable para usuarios no técnicos:
Lo que acabas de construir es exactamente lo que hace una base de datos, pero en un archivo plano:
| Concepto en CSV | Equivalente en Base de Datos |
|---|---|
| Archivo inventario.csv | Tabla inventario |
| Encabezado (Producto, Precio, Cantidad) | Columnas / Campos |
| Cada fila de datos | Registro / Fila |
| DictReader | Consulta SELECT |
| DictWriter | Operación INSERT / UPDATE |
Antes de pasar al Capítulo 4, pregúntate:
En el Capítulo 4 daremos un salto hacia el formato que usan todas las aplicaciones web modernas: JSON. Aprenderás a guardar datos complejos y anidados (diccionarios dentro de diccionarios, listas dentro de diccionarios) que son imposibles de representar en CSV.
Construiremos el sistema de guardado de partidas para un juego o el perfil de usuarios para una app de ecoturismo en Galápagos. Descubrirás por qué JSON es el "idioma universal" de las APIs y la configuración de aplicaciones.
📝 Tarea opcional antes de seguir: Piensa en qué datos complejos necesitarías guardar si fueras a crear un perfil de usuario para una red social (nombre, edad, lista de amigos, publicaciones favoritas, configuración de privacidad, etc.). En el próximo capítulo verás cómo JSON maneja esto elegantemente.
Demuestra lo que has aprendido