"use client"; import { useEffect, useRef, useState } from "react"; import { RielClasesSelector } from "@/components/marca/RielClases"; import type { ClaseFuego } from "@/data/clases-fuego"; import { OPCIONES_INDUSTRIA } from "@/data/industrias"; import type { Canal } from "@/lib/crm/constantes"; import type { RespuestaLead } from "@/lib/leads/esquema"; import { capturarAtribucion, type Atribucion } from "@/lib/utm"; /** * Formulario de captación embebido. Va en el inicio, en el catálogo y en cada * ficha de producto, y siempre viaja con su contexto: qué producto se estaba * viendo, en qué URL y con qué campaña llegó el visitante. * * No pide "solicita información": pide los datos que un ingeniero necesita para * cotizar sin una segunda vuelta de correos. */ export type ProductoInteres = { slug: string; nombre: string; categoriaSlug?: string; }; type Props = { canal: Canal; titulo?: string; descripcion?: string; /** Cuando el formulario vive en una ficha, el producto ya viene resuelto. */ producto?: ProductoInteres; industriaPreseleccionada?: string; clasesPreseleccionadas?: ClaseFuego[]; /** "claro" para las secciones sobre papel. */ variante?: "oscuro" | "claro"; /** Solo nombre, correo, teléfono y mensaje. Para espacios reducidos. */ compacto?: boolean; className?: string; }; type Estado = "listo" | "enviando" | "hecho" | "error"; const URGENCIAS = [ { value: "inmediata", label: "Inmediata — tengo un hallazgo abierto" }, { value: "trimestre", label: "Este trimestre" }, { value: "presupuesto", label: "Estoy armando presupuesto" }, { value: "explorando", label: "Explorando opciones" }, ]; export function FormularioContacto({ canal, titulo = "Solicitar diagnóstico", descripcion = "Cuéntanos qué operación hay que proteger. Te respondemos con el escenario, el agente adecuado y qué documentación te corresponde.", producto, industriaPreseleccionada, clasesPreseleccionadas = [], variante = "oscuro", compacto = false, className = "", }: Props) { const claro = variante === "claro"; const [estado, setEstado] = useState("listo"); const [errores, setErrores] = useState>({}); const [errorGeneral, setErrorGeneral] = useState(null); const [folio, setFolio] = useState(null); const [atiende, setAtiende] = useState(null); const [clases, setClases] = useState(clasesPreseleccionadas); const atribucion = useRef({}); const regionEstado = useRef(null); useEffect(() => { atribucion.current = capturarAtribucion(); }, []); // llevar el foco al aviso de resultado, para lector de pantalla useEffect(() => { if (estado === "hecho" || estado === "error") regionEstado.current?.focus(); }, [estado]); async function enviar(evento: React.FormEvent) { evento.preventDefault(); if (estado === "enviando") return; const datos = new FormData(evento.currentTarget); setEstado("enviando"); setErrores({}); setErrorGeneral(null); const cuerpo = { nombre: String(datos.get("nombre") ?? ""), email: String(datos.get("email") ?? ""), telefono: String(datos.get("telefono") ?? ""), empresa: String(datos.get("empresa") ?? "") || undefined, puesto: String(datos.get("puesto") ?? "") || undefined, ciudad: String(datos.get("ciudad") ?? "") || undefined, industria: String(datos.get("industria") ?? "") || undefined, superficie: String(datos.get("superficie") ?? "") || undefined, urgencia: String(datos.get("urgencia") ?? "") || undefined, mensaje: String(datos.get("mensaje") ?? "") || undefined, clases: clases.length ? clases : undefined, origen: "FORMULARIO" as const, canal, productoSlug: producto?.slug, productoNombre: producto?.nombre, categoriaSlug: producto?.categoriaSlug, productoUrl: typeof window !== "undefined" ? window.location.href.slice(0, 400) : undefined, privacidad: datos.get("privacidad") === "on", _sitio: String(datos.get("_sitio") ?? ""), ...atribucion.current, }; try { const res = await fetch("/api/leads", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(cuerpo), }); const json = (await res.json()) as RespuestaLead; if (json.ok) { setFolio(json.folio); setAtiende(json.atiende ?? null); setEstado("hecho"); return; } setErrores(json.campos ?? {}); setErrorGeneral(json.error); setEstado("error"); } catch { setErrorGeneral("No pudimos enviar la solicitud. Revisa tu conexión e inténtalo otra vez."); setEstado("error"); } } // ---------- acuse de recibo ---------- if (estado === "hecho") { return (

Solicitud registrada

Recibimos tu solicitud

{atiende ? `${atiende} tiene tu caso asignado y da seguimiento.` : "Un especialista tiene tu caso asignado y da seguimiento."}{" "} Si tu situación no puede esperar, marca directo al{" "} 55 4034 8395 .

Folio
{folio}
Registrado
{new Date().toLocaleString("es-MX", { day: "2-digit", month: "short", hour: "2-digit", minute: "2-digit", })}
); } // ---------- formulario ---------- const claseCampo = claro ? "field field-lite" : "field"; const claseEtiqueta = claro ? "field-label !text-deep-muted" : "field-label"; return (

{producto ? "Cotizar este equipo" : "Diagnóstico sin costo"}

{titulo}

{descripcion}

{producto ? (

Interés {producto.nombre}

) : null}
{/* señuelo antispam: invisible y fuera del orden de tabulación */}
{errores.nombre ? ( {errores.nombre} ) : null}
{errores.email ? ( {errores.email} ) : null}
{errores.telefono ? ( {errores.telefono} ) : null}
{!compacto ? ( <>
{/* el riel: el visitante declara su propio riesgo */}
Clases de fuego presentes

{clases.length === 0 ? "Si no las conoces, déjalo vacío y lo clasificamos nosotros" : `Declaraste ${clases.length} ${clases.length === 1 ? "clase" : "clases"}`}

) : null}