feat: sitio comercial FSPM con micro CRM interno
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]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
122e06a4bb
commit
01b817d098
@@ -0,0 +1,144 @@
|
||||
"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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user