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.
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-Keyy 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.
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 │
└────────────────────────────────────────────────────────────────────────────────────────┘
- 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. - 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.
- 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.
- 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 ERP | Protocolos nativos | Perfil de rendimiento | Modelo de concurrencia y bloqueo | Principales modos de fallo arquitectónico |
|---|---|---|---|---|
| SAP S/4HANA | OData v4, IDoc vía SAP BTP, RFC, Async SOAP | Elevado rendimiento por lotes (10k+ registros/min); latencia media en llamadas individuales | SAP Logical Unit of Work (LUW); bloqueos en Enqueue Server; límites de batch en OData | Saturación del pool de conexiones al reiniciar SAP Cloud Connector; tiempos de espera en estructuras BOM complejas |
| Comarch Optima / XL | Optima WebAPI, automatización COM DLL, tablas staging en MS SQL | Rendimiento 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 / nexo | Subiekt GT Sfera (COM/OLE), Subiekt nexo PRO SDK (.NET / WebAPI) | GT: 30-80 pedidos/min; nexo PRO: 250+ pedidos/min | Bloqueos de fila en SQL Server en dok__Dokument y tw__Towar; limitaciones COM de escritorio | Bloqueos de hilos COM por cuadros de diálogo modales en GT; fragmentación de índices con catálogos extensos |
| Microsoft Dynamics 365 BC | Business Central REST API v2.0, OData v4, AL API Pages | Lí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 registrar | Throttling 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
ORDERS05oMATMAS05) 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) medianteCoInitialize. - 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
Zmienionopara 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/$batchcon 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 cabeceraX-Cache-Lookup: HIT.
- Si la clave se registra con éxito (
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 ejecutaXACK. - Reclamación de mensajes huérfanos (
XCLAIM): Si un proceso finaliza de forma abrupta, los procesos supervivientes revisan la lista PEL medianteXPENDINGy reclaman conXCLAIMlos 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:
- 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.
- 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 │
└───────────────────────────────────────────────────────────────────────────────────────┘
- 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.
- 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.
- 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_orderslos registros condate_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:
- Se divide la totalidad del catálogo en bloques ordenados de 500 SKUs (por ejemplo, Bloque 001: referencias
A0001aA0500). - 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)$$
- El ERP calcula hashes idénticos para cada bloque de forma independiente.
- La capa de integración compara los 200 hashes resultantes.
- Si 198 bloques coinciden, 99.000 artículos quedan matemáticamente verificados en plena sincronía.
- Ú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:
- Incorpore un contador de revisión incremental (
revision_id) o una marca temporal UTC en la carga del webhook. - Mantenga una columna de versión en
wp_wc_orders_erp_lookup. - 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; - Si se afectan cero registros, descarte el mensaje desactualizado registrando el evento como
EVENT_SUPERSEDED.
- Incorpore un contador de revisión incremental (
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:
- 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']); } - Al solicitar todas las transacciones los bloqueos en la misma secuencia matemática, los deadlocks circulares quedan completamente neutralizados.
- Aplique un criterio canónico de ordenación de bloqueos. Ordene siempre de forma ascendente los identificadores de producto antes de ejecutar
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étrica | Descripción | SLA Objetivo | Umbral de aviso | Umbral crítico |
|---|---|---|---|---|
erp_queue_consumer_lag | Mensajes 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_ms | Latencia de recepción de webhooks (firma hasta 202) | < 25 milisegundos | > 75 milisegundos | > 250 milisegundos |
order_sync_latency_p99 | Tiempo transcurrido desde el pago hasta el registro en ERP | < 30 segundos | > 120 segundos | > 600 segundos |
dlq_occupancy_count | Volumen de mensajes fallidos en erp:stream:dlq | 0 mensajes | > 10 mensajes | > 50 mensajes |
db_deadlock_rate | Tasa de bloqueos mutuos por cada 1.000 compras | 0,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:
| Elemento | Qué exige | Dónde debe vivir |
|---|---|---|
| Huella encadenada | referencia al registro anterior | en el emisor, nunca recalculada en la tienda |
| Código QR | verificable por la persona receptora | en el documento emitido |
| Registro de anulación | la factura no se borra, se anula | evento nuevo, no DELETE |
| Remisión a la AEAT | inmediata en modalidad Verifactu | responsabilidad 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.





