Desplegada y verificada de punta a punta contra el dominio público, en Chromium y en WebKit a 375px: sitio, catálogo filtrado, acceso al panel, persistencia de sesión y captura de leads con atribución de campaña. No quedó como recurso de Coolify, y está documentado por qué: la API de esa instancia no expone creación de aplicaciones (las rutas /applications/* responden 404 con cuerpo vacío y /openapi.yaml devuelve el HTML del panel), y al crear un servicio por /services la tarea de despliegue quedó colgada sin escribir nada en disco. El servidor está sano; el problema es la instancia. El contenedor corre en el Docker del LXC 102, en la red coolify, con las etiquetas de Traefik que replican el patrón de las apps que ya sirven con TLS ahí, y con coolify.managed=false para que nadie lo confunda con un recurso del panel. La imagen se construye dentro del LXC desde el repo público de Gitea. El costo de esta ruta está escrito en la documentación: un push no redespliega. Se agregó el procedimiento de tres comandos para publicar una versión nueva sin tocar el volumen, y la vía para migrar a app git-based si se consigue el acceso al panel web. Se documenta también una carrera que provocó un diagnóstico equivocado: el acceso usa una acción de servidor, y pulsar el botón antes de que React hidrate no hace nada ni da error. La primera verificación fallaba solo por el dominio público y funcionaba contra Traefik directo, lo que parecía señalar a Cloudflare. No era: los chunks son idénticos por ambos caminos y la hidratación es igual. Era la espera de la prueba. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
101 lines
4.5 KiB
Markdown
101 lines
4.5 KiB
Markdown
# FSPM — sitio comercial y micro CRM
|
|
|
|
**En línea: https://fspm-demo.urieljareth.org** · Panel en `/admin/login`, que
|
|
no se enlaza desde el sitio a propósito. Cómo está desplegado y cómo publicar
|
|
una versión nueva: [`docs/DESPLIEGUE.md`](docs/DESPLIEGUE.md).
|
|
|
|
Demostración construida por **Consultoría E3** para Fire Service Plus México.
|
|
Sitio público orientado a venta consultiva, con un CRM interno que registra cada
|
|
envío de formulario y de widget de WhatsApp como oportunidad, con su atribución
|
|
de campaña.
|
|
|
|
No es un maquetado: los formularios escriben en base de datos, el panel lee de
|
|
ella, y las reglas de asignación reparten los leads entre vendedores.
|
|
|
|
## De dónde salió el contenido
|
|
|
|
El sitio implementa las recomendaciones de una auditoría de marca y mercado
|
|
previa. Los hallazgos que dirigieron el diseño:
|
|
|
|
| Hallazgo de la auditoría | Cómo se resuelve aquí |
|
|
|---|---|
|
|
| Propuesta demasiado amplia, ocho categorías sin rutas | Inicio con tres rutas —industria, riesgo y cumplimiento— y cinco páginas por segmento prioritario |
|
|
| El visitante debe inferir qué solución le toca | Clasificador de riesgo en el héroe: elige operación y clases de fuego, y el sitio lo enruta |
|
|
| Claims sin condiciones ni evidencia por modelo | Aislados en `claimsPorVerificar` y presentados como compromiso de documentación, nunca como característica |
|
|
| Sin meta description ni Open Graph | Metadatos por página, sitemap derivado de los datos, JSON-LD y tarjeta social generada |
|
|
| Conversión poco consultiva | El formulario pide lo que un ingeniero necesita para cotizar, no "solicita información" |
|
|
| Sin forma de medir efectividad comercial | Atribución de primer toque que sobrevive a la navegación, y analítica por campaña en el panel |
|
|
|
|
Los datos de producto provienen de la extracción del sitio público de FSPM. Lo
|
|
que la fuente no sostiene está marcado como pendiente de verificar; nada se
|
|
inventó para rellenar.
|
|
|
|
## Stack
|
|
|
|
Next.js 15 App Router · React 19 · TypeScript estricto · Tailwind CSS v4 ·
|
|
Prisma 6 + SQLite · sesiones propias con `jose` · Playwright para pruebas.
|
|
Sin librería de gráficas: el tablero dibuja SVG a mano.
|
|
|
|
## Arrancar en local
|
|
|
|
```bash
|
|
npm install
|
|
cp .env.example .env # ajusta AUTH_SECRET
|
|
npx prisma db push
|
|
npm run db:seed # datos de demostración
|
|
npm run dev
|
|
```
|
|
|
|
Sitio en `http://localhost:3000`. El panel en `/admin/login` — **no está
|
|
enlazado desde el sitio público**, es intencional.
|
|
|
|
Las credenciales del seed se imprimen al final de `npm run db:seed`. En la
|
|
pantalla de acceso hay además entrada rápida por perfil para la demostración,
|
|
que se apaga con `DEMO_ACCESO_RAPIDO=false`.
|
|
|
|
## Estructura
|
|
|
|
```
|
|
src/app/(sitio)/ páginas públicas
|
|
src/app/admin/ panel comercial, con noindex y fuera del sitemap
|
|
src/app/api/leads/ captura: deduplica contacto, crea oportunidad, asigna
|
|
src/data/ catálogo, industrias, clases de fuego, datos de empresa
|
|
src/lib/crm/ dominio, sesiones, consultas, acciones, asignación
|
|
src/components/marca/RielClases.tsx el elemento firma del diseño
|
|
tests/ Playwright, incluidos dos proyectos WebKit
|
|
docs/CONTRATO.md sistema de diseño y reglas de contenido
|
|
docs/DESPLIEGUE.md variables, Docker y despliegue
|
|
```
|
|
|
|
## Cómo funciona la captura
|
|
|
|
1. El visitante llega con parámetros de campaña. `src/lib/utm.ts` guarda el
|
|
**primer toque** y no lo sobreescribe.
|
|
2. Navega. Envía el formulario en una ficha de producto, ya sin parámetros en
|
|
la URL.
|
|
3. `POST /api/leads` deduplica el contacto por correo o teléfono normalizado,
|
|
crea una **oportunidad nueva** —un mismo contacto acumula varias— y guarda
|
|
la campaña del primer toque junto con el producto y la URL exactos.
|
|
4. Las reglas de asignación eligen vendedor; si ninguna coincide, se reparte por
|
|
turno entre los activos.
|
|
5. El widget de WhatsApp hace lo mismo **antes** de abrir la conversación, así
|
|
el lead existe aunque la persona nunca mande el mensaje.
|
|
|
|
## Pruebas
|
|
|
|
```bash
|
|
npm run test:e2e # todos los proyectos
|
|
npm run test:webkit # solo Safari e iOS
|
|
```
|
|
|
|
Cubren rutas y metadatos, responsividad de 320 a 1440 sin desborde horizontal,
|
|
el circuito de captura con atribución, el aislamiento entre roles del panel y
|
|
accesibilidad básica.
|
|
|
|
## Alcance
|
|
|
|
Los datos del panel son ficticios. Los logotipos de clientes se muestran sin
|
|
atribuirles testimonio ni resultado: la auditoría pide confirmar la relación
|
|
antes de usarlos comercialmente. La información normativa es de referencia y no
|
|
sustituye dictamen técnico ni jurídico.
|