← Todos los proyectos

Abe's Pottery

ERP de cotización a packing slip a prueba de concurrencia para una importadora de cerámica B2B — NestJS 11 + Prisma 7 + PostgreSQL, hecho con tests primero y en producción en Railway.

NestJSPrismaPostgreSQLRedisTypeScript

Un negocio mayorista de cerámica llevaba su parte comercial a base de documentos y buena voluntad. El riesgo real no era una función faltante: era que dos personas actuaran sobre los mismos números al mismo tiempo — dos cotizaciones sobre el mismo lote, dos documentos reclamando el mismo número, totales que cambiaban según quién los tecleara. Abe’s Pottery & Imports necesitaba un sistema donde eso simplemente no pueda pasar. La respuesta es un ERP de cotización → factura → packing slip con la concurrencia diseñada desde el inicio, entregado con tests primero y en producción en Railway.

Qué hace

Cubre el ciclo comercial completo de una importadora y mayorista de cerámica:

  • Catálogo. Categorías y productos con imágenes (en Firebase Storage), además de importación masiva del catálogo real directamente desde Excel.
  • Clientes y direcciones. Clientes de negocio con direcciones de facturación y envío, con manejo correcto de dirección predeterminada.
  • Cotizaciones con máquina de estados. DRAFT → SENT → ACCEPTED / REJECTED / EXPIRED, con una tarea programada que vence por sí sola las cotizaciones caducadas.
  • Conversión y fulfilment. Las cotizaciones aceptadas se convierten en facturas, y los packing slips se generan y se enlazan automáticamente con la factura que cumplen.
  • Dinero confiable. Subtotales, descuentos, flete e impuestos se calculan en el servidor — la fuente de verdad de cada total en cada documento.

Por dentro

Todo el sistema se construye sobre un principio: el backend nunca confía en el cliente, y nunca deja que dos operaciones se corrompan entre sí.

Numeración de documentos a prueba de carreras. Números como Q-2026-00001 se emiten dentro de una transacción que toma un bloqueo a nivel de fila sobre la secuencia antes de incrementarla. Dos peticiones simultáneas se serializan limpiamente en vez de chocar — sin números duplicados, sin huecos por adivinar de forma optimista. La secuencia es por año y se reinicia automáticamente cada enero.

Una máquina de estados, de verdad. Las cotizaciones solo avanzan por transiciones válidas; las inválidas se rechazan de plano en lugar de permitirse en silencio. El vencimiento tampoco es una tarea manual — una tarea programada barre y vence las cotizaciones a tiempo.

Soft-delete bien hecho. El borrado se maneja con una extensión del cliente Prisma que filtra de forma transparente las filas eliminadas en cada query, así nada en la app tiene que acordarse de agregar el filtro. Los borrados físicos son deliberadamente difíciles — protegidos tras un rol de admin y un header de confirmación explícito. El objetivo es la auditabilidad: los datos salen del conjunto de trabajo sin salir del sistema.

Snapshots inmutables en los renglones. Cuando un producto entra a una cotización, factura o packing slip, se copian su nombre, descripción y precio unitario en el renglón. Si luego editas o eliminas el producto, los documentos históricos quedan exactamente como se emitieron — porque una cotización de marzo jamás debe reescribirse sola en junio.

Un contrato tipado de punta a punta. NestJS 11 en TypeScript estricto sobre Prisma 7, DTOs de petición validados, una superficie OpenAPI/Swagger, y un interceptor de auditoría declarativo que registra quién cambió qué — capturado después de que la transacción confirma, para que el logging nunca bloquee la petición.

Construido para no caerse bajo carga. El rate limiting se apoya en Redis pero es fail-open: si Redis desaparece, el limitador degrada a memoria en vez de tumbar la API, y el health check reporta a Redis sin fallar nunca por él. Un único kill switch operativo puede bypasear Redis por completo — sin necesidad de redeploy.

El stack

  • Backend — NestJS 11 en TypeScript estricto sobre Node.js 22, API REST documentada con Swagger / OpenAPI.
  • Datos — PostgreSQL 15 vía Prisma 7 (con el driver @prisma/adapter-pg); Redis 7 para rate-limiting distribuido y un cache de respuesta selectivo.
  • Auth y seguridad — JWT de acceso + refresh tokens rotativos en cookies httpOnly, Passport, hashing de contraseñas con argon2, Helmet, CORS estricto, un throttler global.
  • Plataforma — Firebase Storage para imágenes de producto, nestjs-i18n (en / es), logging estructurado con nestjs-pino y redacción de campos sensibles, feature flags que montan o desmontan módulos al arrancar.
  • Calidad — una suite de tests Jest (unit, e2e e integración) con un harness propio que levanta un Postgres efímero en tmpfs, y CI/CD en GitHub Actions que corre las migraciones antes de cada deploy.

Vale la pena saber

Construí el backend de punta a punta — el modelo de datos, la estrategia de concurrencia, la máquina de estados, la seguridad, los tests y el pipeline de despliegue — y está corriendo en producción hoy. Se hizo con tests primero, lo que significa que las condiciones de carrera estaban probadas como imposibles antes de que existiera una UI para ejercitarlas.

Es un producto real para un negocio mayorista real — trabajo propietario para un cliente — y por eso está construido como debe estarlo un sistema del que depende la empresa de alguien: correcto bajo concurrencia, auditable, y seguro de desplegar por pipeline en vez de a mano.

¿Un café y platicamos?

¿Te gustó lo que leíste? Construyo productos así de punta a punta — y siempre estoy para una buena plática. Hablemos del tuyo, o nomás intercambiamos ideas con un café.

Rentheria · Guadalajara, México