skip to content
Sergio's Blog CS

FirstClasssOOP

/ 9 min read

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

Librerias Importadas
import sqlite3
import sys

Seguido 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.

Clase Usuario
class Usuario:
def __init__(self,id_usuario, nombre,):
self.id= id_usuario
self.nombre = nombre

Esta 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.

Clase Cliente
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.

Clase Administrador
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.

Clase Item
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 = cantidad

Ahora 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.

Conexión inicial 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.cursor

Al 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.

Creación de tablas
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.

Crear
Create
def 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 None

Leer
Read
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 items

Actualizar
Update
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 > 0

Eliminar
Delete
def eliminar(self, nombre):
query = "DELETE FROM Item WHERE nombre = ?"
result= self.ejecutar(query, (nombre,))
return result and result.rowcount >0

Así 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 None
def 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.

Clase Libreia
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.

Gestión de estructuras
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:

Video de demonstración técnica y verificación de requerimientos

Footnotes

  1. V/Sure
  2. Geeks4Geeks