Producto propio
Pullstok: ERP SaaS completo con tienda online
Mi producto propio: un ERP SaaS multi-tenant que diseño, construyo y opero. Punto de venta, stock, facturación electrónica ARCA, contabilidad (en desarrollo), tienda online por subdominio, Mercado Pago, bots con IA y un agente de impresión local. Monorepo con 5 apps, API en Node con Prisma y PostgreSQL.
Stack tecnológico
Las tecnologías usadas y por qué.
Capturas
El problema
Una empresa que vende, minorista o mayorista, con una o varias sucursales, suele tener la operación repartida: el stock en una planilla, la facturación en otro sistema, el cobro con QR en otra app, la contabilidad por fuera y la tienda online por separado. Cada herramienta suelta es un lugar más donde cargar los mismos datos dos veces y una fuente más de inconsistencias.
Pullstok es mi respuesta a eso: un ERP SaaS multi-tenant que junta toda la operación de una empresa en un solo lugar, desde el mostrador hasta la contabilidad. No es un trabajo para un cliente: es un producto propio, que diseño, construyo, despliego y opero de punta a punta.
Un solo sistema
Del presupuesto a la venta y de la venta a la factura electrónica, sin volver a cargar nada. El siguiente paso, en desarrollo: que cada operación genere su asiento contable.
Cada comercio, aislado
Todos los comercios comparten la plataforma, pero cada uno ve solo sus productos, sus clientes y sus ventas.
Conectado al mostrador
Impresoras térmicas, balanzas con etiqueta de peso, lector de códigos con la cámara y cobro con QR de Mercado Pago.
Qué hace
Venta y mostrador
- Punto de venta unificado: venta por unidad, por peso, por monto, bolsa cerrada y por unidad de blíster, con descuentos por línea y precios mayoristas.
- Caja: apertura y cierre de sesiones de caja.
- POS offline: el catálogo se guarda en IndexedDB y las ventas sin conexión van a una cola que se sincroniza después, con un identificador idempotente para que ninguna venta se duplique.
- Mercado Pago QR: vinculación de cuenta por OAuth y un punto de cobro por sucursal.
Circuito comercial
- Presupuestos, pedidos y ventas, con remitos sin precio para mayoristas, generables desde el mismo POS.
- Clientes con cuenta corriente: cobranzas y resumen de cuenta en PDF.
- Proveedores y compras: cuentas de proveedores y compensación de saldos entre clientes y proveedores.
Stock y catálogo
- Productos con variantes, presentaciones y varios códigos de barra, stock por sucursal y códigos Code128 internos.
- Escáner de códigos con la cámara del celular o la notebook.
- Categorías con presets por rubro.
- Listas de precios: lectura de listas de proveedores en PDF con matching contra el catálogo y cola de revisión, importación de costos, aumentos masivos por marca, escalas de precio y carga de productos por CSV.
- Balanzas: lectura de etiquetas EAN-13 con peso de balanzas Systel Cuora configuradas en Qendra, asignación de códigos de balanza y exportación del CSV para cargarlas.
Fiscal y contable
- Facturación electrónica ARCA (ex AFIP): facturas A, B y C, notas de crédito, consulta de padrón y comprobantes internos que nunca van al fisco.
- Puente venta a factura: una venta se convierte en factura con los importes fiscalmente correctos.
- Contabilidad de partida doble (en desarrollo): el módulo que estoy construyendo ahora. Plan de cuentas, ejercicios con cierre, libro diario, mayor, balance de sumas y saldos, y asientos automáticos de ventas, cobranzas, compras y pagos a proveedores.
Canales digitales
- Tienda online por comercio, en su propio subdominio, conectada al mismo catálogo y al mismo stock.
- Bots con IA: chat de la tienda con derivación a un humano en tiempo real, asistente de ventas para vendedores y bot de WhatsApp con function calling que arma borradores de pedido.
Plataforma
- Superadmin para crear, administrar y suspender comercios.
- Onboarding guiado en tres pasos: negocio, categorías y productos.
- Planes (Básico, Pro y Premium) con módulos habilitados por plan y por comercio.
- Reportes: dashboard diario, estadísticas con gráficos, exportación de ventas a PDF y Excel, y panel por vendedor.
- Branding por comercio, horario de atención que bloquea el acceso fuera de hora, recupero de contraseña por mail y backups diarios por comercio.
Arquitectura
Pullstok es un monorepo con pnpm workspaces y cinco aplicaciones, cada una con una responsabilidad clara:
- App ERP: SPA en React 18 con Vite, Tailwind v4 y shadcn/Radix. Todas las vistas se cargan de forma lazy, con layouts separados para autenticación, panel, onboarding y superadmin. TanStack Query maneja el estado del servidor y Socket.io manda señales livianas de cambio que invalidan las queries, así la pantalla se actualiza sola.
- API: Express en capas (rutas, middlewares, controladores, servicios, Prisma). La lógica fiscal y contable vive en módulos puros, sin Express ni Prisma, para poder testearla aislada. Las integraciones externas (ARCA, Mercado Pago) están detrás de clientes con su versión mock, al estilo puertos y adaptadores.
- Tienda: Astro con adapter Node, islas de React y un carrito persistente. El checkout pasa por un proxy servidor a servidor, así la API no queda expuesta por CORS.
- Landing: Astro estático con precios, preguntas frecuentes y chat con IA.
- Print agent: un ejecutable de Windows sin dependencias que conecta la nube con la impresora del mostrador.
Multi-tenancy y seguridad
- El comercio de cada request se resuelve en un middleware (desde el JWT, desde el subdominio de la tienda o desde el token del agente de impresión) y se guarda en un contexto con AsyncLocalStorage.
- Una extensión de Prisma filtra automáticamente unos 45 modelos por comercio: inyecta el
organizationIden cada consulta y escritura, falla si no hay contexto y bloquea las operaciones que no se pueden filtrar de forma segura. Las operaciones de plataforma usan un cliente aparte, explícito. - Roles: superadmin, admin, gerencia, vendedor, cajero y empleado, controlados por middleware. El chat de la tienda usa un token de invitado que el ERP rechaza.
- Validación con Zod en cada endpoint: el body validado reemplaza al original, lo que evita la asignación masiva de campos.
- Errores centralizados: los 4xx devuelven mensajes genéricos y los 5xx se reportan a Sentry sin filtrar detalles al cliente.
Decisiones técnicas destacadas
Aislamiento de comercios en el ORM
En un SaaS multi-tenant, el peor bug posible es que un comercio vea datos de otro. En lugar de confiar en acordarme de filtrar en cada consulta, el filtro vive en una extensión de Prisma alimentada por AsyncLocalStorage. Olvidarse el filtro deja de ser posible: la consulta falla.
ARCA desde cero, sobre SOAP
Sin SDK de terceros: firma del ticket de acceso como CMS/PKCS#7 con node-forge, WSAA para autenticar y WSFEv1 para autorizar comprobantes. El ticket se cachea solo en memoria y la clave privada de cada comercio se guarda cifrada con AES-256-GCM. Cada comercio sube su propio certificado y puede probar en homologación antes de pasar a producción.
Puente nube a hardware local
Un navegador no puede imprimir RAW en una térmica. El print agent es un ejecutable único de Node (SEA) que escucha solo en localhost con lista de orígenes permitidos y manda bytes ESC/POS al spooler de Windows. Para imprimir desde el celular, el agente se vincula con un código y consulta la API por trabajos de impresión, con heartbeat y backoff.
Plata en centavos, fiscal correcto
La conversión de venta a factura calcula el neto desde precios con IVA incluido y transforma descuentos y recargos en líneas propias, para que lo facturado coincida exactamente con lo cobrado. Los asientos contables se generan con reglas puras, todo en centavos enteros.
Ventas que no se pierden
El POS sigue vendiendo sin internet y sincroniza después de forma idempotente. El cobro con QR de Mercado Pago se crea, se consulta hasta que se acredita y recién ahí la venta lo reclama de forma atómica, para que un pago nunca quede asociado a dos ventas.
Calidad y testing
- API: Jest con supertest, alrededor de 230 archivos de test (controladores, middlewares, servicios, validaciones, migraciones e integraciones con fixtures de ARCA) más 23 suites end to end contra PostgreSQL real en Docker.
- Front: Vitest con Testing Library, alrededor de 277 archivos de test, incluyendo la cola offline con IndexedDB simulada.
- Print agent: tests propios con Vitest.
- Las piezas críticas (cálculo fiscal, asientos, parseo de etiquetas de balanza) son funciones puras con tests dedicados, y el parseo de balanzas se validó contra una balanza real.
Infra y deploy
- Todo corre en un VPS propio con nginx: la landing en pullstok.com, el ERP en app.pullstok.com y cada tienda en su subdominio.
- Deploy continuo: cada push a main dispara un workflow de GitHub Actions que corre un script de 9 pasos: guarda el commit actual para rollback, hace backup de la base, actualiza el código, instala, genera Prisma, compila todas las apps, aplica migraciones, reinicia el servicio y verifica la salud de la API y el front. Si el health check falla, vuelve atrás solo.
- Backups: dump diario de la base con retención y backups por comercio en Cloudflare R2.
- Observabilidad: Sentry en la API y en el front.
Cómo lo construyo
Cada cambio arranca con una especificación escrita: objetivo, decisiones, hallazgos de la exploración del código, qué entra y qué no, y una lista de tareas. Trabajo con un flujo de desarrollo guiado por specs y asistido por IA, donde yo defino la arquitectura y las decisiones, y uso agentes para explorar, implementar y revisar, con memoria persistente entre sesiones y una revisión antes de cada push. Los commits siguen conventional commits.
Números
1.100+ commits
Con conventional commits y deploy continuo a producción.
70 modelos de datos
Alrededor de 100 migraciones de Prisma sobre PostgreSQL.
~280 endpoints
Repartidos en unos 57 routers de la API.
500+ archivos de test
Entre la API, el front y el agente de impresión.
Estado
Pullstok está en producción y en desarrollo activo. Hoy el foco está en el módulo contable. Cada módulo nuevo (las balanzas, el POS offline, el cobro con QR, la contabilidad) sale de necesidades reales de los comercios que ya lo usan.









