Arquitectura

Cuál de los 10 canales recibe cada evento

Un evento entra en el dispatcher de Beaconry. Diez canales están mirando. Ninguno recibe todos los eventos. GA4 quiere el funnel completo, Meta quiere conversiones más un par de señales de mid-funnel, X Ads quiere solo los cinco tipos de conversión que preconfiguraste. El enrutamiento no es ad-hoc por método de dispatch, son dos pequeñas estructuras de datos en un archivo. Este es el mapa completo, verificado contra el código fuente.

Tiempo de lectura: ~9 minPublicado: 2026-06-08

El problema que resuelve el enrutamiento

Cada API de conversiones tiene su propio vocabulario de eventos estándar. Meta conoce Purchase y AddToCart. TikTok conoce Purchase e InitiateCheckout. Pinterest conoce checkout y view_content. Se solapan mucho, pero no del todo, y los huecos son donde los setups de tracking ingenuos se rompen en silencio.

Aparecen dos modos de fallo si haces fan-out a ciegas. Primero, doble conteo: una página de tienda WooCommerce que lista productos dispara tanto view_item_list como view_item, y en Meta ambos mapean al mismo nombre de evento (ViewContent). Envía los dos y Meta recibe dos eventos ViewContent con event_ids distintos para una sola vista de página. Ningún dedup se activa (ids distintos), así que el contador de reporting y el Smart-Bidding cuentan ambos por duplicado. Segundo, ruido de eventos custom: envía a Meta un evento scroll y no hay nombre estándar para él, así que aterriza como un Custom Event genérico que contamina el Events Manager y no aporta ningún valor de optimización.

Así que el objetivo es preciso: envía a cada plataforma exactamente los eventos para los que tiene un hogar real y optimizable, y nada más.

Dos estructuras de datos, un archivo

Todo el enrutamiento vive en class-bcnr-forwarder.php. No hay enrutamiento por canal disperso por los métodos de dispatch. Dos constantes cargan la decisión, y el bucle central dispatch() las lee.

1. ANALYTICS_ONLY_EVENTS: señales de comportamiento que nunca son una conversión publicitaria real.

private const ANALYTICS_ONLY_EVENTS = [
    'form_start',
    'form_abandon',
];

2. SKIP_BY_CHANNEL: una lista por canal de nombres de eventos GA4 que ese canal no debería recibir, cada entrada anotada con el motivo. La lista de Meta, por ejemplo, incluye view_item_list, user_engagement, refund y las señales de engagement (scroll, video_start, file_download, click).

El bucle de dispatch resulta entonces casi aburrido de leer, que es justo el punto:

// Central internal-vs-ad-vendor gate.
if ( in_array( $event['name'], self::ANALYTICS_ONLY_EVENTS, true ) ) {
    if ( self::has_ga4() ) {
        self::dispatch_ga4( $event );
        self::after_dispatch( 'ga4', $event );
    }
    return; // hard-stop before any ad channel
}

if ( self::has_ga4() ) { /* GA4 gets everything */ }

if ( self::has_meta() && self::should_dispatch_to_channel( 'meta', $event ) ) {
    self::dispatch_meta( $event );
    self::after_dispatch( 'meta', $event );
}
// ...same shape for tiktok, pinterest, x_ads, snapchat, reddit

should_dispatch_to_channel() es el portero para los canales con capacidad de browser-pixel. Comprueba el conjunto SKIP_BY_CHANNEL del canal, respeta un override por formulario name_<channel> (un formulario de lead puede volver a habilitar un evento normalmente omitido) y aplica una regla de modo híbrido para user_engagement (más sobre esto abajo).

La matriz de commerce

Aquí está el enrutamiento completo para el funnel de 10 eventos de WooCommerce. Una celda muestra el nombre del evento que realmente se envía por el cable, o "skip" cuando el canal no lo recibe. Las columnas son los siete canales que enrutan por nombre de evento. Los tres canales de OAuth-broker (LinkedIn, Google Ads, Microsoft Ads) funcionan distinto y tienen su propia sección.

Evento GA4GA4MetaTikTokPinterestSnapchatRedditX Ads
view_itemViewContentViewContentview_contentVIEW_CONTENTVIEW_CONTENTskip
view_item_listskipskipview_categoryskipskipskip
view_cartViewCart (custom)skipskipskipskipskip
add_to_cartAddToCartAddToCartadd_to_cartADD_CARTADD_TO_CARTaddtocart
remove_from_cartRemoveFromCart (custom)skipskipskipskipskip
searchSearchSearchsearchSEARCHSEARCHskip
begin_checkoutInitiateCheckoutInitiateCheckoutinitiate_checkoutSTART_CHECKOUTCUSTOMskip
add_payment_infoAddPaymentInfoAddPaymentInfoadd_payment_infoADD_BILLINGCUSTOMskip
purchasePurchasePurchasecheckoutPURCHASEPURCHASEpurchase
refundskipskipskipskipskipskip

Algunas celdas merecen el porqué, porque el porqué es todo el diseño.

view_item_list: omitido casi en todas partes

Solo GA4 y Pinterest lo reciben. GA4 tiene un evento view_item_list real. Pinterest tiene un evento view_category propio, así que mapea limpiamente. Todos los demás (Meta, TikTok, Snap, Reddit) lo mapean al mismo nombre que view_item (ViewContent / VIEW_CONTENT), lo que significa que una sola página de tienda que previsualiza un producto dispararía dos eventos del mismo nombre con event_ids distintos. Esa es la trampa del doble conteo, así que view_item_list está en el conjunto de skip para los cuatro.

view_cart y remove_from_cart: custom en Meta, descartados en el resto

Ninguno tiene un evento estándar en la mayoría de plataformas. Meta los acepta como eventos custom con nombre (ViewCart, RemoveFromCart) porque un evento custom de Meta sigue apareciendo limpiamente en el Events Manager y puede usarse para construir audiencias. En TikTok, Pinterest, Snap y Reddit se omiten: sus fallbacks de evento custom o colisionarían con un slot reservado (el CUSTOM_EVENT_1 de Snap está reservado para leads de formulario) o se tragarían en silencio (TikTok descarta nombres no mapeados), así que el evento costaría bytes y no compraría nada. El Smart-Bidding optimiza sobre AddToCart y Purchase, no sobre quitar del carrito.

begin_checkout a Reddit: un evento custom real, a propósito

El vocabulario estándar de Reddit no tiene evento de inicio de checkout. En lugar de omitirlo, Beaconry deja que caiga al tracking_type CUSTOM de Reddit con custom_event_name: 'begin_checkout'. Es una elección deliberada, no un descuido: un evento custom con nombre en Reddit sigue siendo una señal usable y reportable, a diferencia del destino de tragado-al-llegar que el mismo evento encontraría en TikTok. La regla general es "omite lo que sería ruido, conserva lo que es un evento custom usable", y la línea cae en un lugar distinto por plataforma porque cada plataforma maneja los nombres no mapeados de forma diferente.

X Ads: solo conversiones

X (Twitter) Ads requiere un conversion_event_id preconfigurado desde su Events Manager para cada tipo de evento. No hay un stream de eventos genérico al que empujar señales de mid-funnel. Así que el mapa efectivo de X son solo cinco slots (purchase, add_to_cart, lead, signup, pageview), y el resto del funnel (view_item, view_cart, search, begin_checkout, add_payment_info) simplemente no tiene slot al que ir. Las señales de engagement están en su conjunto de skip por la misma razón.

refund: GA4 y ningún otro sitio

Refund es el ejemplo más limpio de "envía a cada plataforma lo que pueda usar", porque la respuesta para nueve de los diez canales es "nada".

GA4 tiene un evento refund de primera clase que resta el ingreso contra la compra original, así que el reporting de reembolsos realmente funciona: un pedido reembolsado reduce tu ingreso reportado. Eso es valor analítico genuino, así que refund va a GA4.

Ninguna plataforma publicitaria tiene un evento de reembolso que haga algo útil. Meta es explícita: refund no es un tipo de evento soportado por la Conversions API (el manejo de reembolsos de Meta vive en la API separada de gestión de pedidos de Commerce-Platform, no en la CAPI). La Events API 2.0 de TikTok no tiene evento estándar de reembolso. Pinterest, Snapchat y Reddit tampoco tienen un evento de reembolso nativo. Podrías forzar un evento custom "Refund" en cada uno, pero sería un contador muerto: ningún Smart-Bidding lo usa, ningún ingreso se resta, y en Snap incluso colisionaría con el slot de lead. Así que cada canal publicitario lista refund en su conjunto de skip. Refund es solo-GA4, by design, verificado contra la documentación actual de cada proveedor.

Hay un análisis más extenso de esta decisión concreta en Por qué los reembolsos solo van a GA4. La versión corta: un evento de reembolso sobre el que nadie optimiza no es una feature, es ruido en el cable.

form_start y form_abandon: la puerta central

Las señales de funnel de formulario son analítica de comportamiento, no conversiones publicitarias. form_start se dispara en el primer focus de campo, form_abandon se dispara cuando alguien empezó un formulario, rellenó al menos un campo y se fue sin enviar. Te dicen dónde un formulario pierde gente, lo cual es útil para ti, e inútil (o peor, engañoso) como señal de conversión en una plataforma publicitaria.

Estos dos no los maneja SKIP_BY_CHANNEL. Se manejan un nivel por encima, mediante la puerta ANALYTICS_ONLY_EVENTS en lo más alto del bucle de dispatch. En el momento en que el dispatcher ve uno de estos nombres, envía a GA4 (si GA4 está siquiera configurado) y retorna de inmediato, antes de alcanzar la ruta de código hacia cualquier canal publicitario.

La razón de que esto sea una puerta separada y central y no simplemente diez entradas de conjunto de skip es la robustez. Con listas de skip por canal, añadir un undécimo canal publicitario significa acordarse de añadir form_abandon a su lista de skip, y olvidarlo significa que un evento de comportamiento se filtra en silencio a un proveedor publicitario como conversión custom. Con la puerta central, un canal nuevo hereda la regla gratis: el hard-stop ocurre antes de que su llamada de dispatch siquiera exista en el bucle. Para clasificar un futuro evento de comportamiento como solo-analítica, añades su nombre a ese único array y no cambias nada más.

Hay una segunda puerta apilada detrás específicamente para form_abandon. Por defecto, un abandono ni siquiera llega a GA4: BCNR_Form_Funnel::observe() lo registra en el almacén de funnel interno del plugin y luego cancela el dispatch por completo, a menos que hayas optado por form_funnel_ga4. Así que de fábrica, form_abandon no toca nada en el cable en absoluto, e incluso cuando optas por ello, llega solo a GA4, nunca a un proveedor publicitario. La historia completa de análisis de abandono está en Funnels de formulario: encuentra el campo que mata la conversión.

Los tres canales de broker enrutan distinto

LinkedIn, Google Ads y Microsoft Ads no tienen un vocabulario plano de nombres de evento al que mapear. Enrutan a través de slots de conversión que el cliente configura, así que no están en SKIP_BY_CHANNEL en absoluto. Se auto-omiten cuando el evento no mapea a uno de sus slots.

LinkedIn mapea cada evento GA4 a uno de cinco slots de regla de conversión (purchase, lead, signup, addtocart, keypageview). Cada slot solo se dispara si el cliente ha pegado en los ajustes la URN de ConversionRule de Campaign-Manager correspondiente. Sin URN, no hay dispatch.

Google Ads y Microsoft Ads comparten un mapeador de tipo de conversión, ads_conversion_kind(). Devuelve un nombre de slot exactamente para los eventos que son tipos de conversión reales de Google/Microsoft (purchase, add_to_cart, begin_checkout, generate_lead, subscribe, schedule, sign_up, contact) y una cadena vacía para todo lo demás, lo que es un skip silencioso. Así que en el funnel de commerce, Google y Microsoft Ads reciben add_to_cart, begin_checkout y purchase, e ignoran los eventos de view y de search, porque esas no son acciones de conversión configurables en esas plataformas.

Ambos necesitan además su identificador de clic para atribuir (gclid para Google, msclkid para Microsoft). Microsoft omite en silencio cualquier upload sin msclkid, porque la API de OfflineConversions lo rechaza de todos modos. Las credenciales nunca viven en el plugin: el OAuth-broker en oauth.beaconry.app guarda los refresh tokens y developer tokens, y el plugin solo carga un site-bearer firmado con HMAC. Eso es lo que te deja conectar Google y Microsoft Ads sin esperar a una aprobación de developer-token, lo cual es su propia historia en Conectar Google Ads sin la espera del developer-token.

Una sutileza: user_engagement y modo híbrido

user_engagement (disparado tras aproximadamente 10 segundos de tiempo activo más un 50 por ciento de scroll) está en el conjunto de skip para Meta y TikTok, pero el skip es condicional. Mapea a ViewContent en ambas plataformas, y enviar un ViewContent del lado del servidor solo tiene sentido cuando el browser pixel correspondiente también está corriendo, porque entonces ambos se disparan con la misma event_id y la plataforma los deduplica en un único evento enriquecido. Con el browser pixel apagado, un ViewContent solitario del lado del servidor procedente del engagement no tiene contraparte de navegador con la que fusionarse y solo infla el contador de ViewContent. Así que should_dispatch_to_channel() deja pasar user_engagement solo cuando el browser pixel híbrido de ese canal está habilitado, y lo omite en caso contrario. La mecánica de dedup por event_id detrás de eso está cubierta en Modo híbrido y dedup por event_id.

EDD y SureCart heredan el mismo enrutamiento

La matriz de enrutamiento se indexa por nombres de evento GA4, no por la plataforma de commerce, así que es agnóstica a la plataforma por construcción. Easy Digital Downloads dispara el mismo funnel menos search (no tiene evento de búsqueda de producto), y SureCart dispara purchase y refund del lado del servidor mientras sus eventos de carrito en JS llegan a través de un puente de navegador. Todos esos eventos entran en el mismísimo bucle dispatch() y golpean la mismísima lógica de SKIP_BY_CHANNEL y de slots. Un add_to_cart de SureCart enruta a los mismos siete destinos que un add_to_cart de WooCommerce, porque el dispatcher no sabe ni le importa qué adaptador lo produjo.

Cómo verificar el enrutamiento para tu propio setup

No tienes que confiar en esta matriz en abstracto. La vista de debug de cada plataforma es la verdad sobre el terreno:

  • GA4 Realtime / DebugView: deberías ver el funnel completo, incluyendo view_item_list, refund y (si está habilitado) form_start.
  • Meta Test Events: deberías ver ViewContent, AddToCart, Purchase y compañía, pero nunca un scroll o un reembolso. Un evento custom ViewCart es esperado y correcto.
  • TikTok / Pinterest / Snap / Reddit event managers: solo eventos estándar, sin ruido de engagement. Reddit mostrará begin_checkout como evento CUSTOM, lo cual es intencionado.
  • Tarjeta Live-Conversions de Beaconry: el registrador interno del plugin muestra el nombre realmente enviado por canal, así que una fila que dice "Meta ViewContent" para lo que GA4 registró como user_engagement es el mapeo de hybrid-dedup funcionando, no un bug.

Si ves un evento en una plataforma que la matriz dice que debería omitirse, el primer sospechoso es un override por formulario name_<channel> que lo vuelve a habilitar, que es la única forma sancionada de desviarse de los valores por defecto.

Para llevar

El enrutamiento de Beaconry no es un montón de condicionales repartidos por diez métodos de dispatch. Es una puerta de solo-analítica más una tabla de skip por canal, ambas en class-bcnr-forwarder.php, ambas anotadas con el motivo de cada entrada. El principio es consistente: una plataforma recibe un evento solo cuando tiene un nombre estándar para él que impulsa optimización, o un evento custom con nombre que sigue siendo usable, y se omite cuando la alternativa sería un doble conteo, un evento tragado o un contador muerto. Refund demuestra el principio en el extremo (solo GA4, porque ninguna plataforma publicitaria puede hacer nada con él), y la puerta de funnel de formulario demuestra la robustez (las señales de comportamiento nunca pueden filtrarse a un proveedor publicitario, incluidos canales aún no añadidos). Envía a cada plataforma exactamente lo que pueda usar, y nada más.