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,78 @@
|
||||
import type { TarjetaKpi } from "@/lib/crm/tipos";
|
||||
import { valorKpi } from "@/components/admin/formato";
|
||||
import { claseVariacion, subirEsMejor } from "@/components/admin/graficas/paleta";
|
||||
|
||||
/**
|
||||
* Ficha de KPI. Rótulo, cifra y variación contra el periodo anterior.
|
||||
*
|
||||
* La dirección de mejora se resuelve por KPI, no por signo: en horas hasta la
|
||||
* primera respuesta bajar es mejor. Si `variacion` es null no se pinta flecha
|
||||
* —no hay base de comparación y no se inventa una.
|
||||
*/
|
||||
export function TarjetaKpiVista({ kpi, dias }: { kpi: TarjetaKpi; dias: number }) {
|
||||
const mejorArriba = subirEsMejor(kpi.clave, kpi.formato);
|
||||
const v = kpi.variacion;
|
||||
|
||||
return (
|
||||
<div className="panel flex min-w-0 flex-col justify-between gap-3 px-4 py-3.5">
|
||||
<p className="font-mono text-[0.625rem] uppercase leading-snug tracking-[0.12em] text-faint">
|
||||
{kpi.etiqueta}
|
||||
</p>
|
||||
|
||||
<p className="font-mono text-2xl font-semibold leading-none text-bright sm:text-[1.75rem]">
|
||||
{valorKpi(kpi.valor, kpi.formato)}
|
||||
</p>
|
||||
|
||||
<div className="flex min-w-0 flex-wrap items-baseline gap-x-2 gap-y-1">
|
||||
{v == null ? (
|
||||
<span className="font-mono text-[0.625rem] uppercase tracking-[0.1em] text-faint">
|
||||
Sin base de comparación
|
||||
</span>
|
||||
) : (
|
||||
<>
|
||||
<span
|
||||
className={`font-mono text-xs font-semibold ${claseVariacion(v, mejorArriba)}`}
|
||||
>
|
||||
<span aria-hidden="true">{v > 0 ? "▲" : v < 0 ? "▼" : "■"}</span>{" "}
|
||||
{v > 0 ? "+" : ""}
|
||||
{v.toFixed(1)} pp
|
||||
</span>
|
||||
<span className="font-mono text-[0.5625rem] uppercase tracking-[0.1em] text-faint">
|
||||
vs. {dias} días previos
|
||||
</span>
|
||||
<span className="sr-only">
|
||||
{v > 0 ? "sube" : v < 0 ? "baja" : "sin cambio"} {Math.abs(v).toFixed(1)} puntos
|
||||
porcentuales contra los {dias} días previos, lo que{" "}
|
||||
{v === 0 ? "no cambia el indicador" : (v > 0) === mejorArriba ? "es una mejora" : "es un retroceso"}.
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{kpi.nota ? (
|
||||
<p className="text-[0.6875rem] leading-snug text-muted">{kpi.nota}</p>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function FilaKpis({ kpis, dias }: { kpis: TarjetaKpi[]; dias: number }) {
|
||||
if (kpis.length === 0) {
|
||||
return (
|
||||
<div className="panel px-4 py-6">
|
||||
<p className="text-sm text-muted">
|
||||
Todavía no hay indicadores para este periodo. Amplía el rango a 90 días o espera
|
||||
los primeros envíos del sitio.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2 xl:grid-cols-4">
|
||||
{kpis.map((k) => (
|
||||
<TarjetaKpiVista key={k.clave} kpi={k} dias={dias} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user