Sistema de Cotizaciones con Workflow de Aprobacion
Plataforma B2B para cotizaciones con calculo financiero exacto (Decimal.js), workflow de aprobacion por descuento, conversion automatica a pedido, folios transaccionales y auditoria completa — Next.js 14 + 4 roles.
El Reto
Empresas B2B (software, equipos industriales, mayoristas) generan cotizaciones complejas: 5-20 items con descuento por item + descuento global + IVA por item. Hacerlo en Excel es lento y peligroso: un decimal mal sumado puede regalar $50,000 al cliente o perder $30,000 por mes en cuentas grandes. Adicionalmente, vendedores aplican descuentos sin autorizacion del manager y la empresa solo se entera cuando llega el pedido.
El reto financiero era usar aritmetica exacta. JavaScript Number suma 0.1 + 0.2 = 0.30000000000000004 — inaceptable para sistemas que manejan dinero. La solucion exige Decimal.js (precision configurable, redondeo HALF_UP) en TODO calculo: precio con descuento, subtotal, IVA, total. Y NUNCA aceptar totales del body del cliente — el servidor SIEMPRE recalcula.
Adicionalmente: workflow estricto BORRADOR → ENVIADA → APROBADA / RECHAZADA → CONVERTIDA, con limites de descuento por rol (VENDEDOR max 10% global, MANAGER max 30%), folios unicos transaccionales (sin duplicados bajo concurrencia), auditoria completa, y conversion atomica de cotizacion a pedido.
La Solucion
Stack Next.js 14 + TypeScript strict + Prisma 5 + PostgreSQL 15. Aritmetica financiera con Decimal.js configurado al inicio: Decimal.set({ precision: 20, rounding: ROUND_HALF_UP }). Cada calculo (calcularItem, calcularCotizacion) en server-side. El cliente puede mandar precios y cantidades, pero los totales se IGNORAN — el servidor siempre recalcula y guarda lo suyo.
Folio transaccional con tabla Sequence + Prisma transaction + SELECT FOR UPDATE: siguienteSecuencia('COT') o ('PED') retorna 'COT-2024-0001' / 'PED-2024-0001' garantizando unicidad incluso bajo 100 requests concurrentes. Workflow con state machine estricta: cada transicion valida estado actual, rol del usuario, y propiedad (VENDEDOR solo retira sus borradores; MANAGER/ADMIN aprueban/rechazan).
Limites de descuento por rol en comercial.policy.ts: si VENDEDOR aplica 15% por item (limite 15% OK) o 12% global (limite 10%, ERROR DESCUENTO_EXCEDIDO 400). Si quiere mas, requiere aprobacion del MANAGER. Conversion a pedido es transaccion: crear Pedido + PedidoItems copiando exactos + cambiar estado a CONVERTIDA. Si falla cualquier paso, rollback total. AuditLog en cada mutacion + cada transicion de estado.
Features Clave
Aritmetica Financiera Exacta con Decimal.js
Decimal.js precision 20 + ROUND_HALF_UP en TODO calculo de dinero. JavaScript Number prohibido. Tests unitarios verifican casos limite (0.5 sube, 0.4 baja). Resultados con 4 decimales de precision en BD via Decimal(12,4).
Servidor Recalcula Siempre (RN-01)
El cliente envia { cantidad, precio, descuento }. El servidor IGNORA cualquier subtotal/impuesto/total que mande. Recalcula con Decimal.js y guarda los suyos. Imposible que el front mienta sobre el total.
Folio Unico Transaccional
Tabla Sequence (id, prefijo, ultimo) + transaccion Prisma con SELECT FOR UPDATE. siguienteSecuencia('COT') retorna 'COT-2024-0001' garantizado unico bajo concurrencia. Probado con 100 requests simultaneos en tests.
Workflow Estricto con Maquina de Estados
BORRADOR → ENVIADA → APROBADA / RECHAZADA / VENCIDA → CONVERTIDA. Cada transicion valida estado, rol, propiedad. Transiciones invalidas (BORRADOR → APROBADA) HTTP 400. Cotizacion APROBADA es INMUTABLE — error al editar.
Limites de Descuento por Rol
VENDEDOR: max 15% por item, max 10% global. MANAGER: max 50% por item, max 30% global. ADMIN: sin limite. Si VENDEDOR aplica >limite → 400 con codigo DESCUENTO_REQUIERE_APROBACION. Bloquea guardado.
Vigencia Lazy con Auto-VENCIDA
Cotizacion ENVIADA cuya fechaVigencia < now() pasa automaticamente a VENCIDA al consultarla. Lazy expiry — no requiere cron. La cotizacion VENCIDA no puede aprobarse.
Conversion Atomica a Pedido
POST /cotizaciones/:id/convertir crea Pedido + PedidoItems en transaccion + cambia estado a CONVERTIDA. Si falla cualquier paso, rollback total. Pedido tiene su propio folio (PED-2024-XXXX). Cotizacion ya CONVERTIDA no puede convertirse de nuevo.
Panel de Aprobaciones para Manager
Vista /aprobaciones lista cotizaciones ENVIADA con dias esperando, total, vendedor, cliente. Botones [Aprobar] / [Rechazar] abren modal con motivo. Rechazar requiere motivo min 10 chars.
Como se Ve
Formulario de Cotizacion Multi-seccion
Encabezado (cliente, vigencia, condiciones), tabla editable de items con autocomplete del catalogo, panel de totales fijo a la derecha que se actualiza en tiempo real con Decimal.js. Advertencia visual si descuento excede limite del rol.
Vista Detalle con Timeline + Acciones
Header con folio + cliente + estado badge grande + botones contextuales segun estado/rol. Tabla items solo lectura. Panel totales. Timeline con eventos: 'BORRADOR creado por VendX', 'ENVIADA por VendX', 'APROBADA por MgrY motivo: ...'.
Panel de Aprobaciones (Manager)
Tabla con cotizaciones ENVIADA: folio, cliente, vendedor, total, dias esperando, [Aprobar] [Rechazar]. Click en rechazar abre modal con campo motivo (textarea min 10 chars + contador).
Reporte Pipeline en Funnel
Grafica funnel recharts: cantidad y valor en cada estado (BORRADOR / ENVIADA / APROBADA / CONVERTIDA). KPIs arriba: tasa de aprobacion 30 dias, valor total en pipeline, ticket promedio.
Detalles Tecnicos
| Componente | Tecnologia |
|---|---|
| Framework | Next.js 14 (App Router) + TypeScript 5 strict |
| Aritmetica financiera | Decimal.js (precision 20, ROUND_HALF_UP) en TODO calculo de dinero |
| ORM + DB | Prisma 5 + PostgreSQL 15 + Decimal(12,4) y Decimal(14,4) en monetarios |
| Auth | Auth.js v5 + Argon2id + 4 roles (ADMIN / VENDEDOR / MANAGER / VIEWER) |
| Validacion | Zod 3 .strict() (rechaza campos extra) + react-hook-form |
| Folios transaccionales | Sequence table + Prisma transaction + SELECT FOR UPDATE |
| Rate limiting | rate-limiter-flexible + Redis (login 5/15min, cotizaciones 20/min) |
| Reportes | recharts (funnel pipeline) + papaparse (CSV) |
| Testing | Vitest (calculo + policy) + integration (workflow) + Playwright E2E |
| Logging | pino structured |
Resultados
6 (BORRADOR/ENVIADA/APROBADA/RECHAZADA/VENCIDA/CONVERTIDA)
Estados de cotizacion
4 (ADMIN/VENDEDOR/MANAGER/VIEWER)
Roles con permisos diferenciados
14 (RN-01 a RN-14)
Reglas de negocio testeadas
Decimal.js (cero flotantes)
Aritmetica financiera
Si — transaccion + FOR UPDATE
Folio unico bajo concurrencia
Atomica (rollback si falla)
Conversion a pedido
Servicios relacionados
¿Tu equipo de ventas cotiza en Excel y a veces se equivocan en el total?
Sistema con calculo financiero exacto, workflow de aprobacion por descuento, folios transaccionales y conversion atomica a pedido. Sin Excel, sin sorpresas.