feat: sitio comercial FSPM con micro CRM interno

Sitio público orientado a venta consultiva e implementación de las
recomendaciones de la auditoría de marca y mercado:

- Inicio con tres rutas (industria, riesgo, cumplimiento) y clasificador
  de riesgo en el héroe, contra el hallazgo de que el visitante tenía que
  inferir qué solución le correspondía.
- Cinco páginas por segmento prioritario y catálogo comparable por clase
  de fuego, capacidad y agente.
- Los claims sin evidencia por modelo quedan aislados y se presentan como
  compromiso de documentación, nunca como característica del producto.
- SEO técnico completo: metadatos por página, sitemap derivado de los
  datos, JSON-LD, iconos y tarjeta social generadas.

Micro CRM:

- Captura por formulario embebido y por widget de WhatsApp que registra
  antes de abrir la conversación.
- Atribución de primer toque que sobrevive a la navegación.
- Deduplicación de contacto; un mismo contacto acumula oportunidades.
- Motor de asignación por reglas con reparto por turno de respaldo.
- Panel con dos roles de alcance distinto, tablero analítico, bitácora,
  pedidos y reglas. No enlazado desde el sitio público y con noindex.

Empaquetado en Docker multietapa con la base de demostración horneada en
la imagen y el volumen de datos separado, para que los leads capturados
sobrevivan a los redespliegues.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
Uriel Jareth
2026-08-28 00:07:20 -06:00
co-authored by Claude Opus 5
parent 122e06a4bb
commit 01b817d098
649 changed files with 26690 additions and 0 deletions
+162
View File
@@ -0,0 +1,162 @@
# Contrato del proyecto — léelo completo antes de escribir código
Sitio + micro CRM para **FSPM (Fire Service Plus México)**, protección contra incendios.
Es una **demo comercial** de Consultoría E3: tiene que verse y funcionar como producción.
Raíz: `H:\MegaSync\Proyectos\fspm\fspm-web`
## Stack ya instalado — no cambies versiones, no agregues dependencias
Next.js 15.5.4 (App Router) · React 19 · TypeScript estricto · Tailwind CSS **v4**
(sin `tailwind.config`, los tokens viven en `@theme` dentro de `src/app/globals.css`) ·
Prisma 6.16 + SQLite · zod 4 · jose · bcryptjs.
Si crees que necesitas una dependencia nueva, **no la instales**: resuélvelo con lo que hay
o dilo en tu reporte final.
## Regla de propiedad de archivos — la más importante
Trabajas en paralelo con otros agentes sobre el mismo árbol. **Solo puedes crear y editar
los archivos de tu lote.** Si necesitas cambiar un archivo ajeno, no lo toques: descríbelo
en tu reporte final y el orquestador lo integra.
Archivos del orquestador, **de solo lectura para ti**:
```
src/app/globals.css src/app/layout.tsx
src/data/*.ts src/lib/db.ts
src/lib/utm.ts src/lib/crm/constantes.ts
src/lib/leads/esquema.ts src/components/marca/RielClases.tsx
src/components/site/Encabezado.tsx src/components/site/Pie.tsx
prisma/schema.prisma package.json next.config.ts .env
```
## Sistema de diseño — deriva todo de aquí, no improvises color ni tipografía
Los tokens están en `src/app/globals.css` bajo `@theme`. Úsalos como clases de Tailwind
(`bg-ink`, `text-muted`, `border-rule`, `text-signal`) o como `var(--color-…)`.
**Dos mundos, y el contraste entre ellos es la idea:**
- **Oscuro = el riesgo y la operación.** `bg-ink` (#0C1116, acero-humo azulado), paneles
`bg-ink-2` / `bg-ink-3`, filetes `border-rule`.
- **Claro = la evidencia y el documento.** `bg-paper` (#EEF1F3, blanco de plano de
ingeniería — **no** es crema), texto `text-deep`.
Alterna secciones entre los dos mundos. Es la traducción visual del diagnóstico del
estudio: convertir autoridad técnica en confianza comprable.
**Señal:** `text-signal` / `bg-signal` = #DB042B, el rojo real de FSPM extraído de su CSS.
Es señal, no relleno: úsalo en el CTA, en el riel de clases y en acentos puntuales.
`text-ember` (#F2B705) es el amarillo del traje de bombero de su propia fotografía:
**solo para estados de riesgo, advertencia y atención**, nunca decorativo.
**Tipografía**, ya cargada en el layout raíz:
- `font-display` — Archivo 800/900. Encabezados. Versalitas y tracking cerrado. Es
rotulación de equipo industrial.
- `font-sans` — IBM Plex Sans. Cuerpo.
- `font-mono` — IBM Plex Mono. **La capa de datos**: etiquetas, especificaciones, folios,
cifras, códigos, UTMs. Todo dato numérico lleva `font-variant-numeric: tabular-nums`
(ya aplicado a `.font-mono` y a `table`).
Radios casi rectos: `rounded-[3px]` / `rounded-[2px]`. Es una herramienta, no una app de consumo.
**Utilidades ya escritas — reúsalas en vez de reinventar:** `.shell` `.shell-wide`
`.eyebrow` `.btn .btn-signal .btn-ghost .btn-deep .btn-sm` `.field .field-lite
.field-label .field-error` `.panel .panel-lite` `.spec .spec-cell .spec-key .spec-val`
`.hair .hair-lite` `.grid-bg .grid-bg-lite` `.hazard` `.badge .badge-signal .badge-ember
.badge-ok` `.link` `.skip` `.anim-rise` `.anim-ignite` `.reveal`.
### Elemento firma: el riel de clases de fuego
`src/components/marca/RielClases.tsx` — cinco celdas fijas `A B C D K`, se encienden solo
las que ese producto/solución/lead cubre. Aparece en el sitio **y** en el panel. Úsalo
siempre que haya clases de fuego; no dibujes otra variante.
### Movimiento
Contenido. Una secuencia orquestada en el héroe y revelados discretos al hacer scroll con
`.reveal` + IntersectionObserver. Nada de efectos dispersos: delatan trabajo automático.
`prefers-reduced-motion` ya está respetado globalmente.
## Piso de calidad, no negociable
- **Responsive real** desde 320px. Prueba mentalmente 320 / 768 / 1280 / 1920.
- **Compatibilidad WebKit (Safari e iOS).** Es requisito explícito del cliente:
usa `-webkit-backdrop-filter` junto a `backdrop-filter`, `-webkit-appearance: none` en
controles, `-webkit-overflow-scrolling: touch` en scroll horizontal, `-webkit-tap-highlight-color`
en elementos táctiles, y evita `gap` en `inline-flex` sin fallback. Nada de `100vh` a secas
en móvil: usa `100dvh` con `100vh` de respaldo.
- **Accesibilidad:** un solo `<h1>` por página, jerarquía correcta, `aria-label` en
controles sin texto, foco visible (ya en `:focus-visible`), contraste suficiente,
navegación completa con teclado.
- Nada de texto dentro de imágenes. `next/image` con `width`/`height` o `fill` + `sizes`.
- Sin `any`. Sin `console.log` en el código final. `npx tsc --noEmit` debe pasar.
- Español de México, acentuación correcta, tono técnico y sereno.
## Honestidad del contenido — regla dura del cliente
El estudio de marca concluyó que los claims del sitio original (*"no caduca"*,
*"20 años de garantía"*, *"no requiere recarga"*) **no pueden presentarse como universales**.
`src/data/catalogo.ts` ya los aisló en `producto.claimsPorVerificar`.
- **Nunca** presentes un claim de `claimsPorVerificar` como hecho.
- Muéstralos en un bloque aparte, honesto, del tipo "Qué documentamos antes de cotizar".
Eso **es** el argumento de venta: la competencia promete, FSPM documenta.
- No inventes cifras, certificados, precios, casos de éxito ni testimonios. Si un dato no
está en `src/data/`, no existe.
- Los logotipos de clientes en `src/data/empresa.ts` se muestran **sin** atribuirles
testimonio ni resultado: el estudio pide confirmar la relación antes de usarlos como caso.
- Los casos de aplicación deben estar rotulados como ilustrativos.
## Datos disponibles (solo lectura)
- `src/data/catalogo.ts` — 8 categorías, 22 productos. `CATEGORIAS`, `PRODUCTOS`,
`getCategoria`, `getProducto`, `productosDeCategoria`.
- `src/data/industrias.ts` — los 5 segmentos prioritarios del estudio. `INDUSTRIAS`, `getIndustria`.
- `src/data/clases-fuego.ts` — `CLASES`, `CLASES_INFO`, `parseClases`, `serializeClases`.
- `src/data/empresa.ts` — `EMPRESA`, `NAV_PRINCIPAL`, `NAV_PIE`, `CLIENTES`, `PRENSA`,
`NORMAS`, `SITIO_URL`, `WHATSAPP`, `AVISO_DEMO`.
- `src/lib/crm/constantes.ts` — dominio del CRM. SQLite no guarda enums: **todo estado sale de aquí.**
- `src/lib/leads/esquema.ts` — contrato de captura de leads y helpers de normalización.
- `src/lib/utm.ts` — `capturarAtribucion()`, para clientes.
Assets en `public/media/`: `marca/`, `producto/`, `clientes/`, `prensa/`, `iconos/`, `foto/`.
**Usa solo archivos que existan**: verifícalo con `ls public/media/<carpeta>`.
## Rutas del proyecto
Público, dentro del grupo `src/app/(sitio)/`:
`/` · `/soluciones` · `/soluciones/[industria]` · `/catalogo` · `/catalogo/[categoria]` ·
`/producto/[slug]` · `/cumplimiento-nom-002` · `/evidencia` · `/casos` ·
`/preguntas-frecuentes` · `/nosotros` · `/contacto` · `/aviso-de-privacidad`
Panel, en `src/app/admin/`:
`/admin/login` · `/admin` · `/admin/oportunidades` · `/admin/oportunidades/[id]` ·
`/admin/contactos` · `/admin/contactos/[id]` · `/admin/pedidos` · `/admin/analitica` ·
`/admin/equipo` · `/admin/reglas`
**El panel no se enlaza desde ninguna parte del sitio público.** Sin enlace en el
encabezado, en el pie, ni en el sitemap. Se llega escribiendo la URL. Además va con
`robots: { index: false, follow: false }` en su metadata.
## Comandos
```
npm run dev # servidor de desarrollo
npx tsc --noEmit # verificación de tipos: tiene que pasar limpio
npx prisma db push # aplicar el esquema
npm run db:seed # sembrar datos de demo
```
No corras `npm install`. No corras `git commit`. No borres archivos ajenos.
## Tu reporte final
Termina con, en este orden:
1. Archivos creados o modificados, con una línea de qué hace cada uno.
2. Resultado literal de `npx tsc --noEmit` sobre tus archivos.
3. Lo que dejaste pendiente o lo que necesitas de otro lote.
4. Cualquier dato del sitio original que te pareció inconsistente o sin sustento.