El siguiente artículo se centra en al proceso de reflexión y aprendizaje para realizar un sistemas de consola para una libreria universitaria. Presentaré el proceso de diseño, levantamiento de requisitos, Analysis, Design, Coding and Testing, y finalmente evidencias de todo el proyecto, incluido el código fuente.
Solución
Levantamiento de requerimientos
Al tratarse de una librearia universitaria, es imporntante considerar los casos de uso del sistema y los posibles actores. En este sentido se cuenta con los siguientes:
- Cliente: Esta entidad se acerca a la libreria e interactua con los elementos disponibles en ella. Puede comprar objetos de diferentes categorías y en múltiples cantidades.
- Administrador: Esta entidad se encarga de registrar los artículos de la tienda, consultar, modificar y eleminar elementos de la misma. Sus cambios afectan directamente el inventario de la libreria
Tipos de requerimientos
En el proceso de desarrollo de programas para clientes, es fundamental contar con apoyo por parte de los intereados con el fin de encontrar aquellos elementos que son esenciales para el programa y para el usuario. Estos se dividen en múltiples categorias que permiten al desarrollador o grupo de estos enfocarse en diferentes ramas del programa. Igualmente, este proceso permite crear programas modulares, eficientes y seguro que cumplan con las expectativas iniciales. Claro está que debido a que el proceso de desarrollo no es linear, es posible que se ajusten, de delimiten o se descarten.
En este proceso, los objetivos se suelen crear bajo la metodología SMART (específicos, medibles, alcanzables, realistas y enmarcados temporalmente). Al aplicar esta estrategia, tanto el desarrollador como el cliente pueda comprender las necesidades y reducir el nivel de abstracción
Requerimientos funcionales
Los requerimientos funcionales según V/sure (s.f)1 y Geeks4Geeks (2025)2 son aquellos que se centran en los elementos esenciales para que el programa pueda cumplir la función definida.
- El cliente puede consultar los artículos disponibles por categoría organizados de manera alfabetica.
- El administrador puede agregar artículos a la librería.
- El administrador pueda eliminar artículos de la tienda con verificación doble de confirmación.
- El administrador pueda modificar las características de los productos como precio.
- El administrador puede consultar los artículos disponibles en la librería.
Requerimientos no funcionales
Los requerimientos no funcionales son aquellos que cumplen con factores adicionales del programa y que no interfieren directamente con el funcionamiento esencial del mismo. Por el contrario, estos se centra en el cómo lograr las funciones posibles mas que el qué lograr. Es por esto que se pueden categorizar dentro de varios subgrupos así:
Requerimientos de rendimiento
- La aplicación debe correr en la terminal de los usuarios y proveer una interfaz de comando para la interacción usuario —> programa
- La aplicación debe manejar múltiples clientes y administradores en el sistema.
- El programa debe poder registrar cientos de productos con múltiples capacidades.
Requerimientos de seguridad
- Los administradores deben verificar su identidad con una contraseña
- Las claves deben guardarse de manera segura en una base de datos
- Los usuarios no pueden forzar la modificación de los productos
Requerimientos de calidad
- Los menús deben ser claros para el usuario
- Cuando se presenten errores, el sistema debe brindar respuesta al usuario
- En todo momento el usuario puede regresar al menu central
UML
Este paradigma ha logrado unificar las diferentes notaciones creadas para demostrar las relaciones entre diferentes elementos. En este caro, haré uso de StarUML, un software que permite construir todo tipo de diagramas de manera rápida y simplificada.
Diagrama de casos de usos
Para el presente escenario, el caso de uso comprar incluye el caso de uso de consultar, pues no se puede comprar sin consultar, mas si se puede consultar sin comprar.
Diagrama de clases
En este diagrama se presenta las relaciones de asociación disponibles entre las clases cliente, administrador e Item. En este se encuentran relaciones 1 a n y n a n.
Estructuras de datos
En la programación, existen estructuras de datos que permiten almacenar los datos de manera temporal o permanente en la memoria ram o en la memoria secundaria. En este caso, los arreglos pueden ser una buena manera de almacenar la información, empero no permiten conservar la información una vez el programa se cierre y se reinicie. Es por esto que dentro de este caso, se define que la mejor manera para almacenar los datos se convierte en una base de datos relacional usando mysqlite3. Presento el esquema de la base de datos.
Desarrollo y explicación
Con motivo del uso de las bases de datos y de un control acertado del cierre de la aplicación por parte de los usuarios y finalizar la ejecución del código, se importan las librerias requeridas
import sqlite3import sysSeguido a esto, se procede a crear las clases para el usuario, quien puede variar según el caso de uso entre cliente y administrador. En este caso, crear dicha clase nos permite mejorar el componente de heriencia de atributos de la clase padre y polimorfismo de OOP.
class Usuario: def __init__(self,id_usuario, nombre,): self.id= id_usuario self.nombre = nombreEsta clase cuenta con atributos básicos que son importantes según nuestro estudio del problema y el levantamiento de requerimientos. Sin embargo, algunos atributos cuentan con valores iniciales por defecto, como el ser cliente frecuente siendo negativo o falso, pues ningún cliente obtiene esta característica por defecto. Adicionalmente, a la clase Cliente se le definen métodos iniciales básicos para interactuar con la base de datos como el poder consultar y comprar.
class Cliente(Usuario): def __init__(self, id_usuario, nombre, esfrecuente=False, total=0.0): super().__init__(id_usuario, nombre) self.esfrecuente = esfrecuente self.total = total def consultar_item(self, basededatos, nombre_item): return basededatos.obtener(nombre_item) def comprar_item(self, basededatos, nombre_item, cantidad): return basededatos.realizar_compra(self.id, nombre_item,cantidad)Es importante recordar que cuando se define el método constructor de una clase que hereda elementos de otros, se debe ejecutar la función super() para heredar los atributos de la función padre.
class Administrador(Usuario): def __init__(self, id_usuario, nombre, contra,esenturno ): super().__init__(id_usuario, nombre) self.contra = contra self.esenturno = esenturno def agregar_item(self, basededatos, nombre, precio, categoria, cantidad): return basededatos.agregar_item(nombre, precio, categoria, cantidad) def consultar_item(self, basededatos, nombre_item): return basededatos.obtener(nombre_item)Adicionalmente, se define la clase relacionada con los items. Esta permitira instanciarse tantas veces sea necesaria para crear la información requerida en la libreria.
class Item: def __init__(self,id_item, nombre, precio, categoria, cantidad): self.id=id_item self.nombre = nombre self.precio = precio self.categoria = categoria self.cantidad = cantidadAhora bien, para lograr establecer una conexión con la base de datos, es importante crear la relación entre la librería importada y el método constructor de la misma. En este se debe crear el conector y el cursor para crear, acceder, modificar y cerrar la base de datos.
class basededatos: def __init__(self, nombrebase="libreria.db"): self.nombrebase = nombrebase self.con = None self.cursor = None self.conectar() self.crear_tabla() def conectar(self): self.con= sqlite3.connect(self.nombrebase) self.cursor=self.con.cursor() def cerrar(self): if self.con: self.con.close() def ejecutar(self, query, params=()): self.cursor.execute(query, params) self.con.commit() return self.cursorAl tiempo, es fundamental popular la base de datos con las tablas relacionales. Para esto, se ejecutan las queries con los parámetros establecidos en el esquema expuesto en la sección de bases de datos.
def crear_tabla(self): self.ejecutar(""" CREATE TABLE IF NOT EXISTS Item (ID INTEGER PRIMARY KEY AUTOINCREMENT, nombre TEXT UNIQUE NOT NULL, precio REAL NOT NULL, categoria TEXT, cantidad INT NOT NULL) """) self.ejecutar(""" CREATE TABLE IF NOT EXISTS Administrador( ID INTEGER PRIMARY KEY, nombre TEXT NOT NULL, contrasena TEXT NOT NULL, esenturno BOOLEAN NOT NULL DEFAULT 0 ) """) self.ejecutar(""" CREATE TABLE IF NOT EXISTS Cliente( ID INTEGER PRIMARY KEY AUTOINCREMENT, nombre TEXT NOT NULL, esfrecuente BOOLEAN NOT NULL DEFAULT 0, total REAL NOT NULL DEFAULT 0.0 ) """)Posteriormente, se crea el sistema CRUD (Create, Read, Update, Delete) de gestión de la base de datos en función de los métodos del usuario administrador o cliente.
Creardef agregar_item(self, nombre, precio, categoria, cantidad): query = "INSERT OR IGNORE INTO Item (nombre, precio, categoria, cantidad) VALUES (?, ?, ?, ?)" result = self.ejecutar(query, (nombre, precio, categoria, cantidad)) return result is not NoneLeer
def obtener(self, nombre= None): if nombre: query = "SELECT ID, nombre, precio, categoria, cantidad FROM Item WHERE nombre LIKE ?" result = self.ejecutar(query, ('%' + nombre + '%',)) else: query = "SELECT ID, nombre, precio, categoria, cantidad FROM Item" result = self.ejecutar(query)
items = [] if result: for row in result.fetchall(): items.append(Item(*row)) return itemsActualizar
def modificar_item(self, nombre, nuevoprecio = None, nuevacategoria=None, nuevacantidad = None): update = [] params = [] if nuevoprecio is not None: update.append("precio = ?") params.append(nuevoprecio) if nuevacategoria is not None: update.append("categoria = ?") params.append(nuevacategoria) if nuevacantidad is not None: update.append("cantidad = ?") params.append(nuevacantidad) if not update: return False params.append(nombre) query = f"UPDATE Item SET {', '.join(update)} WHERE nombre = ?" result = self.ejecutar(query, params)
return result and result.rowcount > 0Eliminar
def eliminar(self, nombre): query = "DELETE FROM Item WHERE nombre = ?" result= self.ejecutar(query, (nombre,)) return result and result.rowcount >0Así mismo, es importante considerar la creación de usuarios por defecto para brindar la oportunidad de una demonstración al cliente que pagó por el desarollo de la aplicación. Por esto, se ejecuta dentro de la base de datos el siguiente código:
def crearcliente(self, nombre): query = "INSERT INTO Cliente (nombre) VALUES (?)" result = self.ejecutar(query, (nombre,)) if result: return Cliente(result.lastrowid, nombre) return Nonedef inicializar_administrador(self): self.ejecutar("INSERT OR IGNORE INTO Administrador (ID, nombre, contrasena, esEnTurno) VALUES (1, 'SuperAdmin', 'admin123', 1)")Finalmente se procede a crear la clase Libreria que permite organizar y relacionar los atributos y métodos ya creados al tiempo que aquellos elementos que le permiten al usuario interactuar con la aplicación mediante la terminal.
class Libreria: def __init__(self): self.gestor = basededatos() self.usuario_actual = None self.datosejemplo() def datosejemplo(self): self.gestor.inicializar_administrador() self.gestor.agregar_item('El Principito', 15000, 'Ficción', 10) self.gestor.agregar_item('POO con Python', 45000, 'Programación', 5) # Inicializa un cliente de prueba self.gestor.crearcliente('Sergio') def menu(self): if isinstance(self.usuario_actual, Administrador): print("\n--- Menú de Administrador ---") print("1. Consultar Items (R)") print("2. Agregar Item (C)") print("3. Modificar Item (U)") print("4. Eliminar Item (D)") print("5. Cerrar Sesión") print("6. Salir") return input("Seleccione una opción: ") elif isinstance(self.usuario_actual, Cliente): print("\n--- Menú de Cliente ---") print("1. Consultar Items") print("2. Comprar Item") print("3. Cerrar Sesión") print("4. Salir") return input("Seleccione una opción: ") else: print("\n--- Menú Principal ---") print("1. Iniciar Sesión como Cliente") print("2. Iniciar Sesión como Administrador") print("3. Registrar Nuevo Cliente") print("4. Salir") return input("Seleccione una opción: ")Para finalizar, se crean las estructuras de control para gestionar las entradas del usuario.
def ejecutar(self):
while True: opcion = self.menu() if self.usuario_actual is None: if opcion == '1': self.iniciar_sesion_cliente() elif opcion == '2': self.iniciar_sesion_admin() elif opcion == '3': self.registrar_cliente() elif opcion == '4': print("Saliendo del sistema...") self.gestor.cerrar() sys.exit(0) else: print("Opción inválida.")
elif isinstance(self.usuario_actual, Administrador): if opcion == '1': self.func_consultar_items() elif opcion == '2': self.func_agregar_item() elif opcion == '3': self.func_modificar_item() elif opcion == '4': self.func_eliminar_item() elif opcion == '5': self.cerrar_sesion() elif opcion == '6': print("Saliendo del sistema...") self.gestor.cerrar() sys.exit(0) else: print("Opción inválida.")
elif isinstance(self.usuario_actual, Cliente): if opcion == '1': self.func_consultar_items() elif opcion == '2': self.func_comprar_item() elif opcion == '3': self.cerrar_sesion() elif opcion == '4': print("Saliendo del sistema...") self.gestor.cerrar() sys.exit(0) else: print("Opción inválida.")Por último, se ejecuta el programa desde la terminal y se obtiene que: