Web Server Actions inDocumentation 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.
src/web/actions/ power the public-facing parts of the platform. They use getPublicServerClient() (anon key + RLS), not the admin client, ensuring all writes go through anonymous RLS policies and that unauthenticated visitors cannot escalate privileges. Only catalog mutation and branding actions that require authentication switch to supabaseAdmin after passing an RBAC guard.
Wizard
submitWizardData
src/web/actions/wizard.ts
- Verify the tenant exists and its status is
"Activo". - Calculate required CFM and price estimate via the engineering utilities (
calculateRequiredCfm,estimatePrice). - Upsert the client record by
legal_name(creates if absent, reuses if present). - Upsert the contact record by email within the client (creates if absent, reuses if present).
- Create a scored lead via
createLeadWithScore(see below). - Insert a diagnostic report (
diagnostic_reports) linking the lead, dimensions, CFM result, and price ranges.
Tenant subdomain code. Used to scope all database writes to the correct tenant.
Type of industrial service being requested.
Space length in meters. Must be a positive number.
Space width in meters. Must be a positive number.
Space height in meters. Must be a positive number.
Industrial environment type. Affects the CFM multiplier and materials recommendation.
Contact’s full name. Minimum 2 characters.
Company legal name. Used as the
legal_name for upsert. Minimum 2 characters.Contact’s job title. Used in lead scoring. Minimum 2 characters.
Contact phone number. Minimum 7 characters.
Contact email address. Must be a valid email format. Used as the rate-limiting key and for contact deduplication.
City where the project is located. Minimum 2 characters.
Project urgency level. Affects lead scoring and price estimation.
Free-text detail for the service type. Only relevant when
servicio is "otro".Promise<WizardResult>:
Auto-generated code for the diagnostic report, e.g.
"DX-2026-0001".Calculated airflow requirement in CFM (cubic feet per minute).
CFM classification tier:
CRITICAL (≥ 15,000 CFM), HIGH (≥ 8,000 CFM), COMPACT (< 2,000 CFM), or STANDARD (everything in between).Space volume in cubic meters (
length × width × height).Minimum price estimate in Colombian Pesos (COP).
Maximum price estimate in Colombian Pesos (COP).
Minimum price estimate in US Dollars (USD).
Maximum price estimate in US Dollars (USD).
Dynamically generated materials recommendation based on the environment type. Examples:
heavy_plant/mining→ epoxy-coated industrial blower with extruded aluminium blades.data_center→ EPA/HEPA air injection system with low-vibration acoustic control.- All others → general-purpose axial extractor with galvanized steel ducting.
UUID of the created lead record in the
leads table."Datos de entrada inválidos: ..."— Zod validation failed; message includes field-level details."Demasiadas solicitudes. Intente nuevamente en un minuto."— rate limit exceeded."El servicio no está disponible para este tenant."— tenant not found or notActivo.- Generic Supabase errors on write failures.
Catalog
getIndustrialCatalog
src/web/actions/catalog.ts
unstable_cache:
- Revalidation: 60 seconds
- Cache tag:
catalog-all
_invalidateCache(tenantCode) to bust the tag on write.
Tenant subdomain code.
Promise<CatalogCategory[]> — a fully nested hierarchy:
saveProduct
id is provided the product is updated; otherwise a new product is inserted. Specifications are replaced atomically: existing rows for the product are deleted before the new set is inserted.
Invalidates the catalog-all cache on success.
Required permission: items.manage
deleteProduct
deleted_at to the current timestamp. The product remains in the database but is excluded from all catalog reads.
Invalidates the catalog-all cache on success.
Required permission: items.manage
saveCategory
id is present the category is updated; otherwise a new row is inserted into product_categories.
Invalidates the catalog-all cache on success.
Required permission: items.manage
deleteCategory
deleted_at. The category and all its descendants remain in the database but are excluded from getIndustrialCatalog.
Invalidates the catalog-all cache on success.
Required permission: items.manage
addProductImage
media_assets table and then creates a join row in product_images linking the asset to the product. The sort_order defaults to 10.
Tenant subdomain code.
UUID of the product to attach the image to.
Original file name (e.g.
"extractor-axial-ev300.png").Storage path or public URL of the uploaded file.
File size in bytes.
MIME type (e.g.
"image/png").Accessibility alt text. Defaults to an empty string if omitted.
Promise<{ asset: MediaAsset; prodImg: ProductImage }> — both the newly created media asset row and the product–image join row.
Invalidates the catalog-all cache on success.
Required permission: items.manage
Leads
createLeadWithScore
src/web/actions/leads.ts
leads table. Called internally by submitWizardData at step 5 of the wizard pipeline, and also available for direct invocation. Validates input with Zod, applies rate limiting per email, and verifies the tenant is "Activo" before writing. Inserts using the public anon client (RLS enforced).
Scoring factors (via calculateLeadScore):
| Signal | Parameter | Impact |
|---|---|---|
| Job title seniority | role | Director de Planta / Gerente de Mantenimiento / Supervisor HVAC: +40 pts. Ingeniero de Proyectos / Compras: +25 pts. Other: +10 pts. |
| Urgency | urgency | alta: +40 pts. media: +20 pts. baja: +5 pts. |
| Email domain | email | Public domain (Gmail, Hotmail, etc.): −20 pts penalty. Disposable/invalid domain: SPAM, score zeroed. |
[0, 100]. risk_level classification: CALIENTE (≥ 70), TIBIO (≥ 40), FRIO (< 40), or SPAM (disposable/invalid email). The lead is inserted with status: "NUEVO" and lead_source: "WEBSITE".
Branding
src/web/actions/branding.ts
getTenantBranding
EMPRESA, LOCALIZACION, IDENTIDAD, and DOCUMENTOS in tenant_settings. Values from the database are merged on top of the hardcoded getBrandingDefaults(tenantCode) so that any key not yet saved in the database still returns a sensible default.
Tenant subdomain code.
Promise<BrandingConfig> — complete branding config object. Falls back to getBrandingDefaults on Supabase error.
saveTenantBranding
tenant_settings and creating a full configuration snapshot in tenant_branding_version. Every successful save increments the version number.
Tenant subdomain code.
Partial branding config with only the keys to update. Routing to the correct
module is automatic based on the key name.Human-readable description for the version snapshot. Defaults to
"Actualización de Branding (Versión N)".branding.manage
getBrandingHistory
Promise<BrandingVersion[]> where each item has:
| Field | Type | Description |
|---|---|---|
id | string | Version UUID |
version_number | number | Sequential version number |
config_values | BrandingConfig | Full configuration snapshot at time of save |
description | string | Human-readable change description |
created_at | string | ISO timestamp |
restoreBrandingVersion
config_values into tenant_settings and creating a new version snapshot with a description of "Restaurado a la Versión N".
Tenant subdomain code.
UUID of the
tenant_branding_version row to restore.branding.manage
uploadBrandingLogo
tenant-logos Supabase Storage bucket and returns its public URL. Creates the bucket if it does not yet exist.
Tenant subdomain code.
Logical file type label used to construct the storage path (e.g.
"logo_url", "favicon_url"). The final path is {tenantId}/{fileType}-{timestamp}.{ext}.Base64-encoded file content. Decoded to a
Buffer before uploading.Original file name used to derive the extension.
MIME type of the image (e.g.
"image/png", "image/svg+xml"). Allowed: image/png, image/jpeg, image/svg+xml, image/gif, image/x-icon, image/vnd.microsoft.icon. Maximum file size: 2 MB.Promise<{ success: boolean; url?: string; error?: string }>. On success, url contains the publicly accessible URL from Supabase Storage.
Required permission: branding.manage
getPublicBranding
getTenantBranding. Used in public-facing pages and middleware that need branding data without an authenticated session context. Internally calls getTenantBranding with the same arguments.