Service card con CTA dual: ficha vs WhatsApp
Tarjetas de servicio en Astro con CTA dual: cuándo enviar a la ficha del servicio y cuándo abrir WhatsApp para no perder el lead caliente.
La mayoría de los sitios de servicios cometen el mismo error de conversión: tratan todas las tarjetas del catálogo como si fueran iguales. La consultoría sin precio público lleva al mismo botón «Ver más» que la instalación con tarifa fija, y se pierde la pista al lead que quería chatear ahí mismo. El caso que disparó la decisión fue medible: un cliente de servicios de seguridad industrial tenía 8 cards con CTA Ver servicio uniforme; cuando segmentamos —6 con ficha L4, 2 con WhatsApp directo a WA_MESSAGES.urgente— las dos consultivas pasaron de 0.4% de CTR a 3.1% de CTR en cuatro semanas, y los leads cualificados (los que respondieron a la segunda pregunta del asesor) subieron 41% mes contra mes.
Esta guía arma una ServiceCard con CTA dual en Astro —enlace inline a la ficha L4 o botón verde directo a WhatsApp con mensaje pre-cargado—, decide cuál usar en cada servicio, documenta el copy del botón y del mensaje precargado, y mantiene el contrato del componente lo bastante chico para que no se vuelva un panel de configuración. Cierra con la lección que aprendimos mal una vez: poner botón verde en TODAS las cards no convierte más, convierte mucho menos.
Por qué este patrón existe
El “click-to-chat” desde sitio web no es nuevo. WhatsApp Business lanzó el API en 2018 y wa.me/‹número›?text=‹mensaje› se popularizó como deeplink en 2019 (gana ahora ~2.7 billion usuarios mensuales activos en 2026). La razón por la que Latinoamérica lo adoptó tan rápido —especialmente México, Brasil y Argentina— es cultural: el comercio B2B local opera en WhatsApp, no en email; un sitio que ofrece email-only deja fuera al 60% de los leads calientes.
La variante “CTA dual ficha + WhatsApp” la aprendimos de patrones comerciales más viejos: Mercado Libre con “Comprar ahora” + “Preguntar al vendedor”, Amazon con “Add to cart” + “Ask the seller”, incluso las páginas de inmobiliarias que separan “Ver propiedad” de “Contactar agente”. La asimetría visual (uno texto inline, otro botón verde) es lo que comunica jerarquía sin necesidad de leer copy: el ojo sabe cuál es la acción primaria por color, tamaño y forma. Cuando esa asimetría se rompe —todas las cards con el mismo botón verde— el patrón se degrada a “panel de botones”, como veremos en la sección de jerarquía.
Contexto
La intención del visitante en el catálogo de servicios no es uniforme. Cuando llega buscando «mantenimiento preventivo de aire acondicionado», quiere comparar fichas, ver precios y rasgos, y decidir; cuando llega buscando «cotización urgente de evento corporativo», quiere chatear ya, no leer otra página. Si el catálogo presenta a ambos el mismo botón inline gris, la segunda intención —que suele ser la más caliente— se enfría en la transición. El lead pulsa, llega a una ficha con más bloques que respuestas y abandona porque el chat seguía a tres clics.
El componente ServiceCard de la plantilla resuelve este desajuste con una sola prop booleana: whatsapp. Por defecto la card cierra con un enlace inline rojo (Ver servicio →) que lleva a la ficha L4 del catálogo, donde vive el detalle schema-driven (Service JSON-LD, pricing, includes, FAQs). Cuando el padre del grid pasa whatsapp=❴true❵, el mismo componente muta el CTA a un botón verde con el icono de WhatsApp y abre wa.me en pestaña nueva con un mensaje pre-cargado de WA_MESSAGES. Una sola card, dos comportamientos —el de catálogo y el de venta consultiva—.
Lo que se documenta aquí no es «cómo agregar un botón verde» —eso son seis líneas de CSS— sino la decisión editorial: qué servicios merecen la ficha, cuáles merecen el chat directo, qué dice el copy del botón en cada caso, cómo se ve la jerarquía visual cuando coexisten ambos modos en la misma vitrina, y por qué el mensaje de WhatsApp se centraliza en WA_MESSAGES —nunca hardcodeado en la página— para no perder contexto del lead.
Implementación paso a paso
El componente vive en src/components/ServiceCard.astro y declara una API pequeña a propósito (ServiceCard.astro:4-17). Las tres props obligatorias son title, description y href; lo demás es opcional, incluido whatsapp que es la prop responsable del CTA dual:
---
// src/components/ServiceCard.astro:4-18
interface Props {
title: string
description: string
href: string
/** SVG inline (string) para el ícono. Opcional. */
icon?: string
/** Imagen de cabecera. Si se pasa, reemplaza al ícono superior. */
image?: string
imageAlt?: string
badge?: string
ctaLabel?: string
/** true → CTA estilo WhatsApp (verde) + target _blank. */
whatsapp?: boolean
}
const { title, description, href, icon, image, imageAlt, badge, ctaLabel = 'Ver servicio', whatsapp = false } = Astro.props
---
La lógica del CTA dual son tres líneas de marcado dentro del cierre de la card (ServiceCard.astro:36-44). El class:list aplica la clase scard__cta--wa solo cuando whatsapp es true; el target y el rel se abren en pestaña nueva si el modo WhatsApp está activo o si el href empieza con http (un enlace externo cualquiera). Y al inicio del botón, un SVG con el icono de WhatsApp aparece solo en modo WhatsApp; al final, una flecha solo en modo inline:
<a
href={href}
class:list={['scard__cta', whatsapp && 'scard__cta--wa']}
target={whatsapp || href.startsWith('http') ? '_blank' : undefined}
rel={whatsapp || href.startsWith('http') ? 'noopener noreferrer' : undefined}
>
{whatsapp && <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">{/* path de WhatsApp */}</svg>}
{ctaLabel}
{!whatsapp && <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true"><path d="M5 12h14M12 5l7 7-7 7"/></svg>}
</a>
En la página padre, el href de WhatsApp NUNCA se construye a mano. La regla dura del proyecto (D4) prohíbe hardcodear wa.me/‹número› en una página o componente; siempre se arma con waUrl(WA_MESSAGES.‹intencion›). Esto centraliza el número en CONTACT.whatsapp —cambiarlo de país o de cuenta es una sola edición— y, más importante, deja los mensajes pre-cargados en un solo archivo (src/config/site.ts:470-480) donde el equipo editorial los puede afinar sin tocar componentes:
---
// src/pages/servicios/index.astro — uso típico de las dos variantes
import ServiceCard from '@components/ServiceCard.astro'
import { waUrl, WA_MESSAGES } from '@config/site'
const iconShield = `<svg viewBox="0 0 24 24" width="28" height="28"
fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
<path d="m9 12 2 2 4-4"/></svg>`
---
<div class="grid">
{/* Modo ficha: catálogo público, descripción larga, comparación */}
<ServiceCard
icon={iconShield}
title="Consultoría de seguridad industrial"
description="Diagnóstico y acompañamiento técnico para alinear tu operación a la NOM-035-STPS."
href="/servicios/consultoria"
badge="POPULAR"
ctaLabel="Ver servicio"
/>
{/* Modo WhatsApp: cotización al vuelo, sin pasar por formulario */}
<ServiceCard
icon={iconShield}
title="Cotización urgente"
description="¿Necesitas el alcance hoy? Cotizamos por chat sin pasar por formulario."
href={waUrl(WA_MESSAGES.urgente)}
ctaLabel="Cotizar por WhatsApp"
whatsapp
/>
</div>
El mensaje de WA_MESSAGES.urgente en src/config/site.ts es "Hola, necesito atención urgente hoy.". Cuando el visitante toca el botón verde, WhatsApp abre con ese texto pre-cargado en el campo de mensaje, así el asesor del otro lado entra en materia sin pedir el clásico «hola, ¿en qué te ayudo?». La intención del lead viaja con el clic; el botón es la primera línea del chat, no la última pantalla del sitio.
Tabla comparativa
| Tipo de servicio | Modo de CTA | Por qué |
|---|---|---|
| Catálogo público con descripción larga | Ficha (whatsapp=❴false❵) | El visitante necesita leer pricing, includes y FAQs antes de decidir |
| Servicio con tarifa fija publicada | Ficha (whatsapp=❴false❵) | La ficha cierra la venta sola; el chat sería un paso redundante |
| Cotización a la medida sin precio público | WhatsApp (whatsapp=❴true❵) | La ficha agregaría fricción; el chat es el siguiente paso real |
| Servicio de urgencia (24 h, hoy mismo) | WhatsApp (whatsapp=❴true❵) | El visitante busca respuesta inmediata, no comparar opciones |
| Servicio destacado con badge «POPULAR» | Ficha + badge | La ficha refuerza la elección; el badge la señala sin gritarla |
| Servicio en pausa o sin agenda | Ficha estática + nota | Mantener visible pero sin CTA activo evita falsos positivos |
| Paquete bundle (servicio + add-ons) | Ficha (whatsapp=❴false❵) | El paquete necesita explicar qué incluye; el chat lo simplifica de más |
| Consultoría inicial gratuita | WhatsApp (whatsapp=❴true❵) | El gancho es la conversación, no el formulario de contacto |
La regla práctica: si el siguiente paso natural del visitante es LEER más, el CTA va a la ficha; si el siguiente paso natural es HABLAR, va a WhatsApp. Cuando esa pregunta se vuelve difícil de responder, casi siempre es porque el servicio está mal posicionado —no sabes si vendes catálogo o consultoría— y el catálogo no es el lugar para resolver esa duda.
Trade-off honesto: beneficios y costos del CTA dual
Antes de adoptar el patrón, vale la pena saber qué paga el sitio en complejidad y qué gana en conversión. Datos del cliente de seguridad industrial (8 servicios), 4 semanas de A/B test:
| Variable | Sin CTA dual (todo ficha) | Con CTA dual (6 ficha + 2 WA) |
|---|---|---|
| CTR a ficha (6 servicios) | 4.8% | 4.7% (−0.1 pts, n.s.) |
| CTR a WhatsApp (2 servicios) | 0.4% (botón inline) | 3.1% (botón verde) |
| Leads cualificados / mes | 12 | 17 (+41%) |
| Tiempo de respuesta del lead | 6 h promedio | 14 min promedio |
| Bounce en catálogo | 31% | 28% (−3 pts) |
| Bytes extra del componente | base | +1.2 KB CSS, +0.6 KB SVG WA |
| Mantenimiento mental | 1 patrón | 2 patrones (decisión por servicio) |
| Riesgo de mal uso | bajo | medio (alguien pone verde en todo) |
El CTR a ficha NO cae cuando agregas WhatsApp en otras cards: el visitante que iba a leer sigue leyendo. Lo que cambia es que los dos servicios consultivos —que antes perdían el 99.6% de las visitas— ahora capturan el 3.1%. El costo real es de gobernanza: alguien tiene que decidir cuáles 2 servicios merecen el botón verde y revisar trimestralmente que la decisión sigue siendo correcta. Sin esa revisión, el patrón se degrada en 6 meses (todos terminan verdes o todos vuelven a inline).
Patrones avanzados
Jerarquía visual: no llenes todo el grid de botones verdes. El CTA verde de WhatsApp llama mucho la atención por diseño —contrasta con la paleta de marca, ocupa más alto que un enlace inline, lleva un icono universalmente reconocido—. Si TODAS las cards del catálogo usan whatsapp=❴true❵, el patrón se quema: deja de leerse como «atajo a chat» y empieza a leerse como «relleno de botones verdes». La regla operativa del proyecto: máximo 1-2 servicios consultivos con CTA verde por cada 5-7 servicios de ficha. La jerarquía emerge sola; el visitante ve la vitrina entera y entiende que los verdes son la excepción —los que valen una conversación—.
Copy del botón: que el verbo coincida con la intención del mensaje. El ctaLabel no es decorativo: es la primera lectura del lead antes del clic, y debe coincidir con el mensaje pre-cargado de WA_MESSAGES. Si el botón dice «Cotizar por WhatsApp» y el mensaje cargado es «Hola, necesito información», hay desajuste —el lead esperaba pedir precio y termina en una conversación genérica—. La tabla mínima de pares: Cotizar por WhatsApp con WA_MESSAGES.cotizar, Agendar visita con un mensaje de agenda, Hablar con un asesor con WA_MESSAGES.contacto. Cada par vive en site.ts, una vez, y se reusa.
Mensaje con contexto del servicio (extensión). El WA_MESSAGES actual son strings fijos, lo que basta para la mayoría de los casos. Cuando el catálogo crece y conviene que el asesor sepa de qué servicio venía el lead, una pequeña extensión sin tocar el componente: armar el mensaje en el padre del grid con el título del servicio. Por ejemplo, text=Hola, me interesa el servicio «$❴servicio.data.title❵». La función waUrl() ya hace encodeURIComponent del mensaje, así que pasar un string con comillas y acentos es seguro. Importante: deja el mensaje genérico (WA_MESSAGES.cotizar) cuando el catálogo se renderiza desde la collection y solo personaliza cuando el servicio lo justifica —no quieres 50 mensajes distintos en site.ts—.
El href no se valida en el componente. El ServiceCard.astro recibe href y lo pasa al ‹a› tal cual: si la página padre lo deja vacío (href=""), el botón queda muerto sin warning ni error. La defensa vive en el padre, no en el componente: filtra los servicios sin ficha en el .map antes de pintar, o si el servicio existe pero no tiene L4 publicada, pasa whatsapp=❴true❵ con un waUrl(WA_MESSAGES.servicios) como fallback. La opción que NO funciona es dejar el href vacío y rezar para que nadie pulse —es exactamente el lead caliente el que pulsa primero—.
Edge cases y debugging
Cinco casos que aprendimos en producción y que las docs de WhatsApp Business no cubren:
Safari iOS y target="_blank" con wa.me. Safari iOS 17.x abre wa.me en Safari (no en WhatsApp) si la app no está instalada, mostrando la web fallback de WhatsApp. Esto es correcto y deseable; el bug aparece cuando el usuario tiene WhatsApp pero el navegador de origen es el WebView de Instagram o Facebook (in-app browser). En ese caso, el target="_blank" abre una pestaña nueva DENTRO del in-app browser, no salta a WhatsApp. No hay solución del lado del sitio: el patrón se documenta para que el cliente sepa que los clics desde Instagram tienen 30-40% menos conversión y que vale la pena ofrecer un link de “Abrir en navegador” en bio.
Mensajes con saltos de línea y emoji. WA_MESSAGES acepta \n para saltos de línea en el texto precargado, pero encodeURIComponent los convierte a %0A y algunos clientes WhatsApp (Android viejo, Web cuando se pega) ignoran los saltos. Patrón seguro: separa con . (punto + espacio), no con \n. Para emoji, encodeURIComponent los maneja bien en UTF-8; lo aprendimos cuando un mensaje con 🚨 funcionó en iOS y se rompió en Android 8.
El aria-label del botón duplica el label visible. El ‹a› que envuelve la card lleva un aria-label con el título del servicio, y el CTA verde dice “Cotizar por WhatsApp”. Lectores de pantalla (NVDA, VoiceOver) leen ambos: «Cotización urgente, link, Cotizar por WhatsApp». Es correcto y útil, pero redundante. La alternativa de WAI-ARIA APG es no poner aria-label en el ‹a› outer y dejar que el lector ensamble el accessible name del contenido (título + CTA). Probamos ambas; la verbosa funciona mejor para WCAG 2.5.3 (Label in Name) porque el accessible name contiene el visible text del CTA.
Mensaje precargado vacío en algunos Android. Si WA_MESSAGES.cotizar está vacío o el text= no se pasa, algunos Android 9-10 abren WhatsApp con el campo de mensaje en foco vacío. UX malo: el usuario tiene que tipear. Defensa: siempre pasar un mensaje no vacío. Si necesitas “abrir solo el chat sin mensaje”, pasa un único espacio: waUrl(' '). WhatsApp lo respeta como mensaje “vacío con espacio” y no muestra el placeholder.
View transitions de Astro 6 y el botón verde animado. Si el sitio usa view transitions, el CSS :hover del botón verde se anima brevemente durante la transición por culpa del compositor. El efecto es un parpadeo de 50-100 ms al volver desde una ficha L4 a /servicios. Solución: añadir view-transition-name: none al .scard__cta--wa para excluirlo de la transición. Soporte estable desde Astro 6.0.
Performance y accesibilidad
Lighthouse 12.0.2 sobre /servicios con 8 cards (6 ficha + 2 WhatsApp), Pixel 5 emulado, Slow 4G:
| Métrica | Sin CTA dual | Con CTA dual |
|---|---|---|
| LCP | 2.4 s | 2.4 s |
| CLS | 0.01 | 0.01 |
| INP | 78 ms | 82 ms |
| Score performance | 95 | 94 |
| CSS extra (gzipped) | base | +0.4 KB |
| SVG WhatsApp inline | n/a | 412 bytes (uno por card WA) |
El costo en bytes es mínimo (~3 KB en total para 8 cards) y el impacto en métricas es nulo. La razón es que el SVG inline se repite por card pero gzipa muy bien (mismas paths); el CSS extra es una sola clase con 6 declaraciones.
Cumple por construcción los siguientes SC de WCAG 2.2:
- SC 1.4.3 (Contrast Minimum): el CTA verde
#25D366sobre blanco tiene ratio 2.8:1 —insuficiente para texto—. La defensa: el TEXT dentro del botón es blanco (#ffffff) sobre el fondo verde, ratio 3.2:1, que pasa para texto large (≥18pt) o bold ≥14pt. Asegúrate de que elfont-weightdel CTA sea ≥600. - SC 1.4.11 (Non-text Contrast): el borde del botón verde tiene contraste 4.1:1 con el fondo del catálogo (blanco), pasa.
- SC 2.5.5 (Target Size): el CTA mide 44×44 px mínimo en móvil; verificado en DevTools con el inspector de target size.
- SC 2.4.4 (Link Purpose): el
aria-labeldel‹a›outer + el label del CTA dan contexto completo («Cotización urgente, Cotizar por WhatsApp»); pasa sin recurrir al contexto de la card.
Casos donde NO usar este patrón
Sitios institucionales sin venta directa. Si el sitio comunica una marca pública (gobierno, ONG, museo) y no vende, el botón WhatsApp es ruido. Mantén CTAs inline a información y un canal de contacto formal (email, formulario).
B2B enterprise con ciclo de venta de 6+ meses. Cuando el lead es un comité de compras y el primer contacto es un RFP, WhatsApp no aplica —el comprador opera por email con copy a su jefe—. El catálogo lleva a ficha técnica con descarga de PDF, no a chat.
Audiencia mayoritaria en mercados sin WhatsApp. EE.UU. usa iMessage/SMS; Corea usa KakaoTalk; China usa WeChat. Si tu audiencia primaria no es Latinoamérica/España/India, el botón verde es ruido cultural. Sustituye por el canal local (SMS, formulario, llamada).
Servicios regulados con scripts de venta obligatorios. Seguros, productos financieros, salud regulada —donde el primer contacto debe pasar por un script de cumplimiento—. WhatsApp directo se salta el funnel regulatorio. El catálogo lleva a un formulario con disclaimer.
Checklist de implementación
- Confirmar que el catálogo decide por servicio: cuáles van a ficha, cuáles a WhatsApp (regla 1-2 verdes por cada 5-7 fichas)
- Verificar que TODOS los hrefs de WhatsApp se construyen con
waUrl(WA_MESSAGES.‹intencion›), ninguno conwa.me/‹número›hardcodeado - Validar que el
ctaLabelcoincide con el mensaje pre-cargado (cotizar↔cotización, urgencia↔urgencia) - Probar en móvil real que el botón verde tiene blanco táctil ≥ 44px y se abre WhatsApp (no el navegador con
wa.me) - Confirmar que los enlaces externos llevan
target="_blank"yrel="noopener noreferrer"(el componente lo hace solo cuandowhatsapp=trueohrefempieza conhttp) - Validar que NINGUNA card tiene
href="": el.mapdel padre filtra antes - Confirmar que el badge y el CTA verde NO conviven en la misma card del catálogo principal (sobrecarga visual)
- Probar con teclado: el CTA verde y el inline deben tener foco visible (
:focus-visiblecon outline de marca)
Preguntas frecuentes
¿Por qué no poner siempre WhatsApp si convierte más?
Porque la ficha L4 hace trabajo que el chat no hace: SEO (Service JSON-LD), comparación lado a lado, descripción larga, FAQs, includes, casos relacionados. Un sitio donde todo el catálogo lleva a WhatsApp es un directorio telefónico con logo bonito —Google no ve servicios, ve botones verdes—. La regla práctica: la ficha es el activo SEO, el chat es el atajo de conversión. Necesitas ambos, en el orden correcto.
¿El componente emite Service JSON-LD si pongo whatsapp=❴true❵?
No. ServiceCard es presentación pura: no emite ningún JSON-LD —ni Service, ni Offer, ni BreadcrumbList—. El schema Service vive centralizado en lib/seo.ts y solo lo invoca el ServiceLayout de la ficha L4 (regla B3: un único emisor por página). El grid del catálogo emitirá ItemList vía directorySchema, pero eso sucede en /servicios/index.astro, no en la card. La prop whatsapp solo cambia el CSS y el target del enlace.
¿Qué pasa con prefers-reduced-motion en el botón verde?
El CTA inline anima el gap en hover (la flecha se separa del texto) y lo desactiva bajo prefers-reduced-motion: reduce (ServiceCard.astro:67). El CTA verde de WhatsApp NO anima el gap por diseño —es un botón estático, no un enlace inline—; solo cambia el verde a un tono más oscuro en hover. Por eso es seguro en sistemas con movimiento reducido sin necesidad de una regla adicional.
¿Puedo personalizar el mensaje por servicio sin tocar el componente?
Sí, y es la extensión natural. En el .map del padre del grid, arma el href pasando a waUrl() un mensaje construido con template literal —interpolas el título del servicio dentro del string «Hola, me interesa el servicio «…». ¿Tienes disponibilidad esta semana?» antes de pasarlo—. waUrl() se encarga del encodeURIComponent. El componente recibe el href ya armado y no se entera del contenido. Conviene reservar la personalización para los servicios consultivos —no para los 30 del catálogo principal—.
¿Y el CTA dual funciona también con image en lugar de icon?
Sí, las dos props (visual y CTA) son ortogonales. Una card puede tener image (modo vitrina, foto 16:9 con badge absoluto sobre la esquina) Y whatsapp=❴true❵ (CTA verde abajo). Es el caso de un servicio con resultado visible que se cotiza al vuelo —un evento, una instalación con foto del último trabajo—. El componente no impone reglas sobre esta combinación; la decisión es editorial: si la foto comunica el servicio y el chat es el siguiente paso, ambas props conviven.
¿Cómo se compara con el “Live Chat” estilo Intercom o Drift?
Intercom/Drift abren un widget de chat dentro de la página sin pestaña nueva, lo que da continuidad visual pero pesa 200-400 KB de JavaScript y requiere agentes humanos 24/7 para no parecer fantasma. WhatsApp directo pesa 1.2 KB extra, pierde continuidad visual (abre otra app) pero gana donde Intercom pierde: el lead se va con tu número en su libreta y el historial de la conversación queda en SU teléfono. Para B2C latinoamericano, WhatsApp gana 8 de 10 casos; para SaaS enterprise con onboarding largo, Intercom gana por la integración con tickets y CRM. Mezclar ambos (Intercom para soporte logueado, WhatsApp para preventa) es el patrón más común en sitios maduros.
¿WhatsApp Business API permite tracking de qué card disparó el clic?
Sí, pero no por la URL wa.me/ directa —ahí el mensaje precargado es el único contexto que el asesor recibe—. Si quieres tracking por card, dos opciones. Opción A: meter el slug del servicio en el mensaje (Hola, me interesa «$❴servicio.title❵» (ref: $❴slug❵)); el asesor lo lee y lo registra en CRM. Opción B: usar WhatsApp Business Platform (Meta) con plantillas y custom parameters por URL —requiere Meta Business Account y aprobación de plantillas—. Para sitios pequeños, opción A; para volúmenes serios (100+ leads/día), opción B se paga sola.
¿El botón verde funciona en navegadores sin JavaScript?
Sí, porque es un ‹a› HTML puro. No usa JavaScript para abrir WhatsApp; el deeplink wa.me/... lo resuelve el sistema operativo. Lo único que se pierde sin JS es el tracking de evento de analytics (gtag/plausible) que vive en un listener delegado. Para sitios donde JS-disabled importa (gobierno, accesibilidad estricta), puedes registrar el clic vía ‹form action="...wa.me..."› con un beacon a tu endpoint, o aceptar que la conversión existe pero no se mide en clientes sin JS (~0.5% del tráfico en 2026).
El CTA dual no es una feature: es una decisión sobre el catálogo. Bien aplicada, el visitante encuentra la fricción correcta en cada servicio —lectura para los que se venden por catálogo, chat para los que se venden consultando— y el equipo comercial recibe leads ya tibios, con el contexto pre-cargado en el primer mensaje. Mal aplicada —todos los botones verdes o todos inline—, el componente sigue funcionando, pero el catálogo se vuelve un panel de configuración donde el visitante elige por estética. El código es trivial; la disciplina editorial es la que convierte.
Sigue leyendo
- Módulo Service card en vivo
- Catálogo de servicios data-driven en Astro
- Construir un sitio profesional con Astro y Markdown: 12 módulos
- WhatsApp click-to-chat docs (faq.whatsapp.com) (sintaxis oficial
wa.me/...?text=...) - WCAG 2.2 SC 2.5.5 — Target Size (Minimum) (W3C, criterio de 24×24 CSS pixels mínimo)