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,443 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { RielClases } from "@/components/marca/RielClases";
|
||||
import { Revelar } from "@/components/site/Revelar";
|
||||
import { getCategoria } from "@/data/catalogo";
|
||||
import type { ClaseFuego } from "@/data/clases-fuego";
|
||||
import { EMPRESA, SITIO_URL } from "@/data/empresa";
|
||||
import { getIndustria } from "@/data/industrias";
|
||||
|
||||
/**
|
||||
* /casos — escenarios de aplicación con la estructura que pide el estudio:
|
||||
* problema → riesgo → solución → prueba → resultado → documentación.
|
||||
*
|
||||
* REGLA DURA: son escenarios construidos con fines de demostración. No
|
||||
* corresponden a clientes reales, no llevan nombres de empresa y no presentan
|
||||
* cifras como si fueran mediciones. El aviso va arriba, no en una nota al pie.
|
||||
*/
|
||||
|
||||
const TITULO = "Casos de aplicación";
|
||||
const DESCRIPCION =
|
||||
"Cuatro escenarios ilustrativos de protección contra incendios con la estructura problema, riesgo, solución, prueba, resultado y documentación. Construidos con fines de demostración.";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: TITULO,
|
||||
description: DESCRIPCION,
|
||||
alternates: { canonical: `${SITIO_URL}/casos` },
|
||||
openGraph: {
|
||||
title: `${TITULO} | ${EMPRESA.nombreCorto}`,
|
||||
description: DESCRIPCION,
|
||||
url: `${SITIO_URL}/casos`,
|
||||
},
|
||||
};
|
||||
|
||||
type Caso = {
|
||||
id: string;
|
||||
industriaSlug: string;
|
||||
titulo: string;
|
||||
escenario: string;
|
||||
clases: ClaseFuego[];
|
||||
categorias: string[];
|
||||
problema: string;
|
||||
riesgo: string;
|
||||
solucion: string[];
|
||||
prueba: string;
|
||||
resultado: string[];
|
||||
documentacion: string[];
|
||||
};
|
||||
|
||||
const CASOS: Caso[] = [
|
||||
{
|
||||
id: "trasvase",
|
||||
industriaSlug: "industria-energia",
|
||||
titulo: "Patio de trasvase de solventes en una planta química",
|
||||
escenario: "Escenario ilustrativo · Planta química, patio de trasvase con contención secundaria",
|
||||
clases: ["A", "B", "C"],
|
||||
categorias: ["agentes-extinguidores", "productos-moviles"],
|
||||
problema:
|
||||
"El patio de trasvase concentra el riesgo más alto de la planta y está a más de cien metros de la caseta de seguridad. La respuesta disponible es equipo portátil montado en pared, pensado para un conato de oficina, no para un derrame inflamado con producto a presión.",
|
||||
riesgo:
|
||||
"Un derrame encendido en contención secundaria no da minutos: escala en segundos y compromete el resto del patio. El costo real no es el equipo perdido, es el paro de línea, la investigación posterior y la renovación de la póliza.",
|
||||
solucion: [
|
||||
"Diagnóstico por zona con inventario de combustibles y volúmenes reales de operación.",
|
||||
"Equipo móvil de mayor capacidad posicionado a distancia de recorrido, no a distancia en línea recta.",
|
||||
"Selección del agente por tipo de combustible, incluida la condición de alcoholes si hay producto polar.",
|
||||
"Capacitación de la brigada del turno que efectivamente está en el patio de noche.",
|
||||
],
|
||||
prueba:
|
||||
"Demostración con fuego controlado del mismo combustible que se maneja en el patio, operada por el personal del turno y no por un instructor. Se mide el tiempo desde la alarma hasta la primera aplicación de agente y se identifica en qué punto del recorrido se pierde tiempo.",
|
||||
resultado: [
|
||||
"Queda documentado cuánto tarda hoy la primera respuesta en el punto más lejano, que es el dato que la operación no tenía.",
|
||||
"El reposicionamiento del equipo se decide con el recorrido cronometrado, no con un plano.",
|
||||
"La brigada opera el equipo por primera vez antes del incidente, no durante.",
|
||||
],
|
||||
documentacion: [
|
||||
"Memoria del diagnóstico por zona con inventario de combustibles.",
|
||||
"Ficha técnica y hoja de datos de seguridad del agente seleccionado.",
|
||||
"Registro de la demostración con fecha, participantes y tiempos observados.",
|
||||
"Constancia de capacitación por persona y turno.",
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "bodega",
|
||||
industriaSlug: "logistica-bodegas",
|
||||
titulo: "Nave de almacenamiento con carga combustible y montacargas eléctricos",
|
||||
escenario: "Escenario ilustrativo · Centro logístico, nave única con racks de gran altura",
|
||||
clases: ["A", "B", "C"],
|
||||
categorias: ["extintores-portatiles", "rociadores"],
|
||||
problema:
|
||||
"La nave tiene extintores suficientes en el conteo, pero el riesgo cambia de pasillo a pasillo: producto terminado en cartón, un área de carga de baterías de montacargas y una zona de residuos al fondo. El equipo está distribuido de forma uniforme sobre un riesgo que no es uniforme.",
|
||||
riesgo:
|
||||
"El área de carga de baterías introduce riesgo eléctrico dentro de una nave de carga combustible, y la zona de residuos genera reencendido cuando se ataca con un agente que apaga la llama pero deja brasa. Una sola ruta de salida agrava cualquier retraso.",
|
||||
solucion: [
|
||||
"Mapa de riesgo por zona, con la altura de almacenamiento y el tipo de carga de cada pasillo.",
|
||||
"Redistribución del equipo por distancia real de recorrido del operador, no por metros cuadrados.",
|
||||
"Solución de activación automática en las zonas de mayor acumulación y en el área de carga de baterías.",
|
||||
"Programa de inspección mensual con responsable nombrado y bitácora por equipo.",
|
||||
],
|
||||
prueba:
|
||||
"Recorrido cronometrado desde el punto más desfavorable de cada zona hasta el equipo más cercano, con un operador de piso y no con personal de seguridad. Verificación de que cada equipo corresponde a la clase de fuego de su zona.",
|
||||
resultado: [
|
||||
"Se identifican las zonas donde el equipo existente no corresponde a la clase de fuego presente.",
|
||||
"La distribución deja de basarse en el conteo total y empieza a basarse en el recorrido.",
|
||||
"La inspección mensual pasa de ser una intención a tener nombre, formato y fecha.",
|
||||
],
|
||||
documentacion: [
|
||||
"Mapa de riesgo por zona con la clasificación aplicada.",
|
||||
"Inventario de equipo con ubicación, clase y capacidad.",
|
||||
"Bitácora de revisión mensual con responsable asignado.",
|
||||
"Constancia de instalación y puesta en marcha de las soluciones automáticas.",
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "gasolinera",
|
||||
industriaSlug: "gasolineras-flotas",
|
||||
titulo: "Estación de servicio con público en isla y autotanques en patio",
|
||||
escenario: "Escenario ilustrativo · Estación de servicio urbana con seis posiciones de carga",
|
||||
clases: ["A", "B", "C"],
|
||||
categorias: ["productos-moviles", "extintores-portatiles"],
|
||||
problema:
|
||||
"La primera respuesta la da el despachador de turno, no un bombero. El equipo instalado requiere una secuencia de operación que nadie del personal ha practicado, y en la isla hay clientes a pocos metros del punto de carga.",
|
||||
riesgo:
|
||||
"Un fuego de hidrocarburo en isla con público presente convierte cualquier duda de operación en un problema de evacuación. Aplicar agua a chorro sobre combustible derramado esparce el fuego en lugar de contenerlo.",
|
||||
solucion: [
|
||||
"Equipo operable en el primer intento por personal sin entrenamiento profundo, en isla y en patio.",
|
||||
"Agente adecuado para hidrocarburos, con la condición de aplicación declarada por escrito.",
|
||||
"Entrenamiento del personal de los tres turnos, no solo del encargado.",
|
||||
"Procedimiento de respuesta y control de derrame colocado donde ocurre el incidente.",
|
||||
],
|
||||
prueba:
|
||||
"Práctica con fuego controlado en la que cada despachador opera el equipo una vez, sin asistencia. Se registra quién completó la secuencia y quién no, y se repite hasta que todos la completan.",
|
||||
resultado: [
|
||||
"Queda constancia nominal de quién puede operar el equipo y quién todavía no.",
|
||||
"El procedimiento deja de vivir en una carpeta y queda colocado en la isla.",
|
||||
"Se documenta la selección del agente frente a los combustibles que la estación despacha.",
|
||||
],
|
||||
documentacion: [
|
||||
"Ficha técnica del equipo y hoja de datos de seguridad del agente.",
|
||||
"Procedimiento de respuesta y control de derrame, firmado por el responsable de la estación.",
|
||||
"Constancias de capacitación por persona y turno.",
|
||||
"Requisitos adicionales de Protección Civil municipal, cuando la autoridad local los establezca.",
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "cocina",
|
||||
industriaSlug: "cocinas-hoteles",
|
||||
titulo: "Cocina de hotel con freidoras bajo una sola campana",
|
||||
escenario: "Escenario ilustrativo · Hotel de servicio completo, cocina central y comedor",
|
||||
clases: ["A", "B", "K"],
|
||||
categorias: ["rociadores", "extintores-portatiles"],
|
||||
problema:
|
||||
"Bajo la misma campana conviven varias freidoras y planchas. El equipo colgado en la pared no está calificado para fuego de aceite y grasa a temperatura de fritura, y el ducto de extracción acumula grasa entre limpiezas.",
|
||||
riesgo:
|
||||
"Aplicar agua sobre aceite hirviendo provoca proyección violenta de grasa encendida sobre quien opera el equipo. Además del riesgo a la persona, un incidente en campana y ducto detiene el servicio de alimentos y arrastra la ocupación del hotel.",
|
||||
solucion: [
|
||||
"Solución específica por cocina, con atención separada a campana y ducto.",
|
||||
"Equipo apto para clase K en la línea de fritura, con su condición de aplicación documentada.",
|
||||
"Demostración con el personal que realmente opera de noche, incluido el turno de cierre.",
|
||||
"Procedimiento posterior al incidente para reanudar el servicio sin improvisar.",
|
||||
],
|
||||
prueba:
|
||||
"Demostración de fuego de aceite en condiciones controladas, con el personal de cocina operando. Verificación de que la limpieza del ducto está registrada y de que el registro corresponde a lo que se observa en el ducto.",
|
||||
resultado: [
|
||||
"El equipo genérico de la pared se sustituye o se complementa con lo que sí aplica a clase K.",
|
||||
"El personal de cierre sabe qué hacer y qué no hacer con el aceite encendido.",
|
||||
"La reanudación del servicio queda escrita antes de necesitarla, no después.",
|
||||
],
|
||||
documentacion: [
|
||||
"Ficha técnica del equipo apto para clase K y condiciones de aplicación.",
|
||||
"Registro de limpieza de campanas y ductos con fecha y responsable.",
|
||||
"Procedimiento posterior al incidente y de reanudación del servicio.",
|
||||
"Constancias de capacitación del personal de cocina por turno.",
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default function PaginaCasos() {
|
||||
return (
|
||||
<>
|
||||
{/* ---------- encabezado con el aviso destacado arriba ---------- */}
|
||||
<section className="grid-bg bg-ink pt-12 pb-14 sm:pt-16 sm:pb-20">
|
||||
<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">Casos de aplicación</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
{/* el aviso va antes del contenido, no al final */}
|
||||
<div
|
||||
role="note"
|
||||
aria-label="Naturaleza de estos casos"
|
||||
className="mb-10 rounded-[6px] border border-rule bg-ink-2 p-0 sm:mb-12"
|
||||
>
|
||||
<div className="hazard h-1.5 w-full rounded-t-[6px]" aria-hidden="true" />
|
||||
<div className="p-5 sm:p-6">
|
||||
<p className="font-mono text-[0.6875rem] font-medium uppercase tracking-[0.16em] text-ember">
|
||||
Escenarios ilustrativos
|
||||
</p>
|
||||
<p className="mt-3 max-w-3xl text-sm leading-relaxed text-muted">
|
||||
Los cuatro casos de esta página están{" "}
|
||||
<span className="text-bright">
|
||||
construidos con fines de demostración y no corresponden a clientes reales
|
||||
</span>
|
||||
. No incluyen nombres de empresa, y sus resultados se describen de forma
|
||||
cualitativa: ninguna cifra de esta página debe leerse como una medición. Están
|
||||
escritos para mostrar cómo se estructura un caso, no para acreditar uno.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="max-w-3xl anim-rise">
|
||||
<p className="eyebrow mb-5">Casos de aplicación</p>
|
||||
<h1 className="font-display text-[2rem] font-black uppercase leading-[1.03] tracking-tight sm:text-[2.75rem] lg:text-5xl">
|
||||
Seis pasos que convierten una compra en un expediente
|
||||
</h1>
|
||||
<p className="mt-6 text-base leading-relaxed text-muted sm:text-lg">
|
||||
Problema, riesgo, solución, prueba, resultado y documentación. Es la estructura que
|
||||
recomienda el estudio de marca y también el orden en el que conviene trabajar: cuando
|
||||
la prueba se hace antes de la compra, la documentación deja de ser un trámite y se
|
||||
vuelve la constancia de algo que sí ocurrió.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* ---------- los casos ---------- */}
|
||||
{CASOS.map((caso, indice) => {
|
||||
const industria = getIndustria(caso.industriaSlug);
|
||||
const claro = indice % 2 === 0;
|
||||
|
||||
return (
|
||||
<section
|
||||
key={caso.id}
|
||||
id={caso.id}
|
||||
aria-labelledby={`caso-${caso.id}`}
|
||||
className={[
|
||||
"border-t py-14 sm:py-20",
|
||||
claro ? "grid-bg-lite border-rule bg-paper" : "border-rule bg-ink",
|
||||
].join(" ")}
|
||||
>
|
||||
<div className="shell">
|
||||
<div className="grid gap-10 lg:grid-cols-[1fr_1.35fr] lg:gap-16">
|
||||
{/* identificación del caso */}
|
||||
<Revelar className="lg:sticky lg:top-24 lg:self-start">
|
||||
<p
|
||||
className={[
|
||||
"font-mono text-[0.625rem] uppercase tracking-[0.16em]",
|
||||
claro ? "text-deep-muted" : "text-faint",
|
||||
].join(" ")}
|
||||
>
|
||||
Caso {String(indice + 1).padStart(2, "0")} de {CASOS.length}
|
||||
</p>
|
||||
|
||||
<h2
|
||||
id={`caso-${caso.id}`}
|
||||
className={[
|
||||
"mt-4 font-display text-2xl font-extrabold leading-tight sm:text-3xl",
|
||||
claro ? "text-deep" : "text-bright",
|
||||
].join(" ")}
|
||||
>
|
||||
{caso.titulo}
|
||||
</h2>
|
||||
|
||||
<p
|
||||
className={[
|
||||
"mt-4 font-mono text-[0.6875rem] uppercase leading-relaxed tracking-[0.1em]",
|
||||
claro ? "text-deep-muted" : "text-faint",
|
||||
].join(" ")}
|
||||
>
|
||||
{caso.escenario}
|
||||
</p>
|
||||
|
||||
<div className="mt-7">
|
||||
<RielClases
|
||||
activas={caso.clases}
|
||||
tamano="md"
|
||||
claro={claro}
|
||||
etiqueta="Clases del escenario"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ul className="mt-7 flex flex-wrap gap-2">
|
||||
{caso.categorias.map((slug) => {
|
||||
const categoria = getCategoria(slug);
|
||||
if (!categoria) return null;
|
||||
return (
|
||||
<li key={slug}>
|
||||
<Link
|
||||
href={`/catalogo/${categoria.slug}`}
|
||||
className={[
|
||||
"badge [-webkit-tap-highlight-color:transparent]",
|
||||
claro
|
||||
? "!border-rule-lite !text-deep-muted hover:!border-signal hover:!text-deep"
|
||||
: "hover:border-signal hover:text-bright",
|
||||
].join(" ")}
|
||||
>
|
||||
{categoria.nombre}
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
|
||||
{industria ? (
|
||||
<Link
|
||||
href={`/soluciones/${industria.slug}`}
|
||||
className="mt-7 inline-block font-mono text-[0.625rem] uppercase tracking-[0.14em] text-signal hover:text-signal-hi"
|
||||
>
|
||||
Segmento: {industria.nombre} →
|
||||
</Link>
|
||||
) : null}
|
||||
</Revelar>
|
||||
|
||||
{/* los seis pasos */}
|
||||
<div className="space-y-8">
|
||||
<Paso claro={claro} n="01" titulo="Problema">
|
||||
<p className={parrafo(claro)}>{caso.problema}</p>
|
||||
</Paso>
|
||||
|
||||
<Paso claro={claro} n="02" titulo="Riesgo" acento>
|
||||
<p className={parrafo(claro)}>{caso.riesgo}</p>
|
||||
</Paso>
|
||||
|
||||
<Paso claro={claro} n="03" titulo="Solución">
|
||||
<Lista claro={claro} elementos={caso.solucion} />
|
||||
</Paso>
|
||||
|
||||
<Paso claro={claro} n="04" titulo="Prueba">
|
||||
<p className={parrafo(claro)}>{caso.prueba}</p>
|
||||
</Paso>
|
||||
|
||||
<Paso claro={claro} n="05" titulo="Resultado">
|
||||
<Lista claro={claro} elementos={caso.resultado} />
|
||||
<p
|
||||
className={[
|
||||
"mt-4 font-mono text-[0.625rem] uppercase leading-relaxed tracking-[0.1em]",
|
||||
claro ? "text-deep-muted" : "text-faint",
|
||||
].join(" ")}
|
||||
>
|
||||
Resultados descritos de forma cualitativa: este escenario no tiene mediciones
|
||||
reales que reportar.
|
||||
</p>
|
||||
</Paso>
|
||||
|
||||
<Paso claro={claro} n="06" titulo="Documentación">
|
||||
<Lista claro={claro} elementos={caso.documentacion} />
|
||||
</Paso>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* ---------- cierre ---------- */}
|
||||
<section className="border-t border-rule bg-ink-2 py-14 sm:py-20">
|
||||
<div className="shell">
|
||||
<div className="max-w-3xl">
|
||||
<Revelar>
|
||||
<p className="eyebrow mb-4">Sobre los casos reales</p>
|
||||
<h2 className="font-display text-2xl font-extrabold sm:text-3xl">
|
||||
Por qué todavía no publicamos casos con nombre
|
||||
</h2>
|
||||
</Revelar>
|
||||
<p className="mt-4 text-sm leading-relaxed text-muted sm:text-base">
|
||||
Un caso de estudio requiere autorización del cliente sobre qué se puede contar, qué
|
||||
periodo cubre y qué cifras se pueden publicar. Mientras eso no exista por escrito,
|
||||
preferimos escenarios rotulados como estos antes que atribuir un resultado a una marca
|
||||
que no lo ha confirmado.
|
||||
</p>
|
||||
<div className="mt-8 flex flex-wrap gap-3">
|
||||
<Link href="/contacto" className="btn btn-signal">
|
||||
Plantear tu escenario
|
||||
</Link>
|
||||
<Link href="/evidencia" className="btn btn-ghost">
|
||||
Ver el centro de evidencia
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function parrafo(claro: boolean): string {
|
||||
return [
|
||||
"text-sm leading-relaxed sm:text-base",
|
||||
claro ? "text-deep-muted" : "text-muted",
|
||||
].join(" ");
|
||||
}
|
||||
|
||||
function Paso({
|
||||
n,
|
||||
titulo,
|
||||
claro,
|
||||
acento = false,
|
||||
children,
|
||||
}: {
|
||||
n: string;
|
||||
titulo: string;
|
||||
claro: boolean;
|
||||
acento?: boolean;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className={[
|
||||
"border-l-2 pl-5 sm:pl-6",
|
||||
acento ? "border-l-ember" : claro ? "border-l-rule-lite" : "border-l-rule",
|
||||
].join(" ")}
|
||||
>
|
||||
<p
|
||||
className={[
|
||||
"font-mono text-[0.625rem] font-semibold uppercase tracking-[0.16em]",
|
||||
acento ? (claro ? "text-ember-dim" : "text-ember") : "text-signal",
|
||||
].join(" ")}
|
||||
>
|
||||
{n} · {titulo}
|
||||
</p>
|
||||
<div className="mt-3.5">{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Lista({ elementos, claro }: { elementos: string[]; claro: boolean }) {
|
||||
return (
|
||||
<ul className="space-y-3">
|
||||
{elementos.map((elemento) => (
|
||||
<li key={elemento} className="flex gap-3">
|
||||
<span aria-hidden="true" className="mt-2 h-1.5 w-1.5 shrink-0 rounded-full bg-signal" />
|
||||
<p className={parrafo(claro)}>{elemento}</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user