Patrón de diseño - Bri0714/Cliente-Servidor GitHub Wiki
Patrón de Diseño Modelo-Vista-Controlador (MVC)
Introducción al Patrón MVC
El Modelo-Vista-Controlador (MVC) es un patrón de diseño arquitectónico ampliamente utilizado en el desarrollo de software para organizar el código de manera modular y mejorar su mantenimiento. Su principal objetivo es separar la lógica de negocio, la interfaz de usuario y la gestión de eventos, permitiendo que cada componente sea independiente y reutilizable.
Componentes del MVC
-
Modelo (Model)
- Representa los datos y la lógica de negocio de la aplicación.
- Gestiona el acceso a la base de datos o cualquier otro recurso de datos.
- No debe contener lógica de presentación.
-
Vista (View)
- Se encarga de la presentación de los datos al usuario.
- Recibe datos del modelo y los muestra de una manera comprensible.
- No debe contener lógica de negocio.
-
Controlador (Controller)
- Actúa como intermediario entre la Vista y el Modelo.
- Procesa las entradas del usuario y llama a los métodos apropiados del modelo.
- Actualiza la vista en función de los cambios en el modelo.
Ventajas del Patrón MVC
- Separación de responsabilidades, lo que facilita la mantenibilidad del código.
- Reutilización de componentes, permitiendo usar el mismo modelo con diferentes vistas.
- Facilita las pruebas unitarias, ya que cada componente puede probarse por separado.
- Mejor organización del código, reduciendo la complejidad y aumentando la escalabilidad.
Implementación del MVC en la Aplicación para consultar tarjetas de crédito
A continuación, se describe cómo se implementó el patrón MVC en la aplicación para consultar tarjetas de crédito para el Servidor.
BaseDeDatos.py
Modelo: import sqlite3
from datetime import datetime
class BaseDeDatos:
# Constructor de la clase
def __init__(self, db_nombre='banco_universidad.db'):
try:
self.conexion = sqlite3.connect(db_nombre, check_same_thread=False)
self.cursor = self.conexion.cursor()
self.crear_tablas()
#self.precargar_datos() Para que no se vuelvan a replicar los datos cada vez que se inicie el servidor se crea el metodo verificar_y_precargar_datos
self.verificar_y_precargar_datos()
print('Conexión exitosa a la base de datos')
except sqlite3.Error as e:
print(f'Error en la conexión a la base de datos: {e}')
# Método para Crear tablas
def crear_tablas(self):
try:
# Tabla cliente
self.cursor.execute(
"""
CREATE TABLE IF NOT EXISTS cliente(
id_cliente INTEGER PRIMARY KEY AUTOINCREMENT,
nombre TEXT NOT NULL
)
"""
)
print('Tabla Cliente creada con éxito')
# Tabla tarjeta
self.cursor.execute(
"""
CREATE TABLE IF NOT EXISTS tarjeta(
id_tarjeta INTEGER PRIMARY KEY AUTOINCREMENT,
id_cliente INTEGER NOT NULL,
nombre_banco TEXT NOT NULL,
numero_tarjeta TEXT NOT NULL,
cupo_total REAL NOT NULL,
cupo_disponible REAL NOT NULL,
FOREIGN KEY (id_cliente) REFERENCES cliente(id_cliente)
)
"""
)
print('Tabla Tarjeta creada con éxito')
# Tabla compras
self.cursor.execute(
"""
CREATE TABLE IF NOT EXISTS compras(
id INTEGER PRIMARY KEY AUTOINCREMENT,
numero_tarjeta TEXT,
fecha TEXT,
monto REAL,
descripcion TEXT,
FOREIGN KEY (numero_tarjeta) REFERENCES tarjeta(numero_tarjeta)
)
"""
)
print('Tabla Compras creada con éxito')
self.conexion.commit()
except sqlite3.Error as e:
print(f'Error en la creación de la tabla: {e}')
# Método para verificar y precargar los datos
def verificar_y_precargar_datos(self):
try:
self.cursor.execute("SELECT COUNT(*) FROM cliente")
resultado = self.cursor.fetchone()
if resultado and resultado[0] == 0:
self.precargar_datos()
except sqlite3.Error as e:
print(f'Error al verificar y precargar datos: {e}')
# Método para precargar los datos
def precargar_datos(self):
try:
self.cursor.executescript(
"""
-- Insertar clientes
INSERT INTO cliente (nombre) VALUES
('Juan Pérez'),
('María Gómez'),
('Carlos Rodríguez'),
('Laura Sánchez'),
('Andrés Fernández'),
('Diana Castro'),
('Sergio Ramírez'),
('Valentina López'),
('Felipe Morales'),
('Camila Vargas');
-- Insertar tarjetas con bancos colombianos
INSERT INTO tarjeta (id_cliente, nombre_banco, numero_tarjeta, cupo_total, cupo_disponible) VALUES
(1, 'BBVA', '1111-2222-3333-4444', 5000000, 5000000),
(1, 'Davivienda', '5555-6666-7777-8888', 7000000, 7000000),
(2, 'Banco Bogotá', '2222-3333-4444-5555', 4000000, 4000000),
(2, 'Bancolombia', '6666-7777-8888-9999', 6000000, 6000000),
(3, 'Colpatria', '3333-4444-5555-6666', 3000000, 3000000),
(3, 'Mastercard', '7777-8888-9999-0000', 8000000, 8000000),
(4, 'Banco de Occidente', '4444-5555-6666-7777', 5000000, 5000000),
(4, 'BBVA', '8888-9999-0000-1111', 7000000, 7000000),
(5, 'Davivienda', '5555-6666-7777-8889', 6000000, 6000000),
(5, 'Banco Bogotá', '9999-0000-1111-2222', 9000000, 9000000),
(6, 'Bancolombia', '6666-7777-8888-9998', 5000000, 5000000),
(6, 'Colpatria', '1111-3333-5555-7777', 7000000, 7000000),
(7, 'Mastercard', '2222-4444-6666-8888', 4000000, 4000000),
(7, 'Banco de Occidente', '3333-5555-7777-9999', 8000000, 8000000),
(8, 'BBVA', '4444-6666-8888-0000', 5000000, 5000000),
(8, 'Davivienda', '5555-7777-9999-1111', 7000000, 7000000),
(9, 'Banco Bogotá', '6666-8888-0000-2222', 6000000, 6000000),
(9, 'Bancolombia', '7777-9999-1111-3333', 9000000, 9000000),
(10, 'Colpatria', '8888-0000-2222-4444', 5000000, 5000000),
(10, 'Mastercard', '9999-1111-3333-5555', 7000000, 7000000);
-- Insertar compras por tarjeta (2 compras por cada tarjeta)
INSERT INTO compras (numero_tarjeta, fecha, monto, descripcion) VALUES
('1111-2222-3333-4444', '2025-02-01', 200000, 'Compra en supermercado'),
('1111-2222-3333-4444', '2025-02-05', 300000, 'Pago de servicios'),
('5555-6666-7777-8888', '2025-02-02', 400000, 'Compra en tienda de ropa'),
('5555-6666-7777-8888', '2025-02-06', 500000, 'Restaurante'),
('2222-3333-4444-5555', '2025-02-07', 600000, 'Gasolina'),
('2222-3333-4444-5555', '2025-02-10', 200000, 'Compra en línea'),
('6666-7777-8888-9999', '2025-02-12', 700000, 'Electrodomésticos'),
('6666-7777-8888-9999', '2025-02-15', 300000, 'Pago de internet'),
('3333-4444-5555-6666', '2025-02-18', 400000, 'Compra en ferretería'),
('3333-4444-5555-6666', '2025-02-20', 150000, 'Cine y entretenimiento'),
('7777-8888-9999-0000', '2025-02-21', 800000, 'Compra de tecnología'),
('7777-8888-9999-0000', '2025-02-23', 200000, 'Taxi y transporte'),
('4444-5555-6666-7777', '2025-02-24', 100000, 'Compra en librería'),
('4444-5555-6666-7777', '2025-02-26', 300000, 'Pago de servicios públicos'),
('8888-9999-0000-1111', '2025-02-27', 500000, 'Cena en restaurante'),
('8888-9999-0000-1111', '2025-02-28', 600000, 'Compra de muebles'),
('5555-6666-7777-8889', '2025-03-01', 250000, 'Compra en supermercado'),
('5555-6666-7777-8889', '2025-03-03', 350000, 'Pago de gimnasio'),
('9999-0000-1111-2222', '2025-03-05', 450000, 'Compra de videojuegos'),
('9999-0000-1111-2222', '2025-03-07', 200000, 'Taxi y transporte'),
('6666-7777-8888-9998', '2025-03-10', 700000, 'Electrodomésticos'),
('6666-7777-8888-9998', '2025-03-12', 500000, 'Pago de internet'),
('1111-3333-5555-7777', '2025-03-15', 800000, 'Compra en ferretería'),
('1111-3333-5555-7777', '2025-03-17', 300000, 'Cine y entretenimiento'),
('2222-4444-6666-8888', '2025-03-18', 500000, 'Compra de tecnología'),
('2222-4444-6666-8888', '2025-03-20', 200000, 'Taxi y transporte'),
('3333-5555-7777-9999', '2025-03-21', 150000, 'Compra en librería'),
('3333-5555-7777-9999', '2025-03-23', 300000, 'Pago de servicios públicos'),
('4444-6666-8888-0000', '2025-03-25', 600000, 'Cena en restaurante'),
('4444-6666-8888-0000', '2025-03-27', 500000, 'Compra de muebles');
-- 5 Actualizar el cupo disponible de cada tarjeta
UPDATE tarjeta
SET cupo_disponible = cupo_total - (
SELECT COALESCE(SUM(monto), 0)
FROM compras
WHERE compras.numero_tarjeta = tarjeta.numero_tarjeta
);
"""
)
self.conexion.commit()
print("✅ Datos precargados con éxito")
except sqlite3.Error as e:
print(f"❌ Error al precargar datos: {e}")
# Metodo para obtener los detalles de un cliente
def obtener_detalle_cliente(self, id_cliente, fecha_inicio=None, fecha_fin=None):
self.cursor.execute("SELECT nombre FROM cliente WHERE id_cliente = ?", (id_cliente,))
cliente = self.cursor.fetchone()
self.cursor.execute("SELECT nombre_banco, numero_tarjeta, cupo_total, cupo_disponible FROM tarjeta WHERE id_cliente = ?", (id_cliente,))
tarjetas = self.cursor.fetchall()
query = """
SELECT c.fecha, c.monto, c.descripcion, t.nombre_banco, t.numero_tarjeta
FROM compras c
JOIN tarjeta t ON c.numero_tarjeta = t.numero_tarjeta
WHERE t.id_cliente = ?
"""
params = [id_cliente]
if fecha_inicio and fecha_fin:
query += " AND fecha BETWEEN ? AND ?"
params.extend([fecha_inicio, fecha_fin])
self.cursor.execute(query, tuple(params))
compras = self.cursor.fetchall()
#print(cliente,tarjetas,compras)
return cliente, tarjetas, compras
# Metodo para obtener todos los clientes
def obtener_clientes(self):
self.cursor.execute("SELECT * FROM cliente")
return self.cursor.fetchall()
📌 Función en el MVC:
- Gestiona la conexión a la base de datos SQLite.
- Crea y estructura las tablas necesarias (cliente, tarjeta, compras).
- Inserta datos de prueba si la base está vacía.
- Contiene métodos para obtener detalles de clientes, tarjetas y compras.
vista.py
Vista: import sys
import os
# Agregar el directorio raíz al sys.path
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
import socket
import json
import threading
from Controlador.Controlador import Controlador
class ServidorVista:
#Clase que representa el servidor y maneja múltiples clientes usando hilos.
def __init__(self, host="localhost", puerto=5000):
#Inicializa el servidor con los datos básicos."""
self.host = host
self.puerto = puerto
self.servidor = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.controlador = Controlador() # Instancia del controlador
def iniciar_servidor(self):
#Inicia el servidor y espera conexiones de clientes.
self.servidor.bind((self.host, self.puerto)) # .bind() enlaza el socket a la dirección y puerto especificados
self.servidor.listen(5) # .listen() pone el socket en modo de escucha para aceptar conexiones
print(f"Servidor iniciado en {self.host}:{self.puerto}")
while True:
conexion, _ = self.servidor.accept()
print("Cliente conectado.")
hilo = threading.Thread(target=self.manejar_cliente, args=(conexion,))
hilo.start() # Iniciar el hilo para manejar al cliente
def manejar_cliente(self, conexion):
#Maneja la conexión de un cliente en un hilo separado.
try:
datos = json.loads(conexion.recv(1024).decode("utf-8"))
respuesta = self.controlador.procesar_peticion(datos) # Procesar petición con el controlador
conexion.sendall(json.dumps(respuesta).encode("utf-8"))
except Exception as e:
conexion.sendall(json.dumps({"error": f"Error del servidor: {e}"}).encode("utf-8"))
finally:
conexion.close()
if __name__ == "__main__":
servidor = ServidorVista()
servidor.iniciar_servidor()
📌 Función en el MVC:
- Implementa un servidor basado en sockets para recibir peticiones de clientes.
- Crea un hilo por cada conexión entrante.
- Recibe datos en JSON, los envía al controlador y responde al cliente con los resultados procesados.
Controlador.py
Controlador: from Modelo.BaseDeDatos import BaseDeDatos
class Controlador:
def __init__(self):
self.db = BaseDeDatos()
def procesar_peticion(self, datos):
if datos["accion"] == "listar_clientes":
clientes = self.db.obtener_clientes()
return {"clientes": [{"id": c[0], "nombre": c[1]} for c in clientes]}
elif datos["accion"] == "detalle_cliente":
id_cliente = datos.get("id_cliente")
fecha_inicio = datos.get("fecha_inicio")
fecha_fin = datos.get("fecha_fin")
cliente, tarjetas, compras = self.db.obtener_detalle_cliente(id_cliente, fecha_inicio, fecha_fin)
if not cliente:
return {"error": "Cliente no encontrado"}
return {
"nombre": cliente[0],
"num_compras": len(compras),
"tarjetas": [
{
"nombre_banco": t[0],
"numero_tarjeta": t[1],
"cupo_total": t[2],
"cupo_disponible": t[3]
}
for t in tarjetas],
"compras": [
{
"fecha": c[0],
"monto": c[1],
"descripcion": c[2],
"nombre_banco": c[3],
"numero_tarjeta": c[4]
}
for c in compras]
}
return {"error": "Acción no reconocida"}
📌 Función en el MVC:
- Actúa como intermediario entre la vista y el modelo.
- Recibe peticiones desde la vista y decide qué acción tomar.
- Consulta a BaseDeDatos y formatea los datos antes de devolverlos a la vista.
- Soporta acciones como listar_clientes y detalle_cliente.
A continuación, se describe cómo se implementó el patrón MVC en la aplicación para consultar tarjetas de crédito para el Cliente.
modelo.py
Modelo: import socket
import json
class ClienteModelo:
#Clase encargada de la comunicación con el servidor
def __init__(self, host="localhost", puerto=5000):
self.host = host
self.puerto = puerto
def enviar_peticion(self, datos):
"""Envía una petición JSON al servidor y recibe la respuesta"""
try:
cliente = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
cliente.connect((self.host, self.puerto))
cliente.sendall(json.dumps(datos).encode("utf-8"))
respuesta = json.loads(cliente.recv(4096).decode("utf-8"))
cliente.close()
return respuesta
except Exception as e:
return {"error": f"Error de conexión: {e}"}
def obtener_clientes(self):
#Solicita la lista de clientes al servidor
return self.enviar_peticion({"accion": "listar_clientes"})
def obtener_detalle_cliente(self, id_cliente, fecha_inicio=None, fecha_fin=None):
#Solicita detalles de un cliente, incluyendo tarjetas y compras
datos = {"accion": "detalle_cliente", "id_cliente": id_cliente}
if fecha_inicio and fecha_fin:
datos["fecha_inicio"] = fecha_inicio
datos["fecha_fin"] = fecha_fin
return self.enviar_peticion(datos)
📌 Función en el MVC:
- Se encarga de la comunicación con el servidor.
- Usa sockets para enviar peticiones en formato JSON y recibir respuestas.
- Tiene métodos para solicitar la lista de clientes y obtener detalles de un cliente específico.
vista.py
Vista: import socket
import json
class ClienteModelo:
#Clase encargada de la comunicación con el servidor
def __init__(self, host="localhost", puerto=5000):
self.host = host
self.puerto = puerto
def enviar_peticion(self, datos):
"""Envía una petición JSON al servidor y recibe la respuesta"""
try:
cliente = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
cliente.connect((self.host, self.puerto))
cliente.sendall(json.dumps(datos).encode("utf-8"))
respuesta = json.loads(cliente.recv(4096).decode("utf-8"))
cliente.close()
return respuesta
except Exception as e:
return {"error": f"Error de conexión: {e}"}
def obtener_clientes(self):
#Solicita la lista de clientes al servidor
return self.enviar_peticion({"accion": "listar_clientes"})
def obtener_detalle_cliente(self, id_cliente, fecha_inicio=None, fecha_fin=None):
#Solicita detalles de un cliente, incluyendo tarjetas y compras
datos = {"accion": "detalle_cliente", "id_cliente": id_cliente}
if fecha_inicio and fecha_fin:
datos["fecha_inicio"] = fecha_inicio
datos["fecha_fin"] = fecha_fin
return self.enviar_peticion(datos)
📌 Función en el MVC:
- Es la interfaz gráfica de la aplicación, construida con Flet.
- Contiene botones, campos de entrada y listas desplegables para interactuar con el usuario.
- Usa ClienteControlador para solicitar y mostrar datos.
- Formatea la información recibida del servidor, mostrando los clientes, tarjetas y compras.
Controlador.py
Controlador: from Modelo.modelo import ClienteModelo
class ClienteControlador:
#Clase que maneja la lógica de la aplicación
def __init__(self, vista):
self.modelo = ClienteModelo()
self.vista = vista
def cargar_clientes(self):
#Obtiene la lista de clientes y la envía a la vista
clientes = self.modelo.obtener_clientes()
if "clientes" in clientes:
self.vista.mostrar_clientes(clientes["clientes"])
else:
self.vista.mostrar_mensaje("Error al obtener clientes")
def mostrar_detalle_cliente(self, id_cliente, fecha_inicio=None, fecha_fin=None):
#Obtiene los detalles del cliente y los envía a la vista
detalle = self.modelo.obtener_detalle_cliente(id_cliente, fecha_inicio, fecha_fin)
if "error" in detalle:
self.vista.mostrar_mensaje(detalle["error"])
else:
self.vista.mostrar_detalle(detalle)
📌 Función en el MVC:
- Actúa como intermediario entre el modelo y la vista.
- Llama a ClienteModelo para obtener datos del servidor.
- Procesa los datos y los envía a la vista para que sean mostrados.
- Maneja la carga de clientes y la consulta de detalles de un cliente.
main.py
Punto de entrada: import flet as ft
from Vista.vista import ClienteVista
def main(page: ft.Page):
page.title = "Gestión de Clientes"
ClienteVista(page)
ft.app(target=main)
📌 Función en el MVC:
- Se encarga de iniciar la aplicación con Flet.
- Define el título de la ventana y carga la interfaz gráfica ClienteVista.
- ft.app(target=main) inicia la aplicación en un entorno Flet.