Depósitos CPS

El stock de toda la empresa en un solo lugar: qué hay, dónde está y a qué obra se fue.

RolDesarrollador único: modelo de datos, núcleo SQL, backend, frontend y deployClienteGrupo CPS, construcción y aberturas de aluminioAño2026CódigoPrivado · herramienta interna de Grupo CPS
Next.jsTypeScriptTailwind 4shadcn/uiSupabasePostgreSQLRLSCoolify
100% trazable

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.

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.

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.

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.

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.

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.

Todo el circuito del material, del pedido a la obra.

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

  2. 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ó.

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

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

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

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

Cómo encaja todo, en un diagrama que se puede tocar.

Cinco vistas con cajas que se pueden mover y un recorrido explicado paso a paso: quién usa el sistema y qué hay detrás, el circuito del material del pedido a la obra, cómo se asienta y se valoriza cada movimiento, la vida de una herramienta con número de serie y cómo está construido.

La lógica de negocio vive en la base de datos, no en la interfaz.

UI

Next.js + shadcn/ui

Pantallas de operación y consulta: dashboard personalizable, movimientos, kardex y catálogos.

API

Supabase self-hosted

PostgreSQL + Auth + API corriendo en el propio servidor de la empresa, no en una nube de terceros.

RPC

RPCs transaccionales

Cada operación es una función que valida rol y reglas de negocio dentro de la base, en una transacción.

LED

Kardex inmutable

Los movimientos solo se insertan; corregir es contra-asentar. Un trigger mantiene los saldos por depósito.

SEC

RLS en todas las tablas

Las tablas de movimientos no tienen políticas de escritura: la única puerta de entrada son las RPCs.

OPS

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.

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.

En números.

100%

de los saldos trazables: cada número es la suma de comprobantes con usuario y fecha.

0

stock negativo posible, por restricción de la base, incluso con operaciones concurrentes.

En producción

lo usan logística, jefes de obra y dirección de Grupo CPS, en el servidor de la empresa.

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.

El sistema por dentro.

Dashboard de inicio del sistema de depósitos de Grupo CPS
El dashboard de inicio: stock valorizado, movimientos de los últimos 30 días, stock por depósito, gasto por obra y alertas de pedidos vencidos, transferencias demoradas y stock bajo mínimo.
Kardex por depósito con saldos y movimientos del período
El kardex por depósito u obra: saldos del período, cada movimiento con su comprobante y su usuario, y anulaciones como contra-asiento. Nada se borra.
Orden de compra generada en PDF lista para firmar
Orden de compra en PDF, lista para imprimir y firmar con el proveedor: el circuito físico también sale del sistema.
  • 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.