case study · Sistema de stock · Full-stack
Depósitos CPS
El stock de toda la empresa en un solo lugar: qué hay, dónde está y a qué obra se fue.
El stock nunca se corrige "a dedo": cada cambio nace de un comprobante numerado, con usuario y fecha. El número que ves es la suma de su historia. Y el stock negativo es imposible por diseño, incluso con operaciones simultáneas.
Sistema interno de Grupo CPS para gestionar materiales y herramientas entre depósitos y obras: pedidos y órdenes de compra, transferencias, vales de consumo con rendición, reparaciones y ajustes. Cada operación queda asentada en un kardex inmutable con costeo por precio promedio ponderado, y el equipo ve stock valorizado, alertas y reportes en vivo.
01 · problema de negocio
Grupo CPS mueve materiales y herramientas entre varios depósitos y obras en simultáneo. Ese movimiento se seguía con planillas, mensajes y memoria: saber cuánto había de un artículo y dónde estaba implicaba llamar, contar y confiar.
Las consecuencias eran siempre las mismas: faltantes que aparecían con la obra arrancada, material "en viaje" que no figuraba en ningún lado, compras de apuro sin registro, y ningún número confiable de cuánto capital había inmovilizado en stock ni cuánto había consumido cada obra.
Y había un problema más fino: las herramientas. Una amoladora no es "3 amoladoras": es esta amoladora, con su número de serie, que está en tal obra, volvió rota o se perdió. Y alguien tiene que responder por ella.
02 · restricciones
Lo que había que tener en cuenta.
Varios depósitos y obras operando a la vez
Movimientos concurrentes desde distintos lugares. El saldo no puede depender de que dos personas no carguen al mismo tiempo.
Usuarios no técnicos, con roles distintos
Logística opera; jefes de obra y dirección consultan y piden. Cada uno tenía que ver y poder hacer solo lo suyo, sin que eso dependiera de la buena voluntad.
Servidor propio, sin nube de terceros
Los datos de la empresa quedan en su VPS. Eso fijó Supabase self-hosted, Docker y Coolify, y me dejó a cargo también de la operación.
Números que tienen que poder defenderse
El stock valorizado y el gasto por obra se usan para decidir compras y cerrar obras. Una estimación no sirve: cada número tiene que explicarse con comprobantes.
la decisión clave
El stock no se edita: se registra. Todo lo demás se deriva de ahí.
La decisión que ordena todo el sistema: nadie escribe "quedan 40 bolsas". Se registra el movimiento que lo explica (una compra recibida, una transferencia, un vale de consumo, un ajuste) como asiento de un kardex inmutable: los movimientos solo se insertan, y corregir es generar el contra-asiento, nunca borrar.
El saldo por depósito lo mantiene la propia base de datos, con una restricción que hace imposible el stock negativo incluso con operaciones concurrentes. Y lo que está viajando o en el service también es stock: los depósitos virtuales "En tránsito" y "Reparación" garantizan que la suma de todos los saldos siempre iguale el stock real de la empresa.
Sobre esa base, el costeo usa precio promedio ponderado congelado en cada movimiento: las transferencias no alteran el promedio y las devoluciones reingresan al costo del vale original. Por eso el "stock valorizado" del dashboard es un número defendible, no una estimación.
03 · decisiones técnicas y por qué
Por qué está armado así.
Kardex append-only
Los movimientos solo se insertan; corregir es contra-asentar. Con esa sola decisión salieron gratis la auditoría, las anulaciones, el kardex por período y la valorización: todo es una consulta sobre la misma tabla.
El saldo lo mantiene la base, no la app
Un trigger actualiza el saldo por depósito en cada asiento y una restricción lo impide bajar de cero. Dos operaciones simultáneas sobre el mismo artículo no pueden dejar stock negativo, sin locks en la aplicación.
Costeo promedio ponderado congelado en cada movimiento
El costo unitario queda fijado al asentar: las transferencias no alteran el promedio y las devoluciones reingresan al costo del vale original. Así la valorización no cambia retroactivamente.
RPCs transaccionales como única puerta de escritura
Cada operación es una función SECURITY DEFINER que valida rol y reglas de negocio dentro de una transacción. Las tablas de movimientos no tienen políticas de escritura: aunque alguien hable directo con la API, no puede saltarse las reglas.
Depósitos virtuales "En tránsito" y "Reparación"
Lo que viaja o está en el service también es stock, en un depósito propio. Así la suma de todos los saldos siempre iguala el stock real y nada queda "en ningún lado".
Herramientas como unidades, no como cantidades
Cada herramienta tiene número de serie y ciclo de vida propio: disponible → en obra → en reparación → rota o perdida. Las rotas o perdidas se imputan a la obra que las tenía.
04 · qué se descartó
Lo que descarté en el camino.
Editar cantidades directamente
Es lo que hacían las planillas. Sin la historia detrás del número, no hay forma de explicar un faltante, anular sin borrar ni valorizar el stock.
Reglas de negocio en la capa de aplicación
Cualquier cliente que hable con la API podría saltárselas, y cada regla habría que repetirla en cada pantalla. En PostgreSQL hay una sola puerta y no tiene atajo.
Supabase en la nube
Más cómodo, pero los datos de la empresa saldrían de su servidor. Pesó más tener el control y no pagar un plan; a cambio, la operación la llevo yo.
Un sistema solo de materiales
Sin herramientas por número de serie, la mitad del problema (quién tiene qué, en qué obra, y quién responde) seguía viviendo en mensajes.
qué se construyó
Todo el circuito del material, del pedido a la obra.
- 01
Pedidos y órdenes de compra
Cualquier usuario, también los jefes de obra, pide lo que necesita con destino y fecha límite. Logística arma las órdenes, que pueden mezclar proveedores por renglón y alimentan un histórico de precios por artículo y proveedor.
- 02
Recepciones parciales
Las compras y transferencias se reciben de a tandas. Cerrar con faltantes es una decisión explícita, con motivo: recién ahí se da de baja lo que no llegó.
- 03
Transferencias "en viaje"
El material que va de un depósito a otro pasa por un estado intermedio real: mientras viaja no está en ninguno de los dos, está en tránsito, con camión y personas asignadas al traslado.
- 04
Vales de consumo con rendición
Lo que sale a obra se rinde: lo usado se imputa al gasto de la obra y lo que vuelve reingresa al stock al costo congelado del vale.
- 05
Herramientas por número de serie
Cada unidad física tiene su ciclo de vida: disponible → en obra → en reparación → rota o perdida. Se rinden por unidad, y las rotas o perdidas se imputan a la obra que las tenía.
- 06
Dashboard y alertas por usuario
Cada usuario arma su inicio con los widgets que le sirven, y las notificaciones avisan solas: pedidos vencidos o por vencer, transferencias demoradas y stock bajo mínimo.
El sistema también resuelve lo aburrido pero crítico: kardex por depósito u obra en cualquier período, consulta de un artículo en todos los depósitos a la vez, compras en pesos o dólares con su moneda registrada, y órdenes de compra en PDF con lugar para firmas, porque el circuito físico con el proveedor también es parte del sistema.
arquitectura
La lógica de negocio vive en la base de datos, no en la interfaz.
Next.js + shadcn/ui
Pantallas de operación y consulta: dashboard personalizable, movimientos, kardex y catálogos.
Supabase self-hosted
PostgreSQL + Auth + API corriendo en el propio servidor de la empresa, no en una nube de terceros.
RPCs transaccionales
Cada operación es una función que valida rol y reglas de negocio dentro de la base, en una transacción.
Kardex inmutable
Los movimientos solo se insertan; corregir es contra-asentar. Un trigger mantiene los saldos por depósito.
RLS en todas las tablas
Las tablas de movimientos no tienen políticas de escritura: la única puerta de entrada son las RPCs.
VPS + Coolify
Deploy automático en cada push, migraciones aplicadas por CI y backups diarios del servidor y de la base.
Ocultar botones en la interfaz es ergonomía; la barrera real está en PostgreSQL. Aunque alguien hablara directo con la API, no puede saltarse las reglas: cada escritura pasa por una función que valida todo del lado de la base.
seguridad y trazabilidad
Un sistema de stock es un sistema de confianza: cada número tiene que poder defenderse.
Roles con lo mínimo necesario
El rol admin opera y administra; el rol consulta (jefes de obra, dirección) lee todo pero solo escribe sus propios pedidos de compra y su dashboard. Y nadie puede auto-promoverse de rol.
Usuarios cerrados por defecto
El registro público está deshabilitado y los perfiles nacen inactivos, sin acceso a datos, hasta que un admin los activa desde la app.
Quién hizo qué, siempre
Emitir, recibir, cancelar, rendir: cada acción queda registrada con usuario y fecha, visible en todas las pantallas para todos los usuarios.
Anular no es borrar
Las anulaciones generan el asiento inverso y la historia queda completa. Nada se corrige en silencio.
05 · resultado medible
En números.
de los saldos trazables: cada número es la suma de comprobantes con usuario y fecha.
stock negativo posible, por restricción de la base, incluso con operaciones concurrentes.
lo usan logística, jefes de obra y dirección de Grupo CPS, en el servidor de la empresa.
calidad e ingeniería
El núcleo se prueba donde vive: en SQL.
- Tests del núcleo SQL
- Suite de integración que recorre el circuito completo, en una transacción que se revierte
- Qué cubre
- Numeración, costeo PPP, recepciones parciales, rendiciones, herramientas por serie, RLS y roles
- Stock negativo
- Imposible por diseño, incluso en concurrencia
- Esquema versionado
- Migraciones aplicadas por CI sobre una base limpia antes de cada deploy
La suite corre dentro de una transacción que se revierte al final: prueba el núcleo real contra la base real, sin ensuciarla. Si la regla vive en PostgreSQL, el test también.
capturas
El sistema por dentro.



aprendizajes
- Si la gente no le cree al número, vuelve a la planilla. Por eso todo está pensado para que cada saldo se pueda explicar con sus comprobantes.
- Modelar movimientos en vez de cantidades simplificó todo lo demás: kardex, auditoría, anulaciones y valorización salieron del mismo diseño.
- Poner las reglas de negocio en la base de datos, y testearlas ahí, hace que la seguridad no dependa de qué botones muestra la interfaz.
- El circuito físico también es software: la orden de compra en PDF con lugar para firmas importa tanto como la tabla que la genera.
¿Querés hablar de este proyecto?
El código es interno de la empresa, pero puedo contar cómo está diseñado. Escribime si te interesa esto o querés hablar de una posición.