feat: catálogo como centro del embudo y captura de baja fricción

Reestructuración pedida por el cliente tras revisar la primera versión.

Conversión:
- Formulario de 14 campos a 4: nombre, correo, teléfono y mensaje. La
  clasificación de riesgo ya no se pregunta, se deduce del contexto de la
  página y viaja oculta. Menos fricción, misma riqueza para el análisis.
- El inicio deja de bifurcar y empuja al catálogo: 36 llamados desembocan
  ahí, y las rutas por industria y escenario pasan a ser la forma de entrar
  al catálogo ya filtrado, en lugar de competir como destino.
- Catálogo rehecho como tienda: barra lateral con filtros, búsqueda que
  acepta expresiones regulares, títulos que abren la ficha, un solo botón
  primario por tarjeta y el comparador detrás de un desplegable.
- Ficha de producto orientada al cierre: bloque en lenguaje llano sobre si
  el equipo corresponde al caso, y "Cotizar este equipo" con el formulario
  precargado. Se retiraron los enlaces que sacaban del embudo a leer normas.
- Todos los llamados se unifican en "Recibir cotización".
- Widget de WhatsApp: nombre, teléfono, correo y mensaje libre. El texto que
  escribe la persona es el que viaja a la conversación.

Doble audiencia:
- 13 escenarios con la iconografía original de FSPM traducen un lugar
  reconocible a clases de fuego y productos, para quien no domina la
  terminología. La capa técnica se conserva para búsqueda.
- Héroe con fotografía en todas las landings.

Correcciones:
- El contenedor moría al arrancar porque el CLI de Prisma no podía escribir
  sus motores; el chown ahora incluye /app/prisma-cli.
- Iconos repetidos entre escenarios. El set original no tiene icono de
  cocina, así que ese escenario usa un marcador tipográfico explícito.
- FIREMIKS no aparecía en ningún escenario.
- Los ids de campo del formulario colisionaban con dos instancias por página.
- Se documentó el despliegue, que faltaba por completo.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
Uriel Jareth
2026-08-28 01:30:44 -06:00
co-authored by Claude Opus 5
parent 3c6bdcfc06
commit 0e0331c1fc
580 changed files with 7239 additions and 2683 deletions
+55 -40
View File
@@ -1,6 +1,8 @@
import type { Metadata } from "next";
import Link from "next/link";
import { FormularioContacto } from "@/components/formulario/FormularioContacto";
import { Migas } from "@/components/site/contenido/Migas";
import { FOTOS, HeroeLanding } from "@/components/site/HeroeLanding";
import { Revelar } from "@/components/site/Revelar";
import { EMPRESA, SITIO_URL, WHATSAPP } from "@/data/empresa";
@@ -11,10 +13,14 @@ import { EMPRESA, SITIO_URL, WHATSAPP } from "@/data/empresa";
* atención, ni domicilio con número, ni mapa: nada de eso está confirmado, y
* una dirección equivocada en un sitio de protección contra incendios es un
* problema real, no un detalle.
*
* El formulario pide cuatro campos, así que la página no puede prometer que la
* respuesta llega con la clase de fuego y la superficie ya declaradas: lo que
* promete es que preguntamos nosotros lo que falte.
*/
const TITULO = "Contacto";
const DESCRIPCION = `Habla con ${EMPRESA.nombre}. Teléfono, correo y solicitud de diagnóstico para operaciones que necesitan protección contra incendios documentada.`;
const TITULO = "Contacto: habla con un especialista de FSPM";
const DESCRIPCION = `Teléfono, correo y WhatsApp de ${EMPRESA.nombre}, en ${EMPRESA.ciudad}. Solicita un diagnóstico de protección contra incendios con cuatro campos y te contesta una persona con nombre, no un buzón compartido.`;
export const metadata: Metadata = {
title: TITULO,
@@ -31,37 +37,33 @@ const MENSAJE_WHATSAPP = encodeURIComponent(
"Hola, me gustaría información sobre protección contra incendios.",
);
const MENSAJE_INICIAL = "Necesito proteger ";
export default function PaginaContacto() {
return (
<>
{/* ---------- encabezado ---------- */}
<section className="grid-bg bg-ink pt-12 pb-14 sm:pt-16 sm:pb-16">
<div className="shell">
<nav aria-label="Ruta de navegación" className="mb-8">
<ol className="flex flex-wrap items-center gap-2 font-mono text-[0.625rem] uppercase tracking-[0.14em] text-faint">
<li>
<Link href="/" className="hover:text-bright">
Inicio
</Link>
</li>
<li aria-hidden="true">/</li>
<li className="text-bright">Contacto</li>
</ol>
</nav>
<div className="max-w-3xl anim-rise">
<p className="eyebrow mb-5">Contacto</p>
<h1 className="font-display text-[2rem] font-black uppercase leading-[1.03] tracking-tight sm:text-[2.75rem] lg:text-5xl">
Si tu situación no puede esperar, marca
</h1>
<p className="mt-6 text-base leading-relaxed text-muted sm:text-lg">
Para todo lo demás, el formulario sirve mejor: llega con la clase de fuego, la
superficie y el tipo de operación, así que la primera respuesta puede ser técnica en
lugar de una pregunta de vuelta.
</p>
</div>
<HeroeLanding
antes={<Migas actual="Contacto" />}
eyebrow="Contacto"
titulo="Si tu situación no puede esperar, marca"
entrada="Para todo lo demás, el formulario funciona mejor: son cuatro campos, queda con folio y lo recibe una persona con nombre. Lo que falte lo preguntamos nosotros."
foto={FOTOS.contacto}
enfoque="50% 38%"
altura="alto"
prioridad
>
<div className="flex flex-col gap-4 sm:flex-row sm:items-center">
<a href={`tel:${EMPRESA.telefonoTel}`} className="btn btn-signal w-full sm:w-auto">
Marcar {EMPRESA.telefono}
</a>
<Link
href="#formulario"
className="font-mono text-[0.6875rem] uppercase tracking-[0.12em] text-bright underline decoration-rule-2 underline-offset-4 hover:decoration-signal [-webkit-tap-highlight-color:transparent]"
>
O envía el formulario
</Link>
</div>
</section>
</HeroeLanding>
{/* ---------- datos verificados ---------- */}
<section className="border-t border-rule bg-ink-2 py-12 sm:py-14" aria-labelledby="datos-titulo">
@@ -73,16 +75,16 @@ export default function PaginaContacto() {
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
<a
href={`tel:${EMPRESA.telefonoTel}`}
className="group flex flex-col rounded-[6px] border border-rule bg-ink p-5 transition-colors duration-200 hover:border-signal [-webkit-tap-highlight-color:transparent]"
className="group flex flex-col rounded-[6px] border border-signal/60 bg-ink p-5 transition-colors duration-200 hover:border-signal [-webkit-tap-highlight-color:transparent]"
>
<span className="font-mono text-[0.625rem] uppercase tracking-[0.14em] text-faint">
<span className="font-mono text-[0.625rem] uppercase tracking-[0.14em] text-signal">
Teléfono
</span>
<span className="mt-2.5 font-mono text-lg font-semibold text-bright transition-colors group-hover:text-signal-hi">
{EMPRESA.telefono}
</span>
<span className="mt-2 text-xs leading-relaxed text-muted">
Marca directo desde el móvil.
La vía más rápida. Marca directo desde el móvil.
</span>
</a>
@@ -144,15 +146,26 @@ export default function PaginaContacto() {
</section>
{/* ---------- formulario completo ---------- */}
<section id="formulario" className="grid-bg-lite border-t border-rule bg-paper py-14 sm:py-20">
<section
id="formulario"
className="grid-bg-lite border-t border-rule bg-paper py-14 sm:py-20"
aria-labelledby="solicitud-titulo"
>
<div className="shell">
<div className="grid gap-10 lg:grid-cols-[1fr_1.1fr] lg:items-start lg:gap-16">
<div>
<Revelar>
<p className="eyebrow mb-4">Solicitud</p>
<h2 className="font-display text-2xl font-extrabold text-deep sm:text-3xl">
<h2
id="solicitud-titulo"
className="font-display text-2xl font-extrabold text-deep sm:text-3xl"
>
Qué pasa cuando envías el formulario
</h2>
<p className="mt-4 text-sm leading-relaxed text-deep-muted sm:text-base">
Nombre, correo, teléfono y una línea de contexto. Nada más: los cuestionarios
técnicos de catorce campos son la razón por la que la gente cierra la pestaña.
</p>
</Revelar>
<ol className="mt-8 space-y-6">
@@ -164,8 +177,8 @@ export default function PaginaContacto() {
},
{
n: "02",
t: "Se interpreta el escenario",
d: "Con la clase de fuego, la superficie y el tipo de operación que declaraste, alguien revisa qué configuración aplica y qué habría que descartar.",
t: "Preguntamos lo que falta",
d: "Con la página desde la que escribiste y lo que dejaste en el mensaje ya se intuye el escenario. Lo que no se intuye —qué se puede quemar, quién opera, qué superficie— lo preguntamos nosotros en la primera respuesta.",
},
{
n: "03",
@@ -196,7 +209,7 @@ export default function PaginaContacto() {
{[
{ href: "/preguntas-frecuentes", label: "Preguntas frecuentes" },
{ href: "/cumplimiento-nom-002", label: "Cumplimiento NOM-002" },
{ href: "/evidencia", label: "Centro de evidencia" },
{ href: "/evidencia", label: "Documentación técnica" },
{ href: "/catalogo", label: "Catálogo" },
].map((enlace) => (
<li key={enlace.href}>
@@ -234,11 +247,13 @@ export default function PaginaContacto() {
</div>
<FormularioContacto
id="formulario-contacto"
canal="contacto"
variante="claro"
compacto={false}
titulo="Solicitar diagnóstico"
descripcion="Cuéntanos qué operación hay que proteger. Te respondemos con el escenario, el equipo que aplica y qué documentación te corresponde."
titulo="Recibir cotización"
descripcion="Completa la frase del mensaje con lo que hay que proteger. Te respondemos con el escenario, el equipo que aplica y qué documentación te corresponde."
mensajeInicial={MENSAJE_INICIAL}
textoBoton="Recibir cotización"
/>
</div>
</div>