Hoy construimos los primeros ladrillos del sistema: el gestor de archivos y el servicio de autenticación paso a paso en Spyder IDE.
utils/ no sabe nada de usuarios. services/ no sabe cómo se dibuja el menú. Si mañana cambias JSON por una base de datos real, solo tocas utils/file_manager.py y todo lo demás sigue funcionando.mi_mini_market/data/, menus/, services/, utils/utils/file_manager.py
La navaja suiza del proyecto: leer y guardar JSON
json.load() directamente en cada servicio?
file_manager lo cambias una sola vez. Eso se llama principio DRY: Don't Repeat Yourself. 🧠
# ① Importamos las librerías que necesitamos import json # json → convierte entre texto plano y objetos Python import os # os → nos da acceso al sistema de archivos del SO def leer_json(ruta): # ② Antes de abrir, verificamos que el archivo exista if not os.path.exists(ruta): # Si no existe todavía, devolvemos lista vacía (no un error) return [] # ③ Abrimos el archivo en modo lectura ("r") con codificación UTF-8 with open(ruta, "r", encoding="utf-8") as archivo: try: # json.load() lee el archivo y lo convierte a lista/dict de Python return json.load(archivo) except json.JSONDecodeError: # ④ Si el JSON está corrupto o vacío, devolvemos lista vacía return [] def guardar_json(ruta, datos): # ⑤ Abrimos en modo escritura ("w") — crea el archivo si no existe with open(ruta, "w", encoding="utf-8") as archivo: # json.dump() convierte el dict/lista a texto y lo escribe # indent=4 → legible para humanos; ensure_ascii=False → acepta tildes json.dump(datos, archivo, indent=4, ensure_ascii=False)
import json e import os — Son librerías de la biblioteca estándar de Python. No necesitas instalarlas. json serializa Python ↔ texto; os te permite preguntarle al sistema operativo si un archivo existe.os.path.exists(ruta) — La primera vez que el proyecto corra, Usuarios.json aún no existe. Sin esta verificación, open() lanzaría un error. Devolvemos [] (lista vacía) para que el código de arriba no se rompa.with open(...) as archivo: — El bloque with garantiza que el archivo se cierre automáticamente aunque ocurra un error. encoding="utf-8" es obligatorio para soportar tildes y ñ.except json.JSONDecodeError — Protege contra archivos vacíos o corruptos. Sin esto, un Usuarios.json vacío rompería el sistema al arrancar.mode="w" — Escribe desde cero. Si el archivo ya existe, lo sobreescribe completamente. Por eso pasamos la lista completa de usuarios, no solo el nuevo: reemplazamos todo el archivo.utils/file_manager.py en Spyderimport json e import osleer_json(ruta) con el os.path.exists y el try/exceptguardar_json(ruta, datos) con indent=4 y ensure_ascii=Falseservices/auth_service.py → registrar_usuario()
Create del CRUD de Usuarios: guardar nuevo usuario en Usuarios.json
file_manager que lo guarde. Nota que auth_service no sabe cómo se guarda el archivo; eso es trabajo de file_manager. 🤝
# ⑥ Importamos SOLO las funciones que necesitamos de nuestra utilidad from utils.file_manager import leer_json, guardar_json # ⑦ Constante con la ruta al archivo de usuarios RUTA_USUARIOS = "data/usuarios.json" def registrar_usuario(): # ⑧ Leemos los usuarios existentes ANTES de agregar uno nuevo usuarios = leer_json(RUTA_USUARIOS) print("\n===== REGISTRO DE USUARIO =====") # ⑨ Pedimos los datos al usuario con input() nombre = input("Nombre: ") correo = input("Correo: ") password = input("Contraseña: ") print("\nSeleccione rol:") print("1. Comprador") print("2. Vendedor") opcion_rol = input("Opción: ") # ⑩ Convertimos la opción numérica a texto descriptivo if opcion_rol == "1": rol = "comprador" elif opcion_rol == "2": rol = "vendedor" else: print("Rol no válido.") return # Salimos de la función si la opción es inválida # ⑪ Validamos que el correo no esté duplicado for usuario in usuarios: if usuario["correo"] == correo: print("Ya existe un usuario con ese correo.") return # Salimos sin guardar nada # ⑫ Construimos el diccionario del nuevo usuario nuevo_usuario = { "id": len(usuarios) + 1, # ← id automático: cuántos hay + 1 "nombre": nombre, "correo": correo, "password": password, "rol": rol } # ⑬ Añadimos el nuevo usuario a la lista y la guardamos completa usuarios.append(nuevo_usuario) guardar_json(RUTA_USUARIOS, usuarios) print("Usuario registrado correctamente.")
from utils.file_manager import … — Importación relativa. Usamos from … import porque solo necesitamos dos funciones específicas. Esto hace el código más legible que import utils.file_manager y luego escribir el nombre completo cada vez.RUTA_USUARIOS = "data/usuarios.json" — Una constante en mayúsculas. Convención de Python para valores que no cambian. Si mañana renombras el archivo, lo cambias en un solo lugar, no en cada función.guardar_json sobreescribe el archivo.input() — Siempre devuelve un str. Por eso comparamos la opción con "1" (cadena) y no con 1 (número)."comprador" / "vendedor"). El JSON guardará la palabra, no el número, lo que hace el dato autoexplicativo.for. Si encontramos el mismo correo, mostramos el error y salimos con return. Sin esta verificación, habría dos usuarios con el mismo correo y el login se confundiría."id": len(usuarios) + 1 — Generamos un id autoincrementado simple: si ya hay 3 usuarios, el nuevo tendrá id=4. En producción real usaríamos UUID o la base de datos; aquí es suficiente para el mini-market.append() + guardar_json() — append agrega al final de la lista en memoria. Luego guardar_json escribe toda la lista al disco. Dos pasos: primero modificamos en RAM, luego persistimos.services/auth_service.py en Spyderfrom utils.file_manager import leer_json, guardar_jsonRUTA_USUARIOS = "data/usuarios.json"def registrar_usuario(): y cargué la lista con leer_jsoninput() para nombre, correo, contraseña y el submenú de rolfornuevo_usuario con los 5 camposusuarios.append() y luego a guardar_json()data/ ya existe antes de correr el registro. Python no crea carpetas automáticamente. Si no existe, guardar_json lanzará un error al intentar escribir en data/usuarios.json.auth_service.py → iniciar_sesion()
Read del CRUD de Usuarios: buscar y validar credenciales
menu_principal.py decida qué hacer con él. 🔑
None. En Python, cuando una función termina sin un return explícito, automáticamente devuelve None. En menu_principal.py vamos a verificar: if usuario is None → mostrar error, if usuario["rol"] == "vendedor" → menú vendedor. ¡El dato viaja de servicio a servicio! 🚀
def iniciar_sesion(): # ⑭ Leemos todos los usuarios registrados del JSON usuarios = leer_json(RUTA_USUARIOS) print("\n===== INICIO DE SESIÓN =====") # ⑮ Pedimos las credenciales al usuario correo = input("Correo: ") password = input("Contraseña: ") # ⑯ Recorremos la lista buscando coincidencia de correo Y contraseña for usuario in usuarios: if (usuario["correo"] == correo and usuario["password"] == password): # ⑰ Si lo encontramos → saludamos y RETORNAMOS el objeto usuario print(f"Bienvenido, {usuario['nombre']}") return usuario # ← Este dict llega a main.py para saber el rol # ⑱ Si el for termina sin encontrar nada → implícitamente retorna None # En main.py haremos: if usuario is None: print("Credenciales incorrectas") return None
for y and — Recorremos uno a uno. La condición combina dos comparaciones con and: ambas deben ser verdaderas. Un correo correcto con contraseña equivocada no inicia sesión.return usuario — Devolvemos el diccionario completo del usuario. Esto le permite a main.py leer el campo "rol" y decidir si mostrar el menú de comprador o de vendedor. La función de login no decide qué menú abrir; eso lo hace menu_principal.py (separación de responsabilidades).return None — Si el bucle termina sin retornar nada (no encontró coincidencia), devolvemos explícitamente None. En menu_principal.py: if usuario is None: print("Credenciales incorrectas").def iniciar_sesion(): en el mismo archivo auth_service.pyleer_json(RUTA_USUARIOS)input()for con la condición correo == … and password == …usuario si coincide, y None al final si nomenu_principal.py?
Flujo completo: menú → servicio → utilidad → JSON
from services.auth_service import registrar_usuario, iniciar_sesion while True: print("\n===== MENÚ PRINCIPAL =====") print("1. Registrarse") print("2. Iniciar sesión") print("3. Ver productos disponibles") print("4. Salir") opcion = input("Elige una opción: ") if opcion == "1": registrar_usuario() # ← Llama al servicio directamente elif opcion == "2": usuario = iniciar_sesion() # ← Recibe el dict o None if usuario is not None: if usuario["rol"] == "vendedor": print("→ Abriendo menú vendedor...") # menu_vendedor(usuario) else: print("→ Abriendo menú comprador...") # menu_comprador(usuario) else: print("Correo o contraseña incorrectos.") elif opcion == "4": print("¡Hasta luego!") break # ← Termina el ciclo while
main.py llama iniciar_sesion()
→ auth_service llama leer_json()
→ file_manager abre data/usuarios.json
→ devuelve la lista
→ auth_service busca el usuario
→ devuelve el dict o None
→ main.py decide qué menú abrir.
Cada capa hace solo su parte.
Hoy construiste los primeros cimientos de PyMarket. Creaste utils/file_manager.py con leer_json() y guardar_json(), la única capa que toca el disco. Luego en services/auth_service.py implementaste registrar_usuario() (CREATE del CRUD) que pide datos, valida duplicados y guarda en JSON; y iniciar_sesion() (READ del CRUD) que busca credenciales y retorna el objeto usuario para que main.py sepa a qué menú enviar. Cada archivo tiene una sola responsabilidad — eso es arquitectura por capas en acción.