# Conectar otro CMS (/es/guides/custom-cms)

Para Shopify, utiliza la [integración nativa](/es/second-purchase/shopify) y el programa guiado de **Segunda compra**. Para otro CMS o una tienda a medida, tu backend puede sincronizar contactos, enviar eventos y activar flujos personalizados mediante la API existente de Bitelio.

Esta guía describe una integración a medida, no un plugin de WooCommerce o PrestaShop. No importa un historial de pedidos ni un catálogo, no habilita el bloque Thank You de Shopify ni alimenta el programa guiado de Segunda compra. Tu CMS sigue siendo responsable de determinar qué pedidos están pagados, entregados, cancelados o reembolsados, y cuáles son recompras.

## 1. Prepara la integración

Durante el desarrollo, utiliza un proyecto separado de Bitelio y contactos ficticios. Empieza sin flujos activos ni webhooks de salida. Las peticiones siguientes escriben contactos y eventos reales; el registro de eventos no tiene una opción de simulación y una clave secreta de prueba no pone `/v1/track` en modo de prueba.

Mantén una cola persistente de operaciones pendientes en tu backend. Verifica los webhooks del CMS, elimina duplicados, procesa los cambios de cada cliente en orden y guarda los identificadores que devuelva Bitelio. Define en tu tienda qué cuenta como primera compra válida y como segunda compra, incluyendo identidad de invitados, suscripciones, pedidos de prueba, cancelaciones y reembolsos.

| Operación                                     | Autenticación                              | Finalidad                                               |
| --------------------------------------------- | ------------------------------------------ | ------------------------------------------------------- |
| `POST /v1/contacts`, `PATCH /v1/contacts/:id` | Clave secreta (`sk_*`) con `contacts:edit` | Mantener datos y consentimiento del contacto            |
| `POST /v1/track`                              | Clave pública (`pk_*`) del mismo proyecto  | Registrar un evento personalizado y avisar a los flujos |

Realiza estas llamadas desde tu servidor. Nunca pongas una clave secreta en el navegador, un tema o un repositorio público. `/v1/track` acepta **solo la clave pública**: usarla desde un servidor no la convierte en secreta ni demuestra que una compra haya ocurrido. Cualquiera que tenga esa clave pública puede enviar eventos, por lo que estos no deben autorizar reembolsos, recompensas, accesos u otras acciones financieras.

## 2. Registra al cliente sin inventar consentimiento

Este ejemplo registra a un cliente nuevo que no ha dado consentimiento de marketing:

```bash
curl -X POST https://api.bitelio.com/v1/contacts \
  -H "Authorization: Bearer sk_YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "buyer@example.com",
    "subscribed": false,
    "data": {
      "cms_order_count": 1,
      "cms_repurchase_eligible": false
    }
  }'
```

La respuesta es el propio contacto, con su `id` en el primer nivel: `201` si es nuevo y `200` si ya existía. Guarda ese ID junto al cliente en tu CMS.

Este endpoint crea o actualiza por email. Enviar explícitamente `subscribed: false` también da de baja a un contacto existente, así que utiliza este ejemplo exacto solo para un cliente inicial sin consentimiento. En posteriores actualizaciones de pedidos, omite `subscribed` para conservar su elección actual. Envía `subscribed: true` solo tras un consentimiento real y registrado; una compra no permite revertir una baja. Consulta [importación de consentimiento](/es/guides/importing-consent) y [doble opt-in](/es/recipes/double-opt-in).

## 3. Actualiza la elegibilidad y registra la entrega

Cuando el CMS confirme que la primera compra válida está entregada y no hay incidencias que la excluyan, actualiza el contacto usando el ID obtenido antes:

```bash
curl -X PATCH https://api.bitelio.com/v1/contacts/CONTACT_ID \
  -H "Authorization: Bearer sk_YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{"data":{"cms_repurchase_eligible":true}}'
```

Después registra un único evento para esa entrega:

```bash
curl -X POST https://api.bitelio.com/v1/track \
  -H "Authorization: Bearer pk_YOUR_PUBLIC_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: cms:first-delivered:order-1001:v1" \
  -d '{
    "event": "cms.first_order.delivered",
    "email": "buyer@example.com",
    "data": {"order_id":{"value":"order-1001","persistent":false}}
  }'
```

Envíalo solo después de haber creado correctamente el contacto. El registro de eventos puede crear un contacto que falte, y los contactos nuevos quedan suscritos por defecto si omites `subscribed`. Aquí la omisión conserva la elección del contacto existente. El formato `persistent: false` mantiene el ID del pedido en ese evento sin añadirlo a los campos del contacto.

Utiliza un espacio de nombres propio, como `cms.*`. No imites eventos del sistema bajo `shopify.*`, `pixel.*` o `email.*`; la API pública de eventos los rechaza.

Cuando haya una segunda compra, cancelación, reembolso u otra exclusión, cambia `cms_repurchase_eligible` a `false` antes de registrar su evento. Mantén ese estado actualizado mientras un flujo esté esperando. Bitelio no lo calcula a partir del contenido de pedidos personalizados.

## 4. Construye el flujo personalizado

En **Flujos de trabajo**, crea un flujo desactivado con esta secuencia:

1. Disparador: `cms.first_order.delivered`.
2. Espera: el intervalo que elijas tras la entrega.
3. Condición: el dato actual del contacto `data.cms_repurchase_eligible` es igual a `true`.
4. Sí: enviar una plantilla revisada de **Marketing** con un enlace fijo a tu tienda. No: salir.

Mantén desactivada la reentrada si el cliente debe entrar una sola vez. Comprueba el campo actual `data.*`, no la captura original de `event.*`. Un paso `WAIT_FOR_EVENT` solo escucha mientras espera; no busca compras anteriores. Los envíos de marketing también comprueban la suscripción y las preferencias de temas.

Editar flujos requiere `workflows:edit`; iniciar ejecuciones manualmente y cancelarlas requiere `workflows:publish`. Utiliza los permisos existentes de la cuenta. El flujo personalizado no utiliza el interruptor de ejecución de Retention, así que ese interruptor no sirve como entorno de pruebas para esta integración.

Para detener trabajo que ya está en marcha, cancela sus ejecuciones pendientes. Desactivar el flujo solo impide nuevas ejecuciones. La condición y el envío posterior no son una comprobación atómica del pedido: una cancelación urgente también debe cancelar la ejecución pendiente desde tu backend o el panel.

## 5. Mide la segunda compra que comunicas

Cuando el CMS confirme que el cliente ha realizado su **segunda** compra válida, cambia la elegibilidad a `false` y envía este evento una sola vez. No lo envíes para cada compra posterior:

```bash
curl -X POST https://api.bitelio.com/v1/track \
  -H "Authorization: Bearer pk_YOUR_PUBLIC_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: cms:second-paid:order-1002:v1" \
  -d '{
    "event": "cms.second_order.paid",
    "email": "buyer@example.com",
    "data": {"order_id":{"value":"order-1002","persistent":false}}
  }'
```

En **Configuración → Datos → Objetivos de conversión**, crea un objetivo llamado exactamente `cms.second_order.paid` y asígnalo al flujo. Deja vacío el campo de importe para contar conversiones sin dinero. Crear objetivos requiere `settings:manage`, asignarlos a flujos requiere `workflows:edit` y consultar resultados requiere `analytics:view`.

Son conversiones comunicadas por tu integración y fechadas cuando Bitelio recibe el evento. Una fecha histórica dentro del contenido no las sitúa en el pasado. La atribución da crédito a un clic o apertura reciente; no demuestra que el mensaje haya causado la compra. Si añades campos de importe y moneda, el valor es **ingreso bruto**, sin reversión automática de reembolsos, costes ni margen. Con campos no persistentes, las rutas del objetivo deben incluir `.value`, por ejemplo `amount.value` y `currency.value`.

Un [grupo de control con objetivo de conversión](/es/guides/holdouts) permite comparar enviar frente a no enviar sin Shopify. Retiene marketing elegible en todo el **proyecto**, no solo en este flujo, y necesita un plan de medición explícito. No lo actives simplemente para probar esta guía.

## 6. Valida la integración y gestiona reintentos

* Prueba únicamente en el proyecto aislado, con contactos ficticios y sin webhooks de salida activos. Mantén los flujos desactivados para inspeccionar primero los eventos guardados. Para probar ejecuciones, utiliza un flujo temporal con condiciones y salida, sin acciones de correo, SMS ni webhook.
* Comprueba que repetir una petición devuelve `409` con `IDEMPOTENCY_KEY_REUSED` y guarda los IDs de la primera respuesta correcta. Las claves pertenecen al proyecto y se retienen 24 horas por defecto; no evitan duplicados de pedidos de forma permanente ni reproducen la primera respuesta.
* Mantén tu propia deduplicación persistente. Un timeout o `5xx` puede llegar después de una escritura: no generes otra clave para repetir a ciegas. Comprueba antes el historial de contactos y eventos. Respeta `Retry-After` ante un `429`, limita los reintentos y evita importaciones de contactos sin límites.
* Un `200` al registrar confirma el evento guardado, no la entrega del correo. Comprueba por separado las ejecuciones y los registros de correo.
* Antes de activar el flujo real, verifica el dominio de envío, las reglas de consentimiento, los límites de facturación y la actualización de elegibilidad. Revisa el mensaje y activa solo tras comprobar la integración.

El endpoint separado del panel `/events/track` recibe `name` e identificadores de contacto en lugar de este formato. No ofrece el contrato de idempotencia ni de límite de peticiones del endpoint público; no es un sustituto directo.

Consulta [declaración de eventos](/es/guides/declaring-events), [idempotencia](/es/guides/idempotency), [flujos](/es/concepts/workflows) y [objetivos de conversión](/es/guides/conversion-goals) para los contratos individuales.
