Volver al Blog
E-commerceSoftwareDesarrollo

Sincronización de Stock Multi-Almacén y Punto de Venta (POS) en WooCommerce en Tiempo Real

Brayan Developer
5 min de lectura
Sincronización de Stock Multi-Almacén y Punto de Venta (POS) en WooCommerce en Tiempo Real
Estrategias de arquitectura, bloqueos de concurrencia y webhooks para sincronizar el inventario físico (POS) con tiendas online WooCommerce y múltiples almacenes sin sobreventas.

Gestionar inventarios cuando una empresa opera simultáneamente tiendas físicas (cajas registradoras / sistemas POS) y una tienda online de alto tráfico en WooCommerce representa uno de los mayores retos de ingeniería de retail: la sobreventa (overselling) por desfase de stock. Cuando un cliente compra la última unidad en caja mientras otro usuario finaliza el checkout online, se producen quiebres de inventario y fricción operativa. En este artículo detallamos la arquitectura técnica de sincronización bidireccional en tiempo real, bloqueo pesimista de stock y enrutamiento de pedidos multi-almacén.

Portada

Desafíos de la Concurrencia de Inventario Omnicanal#

En una arquitectura tradicional sin sincronización atómica, las actualizaciones de inventario se ejecutan por lotes periódicos (cron jobs cada 15 o 30 minutos). Esta ventana de tiempo permite ventas duplicadas:

[Tienda Física - Caja POS]                    [Tienda Online WooCommerce]
         │                                                │
         ├─── Compra en Caja (Stock: 1 -> 0)               ├─── Usuario agrega al carrito (Stock en BD: 1)
         │                                                │
         └─── Cron de sincronización (Esperando 15 min) ──┼─── Checkout aprobado (Stock intenta pasar a 0)
                                                          │
                                                          ▼
                                              🚨 ERROR: Sobreventa generada

Para evitar esto, implementamos una arquitectura basada en Webhooks reactivos, colas de mutación y eventos transaccionales ACID:

[Tienda Física / POS]                     [Event Bus / API Gateway]                  [WooCommerce Engine]
         │                                           │                                        │
         ├─── Venta en POS (Instantánea) ───────────►├─── Disparo de Evento Webhook ─────────►├── Bloqueo transaccional
         │                                           │                                        ├── Descuento por Almacén
         │                                           │                                        └── Emisión WebSocket UI
         ◄─── Confirmación de Stock OK ──────────────┴────────────────────────────────────────┘

Estructura de Base de Datos para Multi-Almacén (HPOS)#

WooCommerce almacena de forma predeterminada un único valor de stock global por producto. Para soportar múltiples ubicaciones físicas (Almacén Central, Tienda Miraflores, Tienda San Isidro), creamos una tabla relacional optimizada:

-- Tabla dedicada para inventario multi-almacén indexado por SKU y Almacén
CREATE TABLE wp_wc_warehouse_stocks (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    product_id BIGINT UNSIGNED NOT NULL,
    warehouse_id VARCHAR(50) NOT NULL,
    stock_quantity INT NOT NULL DEFAULT 0,
    reserved_quantity INT NOT NULL DEFAULT 0,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    UNIQUE KEY uq_product_warehouse (product_id, warehouse_id),
    INDEX idx_product (product_id),
    INDEX idx_warehouse (warehouse_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

Endpoint REST Personalizado para Mutación Atómica de Stock#

Creamos un endpoint seguro con autenticación Bearer / HMAC que permite a las cajas POS actualizar el inventario con transacciones y bloqueos de fila (FOR UPDATE):

<?php
/**
 * Plugin Name: WooCommerce POS Realtime Stock Sync
 * Description: Sincronización atómica de stock multi-almacén para puntos de venta físicos.
 * Version: 1.0.0
 * Author: Brayan Developer
 */

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

add_action('rest_api_init', function () {
    register_rest_route('pos-sync/v1', '/update-stock', [
        'methods'  => 'POST',
        'callback' => 'handle_pos_stock_update',
        'permission_callback' => function ($request) {
            $auth_token = $request->get_header('x-pos-sync-token');
            return $auth_token === get_option('pos_sync_api_secret');
        }
    ]);
});

function handle_pos_stock_update(WP_REST_Request $request) {
    global $wpdb;
    
    $params = $request->get_json_params();
    $sku = sanitize_text_field($params['sku'] ?? '');
    $warehouse_id = sanitize_text_field($params['warehouse_id'] ?? '');
    $quantity_delta = (int)($params['quantity_delta'] ?? 0); // Ejemplo: -1 tras una venta física

    if (empty($sku) || empty($warehouse_id) || $quantity_delta === 0) {
        return new WP_REST_Response(['error' => 'Parámetros inválidos'], 400);
    }

    $product_id = wc_get_product_id_by_sku($sku);
    if (!$product_id) {
        return new WP_REST_Response(['error' => 'Producto no encontrado por SKU'], 404);
    }

    // Iniciar transacción de base de datos para evitar condiciones de carrera
    $wpdb->query('START TRANSACTION');

    try {
        // 1. Bloqueo de fila exclusivo
        $current_row = $wpdb->get_row($wpdb->prepare(
            "SELECT stock_quantity FROM {$wpdb->prefix}wc_warehouse_stocks 
             WHERE product_id = %d AND warehouse_id = %s FOR UPDATE",
            $product_id,
            $warehouse_id
        ));

        if (!$current_row) {
            $wpdb->query('ROLLBACK');
            return new WP_REST_Response(['error' => 'Almacén no asignado al producto'], 404);
        }

        $new_warehouse_stock = $current_row->stock_quantity + $quantity_delta;

        if ($new_warehouse_stock < 0) {
            $wpdb->query('ROLLBACK');
            return new WP_REST_Response(['error' => 'Stock insuficiente en el almacén'], 409);
        }

        // 2. Actualizar inventario local del almacén
        $wpdb->update(
            "{$wpdb->prefix}wc_warehouse_stocks",
            ['stock_quantity' => $new_warehouse_stock],
            ['product_id' => $product_id, 'warehouse_id' => $warehouse_id]
        );

        // 3. Recalcular y actualizar stock global de WooCommerce
        $total_stock = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT SUM(stock_quantity) FROM {$wpdb->prefix}wc_warehouse_stocks WHERE product_id = %d",
            $product_id
        ));

        wc_update_product_stock($product_id, $total_stock);

        $wpdb->query('COMMIT');

        // 4. Invalidar caché en Redis para el catálogo
        wp_cache_delete("product_{$product_id}", 'products');

        return new WP_REST_Response([
            'status'             => 'success',
            'product_id'         => $product_id,
            'sku'                => $sku,
            'warehouse_id'       => $warehouse_id,
            'warehouse_stock'    => $new_warehouse_stock,
            'global_total_stock' => $total_stock,
        ], 200);

    } catch (\Exception $e) {
        $wpdb->query('ROLLBACK');
        return new WP_REST_Response(['error' => $e->getMessage()], 500);
    }
}

Reserva Temporal de Stock Durante el Checkout Online#

Cuando un usuario online entra en la pantalla de pago (Checkout), se debe aplicar una reserva de stock con expiración automática (TTL de 10 minutos) en Redis para evitar que la caja física venda el artículo mientras se ingresan los datos de tarjeta:

// services/inventoryReservation.ts
import { Redis } from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);

export async function reserveCheckoutStock(
  orderSessionId: string,
  items: Array<{ productId: number; quantity: number }>
): Promise<boolean> {
  const ttlSeconds = 600; // 10 minutos de reserva

  for (const item of items) {
    const key = `stock:reserved:${item.productId}`;
    
    // Incrementar cantidad reservada
    await redis.incrby(key, item.quantity);
    await redis.expire(key, ttlSeconds);
  }

  return true;
}

Enrutamiento Inteligente de Pedidos hacia el Almacén más Cercano#

Al completarse una orden online, un algoritmo de despacho evalúa el código postal o coordenadas del cliente para asignar el pedido a la sucursal óptima:

  1. Disponibilidad: El almacén debe contar con el 100% de los items para evitar envíos divididos innecesarios.
  2. Distancia: Algoritmo de Haversine para calcular la tienda más próxima con menor coste de delivery.
  3. Impresión Automática de Ticket: Despacho vía Webhook a la impresora térmica de la tienda asignada.

Conclusión#

La sincronización de stock multi-almacén y terminales POS en tiempo real elimina por completo las pérdidas por sobreventa, optimiza los tiempos de entrega local y brinda una experiencia unificada tanto en canales digitales como en puntos de venta físicos.

¿Tu empresa cuenta con tiendas físicas y necesita sincronizar su inventario en tiempo real con WooCommerce o Next.js? Conoce nuestros servicios especializados en Desarrollo E-commerce y Sistemas a Medida o solicita una cotización técnica.

Etiquetas
WooCommercePunto de VentaMulti-AlmacénStock en Tiempo RealWordPressE-commerce
Compartir:XLinkedInWhatsApp

¿Te gustaría profundizar en estos temas?

Aprende sobre desarrollo de software, apps a medida, automatizaciones con N8N, Next.js y Cloud con casos reales.

Hablemos