El informe de pagos muestra 150,00. La exportación de analítica también. Ambos contienen tres identificadores de pedido distintos. Es tentador dar por buena la integración.

Esas comprobaciones pueden cumplirse aunque los informes describan pedidos diferentes.

Este pequeño contraejemplo se puede ejecutar. Usa datos inventados, no un incidente de un cliente ni una afirmación sobre un proveedor de analítica. Muestra qué no demuestra una comparación de totales y cuál es la siguiente comprobación.

Las sumas iguales no demuestran que los registros coincidan

Una tienda tiene tres pedidos pagados: O101 por 40,00, O102 por 60,00 y O103 por 50,00. Una exportación de eventos de compra sin procesar contiene O101 dos veces, O102 una vez y un O999 sin correspondencia por 10,00. Todos los importes usan la misma moneda ficticia con dos decimales.

ID de pedido Importe del pedido pagado Importes de eventos sin procesar Filas de eventos
O101 40,00 40,00 + 40,00 2
O102 60,00 60,00 1
O103 50,00 Ningún evento 0
O999 Sin pedido en este ejemplo 10,00 1
Total 150,00 150,00 4

El duplicado añade 40,00. El registro sin correspondencia añade otros 10,00. Juntos compensan exactamente los 50,00 ausentes. También hay tres IDs distintos a cada lado, pero solo dos son comunes.

El número de filas de eventos detecta una diferencia en este ejemplo. La suma y el número de IDs distintos no. Ninguno de esos agregados identifica el pedido ausente.

Ambos informes sintéticos suman 150,00 pese a contener pedidos diferentes.

Definir qué deben significar las dos exportaciones

Un evento de compra del navegador registra lo que comunicó la instrumentación. Un pedido pagado representa el estado de negocio mantenido por el sistema de pedidos o pagos. Sirven para preguntas diferentes. Uno no debe sustituir al otro sin explicarlo.

Antes de comparar, defina el contrato: tienda o cuenta, moneda, identificador compartido, estado de pago incluido, definición del importe y periodo. Decida si compara pedidos, intentos de pago o líneas de pedido. Un pedido puede incluir legítimamente varios pagos o artículos.

Valor bruto de pedidos, pagos capturados, ingresos netos tras reembolsos y transferencias tras comisiones no son intercambiables. Tampoco lo son las horas del evento, pago, ingesta y transferencia. Acuerde un corte y tenga en cuenta el retraso real de procesamiento. Un registro reciente que aún no ha llegado no está necesariamente perdido.

El contrato de este ejemplo es deliberadamente limitado: una instantánea sintética completa, tres pedidos ya pagados, un evento esperado por pedido, sin reembolsos, impuestos, comisiones, pagos divididos ni conversión de moneda. Son eventos sin procesar, no una simulación de las reglas de deduplicación de un proveedor.

Agrupar los eventos antes de unir las tablas

Ejecute el ejemplo con Python 3 y su módulo sqlite3. Crea una base de datos en memoria, sin solicitudes de red ni cambios en producción. Los importes son enteros en la unidad menor de la moneda del ejemplo. Así, la comparación no depende de aritmética binaria en coma flotante.

import sqlite3

db = sqlite3.connect(":memory:")
db.executescript("""
CREATE TABLE orders (order_id TEXT PRIMARY KEY, cents INTEGER NOT NULL);
CREATE TABLE events (event_id TEXT PRIMARY KEY, order_id TEXT NOT NULL,
                     cents INTEGER NOT NULL);
INSERT INTO orders VALUES ('O101', 4000), ('O102', 6000), ('O103', 5000);
INSERT INTO events VALUES ('E1', 'O101', 4000), ('E2', 'O101', 4000),
                          ('E3', 'O102', 6000), ('E4', 'O999', 1000);
""")

for table in ("orders", "events"):
    total, ids = db.execute(
        f"SELECT SUM(cents), COUNT(DISTINCT order_id) FROM {table}"
    ).fetchone()
    print(f"{table}: cents={total}, distinct_order_ids={ids}")

query = """
WITH observed AS (
  SELECT order_id, COUNT(*) AS event_rows, SUM(cents) AS event_cents
  FROM events GROUP BY order_id
), ids AS (
  SELECT order_id FROM orders UNION SELECT order_id FROM events
)
SELECT ids.order_id, o.cents, COALESCE(e.event_rows, 0), e.event_cents
FROM ids
LEFT JOIN orders o ON o.order_id = ids.order_id
LEFT JOIN observed e ON e.order_id = ids.order_id
ORDER BY ids.order_id
"""
for row in db.execute(query):
    print(row)
db.close()

El resultado es:

orders: cents=15000, distinct_order_ids=3
events: cents=15000, distinct_order_ids=3
('O101', 4000, 2, 8000)
('O102', 6000, 1, 6000)
('O103', 5000, 0, None)
('O999', None, 1, 1000)

La consulta agrupa primero los eventos en una fila por ID de pedido. Si se une directamente una fila de pedido a varias de eventos, el importe del pedido puede repetirse y falsear la propia conciliación. La unión de todos los IDs conserva también los registros sin correspondencia de ambos lados. Un inner join los ocultaría.

O103 no tiene evento. O999 no tiene un pedido correspondiente en el ejemplo. O101 tiene dos filas de eventos. Son tres observaciones distintas, no una diferencia inexplicable en un total.

O103 carece de evento y O999 no tiene un pedido correspondiente en el ejemplo.

En una exportación real, examine los identificadores ausentes antes de agrupar. No los sustituya silenciosamente por un valor común como unknown: registros independientes parecerían pertenecer al mismo pedido. Delimite los IDs por tienda, cuenta y entorno. Si las exportaciones usan IDs diferentes, establezca una correspondencia en lugar de unir por importes iguales o marcas de tiempo cercanas.

Una discrepancia inicia la investigación, no es un diagnóstico

En este ejemplo conocemos los defectos porque los introdujimos nosotros. En producción, un ID sin correspondencia también puede proceder de otro límite diario, un entorno de pruebas, una exportación incompleta o un estado de negocio legítimo excluido de un informe. Mantenga esas posibilidades hasta revisar los registros.

La entrega desde el navegador y la confirmación de pago tienen fallos posibles diferentes. La guía de Stripe para completar pedidos de Checkout explica por qué un pago correcto no garantiza una visita a la página de retorno. Un evento de esa página, por sí solo, no es un mecanismo completo de confirmación de pago.

La entrega del servidor también requiere cuidado. Stripe documenta entregas duplicadas de webhooks. Recibir una notificación no equivale a aplicar su efecto de negocio una sola vez de forma segura. Un ID de evento del proveedor y un ID de pedido resuelven problemas de identidad distintos: varios eventos diferentes pueden corresponder al mismo pedido. Este artículo no implementa un manejador de pagos ni prescribe una clave universal de deduplicación.

Asimismo, que sendBeacon() devuelva true significa que el navegador puso los datos en cola para enviarlos, no que la aplicación de analítica aceptó y guardó una compra. Es el contrato documentado del valor de retorno, no una prueba de pérdida en un colector concreto.

Agrupar los eventos por pedido antes de unirlos a los pedidos pagados.

Conciliar las identidades antes de interpretar la atribución

Una conciliación útil conserva listas separadas de pedidos pagados sin el evento esperado, eventos sin un pedido pagado correspondiente, filas repetidas y diferencias de importe o moneda. Mida esas categorías con reglas estables. Mantenga visibles los ingresos sin correspondencia en lugar de inventar un canal para cuadrar un gráfico de atribución.

Corregir la cobertura de recopilación no demuestra que un canal causó una compra. La atribución pregunta cómo repartir el mérito. Conviene separarla de la cuestión de si los registros de compra coinciden.

La lección no es que todos los paneles estén equivocados. Los totales coincidentes son una prueba más débil de lo que parecen. Conserve esa comprobación, pero añada una comparación por registro antes de declarar conciliada una integración de ingresos.