ES

Guía de arquitectura para la integración de WooCommerce con ERP 2026

Última verificación: 24 de agosto de 2026
33 min de lectura
Guía
Experto WooCommerce

Conectar una tienda digital corporativa basada en WooCommerce con un sistema de planificación de recursos empresariales (ERP) constituye uno de los desafíos de ingeniería de software más complejos en el comercio digital. Cuando el catálogo supera las cincuenta mil unidades de mantenimiento de stock (SKU), el volumen diario alcanza miles de transacciones y el inventario debe sincronizarse en tiempo real a través de múltiples canales comerciales, las integraciones síncronas tradicionales punto a punto colapsan. Las peticiones HTTP directas entre plataformas desencadenan saturación de procesos, bloqueos de bases de datos, tormentas de webhooks y sobreventa crítica de inventario en el checkout.

Respuesta directa: Cómo diseñar una integración bidireccional tolerante a fallos entre WooCommerce y ERP

Una integración bidireccional y tolerante a fallos entre WooCommerce y un sistema ERP requiere una arquitectura desacoplada, asíncrona y guiada por eventos sustentada sobre cinco pilares de ingeniería:

  • Almacenamiento asíncrono en búfer: Ingesta de webhooks y eventos de compra en una cola intermedia (Redis Streams o Cloudflare Queues) respondiendo con HTTP 202 Accepted en menos de 25 ms para independizar la tienda de la latencia del ERP.
  • Idempotencia garantizada: Deduplicación estricta de solicitudes mediante cabeceras X-Idempotency-Key y registros de bloqueo atómicos en Redis para erradicar pedidos duplicados y dobles apuntes contables.
  • Control de concurrencia transaccional: Bloqueo a nivel de fila en MySQL InnoDB (SELECT ... FOR UPDATE) o control de versiones optimista para evitar condiciones de carrera y sobreventas en momentos de alta demanda.
  • Tratamiento resiliente de errores: Aplicación de retroceso exponencial con fluctuación aleatoria (full jitter) y enrutamiento de cargas fallidas recurrentes a una cola de mensajes no entregados (Dead Letter Queue - DLQ) para análisis y reproducción.
  • Conciliación en dos niveles: Combinación de sincronizaciones delta horarias con auditorías nocturnas mediante sumas criptográficas SHA-256 por bloques para eliminar cualquier discrepancia de datos.
SLA de ingesta: Menos de 25 ms en webhooks no bloqueantes
Modelo de consistencia: Consistencia eventual con bloqueos atómicos
Estrategia de recuperación: Dead Letter Queue con protocolos de reintento

Para los equipos de desarrollo y directores de tecnología que planifican o modernizan su infraestructura de comercio electrónico, nuestros servicios de integración WooCommerce ERP proporcionan soporte especializado en arquitectura e implantación. Esta guía analiza los patrones arquitectónicos, las matrices de compatibilidad de plataformas ERP, las implementaciones reales en PHP 8.4 y los manuales operativos para resolver incidencias en producción.

#Fundamentos de arquitectura: Integración asíncrona guiada por eventos

En arquitecturas iniciales o simplistas de WooCommerce, los eventos de la tienda suelen activar peticiones HTTP directas hacia el punto de enlace del ERP. Por ejemplo, al completarse una compra, el hook woocommerce_checkout_order_processed inicia una llamada cURL síncrona hacia SAP S/4HANA, Microsoft Dynamics 365 o Comarch Optima.

Este modelo síncrono introduce una vulnerabilidad operativa determinante:

[Navegador del cliente] 
      │ (1) Enviar pedido en el checkout

[WooCommerce / PHP-FPM Worker] ──(2) POST HTTP síncrono──▶ [Endpoint ERP (Lento / No disponible)]
      │                                                           │
      │ ◀───(3) HTTP 504 Gateway Timeout (Bloqueo de 60s)────────┘

[Cliente ve pantalla de error] ──▶ Clics reiterados ──▶ Deadlocks en BD y pedidos duplicados

Cuando el sistema ERP realiza cierres contables nocturnos, mantenimientos de base de datos o sufre congestión de red, los tiempos de respuesta se incrementan desde 200 milisegundos hasta más de treinta segundos. Dado que los procesos de trabajo de PHP-FPM quedan vinculados síncronamente al socket de red, el grupo de hilos del servidor web se satura con rapidez. Los nuevos visitantes que navegan por el catálogo o intentan cargar su carrito reciben errores HTTP 504 Gateway Timeout. Asimismo, si la conexión se interrumpe tras la creación del pedido en el ERP pero antes de que WooCommerce reciba el acuse de recibo, los reintentos automáticos generarán facturas duplicadas y dobles reservas de inventario.

#El patrón de intermediario de eventos desacoplado

La fiabilidad empresarial exige separar de forma estricta la emisión de eventos de su consumo efectivo. La tienda virtual y el sistema ERP se comunican exclusivamente mediante un intermediario de eventos (event broker).

┌─────────────────────────────────────────────────────────────────────────────────────────┐
│                       PIPELINE DESACOPLADO GUIADO POR EVENTOS                           │
│ (Desacoplamiento total entre el checkout de la tienda y el procesamiento en el ERP)    │
└─────────────────────────────────────────────────────────────────────────────────────────┘

 ┌─────────────────────────┐                     ┌─────────────────────────┐
 │   Tienda WooCommerce    │                     │       Sistema ERP       │
 │ (Hub de pedidos y datos)│                     │ (Stock maestro y libros)│
 └────────────┬────────────┘                     └────────────▲────────────┘
              │                                               │
    (1) Ingesta rápida                              (4) Envío controlado
       (No bloqueante)                                 (Ajustado a límites)
              ▼                                               │
 ┌────────────────────────────────────────────────────────────┴───────────────────────────┐
 │                               BROKER DE MENSAJES Y BÚFER                               │
 │                          (Redis Streams / Cloudflare Queues)                           │
 │                                                                                        │
 │   ┌──────────────────────┐  ┌──────────────────────┐  ┌────────────────────────────┐   │
 │   │  orders.incoming     │  │  inventory.delta     │  │  dead.letter.queue (DLQ)   │   │
 │   │  [Evento 1][Evento 2]│  │  [SKU 101][SKU 102]  │  │  [Cargas fallidas + trazas]│  │
 │   └──────────┬───────────┘  └──────────┬───────────┘  └─────────────▲──────────────┘   │
 └──────────────┼─────────────────────────┼────────────────────────────┼──────────────────┘
                │                         │                            │
      (2) Lectura del stream    (5) Actualización stock       (3) Límite de
         por grupo consumidor      con bloqueos InnoDB           reintentos superado
                ▼                         ▼                            │
 ┌─────────────────────────────────────────────────────────────────────┴──────────────────┐
 │                           POOL DE DEMONIOS WP-CLI (PHP 8.4)                            │
 │                                                                                        │
 │  - Control de señales POSIX (SIGTERM/SIGINT) - Validación de idempotencia (SETNX)      │
 │  - Gestión de memoria (wp_cache_flush)       - Retroceso exponencial con jitter        │
 └────────────────────────────────────────────────────────────────────────────────────────┘
  1. Captura inmediata de eventos: Al formalizarse un pedido, WooCommerce escribe una carga compacta en un flujo de Redis (orders.incoming) y muestra la confirmación al comprador en menos de 15 milisegundos.
  2. Procesamiento asíncrono en segundo plano: Un conjunto de procesos supervisados ejecutados mediante WP-CLI consume mensajes del flujo al ritmo admisible por la interfaz del ERP.
  3. Contrapresión controlada (backpressure): Si el ERP limita las conexiones o entra en una ventana de mantenimiento, los mensajes se almacenan de manera segura en el búfer sin degradar la experiencia de usuario en la tienda.
  4. Ejecución idempotente: Cada mensaje contiene una clave determinista única. Si un proceso consumidor se detiene de forma anómala, el proceso sustituto reanuda el trabajo pendiente sin duplicar registros en la base de datos.

#Matriz de sistemas ERP y compatibilidad de protocolos

Cada software de gestión empresarial impone particularidades en cuanto a arquitectura de red, restricciones de concurrencia, autenticación y capacidad de procesamiento. Una integración exitosa requiere adaptar la capa de comunicación a cada entorno específico.

La siguiente tabla compara cuatro sistemas ERP representativos en el mercado europeo e internacional integrados con WooCommerce:

Plataforma ERPProtocolos nativosPerfil de rendimientoModelo de concurrencia y bloqueoPrincipales modos de fallo arquitectónico
SAP S/4HANAOData v4, IDoc vía SAP BTP, RFC, Async SOAPElevado rendimiento por lotes (10k+ registros/min); latencia media en llamadas individualesSAP Logical Unit of Work (LUW); bloqueos en Enqueue Server; límites de batch en ODataSaturación del pool de conexiones al reiniciar SAP Cloud Connector; tiempos de espera en estructuras BOM complejas
Comarch Optima / XLOptima WebAPI, automatización COM DLL, tablas staging en MS SQLRendimiento API moderado (50-100 pedidos/min); muy elevado en staging SQL (50k/min)Single-Threaded Apartment (STA) en COM; escalado de bloqueos en MS SQL (sp_lock)Fugas de memoria en procesos COM que obligan a reinicios; bloqueos de licencias por hilos colgados
InsERT Subiekt GT / nexoSubiekt GT Sfera (COM/OLE), Subiekt nexo PRO SDK (.NET / WebAPI)GT: 30-80 pedidos/min; nexo PRO: 250+ pedidos/minBloqueos de fila en SQL Server en dok__Dokument y tw__Towar; limitaciones COM de escritorioBloqueos de hilos COM por cuadros de diálogo modales en GT; fragmentación de índices con catálogos extensos
Microsoft Dynamics 365 BCBusiness Central REST API v2.0, OData v4, AL API PagesLímite de 600 peticiones/min en la nube; procesamiento JSON batch (100 peticiones secundarias)Aislamiento por instantánea en Azure SQL; bloqueos en cabeceras de venta al registrarThrottling HTTP 429; pérdida de notificaciones webhook durante facturaciones masivas

#Patrones de integración para SAP S/4HANA

En entornos SAP S/4HANA, la comunicación suele estructurarse a través de SAP Business Technology Platform (SAP BTP) y SAP Cloud Connector. Las llamadas síncronas individuales mediante OData v4 (API_SALES_ORDER_SRV) presentan latencias habituales de entre 450 y 800 milisegundos por petición.

Para tiendas con volumen de ventas elevado resulta imprescindible adoptar mecanismos por lotes:

  • Agrupación JSON (Batching): Empaquetar hasta cien pedidos de venta en una única petición multipart OData. Esto reduce la sobrecarga de negociación de conexiones y asegura que todos los documentos se procesen de forma atómica.
  • Colas intermedias IDoc: Para cargas masivas de catálogo o actualizaciones globales de precios se emplean interfaces IDoc (como ORDERS05 o MATMAS05) procesadas en segundo plano dentro de SAP.
  • Control de unidades lógicas (SAP LUW): SAP gestiona la integridad transaccional mediante Logical Units of Work. La capa de integración debe almacenar el número de documento asignado por SAP en una tabla de correspondencias (wp_wc_orders_erp_lookup), vinculado al identificador del pedido en WooCommerce.

#Comarch ERP Optima y Comarch ERP XL

Comarch Optima cuenta con una amplia implantación en empresas medianas de Europa Central. Diseñado originalmente sobre Microsoft SQL Server para redes locales Windows, su conexión moderna mediante API exige consideraciones específicas.

  • WebAPI frente a automatización COM: Aunque Optima WebAPI expone servicios REST, internamente instancia componentes COM (Optima.dll). Esto obliga a inicializar cada hilo de ejecución en el modelo Single-Threaded Apartment (STA) mediante CoInitialize.
  • Gestión de puestos y licencias: La ejecución simultánea de múltiples procesos PHP sin control de colas agota rápidamente los puestos de licencia disponibles del módulo de integración, generando rechazos inmediatos.
  • Arquitectura con tablas de staging: En entornos B2B con decenas de miles de variaciones de stock por hora, la arquitectura más fiable consiste en replicar los datos desde instantáneas de solo lectura de SQL Server a Redis, mientras que la creación de documentos de venta se delega en servicios dedicados de Windows con ejecución secuencial.

#InsERT Subiekt GT y Subiekt nexo PRO

InsERT Subiekt GT recurre a la interfaz Sfera para Subiekt GT (COM/OLE Automation), mientras que Subiekt nexo PRO ofrece un SDK sobre .NET y servicios WebAPI avanzados.

  • Restricciones en Subiekt GT Sfera: Las llamadas a Sfera se ejecutan síncronamente en el entorno de escritorio de Windows. Si se produce un error no controlado o surge una ventana modal del sistema, el hilo COM queda bloqueado indefinidamente. El servicio de integración debe implementarse como un servicio de Windows administrado (en C# o Go) con supervisión continua y tiempos de espera estrictos de 30 segundos.
  • Capacidades de Subiekt nexo PRO: Nexo PRO admite procesamiento multihilo y asíncrono. Al sincronizar el árbol de productos, es aconsejable consultar las modificaciones basándose en la columna temporal Zmieniono para recuperar únicamente las novedades posteriores al último punto de control.

#Microsoft Dynamics 365 Business Central

La variante en la nube de Business Central aplica directivas de uso rigurosas:

  • Límites de peticiones (Throttling): Microsoft establece un tope de 600 llamadas por minuto por entorno. Superar esta cuota genera inmediatamente un código HTTP 429 con cabecera Retry-After.
  • Puntos de enlace $batch: Para enviar cincuenta pedidos sin emitir cincuenta peticiones HTTP independientes, se remite una solicitud POST a https://api.businesscentral.dynamics.com/v2.0/{tenant}/production/api/v2.0/$batch con un array de subpeticiones procesadas secuencialmente en la nube.
  • Captura de cambios (Change Data Capture): En lugar de consultar constantemente las variaciones de stock, se configuran suscripciones a webhooks en Business Central (notificaciones de Graph) para activar sincronizaciones diferenciales puntuales al recibir cada aviso.

#Patrones de tolerancia a fallos para transacciones de alto volumen

Una integración de nivel corporativo debe construirse bajo el principio de que los enlaces de red, servidores y bases de datos sufrirán fallos intermitentes. Cinco patrones arquitectónicos garantizan la continuidad operativa.

┌─────────────────────────────────────────────────────────────────────────────────────────┐
│                      PATRÓN DE IDEMPOTENCIA Y MANEJO DE ERRORES                         │
└─────────────────────────────────────────────────────────────────────────────────────────┘

 Petición entrante / Webhook


 ┌───────────────────────────────────┐
 │ Extraer cabecera                  │
 │ X-Idempotency-Key                 │
 └─────────────────┬─────────────────┘


 ┌───────────────────────────────────┐
 │ Bloqueo atómico en Redis:         │
 │ SETNX lock:idempotency:{key}      │
 └─────────┬─────────────────────────┘

     ┌─────┴────────────────────────┐
     │ Clave obtenida (Nueva)       │ Clave existente (Duplicado / En curso)
     ▼                              ▼
 ┌───────────────────────────┐  ┌─────────────────────────────────────────────────┐
 │ Estado: PROCESSING        │  │ Comprobar estado:                               │
 │ (TTL: 86400 segundos)     │  │ - Si 'PROCESSING': Devolver HTTP 409 Conflict  │
 └─────────┬─────────────────┘  │ - Si 'COMPLETED': Devolver respuesta en caché   │
           │                    └─────────────────────────────────────────────────┘

 ┌───────────────────────────┐
 │ Ejecutar lógica comercial │
 │ (Transacción + Escritura) │
 └─────────┬─────────────────┘

     ┌─────┴────────────────────────┐
     │ Éxito                        │ Fallo (Excepción / Timeout)
     ▼                              ▼
 ┌───────────────────────────┐  ┌─────────────────────────────────────────────────┐
 │ Actualizar estado Redis:  │  │ Calcular retroceso exponencial con jitter:      │
 │ COMPLETED + Caché Payload │  │ sleep = min(T_max, T_base * 2^intento) + rand() │
 └─────────┬─────────────────┘  └─────────────┬───────────────────────────────────┘
           │                                  │
           ▼                                  ▼
 ┌───────────────────────────┐  ┌─────────────────────────────────────────────────┐
 │ Devolver HTTP 200/201     │  │ Si intentos < 5: Reencolar en cola diferida     │
 └───────────────────────────┘  │ Si intentos >= 5: Enrutar a Dead Letter Queue   │
                                └─────────────────────────────────────────────────┘

#1. Gestión de peticiones idempotentes (X-Idempotency-Key)

En arquitecturas asíncronas, los reintentos de red y los webhooks duplicados son habituales. Sin salvaguardas de idempotencia, procesar dos veces un webhook de confirmación de pago podría originar dos órdenes de expedición o reembolsar dos veces un importe al comprador.

Toda petición de escritura debe incluir un identificador único:

  • Estructura de la clave: El cliente transmite la cabecera X-Idempotency-Key (UUIDv4) o el servidor genera un hash determinista: sha256(order_id + status + timestamp).
  • Bloqueo atómico de estado: Antes de ejecutar la acción, el proceso consumidor realiza una inserción atómica en Redis:
    SET lock:idempotency:{hash} "PROCESSING" NX EX 86400
  • Resolución de estados:
    • Si la clave se registra con éxito (OK), se ejecuta la operación. Al finalizar satisfactoriamente, el valor cambia a "COMPLETED:{json_respuesta}".
    • Si la clave ya existe con el valor "PROCESSING", otro proceso está atendiendo la solicitud. La petición duplicada devuelve HTTP 409 Conflict o aguarda la liberación.
    • Si el valor comienza por "COMPLETED:", el proceso omite la ejecución y entrega de inmediato la respuesta en caché junto con la cabecera X-Cache-Lookup: HIT.

#2. Redis Streams para ordenación fiable de eventos

Las listas simples de Redis (LPUSH/RPOP) pierden datos si un proceso consumidor sufre una caída tras extraer el elemento pero antes de consolidar la transacción en la base de datos. Redis Streams solventa este problema mediante grupos de consumidores:

  • Inserción de mensajes (XADD): Los eventos se agregan a un registro persistente con identificadores temporales en milisegundos.
  • Grupos de consumidores (XREADGROUP): Múltiples procesos consumen del mismo flujo sin duplicar tareas. Redis mantiene constancia de qué consumidor tiene asignado cada mensaje.
  • Confirmación explícita (XACK): Un mensaje solo se retira de la lista de pendientes (Pending Entries List - PEL) cuando el proceso completa las operaciones en la base de datos y ejecuta XACK.
  • Reclamación de mensajes huérfanos (XCLAIM): Si un proceso finaliza de forma abrupta, los procesos supervivientes revisan la lista PEL mediante XPENDING y reclaman con XCLAIM los mensajes inactivos durante más de 60 segundos.

#3. Colas de mensajes no entregados (DLQ) y retroceso exponencial con fluctuación aleatoria (full jitter)

Los errores de integración deben clasificarse en dos categorías:

  1. Errores transitorios: Tiempos de espera de red, respuestas HTTP 502/503/504, límites de tasa HTTP 429 y bloqueos mutuos temporales en MySQL. Estos deben reintentarse automáticamente.
  2. Errores permanentes: Errores de validación HTTP 400, recursos inexistentes 404, entidades no procesables 422 o desajustes en la configuración fiscal. No deben reintentarse de forma automática.

Para los fallos transitorios se emplea el algoritmo de retroceso exponencial con fluctuación aleatoria (full jitter), evitando que cientos de consumidores sature simultáneamente un servidor ERP en recuperación:

$$\text{Intervalo} = \min\left(T_{\text{max}}, T_{\text{base}} \times 2^{\text{intento}}\right)$$

$$\text{Tiempo de espera} = \text{random}\left(0, \text{Intervalo}\right)$$

Si un mensaje no se procesa tras cinco intentos, se traslada a la Dead Letter Queue (dlq:erp_sync). El registro en la DLQ almacena la carga original, la traza completa de la excepción, el código de estado HTTP y las marcas temporales para su análisis y posterior reproducción.


#Control de concurrencia en base de datos y bloqueo de inventario

Durante campañas de descuento o lanzamientos exclusivos, cientos de clientes intentan adquirir las mismas existencias en pocos segundos. Si dos transacciones leen el inventario simultáneamente, verifican disponibilidad y descuentan unidades en paralelo, se producirán sobreventas físicas no deseadas.

#Bloqueo pesimista: MySQL InnoDB SELECT FOR UPDATE

WooCommerce almacena los niveles de stock en la base de datos MySQL. El empleo de las funciones estándar get_stock_quantity() y wc_update_product_stock() no resulta seguro en alta concurrencia, dado que ejecutan lecturas no bloqueantes.

El bloqueo pesimista garantiza la integridad reservando la fila correspondiente durante toda la transacción:

Transacción A (Cliente 1)                       Transacción B (Cliente 2)
─────────────────────────                       ─────────────────────────
START TRANSACTION;                              START TRANSACTION;

SELECT stock_quantity                           SELECT stock_quantity
FROM wp_wc_product_meta                         FROM wp_wc_product_meta
WHERE product_id = 4500                         WHERE product_id = 4500
FOR UPDATE;                                     FOR UPDATE;
──▶ Fila bloqueada por Transacción A                ──▶ EN ESPERA (Hilo bloqueado)

Comprobar stock: 5 disponibles.
Descontar: 5 - 1 = 4.

UPDATE wp_wc_product_meta
SET stock_quantity = 4
WHERE product_id = 4500;

COMMIT; ── Libera el bloqueo ─────────────────▶ Bloqueo concedido a Transacción B!
                                                Comprobar stock: 4 disponibles.
                                                Descontar: 4 - 1 = 3.
                                                UPDATE wp_wc_product_meta SET ...
                                                COMMIT;

Regla arquitectónica crítica: Nunca ejecute una petición HTTP externa hacia el ERP mientras mantenga una transacción de base de datos abierta. Bloquee la fila, actualice las tablas locales, confirme la transacción en menos de 50 milisegundos y encole el evento de sincronización hacia el ERP en segundo plano.

#Control de concurrencia optimista (OCC)

Cuando la frecuencia de modificación es moderada, el control optimista ofrece mayor rendimiento sin bloquear lecturas.

Se añade una columna de versión (version) en los metadatos del producto:

UPDATE wp_wc_product_meta 
SET stock_quantity = stock_quantity - :cantidad_compra,
    version = version + 1
WHERE product_id = :producto_id 
  AND version = :version_esperada 
  AND stock_quantity >= :cantidad_compra;

Si otro proceso modificó el artículo entre la lectura y la escritura, la condición de versión no se cumple y se actualizan cero filas. La aplicación detecta esta circunstancia y repite el proceso con el nuevo valor de versión.


#Listados de código de producción: Consumidor de colas en PHP 8.4 y demonio WP-CLI

Los siguientes componentes reflejan implementaciones reales preparadas para entornos WordPress y WooCommerce con PHP 8.4.

#Listado 1: Demonio consumidor de colas en segundo plano (QueueConsumerCommand.php)

Este comando de WP-CLI opera como un servicio de sistema continuo bajo Systemd o Supervisord. Procesa flujos de Redis Streams, gestiona paquetes de datos, atiende señales POSIX y recicla la memoria del proceso.

<?php
declare(strict_types=1);

namespace WPPoland\ErpIntegration\Cli;

use WP_CLI;
use Redis;
use Throwable;

if (!defined('ABSPATH')) {
    exit;
}

/**
 * Demonio supervisado de WP-CLI para la sincronización asíncrona de WooCommerce con ERP.
 */
class QueueConsumerCommand
{
    private const STREAM_KEY = 'erp:stream:orders';
    private const CONSUMER_GROUP = 'erp_sync_group';
    private const BATCH_SIZE = 10;
    private const BLOCK_TIMEOUT_MS = 2000;
    private const MAX_MEMORY_BYTES = 134217728; // Umbral de 128 MB antes del reinicio ordenado

    private Redis $redis;
    private string $consumerName;
    private bool $shouldRun = true;

    public function __construct()
    {
        $this->consumerName = 'worker_' . gethostname() . '_' . getmypid();
        $this->initRedis();
        $this->registerSignalHandlers();
    }

    /**
     * Punto de entrada: wp erp-queue consume
     */
    public function __invoke(array $args, array $assocArgs): void
    {
        WP_CLI::line("Iniciando demonio consumidor de colas ERP [{$this->consumerName}] en PHP " . PHP_VERSION);
        $this->ensureConsumerGroup();

        $processedCount = 0;

        while ($this->shouldRun) {
            // Gestión de señales POSIX
            if (function_exists('pcntl_signal_dispatch')) {
                pcntl_signal_dispatch();
            }

            try {
                $messages = $this->redis->xReadGroup(
                    self::CONSUMER_GROUP,
                    $this->consumerName,
                    [self::STREAM_KEY => '>'],
                    self::BATCH_SIZE,
                    self::BLOCK_TIMEOUT_MS
                );

                if (empty($messages) || !isset($messages[self::STREAM_KEY])) {
                    $this->reclaimOrphanedMessages();
                    $this->checkMemoryThreshold();
                    continue;
                }

                foreach ($messages[self::STREAM_KEY] as $messageId => $payload) {
                    $this->processMessage((string) $messageId, $payload);
                    $processedCount++;
                }

                $this->checkMemoryThreshold();
            } catch (Throwable $e) {
                WP_CLI::error("Excepción no controlada en el bucle consumidor: " . $e->getMessage(), false);
                sleep(2); // Pausa preventiva tras fallos de infraestructura
            }
        }

        WP_CLI::success("Consumidor de colas finalizado limpiamente tras procesar {$processedCount} mensajes.");
    }

    private function processMessage(string $messageId, array $payload): void
    {
        $orderId = isset($payload['order_id']) ? (int) $payload['order_id'] : 0;
        $idempotencyKey = $payload['idempotency_key'] ?? '';

        if ($orderId <= 0 || empty($idempotencyKey)) {
            WP_CLI::warning("Carga de datos errónea en el mensaje {$messageId}. Enrutando a DLQ.");
            $this->routeToDlq($messageId, $payload, 'Error de validación: falta order_id o idempotency_key');
            $this->redis->xAck(self::STREAM_KEY, self::CONSUMER_GROUP, [$messageId]);
            return;
        }

        // Comprobación de bloqueo de idempotencia en Redis
        $lockKey = "erp:lock:idemp:{$idempotencyKey}";
        $acquired = $this->redis->set($lockKey, 'PROCESSING', ['NX', 'EX' => 86400]);

        if (!$acquired) {
            $status = (string) $this->redis->get($lockKey);
            if (str_starts_with($status, 'COMPLETED')) {
                WP_CLI::line("El evento {$idempotencyKey} ya fue procesado con anterioridad. Confirmando.");
                $this->redis->xAck(self::STREAM_KEY, self::CONSUMER_GROUP, [$messageId]);
                return;
            }
            WP_CLI::line("El evento {$idempotencyKey} está siendo procesado por otro hilo. Omitiendo.");
            return;
        }

        try {
            // Ejecución de la lógica de sincronización
            $this->syncOrderToErp($orderId, $payload);

            // Marcado como completado y confirmación en el flujo
            $this->redis->set($lockKey, 'COMPLETED:' . time(), ['EX' => 86400]);
            $this->redis->xAck(self::STREAM_KEY, self::CONSUMER_GROUP, [$messageId]);
            WP_CLI::line("Pedido #{$orderId} sincronizado correctamente [Mensaje: {$messageId}]");
        } catch (Throwable $e) {
            WP_CLI::warning("Fallo al sincronizar el pedido #{$orderId}: " . $e->getMessage());
            $this->redis->del($lockKey); // Liberación del bloqueo para reintento

            $attempts = isset($payload['_retry_count']) ? ((int) $payload['_retry_count']) + 1 : 1;
            if ($attempts >= 5) {
                $this->routeToDlq($messageId, $payload, $e->getMessage());
                $this->redis->xAck(self::STREAM_KEY, self::CONSUMER_GROUP, [$messageId]);
            } else {
                // Reencolado con contador de reintentos incrementado
                $payload['_retry_count'] = $attempts;
                $this->redis->xAdd(self::STREAM_KEY, '*', $payload);
                $this->redis->xAck(self::STREAM_KEY, self::CONSUMER_GROUP, [$messageId]);
            }
        }
    }

    private function syncOrderToErp(int $orderId, array $payload): void
    {
        $order = wc_get_order($orderId);
        if (!$order) {
            throw new \RuntimeException("El pedido de WooCommerce #{$orderId} no existe en la base de datos.");
        }

        // Llamada al adaptador del cliente ERP (por ejemplo, cliente SAP o Dynamics)
        // $this->erpClient->createSalesOrder($order);
    }

    private function reclaimOrphanedMessages(): void
    {
        // Inspección de mensajes pendientes durante más de 60 segundos
        $pending = $this->redis->xPending(self::STREAM_KEY, self::CONSUMER_GROUP, '-', '+', 5);
        if (empty($pending)) {
            return;
        }

        $staleIds = [];
        foreach ($pending as $entry) {
            $messageId = $entry[0];
            $idleMs = $entry[2];
            if ($idleMs > 60000) {
                $staleIds[] = $messageId;
            }
        }

        if (!empty($staleIds)) {
            $claimed = $this->redis->xClaim(
                self::STREAM_KEY,
                self::CONSUMER_GROUP,
                $this->consumerName,
                60000,
                $staleIds,
                ['JUSTID']
            );
            WP_CLI::line("Reclamados " . count($claimed) . " mensajes huérfanos de procesos detenidos.");
        }
    }

    private function routeToDlq(string $messageId, array $payload, string $reason): void
    {
        $dlqEntry = [
            'original_id' => $messageId,
            'payload' => json_encode($payload, JSON_THROW_ON_ERROR),
            'failure_reason' => $reason,
            'failed_at' => (new \DateTimeImmutable('now', new \DateTimeZone('UTC')))->format(\DateTimeInterface::ATOM),
            'consumer' => $this->consumerName,
        ];
        $this->redis->xAdd('erp:stream:dlq', '*', $dlqEntry);
        WP_CLI::error("Mensaje {$messageId} trasladado a DLQ: {$reason}", false);
    }

    private function checkMemoryThreshold(): void
    {
        // Limpieza de caché de objetos y registro de consultas SQL
        wp_cache_flush();
        global $wpdb;
        $wpdb->queries = [];
        if (function_exists('gc_collect_cycles')) {
            gc_collect_cycles();
        }

        $memoryUsed = memory_get_usage(true);
        if ($memoryUsed >= self::MAX_MEMORY_BYTES) {
            WP_CLI::line("Límite de memoria alcanzado (" . round($memoryUsed / 1048576, 2) . " MB). Reiniciando proceso...");
            $this->shouldRun = false;
        }
    }

    private function registerSignalHandlers(): void
    {
        if (!function_exists('pcntl_signal')) {
            return;
        }
        pcntl_signal(SIGTERM, function () {
            WP_CLI::line("Señal SIGTERM recibida. Finalizando lote actual antes de salir...");
            $this->shouldRun = false;
        });
        pcntl_signal(SIGINT, function () {
            WP_CLI::line("Señal SIGINT recibida. Deteniendo demonio...");
            $this->shouldRun = false;
        });
    }

    private function initRedis(): void
    {
        $this->redis = new Redis();
        $this->redis->connect('127.0.0.1', 6379, 2.5);
    }

    private function ensureConsumerGroup(): void
    {
        try {
            $this->redis->xGroup('CREATE', self::STREAM_KEY, self::CONSUMER_GROUP, '0', true);
        } catch (Throwable) {
            // El grupo ya existe
        }
    }
}

#Listado 2: Endpoint de webhook seguro con validación HMAC (WebhookController.php)

Este controlador de REST API procesa avisos remitidos por el ERP (como ajustes de inventario), comprueba la firma criptográfica HMAC SHA-256 en tiempo constante, evalúa la validez temporal y encola los eventos en Redis Streams en menos de 20 milisegundos.

<?php
declare(strict_types=1);

namespace WPPoland\ErpIntegration\Api;

use WP_REST_Controller;
use WP_REST_Request;
use WP_REST_Response;
use WP_Error;
use Redis;
use Throwable;

if (!defined('ABSPATH')) {
    exit;
}

class WebhookController extends WP_REST_Controller
{
    protected $namespace = 'erp-sync/v1';
    protected $rest_base = 'webhook';

    private const WEBHOOK_SECRET_OPTION = 'erp_webhook_hmac_secret';
    private const MAX_TIMESTAMP_SKEW_SECONDS = 300; // Margen de 5 minutos contra ataques de repetición

    public function register_routes(): void
    {
        register_rest_route($this->namespace, '/' . $this->rest_base, [
            [
                'methods' => 'POST',
                'callback' => [$this, 'handleIncomingWebhook'],
                'permission_callback' => [$this, 'validateHmacSignature'],
            ],
        ]);
    }

    /**
     * Validación de firma HMAC y vigencia temporal en tiempo constante.
     */
    public function validateHmacSignature(WP_REST_Request $request): bool|WP_Error
    {
        $signatureHeader = $request->get_header('x-erp-signature-256');
        $timestampHeader = $request->get_header('x-erp-timestamp');

        if (empty($signatureHeader) || empty($timestampHeader)) {
            return new WP_Error(
                'rest_forbidden',
                'Faltan cabeceras de autenticación requeridas: X-ERP-Signature-256 o X-ERP-Timestamp.',
                ['status' => 401]
            );
        }

        // Validación de marca temporal para evitar ataques de repetición
        $requestTime = (int) $timestampHeader;
        $currentTime = time();
        if (abs($currentTime - $requestTime) > self::MAX_TIMESTAMP_SKEW_SECONDS) {
            return new WP_Error(
                'rest_forbidden',
                'La marca temporal del webhook supera la desviación máxima permitida (300 s).',
                ['status' => 403]
            );
        }

        $rawBody = $request->get_body();
        $secret = (string) get_option(self::WEBHOOK_SECRET_OPTION, '');

        if (empty($secret)) {
            return new WP_Error('rest_error', 'Clave secreta HMAC no configurada en el servidor.', ['status' => 500]);
        }

        $signedPayload = "t={$timestampHeader}.{$rawBody}";
        $expectedSignature = hash_hmac('sha256', $signedPayload, $secret);

        // Comparación segura en tiempo constante
        if (!hash_equals($expectedSignature, $signatureHeader)) {
            return new WP_Error(
                'rest_forbidden',
                'Firma criptográfica HMAC no válida.',
                ['status' => 403]
            );
        }

        return true;
    }

    /**
     * Recepción no bloqueante y almacenamiento en Redis.
     */
    public function handleIncomingWebhook(WP_REST_Request $request): WP_REST_Response|WP_Error
    {
        $params = $request->get_json_params();
        if (empty($params) || !is_array($params)) {
            return new WP_Error('rest_bad_request', 'Cuerpo JSON no válido.', ['status' => 400]);
        }

        $eventId = $request->get_header('x-idempotency-key') ?: wp_generate_uuid4();
        $eventType = sanitize_text_field((string) ($params['event_type'] ?? 'inventory_delta'));

        try {
            $redis = new Redis();
            $redis->connect('127.0.0.1', 6379, 1.0);

            // Registro en el flujo para tratamiento en segundo plano
            $streamPayload = [
                'event_id' => $eventId,
                'event_type' => $eventType,
                'received_at' => (string) microtime(true),
                'payload_json' => json_encode($params, JSON_THROW_ON_ERROR),
            ];

            $messageId = $redis->xAdd('erp:stream:incoming_webhooks', '*', $streamPayload);

            return new WP_REST_Response([
                'status' => 'accepted',
                'message_id' => $messageId,
                'event_id' => $eventId,
            ], 202);
        } catch (Throwable $e) {
            return new WP_Error(
                'rest_internal_error',
                'Fallo al encolar el webhook: ' . $e->getMessage(),
                ['status' => 500]
            );
        }
    }
}

#Listado 3: Deducción atómica de inventario con SELECT FOR UPDATE (StockManager.php)

Este servicio de base de datos gestiona la reducción de existencias durante el proceso de compra en WooCommerce, utilizando transacciones InnoDB, bloqueos exclusivos y recuperación ante bloqueos mutuos.

<?php
declare(strict_types=1);

namespace WPPoland\ErpIntegration\Database;

use wpdb;
use RuntimeException;
use InvalidArgumentException;
use Throwable;

if (!defined('ABSPATH')) {
    exit;
}

class StockManager
{
    private wpdb $db;
    private const MAX_DEADLOCK_RETRIES = 3;

    public function __construct()
    {
        global $wpdb;
        $this->db = $wpdb;
    }

    /**
     * Descuenta existencias de forma atómica mediante bloqueo a nivel de fila.
     *
     * @param int $productId Identificador del producto o variación
     * @param int $quantityToDeduct Cantidad positiva a descontar
     * @return int Nivel de stock resultante
     * @throws RuntimeException En caso de inventario insuficiente o deadlock persistente
     */
    public function deductStockAtomically(int $productId, int $quantityToDeduct): int
    {
        if ($quantityToDeduct <= 0) {
            throw new InvalidArgumentException("La cantidad a descontar debe ser un entero positivo.");
        }

        $attempt = 0;

        while ($attempt < self::MAX_DEADLOCK_RETRIES) {
            $attempt++;

            try {
                $this->db->query('START TRANSACTION');

                // Bloqueo exclusivo sobre el registro de stock
                $query = $this->db->prepare(
                    "SELECT meta_value FROM {$this->db->postmeta} 
                     WHERE post_id = %d AND meta_key = '_stock' 
                     FOR UPDATE",
                    $productId
                );

                $currentStockRaw = $this->db->get_var($query);

                if ($currentStockRaw === null) {
                    throw new RuntimeException("No existe registro de stock para el producto {$productId}.");
                }

                $currentStock = (int) $currentStockRaw;

                if ($currentStock < $quantityToDeduct) {
                    $this->db->query('ROLLBACK');
                    throw new RuntimeException(
                        "Stock insuficiente. Solicitado: {$quantityToDeduct}, Disponible: {$currentStock}"
                    );
                }

                $newStock = $currentStock - $quantityToDeduct;

                // Actualización del valor de stock
                $this->db->update(
                    $this->db->postmeta,
                    ['meta_value' => (string) $newStock],
                    ['post_id' => $productId, 'meta_key' => '_stock'],
                    ['%s'],
                    ['%d', '%s']
                );

                // Modificación del estado si el stock llega a cero
                if ($newStock === 0) {
                    $this->db->update(
                        $this->db->postmeta,
                        ['meta_value' => 'outofstock'],
                        ['post_id' => $productId, 'meta_key' => '_stock_status'],
                        ['%s'],
                        ['%d', '%s']
                    );
                }

                $this->db->query('COMMIT');

                // Invalidación de cachés de WooCommerce tras el commit
                wp_cache_delete($productId, 'post_meta');
                if (function_exists('wc_delete_product_transients')) {
                    wc_delete_product_transients($productId);
                }

                return $newStock;
            } catch (Throwable $e) {
                $this->db->query('ROLLBACK');

                // Captura del error de deadlock 1213 en MySQL
                $isDeadlock = str_contains($e->getMessage(), 'Deadlock found') ||
                              ($this->db->last_error && str_contains($this->db->last_error, '1213'));

                if ($isDeadlock && $attempt < self::MAX_DEADLOCK_RETRIES) {
                    // Retroceso exponencial con dispersión antes de reintentar
                    $backoffUs = (int) (pow(2, $attempt) * 10000 + random_int(1000, 5000));
                    usleep($backoffUs);
                    continue;
                }

                throw new RuntimeException(
                    "Fallo en la transacción de stock [Intento {$attempt}]: " . $e->getMessage(),
                    0,
                    $e
                );
            }
        }

        throw new RuntimeException("Superado el número máximo de reintentos por deadlock para el producto {$productId}.");
    }
}

#Guía de conciliación de extremo a extremo y resolución de split-brain

Incluso disponiendo de colas de mensajes e idempotencia rigurosa, ciertos factores externos — como devoluciones físicas en almacén, ventas presenciales mediante terminales punto de venta (TPV) o la restauración de copias de seguridad de la base de datos — generarán discrepancias con el paso del tiempo.

Una integración robusta debe contemplar auditorías automáticas periódicas y reglas estrictas sobre la autoridad de los datos.

#Reglas de gobernanza de fuente única de verdad (Single source of truth)

Para eludir colisiones en las actualizaciones bidireccionales, las responsabilidades de cada sistema deben estar fijadas de forma inequívoca:

┌─────────────────────────────────────────────────────────────────────────────────────────┐
│                          MODELO DE GOBERNANZA SPLIT-BRAIN                               │
└─────────────────────────────────────────────────────────────────────────────────────────┘

 ┌───────────────────────────────────────────────────────────────────────────────────────┐
 │                               SISTEMA ERP (SISTEMA MAESTRO)                           │
 │                                                                                       │
 │  - Existencias físicas reales (Almacenes centrales y delegaciones)                    │
 │  - Tarifas de precios B2B/B2C, escalas por volumen y condiciones contractuales        │
 │  - Catálogo maestro (SKU base, códigos EAN/barras, impuestos y aranceles)             │
 │  - Facturación, contabilidad oficial y libros contables                               │
 └───────────────────────────────────────────┬───────────────────────────────────────────┘

                       Sincronización autoritativa hacia la tienda


 ┌───────────────────────────────────────────────────────────────────────────────────────┐
 │                       WOOCOMMERCE (FRONTAL DE COMERCIO DIGITAL)                       │
 │                                                                                       │
 │  - Sesiones de carrito activas e intención de compra de los usuarios                  │
 │  - Contenido comercial, metadatos SEO y taxonomías de producto                        │
 │  - Reservas temporales durante el checkout (tiempo de vida: 5 minutos)                │
 │  - Perfiles de clientes y direcciones de entrega                                      │
 └───────────────────────────────────────────────────────────────────────────────────────┘
  1. Niveles de inventario: El ERP es el maestro absoluto. Ante cualquier disparidad detectada durante una auditoría, el valor del ERP sobrescribe el registro en WooCommerce.
  2. Precios y descuentos: El ERP es el maestro. WooCommerce calcula impuestos y descuentos para la visualización del carrito, pero el ERP valida y liquida los importes contables definitivos al generar el pedido.
  3. Creación de pedidos: WooCommerce es el maestro de la intención de compra. Una vez completado el pago en línea, WooCommerce custodia el registro primario y lo remite a la cola. Cuando el ERP confirma la recepción y emite un identificador de documento (ERP_DOC_ID), el ERP asume el control de los estados subsiguientes (preparación, expedición, cancelación).

#Arquitectura de conciliación en dos niveles

El procedimiento de conciliación integral opera en dos frecuencias diferenciadas:

┌─────────────────────────────────────────────────────────────────────────────────────────┐
│                             BUCLES DE CONCILIACIÓN EN DOS NIVELES                       │
└─────────────────────────────────────────────────────────────────────────────────────────┘

 ┌───────────────────────────────────────────────────────────────────────────────────────┐
 │ NIVEL 1: CONCILIACIÓN DELTA HORARIA CONTINUA                                          │
 │ (Alta frecuencia, volumen reducido, detección ágil de desajustes)                     │
 │                                                                                       │
 │  Consulta en ERP y WooCommerce:                                                       │
 │  WHERE updated_at >= NOW() - INTERVAL 90 MINUTE                                       │
 │  ──▶ Identificar SKUs y pedidos modificados ──▶ Encolar en flujo prioritario          │
 └───────────────────────────────────────────────────────────────────────────────────────┘

 ┌───────────────────────────────────────────────────────────────────────────────────────┐
 │ NIVEL 2: AUDITORÍA CRIPTOGRÁFICA NOCTURNA DE SUMAS DE VERIFICACIÓN                    │
 │ (Integridad completa del catálogo, comparación por bloques, corrección automática)    │
 │                                                                                       │
 │  Catálogo WooCommerce (Bloque 01: SKUs 00001 - 00500) ──▶ SHA-256: e3b0c442...         │
 │                                                                    │                  │
 │                                                            [Comparación Hash]         │
 │                                                                    │                  │
 │  Catálogo Maestro ERP (Bloque 01: SKUs 00001 - 00500) ──▶ SHA-256: e3b0c442...         │
 │                                                                                       │
 │  - Si los hashes COINCIDEN: Bloque validado. Continuar con Bloque 02.                 │
 │  - Si los hashes DIFIEREN: Ejecutar sincronización detallada solo para Bloque 01.     │
 └───────────────────────────────────────────────────────────────────────────────────────┘

#Nivel 1: Conciliación delta horaria

La comprobación delta horaria examina los registros alterados en los últimos noventa minutos en ambos extremos (estableciendo un margen de solapamiento de treinta minutos).

  • WooCommerce localiza en wp_wc_orders los registros con date_updated_gmt >= (NOW() - INTERVAL 90 MINUTE).
  • El conector del ERP recupera los asientos y movimientos de stock recientes.
  • El motor de conciliación coteja los estados; cualquier pedido ausente o con estado discrepante se transfiere automáticamente a la cola prioritaria de sincronización.

#Nivel 2: Auditoría criptográfica nocturna de sumas de verificación

Examinar cien mil artículos uno por uno a través de la red diariamente genera un consumo excesivo de ancho de banda y recursos de base de datos. Para solventarlo, se implementa una auditoría mediante cálculo de hashes por bloques:

  1. Se divide la totalidad del catálogo en bloques ordenados de 500 SKUs (por ejemplo, Bloque 001: referencias A0001 a A0500).
  2. Se genera un hash SHA-256 a partir de la concatenación textual de los registros de cada bloque: $$\text{Hash} = \text{SHA256}\left(\sum_{i=1}^{500} \text{SKU}_i + \text{Precio}_i + \text{Stock}_i\right)$$
  3. El ERP calcula hashes idénticos para cada bloque de forma independiente.
  4. La capa de integración compara los 200 hashes resultantes.
  5. Si 198 bloques coinciden, 99.000 artículos quedan matemáticamente verificados en plena sincronía.
  6. Únicamente los dos bloques discordantes (1.000 artículos en total) se descargan para su subsanación granular, reduciendo el tráfico de datos en un 99 por ciento.

#Guía de resolución de incidencias en entornos de producción

Ante cualquier anomalía en el entorno de producción, los equipos de desarrollo deben intervenir aplicando protocolos estructurados.

#Incidencia 1: Entregas desordenadas de webhooks y condiciones de carrera

  • Síntomas: Un administrador cancela un pedido en WooCommerce. Cinco minutos después, el estado vuelve a cambiar a “En proceso” debido al procesamiento tardío de un webhook del ERP retrasado en el tránsito de red.
  • Origen: Las redes asíncronas no aseguran la entrega en secuencia de los paquetes. El webhook B (emitido a las 14:02) llegó antes que el webhook A (emitido a las 14:00).
  • Procedimiento de resolución:
    1. Incorpore un contador de revisión incremental (revision_id) o una marca temporal UTC en la carga del webhook.
    2. Mantenga una columna de versión en wp_wc_orders_erp_lookup.
    3. Ejecute la actualización únicamente si la versión entrante supera a la registrada:
      UPDATE wp_wc_orders_erp_lookup 
      SET erp_status = :new_status, last_event_version = :incoming_version 
      WHERE order_id = :order_id AND last_event_version < :incoming_version;
    4. Si se afectan cero registros, descarte el mensaje desactualizado registrando el evento como EVENT_SUPERSEDED.

#Incidencia 2: Bloqueos mutuos (deadlocks) en base de datos durante campañas flash

  • Síntomas: Los compradores reciben avisos de error al tramitar el pedido. El registro de errores de MySQL indica: ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction.
  • Origen: Dos transacciones simultáneas adquieren los mismos dos productos (Producto A y Producto B) en orden inverso. La transacción 1 bloquea el Producto A y solicita el Producto B; la transacción 2 bloquea el Producto B y solicita el Producto A.
  • Procedimiento de resolución:
    1. Aplique un criterio canónico de ordenación de bloqueos. Ordene siempre de forma ascendente los identificadores de producto antes de ejecutar SELECT ... FOR UPDATE:
      $productIds = [842, 105, 330];
      sort($productIds, SORT_NUMERIC); // Resultado: [105, 330, 842]
      foreach ($productIds as $id) {
          $stockManager->deductStockAtomically($id, $cartItems[$id]['qty']);
      }
    2. Al solicitar todas las transacciones los bloqueos en la misma secuencia matemática, los deadlocks circulares quedan completamente neutralizados.

#Evaluación de rendimiento, monitorización y acuerdos de nivel de servicio (SLA)

El mantenimiento de una integración empresarial exige la supervisión constante de métricas clave para prevenir degradaciones del servicio.

┌─────────────────────────────────────────────────────────────────────────────────────────┐
│                              PILA DE MONITORIZACIÓN DE INTEGRACIÓN                      │
└─────────────────────────────────────────────────────────────────────────────────────────┘

 [Workers PHP de WooCommerce] ──▶ Trazas OpenTelemetry ──▶ [Jaeger / Tempo / Datadog]
 [Broker Redis Streams]       ──▶ Prometheus Exporter  ──▶ [Servidor Prometheus]
 [Demonios WP-CLI]            ──▶ Métricas StatsD     ──▶        │

                                                        [Cuadros de mando Grafana]

                                                        [Alertas Alertmanager]

                                           ┌─────────────────────┴─────────────────────┐
                                           ▼                                           ▼
                                    [PagerDuty / Opsgenie]                      [Canal de Slack]

#Métricas esenciales y umbrales de alerta

MétricaDescripciónSLA ObjetivoUmbral de avisoUmbral crítico
erp_queue_consumer_lagMensajes no leídos acumulados en orders.incoming< 100 mensajes> 500 mensajes durante 5 min> 2.500 mensajes o antigüedad > 15 min
webhook_ingest_p95_msLatencia de recepción de webhooks (firma hasta 202)< 25 milisegundos> 75 milisegundos> 250 milisegundos
order_sync_latency_p99Tiempo transcurrido desde el pago hasta el registro en ERP< 30 segundos> 120 segundos> 600 segundos
dlq_occupancy_countVolumen de mensajes fallidos en erp:stream:dlq0 mensajes> 10 mensajes> 50 mensajes
db_deadlock_rateTasa de bloqueos mutuos por cada 1.000 compras0,00 %> 0,10 % (1 por 1.000)> 1,00 % (10 por 1.000)

#Verifactu, el SII de la AEAT y la excepción de TicketBAI

El Reglamento aprobado por el Real Decreto 1007/2023 cambia el reparto de responsabilidades entre la tienda y el ERP. Un sistema informático de facturación debe generar, por cada factura, un registro de facturación encadenado con el anterior mediante una huella, de modo que cualquier alteración posterior rompa la cadena de forma detectable.

Quien haya construido la cola de sincronización de las secciones anteriores reconocerá el patrón, porque es el mismo encadenamiento por huella que se usa para detectar pérdida de eventos. La diferencia es que aquí la cadena tiene consecuencias fiscales:

ElementoQué exigeDónde debe vivir
Huella encadenadareferencia al registro anterioren el emisor, nunca recalculada en la tienda
Código QRverificable por la persona receptoraen el documento emitido
Registro de anulaciónla factura no se borra, se anulaevento nuevo, no DELETE
Remisión a la AEATinmediata en modalidad Verifacturesponsabilidad del emisor

De aquí sale la regla de diseño más importante para el mercado español: la tienda no debe ser el emisor. Si WooCommerce genera la factura, WooCommerce se convierte en sistema informático de facturación y hereda todas las obligaciones anteriores, incluida la inalterabilidad del registro. Dejando la emisión en el ERP, la tienda queda como origen del pedido y la cadena vive en un único sistema.

El SII añade una segunda vía para los sujetos obligados, con envío de los libros registro a la AEAT en plazos muy cortos. Su implicación operativa es que el desfase de la sincronización deja de ser un detalle interno: un pedido que tarda horas en llegar al ERP consume el plazo de remisión.

Queda la excepción territorial que rompe cualquier integración diseñada solo para territorio común. En el País Vasco rige TicketBAI, con implantación propia en Bizkaia, Gipuzkoa y Álava, y en Bizkaia se integra además en el sistema Batuz. Si la empresa factura desde una de las diputaciones forales, el formato, el fichero y el calendario son distintos. Trate el territorio fiscal como un dato del emisor y no como una constante del proyecto.


#Próximos pasos de ingeniería

La consolidación de una integración corporativa entre WooCommerce y un sistema ERP requiere una apuesta decidida por el desacoplamiento asíncrono, la idempotencia determinista, el control transaccional de concurrencia y la conciliación automatizada.

Si necesita evaluar su arquitectura tecnológica actual o desarrollar una solución de integración a medida para su ERP, descubra nuestros servicios de integración WooCommerce ERP o contacte con los desarrolladores WooCommerce de WPPoland.

Siguiente paso

Transforma el artículo en una implementación real

Este bloque refuerza el enlazado interno y lleva al lector al siguiente paso más útil dentro de la arquitectura del sitio.

Cluster relacionado

Explora otros servicios WordPress y base de conocimiento

Refuerza tu negocio con soporte técnico profesional en áreas clave del ecosistema WordPress.

¿Por qué fallan las llamadas síncronas REST o SOAP entre WooCommerce y los sistemas ERP?#
Las llamadas síncronas vinculan la ejecución de los procesos PHP-FPM a la latencia de respuesta de los servidores ERP externos. Cuando el ERP sufre picos de carga, procesos por lotes o interrupciones de red, los hilos de PHP se bloquean hasta alcanzar el tiempo de espera del socket (habitualmente 30-60 segundos). Esto agota el grupo de hilos del servidor web, genera errores HTTP 504 Gateway Timeout para los compradores y descarta transacciones sin confirmación.
¿Cómo proporcionan los Redis Streams mayor fiabilidad que las listas estándar de Redis?#
Las listas tradicionales con LPUSH y RPOP carecen de seguimiento nativo de confirmaciones y estados de consumidores. Redis Streams ofrece grupos de consumidores (XREADGROUP), registros persistentes append-only, listas de mensajes pendientes (PEL), confirmaciones explícitas (XACK) y reasignación de mensajes huérfanos tras caídas del proceso (XCLAIM).
¿Cómo se evita la sobreventa (overselling) durante ventas flash de alta concurrencia?#
Evitar la sobreventa requiere control transaccional de concurrencia. Dentro de una transacción activa en MySQL InnoDB, SELECT stock_quantity FROM wp_wc_product_meta WHERE product_id = :id FOR UPDATE bloquea la fila en modo exclusivo antes de reducir el stock. Alternativamente, una actualización optimista con verificación de versión (UPDATE ... SET stock = stock - qty, version = version + 1 WHERE id = :id AND version = :cur AND stock >= qty) garantiza un decremento atómico.
¿Qué función cumple la clave de idempotencia en el procesamiento de webhooks de ERP?#
Una clave de idempotencia (enviada en la cabecera X-Idempotency-Key o derivada de un hash SHA-256 del ID de pedido y marca de tiempo) garantiza que la recepción repetida de un mensaje no provoque efectos secundarios duplicados. El receptor establece un bloqueo atómico en Redis (SETNX lock:idempotency:{key} PROCESSING EX 86400). Si la clave ya se ha completado, devuelve la respuesta en caché en lugar de crear un nuevo documento en el ERP.
¿Cómo se gestionan las restricciones de tasa (rate limits) en APIs de ERP en la nube como Dynamics 365?#
Las APIs en la nube imponen límites estrictos (Dynamics 365 Business Central limita a 600 peticiones por minuto por inquilino). La integración gestiona esto almacenando peticiones en Redis, aplicando un algoritmo de token bucket en el consumidor y utilizando retroceso exponencial con fluctuación aleatoria (full jitter) ante respuestas HTTP 429 Too Many Requests.
¿Qué causa fugas de memoria en demonios WP-CLI con PHP 8.4 y cómo se resuelven?#
WordPress acumula consultas a la base de datos, metadatos de objetos y registros de hooks en matrices internas de PHP que crecen indefinidamente durante ejecuciones prolongadas. La solución requiere invocar wp_cache_flush(), reiniciar $wpdb->queries a un array vacío, ejecutar gc_collect_cycles() tras cada lote y definir un umbral de memoria (por ejemplo, 128 MB) para que el proceso termine de forma controlada y sea reiniciado por el supervisor.
¿De qué forma detecta y repara la conciliación en dos niveles las desviaciones silenciosas de datos?#
El modelo en dos niveles ejecuta una comprobación delta horaria que consulta los registros modificados en los últimos 90 minutos en ambos sistemas. En paralelo, una auditoría nocturna genera sumas SHA-256 de tuplas SKU-stock-precio ordenadas en bloques de 500 artículos. Si un hash no coincide, una sincronización diferencial dirigida aísla y repara exclusivamente los registros discrepantes.
¿Cómo influye el almacenamiento de pedidos de alto rendimiento (HPOS) de WooCommerce en la integración con ERP?#
Históricamente WooCommerce guardaba los pedidos en las tablas wp_posts y wp_postmeta, de modo que consultar un único pedido implicaba múltiples operaciones de unión sobre millones de filas no estructuradas. HPOS traslada los pedidos a tablas relacionales dedicadas y optimizadas (wp_wc_orders, wp_wc_order_addresses, wp_wc_order_operational_data). Esto suprime esos joins costosos, facilita la indexación directa de las referencias del ERP, agiliza las búsquedas por identificadores externos y reduce de forma significativa la contención de bloqueos de base de datos durante importaciones masivas o continuas de ventas.
¿Por qué no debe utilizarse el pseudo-cron de WordPress (wp-cron.php) para la sincronización con ERP?#
El sistema de cron interno de WordPress solo se ejecuta cuando un visitante solicita una página de la tienda. En plataformas con tráfico masivo, peticiones simultáneas desencadenan múltiples ejecuciones paralelas de cron, saturando los recursos del servidor. En sitios con escaso tráfico, las tareas acumuladas quedan pausadas durante horas. Adicionalmente, las peticiones web se rigen por límites estrictos de tiempo de ejecución (max_execution_time) en PHP-FPM. Los procesos de sincronización pesados deben canalizarse a través de demonios supervisados por WP-CLI desacoplados del servidor web.
¿Cómo se gestionan las discrepancias en el cálculo de impuestos y las divisas múltiples entre sistemas?#
Para garantizar el cumplimiento de la normativa fiscal europea e internacional, WooCommerce transfiere los importes brutos y netos calculados en el checkout junto con los códigos impositivos específicos aplicados. El ERP efectúa la validación de los importes. Si surgen diferencias mínimas de céntimos debido a distintos algoritmos de redondeo (redondeo por línea frente a redondeo sobre el total), el ERP contabiliza automáticamente la desviación en una cuenta contable de ajuste por diferencias de redondeo.

¿Necesitas un FAQ adaptado a tu sector y mercado? Preparamos una versión alineada con tus objetivos de negocio.

Hablemos

Artículos Relacionados