Proyectos
05Distribuidora ERP
05En desarrollo70h invertidas

Distribuidora ERP

SaaS multi-tenant para distribuidoras de agua y bidones — producto propio construido a partir de Celestina.

ReactViteSupabasePostgreSQLTailwindOneSignal
El problema

Celestina resolvía la logística de un solo cliente. Escalar el negocio a una segunda o tercera distribuidora con ese mismo código significaba clonar el proyecto y levantar un deploy y una base de Supabase nuevos por cada una — sin aislamiento real entre clientes y con cada bug o feature multiplicado por N instancias a mantener.

La idea

Fork de Celestina reescrito como SaaS multi-tenant real: una sola base de datos y un solo deploy sirven a todas las distribuidoras, aisladas entre sí por Row Level Security. La empresa y el rol de cada usuario viajan firmados en el token de sesión —inyectados por un Custom Access Token Hook— en vez de leerse de una tabla o del localStorage que el cliente podría manipular. Motor de facturación automática corriendo server-side con pg_cron, y un panel de operador de plataforma que administra altas de empresas sin acceso directo a los datos de ningún tenant.

El resultado

Producto funcional con el modelo multi-tenant completo: alta de una distribuidora nueva sin tocar código (marca, alias bancario, textos de WhatsApp se configuran por empresa), aprobación manual de paneles de repartidor nuevos, RLS con USING y WITH CHECK en cada tabla de negocio, y funciones SECURITY DEFINER con guard explícito de tenant propio para que ni una función de facturación pueda tocar datos de otra empresa por error. El operador de la plataforma administra altas sin ver un solo dato de negocio de ningún cliente — todo pasa por una Edge Function que valida sus permisos aparte. Documentado en profundidad (diseño multi-tenant, lógica de negocio, onboarding) para poder incorporar clientes nuevos sin re-explicar el sistema cada vez.

Arquitectura

Fork de Celestina reescrito para multi-tenancy real: misma base de código de producto, pero con aislamiento de datos por empresa y roles inyectados server-side en vez de leídos del cliente.

TenancyCustom Access Token Hook inyecta empresa_id y rol en el JWT al login — RLS los lee directo del token, no de una tabla que el cliente pueda falsear.
RLSCada tabla de negocio filtra por empresa_id = empresa_actual() con USING y WITH CHECK — una empresa no puede leer ni escribir filas de otra.
Funciones SECURITY DEFINERLas funciones de negocio que reciben un id como parámetro (ej. facturación) verifican explícitamente que ese id sea de la empresa de quien llama, porque dentro de una SECURITY DEFINER RLS no se aplica solo.
Facturaciónpg_cron dispara facturacion_diaria() una vez por día, recorre todas las empresas activas y genera la deuda de los clientes con abono mensual.
Panel de plataformaEl operador no lee tablas de negocio directo — todo pasa por una Edge Function que valida sus permisos contra plataforma_admins.
Decisiones técnicas

¿Clonar el código por cada cliente nuevo o construir multi-tenancy real?

elegido

Una sola base de datos y un solo deploy para todas las empresas, aisladas por RLS

descartado

Seguir el patrón de Celestina: un proyecto de Supabase y un deploy por cliente

Clonar no escala: cada bug hay que arreglarlo N veces, cada feature hay que desplegarla N veces. Multi-tenant real significa un solo lugar donde mantener el producto.

¿Dónde inyectar el rol y la empresa del usuario?

elegido

Custom Access Token Hook — quedan firmados en el JWT

descartado

Leerlos de una tabla o de localStorage en cada request

Si el rol se lee de un lugar que el cliente controla, se puede falsificar. Firmado en el token, el backend confía en el mismo valor que usa RLS del lado del servidor.

Errores reales · lo que salió mal
01

Dentro de una función SECURITY DEFINER, RLS no se aplica automáticamente aunque la tabla tenga políticas — hubo que agregar un chequeo explícito de que el id recibido por parámetro pertenece a la empresa de quien llama, en cada función de negocio que toca datos por id.

Qué mejoraría

Flujo de self-service para que una distribuidora nueva se dé de alta sola, sin intervención manual del operador. Panel de planes/facturación para que la comercialización sea autogestionada, no manual por mi parte.