Debajo del hero, nueva sección #demo: título + placeholder de vídeo 16:9
(sustituible por iframe/video cuando lo haya) + CTA "Empezar ahora". El botón
"Ver una demo en vivo" del hero apunta aquí (se movió el id #demo del mock del
hero a esta sección). Copy añadido a COPY-GUIDE §2.
Aplicado en public/b2b.html (servido en / y /b2b) y en mvp/b2b/index.html.
Nota: ambos ficheros ya tenían el hero divergido (public con copy más nuevo);
no toco ese copy aquí.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Integra el esquema "reformix-full" del equipo de forma ADITIVA, sin tocar los
enums ni columnas existentes de la app (una sola DB, Drizzle es el dueño):
- Enums nuevos: estado_wa, canal_contacto, canal_origen, resultado_contacto,
rol_mensaje, job_tipo, job_estado, nivel_calificacion, visita_estado.
- Tablas nuevas: conversacion_whatsapp, intentos_contacto, lead_calificacion
(score 0-100 + nivel A/B/C/D), visitas, worker_jobs (cola async de los
workers de fotos/render/presupuesto). Referencian nuestros leads/users/tenants.
- Columnas nuevas en leads (nullable, las rellena el bot/Luisa): estado_wa,
canal_origen, espacio, rango_m2, estilo, presupuesto_declarado, viable,
fotos_solicitadas_at.
- Migración 0010 + db-schema/schema.sql regenerado.
El bot/n8n escribe estas tablas en la DB única y usa nuestros leads (creados
solo desde el form web). Pendiente: alinear valores de lead_estado/pipeline_stage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- prepararLogo: el logo (data URI base64, URL http o ruta /public con APP_URL)
se adjunta como imagen inline con Content-ID y se referencia con cid:, que
es la forma fiable de mostrarlo en Gmail/Apple Mail/Outlook (el data URI
directo lo bloquea Gmail). Fallback al nombre de la empresa si no hay logo.
- El acento (barra, botón) ya usa el color de marca del tenant (resolveTheme).
Probado: envío real con el logo de Reformas Ejemplo embebido y tema pizarra.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reescribe los emails del funnel (entrega del presupuesto + enlace al
formulario) con plantilla HTML mobile-first de una columna, dark mode, botón
"bulletproof" en tabla, tipografías de sistema y versión en texto plano.
- mailer.ts: construirEmailHtml/construirEmailText compartidos; acento en el
color de marca del reformista (resolveTheme) + logo si es URL absoluta; copy
con jerarquía y CTA orientado a resultado.
- finalizar.ts: pasa la marca del tenant y un CTA de contacto (wa.me del
reformista o mailto) al email de entrega.
- actions.ts: pasa la marca al email con el enlace al formulario.
- COPY-GUIDE §6.b: asuntos (+alternativas), preheader, headline y cuerpo
mejorados.
Probado: render visual (light) de ambos emails y envío real por SMTP.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Acerca el cálculo a tarifas de mercado sin rehacer el modelo lineal €/m²:
- Impermeabilización como partida propia en zonas húmedas (cocina/baño/integral)
- Extras fijos que no escalan con m²: boletín (siempre), tuberías (piso anterior
a 2000) y cambio de distribución (mover inodoro/ducha/bañera)
- Intensidad por tipo en fontanería/electricidad (baseline cocina) para que un
integral no escale como un baño
- Factor de zona por provincia en tramos (Madrid/BCN 1.40, islas 1.30, capitales
1.20, rural 0.85, resto 1.00)
- 2 preguntas nuevas en el formulario del cliente para disparar los extras
- Panel de precios: campo de impermeabilización + sección de extras fijos
- Seed recalibrado (mano de obra, extras, catálogo suelo/pared)
- Migración 0009 (leads.anterior_a_2000, leads.cambio_distribucion, pricing_config.extras)
- Tests del motor ampliados (impermeabilización, extras, intensidad por tipo)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- agruparPorZona (lib/funnel/fotos.ts): helper puro que agrupa fotos
(antes/después) y notas por zona, con fallback al tipo del lead. 5 tests.
- getLead trae también lead_notas.
- LeadFotosGaleria: galería por zona (Antes/Después + notas) que sustituye el
grid plano de la ficha del panel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Llamada (/llamada): "Llamar ahora" dispara la llamada saliente de Retell;
"Programar" registra fecha/hora. Tras pedir, ofrece enviar por email el
enlace al formulario para subir las imágenes.
- WhatsApp (/whatsapp): arranca la conversación vía webhook al flujo externo
(con el teléfono del lead) y pide confirmación; al confirmar, agradece y
sigue por WhatsApp.
- actions: pedirLlamada, enviarEnlaceFormularioEmail, iniciarWhatsapp,
confirmarWhatsapp.
Verificado en navegador: las 3 pantallas y sus transiciones.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Paso intermedio /solicitud/[id]: el cliente elige llamada, WhatsApp o
formulario (crearLead ahora redirige aquí, no a /fotos).
- /formulario: FormularioZonas permite añadir varias zonas, cada una con tipo,
m², acabado, notas y fotos; /fotos queda como redirect.
- guardarDetallesYFotos: guarda fotos (antes, por zona) y notas (por zona),
agrega los campos del lead (m² suma, tipo único o 'integral', calidad más
alta, tasteText concatenado) para el presupuesto orientativo inmediato, y
señala perfilCompleto al flujo externo.
- Elimina FotosUploader (sustituido por FormularioZonas).
Verificado en navegador: 2 zonas → presupuesto al instante + notas por zona +
evento de perfil en DB.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
api-docs/README.md: método/URL, auth Bearer, esquema del cuerpo (items
foto/texto, zona, momento, flags), respuesta y errores (401/404/422), ejemplos
curl de los 5 casos, y los payloads de los 3 webhooks salientes (perfil,
entrega WhatsApp, arranque WhatsApp) con cómo el flujo externo devuelve las
"después" por el mismo EP.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
POST /api/leads/:id/ingesta (Bearer FUNNEL_API_KEY): acepta items foto/texto
etiquetados por zona y momento, más flags perfilCompleto y finalizar.
- ingesta-schema.ts: zod del cuerpo (union discriminada foto|texto), exportado
para test; rechaza llamadas vacías.
- route.ts: auth 401, valida lead (404), inserta fotos (orden continúa el máx)
y notas, traza fotos_subidas; perfilCompleto→señalarPerfilCompleto,
finalizar→finalizarYEntregar.
- 10 tests del schema. Verificado por HTTP: 401/200/422/404 y finalizar genera
el pdf_url y avanza el lead a whatsapp_entregado.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- webhooks.ts: postWebhook best-effort + señalarGeneracionPerfil,
notificarFlujoWhatsapp (entrega) e iniciarConversacionWhatsapp (arranque).
- perfil.ts: señalarPerfilCompleto arma el JSON por zona (notas + fotos
antes/después) y lo manda al flujo externo; deja traza render_generado.
- finalizar.ts: finalizarYEntregar construye el PDF, persiste pdf_url, envía
el email (siempre) y la señal de WhatsApp, y avanza el lead a entregado.
- orchestrator: comentario en Paso 7 apuntando a la entrega real en finalizar.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- mailer.ts: transport nodemailer perezoso desde env; enviarPresupuestoEmail
(adjunta el PDF) y enviarEnlaceFormulario. Best-effort: sin SMTP configurado
o ante error devuelven false sin lanzar.
- COPY-GUIDE §6.b: copy literal de ambos emails al cliente final.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- build-presupuesto.ts: construirPresupuestoPdf(leadId) agrupa fotos y notas
por zona (fallback al tipoReforma del lead), convierte las imágenes con
resolverImagenPdf y arma el PDF. Carga el lead por id sin scoping (uso
interno desde el route del panel y desde la finalización pública).
- tenant-queries: getTenantPerfilById(tenantId) sin auth; getTenantPerfil lo
reutiliza con el tenant de la sesión.
- PresupuestoDoc: prop zonas + sección "Imágenes de tu reforma" (antes/después
lado a lado + notas por zona).
- route del panel: refactor para reutilizar construirPresupuestoPdf (DRY),
manteniendo getLead como guardia de auth/404.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Variables nuevas (todas opcionales vía zod, sin romper build/demo):
- FUNNEL_API_KEY: clave Bearer del EP de ingesta.
- SMTP_* + EMAIL_FROM: envío de email del presupuesto/enlace.
- PERFIL/WHATSAPP/WHATSAPP_START webhook URLs: señales al flujo externo.
- APP_URL: base para enlaces absolutos.
Helpers emailConfigurado()/perfilWebhookConfigurado()/whatsappWebhookConfigurado()/
whatsappStartConfigurado(). nodemailer como dep directa (stack: Email SMTP).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Prepara el modelo de datos para la ingesta multicanal del perfil del lead:
- lead_fotos: columnas momento (foto_momento antes/despues, default antes) y
zona (tipo_reforma, nullable con fallback al tipoReforma del lead).
- lead_notas: tabla append-only de datos de texto por zona (ej. "suelo
premium"), con origen (ep|funnel|panel) para auditar quién los aportó.
- Migración 0008 + regenerado db-schema/schema.sql.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Genera db-schema/schema.sql (DDL completo: 12 enums, 14 tablas, FKs e
índices) a partir del schema Drizzle, para que el equipo pueda consultar y
proponer cambios sobre el modelo de datos sin leer las migraciones una a una.
- db-schema/schema.sql: foto del esquema actual (drizzle-kit export)
- db-schema/README.md: qué es cada tabla, cómo cambiar el modelo y cómo
regenerar/levantar la BD desde este SQL
- package.json: script db:export para regenerar la foto
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Incluye una imagen WebP optimizada (48caf530-fa5f-476b-8120-195cfce7a9ec.webp) y una imagen PNG de ejemplo de reformas (reformas-ejemplo.png) para usar en la galería de trabajos y demostraciones del funnel.
Integra el agente de voz de Retell (Arquitectura A para la demo): tras la
pre-llamada, el orquestador lanza una llamada saliente real con override de
agente y dynamic variables (empresa + cliente), mientras el render y el
presupuesto se siguen generando con los datos del formulario.
- src/lib/env.ts: esquema zod de RETELL_* (claves opcionales -> sin ellas el
funnel sigue en modo simulado y el build no se rompe)
- src/lib/voice/retell.ts: cliente fino con fetch a create-phone-call
(sin nueva dependencia), normalización E.164 y builder de variables
- orchestrator: dispara la llamada best-effort y guarda el callId
- tests del normalizador de teléfono y del builder de variables
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Añade bajo el título un párrafo orientativo: el precio final se fija tras
la visita gratuita y la estimación se basa en datos estadísticos ajustados
para acercarse lo máximo posible al importe definitivo.
- Añade una sección con el render del resultado y una descripción generada a
partir de los materiales (materialesRender) y el estilo de la llamada, con
fallback elegante cuando faltan datos.
- @react-pdf solo incrusta PNG/JPEG: convierte con sharp los WebP/SVG (render
y logo) que antes se descartaban en silencio dejando el PDF sin imagen. El
render va a JPEG redimensionado (PDF ~360 KB en vez de ~2,7 MB) y el logo a
PNG para conservar transparencia.
- Fija sharp como dependencia directa (ya venía como transitiva de Next).
- Copy nuevo añadido primero a COPY-GUIDE.md (sección entrega del presupuesto).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Los tres uploaders del panel (logo, "quiénes somos" y galería) ahora abren
un recorte interactivo (zoom + encuadre) y reescalan en el navegador antes
de subir: logo y "quiénes somos" a máx. 500 px, galería a máx. 1200 px,
reencodando a WebP (calidad 0.82). El SVG del logo se sube tal cual para no
rasterizar el vector. Esto reduce el peso de los data URIs base64 que se
guardan en Postgres y se inlinean en el funnel.
Nueva dependencia react-easy-crop: librería de recorte ligera, sin estado
global, compatible con React 19; el reescalado y reencodado se hacen con
canvas nativo (lib/image/crop.ts), sin dependencias extra.
Sustituye la paleta negra/azul B2C del panel del reformista por el verde
de marca, neutros cálidos y titulares en Instrument Serif de la landing B2B.
Añade tokens --color-primary-*, --color-stone-50 y --font-display al @theme.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Vista de leads en tarjetas + tabla con toggle (tarjetas por defecto, preferencia persistida)
- Galería de trabajos: gestión en /panel/galeria y bloque público en el funnel
- Selector de tema por reformista (presets + color de marca opcional) aplicado a la landing
- Login y registro rediseñados a pantalla partida 50/50 con foto de reforma
- Enlace "Entrar" funcional en la cabecera del funnel; elimina Navbar muerto
- Unifica tipografía y botones del panel con los tokens de la landing
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Panel/empresa: title y meta description SEO personalizables; foto, texto y
años de experiencia para el bloque "Quiénes somos" (toggle on/off).
- Funnel por slug: metadata SEO desde el tenant, bloque "Quiénes somos" y
testimonios servidos desde DB (sustituye los hardcodeados).
- Flujo de opiniones: el reformista solicita la opinión desde la ficha de un
lead ganado; el cliente la deja en un funnel dedicado /opinion/[id] con
estrellas + texto + fotos; entra como pendiente y el reformista la modera
(publicar/ocultar/eliminar) en /panel/opiniones antes de mostrarla.
- Schema: columnas SEO/about en tenants, testimonioSolicitadoAt en leads,
enum testimonio_estado, tablas testimonios + testimonio_fotos (migración 0006).
- Seed: opiniones demo (2 publicadas, 1 pendiente) y contenido "Quiénes somos".
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- / y /b2b sirven la landing B2B estática (rewrites beforeFiles)
- /{slug} resuelve el funnel del reformista (app/[slug]/page.tsx) con
branding propio (TenantBrand) y atribución de leads por tenant
- crearLead(slug) y páginas /solicitud usan el tenant del lead
- Panel: edición del slug del funnel + URL pública en /panel/empresa
- Helper de slugs reservados para evitar colisiones con rutas reales
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Reemplaza public/b2b.html (bundle estático viejo) por la versión con
reveals al scroll, hover-lift, count-up de stats e imágenes antes/después.
Assets servidos desde /b2b-assets para evitar colisiones. Sirve en el
dominio único reformix.dv3.com.es vía el rewrite /b2b existente.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sirve index.html + assets (fuentes woff2 e imágenes webp) para
desplegar la landing como app estática en Dokploy.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Desempaqueta el bundle autocontenido a index.html + assets servidos
(17 fuentes woff2, pares antes/después webp). Añade scroll-reveal sutil
con stagger, hover-lift en tarjetas, counters animados en stats y dos
imágenes reales: render en la burbuja de WhatsApp y comparativa
antes/después en "Cómo funciona". Respeta prefers-reduced-motion.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>