Python de Cero a Pro - Cap. 18 - Arquitectura de Modelos de Datos
Serie: Arquitectura de Sistemas 🏗️
Section titled “Serie: Arquitectura de Sistemas 🏗️”Este capítulo es el paso 4 de nuestra práctica secuencial.
- Módulo de Persistencia JSON 🗄️
- Módulo de Procesamiento de Fechas 🗓️
- Módulo de Seguridad y Ofuscación 🛡️
- Arquitectura de Modelos de Datos (Estás aquí) 📍
- Interfaz de Usuario TUI Profesional 🎮
Arquitectura de Modelos de Datos (Users.py) 🧩
Section titled “Arquitectura de Modelos de Datos (Users.py) 🧩”En este capítulo aprenderemos a crear el corazón de nuestra aplicación. Un Modelo no es solo una lista de datos; es el guardián de la integridad. Aquí es donde definimos qué es un “Usuario”, cómo se limpia su nombre, cómo se valida su email y cómo se guarda en el archivo JSON.
El Código Completo
Section titled “El Código Completo”import datetimeimport reimport unicodedata
# Importaciones de nuestros módulos previosfrom modules.date_parser import parse_date, parse_date_to_stringfrom modules.save_json import save_json, load_json, find_in_jsonfrom modules.security import obscure_password, reveal_password
class User: """Modelo de Usuario Profesional - Qué Pasa Linux""" path = "data/users/" filename = "users.json"
def __init__(self, datos=None): # 1. Definición de Atributos (Propiedades) con valores por defecto self.nombre = None self.apellido_1 = None self.apellido_2 = "N/A" self.user = None self.password = None self.email = None self.telefono = "N/A" self.edad = 0 self.fecha_nacimiento = None self.calle = "N/A" self.numero = "N/A" # ... más campos como cp, ciudad, estado self.ultimo_login = "Sin actividad previa"
# 2. Inicialización Automática: Si nos dan datos, los cargamos if datos: self.Cargar_Desde_Diccionario(datos)
def Cargar_Desde_Diccionario(self, d): """Mapeo defensivo: Evita errores si faltan datos en el JSON.""" self.nombre = d.get('nombre') self.apellido_1 = d.get('apellido_1') self.apellido_2 = d.get('apellido_2', "N/A") self.email = d.get('email') self.user = d.get('user') # Al cargar, REVELAMOS la contraseña para trabajar con ella en memoria self.password = reveal_password(d.get('password')) self.telefono = d.get('telefono', "N/A") self.fecha_nacimiento = parse_date(d.get('fecha_nacimiento')) self.edad = d.get('edad', 0) # ... carga de dirección similar
# --- Validaciones Reales (Regex y Lógica) ---
def Set_Email(self, email): """Valida formato de email usando expresiones regulares.""" regex = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$' if bool(re.match(regex, email)): self.email = email return True return False
def Set_Fecha_Nacimiento(self, fecha_str): """Asigna fecha y calcula edad en años automáticamente.""" fecha_obj = parse_date(fecha_str) if fecha_obj: self.fecha_nacimiento = fecha_obj today = datetime.date.today() # Cálculo de edad preciso considerando mes y día self.edad = today.year - fecha_obj.year - ((today.month, today.day) < (fecha_obj.month, fecha_obj.day)) return True return False
def Generar_Username(self): """Genera un ID único: Ejemplo 'RamiroPastrano'.""" if not self.nombre or not self.apellido_1: return "UserQPL"
# Limpiamos acentos y espacios def limpiar(text): text = str(text).split()[0] # Solo la primera palabra return "".join(c for c in unicodedata.normalize('NFD', text) if unicodedata.category(c) != 'Mn')
n_limpio = limpiar(self.nombre).capitalize() a_limpio = limpiar(self.apellido_1).capitalize() base = f"{n_limpio}{a_limpio}"
# Checamos si ya existe alguien con ese usuario existentes = self.Consultar_Usuarios() ocupados = [u.get('user') for u in existentes]
if base not in ocupados: self.user = base return base
# Si ya existe, añade un correlativo: User1, User2... c = 1 nuevo = f"{base}{c}" while nuevo in ocupados: c += 1 nuevo = f"{base}{c}" self.user = nuevo return nuevo
def Get_User_Info(self): """Serialización: Prepara el diccionario final con contraseña ofuscada.""" return { "nombre": self.nombre, "apellido_1": self.apellido_1, "email": self.email, "password": obscure_password(self.password), # Protegemos antes de ir al disco "user": self.user, "edad": self.edad, "fecha_nacimiento": parse_date_to_string(self.fecha_nacimiento) # ... resto de campos }
# --- Persistencia (Conectando con el disco) ---
def Guardar(self, modo_edicion=False, id_original=None): info = self.Get_User_Info() if modo_edicion and id_original: # Lógica de actualización (Cap. 15) from modules.save_json import update_json return update_json("user", id_original, info, self.filename, self.path) return save_json(info, self.filename, self.path)
@classmethod def Consultar_Usuarios(cls): """Búsqueda global sin instanciar objetos individuales.""" return load_json(cls.filename, cls.path)
# --- Ejemplo de Ejecución Directa (Para Pruebas) ---if __name__ == "__main__": print("--- Simulador de Modelo de Usuario Profesional ---")
try: # 1. Crear instancia nuevo = User() nuevo.nombre = "Ramiro" nuevo.apellido_1 = "Pastrano"
# 2. Validar Email if nuevo.Set_Email("contacto@ramiropastrano.com"): print("✅ Email validado correctamente.")
# 3. Validar Fecha y Calcular Edad if nuevo.Set_Fecha_Nacimiento("1995-12-31"): print(f"✅ Edad calculada: {nuevo.edad} años.")
# 4. Generar ID único username = nuevo.Generar_Username() print(f"✅ Username generado: {username}")
# 5. Ver el diccionario de guardado (Con contraseña ofuscada) nuevo.password = "PythonPro2026" print("--- Datos listos para el JSON ---") print(nuevo.Get_User_Info())
except Exception as e: print(f"❌ Error en la lógica del modelo: {e}")🧠 Conceptos Detallados para Principiantes 🔍
Section titled “🧠 Conceptos Detallados para Principiantes 🔍”1. ¿Qué es el “Mapeo Defensivo”?
Section titled “1. ¿Qué es el “Mapeo Defensivo”?”Usamos .get('clave') en lugar de ['clave'].
['clave']: Si la clave no existe, tu programa muere (crash)..get('clave'): Si la clave no existe, Python simplemente devuelveNone. Tu programa sigue adelante sin romperse. Esto garantiza que tu software sea extraestable.
2. Validaciones con Regex: El “Filtro Automático”
Section titled “2. Validaciones con Regex: El “Filtro Automático””Las Expresiones Regulares (Regex) son patrones de texto. re.match compara lo que escribió el usuario con el patrón. Si no coincide, el email no se guarda. Esto evita que tu base de datos se llene de “basura” (datos mal escritos).
3. Lógica de “Colisión” en el Username
Section titled “3. Lógica de “Colisión” en el Username”Cuando dos usuarios se llaman igual (ej: dos “Juan Pérez”), el sistema genera JuanPerez para el primero y JuanPerez1 para el segundo. Esto se llama gestión de colisiones y es lo que hace que tu sistema sea escalable, ya que puede manejar miles de usuarios automáticamente sin errores.
4. Serialización Dinámica
Section titled “4. Serialización Dinámica”Fíjate que en Get_User_Info llamamos a obscure_password. El objeto guarda la contraseña “limpia” en la memoria de la computadora (RAM), pero solo se “disfraza” al momento de escribirse en el disco duro (JSON). ¡Doble seguridad!
¿Por qué esto es fundamental para el Desarrollo Limpio? ✨
Section titled “¿Por qué esto es fundamental para el Desarrollo Limpio? ✨”Al agrupar todas estas funciones en una sola clase (User), el resto de tu programa no tiene que preocuparse por cómo se guarda un archivo o cómo se cifra una clave. Simplemente creas un objeto User, le das los datos y dices usuario.Guardar(). Esto es lo que llamamos Encapsulamiento.
Ejercicios de Práctica 📝
Section titled “Ejercicios de Práctica 📝”- Prueba la Validación: Crea un objeto
Usere intenta asignarle un email que no sirva (comohola@com). Comprueba queSet_Emailte devuelvaFalse. - Generador de IDs: Crea 5 usuarios con el mismo nombre y apellido. Observa cómo
Generar_Usernameles asigna un número diferente a cada uno automáticamente. - Mapeo Defensivo: Intenta cargar los datos de un diccionario que esté medio vacío y comprueba que el programa no se cierva gracias a
.get().
Siguiente: Interfaz de Usuario TUI Profesional