Bucle Creativo

Capítulo 3: El Puente hacia las Bases de Datos

Ordenando el Caos con CSV - De archivos de texto a datos estructurados

🎯 Objetivo de Aprendizaje

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.

📖 Activación: La Tiendita del Barrio

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?

📊 Desarrollo: Del Texto Plano a los Datos Estructurados

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.

¿Qué es un CSV?

CSV significa Comma-Separated Values (Valores Separados por Comas). Es un archivo de texto donde:

  • Cada línea es una fila (un registro)
  • Los datos dentro de cada fila están separados por comas (columnas)
  • La primera línea suele ser el encabezado (nombres de las columnas)
Hoja de cálculo y CSV

De Excel a CSV: la misma información, diferente formato

La analogía de la hoja de cálculo 📋

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í:

productos.csv
Producto,Precio,Cantidad
Arroz 1kg,1.25,50
Frijol 1kg,1.80,30
Aceite 1L,2.50,20
¡Exactamente igual, pero en texto plano! Cada coma es como el borde entre celdas.
Conexión con bases de datos: Esto es exactamente cómo se ven las tablas en una base de datos relacional. Cada fila es un registro, cada columna es un campo. Si dominas CSV, dominas la estructura básica de cualquier base de datos.

El módulo csv: Tu mejor amigo

Python tiene un módulo integrado llamado csv que hace todo el trabajo pesado por ti. No necesitas separar manualmente con split(",").

📦 Importar el módulo
import csv

Leyendo archivos CSV

Método 1: csv.reader() — Listas de datos

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']
Cada fila es una lista de strings. Simple, pero tienes que recordar qué índice corresponde a qué columna.

Método 2: csv.DictReader() — Diccionarios con nombres (¡RECOMENDADO!)

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'}
Cada fila es un diccionario donde las claves son los nombres de las columnas. Mucho más claro y fácil de usar.

Accediendo a datos específicos

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
Tip importante: Los datos leídos de CSV siempre son strings. Si necesitas hacer cálculos, debes convertirlos a float o int.

✍️ Escribiendo archivos CSV

Método 1: csv.writer() — Escribir listas

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)
newline='' es importante en Windows para evitar líneas en blanco extra.

Método 2: csv.DictWriter() — Escribir diccionarios (¡RECOMENDADO!)

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)
Mucho más claro: no necesitas recordar el orden de las columnas, solo usas los nombres.

🎯 Manos a la obra: El Inventario de la Tiendita del Barrio

Vamos a construir un sistema completo de inventario para una tienda de barrio en Ecuador. El sistema permitirá:

  • Ver todos los productos
  • Agregar nuevos productos
  • Actualizar precios
  • Calcular el valor total del inventario
  • Alertar de productos con stock bajo

Paso 1: Crear el archivo CSV inicial

Crea un archivo llamado inventario.csv con este contenido:

📄 inventario.csv
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

Paso 2: El sistema completo

Crea un archivo llamado inventario.py:

🐍 inventario.py - Sistema de Inventario para Tiendita del Barrio
# 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()

Paso 3: Pruébalo

Ejecuta el programa:

python inventario.py

Prueba todas las opciones:

  1. Ver el inventario completo
  2. Agregar un producto nuevo (ej: "Café 250g", precio 3.50, cantidad 15)
  3. Actualizar el precio del arroz a 1.30
  4. Calcular el valor total
  5. Ver alertas de stock bajo

🔍 Entendiendo el código clave

csv.DictReader y conversión de tipos

fila['Precio'] = float(fila['Precio'])
fila['Cantidad'] = int(fila['Cantidad'])
CSV siempre lee todo como texto. Si quieres hacer cálculos matemáticos, debes convertir explícitamente a float o int.

writerow() vs writerows()

escritor.writerow(producto)   # Escribe UNA fila
escritor.writerows(inventario)  # Escribe TODAS las filas de una lista

newline='' al abrir para escritura

with open(ARCHIVO_INVENTARIO, "w", newline='') as archivo:
En Windows, sin newline='', el módulo csv agrega saltos de línea dobles. Esta configuración lo evita.

🔄 Actualizar cantidades (vendiendo productos)

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)

️ Manejo de errores en CSV

Los archivos CSV pueden tener problemas. Veamos los más comunes:

Archivo vacío o sin encabezado

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

Datos con comas dentro

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
El módulo csv lee esto correctamente como un solo campo.

🧩 Retos DUA: Elige tu camino

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.

Reto Visual

Para los creativos

Agrega una función que genere un gráfico de barras ASCII mostrando la cantidad de cada producto:

ARROZ 1KG ██████████████████████████████████████████████████ 50 FRIJOL 1KG ██████████████████████████████ 30 ACEITE 1L ████████████████████ 20 AZÚCAR 1KG ████████████████████████████████████████████ 45
🔢

Reto Lógico

Para los analíticos

Agrega funciones avanzadas de análisis:

  1. Producto más caro: Mostrar cuál es el producto con mayor precio
  2. Producto más vendido: (necesitarás un archivo separado de ventas)
  3. Valor por categoría: Agrupar productos por tipo (granos, lácteos, enlatados) y sumar el valor de cada categoría
🌎

Reto de Expansión

Para los emprendedores

Transforma el sistema para un negocio real de tu comunidad:

  • Farmacia: Control de medicamentos con fecha de vencimiento
  • Ferretería: Inventario de herramientas y materiales
  • Papelería: Control de útiles escolares
  • Restaurante: Inventario de ingredientes con alertas de reposición

Adapta los campos (agrega "Proveedor", "Fecha de vencimiento", "Código de barras", etc.).

🗣️

Reto de Accesibilidad

Para los empáticos

Haz el sistema más amigable para usuarios no técnicos:

  1. Agrega confirmaciones antes de cada acción importante
  2. Crea un historial de cambios (quién modificó qué y cuándo)
  3. Agrega búsqueda parcial (si escribo "arr", que encuentre "Arroz")
  4. Genera un reporte imprimible en formato bonito para mostrar al dueño

🎓 La conexión con bases de datos reales

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
La diferencia: Una base de datos real (como PostgreSQL, MySQL, SQLite) puede manejar millones de registros, múltiples usuarios al mismo tiempo, y tiene índices para buscar rápido. Pero la estructura es la misma.
Reflexión: Si puedes manejar CSV, puedes manejar cualquier base de datos. Solo cambia la sintaxis, no la lógica.

Checkpoint de aprendizaje

Antes de pasar al Capítulo 4, pregúntate:

Si marcaste todos los casilleros, ¡increíble! Ya estás pensando en estructuras de datos. Estás listo para el formato que domina la web moderna.

🚀 Vista previa del próximo capítulo

Capítulo 4: JSON - El lenguaje universal de la web

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.

🎮 Evaluación Lúdica: Pon a prueba tu conocimiento

🏆 Quiz: CSV y Bases de Datos

Demuestra lo que has aprendido

Pregunta 1 de 5