Volver al curso

Python Desde Cero

leccion
21 / 21
beginner
20 horas
Python para el Mundo Real

Proyecto Final: Aplicacion Completa con Python

Lectura
40 min~6 min lectura

Proyecto Final: Aplicacion Completa con Python

Ha llegado el momento de integrar todo lo aprendido en el curso. Construiremos un sistema completo de gestion de inventario con las siguientes caracteristicas: lectura y escritura de archivos CSV/JSON, OOP, manejo de errores, consumo de API externa y generacion de reportes.

Descripcion del proyecto

Sistema de Gestion de Inventario con Precios Actualizados:

  • Carga productos desde un CSV
  • Obtiene tipo de cambio de una API real
  • Calcula precios en multiples monedas
  • Detecta productos con bajo stock y proximos a vencer
  • Genera reportes en TXT y JSON
  • Interfaz de linea de comandos interactiva
Estructura del proyecto
inventario_app/
    main.py            # Punto de entrada
    modelos.py         # Clases Producto, Inventario
    api_cliente.py     # Consumo de APIs externas
    reportes.py        # Generacion de reportes
    datos/
        productos.csv  # Datos iniciales
        config.json    # Configuracion
modelos.py
from datetime import date
from abc import ABC, abstractmethod

class Producto(ABC):
    _contador = 0
    
    def __init__(self, nombre, precio_usd, cantidad, categoria):
        Producto._contador += 1
        self.id = Producto._contador
        self.nombre = nombre
        self.precio_usd = precio_usd
        self.cantidad = cantidad
        self.categoria = categoria
    
    def precio_en(self, moneda, tasa_cambio):
        return self.precio_usd * tasa_cambio
    
    @property
    def valor_total_usd(self):
        return self.precio_usd * self.cantidad
    
    @abstractmethod
    def es_vendible(self):
        pass
    
    @abstractmethod
    def alerta(self):
        pass
    
    def __str__(self):
        return f'[{self.id}] {self.nombre} (${self.precio_usd}) x{self.cantidad}'
    
    def __lt__(self, other):
        return self.precio_usd < other.precio_usd


class ProductoPerecible(Producto):
    def __init__(self, nombre, precio_usd, cantidad, categoria, fecha_vencimiento):
        super().__init__(nombre, precio_usd, cantidad, categoria)
        self.fecha_vencimiento = fecha_vencimiento
    
    @property
    def dias_restantes(self):
        return (self.fecha_vencimiento - date.today()).days
    
    def es_vendible(self):
        return self.cantidad > 0 and self.dias_restantes > 0
    
    def alerta(self):
        if self.dias_restantes <= 0:
            return f'VENCIDO: {self.nombre}'
        if self.dias_restantes <= 7:
            return f'URGENTE: {self.nombre} vence en {self.dias_restantes} dias'
        if self.cantidad <= 5:
            return f'BAJO STOCK: {self.nombre} ({self.cantidad} unidades)'
        return None


class ProductoGeneral(Producto):
    def es_vendible(self):
        return self.cantidad > 0
    
    def alerta(self):
        if self.cantidad <= 5:
            return f'BAJO STOCK: {self.nombre} ({self.cantidad} unidades)'
        return None


class Inventario:
    def __init__(self):
        self._productos = {}
    
    def agregar(self, producto):
        self._productos[producto.id] = producto
    
    def buscar(self, id_o_nombre):
        if isinstance(id_o_nombre, int):
            return self._productos.get(id_o_nombre)
        return [
            p for p in self._productos.values()
            if id_o_nombre.lower() in p.nombre.lower()
        ]
    
    def vender(self, id_producto, cantidad):
        p = self._productos.get(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: solo hay {p.cantidad}')
        p.cantidad -= cantidad
        return p.precio_usd * cantidad
    
    def alertas(self):
        return [a for p in self._productos.values() if (a := p.alerta())]
    
    def valor_total_usd(self):
        return sum(p.valor_total_usd for p in self._productos.values())
    
    def __len__(self):
        return len(self._productos)
    
    def __iter__(self):
        return iter(self._productos.values())
    
    def todos(self):
        return list(self._productos.values())
api_cliente.py
import requests
import json
from pathlib import Path
from datetime import datetime, timedelta

def obtener_tipo_cambio(moneda='ARS', cache_horas=6):
    cache_file = Path(f'.cache_tasa_{moneda}.json')
    
    if cache_file.exists():
        cached = json.loads(cache_file.read_text())
        edad = datetime.now() - datetime.fromisoformat(cached['timestamp'])
        if edad < timedelta(hours=cache_horas):
            print(f'[Cache] Tasa USD/{moneda}: {cached["tasa"]}')
            return cached['tasa']
    
    try:
        respuesta = requests.get(
            f'https://open.er-api.com/v6/latest/USD',
            timeout=10
        )
        respuesta.raise_for_status()
        datos = respuesta.json()
        tasa = datos['rates'].get(moneda, 1.0)
        
        cache_file.write_text(json.dumps({
            'timestamp': datetime.now().isoformat(),
            'tasa': tasa
        }))
        
        print(f'[API] Tasa USD/{moneda}: {tasa}')
        return tasa
    
    except Exception as e:
        print(f'Error obteniendo tasa: {e}. Usando tasa 1.0')
        return 1.0
reportes.py
import csv
import json
from datetime import datetime
from pathlib import Path

def guardar_reporte_txt(inventario, tasa_ars, archivo='reporte.txt'):
    with open(archivo, 'w', encoding='utf-8') as f:
        f.write('REPORTE DE INVENTARIO\n')
        f.write('=' * 50 + '\n')
        f.write(f'Generado: {datetime.now().strftime("%Y-%m-%d %H:%M")}\n')
        f.write(f'Total productos: {len(inventario)}\n')
        f.write(f'Valor total USD: {inventario.valor_total_usd():.2f}\n')
        f.write(f'Valor total ARS: {inventario.valor_total_usd() * tasa_ars:.2f}\n')
        f.write('\n')
        
        alertas = inventario.alertas()
        if alertas:
            f.write('ALERTAS:\n')
            for a in alertas:
                f.write(f'  - {a}\n')
            f.write('\n')
        
        f.write('TODOS LOS PRODUCTOS:\n')
        f.write('-' * 50 + '\n')
        for p in sorted(inventario):
            precio_ars = p.precio_usd * tasa_ars
            f.write(f'{p.nombre}\n')
            f.write(f'  Precio: USD {p.precio_usd} / ARS {precio_ars:.0f}\n')
            f.write(f'  Stock: {p.cantidad} | Categoria: {p.categoria}\n')
    
    print(f'Reporte guardado en {archivo}')

def exportar_csv(inventario, archivo='inventario_export.csv'):
    with open(archivo, 'w', newline='', encoding='utf-8') as f:
        campos = ['id', 'nombre', 'precio_usd', 'cantidad', 'categoria', 'valor_total']
        writer = csv.DictWriter(f, fieldnames=campos)
        writer.writeheader()
        for p in inventario:
            writer.writerow({
                'id': p.id,
                'nombre': p.nombre,
                'precio_usd': p.precio_usd,
                'cantidad': p.cantidad,
                'categoria': p.categoria,
                'valor_total': p.valor_total_usd
            })
    print(f'CSV exportado: {archivo}')
main.py
import csv
from datetime import date, timedelta
from modelos import Inventario, ProductoGeneral, ProductoPerecible
from api_cliente import obtener_tipo_cambio
from reportes import guardar_reporte_txt, exportar_csv

def cargar_datos_ejemplo(inventario):
    productos = [
        ProductoGeneral('Laptop Gaming', 1200, 5, 'Computadoras'),
        ProductoGeneral('Mouse Inalambrico', 35, 50, 'Accesorios'),
        ProductoGeneral('Monitor 4K', 450, 3, 'Monitores'),
        ProductoGeneral('Teclado Mecanico', 80, 4, 'Accesorios'),
        ProductoPerecible('Bateria AA x4', 5.99, 100, 'Accesorios', date.today() + timedelta(365)),
        ProductoPerecible('Pantalla Tactil', 299, 2, 'Componentes', date.today() + timedelta(5)),
    ]
    for p in productos:
        inventario.agregar(p)

def menu_principal():
    inventario = Inventario()
    cargar_datos_ejemplo(inventario)
    tasa_ars = obtener_tipo_cambio('ARS')
    
    while True:
        print('\n=== SISTEMA DE INVENTARIO ===')
        print('1. Ver todos los productos')
        print('2. Buscar producto')
        print('3. Registrar venta')
        print('4. Ver alertas')
        print('5. Generar reporte TXT')
        print('6. Exportar CSV')
        print('7. Ver valor total del inventario')
        print('0. Salir')
        
        opcion = input('\nOpcion: ').strip()
        
        if opcion == '1':
            print('\nProductos:')
            for p in sorted(inventario):
                precio_ars = p.precio_usd * tasa_ars
                estado = 'OK' if p.es_vendible() else 'NO VENDIBLE'
                print(f'  {p} | ARS {precio_ars:.0f} [{estado}]')
        
        elif opcion == '2':
            termino = input('Buscar: ')
            resultados = inventario.buscar(termino)
            for p in resultados:
                print(f'  {p}')
        
        elif opcion == '3':
            try:
                id_p = int(input('ID del producto: '))
                cant = int(input('Cantidad: '))
                total = inventario.vender(id_p, cant)
                print(f'Venta exitosa: USD {total:.2f} / ARS {total * tasa_ars:.0f}')
            except (ValueError, KeyError) as e:
                print(f'Error: {e}')
        
        elif opcion == '4':
            alertas = inventario.alertas()
            if alertas:
                print('\nALERTAS:')
                for a in alertas:
                    print(f'  - {a}')
            else:
                print('Sin alertas activas')
        
        elif opcion == '5':
            guardar_reporte_txt(inventario, tasa_ars)
        
        elif opcion == '6':
            exportar_csv(inventario)
        
        elif opcion == '7':
            total_usd = inventario.valor_total_usd()
            print(f'Valor total: USD {total_usd:.2f} / ARS {total_usd * tasa_ars:.0f}')
        
        elif opcion == '0':
            print('Hasta pronto!')
            break
        
        else:
            print('Opcion invalida')

if __name__ == '__main__':
    menu_principal()
Como ejecutar el proyecto
# Instalar dependencias
pip install requests

# Crear los archivos en la misma carpeta
# modelos.py, api_cliente.py, reportes.py, main.py

# Ejecutar
python main.py
Que aprendiste en este curso

A lo largo de los 5 modulos del curso cubriste:

Modulo 1 - Fundamentos:

  • Variables y tipos de datos
  • Operadores y expresiones
  • Input/output basico

Modulo 2 - Control de Flujo y Funciones:

  • if/elif/else
  • Bucles for y while
  • Funciones con parametros y return
  • Manejo de errores con try/except

Modulo 3 - Estructuras de Datos:

  • Listas y tuplas
  • Diccionarios y sets
  • Comprension de listas

Modulo 4 - Programacion Orientada a Objetos:

  • Clases y objetos
  • Herencia y polimorfismo
  • Metodos especiales

Modulo 5 - Python para el Mundo Real:

  • Manejo de archivos TXT, CSV, JSON
  • Automatizacion de tareas
  • Consumo de APIs REST
  • Proyecto final integrador
💡 Concepto Clave

Revisemos los puntos más importantes de esta lección antes de continuar.

Proximos pasos

Ahora que dominas los fundamentos, aqui tienes caminos para continuar:

  1. Web con Django o FastAPI: Construye aplicaciones web con Python
  2. Data Science: pandas, numpy, matplotlib para analisis de datos
  3. Machine Learning: scikit-learn, TensorFlow, PyTorch
  4. Automatizacion avanzada: Selenium, Playwright para browser automation
  5. DevOps: Docker, CI/CD con Python

Sigue practicando. La mejor forma de aprender a programar es construir proyectos reales que te importen. Felicitaciones por completar el curso de Python desde Cero!

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