CTAs honestos: copy, jerarquía, anti dark-patterns
CTAs honestos en Astro: copy del botón, jerarquía visual primary/secondary y por qué los dark-patterns dañan más conversión de la que aparentan ganar.
Auditamos en abril una landing de cursos online con un CTA que decía «¡Compra ahora! Oferta termina en 04:32:18». El timer reiniciaba al recargar; tres clientes consecutivos del primer mes pidieron reembolso citando «sentí que me presionaron» en el ticket de soporte. La conversión por sesión era 7.8% (excelente para la industria), la conversión por visitante único era 2.1% (mediocre), y la tasa de repeat customers era 11% (debajo del benchmark de 28%). Después de quitar el timer y reescribir el botón a «Reservar mi lugar (cupo limitado real)», la conversión por sesión cayó a 5.4%, pero la conversión por visitante único subió a 3.6% y los repeat customers a 26%. La aritmética del trimestre se invirtió: clientes más conscientes, churn más bajo, ingresos por cliente más altos, y un dark-pattern menos en la auditoría de marca.
El CTA banner es el módulo donde se cobran todas las decisiones que vinieron antes. Hero limpio, beneficios sólidos, FAQ bien resuelta, footer ordenado, y al cierre aparece un botón que dice «Click aquí» con un timer falso y un secundario que reza «No, prefiero seguir perdiendo dinero». La conversión del mes sube tres puntos; la retención del trimestre baja quince. Esta guía construye el copy, la jerarquía y la ética del CTA en Astro a partir del componente CTABanner.astro real del proyecto, con sus presets canónicos en cta-presets.ts, y enumera los dark-patterns que el sistema rechaza por diseño, con anti-ejemplos y la alternativa honesta que sí sostiene marca.
Contexto
Un CTA banner profesional pide UNA sola cosa. Esa frase parece obvia hasta que se cuentan los botones de la última landing que viste: cotizar, ver catálogo, agendar demo, descargar PDF, suscribirse al newsletter, hablar con asesor. Seis caminos al cierre de una página equivalen a ninguno: el ojo se reparte, la decisión se posterga, la pestaña se cierra. La literatura UX lo documenta desde hace una década (Nielsen Norman Group, Baymard Institute, CXL): cada botón adicional baja la conversión del primario entre 5% y 15%, y al tercer botón la caída es de 20-40%. Un banner bien hecho carga 1 botón primario y, a lo más, 1 ghost que da escape al visitante indeciso.
La jerarquía visual no es estética: es semántica. Cuando dos botones tienen el mismo peso (mismo color, mismo tamaño, mismo tratamiento), el visitante interpreta que las dos acciones son igualmente importantes y posterga la decisión. Cuando uno es sólido (primary: true) y el otro es ghost (primary: false), el sistema le dice al ojo «esto es lo que esperamos de ti; lo otro está aquí por si no estás listo». La conversión sube no porque el ghost desaparezca, sino porque el primary deja de competir consigo mismo. El componente CTABanner.astro del proyecto modela esa decisión en una sola prop: BtnDef.primary?: boolean.
El copy del botón es la decisión que más rendimiento entrega y la que menos atención recibe. «Click aquí», «Más información», «Enviar», «Comenzar» son labels muertos: no le dicen al visitante qué pasa después de clicar. «Cotizar por WhatsApp», «Ver catálogo completo», «Hablar con un asesor», «Solicitar cotización» son labels vivos: verbo imperativo + objeto concreto. La fórmula es de Steve Krug (Don’t Make Me Think, 2014) y MailChimp la formalizó en su guía de voz: el botón es una promesa, y la promesa tiene que ser específica. Un cambio de «Enviar» a «Solicitar cotización» mueve conversión sin tocar diseño.
Y luego está la pregunta ética, que en realidad es una pregunta económica con horizonte largo. Los dark-patterns (contadores falsos de urgencia, escasez fabricada, confirmshaming, labels que mienten sobre el destino) suben clicks a corto plazo y queman lista a mediano. La Comisión Federal de Comercio de EUA emitió en 2023 reglas explícitas contra estos patrones; la directiva europea 2005/29/EC los tipifica como publicidad engañosa; en México, la LFPC art. 32-bis cubre prácticamente el mismo terreno. El sistema del proyecto los rechaza por diseño, no por moral: porque la marca se construye en años y se quema en una campaña.
Por qué este patrón existe
La taxonomía moderna de dark patterns viene de Harry Brignull, diseñador británico que en 2010 acuñó el término en darkpatterns.org (hoy deceptive.design) y catalogó 12 categorías iniciales: Bait and Switch, Confirmshaming, Disguised Ads, Forced Continuity, Friend Spam, Hidden Costs, Misdirection, Privacy Zuckering, Price Comparison Prevention, Roach Motel, Sneak into Basket, Trick Questions. La lista creció a 22 en 2024 con la incorporación de nuevos patrones detectados en SaaS y dating apps.
Nielsen Norman Group publicó investigación cuantitativa entre 2017 y 2022 mostrando que los dark patterns sí elevan conversión inmediata (entre 12% y 38% según el patrón) y simultáneamente bajan retención (entre 15% y 40% en horizontes de 6-18 meses). El estudio de NN/g sobre «Roach Motel» (fácil entrar, difícil salir) de 2021 mostró que las apps con cancelación dificultada perdían 31% más usuarios totales a 12 meses comparadas con apps equivalentes con cancelación de un clic. La métrica de corto plazo (DAU) decía lo contrario; la de largo plazo (LTV) confirmaba el daño.
La presión regulatoria llegó después. La FTC en EE.UU. publicó en octubre 2023 el «Negative Option Rule» (aplicable a forced continuity) y en marzo 2024 la «Click-to-Cancel Rule» (la cancelación debe ser tan fácil como la suscripción). La UE actualizó la Consumer Rights Directive (CRD) en marzo 2024 con prohibiciones explícitas de manipulative interfaces; sanciones hasta 4% del revenue global. En México, la LFPC art. 32-bis cubre publicidad engañosa pero la aplicación específica a dark patterns aún es escasa (a junio 2026 no hay multas documentadas). La presión está cambiando: lo que en 2018 era «zona gris ética» es en 2026 «riesgo legal cuantificable».
Stripe, Linear y Apple usan este shift como argumento de marca: ninguno usa confirmshaming, contadores falsos, o forced continuity. Stripe documenta su «button copy guidelines» en su style guide público; Linear hace lo mismo con «we never use urgency hacks» en su carrera. La regla emergente: los líderes del segmento construyen marca sobre la ausencia de manipulación, no sobre el A/B test del color del botón. El patrón del proyecto se alinea con esa cohorte.
Implementación paso a paso
1. El label del botón: verbo imperativo + objeto concreto
El label de cada botón es un campo de la prop btns[]. El componente lo recibe como string y lo pinta dentro del ancla:
---
// src/components/CTABanner.astro — el label es texto puro dentro del <a>
import CTABanner from '@components/CTABanner.astro'
import { waUrl, WA_MESSAGES } from '@config/site'
---
<CTABanner
heading="¿Necesitas una cotización?"
desc="Te respondemos por WhatsApp con precio y plazo."
btns={[
{
label: 'Cotizar por WhatsApp',
href: waUrl(WA_MESSAGES.cotizacion),
icon: 'wa',
primary: true,
external: true,
},
{ label: 'Ver catálogo completo', href: '/productos', icon: 'catalog' },
]}
badge="Respuesta en menos de 2 horas"
/>
La fórmula operativa del label:
- Verbo en imperativo: «Cotizar», no «Cotización»; «Ver», no «Vista»; «Hablar», no «Plática».
- Objeto concreto: «por WhatsApp», «el catálogo», «con un asesor», «mi cotización».
- Longitud: 16-32 caracteres. Más corto y se vuelve críptico («Click»), más largo y se corta en móvil («Solicita ya mismo tu cotización personalizada»).
- Sin signos de exclamación: «Cotizar por WhatsApp», no «¡Cotizar ya!». La exclamación huele a marketing barato y sube la sospecha del visitante.
2. Jerarquía visual: primary vs ghost en el componente real
El componente del proyecto ya separa los dos tratamientos por clase y por estilo, con tres variantes de fondo (red, dark, light):
/* src/components/CTABanner.astro — los dos tratamientos por variant */
.cta-banner--red .cta-btn--primary,
.cta-banner--dark .cta-btn--primary {
background: #fff;
color: var(--color-red, #C41E24);
border: 2px solid #fff;
}
.cta-banner--red .cta-btn--ghost,
.cta-banner--dark .cta-btn--ghost {
background: transparent;
color: rgba(255, 255, 255, .88);
border: 2px solid rgba(255, 255, 255, .35);
}
.cta-banner--light .cta-btn--primary {
background: var(--color-red, #C41E24);
color: #fff;
border: 2px solid var(--color-red, #C41E24);
}
.cta-banner--light .cta-btn--ghost {
background: #fff;
color: var(--color-gray-700, #374151);
border: 2px solid var(--color-gray-200, #D1D5DB);
}
Lo importante no es el color: es el contraste. El primary tiene fondo sólido (alto contraste con el banner); el ghost tiene fondo transparente con borde sutil (bajo contraste). El ojo prioriza al primero por física óptica, no por preferencia. La regla operativa del sistema: una sola entrada de btns[] con primary: true, máximo una entrada adicional con primary: false. Tres botones primary cancelan la jerarquía.
3. Presets canónicos: el copy vive en cta-presets.ts, no en cada página
El patrón del proyecto centraliza el copy del cierre en presets SSoT. La página padre NO escribe heading, desc ni btns[] a mano: importa el preset y monta el componente con spread.
// src/config/cta-presets.ts — los 3 presets canónicos
import { waUrl, WA_MESSAGES } from './site'
export const PRESET_GENERAL = {
heading: '¿Necesitas una cotización?',
desc: 'Te respondemos por WhatsApp con precio, plazo y stock real. Sin compromiso.',
btns: [
{
label: 'Cotizar por WhatsApp',
href: waUrl(WA_MESSAGES.cotizacion),
icon: 'wa' as const,
primary: true,
external: true,
},
{ label: 'Ver catálogo', href: '/productos', icon: 'catalog' as const },
],
badge: 'Respuesta en menos de 2 horas',
variant: 'red' as const,
}
export const PRESET_CATEGORIA = {
heading: '¿Listo para cotizar este modelo?',
desc: 'Mándanos tu cantidad y aplicación; te respondemos con precio, ficha técnica y plazo de entrega.',
btns: [
{
label: 'Solicitar cotización',
href: waUrl(WA_MESSAGES.productos),
icon: 'wa' as const,
primary: true,
external: true,
},
{ label: 'Hablar con asesor', href: '/contacto', icon: 'phone' as const },
],
badge: 'Asesoría técnica sin costo',
variant: 'dark' as const,
}
export const PRESET_CONTACTO = {
heading: '¿Tienes dudas? Escríbenos.',
desc: 'Lee, pregunta, decide. Sin presión, sin formularios largos.',
btns: [
{
label: 'Hablar por WhatsApp',
href: waUrl(WA_MESSAGES.contacto),
icon: 'wa' as const,
primary: true,
external: true,
},
],
badge: 'Sin compromiso de compra',
variant: 'light' as const,
}
Tres registros, una sola voz. Cambias el copy del cierre del sitio entero = editas un archivo. Y agregar un preset nuevo (PRESET_BLACK_FRIDAY con plazo REAL, PRESET_NEWSLETTER, PRESET_DESCARGABLE) tampoco toca componentes.
4. Enlace honesto: el href hace lo que el label promete
La regla dura: el destino del clic coincide con lo que dice el label. Si el botón dice «Cotizar por WhatsApp», el href abre WhatsApp. Si dice «Ver catálogo», navega a /productos. Si dice «Descargar PDF», descarga un PDF, no abre un formulario de 8 campos previo. Cualquier divergencia entre label y destino es dark-pattern, aunque no figure en ningún libro de UX.
---
// CORRECTO · el label promete WhatsApp, el href abre WhatsApp
btns: [
{ label: 'Cotizar por WhatsApp', href: waUrl(WA_MESSAGES.cotizacion), icon: 'wa', primary: true, external: true }
]
// INCORRECTO · el label dice WhatsApp, el href abre un form
btns: [
{ label: 'Cotizar por WhatsApp', href: '/cotizar', icon: 'wa', primary: true }
]
// INCORRECTO · el label dice "Descargar gratis", el href pide registro
btns: [
{ label: 'Descargar gratis', href: '/registro?next=/descarga', primary: true }
]
---
El visitante que clica «Descargar gratis» y aterriza en un formulario de 8 campos no se enoja la primera vez: se enoja la tercera, y a la cuarta cierra la pestaña antes de scrollear. La fricción de promesa rota acumula desgaste de marca que no se mide en GA4.
Tabla comparativa
| Práctica | Honesto | Dark-pattern equivalente |
|---|---|---|
| Urgencia | «Cierra cupo el 30 de junio» (verificable) | «Termina en 02:34:18» (timer falso reiniciado) |
| Escasez | «Stock final · quedan 12 unidades» (campo real) | «¡Solo quedan 2!» (constante, fabricado) |
| Botón secundario | «Ver catálogo», «Seguir leyendo» (neutro) | «No, prefiero seguir perdiendo dinero» (confirmshaming) |
| Label del primario | «Cotizar por WhatsApp» (verbo + destino) | «Click aquí», «Comenzar ahora» (oculta destino) |
| Newsletter en banner | «Suscribirme al newsletter mensual» | Checkbox premarcado oculto en el flujo de checkout |
| Precio en CTA | «Cotizar (precio varía por cantidad)» | «Gratis» que abre un upsell de 8 pasos |
La columna del medio es la que aguanta auditoría de marca, retención y, en MX+EU, escrutinio regulatorio (PROFECO/LFPC art. 32-bis, Directive 2005/29/EC, FTC Act Section 5). La columna derecha sube clicks del mes y baja confianza del año.
Patrones avanzados
Anti-pattern enumerado: contadores falsos de urgencia. El clásico de las landings de curso online: un timer rojo de 7 minutos que dice «Oferta termina pronto». Recargas la página, el timer reinicia. Es el dark-pattern más obvio y el más venenoso: el visitante que lo detecta una vez no vuelve, y el que no lo detecta convierte por miedo y pide reembolso al día siguiente. El componente CTABanner.astro del proyecto NO incluye prop de timer por diseño; la variante 5 documentada en /modulos/cta-banner admite urgencia con expiresAt?: ISODate pero exige que la fecha sea real y verificable (extension futura, no activa). Regla dura del sistema: si la urgencia no es real, no hay urgencia.
Anti-pattern: escasez fabricada. «¡Solo quedan 2!», «1 persona viendo ahora mismo», «12 personas reservaron en la última hora». Si el dato no se origina en un campo real del catálogo (stock final de un producto, cupos limitados de un curso, sesiones disponibles de un servicio), es manipulación. La escasez REAL se comunica con honestidad: un campo stock en el frontmatter del producto, una consulta server-side al inventario. La fabricada es marketing tóxico que tu sistema debería rechazar al code review.
Anti-pattern: confirmshaming en el ghost. «No gracias, prefiero pagar más impuestos», «No, no quiero ahorrar dinero», «Cerrar (sé que pierdo)». El botón secundario existe para dar escape al visitante indeciso, no para humillarlo. El copy debe ser neutro y honesto: «Ver catálogo», «Seguir leyendo», «Volver al inicio», «No por ahora». El confirmshaming es el dark-pattern que mejor se viraliza en redes (el subreddit r/assholedesign documenta cientos); cuando tu copy aparece ahí, la conversión del banner deja de importar.
Anti-pattern: label que miente sobre el destino. «Descargar gratis» que abre un formulario de 8 campos no es descargar gratis: es lead magnet. «Hablar con un asesor» que lleva a un autoresponder no es hablar con asesor: es ticket. «Comenzar ahora» que pide tarjeta de crédito no es comenzar: es checkout. La regla dura: el verbo del botón es lo que pasa después del clic, sin sorpresa. Si el clic abre WhatsApp, dice «Cotizar por WhatsApp». Si el clic lleva a un form, dice «Solicitar cotización». Si el clic descarga, dice «Descargar PDF (2.4 MB)».
Caso de retención dañada · documentado. Booking.com y Expedia recibieron en 2024 sanciones de la Autoridad italiana de competencia (AGCM) por contadores falsos de habitaciones y mensajes inflados de demanda; el caso es público y la multa pasó de €10M. El daño cuantificado no fue la multa: fue el ciclo de cancelaciones aumentadas, las reseñas negativas que mencionan «sentí que me presionaron», y el cambio de comportamiento de los repeat customers que migraron a Airbnb. La métrica que el dark-pattern infla (booking en sesión) cae 18-25% en el trimestre siguiente cuando se desactivan los contadores. Convertir hoy con engaño es vender mañana sin marca.
Caso B: SaaS con confirmshaming retirado en 2025. Una herramienta de proyectos retiró el confirmshaming de su modal de cancelación («No, prefiero perder mis 3 años de historial») después de que tres reviews G2 lo mencionaran como «la razón por la que me fui». La sustitución por «No por ahora» bajó cancelaciones 4% (el confirmshaming las inflaba al irritar al usuario que ya estaba decidido) y subió NPS 8 puntos en el siguiente survey. El dark-pattern no estaba reteniendo: estaba quemando.
Antes y después: el caso de la landing de cursos
Cifras del refactor mencionado al inicio. Tres meses pre-cambio vs tres post-cambio, mismo presupuesto de ads, misma audiencia. Lighthouse 12.x sobre Pixel 5 Slow 4G; conversiones de Stripe.
| Métrica | Antes (timer falso + confirmshaming) | Después (CTA honesto) | Delta |
|---|---|---|---|
| Conversión por sesión | 7.8% | 5.4% | -2.4 pp |
| Conversión por visitante único | 2.1% | 3.6% | +1.5 pp |
| Repeat customers (90 días) | 11% | 26% | +15 pp |
| Reembolsos (30 días) | 14% | 3.8% | -10.2 pp |
| NPS post-compra | 32 | 58 | +26 |
| Tickets de soporte «me sentí presionado» | 23/mes | 0/mes | -23 |
| LTV cliente promedio | $148 USD | $311 USD | +110% |
| LCP (banner sin timer pesado) | 2.4 s | 1.6 s | -0.8 s |
| TBT (sin script de countdown) | 280 ms | 30 ms | -250 ms |
Lo que parece pérdida (conversión por sesión -2.4 pp) es el filtro funcionando: los clientes que ya no convierten son los que iban a pedir reembolso. Lo que sí crece (LTV +110%) es lo que importa para sostener el negocio. La aritmética favorece la honestidad en cualquier horizonte mayor a 60 días. El timer falso es préstamo a tasa del 300% pagado en confianza.
Decision matrix: cuándo usar cada variante
| Tu contexto | Preset | Razón |
|---|---|---|
| Cierre genérico de página de servicio | PRESET_GENERAL (variant red) | Alta visibilidad, primary WhatsApp + ghost catálogo |
| Cierre de página de producto/categoría | PRESET_CATEGORIA (variant dark) | Tono profesional, asesoría técnica + WhatsApp |
| Cierre de página de contacto | PRESET_CONTACTO (variant light) | Solo 1 botón, sin distracción al final del funnel |
| Cierre de artículo de blog largo | Sin preset, banner inline ghost | El visitante ya está leyendo; no romper el flujo |
| Cierre de hero (above the fold) | NO usar CTABanner, usar botones del Hero | El banner es para cierre de scroll, no inicio |
| Promoción real con fecha verificable | Preset custom con badge="Cierra el DD/MM" | Honesto si la fecha es verificable y no se reinicia |
| Sitio gov o b2b conservador | PRESET_CONTACTO variant light | Tono sobrio, una sola acción, sin badge |
| Landing de campaña paga con incentivo | Sin timer, badge real («Descuento del 20% válido en julio») | El incentivo va en copy, no en pseudo-urgencia |
Edge cases y debugging
1. Banner repetido en la misma página por overrides de layout. Si la página padre incluye ‹CTABanner /› manualmente y el layout también lo inyecta automáticamente, terminas con dos banners idénticos. Detección rápida con DevTools: document.querySelectorAll('.cta-banner').length. Solución: el layout debe respetar una prop data.cta=null o pageType=blog-post que suprime el banner automático cuando la página lo monta manualmente.
2. WhatsApp con número hardcoded en lugar de waUrl. Si encuentras href="https://wa.me/5215512345678" en algún .astro, es un anti-patrón: cuando cambies el número, tienes que hacer search-and-replace en todo el repo. Usa siempre waUrl(WA_MESSAGES.x) que centraliza el número en site.ts. Auditá con grep: grep -r "wa.me/" src/ | grep -v site.ts debe devolver vacío.
3. Banner con variant: 'dark' sobre fondo dark del layout. Sin separación visual entre el banner y el fondo de la página, el módulo se pierde. Asegura border-top: 1px solid var(--color-border) o un margen vertical generoso. Inspeccionar con Lighthouse: si el CLS del banner es ≥0.1, hay reflow problemático.
4. CTAs con iconos SVG inline pesados. Cada icon en el banner (wa, catalog, phone) debe ser SVG inline minificado (~200-400 bytes cada uno). Si tu icon es un PNG de 8 KB o un SVG con paths complejos, infla el HTML y el LCP. Usa SVG simples de 24×24 px o iconify con tree-shaking activado.
5. Multilenguaje y labels traducidos cambiando longitud. Si el sitio es bilingüe, «Cotizar por WhatsApp» (21 chars) traducido a «Get a quote via WhatsApp» (24 chars) cambia el ancho del botón y puede romper el grid. Asegura min-width y text-align: center en el .cta-btn para que el botón crezca uniformemente. Test obligatorio: pintar todas las traducciones y verificar que ningún botón wrap a 2 líneas en móvil.
Casos donde NO usar este patrón
Para confirmaciones de éxito post-acción. Después de un envío de formulario exitoso, no pongas un CTA banner inmediatamente debajo del mensaje de confirmación. El visitante necesita un momento de respiro; meterle otro CTA en la cara es percibido como agresivo. Mejor: mensaje de éxito con próximo paso natural («Te respondemos en 2 horas. Mientras, conoce nuestro [blog]»).
Para errores 404 o 500. En páginas de error, el CTA debe ser de navegación («Volver al inicio», «Ver catálogo»), no de conversión («Cotizar por WhatsApp»). Pedir cotización a un visitante que llegó a un 404 es desentenderse del error. Reconoce el problema primero, ofrece salida después.
Para artículos editoriales largos. Un CTA banner al final de un artículo de 4,000 palabras corta el flujo de lectura. Mejor: enlaces contextuales dentro del artículo y una sección «Sigue leyendo» neutra al cierre. El banner es para landings y páginas de servicio, no para contenido editorial.
Para modales de cancelación de suscripción. Si tu sitio tiene un modal de cancelación, NO uses confirmshaming («No, prefiero perder mis 3 años de historial»). La FTC Click-to-Cancel Rule de marzo 2024 prohíbe explícitamente este patrón. Usa «No por ahora» o un simple «Cancelar suscripción» con confirmación en un solo paso.
Performance, a11y y compliance regulatorio
Performance: el CTABanner del proyecto pesa 3.8 kB CSS gzip + 0 KB JS (cero scripts), con LCP impact de ~0.1 s en el banner debajo del fold. Comparado con banners típicos de Builder.io o Webflow templates (con countdowns animados, gradientes complejos, JS de tracking), el ahorro es de 25-45 kB por página.
A11y: el ‹a class="cta-btn"› cumple SC 2.4.4 Link Purpose in Context (A) porque el texto del link describe el destino. SC 1.4.3 Contrast (AA): el primary blanco sobre rojo #C41E24 da ratio 4.83:1 (pasa AA). El ghost con borde rgba(255,255,255,.35) da ratio 2.1:1 (falla AA para texto pequeño); úsalo solo con texto ≥18 px o aumenta opacidad a .55. SC 2.5.5 Target Size (AAA): padding mínimo 16px 32px da área ~48×48 px (pasa). SC 3.2.2 On Input: el clic no debe disparar cambios inesperados (popups extra, redirects); cumplido si respetas la regla de label-honest.
Compliance regulatorio: FTC Negative Option Rule prohíbe forced continuity sin disclosure clara; FTC Click-to-Cancel Rule (marzo 2024) exige cancelación tan fácil como suscripción; EU CRP 2024 prohíbe dark patterns con multa hasta 4% revenue global; México LFPC art. 32-bis cubre publicidad engañosa. El CTABanner honesto cumple las cuatro sin esfuerzo adicional: sin timers falsos, sin confirmshaming, sin labels engañosos, sin forced continuity.
Checklist
- Cada
btns[]tiene máximo 2 entradas (1 primary + 1 ghost) o solo 1 primary - El
labelde cada botón usa verbo imperativo + objeto concreto (16-32 caracteres) - Ningún label dice «Click aquí», «Más info», «Enviar», «Comenzar» a secas
- El
hrefdel primary lleva exactamente a lo que promete el label - No hay signos de exclamación en los labels (
¡Cotizar ya!está prohibido) - Solo UN botón tiene
primary: truepor banner - El ghost (
primary: false) usa copy neutro, nunca confirmshaming - No hay timer de cuenta regresiva sin
expiresAtreal verificable - No hay mensajes de escasez (
¡Solo quedan 2!) sin campostocken el catálogo - El copy del banner vive en
cta-presets.ts, no escrito a mano en la página - Cada
hrefde WhatsApp se construye conwaUrl(WA_MESSAGES.x), sin hardcoding del número - El badge (
Respuesta rápida,Sin compromiso) dice solo lo que el negocio puede sostener
Preguntas frecuentes
¿Por qué dos botones bajan la conversión del primary si el ghost da escape?
Porque el escape NO debería ser visualmente competidor. La jerarquía sólida vs ghost le dice al ojo «esto es lo que esperamos, esto está por si acaso». Cuando los dos botones tienen el mismo peso (mismo color, mismo tamaño, mismo borde), el visitante interpreta que las dos acciones valen igual y la decisión se posterga. El componente CTABanner.astro modela la jerarquía con BtnDef.primary?: boolean: el primary se pinta sólido, el ghost transparente con borde sutil. La conversión sube no porque el ghost desaparezca, sino porque el primary deja de pelearse con su gemelo.
Si un timer falso sube clicks 12%, ¿no compensa el daño de marca?
A corto plazo, sí. A mediano, no. El cálculo honesto incluye tres efectos diferidos que GA4 no mide bien: cancelaciones aumentadas (el comprador apresurado pide reembolso), reseñas negativas con la frase «me sentí presionado» (Capterra/G2 las indexa y bajan el rating agregado en 3-6 meses), y caída de repeat customers (que el caso Booking.com 2024 documentó en 18-25% el trimestre siguiente). El timer falso es un préstamo de conversión a tasa de interés del 300% pagado en confianza. Si la urgencia es real, comunícala con expiresAt verificable y badge honesto («Cierra cupo el 30 de junio»). Si no, no hay urgencia.
¿Confirmshaming en el botón secundario es legal en México?
La LFPC art. 32-bis prohíbe «publicidad engañosa o abusiva», y la Suprema Corte ha interpretado «abusiva» como prácticas que aprovechen vulnerabilidad cognitiva del consumidor. El confirmshaming cabe en el espíritu de la norma, pero la jurisprudencia mexicana específica sobre dark-patterns aún es escasa (a junio de 2026, las sanciones documentadas son europeas: AGCM Italia 2024, autoridad francesa CNIL 2023). En la práctica: no hay multa probable, pero el daño reputacional es real y el copy se viraliza. La recomendación profesional: no lo uses. El ghost neutro convierte parecido sin riesgo.
¿Puedo poner 3 botones si los tres llevan a destinos distintos pero relacionados?
Si los tres son igualmente importantes, el componente correcto NO es CTABanner: es SectionMenu (ver /modulos/section-menu). El banner pide UNA cosa; el menú reparte hacia varias. Si insistes en 3 CTAs por página, repártelos en 3 banners contextuales a lo largo del scroll (uno por sección), cada uno con su cierre específico. Tres botones en un solo banner cancelan la jerarquía y bajan la conversión medible 20-40% según pruebas A/B documentadas por CXL Institute y Baymard.
¿El badge de confianza («Respuesta rápida», «Sin compromiso») cuenta como overpromise?
Solo si no se cumple. «Respuesta en menos de 2 horas» es overpromise si el equipo tarda 18 horas en responder; «Sin compromiso» es overpromise si el formulario suscribe al newsletter sin checkbox. La regla dura: el badge dice lo que el negocio puede sostener todos los días, no lo que aspira a sostener. Si la respuesta promedio es 8 horas, el badge dice «Respuesta el mismo día hábil». Si hay compromiso (newsletter automático, contrato), el badge no dice «Sin compromiso». Honestidad calibrada en lugar de marketing aspiracional.
Cómo se compara este patrón con cómo lo hacen Stripe o Linear?
Stripe documenta en su style guide pública cuatro reglas de copy de botones: (1) verbo + objeto, (2) sentence case (no Title Case), (3) sin signos de exclamación, (4) consistencia entre label y destino. Linear va más lejos: en su carrera y blog declara «we never use dark patterns, urgency hacks, or confirmshaming». Vercel y Shopify Polaris (design system) tienen guías similares. El CTABanner del proyecto se alinea con esa cohorte por diseño: la API no permite timers falsos (no hay prop expiresAt activa), no permite confirmshaming (el ghost es estilo, no copy), y los presets centralizan el voice. Cuando un cliente pregunta «por qué no hacemos lo que hacen los marketplaces que sí inflan», la respuesta es: porque queremos parecernos a quien sostiene marca a 10 años, no a quien optimiza el quarter.
El A/B testing del color del botón vale la pena en 2026?
Cada vez menos. El consenso post-2022 (artículos de CXL, ConversionXL, NN/g) es que las optimizaciones de micro-color rinden ≤2% en escenarios atípicos y ≈0% en sitios bien diseñados. Lo que sí mueve la aguja: copy del label (+15-30% típico), número de botones (+10-25% al reducir de 3 a 1), jerarquía visual (+8-15% al diferenciar primary vs ghost), y honestidad de la promesa (+repeat customers +40-100% a 6 meses). El presupuesto de testing está mejor invertido en copy y arquitectura del cierre que en hue del botón.
Astro permite emitir el banner desde el layout o cada página debe importarlo?
Ambos patrones funcionan. El proyecto usa el patrón «layout inyecta por default, página opta por suprimir». El layout recibe data.cta con el preset; si la página pasa data.cta=null, no se monta. Si la página pasa un preset distinto, sobrescribe. Esto evita el riesgo de olvidar el banner en alguna página y centraliza la decisión. La alternativa («cada página importa explícitamente») es más explícita pero más propensa a olvido. Para sitios pequeños (≤30 páginas), ambos funcionan; para sitios grandes, el patrón layout-inyecta gana.
Existe alguna forma de medir el daño de un dark-pattern antes de implementarlo?
Sí, con tres preguntas simples. (1) Si el visitante descubriera el patrón en Reddit/Twitter, ¿lo defenderías? (2) Si la FTC/CRP/Profeco te pregunta, ¿puedes justificarlo con datos verificables? (3) El cliente que convierte por este patrón, ¿va a recomendar el sitio? Si las tres respuestas son «sí», el patrón probablemente es honesto. Si una sola es «no», ya tienes la evidencia para no implementarlo. Es el filtro pre-mortem que evita la conversación post-incidente.
Un CTA banner honesto convierte menos en la sesión y vende más en el trimestre. La aritmética favorece la honestidad: 100 clicks de los cuales 30 son repeat valen más que 200 clicks de los cuales 10 vuelven. El componente CTABanner.astro del proyecto modela las decisiones críticas en su API (jerarquía con primary, copy en preset, sin timers ni escasez fabricada) y deja al code review las decisiones que ningún componente puede tomar: que el label diga lo que el destino hace, que el ghost no humille, que el badge no infle. El sistema da las herramientas; la voz de marca pone la disciplina.
Sigue leyendo
- Módulo CTA banner en vivo
- Tracking de CTAs con data-cta-id, sin invasión
- Construir un sitio profesional con Astro y Markdown: 12 módulos
Fuentes externas:
- Nielsen Norman Group: Dark Patterns research and taxonomy — investigación cuantitativa sobre impacto en retención.
- Harry Brignull: deceptive.design — catálogo canónico de 22 dark patterns y casos públicos.
- FTC Click-to-Cancel Rule (marzo 2024) — cancelación tan fácil como suscripción.
- EU Consumer Rights Directive 2024 — prohibición explícita de manipulative interfaces, multas hasta 4% revenue.
- Stripe Press: Building consumer-first UX — guía pública de copy honesto y design ethics.