Files
fspm-web/src/app/(sitio)/nosotros/page.tsx
T
Uriel JarethandClaude Opus 5 31ee899987 fix: defectos encontrados por la suite de extremo a extremo
La primera corrida completa dio 57 fallas. Solo una parte eran del
producto; el resto era instrumentación mal escrita. Queda en 261 en verde.

Defectos del producto:

- La atribución de campaña se perdía. La captura vivía en un efecto de
  React, así que si la persona llegaba con parámetros de campaña y
  navegaba antes de hidratar, el primer toque nunca se guardaba y
  utmCampaign llegaba vacío al CRM. Ahora se captura en un script en
  línea que corre al analizar el documento.
- Siete páginas sin og:image: una página que declara su propio openGraph
  no hereda las imágenes del layout, las sobreescribe. Solo se nota al
  compartir el enlace. Se resuelve con una función compartida en seo.ts.
- El panel no se podía usar desde Safari por HTTP: la cookie Secure no se
  guarda y el acceso falla en silencio. Queda explícito y documentado.
- El panel se corría de lado en móvil: la cabecera no cabía a 375px.
- Las tarjetas de perfil del acceso se cortaban en móvil. Un elemento de
  rejilla nace con min-width auto y no puede encogerse bajo su contenido.
  Era además el origen de un desplazamiento que se arrastraba al tablero.
- Las tablas accesibles de las gráficas volvían desplazable la página:
  sr-only fija width 1px y el algoritmo de tablas lo ignora.
- El campo de búsqueda del catálogo no tenía nombre accesible.
- Límite de tasa de 5 envíos por IP: bloqueaba una demostración en vivo
  con varias personas en la misma red. Configurable, ahora 20.
- El salto de contenido pasa de left:-9999px a recorte de 1px.

Instrumentación corregida, con la razón escrita en cada caso:

- El desborde horizontal se mide intentando desplazar la página, no con
  documentElement.scrollWidth, que sobreinforma con contenedores propios.
- La comprobación de accesibilidad respeta el árbol de accesibilidad.
- El aislamiento entre roles se prueba pidiendo por URL una oportunidad
  ajena, no comparando folios entre listados paginados.
- Fuera las esperas por networkidle, que agotaban el tiempo sin fallar.
- Que el panel no esté enlazado se comprueba leyendo el HTML servido.
- Safari no enfoca enlaces con Tab: declarado como del navegador.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-28 02:37:42 -06:00

403 lines
18 KiB
TypeScript

import type { Metadata } from "next";
import Image from "next/image";
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 { CATEGORIAS, PRODUCTOS } from "@/data/catalogo";
import { CLIENTES, EMPRESA, PRENSA, SITIO_URL } from "@/data/empresa";
import { INDUSTRIAS } from "@/data/industrias";
import { imagenesOg } from "@/lib/seo";
/**
* /nosotros — origen de la tecnología, posicionamiento y postura editorial.
*
* La parte más importante de esta página es la sección de postura: por qué la
* empresa documenta en lugar de prometer. Es la traducción del hallazgo central
* del estudio de marca y la razón de ser del resto del sitio.
*
* Los logotipos de clientes se muestran sin atribuirles testimonio ni
* resultado: el estudio pide confirmar la relación antes de usarlos como caso.
*/
const TITULO = "Quiénes somos: protección contra incendios documentada";
const DESCRIPCION = `${EMPRESA.nombre}: de dónde viene la tecnología FireAde, en qué operaciones trabajamos y por qué documentamos en lugar de prometer. La postura editorial que gobierna todo el sitio, explicada en cinco reglas.`;
export const metadata: Metadata = {
title: TITULO,
description: DESCRIPCION,
alternates: { canonical: `${SITIO_URL}/nosotros` },
openGraph: {
title: `${TITULO} | ${EMPRESA.nombreCorto}`,
description: DESCRIPCION,
url: `${SITIO_URL}/nosotros`,
...imagenesOg(),
},
};
const MENSAJE_INICIAL = "Quiero hablar con alguien sobre ";
const POSTURA = [
{
n: "01",
titulo: "Una afirmación sin documento es publicidad",
llano: "Si te decimos algo del equipo, te damos el papel que lo dice.",
texto:
"El sitio anterior presentaba vida útil ilimitada, veinte años de garantía y ausencia de recarga como características del producto. Eran, en el mejor de los casos, afirmaciones con condiciones que nadie escribió. Ahora viven en un bloque aparte de cada ficha, junto con lo que hay que entregar para poder sostenerlas.",
},
{
n: "02",
titulo: "Los campos vacíos se quedan vacíos",
llano: "Si no sabemos un dato, la ficha dice que no lo sabemos.",
texto:
"Cuando no hay dato de alcance, de capacidad o de caudal, la ficha lo dice. No ponemos un guion ni una estimación redondeada: un número inventado en una ficha técnica reaparece en una auditoría con el nombre de quien lo firmó.",
},
{
n: "03",
titulo: "No hay botones de descarga que no descarguen nada",
llano: "Ningún botón de este sitio te va a llevar a un archivo que no existe.",
texto:
"El centro de evidencia explica qué documentación corresponde a cada compra y cómo se solicita por número de modelo. Lo que no hace es simular un acervo. Un botón falso destruye la credibilidad de todo lo demás.",
},
{
n: "04",
titulo: "Los casos van rotulados",
llano: "Los ejemplos de aplicación dicen arriba que son ejemplos.",
texto:
"Los escenarios están construidos con fines de demostración y lo declaran en el encabezado, no en una nota al pie. Un caso con nombre de cliente requiere autorización por escrito sobre qué se puede contar y qué cifras se pueden publicar.",
},
{
n: "05",
titulo: "Preferimos la respuesta incómoda",
llano: "Si algo no nos conviene decirte pero te conviene saberlo, te lo decimos.",
texto:
"Si un certificado no cubre tu caso de uso, si un plazo trae condiciones o si simplemente no tenemos un documento, va en la propuesta. Sale más barato ahí que en la verificación.",
},
];
export default function PaginaNosotros() {
return (
<>
<HeroeLanding
antes={<Migas actual="Nosotros" />}
eyebrow="Quiénes somos"
titulo={EMPRESA.promesa}
entrada={EMPRESA.descripcion}
foto={FOTOS.bombero}
enfoque="58% 32%"
alt="Personal de respuesta a incendios trabajando en una plataforma industrial"
altura="alto"
prioridad
>
<div className="flex flex-col gap-4 sm:flex-row sm:items-center">
<Link href="#hablar" className="btn btn-signal w-full sm:w-auto">
Hablar con un especialista
</Link>
<p className="font-mono text-[0.625rem] uppercase leading-relaxed tracking-[0.1em] text-faint">
Tecnología de origen {EMPRESA.fundacionOrigen} · {CATEGORIAS.length} categorías ·{" "}
{PRODUCTOS.length} equipos y agentes
</p>
</div>
</HeroeLanding>
{/* ---------- origen de la tecnología ---------- */}
<section className="grid-bg-lite bg-paper py-14 sm:py-20" aria-labelledby="origen-titulo">
<div className="shell">
<div className="grid gap-10 lg:grid-cols-[1fr_1.15fr] lg:gap-16">
<div>
<Revelar>
<p className="eyebrow mb-4">Origen</p>
<h2
id="origen-titulo"
className="font-display text-2xl font-extrabold text-deep sm:text-3xl"
>
La tecnología viene de {EMPRESA.empresaOrigen}
</h2>
</Revelar>
<div className="mt-5 space-y-4 text-sm leading-relaxed text-deep-muted sm:text-base">
<p className="text-base text-deep">
No fabricamos un extintor más barato. Traemos una tecnología distinta y
respondemos por su documentación.
</p>
<p>
{EMPRESA.empresaOrigen} se presenta como fundada en {EMPRESA.fundacionOrigen} por{" "}
{EMPRESA.fundadorOrigen}. {EMPRESA.nombreCorto} opera en México como{" "}
{EMPRESA.razonSocial} y usa esa herencia técnica como respaldo de desarrollo.
</p>
<p>
El activo de diferenciación es el agente FireAde 2000 y la familia de equipos
Enforcer, que permiten competir en un terreno distinto al del extintor
convencional: no por precio unitario, sino por tiempo de control, costo de
limpieza y continuidad de la operación.
</p>
<p>
Ese es también el límite honesto de lo que podemos afirmar aquí. Los datos de
origen provienen de la comunicación pública de la empresa; el desempeño de cada
formulación y de cada modelo se demuestra con su documentación, no con su
historia.
</p>
</div>
</div>
<div className="panel-lite p-6 sm:p-8">
<h3 className="font-display text-lg font-extrabold text-deep sm:text-xl">
Dónde decidimos jugar
</h3>
<p className="mt-4 text-sm leading-relaxed text-deep-muted">
El estudio de marca fue explícito: {EMPRESA.nombreCorto} no debería competir por
catálogo más grande ni por precio unitario. El espacio defendible está entre
tecnología especializada, respuesta rápida, evidencia técnica y costo total de
operación.
</p>
<hr className="hair-lite my-6" />
<h4 className="font-mono text-[0.6875rem] uppercase tracking-[0.16em] text-deep-muted">
Los cinco segmentos donde eso aplica
</h4>
<ul className="mt-4 space-y-3">
{INDUSTRIAS.map((industria) => (
<li key={industria.slug} className="flex gap-3">
<span className="mt-0.5 font-mono text-[0.625rem] font-semibold tracking-[0.08em] text-signal">
{String(industria.prioridad).padStart(2, "0")}
</span>
<p className="text-sm leading-relaxed text-deep-muted">
<Link
href={`/soluciones/${industria.slug}`}
className="link font-semibold text-deep"
>
{industria.nombre}
</Link>
{" — "}
{industria.dolor}
</p>
</li>
))}
</ul>
</div>
</div>
</div>
</section>
{/* ---------- postura editorial ---------- */}
<section
className="border-t border-rule bg-ink py-14 sm:py-20"
aria-labelledby="postura-titulo"
>
<div className="shell">
<div className="max-w-3xl">
<p className="font-mono text-[0.6875rem] font-medium uppercase tracking-[0.16em] text-ember">
Postura editorial
</p>
<h2
id="postura-titulo"
className="mt-4 font-display text-2xl font-extrabold sm:text-3xl"
>
Por qué documentamos en lugar de prometer
</h2>
<p className="mt-5 text-base leading-relaxed text-muted">
La competencia promete. En protección contra incendios, una promesa sin documento no
es solo una debilidad comercial: es una exposición legal para quien la compra y para
quien la firma. La auditoría de marca lo señaló como el mayor riesgo del sitio
anterior, y estas cinco reglas son la respuesta.
</p>
</div>
<ol className="mt-12 grid gap-px bg-rule sm:grid-cols-2 lg:grid-cols-3">
{POSTURA.map((regla) => (
<li key={regla.n} className="bg-ink-2 p-6 sm:p-7">
<p className="font-mono text-[0.625rem] font-semibold tracking-[0.1em] text-ember">
{regla.n}
</p>
<h3 className="mt-3 font-display text-base font-extrabold leading-tight text-bright">
{regla.titulo}
</h3>
<p className="mt-3 text-base leading-relaxed text-bright">{regla.llano}</p>
<p className="mt-3 text-sm leading-relaxed text-muted">{regla.texto}</p>
</li>
))}
</ol>
</div>
</section>
{/* ---------- logotipos de clientes, sin atribuirles resultado ---------- */}
<section
className="grid-bg-lite border-t border-rule bg-paper py-14 sm:py-16"
aria-labelledby="clientes-titulo"
>
<div className="shell">
<Revelar>
<p className="eyebrow mb-4">Logotipos publicados por la empresa</p>
<h2
id="clientes-titulo"
className="max-w-2xl font-display text-2xl font-extrabold text-deep sm:text-3xl"
>
Marcas que aparecen en la comunicación de FSPM
</h2>
<p className="mt-4 max-w-2xl text-sm leading-relaxed text-deep-muted sm:text-base">
Se muestran como logotipos y nada más. No les atribuimos testimonio, resultado ni
periodo: mientras la relación no esté confirmada por escrito con cada empresa,
presentarlas como caso de éxito sería exactamente lo que este sitio no hace.
</p>
</Revelar>
<ul className="mt-9 grid grid-cols-2 gap-3 sm:grid-cols-4 lg:grid-cols-6">
{CLIENTES.map((cliente) => (
<li
key={cliente.nombre}
className="flex h-20 items-center justify-center rounded-[3px] border border-rule-lite bg-white px-4"
>
<Image
src={cliente.logo}
alt={cliente.nombre}
width={104}
height={36}
sizes="104px"
className="max-h-9 w-auto object-contain opacity-70"
/>
</li>
))}
</ul>
</div>
</section>
{/* ---------- Christmas Hero, sin exagerarlo ---------- */}
<section
className="border-t border-rule bg-ink py-14 sm:py-20"
aria-labelledby="prensa-titulo"
>
<div className="shell">
<div className="grid gap-10 lg:grid-cols-[1fr_1fr] lg:gap-16">
<div>
<Revelar>
<p className="eyebrow mb-4">Antecedente de comunicación</p>
<h2
id="prensa-titulo"
className="font-display text-2xl font-extrabold sm:text-3xl"
>
&laquo;Christmas Hero&raquo;
</h2>
</Revelar>
<div className="mt-5 space-y-4 text-sm leading-relaxed text-muted sm:text-base">
<p className="text-base text-bright">
Un adorno navideño que apagaba el fuego de un árbol de Navidad. Salió en prensa y
demuestra algo que importa aquí: la empresa sabe explicar lo técnico.
</p>
<p>
En 2019, {EMPRESA.nombreCorto} desarrolló esa pieza de comunicación. Fue publicada
por LatinSpots en 2019 y obtuvo una mención como segundo finalista de El Ojo Local
México.
</p>
<p>
Lo decimos con esa medida, sin convertirlo en más de lo que fue. Importa por dos
razones concretas: demuestra que la empresa sabe traducir un problema técnico en
algo que una persona entiende de inmediato, y demuestra que puede sostener
atención de prensa cuando tiene algo que decir.
</p>
<p>
Ese es exactamente el trabajo pendiente en el resto de la comunicación: la
ingeniería está; la traducción es lo que faltaba.
</p>
</div>
</div>
<div>
<h3 className="font-mono text-[0.6875rem] uppercase tracking-[0.16em] text-faint">
Publicaciones que han cubierto a FSPM
</h3>
<ul className="mt-6 grid grid-cols-2 gap-3 sm:grid-cols-3">
{PRENSA.map((medio) => (
<li key={medio.medio}>
<a
href={medio.url}
target="_blank"
rel="noopener noreferrer"
className="flex h-20 items-center justify-center rounded-[3px] border border-rule bg-ink-2 px-4 transition-colors duration-200 hover:border-signal/60 [-webkit-tap-highlight-color:transparent]"
>
<Image
src={medio.logo}
alt={`Nota en ${medio.medio}`}
width={110}
height={36}
sizes="110px"
className="max-h-9 w-auto object-contain opacity-75 transition-opacity duration-200 hover:opacity-100"
/>
</a>
</li>
))}
</ul>
<p className="mt-5 text-xs leading-relaxed text-faint">
Los enlaces llevan a la nota original de cada medio. Varias corresponden a la
cobertura de Christmas Hero; aparecen como cobertura periodística, no como respaldo
del desempeño de un producto.
</p>
</div>
</div>
</div>
</section>
{/* ---------- conversión ---------- */}
<section
id="hablar"
className="grid-bg-lite border-t border-rule bg-paper py-14 sm:py-20"
aria-labelledby="hablar-titulo"
>
<div className="shell">
<div className="grid gap-10 lg:grid-cols-[1fr_1.05fr] lg:items-start lg:gap-16">
<div>
<Revelar>
<p className="eyebrow mb-4">Siguiente paso</p>
<h2
id="hablar-titulo"
className="font-display text-2xl font-extrabold text-deep sm:text-3xl"
>
Cuéntanos qué hay que proteger
</h2>
<p className="mt-4 text-sm leading-relaxed text-deep-muted sm:text-base">
No necesitas llegar con un requerimiento técnico redactado. Con el tipo de
instalación y lo que te preocupa es suficiente para empezar: la primera respuesta
ya trae la selección propuesta y qué documentación le corresponde.
</p>
</Revelar>
<hr className="hair-lite my-8" />
<h3 className="font-mono text-[0.6875rem] uppercase tracking-[0.16em] text-deep-muted">
O empieza por donde te sirva más
</h3>
<ul className="mt-4 flex flex-wrap gap-2">
{[
{ href: "/catalogo", label: "Catálogo" },
{ href: "/cumplimiento-nom-002", label: "Cumplimiento NOM-002" },
{ href: "/evidencia", label: "Documentación técnica" },
{ href: "/preguntas-frecuentes", label: "Preguntas frecuentes" },
].map((enlace) => (
<li key={enlace.href}>
<Link
href={enlace.href}
className="badge !border-rule-lite !text-deep-muted hover:!border-signal hover:!text-deep [-webkit-tap-highlight-color:transparent]"
>
{enlace.label}
</Link>
</li>
))}
</ul>
</div>
<FormularioContacto
id="formulario-nosotros"
canal="catalogo"
variante="claro"
titulo="Hablar con un especialista"
descripcion="Completa la frase del mensaje con lo que necesitas resolver. Te contesta una persona con nombre, no un buzón."
mensajeInicial={MENSAJE_INICIAL}
textoBoton="Solicitar contacto"
/>
</div>
</div>
</section>
</>
);
}