Sesión · PyMarket Fase 2: Capa de Datos y Autenticación
// Sesiones 27-28 · Fundamentos de Programación · Profe Diego

PyMarket Fase 2: Capa de Datos y Autenticación

Hoy construimos los primeros ladrillos del sistema: el gestor de archivos y el servicio de autenticación paso a paso en Spyder IDE.

🐍 Python 3.x
🛠️ Spyder IDE
📁 Arquitectura por capas
📄 Persistencia JSON
Progreso de la sesión 0 %
Pasos completados: 0 / 0
0
DA
DANI · Asistente de clase
Antes de escribir una sola línea, necesitamos que el proyecto tenga su "casa" lista. Una aplicación bien organizada separa sus responsabilidades en capas. Así, si algo falla, sabemos exactamente dónde buscar. 🏗️
📂 mi_mini_market/
  ├── 📂 data/         ← Capa de datos (JSON)
  ├── 📂 menus/       ← Capa de interfaz (consola)
  ├── 📂 services/    ← Capa de servicios (lógica)
  ├── 📂 utils/       ← Capa de utilidades
      ├── file_manager.py  ← Hoy: Paso 1
  ├── 📂 services/
      ├── auth_service.py  ← Hoy: Pasos 2 y 3
  └── main.py         ← Punto de entrada
💡
¿Por qué capas? Cada carpeta tiene una sola responsabilidad. 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.
Abrí Spyder IDE y retomé el proyecto mi_mini_market/
Verifiqué que existen las carpetas: data/, menus/, services/, utils/
1
DA
DANI · Asistente de clase
Este archivo es el único que va a tocar directamente el disco duro. Todos los demás servicios le piden a él que lea o guarde. ¿Por qué? Para no repetir código y para que si algo falla con los archivos, haya un solo lugar donde revisar. Le llamamos utilidad porque sirve a todos. 🔧
👤
Estudiante
¿Y no podríamos escribir json.load() directamente en cada servicio?
DA
DANI · Asistente de clase
Sí podrías, pero imagina que tienes 5 servicios haciendo lo mismo. Si un día quieres agregar un try/except o cambiar el encoding, ¿vas a editar 5 archivos? Con file_manager lo cambias una sola vez. Eso se llama principio DRY: Don't Repeat Yourself. 🧠
utils / file_manager.py
# ① 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.
Creé el archivo utils/file_manager.py en Spyder
Escribí los imports: import json e import os
Codifiqué la función leer_json(ruta) con el os.path.exists y el try/except
Codifiqué la función guardar_json(ruta, datos) con indent=4 y ensure_ascii=False
Guardé el archivo (Ctrl+S) y verifiqué que no hay errores en la consola
2
DA
DANI · Asistente de clase
Este es el primer CREATE de nuestro CRUD. Vamos a pedir datos al usuario, construir un diccionario Python y pedirle a file_manager que lo guarde. Nota que auth_service no sabe cómo se guarda el archivo; eso es trabajo de file_manager. 🤝
services / auth_service.py — parte 1
# ⑥ 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.
Leer antes de agregar — Primero cargamos todos los usuarios existentes. Así podemos verificar duplicados Y, al guardar, enviamos la lista completa (los viejos + el nuevo), porque 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).
if/elif/else para el rol — Convertimos el número elegido a texto descriptivo ("comprador" / "vendedor"). El JSON guardará la palabra, no el número, lo que hace el dato autoexplicativo.
Validación de correo duplicado — Recorremos la lista con 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.
Creé el archivo services/auth_service.py en Spyder
Escribí el import: from utils.file_manager import leer_json, guardar_json
Definí la constante RUTA_USUARIOS = "data/usuarios.json"
Escribí def registrar_usuario(): y cargué la lista con leer_json
Agregué los input() para nombre, correo, contraseña y el submenú de rol
Implementé la validación de correo duplicado con el bucle for
Construí el diccionario nuevo_usuario con los 5 campos
Llamé a usuarios.append() y luego a guardar_json()
⚠️
¡Ojo! Asegúrate de que la carpeta 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.
3
DA
DANI · Asistente de clase
El login es un READ: leemos el JSON, buscamos si existe alguien con ese correo y contraseña, y si lo encontramos devolvemos el objeto usuario. Eso es importante: la función no imprime el menú, solo devuelve el usuario para que menu_principal.py decida qué hacer con él. 🔑
👤
Estudiante
¿Y si el usuario no existe, qué devuelve?
DA
DANI · Asistente de clase
Devuelve 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! 🚀
services / auth_service.py — parte 2 (continúa el mismo archivo)
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
Leer usuarios — Igual que en registro, cargamos todos los usuarios. La diferencia: aquí solo leemos, no modificamos nada. Es un READ puro del CRUD.
Pedir credenciales — Le preguntamos solo correo y contraseña. No pedimos nombre ni rol: el sistema los conoce porque están guardados en el JSON.
Búsqueda con 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").
Agregué def iniciar_sesion(): en el mismo archivo auth_service.py
Cargué la lista con leer_json(RUTA_USUARIOS)
Pedí correo y contraseña con input()
Escribí el bucle for con la condición correo == … and password == …
Retorné el objeto usuario si coincide, y None al final si no
Guardé el archivo y probé registrar un usuario y luego iniciar sesión
🔗
DA
DANI · Asistente de clase
Así queda el flujo cuando el usuario elige "Iniciar sesión" en el menú principal. Cada flecha cruza una capa: interfaz → servicio → utilidad → datos. Y la respuesta viaja de vuelta por el mismo camino. 🔄
menu_principal.py — fragmento de cómo se usa auth_service
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
🌊
Flujo de datos completo:
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.

// Resumen de la sesión

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.

Scroll to Top