analytics
Portafolio de Iván
Proyectos chevron_right Cotizador sin fuga de márgenes

Sistema en producción · v1.2.0

shield_lock assertNoLeak() en runtime link Enlace público por token picture_as_pdf PDF de marca speed −250 MB de RAM

Cotizador con enlace público que no filtra márgenes

Un tour operador en Chiapas cotizaba con Excel y Word. Ahora cotiza en una aplicación propia y le manda al cliente un enlace donde arma su propio paquete, con el precio calculado siempre del lado del servidor.

Cifras clave

1

único lugar donde se calcula el precio: el servidor

v1.2.0

versión en producción

SHA-256

huella que invalida la selección si el vendedor edita

~250 MB

de RAM ahorrados por la elección de stack

Stack

Monorepo pnpmFastify 5DrizzlePostgreSQL 16ViteReact 19TypeScriptTailwind 4TanStack
schema

Del cotizador interno al enlace del cliente

El precio nace y muere en el servidor. El navegador del cliente pinta un total, nunca lo calcula, y la respuesta pasa por una guardia antes de salir.

  1. edit_note Interno

    El vendedor arma la cotización

    Elige servicios e ítems; los costos quedan congelados en una instantánea por ítem.

  2. tag Folio

    Folio atómico, sin reciclaje

    Cada cotización recibe un folio único que jamás se reutiliza.

  3. link Público

    Enlace con token

    El cliente abre su enlace y elige ocupación y opcionales, con el total en vivo.

  4. shield_lock Guardia

    assertNoLeak()

    Toda respuesta pública se revisa: si trae costo, margen o proveedor, se lanza excepción.

  5. picture_as_pdf Cierre

    PDF de marca on-demand

    El documento se genera al momento, con la selección firmada por su huella.

store

Contexto

Un tour operador en Chiapas vendía paquetes cotizando en hojas de cálculo y pasando el resultado a un documento de texto con el diseño de la marca. Cada cambio del cliente —una habitación más, un traslado menos— obligaba a rehacer las dos cosas a mano.

El objetivo era doble: quitarle al vendedor el trabajo repetitivo y darle al cliente algo mejor que un archivo adjunto. Es decir, un enlace donde pueda mover las variables que de verdad le importan y ver el efecto en su total, sin tener que escribir «¿y si vamos cuatro en vez de dos?».

Con una condición de fondo: en este negocio, el margen es información reservada. El cliente debe poder jugar con su paquete sin acercarse nunca a lo que ese paquete le cuesta a la agencia.

report

El problema

Un enlace público que calcula precios es, por definición, un enlace que puede filtrar cómo se calculan.

En cuanto el cliente puede recalcular su total en el navegador, aparece la tentación de mandarle los datos para que el navegador haga la cuenta. Y ahí es donde se filtra el negocio: costos por ítem, escalas de margen, nombres de proveedores. No hace falta enseñarlo en pantalla; basta con que viaje en la respuesta.

Vi de cerca el otro camino y su factura. En un proyecto hermano el motor de cálculo se duplicó en el navegador y en el servidor, y esa duplicación es hoy su peor deuda: los dos motores nunca coinciden exactamente, y cada divergencia es una discusión con un cliente que vio un número distinto al que aparece en el documento final.

También estaba el problema del tiempo. Una cotización enviada y no respondida es dinero potencial enfriándose, y el vendedor no tiene forma de saber cuáles vale la pena perseguir.

rule

Decisiones clave

01 Una guardia que revisa cada respuesta pública

Todo lo que sale por la ruta pública pasa por una función que inspecciona el contenido y lanza excepción si detecta costo, margen o datos de proveedor. No es una convención del equipo: es una comprobación que corre en producción, en cada petición.

psychology Las reglas que dependen de que todos se acuerden se rompen el día que alguien agrega un campo con prisa. Una guardia en runtime convierte «no debemos filtrar» en «no podemos filtrar», y falla ruidosamente en desarrollo antes de fallar callada en producción.

02 El precio se calcula solo en el servidor

El navegador del cliente muestra el total que le da el servidor; no tiene el motor de cálculo ni los datos para reproducirlo.

psychology Duplicar el motor es duplicar la verdad. Lo vi convertirse en la peor deuda de un proyecto hermano: dos implementaciones que nunca coinciden al último decimal y una discusión recurrente sobre cuál de las dos es la buena.

03 Congelar el costo y firmar la selección

Cada ítem guarda una instantánea de su costo en el momento de cotizar, el folio es atómico y no se recicla, y la selección del cliente lleva una huella SHA-256 que la invalida si el vendedor edita la cotización después.

psychology Sin instantánea, una cotización de la semana pasada cambia sola cuando cambia una tarifa. Sin huella, el cliente puede aceptar una versión que ya no existe, y el negocio queda obligado a algo que nunca ofreció.

04 Elegir el stack por lo que consume, no por lo que está de moda

Se usó una capa de acceso a datos ligera en vez de la opción habitual, y un generador de PDF en proceso en vez de levantar un navegador completo para renderizar. La diferencia es del orden de 250 MB de memoria.

psychology Todo esto vive en un servidor de 16 GB donde ya corren más de 60 contenedores. Ahí, 250 MB no son una optimización prematura: son el margen entre convivir con los demás servicios o empezar a competir con ellos.

05 La pantalla que paga el proyecto

Una vista lista las cotizaciones que el cliente abrió y no ha aceptado, ordenadas por un puntaje determinista cuyas reglas la interfaz explica en palabras: «La abrió 3 veces · Leyó 2 min · Caduca en 2 días».

psychology Cero inteligencia artificial y funciona desde el primer día, porque las reglas son visibles y el vendedor puede estar de acuerdo o no con ellas. Un puntaje que nadie entiende no se usa; uno que se explica solo, sí.

construction

La solución

Es un monorepo con un backend en Fastify sobre PostgreSQL y una interfaz en React con TypeScript. El vendedor trabaja en una aplicación instalable que arma la cotización; el cliente recibe un enlace con token que abre su propia vista, donde elige ocupación y opcionales y ve el total actualizarse.

Cuando el cliente confirma, el PDF con el diseño de la marca se genera al momento, con la selección ya firmada. El documento no se guarda de antemano ni se regenera distinto: sale de la misma fuente que el total que el cliente vio en pantalla.

Y del lado interno está la pantalla de seguimiento, que convierte «tengo muchas cotizaciones enviadas» en una lista ordenada por probabilidad de cierre, con el porqué escrito al lado de cada una.

emoji_events

Resultados

lightbulb

Lo que aprendí

Que la confidencialidad se diseña o no existe. Escribir «no expongas márgenes» en una guía de estilo no evita nada; una comprobación que revienta la petición, sí.

Que ver la deuda de otro proyecto de cerca vale más que leer sobre ella. La duplicación del motor de cálculo no se evitó por doctrina, sino porque ya había visto la factura mensual que cobra.

Y que la funcionalidad que paga el proyecto casi nunca es la más sofisticada. Aquí fue una lista ordenada con reglas explicadas en español, sin una línea de inteligencia artificial.