Café y Código

17. Persistencia de Datos: Lectura y Escritura de Archivos ☕

El poder de la Persistencia

Hasta ahora, cada vez que cerrabas tu programa de Python, todos los datos (productos, ventas, clientes) se borraban de la memoria RAM. Para construir aplicaciones reales, necesitas persistir la información. Es decir, guardarla en el disco duro para poder recuperarla después.

En Python, la forma más limpia y moderna de abrir y cerrar archivos es usando la instrucción with open(). Esta asegura que el archivo se cierre correctamente incluso si ocurre un error inesperado.

persistencia_basica.py
PYTHON
1 # Estructura básica para escribir (w = write)
2 with open("saludo.txt", "w", encoding="utf-8") as archivo:
3 archivo.write("¡Bienvenidos a la Cafetería Python!\n")
4
5 # Estructura básica para leer (r = read)
6 with open("saludo.txt", "r", encoding="utf-8") as archivo:
7 contenido = archivo.read()
8 print(contenido)

1. Archivos CSV con Lista de Listas 📊

Los archivos CSV (Valores Separados por Comas) son el estándar para guardar tablas de datos. En Python, los representamos de forma muy natural como una lista de listas (arreglo bidimensional): [[codigo, nombre, precio]].

💡 Tip de Productividad: El Delimitador Personalizado
Por defecto, un archivo CSV separa los campos con comas (,). Sin embargo, en sistemas en español o cuando abres datos en Excel, la coma se usa como decimal. Por eso, es muy común cambiar el delimitador a punto y coma (;) usando el parámetro delimiter.
catalogo_csv.py
PYTHON
1 import csv
2
3 # Nuestros productos de la cafetería representados como [[codigo, nombre, precio]]
4 productos = [
5 ["C001", "Café Expreso", 1500],
6 ["C002", "Capuchino Italiano", 2200],
7 ["C003", "Medialuna Dulce", 800],
8 ["C004", "Tarta de Arándanos", 2500]
9 ]
10
11 # 1. Guardar a CSV usando PUNTO Y COMA (;) como delimitador
12 with open("productos.csv", "w", newline="", encoding="utf-8") as archivo:
13 # Pasamos delimiter=";" para cambiar el delimitador
14 escritor = csv.writer(archivo, delimiter=";")
15 escritor.writerows(productos)
16 print("¡Catálogo guardado con éxito en productos.csv!")
17
18 # 2. Importar/Leer desde el archivo CSV con delimitador ";"
19 productos_importados = []
20 with open("productos.csv", "r", encoding="utf-8") as archivo:
21 lector = csv.reader(archivo, delimiter=";")
22 for fila in lector:
23 if fila: # Evitar líneas vacías
24 codigo = fila[0]
25 nombre = fila[1]
26 precio = int(fila[2]) # Convertimos a entero para poder operar
27 productos_importados.append([codigo, nombre, precio])
28
29 print("Productos cargados:", productos_importados)

2. Archivos de Texto (TXT): Generando Boletas y Reportes 🧾

Cuando un cliente compra en nuestra cafetería, debemos imprimirle un voucher o boleta de venta. Además, al finalizar la jornada laboral, nos interesa registrar un reporte de caja acumulando el total vendido.

Para esto usamos el formato de texto plano (.txt) y el modo de apertura "a" (append / añadir al final), que nos permite registrar nuevas líneas sin borrar el contenido anterior.

vouchers_txt.py
PYTHON
1 import datetime
2
3 # Generar un voucher de entrega (Boleta) para un cliente
4 def guardar_boleta(cliente, productos_comprados, total):
5 fecha_hora = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
6 nombre_archivo = f"boleta_{cliente.lower().replace(' ', '_')}.txt"
7
8 with open(nombre_archivo, "w", encoding="utf-8") as boleta:
9 boleta.write("========================================\n")
10 boleta.write(" CAFETERÍA PYTHON EXPRESS \n")
11 boleta.write("========================================\n")
12 boleta.write(f"Cliente: {cliente}\n")
13 boleta.write(f"Fecha: {fecha_hora}\n")
14 boleta.write("----------------------------------------\n")
15 for prod, cant, subtotal in productos_comprados:
16 boleta.write(f"{prod:<22} x{cant:<3} ${subtotal:>8}\n")
17 boleta.write("----------------------------------------\n")
18 boleta.write(f"TOTAL PAGADO: ${total:>8}\n")
19 boleta.write("========================================\n")
20 boleta.write(" ¡Gracias por su preferencia! \n")
21 boleta.write("========================================\n")
22 print(f"Boleta guardada como: {nombre_archivo}")
23
24 # Guardar o actualizar el reporte acumulado del día (reporte_caja.txt)
25 def registrar_en_reporte_diario(cliente, total_venta):
26 fecha_hora = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
27 # Usamos "a" para añadir datos sin sobreescribir el reporte existente
28 with open("reporte_caja.txt", "a", encoding="utf-8") as reporte:
29 reporte.write(f"[{fecha_hora}] Venta a {cliente} por un total de: ${total_venta}\n")
30
31 # Simulamos una venta
32 compra = [
33 ["Café Expreso", 2, 3000],
34 ["Medialuna Dulce", 3, 2400]
35 ]
36 guardar_boleta("Benjamín Torres", compra, 5400)
37 registrar_en_reporte_diario("Benjamín Torres", 5400)

3. Estructuras de Diccionario y JSON 📦

Aunque el formato CSV es genial para listas sencillas, cuando queremos almacenar estructuras de datos más complejas (por ejemplo, productos que además tienen categorías, niveles de stock, descripción o propiedades anidadas), la mezcla de Diccionarios de Python y JSON es la mejor opción.

inventario_json.py
PYTHON
1 import json
2
3 # Estructura del menú con stock y categoría utilizando diccionarios
4 inventario_completo = {
5 "C001": {"nombre": "Café Expreso", "precio": 1500, "stock": 50, "categoria": "Bebidas"},
6 "C002": {"nombre": "Capuchino Italiano", "precio": 2200, "stock": 30, "categoria": "Bebidas"},
7 "C003": {"nombre": "Medialuna Dulce", "precio": 800, "stock": 100, "categoria": "Pastelería"}
8 }
9
10 # 1. Guardar diccionario a JSON (Escribir con identación legible)
11 with open("inventario.json", "w", encoding="utf-8") as archivo_json:
12 # indent=4 le da un formato visual ordenado y estructurado
13 # ensure_ascii=False respeta los acentos y caracteres del español
14 json.dump(inventario_completo, archivo_json, indent=4, ensure_ascii=False)
15 print("Inventario JSON guardado.")
16
17 # 2. Cargar/Importar diccionario desde JSON
18 with open("inventario.json", "r", encoding="utf-8") as archivo_json:
19 datos_cargados = json.load(archivo_json)
20
21 # Acceder de forma estructurada
22 print("El precio del Capuchino es:", datos_cargados["C002"]["precio"])

☕ Mini Desafío: Cambiar Delimitadores

Crea un pequeño script que lea un archivo llamado pedidos.txt que usa la barra vertical (|) como delimitador de datos en formato: cliente|producto|cantidad|precio_total. Haz que tu programa cargue la información de cada línea e imprima un resumen ordenado en la consola.

👁️ Ver solución recomendada
delimitador_personalizado.py
PYTHON
1 # Creamos un archivo de prueba con delimitador personalizado '|'
2 with open("pedidos.txt", "w", encoding="utf-8") as f:
3 f.write("Daniela|Café Cortado|2|3600\n")
4 f.write("Andrés|Té Verde|1|1500\n")
5 f.write("Sofía|Tarta de Manzana|2|4400\n")
6
7 # Leemos y procesamos usando split('|')
8 with open("pedidos.txt", "r", encoding="utf-8") as f:
9 for linea in f:
10 linea = linea.strip()
11 if not linea:
12 continue
13 # Separamos usando nuestro delimitador vertical
14 cliente, producto, cant, total = linea.split("|")
15 print(f"El cliente {cliente} pidió {cant}x '{producto}' pagando ${total} en total.")

4. Proyecto Integrador Final: Cafetería Express ☕🚀

Este proyecto une los tres tipos de persistencia estudiados. Permite gestionar una cafetería real con las siguientes funciones:

  • Importar Catálogo (CSV): Carga los productos usando una lista de listas [[codigo, nombre, precio]] y delimitador ;.
  • Vender Productos: Pide código y cantidad, calcula el total, genera un archivo TXT con el voucher de la boleta y actualiza un reporte general de ventas.
  • Exportar Estado (JSON + Diccionario): Guarda todas las estadísticas finales del negocio y los productos con su stock restante estructurados en un archivo JSON.
cafeteria_completa.py
PYTHON
1 import csv
2 import json
3 import datetime
4 import os
5
6 # Base de datos en memoria (Lista de listas para productos)
7 # Estructura: [codigo, nombre, precio, stock]
8 catalogo = []
9
10 # Cargar catálogo de ejemplo en CSV si no existe
11 def inicializar_csv_si_no_existe():
12 if not os.path.exists("catalogo_inicial.csv"):
13 datos = [
14 ["C01", "Café Americano", 1200, 100],
15 ["C02", "Café Latte", 1800, 80],
16 ["C03", "Muffin de Chocolate", 1100, 50],
17 ["C04", "Jugo de Naranja Natural", 2000, 30]
18 ]
19 with open("catalogo_inicial.csv", "w", newline="", encoding="utf-8") as f:
20 escritor = csv.writer(f, delimiter=";")
21 escritor.writerows(datos)
22 print("-> Archivo 'catalogo_inicial.csv' de muestra creado.")
23
24 # 1. IMPORTAR PRODUCTOS DESDE CSV
25 def importar_catalogo_csv():
26 global catalogo
27 catalogo = []
28 try:
29 with open("catalogo_inicial.csv", "r", encoding="utf-8") as f:
30 lector = csv.reader(f, delimiter=";")
31 for fila in lector:
32 if fila:
33 codigo = fila[0]
34 nombre = fila[1]
35 precio = int(fila[2])
36 stock = int(fila[3])
37 catalogo.append([codigo, nombre, precio, stock])
38 print(f"✔ Catálogo importado correctamente. {len(catalogo)} productos cargados.")
39 except FileNotFoundError:
40 print("❌ Error: No se encontró 'catalogo_inicial.csv'. Por favor, inicialízalo primero.")
41
42 # 2. VENDER Y EMITIR BOLETA TXT
43 def realizar_venta():
44 if not catalogo:
45 print("⚠ Primero debes importar el catálogo (Opción 1).")
46 return
47
48 print("\n--- MENÚ DE PRODUCTOS ---")
49 for prod in catalogo:
50 print(f"Código: {prod[0]} | {prod[1]:<22} | Precio: ${prod[2]:>5} | Stock: {prod[3]}")
51
52 cliente = input("\nNombre del cliente: ").strip()
53 if not cliente:
54 print("❌ El nombre del cliente no puede estar vacío.")
55 return
56
57 productos_vendidos = []
58 total_venta = 0
59
60 while True:
61 codigo_buscado = input("Escribe el código del producto a comprar (o 'fin' para cobrar): ").strip().upper()
62 if codigo_buscado == "FIN":
63 break
64
65 # Buscar producto
66 producto_encontrado = None
67 for prod in catalogo:
68 if prod[0] == codigo_buscado:
69 producto_encontrado = prod
70 break
71
72 if not producto_encontrado:
73 print("❌ Código de producto no encontrado. Intenta nuevamente.")
74 continue
75
76 # Validar stock
77 try:
78 cantidad = int(input(f"Cantidad de '{producto_encontrado[1]}' (Disponibles: {producto_encontrado[3]}): "))
79 if cantidad <= 0:
80 print("❌ La cantidad debe ser mayor que 0.")
81 continue
82 if cantidad > producto_encontrado[3]:
83 print("❌ No hay suficiente stock disponible.")
84 continue
85 except ValueError:
86 print("❌ Debes ingresar un número entero válido.")
87 continue
88
89 # Descontar stock y agregar a la venta
90 producto_encontrado[3] -= cantidad # Descontamos stock del catálogo
91 subtotal = producto_encontrado[2] * cantidad
92 total_venta += subtotal
93 productos_vendidos.append([producto_encontrado[1], cantidad, subtotal])
94 print(f"✔ Agregado: {cantidad}x {producto_encontrado[1]} (${subtotal})")
95
96 if not productos_vendidos:
97 print("⚠ No se realizó ninguna compra.")
98 return
99
100 # Guardar boleta en TXT
101 guardar_boleta_txt(cliente, productos_vendidos, total_venta)
102 # Registrar en bitácora de caja general en TXT
103 registrar_venta_caja_txt(cliente, total_venta)
104
105 print(f"✔ ¡Venta finalizada con éxito! Total: ${total_venta}")
106
107 def guardar_boleta_txt(cliente, items, total):
108 nombre_archivo = f"boleta_{cliente.lower().replace(' ', '_')}_{datetime.datetime.now().strftime('%H%M%S')}.txt"
109 with open(nombre_archivo, "w", encoding="utf-8") as f:
110 f.write("========================================\n")
111 f.write(" CAFETERÍA PYTHON EXPRESS \n")
112 f.write("========================================\n")
113 f.write(f"Cliente: {cliente}\n")
114 f.write(f"Fecha: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
115 f.write("----------------------------------------\n")
116 for nombre, cant, subtotal in items:
117 f.write(f"{nombre:<22} x{cant:<3} ${subtotal:>8}\n")
118 f.write("----------------------------------------\n")
119 f.write(f"TOTAL PAGADO: ${total:>8}\n")
120 f.write("========================================\n")
121 print(f"-> Boleta virtual TXT generada: {nombre_archivo}")
122
123 def registrar_venta_caja_txt(cliente, total):
124 with open("reporte_caja_diaria.txt", "a", encoding="utf-8") as f:
125 f.write(f"[{datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Cliente: {cliente} | Total: ${total}\n")
126
127 # 3. EXPORTAR CATÁLOGO ACTUALIZADO Y VENTAS A DICCIONARIO + JSON
128 def exportar_estado_json():
129 if not catalogo:
130 print("⚠ No hay catálogo cargado para exportar.")
131 return
132
133 # Estructuramos en un diccionario robusto
134 datos_cierre = {
135 "fecha_cierre": datetime.datetime.now().strftime("%Y-%m-%d"),
136 "productos_actualizados": {}
137 }
138
139 for prod in catalogo:
140 codigo, nombre, precio, stock = prod
141 # Mezclamos lista de listas convirtiéndolo a un diccionario estructurado
142 datos_cierre["productos_actualizados"][codigo] = {
143 "nombre": nombre,
144 "precio": precio,
145 "stock_restante": stock
146 }
147
148 with open("cierre_caja.json", "w", encoding="utf-8") as f:
149 json.dump(datos_cierre, f, indent=4, ensure_ascii=False)
150 print("✔ Reporte de inventario final y cierre exportado con éxito a 'cierre_caja.json'.")
151
152 # MENU PRINCIPAL DE LA CAFETERÍA
153 def main():
154 inicializar_csv_si_no_existe()
155 while True:
156 print("\n☕ === BIENVENIDO A CAFETERÍA PYTHON === ☕")
157 print("1. Importar Catálogo Inicial (CSV con delimitador ';')")
158 print("2. Realizar una Venta (Genera Boleta TXT y descuenta stock)")
159 print("3. Exportar Inventario Actualizado y Cierre de Caja (JSON)")
160 print("0. Salir del programa")
161
162 opcion = input("Elige una opción: ").strip()
163 if opcion == "1":
164 importar_catalogo_csv()
165 elif opcion == "2":
166 realizar_venta()
167 elif opcion == "3":
168 exportar_estado_json()
169 elif opcion == "0":
170 print("¡Gracias por operar la cafetería! Buen día.")
171 break
172 else:
173 print("❌ Opción inválida, intenta de nuevo.")
174
175 if __name__ == "__main__":
176 main()

Prueba de Conocimientos 📝

Dato curioso: Primera versión pública en 1991; Guido van Rossum. Wikipedia

Ko-fi
Donaciones
Apoyá cafeycodigo con un café en Ko-fi. Colaboradores: insignia, muro y zona exclusiva.