# Referencia de seguimiento para desarrolladores: window.RTR_HOOKS

Cómo envían los widgets de Rentware los eventos de reserva al dataLayer, a gtag, al píxel de Meta, a Umami y al píxel de OpenAI: opciones, payloads, callbacks, configuración de GTM y consentimiento.

Esta página está pensada para desarrolladores y agencias que conectan los widgets de reserva de Rentware con sus propias etiquetas. Describe la parte del **navegador**. El seguimiento en el servidor a través de Rentware (GA4, Google Ads y Meta desde nuestro servidor) no necesita código: consulta [Seguimiento de conversiones: qué configurar y dónde](https://rentware.com/es/help/seguimiento-de-conversiones/).

## Resumen

- El widget **no envía nada** a las etiquetas del navegador a menos que la página defina `window.RTR_HOOKS`. El nombre antiguo `window.RENTWARE_HOOKS` también funciona; si existen los dos, prevalece `RTR_HOOKS`.
- `RTR_HOOKS` se lee cada vez que se dispara un evento. Puede definirse tarde, por ejemplo desde una etiqueta de GTM, siempre que exista antes del evento.
- El widget nunca carga una etiqueta por sí mismo. `gtag`, `fbq`, `umami` y `oaiq` tienen que estar ya en la página; si falta una función, ese canal se omite sin avisar. Solo el `dataLayer` se crea si no existe.
- El widget no comprueba el consentimiento para tus etiquetas. Cárgalas solo después del consentimiento (es tarea de tu banner de cookies).
- Todos los importes son **netos**: descuentos aplicados, fianzas excluidas, IVA en `tax`. Los importes son números en unidades principales (`49.5`), salvo donde se indique.

## Fragmento

```html
<script>
  window.RTR_HOOKS = Object.assign(window.RTR_HOOKS || {}, {
    // eventos de comercio electrónico al estilo GA4 en window.dataLayer (Google Tag Manager; funciona sin GTM)
    enableDataLayer: true,

    // gtag('event', …) directamente
    // enableGtagTracking: true,
    // gtag('event', 'conversion', …) adicional en la compra
    // gtagAdsSendTo: 'AW-123456789/AbCdEfGhIj',
    // gtag('set', 'user_data', …) para conversiones mejoradas
    // enableGtagUserDataTracking: true,

    // fbq('track', …) en tu píxel de Meta
    // enableMetaTracking: true,
    // además fbq('init', id, {…}) con datos del cliente (coincidencia avanzada)
    // metaPixelId: '123456789012345',

    // umami.track(…)
    // enableUmamiTracking: true,
    // oaiq('measure', 'order_created', …)
    // enableOpenAiTracking: true,

    // onAddToCart: (data) => {},
    // onInitiateCheckout: (data) => {},
    // onCheckoutSuccess: (data) => {},
  })
</script>
```

`Object.assign` conserva las claves que otro script ya haya definido.

## Todas las opciones

| Clave | Tipo | Efecto |
|---|---|---|
| `enableDataLayer` | boolean | Envía `add_to_cart`, `begin_checkout`, `add_shipping_info` y `purchase` a `window.dataLayer` |
| `enableGtagTracking` | boolean | Llama a `gtag('event', …)` con los mismos cuatro eventos |
| `gtagAdsSendTo` | string | En la compra, llama además a `gtag('event', 'conversion', {…, send_to})`, p. ej. `'AW-123456789/AbCdEfGhIj'` |
| `enableGtagUserDataTracking` | boolean | Cuando se guardan los datos del cliente, llama a `gtag('set', 'user_data', {…})` |
| `enableMetaTracking` | boolean | Llama a `fbq('track', …)`: `AddToCart`, `InitiateCheckout`, `Purchase` |
| `metaPixelId` | string | Con `enableMetaTracking`: cuando se guardan los datos del cliente, llama a `fbq('init', metaPixelId, {…})` con los datos del cliente |
| `enableUmamiTracking` | boolean | Llama a `umami.track(…)`: `add_to_cart`, `begin_checkout`, `purchase` |
| `enableOpenAiTracking` | boolean | En la compra, llama a `oaiq('measure', 'order_created', …)` |
| `onAddToCart` | function | Se llama después de añadir un artículo |
| `onInitiateCheckout` | function | Se llama cuando se abre el checkout |
| `onCheckoutSuccess` | function | Se llama cuando se completa la reserva |

## Eventos por paso

| Paso | dataLayer / gtag | Píxel de Meta | Umami | OpenAI | Se dispara cuando |
|---|---|---|---|---|---|
| Añadir al carrito | `add_to_cart` | `AddToCart` | `add_to_cart` | – | se ha añadido un artículo correctamente |
| Checkout abierto | `begin_checkout` | `InitiateCheckout` | `begin_checkout` | – | se abre el checkout |
| Datos del cliente guardados | `add_shipping_info` | – | – | – | se guarda el formulario de contacto y facturación |
| Reserva completada | `purchase` | `Purchase` | `purchase` | `order_created` | consulta «Cuándo se dispara purchase» |

No hay ningún evento del navegador para elegir ubicación y fechas, para ver un artículo ni para iniciar el pago (`add_payment_info` solo existe en el servidor). No hay `CustomEvent` del DOM.

### Cuándo se dispara purchase

`purchase` se dispara cuando la reserva se **completa**, que no siempre coincide con el momento del pago:

- **Reserva, pago por adelantado o transferencia bancaria, vale:** justo después de enviarla (aún sin pagar).
- **Pago con tarjeta dentro del checkout:** cuando el pago se realiza correctamente.
- **Pagos con redirección (PayPal, métodos de pago que salen de la página):** solo cuando el visitante vuelve a una página con el widget. Si no vuelve nunca, no hay `purchase` en el navegador. La compra en el servidor se envía de todos modos.

## dataLayer

Antes de cada evento, el widget envía `{ ecommerce: null }` para borrar el objeto de comercio electrónico anterior, como recomienda Google.

```js
// add_to_cart: cada artículo cuya cantidad ha aumentado, incluidos los añadidos automáticamente
{ event: 'add_to_cart', ecommerce: {
  currency: 'EUR',
  value: 59.5,                      // Σ precio × cantidad
  items: [{ item_id: 'a1b2c3', item_name: 'SUP Board', price: 29.75, quantity: 2, index: 0 }]
} }

// begin_checkout y add_shipping_info: valor neto del carrito
{ event: 'begin_checkout', ecommerce: { currency: 'EUR', value: 59.5 } }
{ event: 'add_shipping_info', ecommerce: { currency: 'EUR', value: 59.5 } }

// purchase
{ event: 'purchase', ecommerce: {
  transaction_id: 'SNLDX1',         // referencia del pedido
  currency: 'EUR',
  value: 59.5,                      // neto, descuentos aplicados, fianzas excluidas
  tax: 11.31,                       // IVA
  items: [{ item_id: 'a1b2c3', item_name: 'SUP Board', price: 29.75, quantity: 2 }]
} }
```

- `price` es el precio unitario neto con descuento. Las líneas de fianza nunca están en `items`.
- `transaction_id` es la **referencia del pedido** (p. ej. `SNLDX1`). El servidor envía el mismo valor como `transaction_id` de GA4, `order_id` de Google Ads y `order_id` de Meta.
- El dataLayer **no** contiene correo electrónico, teléfono ni ID de evento.

## gtag

Con `enableGtagTracking`, el widget llama a `gtag('event', name, params)` con los mismos objetos `ecommerce` que el dataLayer (los items de `purchase` también llevan `index`).

Con `gtagAdsSendTo`, la compra desencadena además:

```js
gtag('event', 'conversion', {
  transaction_id: 'SNLDX1', currency: 'EUR', value: 59.5, tax: 11.31,
  items: [/* como arriba */],
  send_to: 'AW-123456789/AbCdEfGhIj'
})
```

Con `enableGtagUserDataTracking`, cuando se guardan los datos del cliente:

```js
gtag('set', 'user_data', {
  email: 'jane@example.com',
  phone_number: '+4915112345678',   // solo si es un E.164 válido; si no, se omite
  address: { first_name: 'Jane', last_name: 'Doe', city: 'Berlin', postal_code: '10115', country: 'DE' }
})
```

Se usa la dirección de envío si el cliente la ha introducido; si no, la de facturación.

## Píxel de Meta

```js
fbq('track', 'AddToCart', { content_ids: ['a1b2c3'], content_type: 'product', currency: 'EUR', value: '29.75' })

fbq('track', 'InitiateCheckout', { value: '59.50', currency: 'EUR' })

fbq('track', 'Purchase', {
  order_id: 'SNLDX1',
  currency: 'EUR',
  value: '59.50',
  content_ids: ['a1b2c3'],
  content_type: 'product',
  contents: [{ id: 'a1b2c3', quantity: 2, price: '29.75', item_name: 'SUP Board' }],
  num_items: 1                      // número de líneas del carrito
}, { eventID: 'purchase_SNLDX1' })
```

- Los importes de Meta son cadenas con dos decimales.
- `eventID` es `purchase_` + referencia del pedido, el mismo `event_id` que el Purchase de la API de conversiones en el servidor, así que Meta deduplica ambos.
- Con `metaPixelId`, cuando se guardan los datos del cliente: `fbq('init', metaPixelId, { em, fn, ln, ct, zp, country })` (valores sin cifrar; el píxel les aplica el hash).

## Umami

Con `enableUmamiTracking` y el script de Umami en la página:

```js
umami.track('add_to_cart', { item_id: 'a1b2c3', item_name: 'SUP Board', quantity: 1, currency: 'EUR', value: 29.75 })
umami.track('begin_checkout', { value: 59.5, currency: 'EUR' })
umami.track('purchase', { revenue: 59.5, currency: 'EUR', transaction_id: 'SNLDX1', num_items: 1 })
```

Solo `purchase` lleva `revenue`. Los campos vacíos se omiten. Umami no instala cookies; tú decides si pides consentimiento.

## Píxel de OpenAI Ads

Con `enableOpenAiTracking` y el píxel de OpenAI (`oaiq`) en la página:

```js
oaiq('measure', 'order_created', {
  type: 'contents',
  amount: 5950,                     // unidades menores (céntimos), neto
  currency: 'EUR',
  contents: [{ id: 'a1b2c3', name: 'SUP Board', content_type: 'product', quantity: 2 }]
}, { event_id: 'purchase_SNLDX1' })
```

También puedes dejar la opción desactivada y llamar tú mismo a `oaiq` en `onCheckoutSuccess`.

## Callbacks

Los errores que se lanzan dentro de un callback se capturan y se registran en la consola, para que no rompan la reserva.

```js
onAddToCart({ productId, quantity, currency, name })
```

`productId` es el ID del artículo y `quantity`, la cantidad añadida.

```js
onInitiateCheckout({ transactionTotal, transactionId })
```

`transactionTotal` es el **importe a pagar** (bruto, fianzas incluidas), no el valor neto del carrito que usan los demás eventos. `transactionId` es la referencia del pedido.

```js
onCheckoutSuccess({ ecommerceData, fbqData, eventID, price })
```

- `ecommerceData.purchase`: el objeto `purchase` del dataLayer (`transaction_id`, `currency`, `value`, `tax`, `items`).
- `fbqData`: los parámetros del Purchase de Meta (sin `order_id`).
- `eventID`: `purchase_` + referencia del pedido.
- `price`: objeto heredado `{ gross, net, vat }` en céntimos, fianzas incluidas. Usa `ecommerceData` en su lugar.

Ejemplo, un píxel propio:

```js
window.RTR_HOOKS = Object.assign(window.RTR_HOOKS || {}, {
  onCheckoutSuccess: ({ ecommerceData, eventID }) => {
    const p = ecommerceData.purchase
    myPixel('purchase', { order: p.transaction_id, value: p.value, currency: p.currency, dedup: eventID })
  },
})
```

## Google Tag Manager

1. Añade el fragmento con `enableDataLayer: true`, ya sea en la página o como etiqueta **HTML personalizado** (Custom HTML) de GTM con el activador **Inicialización - Todas las páginas** (Initialization – All Pages).
2. Crea activadores de tipo **Evento personalizado** (Custom Event) para `add_to_cart`, `begin_checkout`, `add_shipping_info` y `purchase`.
3. Crea variables de tipo **Variable de capa de datos** (Data Layer Variable, versión 2), por ejemplo `ecommerce.value`, `ecommerce.currency`, `ecommerce.transaction_id`, `ecommerce.items`.
4. Etiquetas de evento de GA4: nombre del evento `{{Event}}` o el nombre fijo, y «Enviar datos de comercio electrónico» con la fuente de datos «Capa de datos».
5. **Purchase de Meta a través de GTM:** define el ID de evento como `purchase_{{DLV - ecommerce.transaction_id}}`. Así coincide con el Purchase del servidor y Meta los deduplica.

Si también usas el seguimiento en el servidor a través de Rentware para la misma propiedad de GA4, no envíes allí también los eventos del dataLayer: GA4 solo deduplica `purchase` (por `transaction_id`), así que add to cart y los demás eventos del embudo se contarían dos veces.

## Consentimiento

El widget lee el consentimiento para lo que envía al **servidor de Rentware** (que decide qué va a GA4, Google Ads y Meta). No controla tus propias etiquetas.

- Lee **Google Consent Mode v2** de `window.dataLayer`: entradas `gtag('consent', 'default' | 'update', {…})` y objetos al estilo de GTM como `{ event: 'consent_update', ad_storage: 'granted', … }`. Los aplica en orden: un `update` posterior sobrescribe los valores anteriores.
- Señales: `ad_storage`, `analytics_storage`, `ad_user_data`, `ad_personalization`. Cada una es `denied` por defecto.
- Si la página no tiene ninguna entrada de Consent Mode, la solicitud se marca como «sin señal de consentimiento», y el registro de seguimiento muestra `skipped: no consent signal`.
- Las entradas `default` específicas de una región (con `region`) no se pueden asociar a la ubicación del visitante en el navegador: solo se aplican sus valores `denied`.
- **El consentimiento tardío funciona:** el consentimiento se vuelve a leer en cada solicitud. Un visitante que acepta después de que se cargue el widget queda cubierto a partir de la siguiente solicitud, siempre que tu banner envíe un `update` de Consent Mode a `window.dataLayer`.
- El widget solo lee el dataLayer llamado `dataLayer` en la misma ventana. Un dataLayer con otro nombre, o un widget dentro de un iframe, significa que no hay señal de consentimiento.

Para comprobar una página: acepta el banner, abre la consola y escribe `dataLayer`. Tiene que haber una entrada `consent` (o un evento `consent_update`) antes de que el visitante reserve.

## ID de clic

- El widget captura `gclid`, `gbraid` y `wbraid` de la URL (`gclid` también del parámetro del vinculador `_gl` de Google o de la cookie `_gcl_aw`) y `fbclid` de la URL o de la cookie `_fbc` de Meta. La URL prevalece sobre la cookie.
- La captura se hace **solo con consentimiento `ad_storage`**. Antes de eso no se lee ninguna cookie ni se guarda nada. Si el consentimiento llega más tarde mientras el ID de clic sigue en la URL, se captura en ese momento.
- Los ID de clic se guardan en `sessionStorage` para la pestaña (misma pestaña, mismo dominio; desaparecen al cerrarla). Gana el primer clic.
- El widget los envía con sus solicitudes al servidor de Rentware, que los usa para Google Ads y Meta. No se añaden a tus eventos del navegador.
- ¿La página de destino y la de reserva son distintas? Ejecuta también el script del widget en la página de destino, o deja que tu etiqueta de Google (con `url_passthrough`) o tu píxel de Meta escriba la cookie `_gcl_aw` / `_fbc`. `gbraid` y `wbraid` no tienen cookie de respaldo.

## Lista de comprobación

1. Añade `RTR_HOOKS`, abre la página y acepta el banner.
2. Añade un artículo al carrito y escribe `dataLayer` en la consola: las últimas entradas son `{ ecommerce: null }` y `{ event: 'add_to_cart', … }`.
3. Completa una reserva con «reservar» o pago por adelantado y revisa la entrada `purchase`; su `transaction_id` es la referencia del pedido.
4. Después, cancela la reserva de prueba en Rentware.
