Proyecto OOP: Sistema de Inventario en Python
En esta leccion aplicaremos todos los conceptos de OOP para construir un sistema de inventario completo. Es el tipo de proyecto que podrias mostrar en un portfolio o entrevista.
Diseno del sistema- Producto: clase base abstracta
- ProductoPerecible y ProductoElectronico: clases especializadas
- Inventario: gestiona la coleccion
- Reporte: genera informes
from datetime import date
from abc import ABC, abstractmethod
class Producto(ABC):
_contador_ids = 0
def __init__(self, nombre, precio, cantidad, categoria):
Producto._contador_ids += 1
self.id = Producto._contador_ids
self.nombre = nombre
self._precio = precio
self.cantidad = cantidad
self.categoria = categoria
self.fecha_ingreso = date.today()
@property
def precio(self):
return self._precio
@precio.setter
def precio(self, nuevo):
if nuevo < 0:
raise ValueError('El precio no puede ser negativo')
self._precio = nuevo
@property
def valor_total(self):
return self._precio * self.cantidad
@abstractmethod
def es_vendible(self):
pass
@abstractmethod
def info_adicional(self):
pass
def __str__(self):
estado = 'Disponible' if self.es_vendible() else 'No disponible'
return f'[{self.id}] {self.nombre} | {self._precio} | Stock: {self.cantidad} | {estado}'
def __lt__(self, other):
return self._precio < other._precio
Clases especializadas
class ProductoPerecible(Producto):
def __init__(self, nombre, precio, cantidad, categoria, fecha_vencimiento):
super().__init__(nombre, precio, cantidad, categoria)
self.fecha_vencimiento = fecha_vencimiento
@property
def dias_para_vencer(self):
return (self.fecha_vencimiento - date.today()).days
def es_vendible(self):
return self.cantidad > 0 and self.dias_para_vencer > 0
def info_adicional(self):
dias = self.dias_para_vencer
if dias < 0:
return f'VENCIDO hace {abs(dias)} dias'
elif dias <= 7:
return f'URGENTE: Vence en {dias} dias'
return f'Vence en {dias} dias ({self.fecha_vencimiento})'
class ProductoElectronico(Producto):
def __init__(self, nombre, precio, cantidad, categoria, marca, garantia_meses):
super().__init__(nombre, precio, cantidad, categoria)
self.marca = marca
self.garantia_meses = garantia_meses
def es_vendible(self):
return self.cantidad > 0
def info_adicional(self):
return f'Marca: {self.marca} | Garantia: {self.garantia_meses} meses'
class ProductoGeneral(Producto):
def es_vendible(self):
return self.cantidad > 0
def info_adicional(self):
return f'Categoria: {self.categoria}'
La clase Inventario
class Inventario:
def __init__(self, nombre):
self.nombre = nombre
self._productos = {}
def agregar(self, producto):
if producto.id in self._productos:
raise ValueError(f'ID {producto.id} ya existe')
self._productos[producto.id] = producto
print(f'Agregado: {producto.nombre}')
def buscar_id(self, id_producto):
return self._productos.get(id_producto)
def buscar_nombre(self, nombre):
return [p for p in self._productos.values() if nombre.lower() in p.nombre.lower()]
def vender(self, id_producto, cantidad):
p = self.buscar_id(id_producto)
if not p:
raise ValueError('Producto no encontrado')
if not p.es_vendible():
raise ValueError(f'{p.nombre} no disponible')
if cantidad > p.cantidad:
raise ValueError(f'Stock insuficiente: {p.cantidad}')
p.cantidad -= cantidad
total = p.precio * cantidad
print(f'Venta: {cantidad}x {p.nombre} = {total}')
return total
def reponer(self, id_producto, cantidad):
p = self.buscar_id(id_producto)
if not p:
raise ValueError('Producto no encontrado')
p.cantidad += cantidad
print(f'Reposicion: +{cantidad} de {p.nombre}')
def bajo_stock(self, minimo=5):
return [p for p in self._productos.values() if p.cantidad <= minimo]
def proximos_vencer(self, dias=7):
return [
p for p in self._productos.values()
if isinstance(p, ProductoPerecible) and 0 < p.dias_para_vencer <= dias
]
def valor_total(self):
return sum(p.valor_total for p in self._productos.values())
def __len__(self):
return len(self._productos)
def __iter__(self):
return iter(self._productos.values())
Clase Reporte
class Reporte:
@staticmethod
def resumen(inventario):
print(f'REPORTE: {inventario.nombre}')
print('=' * 50)
print(f'Total productos: {len(inventario)}')
print(f'Valor total: {inventario.valor_total():.2f}')
bajos = inventario.bajo_stock()
if bajos:
print(f'ALERTA bajo stock ({len(bajos)}): ')
for p in bajos:
print(f' - {p.nombre}: {p.cantidad}')
vencen = inventario.proximos_vencer()
if vencen:
print(f'ALERTA proximos a vencer ({len(vencen)}): ')
for p in vencen:
print(f' - {p.nombre}: {p.info_adicional()}')
print('Todos los productos:')
for producto in sorted(inventario):
print(f' {producto}')
print(f' {producto.info_adicional()}')
Usando el sistema
from datetime import timedelta
tienda = Inventario('TechStore')
tienda.agregar(ProductoElectronico('Laptop Gaming', 1200, 5, 'PC', 'Asus', 24))
tienda.agregar(ProductoElectronico('Mouse', 35, 50, 'Accesorios', 'Logitech', 12))
tienda.agregar(ProductoElectronico('Monitor 4K', 450, 3, 'Monitores', 'Samsung', 36))
tienda.agregar(ProductoPerecible('Bateria Alcalina', 5.99, 100, 'Accesorios', date.today() + timedelta(365)))
tienda.agregar(ProductoPerecible('Bateria Recargable', 15.99, 4, 'Accesorios', date.today() + timedelta(5)))
print('--- Procesando ventas ---')
tienda.vender(1, 2)
tienda.vender(2, 10)
try:
tienda.vender(3, 10) # Solo hay 3
except ValueError as e:
print(f'Error: {e}')
tienda.reponer(3, 5)
Reporte.resumen(tienda)
resultados = tienda.buscar_nombre('bateria')
print('Busqueda bateria:')
for p in resultados:
print(f' {p}')
💡 Concepto Clave
Revisemos los puntos más importantes de esta lección antes de continuar.
Resumen del modulo OOP
Aprendiste:
- Clases y objetos: plantillas e instancias
- Herencia: reutilizar y extender codigo
- Polimorfismo: mismo metodo, diferentes comportamientos
- Metodos especiales: integracion con Python
- Encapsulacion: proteger datos
- Clases abstractas: contratos para subclases
- Propiedades: control sobre atributos
En el proximo y ultimo modulo conectaremos Python con el mundo real: archivos, APIs e internet.
🧠Pon a prueba tu conocimiento
¿Cuál es el aspecto más importante que aprendiste en esta lección?
- Comprendo el concepto principal y puedo explicarlo con mis palabras
- Entiendo cómo aplicarlo en mi situación especÃfica
- Necesito repasar algunas partes antes de continuar
- Quiero ver más ejemplos prácticos del tema
✅ ¡Excelente! Continúa con la siguiente lección para profundizar más.