Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/gcsconsultores/gcs-website/llms.txt

Use this file to discover all available pages before exploring further.

The CRM integration uses an adapter pattern inside the sendToCrm() function in app/actions/lead-actions.ts. Once a lead passes Zod validation in submitLead(), the validated data is handed off to sendToCrm() — the single place where all CRM-specific HTTP logic lives. To connect a real CRM you only need to set two environment variables; no application code needs to change for standard integrations, and remapping field names to fit a provider’s schema requires editing just the body literal inside sendToCrm.

Required environment variables

CRM_API_URL
string
The full endpoint URL for creating a contact or lead record in your CRM (e.g. https://api.hubapi.com/crm/v3/objects/contacts). When this variable is absent, sendToCrm logs the lead to the server console and returns cleanly — no data is lost during development.
CRM_API_KEY
string
The API key or Bearer token for your CRM. Sent as the Authorization: Bearer <CRM_API_KEY> header on every request. For providers that use a different authentication header (e.g. api-key or X-Auth-Token), update the headers object inside sendToCrm.
Set these variables in the Vercel dashboard under Settings → Environment Variables for each deployment environment (Production, Preview, Development). This keeps credentials out of source code and lets you point Preview deployments at a sandbox CRM while Production uses the live instance.

Payload schema

Every call to sendToCrm sends the following JSON body to CRM_API_URL. The outer properties wrapper matches HubSpot’s contact creation format; remap the keys for other providers (see per-CRM examples below).
{
  "properties": {
    "full_name": "string",
    "company": "string",
    "email": "string",
    "phone": "string",
    "area_of_interest": "string (crecimiento|riesgo|estrategia|innovacion|talento|calidad|otro)",
    "message": "string (optional — empty string when not provided)",
    "source": "Sitio web GCS — Orientación Estratégica",
    "submitted_at": "ISO 8601 datetime"
  }
}
submitted_at is generated server-side via new Date().toISOString() at the moment the action runs — it is not supplied by the browser.

Integration examples

HubSpot’s Private App tokens use a Bearer authorization header. The default properties wrapper in sendToCrm already matches HubSpot’s contact creation endpoint — no body changes are needed for a basic integration.
CRM_API_URL=https://api.hubapi.com/crm/v3/objects/contacts
CRM_API_KEY=pat-na1-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
The request lands in HubSpot as a new Contact with the properties listed above. Map custom properties in HubSpot’s object settings if full_name, area_of_interest, etc. are not yet defined as property keys in your portal.

Customizing the request body

All property remapping is confined to the body argument of the fetch call inside sendToCrm. Locate this section in app/actions/lead-actions.ts and edit only the keys inside properties (or replace the entire body shape) to match your CRM’s expected format:
body: JSON.stringify({
  properties: {
    full_name: lead.name,          // ← rename key to match your CRM field
    company: lead.company,
    email: lead.email,
    phone: lead.phone,
    area_of_interest: lead.interest,
    message: lead.message ?? '',
    source: 'Sitio web GCS — Orientación Estratégica',
    submitted_at: new Date().toISOString(),
  },
}),
The lead argument is fully typed as LeadInput (inferred from leadSchema), so TypeScript will flag any accidental typos in field names.

Full sendToCrm function source

async function sendToCrm(lead: LeadInput): Promise<void> {
  const endpoint = process.env.CRM_API_URL
  const apiKey = process.env.CRM_API_KEY

  // If the CRM has not been configured yet, log the lead so it is
  // not lost during development or staging.
  if (!endpoint || !apiKey) {
    console.log('[v0] Nuevo lead (CRM no configurado todavía):', {
      name: lead.name,
      company: lead.company,
      email: lead.email,
      phone: lead.phone,
      interest: lead.interest,
    })
    return
  }

  // Real CRM integration. Adjust the body keys to match your provider.
  await fetch(endpoint, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${apiKey}`,
    },
    body: JSON.stringify({
      properties: {
        full_name: lead.name,
        company: lead.company,
        email: lead.email,
        phone: lead.phone,
        area_of_interest: lead.interest,
        message: lead.message ?? '',
        source: 'Sitio web GCS — Orientación Estratégica',
        submitted_at: new Date().toISOString(),
      },
    }),
  })
}

Error handling

sendToCrm is called inside a try/catch block in submitLead. If the fetch call throws (network timeout, DNS failure, etc.) or if the CRM returns a non-2xx status that causes fetch to throw, the error is caught and a user-facing ok: false response is returned — the raw error is never exposed to the browser.
try {
  await sendToCrm(parsed.data)
  return {
    ok: true,
    message:
      '¡Gracias! Hemos recibido su solicitud. Un consultor de GCS se pondrá en contacto en menos de 24 horas hábiles.',
  }
} catch (error) {
  console.log('[v0] Error al enviar lead al CRM:', error)
  return {
    ok: false,
    message:
      'Ocurrió un problema al enviar su solicitud. Por favor intente de nuevo en unos minutos.',
  }
}
Note that fetch itself does not throw on non-2xx responses — it only throws on network-level failures. To treat 4xx/5xx CRM responses as errors, add a response status check inside sendToCrm:
const response = await fetch(endpoint, { /* ... */ })

if (!response.ok) {
  throw new Error(`CRM returned ${response.status}: ${await response.text()}`)
}
When CRM_API_URL is not set, sendToCrm returns immediately after logging — it does not throw. This means submitLead will still return ok: true to the user, confirming receipt of the form, while the lead data is safely captured in server logs until the CRM is configured.

Build docs developers (and LLMs) love