The wizard atDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/ency07/B2B/llms.txt
Use this file to discover all available pages before exploring further.
/wizard is a 5-step guided form that walks industrial buyers through a pre-engineering assessment, automatically calculates airflow requirements using ASHRAE ACH standards, generates a budget price estimate in both COP and USD, and creates a fully qualified CRM lead in Supabase. The entire flow is backed by the submitWizardData() Server Action defined in src/web/actions/wizard.ts.
Wizard steps
Service selection
The user selects the type of service they need. The
servicio field accepts one of:
fabricacion | venta | mantenimiento | reparacion | otro.When otro is selected, an optional otroDetalle free-text field is revealed to capture additional context (up to 2,000 characters).Technical dimensions
The user enters the physical dimensions of their industrial space:
length,width,height— all in meters, required positive numbers.environment— the operational classification of the space, which determines the ASHRAE ACH multiplier used in the CFM calculation.
| Environment value | Description | ACH (ASHRAE) |
|---|---|---|
heavy_plant | Heavy manufacturing / smelting | 45 |
data_center | Server room / data center | 30 |
mining | Mining operations | 10 (default rate — not separately mapped) |
warehouse | Storage / warehouse | 8 |
default | General industrial | 10 |
Corporate info
The
CorporateInfoStep component (src/web/components/wizard/CorporateInfoStep.tsx) collects B2B contact data used for both the diagnostic report and CRM record creation:nombre— full name of the contact personempresa— legal company name (used as thelegal_namekey for client upsert)cargo— job title, selected from a scored listtelefono— corporate phone numberemail— corporate email addressciudad— cityurgencia— project urgency:baja|media|alta
@gmail.com, @hotmail.com, @outlook.com, @yahoo.com) is detected, a visible warning banner notifies the user that a score penalty will be applied and recommends using a corporate address.Summary
A read-only review screen computed entirely from the data entered in the previous steps. It displays:
- Calculated CFM value and CFM category (COMPACT / STANDARD / HIGH / CRITICAL)
- Estimated price range in COP and USD (±15% deviation band)
- Materials recommendation based on environment type
- Space volume in m³
Success
Triggered after a successful
submitWizardData() call. Displays:- The unique
diagnosticCodegenerated by Supabase for thediagnostic_reportsrecord - The assigned engineer information
- A summary of the CFM category and price estimate
leadId returned from the Server Action links this session to the CRM lead record.submitWizardData() Server Action
Signature:
src/web/actions/wizard.ts
Processing pipeline
- Zod validation —
wizardSubmissionSchemavalidates all fields. On failure, field-level error messages are concatenated and thrown as a singleError. - XSS sanitization —
sanitizeObject()fromsrc/lib/utils/sanitize.tsis applied to all string fields in the parsed data. - Rate limiting —
checkRateLimit(wizard:$)enforces a maximum of 10 requests per minute per email address (default limits). Exceeding the limit throws"Demasiadas solicitudes. Intente nuevamente en un minuto.". - Tenant validation — The tenant must exist in the
tenantstable withstatus = 'Activo'. - Engineering calculation —
calculateRequiredCfm()fromsrc/utils/engineering.tscomputescfmandcubicMetersfrom the submitted dimensions and environment. - Price estimation —
estimatePrice()fromsrc/utils/pricing.tscomputes COP and USD ranges using the service type, urgency multiplier, and volume. - Client upsert — looks up
clientsby(tenant_id, legal_name). If not found, inserts a new client record withstatus = 'PROSPECTO'. - Contact upsert — looks up
client_contactsby(tenant_id, client_id, email). If not found, inserts a new contact. - Lead creation with scoring — delegates to
createLeadWithScore()insrc/web/actions/leads.ts, which calculates a numeric score and classifies the lead asCALIENTE | TIBIO | FRIO | SPAM. - Diagnostic report — inserts a row into
diagnostic_reportswith all calculated values and returns the auto-generateddiagnostic_code.
Input and output types
CFM category thresholds
AftercalculateRequiredCfm() returns the raw CFM value, submitWizardData() classifies it into one of four categories:
| Category | CFM Range | Description |
|---|---|---|
COMPACT | < 2,000 | Small enclosed spaces |
STANDARD | 2,000 – 7,999 | General industrial |
HIGH | 8,000 – 14,999 | Heavy manufacturing |
CRITICAL | ≥ 15,000 | Smelting, heavy plant |
Materials recommendation
ThematerialsRecommendation string in the result is computed from the environment value at runtime — not stored as static copy:
| Environment | Recommendation |
|---|---|
heavy_plant, mining | Industrial Blower or Mushroom extractor with anti-corrosive epoxy coating and extruded aluminum blades |
data_center | Air injection system with EPA/HEPA particle filters and low-vibration acoustic control |
| All others | Multi-purpose or high-capacity axial extractor with 22-gauge galvanized steel ductwork |
Lead scoring
Lead scoring is performed bycalculateLeadScore() in src/web/actions/leads.ts. Scores range from 0 to 100 and are built from three signals:
| Signal | Points |
|---|---|
cargo = Director de Planta / Gerente de Mantenimiento / Supervisor HVAC | +40 |
cargo = Ingeniero de Proyectos / Compras | +25 |
cargo = Otro | +10 |
urgencia = alta | +40 |
urgencia = media | +20 |
urgencia = baja | +5 |
| Public email domain (Gmail, Hotmail, Yahoo, Outlook) | −20 |
| Disposable / known spam domain | Score forced to 0, classified as SPAM |
riskLevel label assigned to the leads row:
| Score | Risk level |
|---|---|
| ≥ 70 | CALIENTE |
| 40 – 69 | TIBIO |
| < 40 | FRIO |
| Disposable domain | SPAM |
Data persistence flow
getPublicServerClient() (anonymous key + RLS). The RLS policies on each table allow INSERT for anonymous sessions scoped to the active tenant, but deny any SELECT of other tenants’ data.
Error handling
| Error condition | Thrown message |
|---|---|
| Zod validation failure | "Datos de entrada inválidos: <field>: <message>" |
| Rate limit exceeded | "Demasiadas solicitudes. Intente nuevamente en un minuto." |
| Tenant not found or inactive | "El servicio no está disponible para este tenant." |
| Client insert failure | "Error al registrar el cliente. Intente nuevamente." |
| Contact insert failure | "Error al registrar el contacto. Intente nuevamente." |
| Diagnostic report insert failure | "Error al generar el reporte de diagnóstico. Intente nuevamente." |
The wizard uses
getPublicServerClient() (anon key + RLS), not the admin client. All database writes go through anonymous RLS policies that allow INSERT only — no SELECT of other tenants’ data is possible from the wizard flow.Pricing engine
estimatePrice() from src/utils/pricing.ts applies three multipliers on top of a per-service base price (in COP):
| Service | Base price (COP) |
|---|---|
fabricacion | $1,200,000 |
venta | $800,000 |
mantenimiento | $300,000 |
reparacion | $500,000 |
alta = 1.35×, media = 1.10×, baja = 1.00×) is capped at 2.0×. The final price is presented as a ±15% range:
COP_TO_USD_RATE constant defined in src/lib/constants.ts.