
Inicio
Presenta el sistema y al equipo.
/
Caso de estudio · Proyecto de equipo · UTN FRM 2025
Un sistema web que calcula cuánto y cuándo reponer cada artículo con modelos de investigación operativa, y genera las órdenes de compra solo.
OptiStock es el trabajo de la materia Investigación Operativa de Ingeniería en Sistemas. El desafío: llevar los modelos de inventario del papel a un sistema que funcione de punta a punta.
Un comercio compra a varios proveedores y vende a clientes. Cada artículo tiene su demanda, y cada proveedor su precio, demora y costo de pedido.
Que cada artículo tenga su política de reposición calculada y que el sistema dispare las compras, sin depender de que alguien se acuerde.
Son actores lógicos: el sistema no tiene login ni roles.
Una SPA en React y una API en Django, con 10 tablas y 6 módulos, donde una venta puede disparar sola una orden de compra y una tarea diaria revisa los artículos de intervalo fijo.
Hice las pantallas de Proveedores, Ventas y Clientes, el encabezado, la página principal y buena parte de los estilos. Incluye paginación, filtros, baja lógica y la gestión del proveedor predeterminado por artículo, conectadas con la API.
Frontend: Pablo Chaparro y Andrea Durán.
Backend: Brenda Mollaret, Leonardo Expósito y Nicolás López.
Cada uno se puede verificar en el sistema.
| ID | Requisito | Tipo | Prioridad |
|---|---|---|---|
| RF-01 | Alta, edición, consulta y baja lógica de artículos y familias. | Funcional | Alta |
| RF-02 | Gestionar proveedores y asociarlos a artículos con precio, demora y costo de pedido. | Funcional | Alta |
| RF-03 | Elegir un proveedor predeterminado por artículo y su modelo de inventario. | Funcional | Alta |
| RF-04 | Calcular lote óptimo, punto de pedido, stock de seguridad y costo de gestión. | Funcional | Alta |
| RF-05 | Registrar ventas validando stock y artículos dados de baja. | Funcional | Alta |
| RF-06 | Crear órdenes de compra manuales sin artículos duplicados ni órdenes activas previas. | Funcional | Alta |
| RF-07 | Llevar el ciclo Pendiente → Enviada → Finalizada, o Cancelada, y sumar stock al finalizar. | Funcional | Alta |
| RF-08 | Generar una orden automática cuando una venta deja el stock en el punto de pedido. | Funcional | Alta |
| RF-09 | Revisar por tarea programada los artículos de intervalo fijo. | Funcional | Alta |
| RF-10 | Listar artículos faltantes y a reponer. | Funcional | Media |
| RF-11 | Filtrar órdenes por estado y por origen manual o automático. | Funcional | Media |
| RF-12 | Impedir bajas que dejen datos inconsistentes, como un artículo con stock. | Funcional | Media |
| RNF-01 | Consistencia: ventas y órdenes se guardan en transacciones atómicas. | No funcional | Alta |
| RNF-02 | Rendimiento: listados paginados de a 10 registros. | No funcional | Media |
| RNF-03 | Usabilidad: formularios validados y avisos claros de error y éxito. | No funcional | Media |
| RNF-04 | Adaptable: interfaz usable en distintos tamaños de pantalla. | No funcional | Media |
| RNF-05 | Configuración: conexión a la base de datos por variables de entorno. | No funcional | Media |
Lo importante era el modelo de inventario. Lo demás se dejó afuera a propósito.
Evolución natural: autenticación, tests de los cálculos y estimar la demanda a partir de las ventas registradas.
Filtrá por actor. Como no hay permisos, cada caso se ata al endpoint que lo resuelve.
Alta, edición y baja lógica, con familia y datos de demanda.
Agrupa artículos por categoría.
Datos de contacto, búsqueda y baja lógica.
Define precio, demora, costo y modelo por proveedor.
Marca el proveedor y recalcula la política.
Artículos en o por debajo del stock de seguridad.
Bajo el punto de pedido y sin orden pendiente.
Corrige el stock a mano.
Toma el precio del proveedor y valida el stock proyectado.
Envía, finaliza o cancela. Al finalizar suma stock.
Elige cliente y artículos; descuenta stock.
Alta, búsqueda por nombre o CUIT y baja lógica.
La venta deja el stock en R y se pide el lote Q.
Corre cada día y crea las órdenes que correspondan.
Cliente y API separados, conectados por REST. Tocá cada bloque para ver qué hace.
Modelo de dominio. Los rombos marcan composición y las flechas, asociación. Las letras son las variables de las fórmulas.
El corazón del sistema. Cada artículo usa el modelo de su proveedor predeterminado.
Se evalúa en cada venta. Si el stock llega al punto de pedido, se genera una orden por el lote óptimo.
Una tarea diaria recorre los artículos de este modelo y pide lo necesario para cubrir el período de revisión y la entrega.
Mismas fórmulas que calcular_datos_inventario(), con z redondeado a dos decimales.
Los dos caminos por los que nace una orden automática.
Por capas en el cliente y por entidad en el servidor.
Las pantallas piden datos a un servicio y no arman URLs. Cambiar un endpoint toca un solo archivo.
Stock, estados y duplicados se validan en la API, aunque la interfaz también valide sus formularios.
Interfaces y enums de TypeScript reflejan los modelos, como los estados de una orden.
Qué se eligió, por qué y qué se resignó.
Demora, costo de pedido y tiempo entre revisiones dependen de a quién se le compra.
Guardarlos en la relación y calcular con el proveedor predeterminado. tiempoEntreRevisiones se movió de Articulo a esta tabla.
Cambiar de proveedor cambia la política. Costo: hay que recalcular al cambiar el predeterminado y no hay política sin él.
El lote fijo reacciona al stock; el intervalo fijo mira el calendario.
Evaluar el punto de pedido dentro de la venta y el intervalo fijo en una tarea programada.
Cada modelo se comporta como en la teoría. Costo: la lógica de creación de órdenes quedó en dos lugares.
Hacía falta una tarea diaria en un trabajo académico, sin sumar infraestructura.
BackgroundScheduler iniciado desde wsgi.py, con endpoint para forzarlo y cambiar el horario.
Sin broker ni workers aparte. Costo: con varios workers de Gunicorn correría una vez por proceso; escalar pide Celery o un cron externo.
Crear una venta toca stock, detalle y órdenes en una sola operación.
Concentrar validaciones y transacciones en create y update.
Atomicidad simple y vistas chicas. Costo: serializers largos y más difíciles de testear que una capa de servicios.
Las ventas y órdenes pasadas no pueden perder sus referencias.
Un campo fechaBaja por entidad y reglas previas, como no dar de baja con stock u órdenes activas.
Se conserva la historia. Costo: cada consulta debe recordar filtrar los registros dados de baja.
Cinco personas trabajando en paralelo sobre el mismo backend.
Diez apps, cada una con modelo, serializer y vista.
Menos conflictos y responsabilidades claras. Costo: mucho código repetido y dependencias cruzadas entre apps.
El nivel de servicio se carga como probabilidad y las fórmulas necesitan z.
Usar norm.ppf en lugar de una tabla manual.
Admite cualquier nivel de servicio. Costo: una dependencia pesada para un solo cálculo.
Capturas de la app real con la API simulada y datos inventados. Tocá una imagen para ampliarla.

Presenta el sistema y al equipo.
/
Maestro con stock, familia y acceso a proveedores y cálculos.
/api/articulos/
Filtra lo que está en riesgo de quiebre.
…/productos_faltantes/
Agrupa el catálogo.
/api/familias/
Búsqueda, bajas y artículos asociados con su modelo de inventario.
/api/proveedores/
Acciones según el estado: enviar, cancelar o finalizar.
/api/ordenes/
Separa lo que generó el sistema de lo manual.
/api/ordenes/automaticas/
Registro con detalle y control de stock.
/api/ventas/
Búsqueda por nombre o CUIT y baja lógica.
/api/clientes/Salidos del historial de commits. Filtrá por área.
tiempoEntreRevisiones estaba en Articulo, pero depende del proveedor elegido.
Se movió a ProveedorArticulo con migraciones, y el frontend sumó el dato en la pantalla de proveedor.
El subtotal de las órdenes automáticas quedaba mal calculado.
Se calcula el subtotal como precio del proveedor por cantidad.
Una orden para un artículo sin punto de pedido fallaba al proyectar el stock.
Se valida el dato antes y se devuelve un mensaje claro.
La revisión de intervalo fijo se caía con campos vacíos.
Cada dato faltante se controla, y ese artículo se saltea sin frenar a los demás.
La baja de un artículo no leía bien el estado de sus órdenes.
Se compara con los estados del enum: Pendiente y Enviada.
Se podía quitar un artículo de un proveedor con una orden pendiente.
Se agregó esa restricción antes de eliminar la asociación.
Probar la revisión implicaba esperar a las 8:00.
Endpoint para forzarla y otro para cambiar el horario.
El modal de proveedor no acompañaba los datos nuevos del modelo.
Rediseño del modal con modelo de inventario y tiempo entre revisiones.
El alta de venta fallaba al integrarse con la API.
Se corrigió el envío del alta y se probaron los ABM contra el backend.
La configuración estaba pensada solo para desarrollo local.
Conexión a MySQL por variables de entorno con dj-database-url, y Gunicorn para servir la app.
Lo que falta resolver, dicho sin vueltas.
La API está abierta y no hay usuarios. Es lo primero antes de exponerla.
Los cálculos y las reglas se probaron a mano.
El frontend apunta a localhost en cada servicio. Debería salir de una variable de entorno.
La fórmula y la condición de revisión de scheduler.py no coinciden del todo con el modelo teórico.
La ruta /inventario quedó como marcador, sin contenido.
Apps de Django, modelos y primeras pantallas.
ABM de artículos, proveedores y clientes, ventas y primeras órdenes de compra.
Modelos de inventario, scheduler con horario configurable, órdenes automáticas y documentación.
Preparación para desplegar: MySQL por variables de entorno y ajustes de CORS y hosts.
Desarrollo Full Stack