Mostrador
Una plataforma de comercio para negocios chicos: tienda online, WhatsApp, punto de venta y listas de precio mayoristas sobre un mismo backend. Es uno de los siete productos de la suite Vimo.

Un negocio chico de Argentina vende por la web, por chats de WhatsApp y en el mostrador, y cada canal tiene su propio stock y su propia lista de clientes. Mostrador los pone a todos sobre un mismo pedido, un mismo stock y un mismo cliente. Todas las tiendas comparten la base, así que una tienda no puede ver nunca lo de otra. El handoff de diseño traía 22 pantallas de panel, una tienda, un sitio y una app móvil.
Tres decisiones que lo definieron.
- 01
El aislamiento lo garantiza Postgres, no el código
Por qué. Cada tabla de negocio tiene tenant_id y RLS forzada. El rol de la API no es dueño de ninguna tabla y no puede saltearse las políticas. Toda consulta corre en una transacción que fija la tienda primero, así que un filtro olvidado devuelve vacío en vez de las filas de otra tienda. Un test recorre el catálogo de Postgres y falla si una tabla nueva no tiene política.
El costo. La RLS sola no hace que los índices sirvan, así que el tenant igual va en cada WHERE. Las foreign keys no pasan por las políticas, y eso pidió su propio test. Y COPY no funciona sobre estas tablas.
- 02
El outbox es la cola
Por qué. Un evento de dominio se escribe en la misma transacción que el cambio que lo causó. No existe el caso de un pedido guardado con su evento perdido. Un worker toma los eventos en lotes y los agrupa por tienda, así cien cambios de precio son una sola revalidación de la tienda. Editar un producto en el panel cambia la tienda en el mismo segundo.
El costo. La entrega es al menos una vez, así que cada consumidor tiene que ser idempotente. Además es un proceso más para levantar: sin él, un cambio nunca llega a la tienda y parece un problema de cache.
- 03
La búsqueda pública es un servicio en Rust
Por qué. La búsqueda con ILIKE tardaba 212 ms p95 sobre 50.000 productos y no encontraba nada con un error de tipeo. El servicio en Rust tiene el catálogo público en memoria y compara por trigramas: 1,7 ms p95, y encuentra. La búsqueda es el único camino del sistema que es trabajo de CPU, y sin recolector de basura los pedidos lentos quedan cerca de la mediana.
El costo. Un segundo lenguaje en el backend, y memoria que crece con cada catálogo. Si el servicio se cae, Caddy manda el mismo pedido a la API en Go: la tienda sigue andando, más lenta y sin tolerancia a tipeo.




Mostrador
Una plataforma de comercio para negocios chicos: tienda online, WhatsApp, punto de venta y listas de precio mayoristas sobre un mismo backend. Es uno de los siete productos de la suite Vimo.
- Rol
- Arquitectura y desarrollo completo
- Stack
- Next.js 15, Expo / React Native, TypeScript, Go, Postgres, Rust

Un negocio chico de Argentina vende por la web, por chats de WhatsApp y en el mostrador, y cada canal tiene su propio stock y su propia lista de clientes. Mostrador los pone a todos sobre un mismo pedido, un mismo stock y un mismo cliente. Todas las tiendas comparten la base, así que una tienda no puede ver nunca lo de otra. El handoff de diseño traía 22 pantallas de panel, una tienda, un sitio y una app móvil.
Tres decisiones que lo definieron.
El aislamiento lo garantiza Postgres, no el código
Por qué. Cada tabla de negocio tiene tenant_id y RLS forzada. El rol de la API no es dueño de ninguna tabla y no puede saltearse las políticas. Toda consulta corre en una transacción que fija la tienda primero, así que un filtro olvidado devuelve vacío en vez de las filas de otra tienda. Un test recorre el catálogo de Postgres y falla si una tabla nueva no tiene política.
El costo. La RLS sola no hace que los índices sirvan, así que el tenant igual va en cada WHERE. Las foreign keys no pasan por las políticas, y eso pidió su propio test. Y COPY no funciona sobre estas tablas.
El outbox es la cola
Por qué. Un evento de dominio se escribe en la misma transacción que el cambio que lo causó. No existe el caso de un pedido guardado con su evento perdido. Un worker toma los eventos en lotes y los agrupa por tienda, así cien cambios de precio son una sola revalidación de la tienda. Editar un producto en el panel cambia la tienda en el mismo segundo.
El costo. La entrega es al menos una vez, así que cada consumidor tiene que ser idempotente. Además es un proceso más para levantar: sin él, un cambio nunca llega a la tienda y parece un problema de cache.
La búsqueda pública es un servicio en Rust
Por qué. La búsqueda con ILIKE tardaba 212 ms p95 sobre 50.000 productos y no encontraba nada con un error de tipeo. El servicio en Rust tiene el catálogo público en memoria y compara por trigramas: 1,7 ms p95, y encuentra. La búsqueda es el único camino del sistema que es trabajo de CPU, y sin recolector de basura los pedidos lentos quedan cerca de la mediana.
El costo. Un segundo lenguaje en el backend, y memoria que crece con cada catálogo. Si el servicio se cae, Caddy manda el mismo pedido a la API en Go: la tienda sigue andando, más lenta y sin tolerancia a tipeo.



