diff --git a/.gitignore b/.gitignore index 069a118..4993434 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ node_modules/ .env.coolify tests/reporte/ tsconfig.tsbuildinfo +test-results/ +playwright-report/ diff --git a/Dockerfile b/Dockerfile index ec2546f..a376684 100644 --- a/Dockerfile +++ b/Dockerfile @@ -161,8 +161,13 @@ RUN chmod +x /usr/local/bin/docker-entrypoint.sh # El directorio de datos nace con dueño nextjs: si el volumen se crea vacío, # Docker hereda estos permisos y el proceso no root puede escribir la base. +# /app/prisma-cli entra en el chown a propósito: al arrancar, `prisma db push` +# verifica y a veces reescribe sus motores dentro de su propio node_modules. Sin +# permiso de escritura el contenedor muere con +# "Can't write to /app/prisma-cli/node_modules/@prisma/engines" y el arranque +# nunca llega a Next. Se detectó con el contenedor ya construido, no en el build. RUN mkdir -p /app/data \ - && chown -R nextjs:nodejs /app/data /app/semilla /app/.next + && chown -R nextjs:nodejs /app/data /app/semilla /app/.next /app/prisma-cli USER nextjs diff --git a/docs/DESPLIEGUE.md b/docs/DESPLIEGUE.md new file mode 100644 index 0000000..7b7d6e0 --- /dev/null +++ b/docs/DESPLIEGUE.md @@ -0,0 +1,149 @@ +# Despliegue + +Sitio de FSPM con micro CRM. Un solo contenedor: Next.js en modo `standalone` y +SQLite en un volumen. No hay base de datos externa. + +## Variables de entorno + +| Variable | Requerida | Qué pasa si falta | +|---|---|---| +| `DATABASE_URL` | Sí | Prisma no arranca. En el contenedor ya viene fijada a `file:/app/data/fspm.db`; **no la cambies** salvo que muevas el volumen. | +| `AUTH_SECRET` | Sí | El arranque falla con un error explícito. Firma las cookies de sesión del panel. | +| `NEXT_PUBLIC_SITE_URL` | Sí, **en construcción** | Las URL canónicas, el sitemap y Open Graph apuntan al dominio equivocado. Ver la advertencia de abajo. | +| `NEXT_PUBLIC_WHATSAPP` | Sí, **en construcción** | El widget de WhatsApp abre una conversación con un número vacío. | +| `DEMO_ACCESO_RAPIDO` | No | Con cualquier valor distinto de `false`, el acceso de un clic por perfil está activo en `/admin/login`. | +| `NODE_ENV` | No | Ya viene en `production` dentro de la imagen. | + +### Advertencia sobre las variables `NEXT_PUBLIC_*` + +Next las sustituye **literalmente durante `next build`**, no las lee al arrancar. +Cambiar `NEXT_PUBLIC_SITE_URL` o `NEXT_PUBLIC_WHATSAPP` en el entorno del +contenedor **no tiene ningún efecto**: hay que reconstruir la imagen pasándolas +como `--build-arg`. Es el error más fácil de cometer aquí, y se manifiesta como +un sitemap con el dominio incorrecto y un widget de WhatsApp que no marca a nadie. + +```bash +docker build \ + --build-arg NEXT_PUBLIC_SITE_URL=https://fspm-demo.urieljareth.org \ + --build-arg NEXT_PUBLIC_WHATSAPP=525540348395 \ + -t fspm-web:demo . +``` + +### Generar un `AUTH_SECRET` + +```bash +node -e "console.log(require('crypto').randomBytes(48).toString('base64url'))" +``` + +Uno por entorno. No reutilices el de desarrollo: cualquiera que lo tenga puede +firmar una cookie de sesión válida y entrar al panel como administrador. + +## El volumen — la parte frágil + +**Si no montas `/app/data`, cada redespliegue borra los leads capturados.** + +La base de demostración se hornea dentro de la imagen durante la construcción. +Al arrancar, el punto de entrada la copia al volumen **solo si el volumen está +vacío**; si ya hay una base, no la toca. Ese es el mecanismo que permite que los +leads que llegan por el formulario y por el widget sobrevivan a una nueva versión +de la imagen. + +Sin volumen, el contenedor sigue funcionando —arranca con los datos sembrados—, +así que la falla es silenciosa: nadie se entera hasta que un lead real desaparece. + +En Coolify: **Storages → Add → Volume Mount**, con destino `/app/data`. +Verifícalo después del primer despliegue con: + +```bash +docker inspect --format '{{json .Mounts}}' +``` + +## Local con Docker + +```bash +docker build -t fspm-web:demo . + +docker run -d --name fspm-demo -p 3100:3000 \ + -v fspm-datos:/app/data \ + -e AUTH_SECRET="" \ + fspm-web:demo + +curl -s http://127.0.0.1:3100/api/salud +``` + +`/api/salud` consulta la base, así que un volumen no escribible se reporta como +contenedor enfermo en lugar de pasar por sano. + +## Coolify + +Los endpoints `POST /applications/*` de la API devuelven 404 en esta instancia +(verificado en 4.1.2 y de nuevo en 4.3.12), así que la aplicación **se crea desde +la interfaz web** y solo el despliegue se dispara por API. + +1. Crea la aplicación apuntando al repositorio de Gitea, rama `main`, + tipo **Dockerfile**. +2. Fija el dominio, para que Coolify no asigne uno aleatorio y devuelva 503. +3. Carga las variables de entorno. Las `NEXT_PUBLIC_*` van también como + argumentos de construcción. +4. Monta el volumen en `/app/data`. +5. Despliega y vigila el registro. + +Despliegues posteriores: + +```bash +curl -H "Authorization: Bearer $COOLIFY_TOKEN" \ + "$COOLIFY_API_URL/deploy?uuid=" +``` + +## Verificación posterior al despliegue + +Hazla en este orden; cada punto descarta una clase distinta de falla. + +```bash +BASE=https://fspm-demo.urieljareth.org + +curl -s $BASE/api/salud # {"ok":true,...} +curl -s $BASE/robots.txt | grep admin # debe prohibir /admin +curl -s $BASE/sitemap.xml | grep -c admin # debe dar 0 +curl -sI $BASE/icon | grep -i content-type # image/* +curl -sI $BASE/opengraph-image | grep -i content-type +curl -s $BASE/sitemap.xml | grep -o 'https://[^<]*' | head -3 # ¿el dominio correcto? +``` + +Después, a mano: + +1. Envía el formulario del inicio y confirma que devuelve folio. +2. Abre el widget de WhatsApp, envíalo y revisa que el enlace lleve el prefijo + `[DEMO]`, el folio y el mensaje que escribiste. +3. Entra a `/admin/login` —**no está enlazado desde el sitio**, es intencional— y + usa el acceso rápido para ver el panel desde los dos roles. +4. Confirma que la oportunidad de los pasos 1 y 2 aparece en el panel con su + campaña y su producto de interés. +5. Redespliega y vuelve a entrar al panel: los leads de los pasos 1 y 2 **deben + seguir ahí**. Si desaparecieron, el volumen no está montado. + +## Antes de enseñarlo a un cliente + +- `AUTH_SECRET` distinto del de desarrollo. +- `DEMO_ACCESO_RAPIDO=false` si la demo queda publicada sin acompañamiento: con + el acceso rápido activo, cualquiera que llegue a `/admin/login` entra como + administrador. +- Los datos del panel son ficticios y los precios de los pedidos son inventados. + Conviene decirlo en voz alta antes de abrir la pantalla de pedidos. + +## Notas del entorno de desarrollo + +- **El repositorio vive en una carpeta sincronizada a la nube.** El cliente de + sincronización mueve archivos de `.next` a media compilación y `npm run build` + falla con `Cannot find module './NNNN.js'` aunque el código esté correcto. + Está reproducido. La construcción dentro de Docker no se ve afectada, porque + copia el contexto y compila en el contenedor. Para verificar en local, usa la + imagen, no `next build`. +- `next.config.ts` acepta `NEXT_DIST_DIR` para mover el directorio de salida, + pero Next lo resuelve relativo a la raíz del proyecto y no acepta rutas + absolutas, así que no resuelve el problema anterior por sí solo. +- El `chown` del Dockerfile incluye `/app/prisma-cli` a propósito: al arrancar, + `prisma db push` reescribe sus motores dentro de su propio `node_modules`, y + sin permiso el contenedor muere con + `Can't write to /app/prisma-cli/node_modules/@prisma/engines`. Se detectó con + el contenedor ya construido, nunca durante el build. diff --git a/package-lock.json b/package-lock.json index 296c890..5e3adf9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "zod": "4.1.11" }, "devDependencies": { - "@playwright/test": "1.56.0", + "@playwright/test": "^1.62.1", "@tailwindcss/postcss": "4.1.13", "@types/node": "22.18.6", "@types/react": "19.1.13", @@ -1217,19 +1217,19 @@ } }, "node_modules/@playwright/test": { - "version": "1.56.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.56.0.tgz", - "integrity": "sha512-Tzh95Twig7hUwwNe381/K3PggZBZblKUe2wv25oIpzWLr6Z0m4KgV1ZVIjnR6GM9ANEqjZD7XsZEa6JL/7YEgg==", + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.62.1.tgz", + "integrity": "sha512-DTcUc8qii+cpHvtOwggMtBRMjKZHXYWdw8syRYu2vtzuq4Wxphqq4NfCs5Zt44L6mA8rfDfj+PHnxFc/FeK6mQ==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.56.0" + "playwright": "1.62.1" }, "bin": { "playwright": "cli.js" }, "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/@prisma/client": { @@ -2456,35 +2456,35 @@ } }, "node_modules/playwright": { - "version": "1.56.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.56.0.tgz", - "integrity": "sha512-X5Q1b8lOdWIE4KAoHpW3SE8HvUB+ZZsUoN64ZhjnN8dOb1UpujxBtENGiZFE+9F/yhzJwYa+ca3u43FeLbboHA==", + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.62.1.tgz", + "integrity": "sha512-0M+L3LAD8/nm554LOla9Ayx0j0tmFZ0FBcoQ7F1VuVHpM/XpiC8RcDzBQB8W5+hA8L22THxELzeF+2WcUzvcLg==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.56.0" + "playwright-core": "1.62.1" }, "bin": { "playwright": "cli.js" }, "engines": { - "node": ">=18" + "node": ">=20" }, "optionalDependencies": { "fsevents": "2.3.2" } }, "node_modules/playwright-core": { - "version": "1.56.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.56.0.tgz", - "integrity": "sha512-1SXl7pMfemAMSDn5rkPeZljxOCYAmQnYLBTExuh6E8USHXGSX3dx6lYZN/xPpTz1vimXmPA9CDnILvmJaB8aSQ==", + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.62.1.tgz", + "integrity": "sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==", "devOptional": true, "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" }, "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/playwright/node_modules/fsevents": { diff --git a/package.json b/package.json index 9f7e1c7..56ab7f7 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "zod": "4.1.11" }, "devDependencies": { + "@playwright/test": "^1.62.1", "@tailwindcss/postcss": "4.1.13", "@types/node": "22.18.6", "@types/react": "19.1.13", @@ -32,8 +33,7 @@ "prisma": "6.16.2", "tailwindcss": "4.1.13", "tsx": "4.20.5", - "typescript": "5.9.2", - "@playwright/test": "1.56.0" + "typescript": "5.9.2" }, "prisma": { "seed": "tsx prisma/seed.ts" diff --git a/src/app/(sitio)/aviso-de-privacidad/page.tsx b/src/app/(sitio)/aviso-de-privacidad/page.tsx index ed2b54b..0e5498f 100644 --- a/src/app/(sitio)/aviso-de-privacidad/page.tsx +++ b/src/app/(sitio)/aviso-de-privacidad/page.tsx @@ -1,5 +1,8 @@ import type { Metadata } from "next"; 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 { AVISO_DEMO, EMPRESA, SITIO_URL } from "@/data/empresa"; /** @@ -7,18 +10,24 @@ import { AVISO_DEMO, EMPRESA, SITIO_URL } from "@/data/empresa"; * * Reproduce, reordenado para poder leerse, el aviso publicado por FSPM en su * sitio público, más una sección que declara qué recaba realmente el formulario - * de esta demo. Lo que no estaba en la fuente no se agregó, y la versión + * de esta demo —cuatro campos visibles y el contexto de la página— y un resumen + * en lenguaje llano arriba, para que alguien pueda enterarse sin leer nueve + * apartados. Lo que no estaba en la fuente no se agregó, y la versión * definitiva es siempre la que publique la empresa. + * + * Va con `robots: { index: false }` a propósito: es la reproducción de un + * documento legal de un tercero dentro de una demo, y no queremos que compita + * en buscadores con el aviso que la empresa publica en su propio dominio. */ const TITULO = "Aviso de privacidad"; -const DESCRIPCION = `Aviso de privacidad de ${EMPRESA.razonSocial}: responsable, finalidades del tratamiento, transferencias, medidas de seguridad y ejercicio de derechos ARCO.`; +const DESCRIPCION = `Aviso de privacidad de ${EMPRESA.razonSocial}: quién es el responsable, qué datos recaba este sitio, para qué se usan, a quién se transfieren y cómo ejercer tus derechos de acceso, rectificación, cancelación y oposición.`; export const metadata: Metadata = { title: TITULO, description: DESCRIPCION, alternates: { canonical: `${SITIO_URL}/aviso-de-privacidad` }, - robots: { index: true, follow: true }, + robots: { index: false, follow: true }, openGraph: { title: `${TITULO} | ${EMPRESA.nombreCorto}`, description: DESCRIPCION, @@ -28,60 +37,101 @@ export const metadata: Metadata = { const CORREO_ARCO = "asistentedireccion@fspm.com.mx"; -const CAMPOS_FORMULARIO = [ - { campo: "Nombre", uso: "Identificarte y dirigirnos a ti por tu nombre en el seguimiento." }, +const MENSAJE_INICIAL = "Mi duda sobre este sitio es "; + +/** El aviso en cinco frases, para quien no va a leer los nueve apartados. */ +const EN_CORTO = [ + "Tus datos los trata Enforcer Units Fire Service Plus México, S.A. de C.V.", + "Se usan para identificarte, contactarte y darle seguimiento a tu solicitud.", + "El aviso original contempla también envío de invitaciones, noticias y campañas.", + "Contempla transferencias a asociados y a terceros para esas mismas finalidades.", + `Puedes pedir acceso, corrección, cancelación u oposición escribiendo a ${CORREO_ARCO}.`, +]; + +/** + * Qué recaba el formulario de este sitio. Cuatro campos visibles y el contexto + * que se deduce de la página, sin preguntarlo. + */ +const CAMPOS_VISIBLES = [ + { campo: "Nombre completo", uso: "Identificarte y dirigirnos a ti por tu nombre en el seguimiento." }, { campo: "Correo electrónico", uso: "Enviarte la respuesta técnica y la propuesta." }, { campo: "Teléfono", uso: "Contactarte cuando la solicitud requiere una llamada." }, - { campo: "Empresa y puesto", uso: "Entender quién decide y con qué alcance, para no dar una respuesta genérica." }, - { campo: "Ciudad o estado", uso: "Ubicar la instalación y saber qué requisitos locales pueden aplicar." }, - { campo: "Tipo de operación y superficie", uso: "Interpretar el escenario y proponer una configuración." }, - { campo: "Clases de fuego declaradas", uso: "Clasificar el riesgo que tú mismo declaras en el selector." }, - { campo: "Urgencia y mensaje", uso: "Priorizar la atención y conocer el requerimiento en tus palabras." }, { - campo: "Datos de origen de la visita", - uso: "Saber por qué campaña o página llegaste, para medir qué contenido resuelve dudas reales.", + campo: "Mensaje", + uso: "Conocer el requerimiento en tus palabras. Lo que escribas aquí es lo único que decides compartir.", + }, + { + campo: "Aceptación de este aviso", + uso: "Registrar que aceptaste el tratamiento antes de enviar. Sin esa casilla no se envía nada.", + }, +]; + +const CAMPOS_CONTEXTO = [ + { + campo: "Página desde la que escribiste", + uso: "Saber si llegaste desde una ficha de producto, un segmento o la página de cumplimiento, para responder sobre eso.", + }, + { + campo: "Producto, categoría, industria o escenario de esa página", + uso: "Interpretar el escenario sin obligarte a llenar un cuestionario técnico.", + }, + { + campo: "Clases de fuego asociadas a esa página", + uso: "Preparar la selección de equipo antes de la primera llamada.", + }, + { + campo: "Origen de la visita (campaña, buscador o enlace)", + uso: "Medir qué contenido resuelve dudas reales. No identifica a nadie por sí solo.", }, ]; export default function PaginaAviso() { return ( <> - {/* ---------- encabezado ---------- */} -
+ } + eyebrow="Documento legal" + titulo="Aviso de privacidad" + entrada={`Reproducción del aviso publicado por ${EMPRESA.razonSocial}, reordenado para poder leerse. La versión vigente y definitiva es la que la empresa publique en su propio sitio.`} + foto={FOTOS.mapa} + enfoque="45% 45%" + prioridad + > +
+

+ Sobre este sitio +

+

{AVISO_DEMO}

+
+
+ + {/* ---------- el aviso en corto ---------- */} +
- -
-

Documento legal

-

- Aviso de privacidad -

-

- Reproducción del aviso publicado por {EMPRESA.razonSocial}, reordenado para poder - leerse. La versión vigente y definitiva es la que la empresa publique en su propio - sitio. +

En corto

+

+ Lo esencial en cinco frases +

+

+ Este resumen no sustituye al aviso: es una entrada para que puedas leerlo sabiendo qué + buscar. El texto que manda es el de los nueve apartados de abajo.

-
- -
-

- Sobre este sitio -

-

{AVISO_DEMO}

+
    + {EN_CORTO.map((punto) => ( +
  • +
  • + ))} +
@@ -91,7 +141,10 @@ export default function PaginaAviso() {
-

+

1. Responsable del tratamiento

@@ -103,8 +156,8 @@ export default function PaginaAviso() {

{EMPRESA.nombreCorto} será el responsable del tratamiento que, con motivo de la - relación jurídica que haya sido celebrada y para el cumplimiento de la misma, se le - dé a tus datos personales. + relación jurídica que haya sido celebrada y para el cumplimiento de la misma, se + le dé a tus datos personales.

En cumplimiento de la Ley Federal de Protección de Datos Personales en Posesión de @@ -124,38 +177,82 @@ export default function PaginaAviso() { 2. Qué datos recaba este sitio

- Esta sección no viene del aviso original: describe exactamente lo que pide el - formulario de este sitio, para que sepas qué estás enviando antes de enviarlo. + Esta sección no viene del aviso original: describe exactamente lo que recoge el + formulario de este sitio, para que sepas qué estás enviando antes de enviarlo. Son + cuatro campos visibles, más el contexto de la página en la que estabas.

+

+ Lo que escribes tú +

- +
- - + {["Dato", "Para qué se usa"].map((c) => ( + + ))} - {CAMPOS_FORMULARIO.map((fila) => ( + {CAMPOS_VISIBLES.map((fila) => ( + + + + + ))} + +
- Campos del formulario de contacto y para qué se usa cada uno. + Campos visibles del formulario de contacto y para qué se usa cada uno.
- Dato - - Para qué se usa - + {c} +
+ + {fila.campo} + + + {fila.uso} +
+
+ +

+ Lo que se deduce de la página, sin preguntártelo +

+
+ + + + + {["Dato", "Para qué se usa"].map((c) => ( + + ))} + + + + {CAMPOS_CONTEXTO.map((fila) => ( - + {["Documento", "Qué prueba", "Quién lo pide"].map((c) => ( @@ -303,16 +396,16 @@ export default function PaginaCumplimiento() { {EXPEDIENTE.map((fila) => ( - + - - @@ -321,22 +414,85 @@ export default function PaginaCumplimiento() {
+ Contexto que el formulario registra a partir de la página de origen. +
+ {c} +
@@ -181,7 +278,10 @@ export default function PaginaAviso() {
-

+

3. Finalidades del tratamiento

@@ -193,10 +293,10 @@ export default function PaginaAviso() { establecidos.

- Esto incluye enviar, por cualquier medio físico, electrónico o magnético, de manera - enunciativa mas no limitativa: invitación a reuniones de trabajo, minutas, noticias - y anuncios en general relacionados con la institución, campañas publicitarias - afines e invitación a eventos. + Esto incluye enviar, por cualquier medio físico, electrónico o magnético, de + manera enunciativa mas no limitativa: invitación a reuniones de trabajo, minutas, + noticias y anuncios en general relacionados con la institución, campañas + publicitarias afines e invitación a eventos.

@@ -204,7 +304,10 @@ export default function PaginaAviso() {
-

+

4. Transferencias

@@ -217,7 +320,10 @@ export default function PaginaAviso() {


-

+

5. Medidas de seguridad

@@ -230,7 +336,10 @@ export default function PaginaAviso() {


-

+

6. Excepciones al consentimiento

@@ -249,8 +358,8 @@ export default function PaginaAviso() {

El ejercicio de los derechos de Acceso, Rectificación, Cancelación y Oposición - respecto de tus datos personales, así como la revocación del consentimiento para su - tratamiento, podrá realizarse a través de los procedimientos implementados o + respecto de tus datos personales, así como la revocación del consentimiento para + su tratamiento, podrá realizarse a través de los procedimientos implementados o solicitándolo al siguiente correo electrónico:

@@ -268,16 +377,19 @@ export default function PaginaAviso() { 8. Cambios al aviso

- {EMPRESA.nombreCorto} podrá enmendar o modificar el presente aviso de privacidad como - lo estime conveniente, e informará y pondrá a tu disposición el aviso actualizado en - su sitio web. + {EMPRESA.nombreCorto} podrá enmendar o modificar el presente aviso de privacidad + como lo estime conveniente, e informará y pondrá a tu disposición el aviso + actualizado en su sitio web.


-

+

9. Contacto

@@ -311,6 +423,48 @@ export default function PaginaAviso() { + + {/* ---------- salida ---------- */} +
+
+
+
+

Si te quedó una duda

+

+ Pregunta antes de enviar tus datos, no después +

+

+ Para ejercer tus derechos de acceso, rectificación, cancelación u oposición, el canal + es el correo del apartado 7:{" "} + + {CORREO_ARCO} + + . Este formulario es para lo demás: dudas sobre el sitio, sobre qué se registra o + sobre lo que necesitas proteger. +

+

+ Y si lo que buscabas era información técnica,{" "} + + la página de cumplimiento + {" "} + es un mejor punto de partida. +

+
+ + +
+
+
); } diff --git a/src/app/(sitio)/casos/page.tsx b/src/app/(sitio)/casos/page.tsx index c9515c8..38f5ac6 100644 --- a/src/app/(sitio)/casos/page.tsx +++ b/src/app/(sitio)/casos/page.tsx @@ -1,6 +1,9 @@ import type { Metadata } from "next"; import Link from "next/link"; +import { FormularioContacto } from "@/components/formulario/FormularioContacto"; import { RielClases } from "@/components/marca/RielClases"; +import { Migas } from "@/components/site/contenido/Migas"; +import { FOTOS, HeroeLanding } from "@/components/site/HeroeLanding"; import { Revelar } from "@/components/site/Revelar"; import { getCategoria } from "@/data/catalogo"; import type { ClaseFuego } from "@/data/clases-fuego"; @@ -13,12 +16,16 @@ import { getIndustria } from "@/data/industrias"; * * 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. + * cifras como si fueran mediciones. El rótulo va en el héroe y se repite arriba + * del contenido, nunca en una nota al pie. + * + * Cada caso abre con un resumen en una frase, para quien no va a leer los seis + * pasos, y después desarrolla el detalle técnico para quien sí. */ -const TITULO = "Casos de aplicación"; +const TITULO = "Casos de aplicación: cómo se resuelve un escenario real"; 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."; + "Cuatro escenarios ilustrativos de protección contra incendios —planta química, bodega, gasolinera y cocina de hotel— con la estructura problema, riesgo, solución, prueba, resultado y documentación. Construidos con fines de demostración."; export const metadata: Metadata = { title: TITULO, @@ -31,11 +38,15 @@ export const metadata: Metadata = { }, }; +const MENSAJE_INICIAL = "Mi escenario se parece al de "; + type Caso = { id: string; industriaSlug: string; titulo: string; escenario: string; + /** El caso en una frase, para quien no va a leer los seis pasos. */ + resumen: string; clases: ClaseFuego[]; categorias: string[]; problema: string; @@ -51,7 +62,10 @@ 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", + escenario: + "Escenario ilustrativo · Planta química, patio de trasvase con contención secundaria", + resumen: + "El equipo estaba a cien metros de donde puede arder. Se cronometró el recorrido y se movió el equipo, no se compró más.", clases: ["A", "B", "C"], categorias: ["agentes-extinguidores", "productos-moviles"], problema: @@ -83,6 +97,8 @@ const CASOS: Caso[] = [ 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", + resumen: + "Había extintores suficientes en el conteo, pero no los correctos en los pasillos correctos.", clases: ["A", "B", "C"], categorias: ["extintores-portatiles", "rociadores"], problema: @@ -114,6 +130,8 @@ const CASOS: Caso[] = [ 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", + resumen: + "Quien responde primero es el despachador. Se entrenó a los tres turnos y se dejó constancia de quién puede operar el equipo.", clases: ["A", "B", "C"], categorias: ["productos-moviles", "extintores-portatiles"], problema: @@ -145,6 +163,8 @@ const CASOS: Caso[] = [ industriaSlug: "cocinas-hoteles", titulo: "Cocina de hotel con freidoras bajo una sola campana", escenario: "Escenario ilustrativo · Hotel de servicio completo, cocina central y comedor", + resumen: + "Lo que estaba colgado en la pared no sirve para aceite hirviendo. Se cambió por equipo apto para clase K y se entrenó al turno de cierre.", clases: ["A", "B", "K"], categorias: ["rociadores", "extintores-portatiles"], problema: @@ -176,32 +196,38 @@ const CASOS: Caso[] = [ export default function PaginaCasos() { return ( <> - {/* ---------- encabezado con el aviso destacado arriba ---------- */} -
-
- + } + eyebrow="Escenarios ilustrativos" + titulo="Cuatro escenarios y cómo se resuelve cada uno" + entrada="Planta química, bodega, gasolinera y cocina de hotel. Cada caso sigue seis pasos: problema, riesgo, solución, prueba, resultado y documentación." + foto={FOTOS.planta} + enfoque="50% 62%" + altura="alto" + prioridad + > +
+ + Plantear mi escenario + +

+ Construidos para demostración · No corresponden a clientes reales +

+
+
- {/* el aviso va antes del contenido, no al final */} -
+ {/* ---------- el rótulo, arriba del contenido ---------- */} +
+
+
- -
-

Casos de aplicación

-

- Seis pasos que convierten una compra en un expediente -

-

- 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ó. -

-
@@ -267,6 +285,15 @@ export default function PaginaCasos() { {caso.titulo} +

+ {caso.resumen} +

+

+ +

+ + Mi caso se parece a este + +
@@ -358,30 +394,52 @@ export default function PaginaCasos() { ); })} - {/* ---------- cierre ---------- */} -
+ {/* ---------- conversión ---------- */} +
-
- -

Sobre los casos reales

-

+
+
+ +

Siguiente paso

+

+ Cuéntanos tu escenario y lo estructuramos igual +

+

+ Los seis pasos no son un formato de presentación: son el orden en el que conviene + trabajar. Empieza describiendo el lugar y nosotros hacemos las preguntas que + faltan. +

+
+ +
+ +

Por qué todavía no publicamos casos con nombre -

- -

- 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. -

-
- - Plantear tu escenario - - - Ver el centro de evidencia - +

+

+ 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. +

+ +
@@ -390,10 +448,9 @@ export default function PaginaCasos() { } function parrafo(claro: boolean): string { - return [ - "text-sm leading-relaxed sm:text-base", - claro ? "text-deep-muted" : "text-muted", - ].join(" "); + return ["text-sm leading-relaxed sm:text-base", claro ? "text-deep-muted" : "text-muted"].join( + " ", + ); } function Paso({ diff --git a/src/app/(sitio)/catalogo/[categoria]/page.tsx b/src/app/(sitio)/catalogo/[categoria]/page.tsx index c45407b..e99e279 100644 --- a/src/app/(sitio)/catalogo/[categoria]/page.tsx +++ b/src/app/(sitio)/catalogo/[categoria]/page.tsx @@ -3,24 +3,52 @@ import Image from "next/image"; import Link from "next/link"; import { notFound } from "next/navigation"; import { FormularioContacto } from "@/components/formulario/FormularioContacto"; -import { Revelar } from "@/components/site/Revelar"; +import { PastillaEscenario } from "@/components/marca/IconoEscenario"; +import { RielClases } from "@/components/marca/RielClases"; +import { HeroeLanding, FOTOS } from "@/components/site/HeroeLanding"; +import { TablaComparativa } from "@/components/site/catalogo/TablaComparativa"; import { TarjetaProducto } from "@/components/site/catalogo/TarjetaProducto"; import { iconoDeAplicacion } from "@/components/site/catalogo/iconos"; import { CATEGORIAS, getCategoria, productosDeCategoria } from "@/data/catalogo"; -import type { ClaseFuego } from "@/data/clases-fuego"; +import { CLASES, type ClaseFuego } from "@/data/clases-fuego"; import { SITIO_URL } from "@/data/empresa"; -import { INDUSTRIAS } from "@/data/industrias"; +import { escenariosDeCategoria } from "@/data/escenarios"; /** - * /catalogo/[categoria] — las ocho rutas que el sitio original no tenía. + * /catalogo/[categoria] — la estantería de una categoría. * - * Además de los productos, la página cruza con los segmentos prioritarios del - * estudio: si esta categoría aparece en el paquete recomendado de una - * industria, se enlaza. Es lo que convierte un catálogo en una ruta de compra. + * Misma corrección que el catálogo: la página explicaba y ahora muestra. Se + * fueron la rejilla de tarjetas de escenario con su riesgo desarrollado, los + * párrafos que introducían cada sección y el discurso de «elige por tu lugar», + * que ya vive en el inicio, en /soluciones y en /cumplimiento-nom-002. + * + * Lo que se conserva es el dato: la descripción de la fuente, las clases + * declaradas por los equipos de la categoría, las aplicaciones que la propia + * fuente le atribuye y los lugares donde entra —esos, como pastillas que + * enlazan al catálogo ya filtrado, no como tarjetas—. Es la densidad técnica + * que posiciona la página. + * + * Cada categoría estrena fotografía y recorte, para que ocho páginas con la + * misma plantilla no se lean como ocho copias. */ type Props = { params: Promise<{ categoria: string }> }; +type Portada = { foto: string; enfoque: string }; + +const PORTADAS: Record = { + "extintores-portatiles": { foto: FOTOS.practica, enfoque: "center 42%" }, + "extintores-fijos": { foto: FOTOS.planta, enfoque: "center 32%" }, + "productos-moviles": { foto: FOTOS.bombero, enfoque: "center 24%" }, + rociadores: { foto: FOTOS.galeria, enfoque: "center 46%" }, + "agentes-extinguidores": { foto: FOTOS.portadaGaleria, enfoque: "center 52%" }, + firemiks: { foto: FOTOS.planta, enfoque: "right 66%" }, + "proteccion-civil": { foto: FOTOS.proteccionCivil, enfoque: "center 30%" }, + robots: { foto: FOTOS.galeria, enfoque: "left 28%" }, +}; + +const PORTADA_RESPALDO: Portada = { foto: FOTOS.practica, enfoque: "center" }; + export function generateStaticParams(): Array<{ categoria: string }> { return CATEGORIAS.map((c) => ({ categoria: c.slug })); } @@ -34,7 +62,13 @@ export async function generateMetadata({ params }: Props): Promise { } const url = `${SITIO_URL}/catalogo/${categoria.slug}`; - const descripcion = `${categoria.tagline} ${productosDeCategoria(categoria.slug).length} equipos con clases de fuego, capacidad y agente declarados.`; + const cuantos = productosDeCategoria(categoria.slug).length; + const lugares = escenariosDeCategoria(categoria.slug) + .slice(0, 4) + .map((e) => e.nombre.toLowerCase()) + .join(", "); + + const descripcion = `${categoria.tagline} ${cuantos} ${cuantos === 1 ? "equipo" : "equipos"} con clases de fuego, capacidad y agente declarados${lugares ? `, para ${lugares}` : ""}. Cotiza directo.`; return { title: categoria.nombre, @@ -56,21 +90,28 @@ export default async function PaginaCategoria({ params }: Props) { if (!categoria) notFound(); const productos = productosDeCategoria(categoria.slug); - const industrias = INDUSTRIAS.filter((i) => i.categorias.includes(categoria.slug)); - - // clases que realmente cubre algún producto de la categoría - const clases = Array.from( - new Set(productos.flatMap((p) => p.clases)), - ) as ClaseFuego[]; - + const escenarios = escenariosDeCategoria(categoria.slug); const otras = CATEGORIAS.filter((c) => c.slug !== categoria.slug); + // clases que realmente declara algún producto de la categoría + const declaradas = new Set(productos.flatMap((p) => p.clases)); + const clases: ClaseFuego[] = CLASES.filter((c) => declaradas.has(c)); + + const portada = PORTADAS[categoria.slug] ?? PORTADA_RESPALDO; + return ( <> - {/* ---------- encabezado de categoría ---------- */} -
-
- + } + > + {clases.length > 0 ? ( +
+ +
+ ) : null} -
-
-

- Categoría {String(categoria.orden).padStart(2, "0")} de {CATEGORIAS.length} -

-

- {categoria.nombre} -

-

- {categoria.tagline} -

-

- {categoria.descripcion} -

+ + ← Volver al catálogo completo + + -
+ {/* ================= LOS EQUIPOS ================= */} +
+
+
+

+ {productos.length} {productos.length === 1 ? "equipo" : "equipos"} + en {categoria.nombre.toLowerCase()} +

+ + + Filtrar en el catálogo → + +
+ +
+ {productos.map((producto) => ( + + ))} +
+ + {/* Comparación cerrada por omisión: sirve al comprador técnico, pero + no es lo primero que hay que ver. */} + {productos.length > 1 ? ( +
+ + Comparar especificaciones ({productos.length}) + + +
+ +

+ Donde la fuente no declara un dato, la celda lo dice: no lo rellenamos con una + estimación. La ficha técnica y el certificado por número de modelo se entregan + antes de cotizar. +

+
+
+ ) : null} +
+
+ + {/* ================= EL DATO DE LA CATEGORÍA ================= */} +
+
+
+
+

+ Qué resuelve esta categoría +

+

{categoria.descripcion}

+ +
Equipos en la categoría
{productos.length}
Clases declaradas
-
{clases.length > 0 ? clases.join(" · ") : "No aplica"}
+
+ {clases.length > 0 ? clases.join(" · ") : "No aplica"} +
-
- {categoria.nombre} +
+

+ Dónde la ubica la fuente +

+
    + {categoria.aplicaciones.map((aplicacion) => { + const icono = iconoDeAplicacion(aplicacion); + return ( +
  • + {icono ? ( + + ) : null} + + {aplicacion} + +
  • + ); + })} +
+ + {escenarios.length > 0 ? ( + <> +

+ Lugares donde entra en la configuración +

+
    + {escenarios.map((escenario) => ( +
  • + + + +
  • + ))} +
+ + ) : null}
- {/* ---------- aplicaciones ---------- */} -
-
-

- Dónde la ubica la fuente -

-
    - {categoria.aplicaciones.map((aplicacion) => { - const icono = iconoDeAplicacion(aplicacion); - return ( -
  • - {icono ? ( - - ) : null} - - {aplicacion} - -
  • - ); - })} -
-

- Las aplicaciones provienen de la información pública de FSPM. La selección final depende - del escenario real: material, superficie, personal y nivel de riesgo del centro de - trabajo. -

-
-
- - {/* ---------- productos de la categoría ---------- */} -
-
- -

Equipos

-

- {productos.length === 1 - ? "El equipo de esta categoría" - : `Los ${productos.length} equipos de esta categoría`} -

-
- -
- {productos.map((producto) => ( - - ))} -
-
-
- - {/* ---------- cruce con industrias ---------- */} - {industrias.length > 0 ? ( -
-
- -

Dónde aplica

-

- Segmentos donde esta categoría forma parte del paquete -

-

- Un equipo aislado rara vez resuelve una instalación. Estas son las operaciones donde - {" "} - {categoria.nombre.toLowerCase()} entra dentro de una configuración más amplia. -

-
- -
    - {industrias.map((industria) => ( -
  • - -

    - {industria.nombre} -

    -

    {industria.dolor}

    - - Ver la solución → - - -
  • - ))} -
-
-
- ) : null} - - {/* ---------- formulario ---------- */} -
+ {/* ================= FORMULARIO Y OTRAS CATEGORÍAS ================= */} +
- -

Cotizar esta categoría

-

- Cuéntanos la instalación y te decimos cuál de estos equipos aplica -

-

- Y también cuál no. Descartar un equipo que no corresponde ahorra más dinero que un - descuento. -

-
+

Cotizar esta categoría

+

+ Cuéntanos la instalación y te decimos cuál de estos equipos aplica +

+

+ Y también cuál no. Descartar un equipo que no corresponde ahorra más que un + descuento. Cuatro campos, sin costo y sin compromiso de compra. +

-
+
-

- Otras categorías -

-
    - {otras.map((c) => ( -
  • - - {c.nombre} - -
  • - ))} -
+
diff --git a/src/app/(sitio)/catalogo/page.tsx b/src/app/(sitio)/catalogo/page.tsx index 5ef32c5..2687705 100644 --- a/src/app/(sitio)/catalogo/page.tsx +++ b/src/app/(sitio)/catalogo/page.tsx @@ -1,24 +1,33 @@ import type { Metadata } from "next"; -import Image from "next/image"; import Link from "next/link"; -import { FormularioContacto } from "@/components/formulario/FormularioContacto"; -import { Revelar } from "@/components/site/Revelar"; +import { HeroeLanding, FOTOS } from "@/components/site/HeroeLanding"; import { ExploradorCatalogo } from "@/components/site/catalogo/ExploradorCatalogo"; -import { CATEGORIAS, PRODUCTOS, productosDeCategoria } from "@/data/catalogo"; -import { CLASES_ORDENADAS } from "@/data/clases-fuego"; +import { construirConsulta, leerEstado } from "@/components/site/catalogo/estado"; +import { CATEGORIAS, PRODUCTOS } from "@/data/catalogo"; import { SITIO_URL } from "@/data/empresa"; /** - * /catalogo — la corrección directa al hallazgo 5.1 del estudio: ocho - * categorías sin rutas por riesgo ni aplicación. + * /catalogo — la tienda. * - * No es una galería de producto. Es un comparador: se entra por clase de fuego - * y por aplicación, y se sale con dos o tres equipos que descartar o cotizar. + * Corrección de rumbo pedida por el cliente: la versión anterior explicaba + * cómo elegir antes de mostrar un solo equipo —tres preguntas, dos «puertas», + * trece tarjetas de escenario con su riesgo, párrafos de contexto entre + * secciones—. Todo eso ya vive en el inicio, en /soluciones y en + * /cumplimiento-nom-002, y aquí estorbaba. + * + * Esta página quedó en tres piezas: héroe corto, barra lateral con filtros y + * rejilla de producto. Nada más. La mecánica de selección —clase de fuego, + * escenario, categoría, agente, búsqueda con expresiones regulares— se conservó + * completa, porque es lo que el cliente aprobó; solo dejó de explicarse. + * + * El estado sigue viviendo en la cadena de consulta, así que + * `/catalogo?escenario=gasolineras` —el enlace que ya usan otras páginas y que + * el vendedor manda por WhatsApp— abre el catálogo filtrado. */ const TITULO = "Catálogo de equipos y agentes contra incendio"; const DESCRIPCION = - "Compara los 22 equipos y agentes de FSPM por clase de fuego, capacidad, agente y alcance declarado. Filtra por el riesgo que tienes, no por el nombre del producto."; + "22 equipos filtrables por clase de fuego A B C D K, categoría, agente y lugar de instalación: extintores portátiles y fijos, equipos móviles, rociadores automáticos, agentes FireAde y dosificadores FireMiks. Especificación declarada y cotización directa."; export const metadata: Metadata = { title: TITULO, @@ -28,22 +37,29 @@ export const metadata: Metadata = { title: TITULO, description: DESCRIPCION, url: `${SITIO_URL}/catalogo`, + images: [{ url: FOTOS.practica }], }, }; -export default function PaginaCatalogo() { - const resumen = [ - { clave: "Categorías", valor: String(CATEGORIAS.length) }, - { clave: "Equipos y agentes", valor: String(PRODUCTOS.length) }, - { clave: "Clases de fuego", valor: `${CLASES_ORDENADAS.length} · A B C D K` }, - ]; +type Props = { + searchParams: Promise>; +}; + +export default async function PaginaCatalogo({ searchParams }: Props) { + const estado = leerEstado(await searchParams); return ( <> - {/* ---------- encabezado: la lógica de lectura del catálogo ---------- */} -
-
- + } + /> -
-
-

Catálogo técnico

-

- Un catálogo se lee por clase de fuego, no por nombre de producto -

-

- Nadie compra «un extintor». Se compra la respuesta a un escenario: qué - se está quemando, dónde, quién va a operar el equipo y qué documentación tiene que - quedar en el expediente. Este catálogo está ordenado en ese sentido. -

- -
- {resumen.map((r) => ( -
-
{r.clave}
-
{r.valor}
-
- ))} -
- -
- - Solicitar diagnóstico - - - Ver qué documentación aplica - -
-
- - {/* cómo elegir: el criterio, no el producto */} - -
-
-
- - {/* ---------- filtros + tabla comparativa ---------- */} - - - {/* ---------- las 8 categorías con su tagline ---------- */} -
-
- -

Ocho categorías

-

- Cada categoría responde a una forma distinta de llegar al fuego -

-

- Portátil, móvil, fijo, automático o el agente por separado. La diferencia no es de - gama: es de quién opera, en cuánto tiempo y con qué instalación. -

-
- -
    - {CATEGORIAS.map((categoria) => { - const cuantos = productosDeCategoria(categoria.slug).length; - return ( -
  • - -
    - {categoria.nombre} -
    -
    -
    -

    - {categoria.nombre} -

    - - {cuantos} {cuantos === 1 ? "equipo" : "equipos"} - -
    -

    {categoria.tagline}

    - - Ver la categoría → - -
    - -
  • - ); - })} -
-
-
- - {/* ---------- formulario embebido: requisito del cliente ---------- */} -
-
-
-
- -

Siguiente paso

-

- Dinos qué hay que proteger y te devolvemos la selección, no un folleto -

-

- Con la clase de fuego, la superficie y quién opera el equipo alcanza para proponer - una configuración y decirte qué documentación te corresponde exigir. Sin costo y sin - compromiso de compra. -

-
- -
    - {[ - "Selección de equipo y agente por escenario, no por catálogo completo.", - "Índice de la documentación que debe acompañar la compra.", - "Referencia de lo que la NOM-002 exige según tu nivel de riesgo.", - ].map((punto) => ( -
  • -
  • - ))} -
-
- - -
-
-
+ {/* El explorador guarda su propio estado y reescribe la URL sin navegar. + Si alguien llega desde otra página a `/catalogo?escenario=…` estando + ya en `/catalogo`, la navegación no lo remontaría por sí sola: esta + llave lo reinicia con el estado que trae la URL. */} + ); } diff --git a/src/app/(sitio)/contacto/page.tsx b/src/app/(sitio)/contacto/page.tsx index f08e0b2..597d2db 100644 --- a/src/app/(sitio)/contacto/page.tsx +++ b/src/app/(sitio)/contacto/page.tsx @@ -1,6 +1,8 @@ import type { Metadata } from "next"; 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 { EMPRESA, SITIO_URL, WHATSAPP } from "@/data/empresa"; @@ -11,10 +13,14 @@ import { EMPRESA, SITIO_URL, WHATSAPP } from "@/data/empresa"; * atención, ni domicilio con número, ni mapa: nada de eso está confirmado, y * una dirección equivocada en un sitio de protección contra incendios es un * problema real, no un detalle. + * + * El formulario pide cuatro campos, así que la página no puede prometer que la + * respuesta llega con la clase de fuego y la superficie ya declaradas: lo que + * promete es que preguntamos nosotros lo que falte. */ -const TITULO = "Contacto"; -const DESCRIPCION = `Habla con ${EMPRESA.nombre}. Teléfono, correo y solicitud de diagnóstico para operaciones que necesitan protección contra incendios documentada.`; +const TITULO = "Contacto: habla con un especialista de FSPM"; +const DESCRIPCION = `Teléfono, correo y WhatsApp de ${EMPRESA.nombre}, en ${EMPRESA.ciudad}. Solicita un diagnóstico de protección contra incendios con cuatro campos y te contesta una persona con nombre, no un buzón compartido.`; export const metadata: Metadata = { title: TITULO, @@ -31,37 +37,33 @@ const MENSAJE_WHATSAPP = encodeURIComponent( "Hola, me gustaría información sobre protección contra incendios.", ); +const MENSAJE_INICIAL = "Necesito proteger "; + export default function PaginaContacto() { return ( <> - {/* ---------- encabezado ---------- */} -
-
- - -
-

Contacto

-

- Si tu situación no puede esperar, marca -

-

- Para todo lo demás, el formulario sirve mejor: llega con la clase de fuego, la - superficie y el tipo de operación, así que la primera respuesta puede ser técnica en - lugar de una pregunta de vuelta. -

-
+ } + eyebrow="Contacto" + titulo="Si tu situación no puede esperar, marca" + entrada="Para todo lo demás, el formulario funciona mejor: son cuatro campos, queda con folio y lo recibe una persona con nombre. Lo que falte lo preguntamos nosotros." + foto={FOTOS.contacto} + enfoque="50% 38%" + altura="alto" + prioridad + > +
+ + Marcar {EMPRESA.telefono} + + + O envía el formulario +
-
+ {/* ---------- datos verificados ---------- */}
@@ -73,16 +75,16 @@ export default function PaginaContacto() {
{/* ---------- formulario completo ---------- */} -
+

Solicitud

-

+

Qué pasa cuando envías el formulario

+

+ Nombre, correo, teléfono y una línea de contexto. Nada más: los cuestionarios + técnicos de catorce campos son la razón por la que la gente cierra la pestaña. +

    @@ -164,8 +177,8 @@ export default function PaginaContacto() { }, { n: "02", - t: "Se interpreta el escenario", - d: "Con la clase de fuego, la superficie y el tipo de operación que declaraste, alguien revisa qué configuración aplica y qué habría que descartar.", + t: "Preguntamos lo que falta", + d: "Con la página desde la que escribiste y lo que dejaste en el mensaje ya se intuye el escenario. Lo que no se intuye —qué se puede quemar, quién opera, qué superficie— lo preguntamos nosotros en la primera respuesta.", }, { n: "03", @@ -196,7 +209,7 @@ export default function PaginaContacto() { {[ { href: "/preguntas-frecuentes", label: "Preguntas frecuentes" }, { href: "/cumplimiento-nom-002", label: "Cumplimiento NOM-002" }, - { href: "/evidencia", label: "Centro de evidencia" }, + { href: "/evidencia", label: "Documentación técnica" }, { href: "/catalogo", label: "Catálogo" }, ].map((enlace) => (
  1. @@ -234,11 +247,13 @@ export default function PaginaContacto() {
diff --git a/src/app/(sitio)/cumplimiento-nom-002/page.tsx b/src/app/(sitio)/cumplimiento-nom-002/page.tsx index 5fd636b..aa66755 100644 --- a/src/app/(sitio)/cumplimiento-nom-002/page.tsx +++ b/src/app/(sitio)/cumplimiento-nom-002/page.tsx @@ -1,21 +1,30 @@ import type { Metadata } from "next"; import Link from "next/link"; import { FormularioContacto } from "@/components/formulario/FormularioContacto"; +import { TarjetaEscenario } from "@/components/marca/IconoEscenario"; +import { Migas } from "@/components/site/contenido/Migas"; +import { FOTOS, HeroeLanding } from "@/components/site/HeroeLanding"; import { Revelar } from "@/components/site/Revelar"; import { EMPRESA, NORMAS, SITIO_URL } from "@/data/empresa"; +import { ESCENARIOS, type Escenario } from "@/data/escenarios"; import { INDUSTRIAS } from "@/data/industrias"; /** - * /cumplimiento-nom-002 — página de intención de búsqueda alta. + * /cumplimiento-nom-002 — la página de mayor intención de compra del sitio. * - * Regla del contenido: no se citan artículos, umbrales ni plazos que no estén - * en src/data/empresa.ts o en la fuente pública. Lo que no está verificado se - * declara como no reproducido, no se estima. + * Orden deliberado: primero las tres preguntas que hace el dueño de un negocio + * —me aplica, qué me pueden multar, qué tengo que tener— y solo después la + * precisión normativa. Cada bloque está escrito en dos niveles: la primera + * frase la entiende cualquiera, la segunda le da al experto el dato. + * + * Regla del contenido: no se citan artículos, umbrales, plazos ni montos de + * sanción que no estén en src/data/empresa.ts o en la fuente pública. Lo que no + * está verificado se declara como no reproducido; no se estima. */ -const TITULO = "Cumplimiento NOM-002-STPS-2010"; +const TITULO = "Cumplimiento NOM-002-STPS-2010: qué te van a pedir"; const DESCRIPCION = - "Qué exige la NOM-002-STPS-2010 a un centro de trabajo, qué documentación se acumula y cómo se ve un expediente completo. Referencia orientativa, no dictamen técnico ni jurídico."; + "¿La NOM-002-STPS-2010 te aplica y qué te van a pedir en una verificación? Explicado sin tecnicismos y con la precisión normativa después: clasificación de riesgo, equipo, plan de emergencia, brigadas, simulacros y los ocho documentos del expediente."; export const metadata: Metadata = { title: TITULO, @@ -31,43 +40,83 @@ export const metadata: Metadata = { const AVISO_ALCANCE = "Esta página es una referencia orientativa construida con información pública. La normativa puede actualizarse y complementarse por estado, municipio, tipo de instalación y autoridad competente. No sustituye un dictamen técnico ni una opinión jurídica."; +const MENSAJE_INICIAL = "Necesito saber si cumplo con la NOM-002. Mi instalación es un "; + +/** + * Las tres preguntas reales, en el orden en el que llegan. Cada respuesta abre + * en lenguaje llano y cierra con el dato normativo. + */ +const PREGUNTAS_REALES = [ + { + id: "me-aplica", + pregunta: "¿Esto me aplica a mí?", + llano: + "Si tienes gente trabajando, sí. No hay tamaño mínimo: aplica igual a una oficina de quince personas que a una planta de trescientas.", + tecnico: + "La NOM-002-STPS-2010 rige para todo centro de trabajo en México. Lo que cambia entre una oficina y una planta no es la norma: es el nivel de riesgo de incendio con el que se clasifica el centro, y de ese nivel dependen el equipo, los sistemas y el plan que te corresponden.", + }, + { + id: "que-me-multan", + pregunta: "¿Qué me pueden multar?", + llano: + "No te vamos a dar una cifra. Quien te promete un monto exacto en una página web se lo está inventando, y con eso no se toma una decisión.", + tecnico: + "El monto de una sanción lo determina la autoridad competente según lo que encuentre en la verificación, y no lo publicamos porque no podemos sostenerlo con una fuente verificable. Lo que sí es concreto —y es lo que conviene preparar— son los ocho documentos del expediente que aparece más abajo.", + }, + { + id: "que-necesito", + pregunta: "¿Qué tengo que tener?", + llano: + "Equipo que sirva para lo que se puede quemar en cada área, un plan escrito, personal que sepa qué hacer y registro de todo lo anterior. No se cumple solo comprando extintores.", + tecnico: + "En el orden que sigue la norma: clasificación del riesgo de incendio, equipo contra incendio, sistemas fijos y medios de detección y alarma cuando el nivel lo exige, plan de atención a emergencias por escrito, brigadas con capacitación acreditada y simulacros ejecutados y documentados.", + }, +]; + /** Elementos que la norma contempla, según la referencia de NORMAS. */ const ELEMENTOS = [ { n: "01", titulo: "Clasificación del riesgo de incendio", + llano: + "Es el primer paso y define todo lo demás. Si se hace mal, todo lo que se compre encima queda mal.", detalle: - "Es el primer paso y condiciona todo lo demás. De la clasificación depende qué equipo, qué sistemas y qué plan te corresponden. Se determina con el procedimiento de la propia norma; los umbrales no se reproducen aquí porque dependen del material y de la superficie de cada centro de trabajo.", + "De la clasificación depende qué equipo, qué sistemas y qué plan te corresponden. Se determina con el procedimiento de la propia norma; los umbrales no se reproducen aquí porque dependen del material y de la superficie de cada centro de trabajo.", }, { n: "02", titulo: "Equipo contra incendio", + llano: "Extintores que se vean, se alcancen y sirvan para lo que hay en esa área concreta.", detalle: - "Extintores disponibles, accesibles y señalizados, seleccionados según la clase de fuego presente en cada área. El equipo correcto en el lugar equivocado no cumple.", + "Disponibles, accesibles y señalizados, seleccionados según la clase de fuego presente en cada zona. El equipo correcto en el lugar equivocado no cumple, y el conteo total no sustituye a la distancia de recorrido.", }, { n: "03", titulo: "Sistemas fijos y de alarma", + llano: "Si tu riesgo es alto, con equipo portátil no alcanza.", detalle: - "Según el nivel de riesgo, el centro de trabajo puede requerir sistemas fijos de supresión y medios de detección y alarma, no solo equipo portátil.", + "Según el nivel de riesgo, el centro de trabajo puede requerir sistemas fijos de supresión y medios de detección y alarma, además del equipo portátil. Es el punto donde la conversación deja de ser una compra y pasa a ser un proyecto.", }, { n: "04", titulo: "Plan de atención a emergencias", + llano: "Un documento que diga quién hace qué el día del incidente. Por escrito, no de palabra.", detalle: - "Documento por escrito. Es lo primero que se pide en una verificación y lo que más frecuentemente no existe o está desactualizado.", + "Es lo primero que se pide en una verificación y lo que con más frecuencia no existe o quedó desactualizado tras un cambio de instalación, de proceso o de personal.", }, { n: "05", titulo: "Brigadas y capacitación", + llano: "Personas con nombre y apellido que sepan operar el equipo, y un papel que lo compruebe.", detalle: - "Personal designado, entrenado y con constancia. La capacitación sin registro documental no es demostrable ante una autoridad.", + "Personal designado, entrenado y con constancia por persona y fecha. La capacitación sin registro documental no es demostrable ante una autoridad, aunque haya ocurrido.", }, { n: "06", titulo: "Simulacros", + llano: "Practicarlo antes, y dejar constancia de que se practicó.", detalle: - "Ejecutados y documentados. El registro del simulacro es evidencia tanto del plan como de la capacitación.", + "Ejecutados y documentados. El registro del simulacro es evidencia doble, del plan y de la capacitación, y es donde se detectan los huecos que un plano no muestra.", }, ]; @@ -114,171 +163,215 @@ const EXPEDIENTE = [ }, ]; +/** Los ocho escenarios más frecuentes en una conversación de cumplimiento. */ +const SLUGS_CUMPLIMIENTO = [ + "industria", + "gasolineras", + "bodegas", + "cocinas-hoteles", + "oficinas", + "auditorios-afluencia", + "flotas-maquinaria", + "minas", +]; + +const ESCENARIOS_CUMPLIMIENTO = ESCENARIOS.filter((e) => SLUGS_CUMPLIMIENTO.includes(e.slug)); + +function hrefEscenario(escenario: Escenario): string { + if (escenario.industria) return `/soluciones/${escenario.industria}`; + const categoria = escenario.categorias[0]; + return categoria ? `/catalogo/${categoria}` : "/catalogo"; +} + export default function PaginaCumplimiento() { return ( <> - {/* ---------- encabezado con la advertencia de alcance ---------- */} -
-
- + } + eyebrow="Cumplimiento NOM-002-STPS-2010" + titulo="¿La NOM-002 te aplica? Sí. Esto es lo que te van a pedir" + entrada="Primero en español llano y después con la precisión normativa. Si solo quieres saber por dónde empezar, empieza por la revisión: te decimos qué te falta antes de venderte nada." + foto={FOTOS.normas} + enfoque="62% 42%" + altura="alto" + prioridad + > +
+ + Revisar mi cumplimiento + +

+ Cuatro campos · Sin costo · Sin compromiso de compra +

+
+
-
-

Referencia normativa

-

- Cumplimiento NOM-002: qué te van a pedir y cómo se ve un expediente completo -

-

- La norma no se cumple comprando extintores. Se cumple demostrando, por escrito, que el - riesgo está clasificado, el equipo corresponde a ese riesgo y el personal sabe qué - hacer. Esta página explica esa lógica y qué documentos la sostienen. + {/* ---------- las tres preguntas reales ---------- */} +

+
+ +

Empecemos por lo básico

+

+ Las tres preguntas que hace todo el mundo, respondidas sin rodeos +

+

+ En cada respuesta, lo primero es lo que necesitas para decidir. Lo segundo es el + detalle normativo, para quien tiene que sostenerlo ante una auditoría.

+
+ +
+ {PREGUNTAS_REALES.map((item) => ( +
+

+ {item.pregunta} +

+

{item.llano}

+
+

+ El dato normativo +

+

{item.tecnico}

+
+ ))}
-

+

Alcance de esta página

-

{AVISO_ALCANCE}

+

{AVISO_ALCANCE}

- {/* ---------- a quién aplica ---------- */} -
+ {/* ---------- identifica tu caso con la iconografía de escenarios ---------- */} +
-
-
- -

A quién aplica

-

- A todo centro de trabajo, sin mínimo de tamaño -

-

- {NORMAS[0].aplica} -

-

- Es la razón por la que una oficina de veinte personas y una planta petroquímica - están bajo la misma norma con obligaciones muy distintas: lo que cambia no es la - norma, es la clasificación del riesgo. -

-
-
+ +

Encuentra tu caso

+

+ Busca el lugar que se parece al tuyo +

+

+ No hace falta que sepas qué clase de fuego tienes. Si administras una gasolinera, un + hotel o una bodega, eso ya define el perfil de riesgo y, con él, las obligaciones que + se te aplican con más peso. +

+
-
-

- Dos niveles, dos conversaciones distintas -

+
    + {ESCENARIOS_CUMPLIMIENTO.map((escenario) => ( +
  • + +
  • + ))} +
-
-
-

- Riesgo ordinario -

-

- El foco está en tener el equipo correcto, señalizado y revisado, con un plan por - escrito y personal capacitado. La mayoría de las PyMEs y oficinas queda aquí y - el error caro es comprar de más por miedo. -

-
- -
-

- Riesgo alto -

-

- La norma establece requerimientos mayores para los centros clasificados como de - riesgo alto de incendio: la conversación pasa de equipo portátil a sistemas - fijos, detección, brigadas formales y evidencia sostenida en el tiempo. -

-
-
- -
- -

- No reproducimos aquí los umbrales de clasificación: dependen del material y de la - superficie de cada centro de trabajo, y determinarlos mal invalida todo lo que se - construya encima. Ese es el primer punto de un diagnóstico. -

-
-
+

+ Si el tuyo no está en la lista, escríbelo tal cual en la revisión: lo clasificamos + nosotros.{" "} + + Ir a la revisión + + . +

{/* ---------- qué contempla la norma ---------- */} -
+
-

Qué contempla

-

+

Qué contempla la norma

+

Seis frentes, y ninguno se cierra con una compra

-

- Cada uno genera un documento distinto. Un expediente incompleto suele fallar en los - frentes que no se compran: el plan, la capacitación y el registro. +

+ Cada frente genera un documento distinto. Los expedientes incompletos casi siempre + fallan en los tres que no se compran: el plan, la capacitación y el registro.

-
    +
      {ELEMENTOS.map((elemento) => ( -
    1. +
    2. {elemento.n}

      -

      +

      {elemento.titulo}

      -

      {elemento.detalle}

      +

      {elemento.llano}

      +

      {elemento.detalle}

    3. ))}
    -
    -

    - Mantenimiento de extintores +

    +

    + La obligación que más se pierde de vista

    -

    - {NORMAS[1].aplica} Es la obligación recurrente que más seguido se pierde: no por - desconocerla, sino porque nadie quedó a cargo de la bitácora. +

    + Los extintores se revisan cada mes +

    +

    + {NORMAS[1].aplica} Casi nunca se pierde por desconocerla: se pierde porque nadie + quedó a cargo de la bitácora.

{/* ---------- el expediente ---------- */} -
+

Expediente

-

- Cómo se ve un expediente que aguanta una verificación +

+ Los ocho documentos que piden en una verificación

-

- Ocho documentos. Ninguno es opcional cuando alguien pregunta, y cada uno prueba algo - distinto. +

+ Esta es la lista con la que conviene compararse antes de que alguien llegue a + pedirla. Ninguno es opcional cuando se pregunta, y cada uno prueba algo distinto.

-

+

Desliza la tabla para ver todas las columnas

{c}
- + {fila.documento} + {fila.quePrueba} + {fila.quienLoPide}
-

- En una compra con nosotros, los documentos que dependen del equipo se entregan por - número de modelo.{" "} - - Ver el índice de documentación por categoría +

+ + Ver qué me falta del expediente - . -

+

+ En una compra con nosotros, los documentos que dependen del equipo se entregan por + número de modelo.{" "} + + Ver el índice de documentación por categoría + + . +

+
- {/* ---------- referencias ---------- */} -
+ {/* ---------- lo que cambia por operación ---------- */} +
-

Referencias

+

Por operación

+

+ Lo que además se te pide según dónde trabajas +

+

+ La NOM-002 es el piso común. Encima de ella, cada sector acumula requisitos propios + de su autoridad, de su aseguradora o de su cliente corporativo. +

+
+ +
    + {INDUSTRIAS.map((industria) => ( +
  • + +

    + {industria.nombre} +

    +

    {industria.dolor}

    +
      + {industria.cumplimiento.map((obligacion) => ( +
    • +
    • + ))} +
    + + Ver el segmento → + + +
  • + ))} +
+
+
+ + {/* ---------- referencias normativas ---------- */} +
+
+ +

Referencias

Normas citadas en esta página

@@ -371,65 +527,48 @@ export default function PaginaCumplimiento() {
- {/* ---------- por industria ---------- */} -
-
- -

Por operación

-

- Lo que cambia según dónde trabajas -

-
-
    - {INDUSTRIAS.map((industria) => ( -
  • - -

    - {industria.nombre} -

    -
      - {industria.cumplimiento.map((obligacion) => ( -
    • -
    • - ))} -
    - - Ver el segmento → - - -
  • - ))} -
-
-
- - {/* ---------- formulario y cierre con la advertencia ---------- */} -
+ {/* ---------- conversión ---------- */} +

Siguiente paso

-

- Validar cumplimiento NOM-002 +

+ Te decimos qué te falta antes de venderte nada

- Revisamos qué tienes, qué te falta y en qué orden conviene resolverlo. Si algo de tu + Revisamos qué tienes, qué te falta y en qué orden conviene resolverlo. Si tu situación requiere un dictamen formal, te lo decimos en lugar de venderte equipo.

+
    + {[ + "No necesitas saber tu nivel de riesgo para escribirnos: eso es justo lo que revisamos.", + "Si ya te levantaron una observación, mándala tal cual y respondemos sobre ella.", + "Si lo que te falta no es equipo sino documentación, lo decimos así.", + ].map((punto) => ( +
  • +
  • + ))} +
+

@@ -440,10 +579,13 @@ export default function PaginaCumplimiento() {

diff --git a/src/app/(sitio)/evidencia/page.tsx b/src/app/(sitio)/evidencia/page.tsx index 467f64d..ac56338 100644 --- a/src/app/(sitio)/evidencia/page.tsx +++ b/src/app/(sitio)/evidencia/page.tsx @@ -1,6 +1,8 @@ import type { Metadata } from "next"; 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, productosDeCategoria } from "@/data/catalogo"; import { EMPRESA, NORMAS, SITIO_URL } from "@/data/empresa"; @@ -9,15 +11,18 @@ import { EMPRESA, NORMAS, SITIO_URL } from "@/data/empresa"; * /evidencia — el centro de evidencia de la recomendación 10.3 del estudio. * * Regla que gobierna esta página: en la extracción pública del sitio de FSPM no - * apareció un índice verificable de documentos por modelo. Así que aquí no hay - * un solo botón de descarga. Un botón falso en una demo destruye la + * apareció un índice verificable de documentos por número de modelo. Así que + * aquí no hay un solo botón de descarga. Un botón falso en una demo destruye la * credibilidad de todo lo demás; decir la verdad sobre el estado del acervo es * precisamente el argumento comercial. + * + * Cada bloque abre en lenguaje llano —qué papel es y para qué te sirve— y + * cierra con la precisión que necesita quien va a defender el expediente. */ -const TITULO = "Centro de evidencia técnica"; +const TITULO = "Documentación técnica: qué papeles debe traer tu equipo"; const DESCRIPCION = - "Qué documentación corresponde a cada compra de equipo contra incendio, qué aplica por categoría de producto y qué afirmaciones documentamos por número de modelo antes de cotizar."; + "Qué documentación corresponde a cada compra de equipo contra incendio: ficha técnica, hoja de seguridad, certificado, garantía, manual, bitácora y constancias. Qué aplica por categoría y qué documentamos por número de modelo antes de cotizar."; export const metadata: Metadata = { title: TITULO, @@ -30,6 +35,9 @@ export const metadata: Metadata = { }, }; +const MENSAJE_INICIAL = + "Necesito la documentación técnica de un equipo. Lo que me están pidiendo es "; + type DocumentoId = | "ficha" | "sds" @@ -40,18 +48,26 @@ type DocumentoId = | "instalacion" | "capacitacion"; -const DOCUMENTOS: Array<{ id: DocumentoId; nombre: string; corto: string; queEs: string }> = [ +const DOCUMENTOS: Array<{ + id: DocumentoId; + nombre: string; + corto: string; + llano: string; + queEs: string; +}> = [ { id: "ficha", nombre: "Ficha técnica del modelo", corto: "Ficha técnica", + llano: "El papel que dice qué es y qué hace el equipo que compraste.", queEs: - "El documento que sostiene cada número que aparece en una página de producto: capacidad, agente, clases de fuego con sus condiciones y alcance con las condiciones de la prueba. Si un dato no está en la ficha, no debería estar en la propuesta.", + "Sostiene cada número que aparece en una página de producto: capacidad, agente, clases de fuego con sus condiciones y alcance con las condiciones de la prueba. Si un dato no está en la ficha, no debería estar en la propuesta.", }, { id: "sds", nombre: "Hoja de datos de seguridad del agente", corto: "Hoja de seguridad", + llano: "Qué pasa si alguien lo respira, lo toca o lo derrama.", queEs: "Manejo, almacenamiento, incompatibilidades y respuesta ante exposición. Es el documento que pide seguridad industrial y el área ambiental, y el que permite responder si el agente puede estar donde va a estar.", }, @@ -59,6 +75,7 @@ const DOCUMENTOS: Array<{ id: DocumentoId; nombre: string; corto: string; queEs: id: "certificado", nombre: "Certificado o listado por modelo", corto: "Certificado", + llano: "La prueba de que un laboratorio externo lo revisó, no solo el fabricante.", queEs: "El certificado del laboratorio u organismo, con alcance, número de expediente y fecha. Una marca de certificación sin documento asociado a un modelo no es evidencia: es un logotipo.", }, @@ -66,6 +83,7 @@ const DOCUMENTOS: Array<{ id: DocumentoId; nombre: string; corto: string; queEs: id: "garantia", nombre: "Garantía con condiciones por escrito", corto: "Garantía", + llano: "Cuánto dura, qué cubre y qué la cancela.", queEs: "Plazo, cobertura, exclusiones y qué obligaciones de mantenimiento la conservan vigente. Un plazo sin condiciones es una cifra publicitaria.", }, @@ -73,29 +91,33 @@ const DOCUMENTOS: Array<{ id: DocumentoId; nombre: string; corto: string; queEs: id: "manual", nombre: "Manual de operación y mantenimiento", corto: "Manual", + llano: "Cómo se usa y cada cuánto hay que revisarlo.", queEs: - "Cómo se opera, cada cuánto se revisa y qué se considera una falla. Sin manual, la capacitación se vuelve tradición oral y la garantía queda expuesta.", + "Operación, periodicidad de revisión y qué se considera una falla. Sin manual, la capacitación se vuelve tradición oral y la garantía queda expuesta.", }, { id: "bitacora", nombre: "Bitácora de revisión", corto: "Bitácora", + llano: "La hoja donde se firma cada revisión. Es la que piden primero.", queEs: - "El formato donde queda constancia de cada revisión, con fecha y responsable. Es la evidencia recurrente que una verificación pide primero.", + "Constancia de cada revisión con fecha y responsable. Es la evidencia recurrente que una verificación solicita antes que cualquier otra, porque delata si el programa existe o solo se describió.", }, { id: "instalacion", nombre: "Constancia de instalación y puesta en marcha", corto: "Instalación", + llano: "Para lo que se instala: quién lo instaló y que se probó funcionando.", queEs: - "Para sistemas que se instalan: ubicación, configuración instalada, pruebas de arranque y quién las ejecutó. Es lo que permite reclamar si el sistema no actúa.", + "Ubicación, configuración instalada, pruebas de arranque y quién las ejecutó. Es lo que permite reclamar si el sistema no actúa cuando debía.", }, { id: "capacitacion", nombre: "Constancia de capacitación", corto: "Capacitación", + llano: "El papel con los nombres de quienes ya saben usarlo.", queEs: - "Nombre, fecha, contenido y quién capacitó. La capacitación sin constancia no es demostrable ante una autoridad.", + "Nombre, fecha, contenido y quién capacitó. La capacitación sin constancia no es demostrable ante una autoridad, aunque haya ocurrido.", }, ]; @@ -152,71 +174,59 @@ export default function PaginaEvidencia() { return ( <> - {/* ---------- encabezado ---------- */} -
-
- - -
-

Centro de evidencia

-

- La documentación es el producto que casi nadie entrega -

-

- Un equipo contra incendio se compra dos veces: cuando se instala y cuando alguien pide - los papeles. Esta página dice exactamente qué documentos deberían acompañar una - compra, qué aplica según la categoría y qué afirmaciones documentamos por número de - modelo antes de cotizar. -

- -
-
-
Documentos del acervo
-
{DOCUMENTOS.length}
-
-
-
Categorías indexadas
-
{CATEGORIAS.length}
-
-
-
Puntos por documentar
-
{totalClaims}
-
-
-
+ } + eyebrow="Centro de evidencia" + titulo="Los papeles que tiene que traer tu equipo" + entrada="Un equipo contra incendio se compra dos veces: cuando se instala y cuando alguien pide los documentos. Aquí está la lista de qué exigir, para que la segunda vez no sea un problema." + foto={FOTOS.publicaciones} + enfoque="65% 45%" + altura="alto" + prioridad + > +
+ + Pedir la documentación de un equipo + +

+ {DOCUMENTOS.length} documentos · {CATEGORIAS.length} categorías · {totalClaims} puntos + por documentar +

-
+ {/* ---------- el estado real del acervo: sin descargas falsas ---------- */} -
+
-
-

+

+

Estado del acervo

-

+

Aquí no hay botones de descarga, y es a propósito

-
+

- En la extracción pública del sitio de FSPM no apareció un índice verificable de - documentos por número de modelo. Podríamos haber puesto una fila de botones - «Descargar ficha» que no llevan a ningún archivo. No lo hicimos: un - botón falso destruye la credibilidad de todo lo demás que dice esta página. + Podríamos haber puesto una fila de botones «Descargar ficha» que no + llevan a ningún archivo. No lo hicimos: un botón falso destruye la credibilidad de + todo lo demás que dice esta página.

- Lo que sí hacemos es esto:{" "} - + En la extracción pública del sitio de FSPM no apareció un índice de documentos + asociado a números de modelo. Lo que sí aparece es un puñado de certificados + referidos al concentrado espumógeno y al agente humectante, no a un equipo + concreto: sirven como antecedente, no como la evidencia que una auditoría te va a + pedir sobre el extintor que tienes colgado en la pared. +

+

+ Así que esto es lo que hacemos:{" "} + los documentos se entregan por número de modelo, a solicitud, junto con la propuesta técnica @@ -225,35 +235,39 @@ export default function PaginaEvidencia() {

-
+
Solicitar documentación - - Ver el catálogo -
{/* ---------- qué documentación corresponde a cada compra ---------- */} -
+

El acervo

-

+

Los {DOCUMENTOS.length} documentos que corresponden a una compra

-

- No todos aplican a todos los productos. Los dos últimos aparecen solo cuando hay - instalación o personal que operar. +

+ Cada uno arranca con para qué te sirve, en una línea, y sigue con lo que tiene que + decir por dentro. Los dos últimos aplican solo cuando hay algo que instalar o + personal que operar.

-
    +
      {DOCUMENTOS.map((documento, i) => ( -
    1. +
    2. -

      +

      {documento.nombre}

      -

      {documento.queEs}

      +

      {documento.llano}

      +

      {documento.queEs}

    3. ))}
    @@ -273,25 +288,32 @@ export default function PaginaEvidencia() {
{/* ---------- índice por categoría ---------- */} -
+

Índice

-

- Qué aplica según la categoría de producto +

+ Qué exigir según lo que estés comprando

-

- Es la lista de lo que conviene exigir en cada compra, no una afirmación de qué está ya - publicado. +

+ Busca tu categoría y lleva esta lista a la conversación con cualquier proveedor, + nosotros incluidos. Es lo que conviene exigir en cada compra, no una afirmación de + qué está ya publicado.

-

+

Desliza la tabla para ver todas las columnas

- + Categoría @@ -312,7 +334,7 @@ export default function PaginaEvidencia() { {d.corto} @@ -323,11 +345,11 @@ export default function PaginaEvidencia() { {CATEGORIAS.map((categoria) => { const aplica = APLICA_POR_CATEGORIA[categoria.slug] ?? []; return ( - + {categoria.nombre} @@ -352,13 +374,13 @@ export default function PaginaEvidencia() { ) : ( - + {d.nombre} no aplica a {categoria.nombre} )} @@ -371,25 +393,38 @@ export default function PaginaEvidencia() {
+ +
+ + Pedir este índice para mi caso + +
{/* ---------- consolidado de claims por verificar ---------- */} -
+
-

+

Compromiso de documentación

-

+

Los {totalClaims} puntos que documentamos antes de cotizar

-
+

- El sitio anterior afirmaba varias cosas como características del producto:{" "} - no caduca,{" "} - 20 años de garantía,{" "} - no requiere recarga, cobertura de todas las clases - de fuego. El estudio de marca concluyó que no pueden presentarse como universales. + El sitio anterior presentaba varias cosas como características del producto:{" "} + no caduca,{" "} + 20 años de garantía,{" "} + no requiere recarga, cobertura de todas las + clases de fuego. El estudio de marca concluyó que no pueden presentarse como + universales.

Aquí no se repiten. Se convierten en una lista de trabajo: cada punto es un documento @@ -402,41 +437,41 @@ export default function PaginaEvidencia() { {porCategoria.map(({ categoria, productos, claims }) => (

- + - + {categoria.nombre} - + {productos.length} {productos.length === 1 ? "equipo" : "equipos"} ·{" "} {claims.length} {claims.length === 1 ? "punto" : "puntos"} por documentar -
+
{claims.length > 0 ? (
    {claims.map((claim) => (
  • ))}
) : ( -

+

Sin puntos pendientes registrados para esta categoría.

)} @@ -455,30 +490,36 @@ export default function PaginaEvidencia() {
{/* ---------- referencias normativas y certificaciones enumeradas ---------- */} -
+

Referencias normativas

-

+

Las normas contra las que se revisa un expediente

    {NORMAS.map((norma) => ( -
  • +
  • {norma.clave}

    -

    +

    {norma.titulo}

    -

    {norma.aplica}

    +

    {norma.aplica}

    Ver la fuente oficial @@ -486,14 +527,14 @@ export default function PaginaEvidencia() { ))}
-
-

+

+

Enumerado por la fuente, pendiente de verificar

-

+

Lo que el sitio anterior lista bajo «certificaciones»

-

+

Estas referencias aparecen enumeradas en la información pública de FSPM. Las reproducimos como inventario de trabajo, no como certificaciones vigentes: una referencia solo se convierte en evidencia cuando existe el documento con laboratorio, @@ -502,34 +543,45 @@ export default function PaginaEvidencia() {

    {CERTIFICACIONES_ENUMERADAS.map((certificacion) => ( -
  • +
  • {certificacion}
  • ))}
-

+

Ninguna norma sustituye a otra. Un listado UL no equivale a una NOM, y una prueba - ambiental no acredita desempeño de extinción. Construir ese mapa de equivalencias, con - el documento en la mano, es parte del trabajo previo a una propuesta. + ambiental no acredita desempeño de extinción. Construir ese mapa de equivalencias, + con el documento en la mano, es parte del trabajo previo a una propuesta.

{/* ---------- solicitud ---------- */} -
+

Solicitar documentación

-

- Pide los documentos por número de modelo +

+ Dinos qué te están pidiendo y te decimos qué existe

-

- Dinos qué equipo te interesa o qué te está pidiendo tu auditoría. Respondemos con lo - que existe, con su alcance, y con lo que no podemos sostener por escrito. +

+ No hace falta que sepas el nombre del documento. Con el equipo que te interesa o + con la observación que te levantaron es suficiente: respondemos con lo que existe, + con su alcance, y con lo que no podemos sostener por escrito.

@@ -539,8 +591,11 @@ export default function PaginaEvidencia() { "Si un plazo de garantía tiene condiciones, van en la propuesta, no en la letra chica.", "Si no tenemos un documento, lo decimos. Es más barato que descubrirlo en la auditoría.", ].map((punto) => ( -
  • -
  • +
  • ))} @@ -548,10 +603,12 @@ export default function PaginaEvidencia() {
    diff --git a/src/app/(sitio)/nosotros/page.tsx b/src/app/(sitio)/nosotros/page.tsx index 7dbcfb7..9606c2c 100644 --- a/src/app/(sitio)/nosotros/page.tsx +++ b/src/app/(sitio)/nosotros/page.tsx @@ -1,21 +1,27 @@ 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 { EMPRESA, PRENSA, SITIO_URL } from "@/data/empresa"; +import { CLIENTES, EMPRESA, PRENSA, SITIO_URL } from "@/data/empresa"; import { INDUSTRIAS } from "@/data/industrias"; /** * /nosotros — origen de la tecnología, posicionamiento y postura editorial. * - * La parte más importante de esta página es la última sección: por qué la + * 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"; -const DESCRIPCION = `${EMPRESA.nombre}: origen de la tecnología FireAde, posicionamiento en operaciones críticas y por qué documentamos en lugar de prometer.`; +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, @@ -28,34 +34,41 @@ export const metadata: Metadata = { }, }; +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 de aplicación están construidos con fines de demostración y lo dicen arriba, 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.", + "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.", }, @@ -64,64 +77,27 @@ const POSTURA = [ export default function PaginaNosotros() { return ( <> - {/* ---------- encabezado ---------- */} -
    -
    - - -
    -
    -

    Quiénes somos

    -

    - {EMPRESA.promesa} -

    -

    - {EMPRESA.descripcion} -

    - -
    -
    -
    Origen de la tecnología
    -
    {EMPRESA.fundacionOrigen}
    -
    -
    -
    Categorías
    -
    {CATEGORIAS.length}
    -
    -
    -
    Equipos y agentes
    -
    {PRODUCTOS.length}
    -
    -
    -
    - -
    - Personal de respuesta a incendios trabajando en una plataforma industrial - -
    + } + 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 + > +
    + + Hablar con un especialista + +

    + Tecnología de origen {EMPRESA.fundacionOrigen} · {CATEGORIAS.length} categorías ·{" "} + {PRODUCTOS.length} equipos y agentes +

    -
    + {/* ---------- origen de la tecnología ---------- */}
    @@ -130,11 +106,18 @@ export default function PaginaNosotros() {

    Origen

    -

    +

    La tecnología viene de {EMPRESA.empresaOrigen}

    +

    + No fabricamos un extintor más barato. Traemos una tecnología distinta y + respondemos por su documentación. +

    {EMPRESA.empresaOrigen} se presenta como fundada en {EMPRESA.fundacionOrigen} por{" "} {EMPRESA.fundadorOrigen}. {EMPRESA.nombreCorto} opera en México como{" "} @@ -149,7 +132,8 @@ export default function PaginaNosotros() {

    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. + formulación y de cada modelo se demuestra con su documentación, no con su + historia.

    @@ -177,7 +161,10 @@ export default function PaginaNosotros() { {String(industria.prioridad).padStart(2, "0")}

    - + {industria.nombre} {" — "} @@ -192,13 +179,19 @@ export default function PaginaNosotros() {

    {/* ---------- postura editorial ---------- */} -
    +

    Postura editorial

    -

    +

    Por qué documentamos en lugar de prometer

    @@ -218,6 +211,7 @@ export default function PaginaNosotros() {

    {regla.titulo}

    +

    {regla.llano}

    {regla.texto}

    ))} @@ -225,23 +219,73 @@ export default function PaginaNosotros() {
    + {/* ---------- logotipos de clientes, sin atribuirles resultado ---------- */} +
    +
    + +

    Logotipos publicados por la empresa

    +

    + Marcas que aparecen en la comunicación de FSPM +

    +

    + 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. +

    +
    + +
      + {CLIENTES.map((cliente) => ( +
    • + {cliente.nombre} +
    • + ))} +
    +
    +
    + {/* ---------- Christmas Hero, sin exagerarlo ---------- */} -
    +

    Antecedente de comunicación

    -

    +

    «Christmas Hero»

    -
    +
    +

    + 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. +

    - En 2019, {EMPRESA.nombreCorto} desarrolló una pieza de comunicación llamada - Christmas Hero: un adorno navideño que funcionaba como dispositivo de extinción - para árboles de Navidad. Fue publicada por LatinSpots en 2019 y obtuvo una mención - como segundo finalista de El Ojo Local México. + 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.

    Lo decimos con esa medida, sin convertirlo en más de lo que fue. Importa por dos @@ -257,7 +301,7 @@ export default function PaginaNosotros() {

    -

    +

    Publicaciones que han cubierto a FSPM

      @@ -267,7 +311,7 @@ export default function PaginaNosotros() { href={medio.url} target="_blank" rel="noopener noreferrer" - className="flex h-20 items-center justify-center rounded-[3px] border border-rule-lite bg-white px-4 transition-colors duration-200 hover:border-signal/60 [-webkit-tap-highlight-color:transparent]" + 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]" > ))}
    -

    - 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. +

    + 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.

    - {/* ---------- salidas ---------- */} -
    + {/* ---------- conversión ---------- */} +
    -
    - -

    Siguiente paso

    -

    - Lo que sigue depende de qué tan urgente sea tu caso -

    -
    -
    - - Hablar con nosotros - - - Ver el catálogo - - - Centro de evidencia - +
    +
    + +

    Siguiente paso

    +

    + Cuéntanos qué hay que proteger +

    +

    + 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. +

    +
    + +
    + +

    + O empieza por donde te sirva más +

    +
      + {[ + { 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) => ( +
    • + + {enlace.label} + +
    • + ))} +
    + +
    diff --git a/src/app/(sitio)/not-found.tsx b/src/app/(sitio)/not-found.tsx index f22ae3f..02f223d 100644 --- a/src/app/(sitio)/not-found.tsx +++ b/src/app/(sitio)/not-found.tsx @@ -1,5 +1,7 @@ import type { Metadata } from "next"; import Link from "next/link"; +import { FormularioContacto } from "@/components/formulario/FormularioContacto"; +import { FOTOS, HeroeLanding } from "@/components/site/HeroeLanding"; import { CATEGORIAS } from "@/data/catalogo"; import { EMPRESA } from "@/data/empresa"; import { INDUSTRIAS } from "@/data/industrias"; @@ -7,126 +9,137 @@ import { INDUSTRIAS } from "@/data/industrias"; /** * 404 con la voz del sitio: dice qué pasó y ofrece salidas reales. * Sin humor, sin disculpas y sin ilustración de astronauta perdido. + * + * Lleva héroe con fotografía como el resto de las páginas, y cierra con el + * formulario: si alguien llegó aquí buscando algo, decir qué buscaba es una + * salida tan válida como el índice. */ export const metadata: Metadata = { title: "Página no encontrada", description: - "La dirección solicitada no existe en este sitio. Rutas disponibles: catálogo, soluciones por industria, cumplimiento y contacto.", + "La dirección solicitada no existe en este sitio de FSPM. Estas son las rutas que sí funcionan: catálogo de equipos contra incendio, soluciones por industria, cumplimiento NOM-002, documentación técnica y contacto.", robots: { index: false, follow: true }, }; +const MENSAJE_INICIAL = "Llegué a una página que no existe. Lo que estaba buscando es "; + +const RECURSOS = [ + { href: "/cumplimiento-nom-002", label: "Cumplimiento NOM-002" }, + { href: "/evidencia", label: "Documentación técnica" }, + { href: "/preguntas-frecuentes", label: "Preguntas frecuentes" }, + { href: "/casos", label: "Casos de aplicación" }, + { href: "/nosotros", label: "Quiénes somos" }, + { href: "/aviso-de-privacidad", label: "Aviso de privacidad" }, +]; + export default function NoEncontrado() { return ( -
    -
    -
    -
    -

    - Error 404 -

    + <> + +
    + + Ir al catálogo + +

    + Si necesitas una respuesta ahora, marca al{" "} + + {EMPRESA.telefono} + + . +

    +
    +
    -

    - Esta dirección no existe en el sitio -

    + {/* ---------- índice de rutas reales ---------- */} +
    +
    +
    +
    +

    Índice del sitio

    +

    + Rutas que sí funcionan +

    -

    - Puede que la página se haya movido, que el enlace venga incompleto o que la dirección - nunca haya existido. Estas son las rutas que sí funcionan. -

    +

    + Categorías del catálogo +

    +
      + {CATEGORIAS.map((categoria) => ( +
    • + + {categoria.nombre} + +
    • + ))} +
    -
    - - Ir al catálogo - - - Ver soluciones - - - Contacto - +
    + +

    + Soluciones por operación +

    +
      + {INDUSTRIAS.map((industria) => ( +
    • + + {industria.nombre} + +
    • + ))} +
    + +
    + +

    + Recursos +

    +
      + {RECURSOS.map((enlace) => ( +
    • + + {enlace.label} + +
    • + ))} +
    -
    - -

    - Si necesitas una respuesta ahora, marca al{" "} - - {EMPRESA.telefono} - - . -

    -
    - -
    -

    Índice rápido

    - -

    - Categorías del catálogo -

    -
      - {CATEGORIAS.map((categoria) => ( -
    • - - {categoria.nombre} - -
    • - ))} -
    - -
    - -

    - Soluciones por operación -

    -
      - {INDUSTRIAS.map((industria) => ( -
    • - - {industria.nombre} - -
    • - ))} -
    - -
    - -

    - Recursos -

    -
      - {[ - { href: "/cumplimiento-nom-002", label: "Cumplimiento NOM-002" }, - { href: "/evidencia", label: "Centro de evidencia" }, - { href: "/preguntas-frecuentes", label: "Preguntas frecuentes" }, - { href: "/casos", label: "Casos de aplicación" }, - { href: "/nosotros", label: "Quiénes somos" }, - { href: "/aviso-de-privacidad", label: "Aviso de privacidad" }, - ].map((enlace) => ( -
    • - - {enlace.label} - -
    • - ))} -
    +
    -
    -
    +
    + ); } diff --git a/src/app/(sitio)/page.tsx b/src/app/(sitio)/page.tsx index 4bb5c84..a0113f8 100644 --- a/src/app/(sitio)/page.tsx +++ b/src/app/(sitio)/page.tsx @@ -2,38 +2,71 @@ import type { Metadata } from "next"; import { BloqueDiagnostico } from "@/components/site/home/BloqueDiagnostico"; import { BloqueNorma } from "@/components/site/home/BloqueNorma"; import { CatalogoDestacado } from "@/components/site/home/CatalogoDestacado"; +import { CategoriasCatalogo } from "@/components/site/home/CategoriasCatalogo"; import { ClasesFuego } from "@/components/site/home/ClasesFuego"; import { Clientes } from "@/components/site/home/Clientes"; +import { EntradaEscenario } from "@/components/site/home/EntradaEscenario"; import { Heroe } from "@/components/site/home/Heroe"; import { Prensa } from "@/components/site/home/Prensa"; import { RutasIndustria } from "@/components/site/home/RutasIndustria"; -import { TresRutas } from "@/components/site/home/TresRutas"; import { EMPRESA, SITIO_URL } from "@/data/empresa"; /** - * PÁGINA DE INICIO. + * PÁGINA DE INICIO — su trabajo es llevar al catálogo. * - * Es la implementación de la recomendación 10.1 del estudio de marca: rehacer - * el inicio con tres rutas — industria, tipo de riesgo y cumplimiento — en - * lugar de un escaparate de ocho categorías que obliga al visitante a inferir - * qué solución le corresponde. + * Encargo literal del cliente: "la página está enfocada en que vean el + * catálogo. Optimiza el home para que vean el catálogo, con botones y + * optimizaciones de funnel para ver el catálogo. La navegación por áreas es + * secundaria." * - * El orden de las secciones no es estético, es el recorrido de un comprador: - * clasificar el riesgo → elegir la ruta → entender el criterio técnico → ver - * el equipo → entender la obligación normativa → pedir el diagnóstico → juzgar - * a quién le está hablando. + * Eso choca de frente con la recomendación 10.1 del estudio de marca, que pidió + * rutas por industria justamente para que el sitio no fuera "un catálogo grande + * y ya". La síntesis no es elegir: es que la navegación por áreas deje de ser un + * destino y pase a ser **la puerta al catálogo ya filtrado**. Escenarios, + * industrias y clases de fuego siguen cualificando al visitante, siguen + * cargando la densidad técnica que posiciona en búsqueda, y ahora todas + * desembocan en `/catalogo?escenario=…`, `?clases=…` o `?categoria=…`. El + * catálogo sabe leer esos parámetros; la traducción vive en + * `src/components/site/home/enlaces.ts`. + * + * ---- El orden, y por qué ---- + * + * 1 Heroe un botón rojo al catálogo con la cifra de equipos, y + * el clasificador como atajo al catálogo filtrado + * 2 CategoriasCatalogo las 8 categorías como filtro. Ocupa la posición que + * tenían las rutas por industria: el equipo aparece + * antes de cualquier página de lectura + * 3 CatalogoDestacado 6 equipos con la misma tarjeta que usa /catalogo + * 4 EntradaEscenario 13 lugares reconocibles → catálogo filtrado + * 5 ClasesFuego el criterio técnico; cada clase → catálogo filtrado + * 6 RutasIndustria compacta, y con el único enlace a /soluciones + * 7 BloqueNorma la obligación normativa, resuelta ahí mismo + * 8 BloqueDiagnostico el formulario, para quien no quiere navegar catálogo + * 9 Clientes · Prensa a quién le está hablando + * + * Disciplina de embudo: un solo llamado primario por sección, y en las + * secciones de producto ese llamado va al catálogo o a una ficha, nunca a una + * página de lectura. Las salidas secundarias son enlaces de texto, no botones. + * Ninguna sección de venta enlaza a `/cumplimiento-nom-002`, `/evidencia` ni + * `/preguntas-frecuentes`: el argumento normativo se cierra en dos frases donde + * hace falta. */ +const TITULO = "Extintores y protección contra incendios en México"; + const DESCRIPCION = - "Extintores, agentes espumógenos y sistemas fijos, móviles y automáticos para industria, energía, logística, gasolineras, cocinas y PyMEs. Clasificamos el riesgo, seleccionamos el agente y entregamos la evidencia que exige la NOM-002-STPS-2010."; + "Extintores, espumas y sistemas fijos, móviles y automáticos para gasolineras, bodegas, cocinas industriales, plantas, minas y oficinas. Entra por el lugar que operas o por clase de fuego (A, B, C, D, K): te decimos qué equipo te toca, cuánto y qué documentar para la NOM-002-STPS-2010."; export const metadata: Metadata = { - title: `${EMPRESA.promesa} | ${EMPRESA.nombreCorto}`, + title: { absolute: `${TITULO} | ${EMPRESA.nombreCorto}` }, description: DESCRIPCION, alternates: { canonical: "/" }, keywords: [ "protección contra incendios", "extintores industriales", + "extintor para gasolinera", + "extintor para cocina industrial", + "protección contra incendios en bodega", "NOM-002-STPS-2010", "agente espumógeno", "sistema contra incendio", @@ -44,7 +77,7 @@ export const metadata: Metadata = { locale: "es_MX", url: SITIO_URL, siteName: EMPRESA.nombre, - title: `${EMPRESA.promesa} | ${EMPRESA.nombreCorto}`, + title: `${TITULO} | ${EMPRESA.nombreCorto}`, description: DESCRIPCION, images: [ { @@ -57,7 +90,7 @@ export const metadata: Metadata = { }, twitter: { card: "summary_large_image", - title: `${EMPRESA.promesa} | ${EMPRESA.nombreCorto}`, + title: `${TITULO} | ${EMPRESA.nombreCorto}`, description: DESCRIPCION, images: ["/media/foto/bombero-plataforma.jpg"], }, @@ -106,10 +139,11 @@ export default function PaginaInicio() { /> - - - + + + + diff --git a/src/app/(sitio)/preguntas-frecuentes/page.tsx b/src/app/(sitio)/preguntas-frecuentes/page.tsx index 8988a15..1dbdbdc 100644 --- a/src/app/(sitio)/preguntas-frecuentes/page.tsx +++ b/src/app/(sitio)/preguntas-frecuentes/page.tsx @@ -1,23 +1,32 @@ import type { Metadata } from "next"; import Link from "next/link"; +import { FormularioContacto } from "@/components/formulario/FormularioContacto"; +import { TarjetaEscenario } from "@/components/marca/IconoEscenario"; +import { Migas } from "@/components/site/contenido/Migas"; +import { FOTOS, HeroeLanding } from "@/components/site/HeroeLanding"; import { Revelar } from "@/components/site/Revelar"; import { EMPRESA, NORMAS, SITIO_URL } from "@/data/empresa"; +import { ESCENARIOS, type Escenario } from "@/data/escenarios"; /** * /preguntas-frecuentes * + * Aquí llega quien tiene miedo de preguntar. Así que cada respuesta abre con + * una frase directa —la respuesta corta, sin condescendencia— y después + * desarrolla el detalle para quien lo necesita. + * * Las preguntas vienen de la página real de FSPM. Las respuestas no se copian: * donde la fuente afirma "no caduca", "20 años de garantía" o "no requiere * recarga", la respuesta explica qué documento respalda eso y bajo qué * condiciones. Es el mismo criterio del resto del sitio. * - * El JSON-LD FAQPage se construye desde el mismo arreglo que se renderiza, así - * que no puede desincronizarse del contenido visible. + * El JSON-LD FAQPage se construye desde el mismo arreglo que se renderiza — + * respuesta corta incluida—, así que no puede desincronizarse de lo visible. */ -const TITULO = "Preguntas frecuentes"; +const TITULO = "Preguntas frecuentes sobre extintores y FireAde 2000"; const DESCRIPCION = - "Respuestas verificables sobre FireAde 2000, vida útil, garantía, recarga, clases de fuego, certificaciones y documentación. Qué documento respalda cada afirmación y bajo qué condiciones."; + "Respuestas directas sobre vida útil, garantía, recarga, clases de fuego, certificaciones UL y NFPA, precio y documentación. Cada respuesta dice qué documento la respalda y bajo qué condiciones."; export const metadata: Metadata = { title: TITULO, @@ -30,9 +39,13 @@ export const metadata: Metadata = { }, }; +const MENSAJE_INICIAL = "Tengo una duda que no está en las preguntas frecuentes: "; + type Pregunta = { id: string; pregunta: string; + /** La respuesta en una frase. Lo primero que se lee al abrir. */ + corta: string; /** Párrafos en texto plano: alimentan la página y el JSON-LD. */ respuesta: string[]; }; @@ -54,26 +67,32 @@ const GRUPOS: Grupo[] = [ { id: "caduca", pregunta: "¿Es cierto que el equipo no caduca?", + corta: + "El agente puede durar muchísimo; el recipiente que lo contiene sigue necesitando revisión. Son dos cosas distintas y conviene no mezclarlas.", respuesta: [ "La información pública de FSPM afirma que FireAde 2000 tiene vida de anaquel ilimitada y que conserva sus propiedades incluso mezclado con agua. No repetimos esa afirmación como característica del producto porque una vida útil se demuestra con un documento, no con una frase.", "Lo que respalda una respuesta seria es la carta técnica del fabricante referida al número de modelo, con las condiciones de almacenamiento y las circunstancias que la invalidan. Eso lo entregamos antes de cotizar.", - "Además, conviene separar dos cosas que suelen mezclarse: la vida útil del agente y el mantenimiento del equipo que lo contiene. Aunque el agente conservara sus propiedades, el recipiente, la válvula y el manómetro siguen sujetos a revisión.", + "Y aunque el agente conservara intactas sus propiedades, el recipiente, la válvula y el manómetro siguen sujetos a revisión periódica. Un equipo con agente vigente y válvula vencida no pasa una verificación.", ], }, { id: "recarga", pregunta: "¿Entonces no requiere recarga?", + corta: + "Puede que no necesites recargarlo cada año, pero sí revisarlo. La revisión no desaparece nunca.", respuesta: [ - "La fuente lo presenta así, pero no especifica si aplica al agente, al extintor completo, ni bajo qué condiciones de mantenimiento. Esa distinción es justamente la que decide si tu expediente aguanta una verificación.", - "Nuestra respuesta es documental: confirmamos por escrito a qué se refiere exactamente la ausencia de recarga en tu modelo y qué obligaciones de mantenimiento subsisten.", - "En todo caso, la revisión periódica del equipo no desaparece. La NOM-154-SCFI-2005, referida por la NOM-002, establece que la revisión de los extintores debe hacerse cada mes.", + "La fuente presenta la ausencia de recarga como característica en varias páginas de producto, y en la de rociadores lo matiza ella misma: dice que al usarse con FireAde 2000 no necesita recarga anual porque el agente no tiene fecha de caducidad, pero que igual se recomienda una revisión anual.", + "Ese matiz es la respuesta. Confirmamos por escrito a qué se refiere exactamente la ausencia de recarga en tu modelo —al agente, al equipo completo, bajo qué condiciones— y qué obligaciones de mantenimiento subsisten.", + "En todo caso, la revisión periódica no desaparece. La NOM-154-SCFI-2005, referida por la NOM-002, establece que la revisión de los extintores debe hacerse cada mes.", ], }, { id: "garantia", pregunta: "¿La garantía es de 20 años o de 15?", + corta: + "En la información pública aparecen las dos cifras, así que no publicamos ninguna: te entregamos el documento de garantía del modelo.", respuesta: [ - "Las dos cifras aparecen en la información pública de FSPM: las páginas de producto mencionan veinte años y la sección de preguntas frecuentes menciona quince para los dispositivos de aplicación. Esa inconsistencia es la razón por la que no publicamos ninguna de las dos como dato.", + "Las páginas de producto mencionan veinte años y la sección de preguntas frecuentes menciona quince para los dispositivos de aplicación. Esa inconsistencia es exactamente la razón por la que no convertimos ninguna de las dos en un dato de venta.", "Lo que sí entregamos es el documento de garantía del modelo, con plazo, cobertura, exclusiones y las obligaciones de mantenimiento que la conservan vigente. Un plazo sin condiciones es una cifra publicitaria, no una garantía.", ], }, @@ -88,16 +107,20 @@ const GRUPOS: Grupo[] = [ { id: "afff", pregunta: "¿FireAde 2000 es un espumógeno AFFF?", + corta: + "Está clasificado en esa familia, pero la etiqueta importa menos que la formulación concreta que te ofrecen.", respuesta: [ "La fuente responde \"sí y no\": lo describe como clasificado en la familia AFFF y, al mismo tiempo, como una evolución tecnológica frente al AFFF tradicional basado en flúor. En el catálogo aparece como FireAde 2000 AR-AFFF, con una solución al 6% para los equipos Enforcer.", - "Para una decisión de compra la categoría importa menos que la formulación concreta. Lo que resuelve la pregunta es la ficha técnica de la variante que se te ofrece y su hoja de datos de seguridad vigente, con la composición declarada.", + "Para una decisión de compra, lo que resuelve la pregunta es la ficha técnica de la variante que se te ofrece y su hoja de datos de seguridad vigente, con la composición declarada. La categoría comercial no dice qué tan bien apaga lo que tú tienes.", ], }, { id: "clases", pregunta: "¿Sirve para las cinco clases de fuego, A, B, C, D y K?", + corta: + "La fuente lo afirma de forma general, pero pone una condición en clase C. Cuando una afirmación trae condición, la condición es parte de la afirmación.", respuesta: [ - "La fuente lo afirma de forma general, pero ella misma pone una condición: para fuegos de clase C la eficacia se declara con agua desmineralizada. Cuando una afirmación trae condición, la condición es parte de la afirmación.", + "Para fuegos de clase C la propia fuente declara la eficacia con agua desmineralizada. Ese detalle cambia la conversación: no es lo mismo comprar un equipo que funciona en tu tablero eléctrico que uno que funciona en un tablero con una condición de agua que tu operación no cumple.", "En cada ficha de producto mostramos el riel de clases con lo que la fuente declara, no con lo que convendría declarar, y anotamos que las condiciones por clase se confirman en la ficha técnica del modelo: concentración, equipo y tipo de agua.", "Hay productos del catálogo a los que deliberadamente no les atribuimos clases de fuego, como TrakAde y VaporAde Plus, porque la fuente no los presenta como agentes de extinción.", ], @@ -105,42 +128,50 @@ const GRUPOS: Grupo[] = [ { id: "ambiental", pregunta: "¿Es cierto que no es tóxico ni contaminante?", + corta: + "Son afirmaciones fuertes y verificables. Por eso pedimos ver los reportes antes de repetirlas.", respuesta: [ - "La fuente afirma que no contiene materiales tóxicos, que no es contaminante, que superó pruebas con organismos acuáticos y que su biodegradabilidad es prácticamente total en veinticinco días. Son afirmaciones fuertes y verificables, y precisamente por eso pedimos verlas antes de repetirlas.", + "La fuente afirma que no contiene materiales tóxicos, que no es contaminante, que superó pruebas con organismos acuáticos y que su biodegradabilidad es prácticamente total en veinticinco días.", "Lo que las sostiene es la hoja de datos de seguridad vigente y los reportes de laboratorio con método, alcance y fecha, referidos a la formulación exacta. Un resultado obtenido sobre una variante no se traslada automáticamente a otra.", - "Lo que no vamos a hacer es prometer que una formulación seguirá conforme a la regulación ambiental futura. Nadie puede sostener eso, y el estudio de marca lo señala como un riesgo reputacional.", + "Lo que no vamos a hacer es prometer que una formulación seguirá conforme a la regulación ambiental futura, como afirma la fuente original. Nadie puede sostener eso, y el estudio de marca lo señala como un riesgo reputacional.", ], }, { id: "enfriamiento", pregunta: "¿Se puede aplicar de forma preventiva para enfriar equipo que no está ardiendo?", + corta: "La fuente describe ese uso, pero lo evaluamos caso por caso y no lo ofrecemos como capacidad general.", respuesta: [ - "La fuente describe ese uso, por ejemplo en transformadores y en maquinaria con carga térmica de riesgo, y menciona también instalaciones para las que no existe documentación en la extracción pública que revisamos.", - "Por eso limitamos la respuesta a lo que puede sostenerse: la aplicación preventiva de enfriamiento se evalúa caso por caso, con la ficha técnica del agente y con el criterio del responsable del equipo que se va a rociar. No la ofrecemos como una capacidad general.", + "La fuente menciona ese uso en transformadores y en maquinaria con carga térmica de riesgo, y también en instalaciones para las que no existe documentación en la extracción pública que revisamos.", + "Por eso limitamos la respuesta a lo que puede sostenerse: la aplicación preventiva de enfriamiento se evalúa con la ficha técnica del agente y con el criterio del responsable del equipo que se va a rociar. Si tu caso es uno de esos, dinos exactamente cuál y respondemos sobre él.", ], }, { id: "compatibilidad", pregunta: "¿Es compatible con mi equipo instalado y con agua salada?", + corta: + "La fuente afirma compatibilidad con cualquier equipamiento y con todo tipo de aguas. La pregunta útil es más estrecha que eso.", respuesta: [ - "La fuente afirma compatibilidad con cualquier equipamiento, incluidos sistemas de espuma de aire comprimido, y con todo tipo de aguas.", - "En la práctica, la pregunta que hay que responder es más estrecha: si es compatible con tu equipo, del fabricante que sea, en la concentración que tu sistema dosifica. Eso se confirma cruzando la ficha técnica del agente con el manual de tu equipo, y es parte del trabajo previo a una propuesta.", + "Lo que hay que responder no es si es compatible en general, sino si es compatible con tu equipo, del fabricante que sea, en la concentración que tu sistema dosifica.", + "Eso se confirma cruzando la ficha técnica del agente con el manual de tu equipo, y es parte del trabajo previo a una propuesta. Si tienes el modelo del sistema a mano, es el dato que más acelera la respuesta.", ], }, { id: "mezcla", pregunta: "¿Puedo mezclarlo con el espumógeno que ya tengo?", + corta: "No. Y la propia fuente lo desaconseja.", respuesta: [ - "No. La propia fuente lo desaconseja, y sin un dato de compatibilidad documentado la mezcla compromete el desempeño y muy probablemente la garantía.", + "Sin un dato de compatibilidad documentado, la mezcla compromete el desempeño y muy probablemente la garantía.", "Si tienes concentrado de otra marca en el tanque, lo correcto es planear la sustitución y el manejo del residuo, no la mezcla. La disposición del concentrado que sale es un tema técnico y de costo que conviene resolver antes de la compra, no después.", ], }, { id: "limpieza", pregunta: "¿Qué implica la limpieza después de usarlo?", + corta: + "La fuente dice que basta agua. Ten en cuenta que el residuo no es solo agente: es agente más lo que se estaba quemando.", respuesta: [ "La fuente indica que el agente no es corrosivo, que su pH es prácticamente neutro y que la limpieza se resuelve con agua.", - "El documento que respalda eso es la hoja de datos de seguridad, y es también el que dice cómo manejar el residuo según el material sobre el que se aplicó. Después de un incendio, el residuo no es solo agente: es agente más lo que se estaba quemando.", + "El documento que respalda eso es la hoja de datos de seguridad, y es también el que dice cómo manejar el residuo según el material sobre el que se aplicó. Después de un incendio esa distinción decide si el residuo se va al drenaje o se maneja como residuo peligroso.", ], }, ], @@ -153,35 +184,42 @@ const GRUPOS: Grupo[] = [ { id: "alcance", pregunta: "¿Cuál es el alcance real de un extintor Enforcer?", + corta: + "La fuente declara más de 10 metros. Lo publicamos con esa etiqueta y no lo convertimos en una cifra exacta.", respuesta: [ - "En el catálogo aparece como \"más de 10 m\", que es el dato declarado por la fuente. Lo publicamos con esa etiqueta y no lo convertimos en una cifra exacta.", - "Un alcance solo significa algo junto con las condiciones de la medición: presión, boquilla, ángulo y agente. Eso viene en la ficha técnica del modelo, y es lo que pedimos antes de proponer una distribución de equipo.", + "Un alcance solo significa algo junto con las condiciones de la medición: presión, boquilla, ángulo y agente.", + "Eso viene en la ficha técnica del modelo, y es lo que pedimos antes de proponer una distribución de equipo. Distribuir con un alcance sin condiciones es como distribuir con un plano sin escala.", ], }, { id: "certificaciones", pregunta: "¿Los equipos tienen certificación UL o cumplen NFPA?", + corta: + "Hay referencias enumeradas, y una referencia no es un certificado. Te entregamos el que corresponde al modelo que se te ofrece.", respuesta: [ - "La información pública de FSPM enumera referencias como UL 162, UL 711, NFPA 11, NFPA 16, NFPA 18, DIN EN 1568-3 y LASTFIRE, entre otras.", - "Una referencia no es un certificado. Un listado aplica a una formulación o a un modelo concreto, con expediente, alcance y fecha, y no se traslada al resto del catálogo. Lo que entregamos es el certificado que corresponde al modelo que se te está ofreciendo.", + "La información pública de FSPM enumera referencias como UL 162, UL 711, NFPA 11, NFPA 16, NFPA 18, DIN EN 1568-3 y LASTFIRE, entre otras. También publica algunos certificados en PDF, pero referidos al concentrado espumógeno y al agente humectante, no a un número de modelo de equipo.", + "Un listado aplica a una formulación o a un modelo concreto, con expediente, alcance y fecha, y no se traslada al resto del catálogo. Lo que entregamos es el certificado que corresponde al modelo que se te está ofreciendo.", "También conviene decirlo con claridad: ninguna norma sustituye a otra. Un listado UL no equivale a una NOM mexicana, y una prueba ambiental no acredita desempeño de extinción.", ], }, { id: "elegir", pregunta: "¿Cómo sé qué equipo me corresponde?", + corta: + "Con tres respuestas: qué se puede quemar en cada área, quién va a operar el equipo y qué documentación te van a pedir.", respuesta: [ - "Por tres respuestas, en este orden: qué se quema en cada área, quién va a operar el equipo y en cuántos segundos, y qué documentación te van a pedir.", "La primera define la clase de fuego y por tanto el agente. La segunda define si el equipo debe ser portátil, móvil, fijo o de activación automática. La tercera define el expediente.", - "El catálogo está construido para responder eso: se filtra por clase de fuego y por aplicación, no por nombre de producto.", + "El catálogo está construido para responder eso: se filtra por clase de fuego y por aplicación, no por nombre de producto. Si no sabes ninguna de las tres, el escenario de tu instalación es suficiente para empezar.", ], }, { id: "precio", pregunta: "¿Cuánto cuesta?", + corta: + "No publicamos precios. Dependen de la configuración, la cantidad, la instalación y el servicio, y preferimos decírtelo antes de que llenes un formulario.", respuesta: [ - "No publicamos precios porque dependen de la configuración, la cantidad, la instalación y el servicio asociado. Preferimos decirlo antes de que llenes un formulario, no después.", - "Lo que sí podemos anticipar es el criterio: el error caro casi nunca es el precio unitario, es comprar equipo que no corresponde a la clase de fuego del área o distribuirlo por metros cuadrados en lugar de por distancia de recorrido.", + "Lo que sí podemos anticipar es el criterio de costo: el error caro casi nunca es el precio unitario. Es comprar equipo que no corresponde a la clase de fuego del área, o distribuirlo por metros cuadrados en lugar de por distancia de recorrido.", + "En la respuesta a una solicitud va la configuración propuesta y qué se puede recortar sin perder cumplimiento. Eso es más útil que una lista de precios.", ], }, ], @@ -194,15 +232,18 @@ const GRUPOS: Grupo[] = [ { id: "nom-002", pregunta: "¿Qué me exige la NOM-002-STPS-2010?", + corta: + "Si tienes gente trabajando, te aplica. Lo que cambia entre una oficina y una planta es el nivel de riesgo con el que se clasifica el centro.", respuesta: [ NORMAS[0].aplica, - "En términos prácticos: la clasificación del riesgo va primero y condiciona todo lo demás. Los umbrales de esa clasificación dependen del material y de la superficie de cada centro de trabajo, así que no los reproducimos aquí.", + "En términos prácticos, la clasificación del riesgo va primero y condiciona todo lo demás. Los umbrales de esa clasificación dependen del material y de la superficie de cada centro de trabajo, así que no los reproducimos aquí.", "Es una referencia orientativa. La normativa puede complementarse por estado y municipio, y esta página no sustituye un dictamen técnico ni jurídico.", ], }, { id: "revision", pregunta: "¿Cada cuánto hay que revisar los extintores?", + corta: "Cada mes.", respuesta: [ NORMAS[1].aplica, "Es la obligación recurrente que más seguido se pierde, y casi nunca por desconocerla: se pierde porque nadie quedó a cargo de la bitácora. Por eso, en una entrega, el formato de bitácora y el responsable nombrado son parte del alcance.", @@ -211,23 +252,45 @@ const GRUPOS: Grupo[] = [ { id: "documentos", pregunta: "¿Qué documentos debo exigir en una compra?", + corta: + "Ficha técnica, hoja de seguridad del agente, certificado por modelo, garantía con condiciones, manual y bitácora. Y dos más si hay instalación o personal que operar.", respuesta: [ - "Ficha técnica del modelo, hoja de datos de seguridad del agente, certificado o listado por modelo, garantía con condiciones por escrito, manual de operación y mantenimiento, y bitácora de revisión. Cuando hay instalación, además la constancia de puesta en marcha; cuando hay personal que operar, la constancia de capacitación.", - "No todos aplican a todos los productos. El centro de evidencia tiene el índice de qué corresponde según la categoría.", + "Cuando hay algo que instalar, se agrega la constancia de instalación y puesta en marcha; cuando hay personal que operar, la constancia de capacitación.", + "No todos aplican a todos los productos. El centro de evidencia tiene el índice de qué corresponde según la categoría, en una tabla que puedes llevarte a la conversación con cualquier proveedor.", ], }, { id: "descargas", pregunta: "¿Por qué no hay documentos para descargar en este sitio?", + corta: + "Porque no vamos a poner botones que no llevan a ningún archivo. Los documentos se entregan por número de modelo, a solicitud.", respuesta: [ - "Porque en la extracción pública del sitio de FSPM no apareció un índice verificable de documentos por número de modelo, y poner botones de descarga que no llevan a ningún archivo sería peor que no tenerlos.", - "Los documentos se entregan por número de modelo, a solicitud, junto con la propuesta técnica. Y cuando un documento no exista o no cubra lo que se afirma, lo decimos en la propuesta.", + "En la extracción pública del sitio de FSPM no apareció un índice de documentos asociado a números de modelo, y simular uno sería peor que no tenerlo.", + "Los documentos se entregan junto con la propuesta técnica. Y cuando un documento no exista o no cubra lo que se afirma, lo decimos en la propuesta en lugar de omitirlo.", ], }, ], }, ]; +/** Escenarios como entrada alternativa para quien no sabe qué preguntar. */ +const SLUGS_ENTRADA = [ + "oficinas", + "cocinas-hoteles", + "gasolineras", + "bodegas", + "industria", + "hogar", +]; + +const ESCENARIOS_ENTRADA = ESCENARIOS.filter((e) => SLUGS_ENTRADA.includes(e.slug)); + +function hrefEscenario(escenario: Escenario): string { + if (escenario.industria) return `/soluciones/${escenario.industria}`; + const categoria = escenario.categorias[0]; + return categoria ? `/catalogo/${categoria}` : "/catalogo"; +} + export default function PaginaPreguntas() { const todas = GRUPOS.flatMap((g) => g.preguntas); @@ -239,7 +302,7 @@ export default function PaginaPreguntas() { name: p.pregunta, acceptedAnswer: { "@type": "Answer", - text: p.respuesta.join(" "), + text: [p.corta, ...p.respuesta].join(" "), }, })), }; @@ -251,50 +314,50 @@ export default function PaginaPreguntas() { dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} /> - {/* ---------- encabezado ---------- */} -
    + } + eyebrow="Preguntas frecuentes" + titulo="Pregunta lo que quieras preguntar" + entrada="Cada respuesta abre con una frase directa y sigue con el detalle. No hay pregunta obvia: la mayoría de los errores caros de esta industria empiezan por no preguntar." + foto={FOTOS.practica} + enfoque="72% 45%" + altura="alto" + prioridad + > +
    + + Hacer mi pregunta + +

    + {todas.length} preguntas · {GRUPOS.length} temas +

    +
    +
    + + {/* ---------- por qué las respuestas no son las de la competencia ---------- */} +
    - - -
    -

    Preguntas frecuentes

    -

    - Las mismas preguntas, respondidas con lo que se puede documentar -

    -

    - Estas preguntas vienen de la página real de FSPM. Las respuestas no. Donde la fuente - afirmaba algo sin condiciones, aquí se explica qué documento lo respalda y qué queda - pendiente de confirmar por número de modelo. Es menos vistoso y sirve mucho más - cuando llega la auditoría. +

    +

    Cómo leer estas respuestas

    +

    + Las preguntas son las del sitio original. Las respuestas, no +

    +

    + Donde la fuente afirmaba algo sin condiciones —que no caduca, que no requiere recarga, + que tiene veinte años de garantía— aquí se explica qué documento lo respalda y qué + queda pendiente de confirmar por número de modelo. Es menos vistoso y sirve mucho más + el día que llega la auditoría.

    - -
    -
    -
    Preguntas
    -
    {todas.length}
    -
    -
    -
    Temas
    -
    {GRUPOS.length}
    -
    -
    {/* ---------- los grupos ---------- */} {GRUPOS.map((grupo, indice) => { - const claro = indice % 2 === 0; + const claro = indice % 2 !== 0; return (
    - +
    - {/* ---------- 8. productos relacionados ---------- */} + {/* ---------- 8. productos relacionados, cada uno con su botón de cotizar ---------- */} {relacionados.length > 0 && categoria ? (
    @@ -410,18 +339,25 @@ export default async function PaginaProducto({ params }: Props) { Otros equipos de {categoria.nombre.toLowerCase()}

    - Comparten agente y clases declaradas; cambian la capacidad y el formato de respuesta. + Comparten agente y clases declaradas; cambian la capacidad y el formato de + respuesta. Si ya sabes cuál es el tuyo, cotízalo directo desde aquí.

    {relacionados.slice(0, 6).map((p) => ( - + ))}
    -
    - +
    + + Cotizar este equipo + + Ver toda la categoría
    diff --git a/src/app/(sitio)/soluciones/[industria]/page.tsx b/src/app/(sitio)/soluciones/[industria]/page.tsx index 8d99f03..efa710d 100644 --- a/src/app/(sitio)/soluciones/[industria]/page.tsx +++ b/src/app/(sitio)/soluciones/[industria]/page.tsx @@ -1,34 +1,109 @@ import type { Metadata } from "next"; -import Image from "next/image"; import Link from "next/link"; import { notFound } from "next/navigation"; import { FormularioContacto } from "@/components/formulario/FormularioContacto"; import { RielClases } from "@/components/marca/RielClases"; +import { FOTOS, HeroeLanding } from "@/components/site/HeroeLanding"; import { Revelar } from "@/components/site/Revelar"; +import { BandaEscenarios } from "@/components/site/soluciones/BandaEscenarios"; import { TarjetaProducto } from "@/components/site/soluciones/TarjetaProducto"; import { getCategoria, productosDeCategoria } from "@/data/catalogo"; import { CLASES_INFO, parseClases, type ClaseFuego } from "@/data/clases-fuego"; -import { EMPRESA } from "@/data/empresa"; +import { EMPRESA, SITIO_URL } from "@/data/empresa"; +import { ESCENARIOS } from "@/data/escenarios"; import { getIndustria, INDUSTRIAS } from "@/data/industrias"; /** * PÁGINA DE SOLUCIÓN POR INDUSTRIA. * * Es una de las cinco landing pages que pide la recomendación 6 del estudio. - * Usa todos los campos del segmento: el dolor como encabezado, el contexto, las - * preguntas del diagnóstico presentadas como la secuencia real de una visita - * técnica, el paquete —la traducción de "no cotices solo un extintor"—, el - * cumplimiento aplicable sobre fondo claro, y el equipo de las categorías que - * corresponden a esa ruta. + * Abre con `HeroeLanding` —fotografía de fondo, requisito explícito del + * cliente— con foto y recorte distintos por segmento, para que las cinco no se + * lean como la misma página con otro título. + * + * El `

    ` lleva la frase que se busca: "protección contra incendios para + * cocinas industriales, hoteles y alimentos". El dolor del comprador, que antes + * era el `

    `, pasó a la entrada: sigue siendo lo primero que se lee, pero + * ya no ocupa el encabezado que tiene que competir en buscadores. + * + * Las dos capas conviven: la banda de escenarios traduce el segmento a lugares + * reconocibles y las preguntas del diagnóstico dan el nivel técnico. Los + * llamados primarios de todas las secciones terminan en `#diagnostico`; los + * enlaces a `/evidencia` y `/cumplimiento-nom-002` se retiraron porque sacaban + * al visitante del embudo hacia páginas de lectura, y su argumento se resolvió + * con dos frases en cada sección. * * El riesgo declarado viaja por query string desde el clasificador del inicio: - * `?clases=B,C` gana sobre las clases dominantes del segmento y llega - * preseleccionado al formulario. + * `?clases=B,C` gana sobre las clases dominantes del segmento y llega al + * formulario como campo oculto. */ type Params = { industria: string }; type Busqueda = { [clave: string]: string | string[] | undefined }; +/** + * Foto, recorte y encabezados por segmento. + * + * `foto` y `enfoque`: las imágenes comparten archivo con otras páginas del + * sitio, así que el recorte es lo que evita que dos landings se vean + * idénticas. + * + * `h1` y `titulo`: el encabezado de la página y el título de buscador se + * escriben a mano por segmento en lugar de componerse con `nombreLargo`. + * Concatenar daba encabezados de ochenta y ocho caracteres —"plantas + * industriales, petróleo, gas, química y energía"— que se comen cuatro líneas + * en el héroe y se truncan en el resultado de búsqueda. Cada par lleva la + * frase que realmente se busca; `nombreLargo` sigue completo en el eyebrow y + * en la descripción. + */ +const PORTADA: Record< + string, + { foto: string; enfoque: string; h1: string; titulo: string } +> = { + "industria-energia": { + foto: FOTOS.bombero, + enfoque: "72% 38%", + h1: "Protección contra incendios para plantas industriales y energía", + titulo: "Protección contra incendios industrial y de energía", + }, + "logistica-bodegas": { + foto: FOTOS.planta, + enfoque: "45% 62%", + h1: "Protección contra incendios para bodegas y centros de distribución", + titulo: "Protección contra incendios para bodegas y logística", + }, + "gasolineras-flotas": { + foto: FOTOS.practica, + enfoque: "62% 46%", + h1: "Protección contra incendios para gasolineras y flotas", + titulo: "Extintores para gasolineras y flotas", + }, + "cocinas-hoteles": { + foto: FOTOS.galeria, + enfoque: "38% 30%", + h1: "Protección contra incendios para cocinas industriales y hoteles", + titulo: "Extintores clase K para cocinas industriales y hoteles", + }, + "pymes-oficinas": { + foto: FOTOS.contacto, + enfoque: "66% 26%", + h1: "Protección contra incendios para PyMEs, oficinas y comercios", + titulo: "Extintores para PyMEs, oficinas y comercios", + }, +}; + +/** Respaldo si algún día se agrega un segmento sin copia propia. */ +function portadaDe(slug: string, nombreLargo: string, imagen: string) { + return ( + PORTADA[slug] ?? { + foto: imagen, + enfoque: "center", + h1: `Protección contra incendios para ${nombreLargo.toLowerCase()}`, + titulo: `Protección contra incendios para ${nombreLargo.toLowerCase()}`, + } + ); +} + export function generateStaticParams(): Params[] { return INDUSTRIAS.map((i) => ({ industria: i.slug })); } @@ -45,27 +120,38 @@ export async function generateMetadata({ return { title: "Solución no encontrada", robots: { index: false, follow: false } }; } - const titulo = `Protección contra incendios para ${industria.nombreLargo.toLowerCase()}`; - const descripcion = `${industria.dolor} ${industria.contexto.split(". ")[0]}. Clases de fuego ${industria.clases.join(", ")}, equipo, capacitación y la evidencia que exige la NOM-002-STPS-2010.`; + const portada = portadaDe(industria.slug, industria.nombreLargo, industria.imagen); + + const lugares = ESCENARIOS.filter((e) => e.industria === industria.slug) + .map((e) => e.nombre.toLowerCase()) + .join(", "); + + const descripcion = `${industria.dolor} Equipo, agente y capacidad para ${ + lugares || industria.nombreLargo.toLowerCase() + }; clases de fuego ${industria.clases.join( + ", ", + )}. Incluye la evidencia que exige la NOM-002-STPS-2010. Diagnóstico sin costo.`; + + const url = `${SITIO_URL}/soluciones/${industria.slug}`; return { - title: titulo, + title: portada.titulo, description: descripcion, alternates: { canonical: `/soluciones/${industria.slug}` }, openGraph: { type: "article", locale: "es_MX", siteName: EMPRESA.nombre, - url: `/soluciones/${industria.slug}`, - title: `${titulo} | ${EMPRESA.nombreCorto}`, + url, + title: `${portada.titulo} | ${EMPRESA.nombreCorto}`, description: descripcion, - images: [{ url: industria.imagen, width: 1200, height: 630, alt: industria.nombreLargo }], + images: [{ url: portada.foto, width: 1200, height: 630, alt: industria.nombreLargo }], }, twitter: { card: "summary_large_image", - title: `${titulo} | ${EMPRESA.nombreCorto}`, + title: `${portada.titulo} | ${EMPRESA.nombreCorto}`, description: descripcion, - images: [industria.imagen], + images: [portada.foto], }, }; } @@ -96,6 +182,9 @@ export default async function PaginaSolucion({ declaradas.length > 0 ? declaradas : (industria.clases as ClaseFuego[]); const vieneDelClasificador = declaradas.length > 0; + const escenarios = ESCENARIOS.filter((e) => e.industria === industria.slug); + const portada = portadaDe(industria.slug, industria.nombreLargo, industria.imagen); + const bloquesCategoria = industria.categorias .map((slugCategoria) => { const categoria = getCategoria(slugCategoria); @@ -106,41 +195,40 @@ export default async function PaginaSolucion({ }) .filter((b): b is NonNullable => b !== null); + /** Migas de pan también como dato estructurado: solo rutas que existen. */ + const migas = { + "@context": "https://schema.org", + "@type": "BreadcrumbList", + itemListElement: [ + { "@type": "ListItem", position: 1, name: "Inicio", item: SITIO_URL }, + { "@type": "ListItem", position: 2, name: "Soluciones", item: `${SITIO_URL}/soluciones` }, + { + "@type": "ListItem", + position: 3, + name: industria.nombreLargo, + item: `${SITIO_URL}/soluciones/${industria.slug}`, + }, + ], + }; + return ( <> - {/* ---------- 1. Encabezado ---------- */} -
    -
    - -
    -