Sitio público orientado a venta consultiva e implementación de las recomendaciones de la auditoría de marca y mercado: - Inicio con tres rutas (industria, riesgo, cumplimiento) y clasificador de riesgo en el héroe, contra el hallazgo de que el visitante tenía que inferir qué solución le correspondía. - Cinco páginas por segmento prioritario y catálogo comparable por clase de fuego, capacidad y agente. - Los claims sin evidencia por modelo quedan aislados y se presentan como compromiso de documentación, nunca como característica del producto. - SEO técnico completo: metadatos por página, sitemap derivado de los datos, JSON-LD, iconos y tarjeta social generadas. Micro CRM: - Captura por formulario embebido y por widget de WhatsApp que registra antes de abrir la conversación. - Atribución de primer toque que sobrevive a la navegación. - Deduplicación de contacto; un mismo contacto acumula oportunidades. - Motor de asignación por reglas con reparto por turno de respaldo. - Panel con dos roles de alcance distinto, tablero analítico, bitácora, pedidos y reglas. No enlazado desde el sitio público y con noindex. Empaquetado en Docker multietapa con la base de demostración horneada en la imagen y el volumen de datos separado, para que los leads capturados sobrevivan a los redespliegues. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
145 lines
5.5 KiB
TypeScript
145 lines
5.5 KiB
TypeScript
"use client";
|
|
|
|
import Link from "next/link";
|
|
import { useId, useMemo, useState } from "react";
|
|
import { RielClasesSelector } from "@/components/marca/RielClases";
|
|
import { serializeClases, type ClaseFuego } from "@/data/clases-fuego";
|
|
import { INDUSTRIAS } from "@/data/industrias";
|
|
|
|
/**
|
|
* EL CLASIFICADOR DE RIESGO — el elemento firma en acción.
|
|
*
|
|
* El hallazgo central del estudio de marca es que el visitante tenía que
|
|
* adivinar qué solución le correspondía. Esto lo resuelve en dos pasos:
|
|
* elige su tipo de operación, enciende las clases de fuego que tiene, y el
|
|
* botón lo deja en la ruta correcta con su riesgo ya declarado
|
|
* (`/soluciones/<slug>?clases=B,C`), que la página de destino y el formulario
|
|
* leen como preselección.
|
|
*
|
|
* Degradación sin JavaScript: el `<noscript>` oculta la interfaz interactiva y
|
|
* publica las cinco rutas como enlaces directos, cada una con las clases
|
|
* dominantes de su segmento en la query string.
|
|
*/
|
|
|
|
export function Clasificador() {
|
|
const [slug, setSlug] = useState<string>(INDUSTRIAS[0].slug);
|
|
const [clases, setClases] = useState<ClaseFuego[]>([]);
|
|
const idPaso1 = useId();
|
|
const idPaso2 = useId();
|
|
|
|
const industria = useMemo(
|
|
() => INDUSTRIAS.find((i) => i.slug === slug) ?? INDUSTRIAS[0],
|
|
[slug],
|
|
);
|
|
|
|
// Sin clases encendidas viajan las dominantes del segmento: el destino
|
|
// nunca queda sin contexto de riesgo.
|
|
const clasesEfectivas = clases.length > 0 ? clases : industria.clases;
|
|
const destino = `/soluciones/${industria.slug}?clases=${serializeClases(clasesEfectivas)}`;
|
|
|
|
return (
|
|
<div className="panel bg-ink-2/85 p-4 sm:p-5" style={{ WebkitBackdropFilter: "blur(6px)", backdropFilter: "blur(6px)" }}>
|
|
<noscript>
|
|
<style>{".clasif-js{display:none!important}.clasif-nojs{display:block!important}"}</style>
|
|
</noscript>
|
|
|
|
<div className="clasif-js">
|
|
{/* Paso 01 — tipo de operación */}
|
|
<p
|
|
id={idPaso1}
|
|
className="font-mono text-[0.625rem] uppercase tracking-[0.16em] text-faint"
|
|
>
|
|
<span className="text-signal">01</span> Tu tipo de operación
|
|
</p>
|
|
|
|
<div
|
|
role="radiogroup"
|
|
aria-labelledby={idPaso1}
|
|
className="mt-2.5 flex flex-wrap gap-1.5"
|
|
>
|
|
{INDUSTRIAS.map((ind) => {
|
|
const activa = ind.slug === industria.slug;
|
|
return (
|
|
<button
|
|
key={ind.slug}
|
|
type="button"
|
|
role="radio"
|
|
aria-checked={activa}
|
|
onClick={() => setSlug(ind.slug)}
|
|
className={[
|
|
"rounded-[3px] border px-2.5 py-2 text-left font-mono text-[0.6875rem] uppercase tracking-[0.06em] transition-colors duration-150",
|
|
activa
|
|
? "border-signal bg-signal/12 text-bright"
|
|
: "border-rule-2 text-muted hover:border-muted hover:text-bright",
|
|
].join(" ")}
|
|
style={{
|
|
WebkitAppearance: "none",
|
|
appearance: "none",
|
|
WebkitTapHighlightColor: "transparent",
|
|
}}
|
|
>
|
|
{ind.nombre}
|
|
</button>
|
|
);
|
|
})}
|
|
</div>
|
|
|
|
<hr className="hair my-4" />
|
|
|
|
{/* Paso 02 — clases de fuego presentes */}
|
|
<div className="flex flex-col gap-3 sm:flex-row sm:items-end sm:justify-between">
|
|
<div>
|
|
<p
|
|
id={idPaso2}
|
|
className="font-mono text-[0.625rem] uppercase tracking-[0.16em] text-faint"
|
|
>
|
|
<span className="text-signal">02</span> Clases de fuego que tienes
|
|
</p>
|
|
|
|
{/* El riel se enciende una sola vez al montar: es el elemento firma
|
|
presentándose, no un efecto que se repite. */}
|
|
<div className="anim-ignite mt-2.5" aria-describedby={idPaso2}>
|
|
<RielClasesSelector valor={clases} onChange={setClases} tamano="lg" />
|
|
</div>
|
|
</div>
|
|
|
|
<p className="max-w-[16rem] text-xs leading-relaxed text-faint">
|
|
{clases.length === 0
|
|
? `Si no lo sabes, seguimos con las clases habituales del segmento: ${industria.clases.join(", ")}.`
|
|
: `Riesgo declarado: ${clases.join(", ")}. Viaja contigo a la siguiente pantalla.`}
|
|
</p>
|
|
</div>
|
|
|
|
<Link href={destino} className="btn btn-signal mt-4 w-full sm:w-auto">
|
|
Ver mi ruta
|
|
<svg width="14" height="10" viewBox="0 0 14 10" aria-hidden="true">
|
|
<path d="M1 5h11M8.5 1.5L12 5l-3.5 3.5" stroke="currentColor" strokeWidth="1.5" fill="none" />
|
|
</svg>
|
|
</Link>
|
|
</div>
|
|
|
|
{/* Respaldo sin JavaScript: las cinco rutas como enlaces. */}
|
|
<div className="clasif-nojs hidden">
|
|
<p className="font-mono text-[0.625rem] uppercase tracking-[0.16em] text-faint">
|
|
Elige tu tipo de operación
|
|
</p>
|
|
<ul className="mt-2.5 divide-y divide-rule">
|
|
{INDUSTRIAS.map((ind) => (
|
|
<li key={ind.slug}>
|
|
<Link
|
|
href={`/soluciones/${ind.slug}?clases=${serializeClases(ind.clases)}`}
|
|
className="flex items-baseline justify-between gap-3 py-2.5"
|
|
>
|
|
<span className="text-sm font-semibold text-bright">{ind.nombre}</span>
|
|
<span className="font-mono text-[0.6875rem] text-signal">
|
|
{ind.clases.join("·")}
|
|
</span>
|
|
</Link>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|