Case study web app — configuratore B2B

5 giorni per un preventivo.
I clienti non aspettano.
La concorrenza nemmeno.

Un'azienda manifatturiera con 300+ varianti di prodotto configurabili perdeva commesse perché i preventivi arrivavano troppo tardi. Ho costruito un configuratore interattivo con motore di pricing real-time in Node.js: ogni commerciale — e ogni cliente — può comporre il prodotto, vedere il prezzo aggiornato al volo e generare l'offerta in PDF in meno di 20 minuti.

ClienteManifattura industriale (NDA)
SettoreComponenti su misura
Anno2024
TipoWeb App B2B
config.industrialb2b.it/configura
IndustrialB2B
CONFIGURATORE PRODOTTI
Utente: G. Martini
1
Tipologia
2
Parametri
3
Materiali
4
Offerta
Parametri prodotto
Diametro nominale
DN 50
DN 80
DN 100
DN 150
Pressione max (bar)
16 bar
25 bar
40 bar
Materiale corpo
Acciaio inox
Ghisa
Bronzo
Riepilogo configurazione
CODICE ARTICOLO
VB-50-25-INOX-F
Base corpo€ 2.100
Materiale inox ×1.8€ 1.680
Pressione 25 bar€ 350
Sconto cliente (15%) − € 617
PREZZO NETTO
€ 3.513
GENERA PDF

Case study configuratore prodotti B2B Node.js

300 varianti di prodotto.
Ogni preventivo fatto a mano.

L'azienda — un produttore italiano di valvole e componenti idraulici industriali su misura — rifornisce impianti di processo, cantieri navali e industria alimentare in tutta Europa. I loro prodotti sono altamente configurabili: diametro, pressione nominale, materiale del corpo, tipo di connessione, rivestimento interno, certificazioni richieste. Combinando le variabili si arriva a oltre 300 configurazioni possibili, ognuna con un prezzo diverso.

Prima di questo progetto, ogni richiesta di offerta seguiva lo stesso percorso: il commerciale raccoglieva le specifiche del cliente, le girava per email all'ufficio tecnico, che verificava la fattibilità della configurazione e passava i dati all'ufficio prezzi, che produceva il preventivo in un foglio Excel e lo restituiva al commerciale. Tempo medio: 3–5 giorni lavorativi.

In un mercato dove i competitor tedeschi e cechi rispondevano con offerte in meno di 24 ore, questo processo era diventato un handicap competitivo misurabile. L'azienda stimava di perdere il 20–25% delle trattative esclusivamente per tempi di risposta.

«Sappiamo già che il prodotto lo facciamo. Il problema è che il cliente non lo sa — perché ci vogliono quattro giorni per dirglielo. Nel frattempo ha già chiamato qualcun altro.»

— Direttore commerciale, chiamata di kickoff
Progetto Configuratore B2B con pricing real-time e generazione offerte
Stack Node.js + Express · React · PostgreSQL · Redis
Integrazioni CRM HubSpot · Puppeteer (PDF) · Email via Nodemailer
Utenti Commerciali interni · Agenti · Clienti diretti (self-service)
Durata 10 settimane
Tag
Node.js React pricing engine PostgreSQL Redis HubSpot API Puppeteer

Requisiti configuratore industriale B2B

Da 5 giorni a 20 minuti.
Senza errori di configurazione.

Configuratore guidato step-by-step con validazione in tempo reale

Il sistema deve guidare l'utente attraverso i parametri del prodotto mostrando solo le combinazioni tecnicamente valide. Se selezioni DN 150 con pressione 40 bar, il materiale "bronzo" deve sparire dalla lista — perché quella combinazione non è omologata. Le regole di compatibilità sono gestite lato server, non lato client.

Pricing real-time con listini differenziati per cliente

Il prezzo deve aggiornarsi a ogni modifica di parametro, senza click su "calcola". Ogni cliente B2B ha un listino personalizzato (sconti, maggiorazioni per certificazioni, prezzi bloccati su alcune famiglie). Il motore di pricing deve applicare le regole corrette in base all'utente loggato, senza mai esporre il listino base al frontend.

Generazione PDF offerta brandizzato in un click

Il PDF dell'offerta deve includere: intestazione aziendale, dati cliente, descrizione tecnica completa del prodotto configurato, prezzo netto con breakdown, condizioni di fornitura, validità offerta e firma digitale del commerciale. Generazione in meno di 5 secondi, inviato via email al cliente automaticamente.

Sincronizzazione automatica con CRM HubSpot

Ogni offerta generata deve creare automaticamente un deal in HubSpot con tutti i dettagli della configurazione, il valore dell'offerta e il commerciale assegnato. Se il cliente esiste già nel CRM, il deal viene collegato al contatto esistente. Se è nuovo, viene creato il contatto. Nessun doppio inserimento manuale.

Accesso self-service per clienti diretti con area riservata

I clienti più grandi devono poter accedere al configuratore direttamente, senza passare da un commerciale. Vedono solo il proprio listino, lo storico delle offerte precedenti e possono ripartire da una configurazione salvata. L'ufficio commerciale riceve una notifica per ogni nuova offerta generata da un cliente in autonomia.

Sfide tecniche pricing engine real-time

Un motore di pricing
che non mente mai.

Un configuratore sbagliato è peggio di nessun configuratore: se genera un prezzo errato o valida una combinazione impossibile, l'azienda si trova a dover rinegoziare un'offerta già accettata. La sfida principale non era tecnica — era di correttezza assoluta.

01

Modellare 1.200+ regole di compatibilità senza hardcoding

Le regole di compatibilità tra parametri (es. "DN 150 + 40 bar → solo acciaio inox") erano distribuite in 8 anni di documenti tecnici, email e conoscenza tacita dei tecnici. Ho strutturato un motore a regole configurabile su database PostgreSQL: ogni regola è una riga con condizioni e conseguenze, modificabile dall'ufficio tecnico senza toccare il codice. Il motore valuta le regole applicabili in ordine di priorità e restituisce le opzioni valide per il passo successivo.

02

Pricing engine: 6 livelli di prezzo, zero esposizione lato client

Il sistema gestisce 6 tipologie di listino (standard, agente, cliente diretto, quadro contrattuale, promozione temporanea, prezzo bloccato). Il calcolo avviene esclusivamente sul server Node.js: il frontend riceve solo il prezzo finale e il breakdown autorizzato per quell'utente. Il listino base non esce mai dall'API. Redis cacca i listini per 15 minuti, riducendo le query al database del 78% durante i picchi di utilizzo.

03

Generazione PDF con Puppeteer: template fedele al brand

Il PDF dell'offerta doveva essere identico al formato cartaceo che l'azienda usava da anni — intestazione precisa al millimetro, tabelle tecniche con formattazione complessa, firma del commerciale come immagine embedded. Puppeteer renderizza un template HTML/CSS a schermo intero, poi stampa in PDF con margini calibrati. Il job viene eseguito in un worker separato per non bloccare il thread principale, con timeout di 10 secondi e retry automatico.

04

Sync HubSpot: gestire duplicati e aggiornamenti parziali

HubSpot non ha un meccanismo nativo di upsert atomico: se cerco un contatto per email e non lo trovo, devo crearlo — ma nel frattempo un altro processo potrebbe averlo già creato. Ho implementato un sistema di idempotenza basato su un ID univoco dell'offerta: ogni sync controlla prima se esiste già un deal con quell'ID nel campo custom, e in caso positivo aggiorna invece di duplicare.

05

Sessione configurazione: recupero dopo refresh o disconnessione

Una configurazione complessa richiede 8–12 minuti. Se il commerciale aggiorna la pagina o perde la connessione, deve poter riprendere da dove aveva lasciato. Ho implementato il salvataggio automatico dello stato di configurazione su PostgreSQL ogni 30 secondi (debounced), con ripristino automatico all'apertura successiva. Le configurazioni salvate restano accessibili anche da dispositivi diversi.

Processo sviluppo configuratore B2B

10 settimane per trasformare
la conoscenza tacita in software.

Settimane 1–2
Estrazione della conoscenza tecnica

Ho passato due settimane con il responsabile tecnico e il più esperto dei commerciali a mappare ogni regola di compatibilità esistente. Non erano documentate — vivevano nella testa delle persone e in un file Excel usato internamente. Ho prodotto un documento strutturato con 1.247 regole in forma "SE [parametro A] = X E [parametro B] = Y → ALLORA [parametro C] non può essere Z". Questo documento è diventato il modello di dati del motore di regole.

→ 1.247 regole mappate · schema database definito · test di completezza con l'ufficio tecnico
Settimane 3–5
Backend Node.js: motore regole + pricing engine

Ho sviluppato il cuore del sistema: il motore di regole in Node.js legge le regole da PostgreSQL, le valuta in ordine di priorità e restituisce le opzioni disponibili per ogni step. Il pricing engine applica le regole del listino attivo per l'utente loggato — base price × coefficiente materiale × coefficiente pressione + optional, poi applica lo sconto contrattuale del cliente. Redis cacca i listini con TTL di 15 minuti. Test di carico: 200 richieste/secondo senza degrado delle performance.

→ 42 endpoint Express · motore regole · pricing engine · Redis cache · 80 test Jest
Settimane 6–7
Frontend React: configuratore step-by-step + salvataggio automatico

Interfaccia React con step progressivi: ogni selezione chiama il backend per ottenere le opzioni valide del passo successivo. Il prezzo si aggiorna in tempo reale tramite debounce a 300ms sulla chiamata API. Lo stato della configurazione viene salvato su PostgreSQL ogni 30 secondi — se il commerciale chiude il browser e riapre, trova tutto esattamente com'era. Ho anche costruito la sezione "storico offerte" per l'accesso self-service dei clienti diretti.

→ React + debounce 300ms · autosave 30s · storico offerte · responsive per tablet
Settimana 8
Generazione PDF con Puppeteer + integrazione HubSpot

Il template PDF è stato costruito in HTML/CSS puro e replicato pixel-per-pixel dal modello cartaceo esistente. Puppeteer renderizza il template in un worker Node.js dedicato e produce il PDF in 3–4 secondi. Il file viene caricato su S3, poi inviato via Nodemailer al cliente e al commerciale. In parallelo, la stessa offerta viene inviata a HubSpot via API: upsert del contatto, creazione del deal con tutti i campi custom della configurazione, associazione al commerciale responsabile.

→ Puppeteer worker · S3 upload · Nodemailer · HubSpot API v3 · upsert idempotente
Settimane 9–10
UAT con i commerciali e go-live

Due settimane di test con 8 commerciali e 3 agenti esterni. Ho rilevato 2 regole di compatibilità mancanti (casi rarissimi mai emersi nelle interviste) e 1 sconto cliente non applicato correttamente su una famiglia di prodotti. Fix rapidi grazie al motore a regole configurabile — nessun deploy per correggere le regole. Go-live con rollout graduale: prima la rete commerciale interna, poi gli agenti, infine i clienti self-service dopo 3 settimane di stabilità.

→ 8 commerciali · 3 agenti UAT · 2 regole aggiunte · go-live in 3 fasi · zero downtime

Soluzione: motore regole e pricing Node.js

Il motore di regole:
1.247 vincoli, zero hardcoding.

La scelta architetturale più importante è stata separare nettamente le regole di business dal codice applicativo. Ogni volta che l'ufficio tecnico aggiunge una nuova variante o modifica una compatibilità, aggiorna una riga su database — senza toccare il deploy.

Soluzione 01 / 03

Rule engine configurabile — modificabile senza codice

Ogni regola è una riga PostgreSQL con quattro campi: param_key, param_value, excluded_key, excluded_values e una priority per la gestione dei conflitti. Il motore Node.js carica le regole rilevanti per la configurazione corrente, le valuta in ordine e restituisce un array di opzioni valide per il passo successivo. L'ufficio tecnico gestisce le regole da un pannello admin costruito con React Table — nessun intervento del developer per aggiungere o modificare vincoli.

// pricing-engine.js — calcolo prezzo con Redis cache async function calculatePrice( config, userId ) { const cacheKey = `listino:${userId}`; let listino = await redis.get( cacheKey ); if ( !listino ) { listino = await db.query( 'SELECT * FROM listini WHERE cliente_id = $1 AND attivo = true', [userId] ); await redis.setEx( cacheKey, 900, JSON.stringify(listino) ); // 15 min TTL } else { listino = JSON.parse( listino ); } const basePrice = getBasePrice( config.tipologia, config.diametro ); const coefMat = listino.find( r => r.param === config.materiale )?.coef ?? 1; const coefPress = listino.find( r => r.param === config.pressione )?.coef ?? 1; const sconto = listino.sconto_cliente ?? 0; const lordo = basePrice * coefMat * coefPress; return { netto: lordo * (1 - sconto), lordo, sconto, breakdown: { basePrice, coefMat, coefPress } }; }
Soluzione 02 / 03

PDF in 4 secondi — identico al modello cartaceo

Puppeteer renderizza un template HTML/CSS con i dati dell'offerta iniettati server-side. Il template replica esattamente il formato cartaceo storico dell'azienda: intestazione con logo vettoriale, tabella parametri tecnici con unità di misura, breakdown prezzo, condizioni di fornitura, validità offerta e firma del commerciale come immagine PNG firmata digitalmente. Il worker Puppeteer gira in un processo separato per non bloccare il thread Express principale — con timeout di 10 secondi e retry automatico in caso di crash. Tempo medio di generazione: 3,8 secondi.

Offerta_VB-50-25-INOX.pdf
OFFERTA N. 2024-0847
Data: 15/11/2024
Valvola a sfera VB · DN 50 · 25 bar · Acciaio inox
Diametro
DN 50
Pressione
25 bar
Prezzo netto (sconto 15%)
€ 3.513,00
Inviato a cliente · HubSpot aggiornato
Soluzione 03 / 03

CRM sempre aggiornato — senza toccare HubSpot

Ogni offerta generata innesca una sequenza asincrona: prima il PDF viene caricato su S3, poi parte il job di sincronizzazione HubSpot. Il job cerca il contatto per email — se esiste lo aggiorna, se non esiste lo crea. Poi crea o aggiorna il deal con un campo custom offerta_id come chiave di idempotenza: se per qualsiasi ragione il job gira due volte, non crea un duplicato. I commerciali trovano ogni offerta in HubSpot già associata al contatto giusto, con il PDF allegato, il valore del deal e la pipeline stage impostata su "Offerta inviata". Zero inserimenti manuali da quando il sistema è online.

Dato post go-live: nei primi 3 mesi il sistema ha generato 412 offerte e sincronizzato 412 deal in HubSpot. Zero duplicati rilevati. Il responsabile commerciale ha smesso di fare il "giro serale" per aggiornare il CRM manualmente.

Stack tecnologico Node.js React PostgreSQL

Node.js al centro.
Ogni strumento al suo posto.

Node.js 20 + Express
React 18
PostgreSQL 16
Redis 7
Puppeteer
HubSpot API v3
AWS S3
Nodemailer + SES
Jest + Supertest
Vite
React Table
Zod (validazione)

Perché Node.js e non PHP/Laravel?

Il pricing engine valuta decine di regole in sequenza per ogni richiesta — un'operazione CPU-bound veloce ma ad alta frequenza. Node.js gestisce meglio la concorrenza I/O-bound tipica di un'API che fa molte query Redis e PostgreSQL in parallelo, senza il overhead del thread model di PHP-FPM. Inoltre il team aveva già familiarità con JavaScript: usare TypeScript sia sul frontend che sul backend ha ridotto il contesto switching e i bug di interfaccia tra i due layer.

Perché Puppeteer e non una libreria PDF?

Librerie come PDFKit o jsPDF richiedono di costruire il layout programmaticamente — ogni riga, ogni tabella, ogni margine definito in codice. Il template HTML/CSS è infinitamente più manutenibile: un grafico può modificare l'aspetto dell'offerta cambiando CSS, senza toccare JavaScript. Il prezzo è un processo leggermente più pesante, risolto con il worker isolato e il pre-warm di Puppeteer all'avvio del server.

Risultati configuratore B2B: -70% ciclo offerta

Da 5 giorni a 20 minuti.
412 offerte in 3 mesi.

Dati rilevati nei primi 3 mesi dopo il go-live completo (commerciali interni + agenti + clienti self-service), confrontati con lo stesso periodo dell'anno precedente.

−70%
Ciclo di offerta
Da 3–5 gg a 20 min medi
412
Offerte generate
Nei primi 3 mesi · zero errori pricing
−95%
Errori configurazione
Da 18 a 1 correzione/mese
+28%
Tasso di chiusura
Offerte accettate vs periodo prec.
3,8s
Generazione PDF
Tempo medio · worker Puppeteer
0
Duplicati HubSpot
412 deal creati · zero duplicati

«Il primo cliente che ha usato l'accesso self-service ci ha inviato un ordine alle 22:30 di un venerdì sera. Prima avrebbe dovuto aspettare lunedì mattina. Quella sera abbiamo capito che il sistema funzionava davvero.»

— Direttore commerciale, 3 mesi dopo il go-live

Learnings sviluppo configuratore prodotti

Cosa porterei
nel prossimo progetto.

Separare le regole di business dal codice è un investimento che si ripaga subito. Nelle prime settimane dopo il go-live sono state aggiunte 23 nuove regole di compatibilità per nuovi prodotti — tutte gestite dall'ufficio tecnico dal pannello admin, senza nessun intervento di sviluppo.

Il pricing deve essere calcolato sempre e solo lato server. Avevo valutato l'opzione di calcolare il prezzo nel frontend per ridurre le chiamate API. L'ho scartata subito: esporre i coefficienti di listino nel JavaScript del browser avrebbe significato regalare alla concorrenza la struttura dei prezzi. Il debounce a 300ms rende l'esperienza percepita comunque real-time.

Il salvataggio automatico della sessione vale sempre il costo di implementazione. Durante l'UAT, due commerciali hanno perso una configurazione per un refresh accidentale. Da quel momento il salvataggio automatico è diventato un requisito non negoziabile per qualsiasi web app con form complessi.

L'idempotenza dei job di sync non è un dettaglio — è fondamentale. In produzione, il job HubSpot ha girato due volte sullo stesso deal in 3 occasioni (timeout + retry). Senza il check sull'offerta_id avremmo avuto 6 deal duplicati nel CRM. L'idempotenza ha salvato la qualità dei dati nel momento più critico: i primi giorni dopo il lancio.

Il self-service B2B cambia il rapporto con il cliente più di qualsiasi funzionalità. I clienti che usano l'accesso diretto generano il 34% delle offerte fuori dall'orario di ufficio. Non chiedono sconti extra, non negoziano il prezzo — perché il processo trasparente e veloce crea una fiducia diversa rispetto alla telefonata con il commerciale.

Hai un processo di vendita che merita di essere automatizzato?

Configuratori, preventivi, cataloghi complessi — se la logica esiste già, si può codificare.