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)
2with 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)
6with 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
1import csv
2
3# Nuestros productos de la cafetería representados como [[codigo, nombre, precio]]
4productos = [
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
12with 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)
16print("¡Catálogo guardado con éxito en productos.csv!")
17
18# 2. Importar/Leer desde el archivo CSV con delimitador ";"
19productos_importados = []
20with 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
29print("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
1import datetime
2
3# Generar un voucher de entrega (Boleta) para un cliente
4def 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)
25def 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
32compra = [
33 ["Café Expreso", 2, 3000],
34 ["Medialuna Dulce", 3, 2400]
35]
36guardar_boleta("Benjamín Torres", compra, 5400)
37registrar_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
1import json
2
3# Estructura del menú con stock y categoría utilizando diccionarios
4inventario_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)
11with 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)
15print("Inventario JSON guardado.")
16
17# 2. Cargar/Importar diccionario desde JSON
18with open("inventario.json", "r", encoding="utf-8") as archivo_json:
19 datos_cargados = json.load(archivo_json)
20
21# Acceder de forma estructurada
22print("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 '|'
2with 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('|')
8with 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
1import csv
2import json
3import datetime
4import os
5
6# Base de datos en memoria (Lista de listas para productos)
7# Estructura: [codigo, nombre, precio, stock]
8catalogo = []
9
10# Cargar catálogo de ejemplo en CSV si no existe
11def 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
25def 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
43def 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
107def 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
123def 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
128def 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
153def 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
175if __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.