Arquitectura de integración
Una fuente, varios destinos
La Autoridad mutable existe solo para Catálogo, precio base y cada Stock Pool. El origen y el fulfillment quedan guardados en cada pedido; no cambian cuando se reconfigura una conexión.
Roles de conexión
| Preset Woo | Catálogo y precio base | Inventario | Pedidos |
|---|---|---|---|
moku_source | Destino | Destino | Ingreso habilitado |
woocommerce_catalog_source | Fuente | Destino | Ingreso habilitado |
woocommerce_source | Fuente | Fuente | Ingreso habilitado |
Un rol source reclama Autoridad; destination nunca la reclama. Por eso WooCommerce puede recibir stock cuando Moku u otra conexión sea la fuente.
Limitación actual
Si una conexión externa controla Inventario, el checkout nativo de Moku sigue bloqueado hasta que exista un Adapter de reserva y débito remoto. Los roles no fingen que ese handshake ya está implementado.
Crear una conexión
La API usa producción, aunque los pagos estén en sandbox. Los IDs y las respuestas de esta guía son ficticios; reemplázalos por los de tu tienda.
Crear una conexión con autoridad híbrida
Solicitud · ejemplo ilustrativo
cURL
: "${MOKU_PAT:?}" &&
test "${MOKU_ALLOW_WRITES:-}" = "1" &&
curl --silent --show-error --fail-with-body \
--request POST \
'https://moku.cl/api/v1/vendors/vendor_demo_ceramica/connections' \
--header "Authorization: Bearer ${MOKU_PAT}" \
--header 'Accept: application/json' \
--header 'Idempotency-Key: demo_connection_001' \
--header 'Content-Type: application/json' \
--data-raw '{
"provider": "woocommerce",
"external_account_id": "https://tienda.example.com",
"display_name": "WooCommerce de ejemplo",
"roles": {
"catalog": "source",
"base_price": "source",
"inventory": "destination",
"order_ingress": "enabled",
"external_order_fulfillment": "moku"
}
}'PHP
Ejemplo para PHP CLI con la extensión cURL. No es un plugin de WordPress ni debe ejecutarse en el navegador.
<?php
$token = getenv('MOKU_PAT');
if ($token === false || $token === '') {
throw new RuntimeException('MOKU_PAT');
}
if (getenv('MOKU_ALLOW_WRITES') !== '1') {
throw new RuntimeException('MOKU_ALLOW_WRITES=1');
}
$url = 'https://moku.cl/api/v1/vendors/vendor_demo_ceramica/connections';
$body = <<<'MOKU_REQUEST_JSON'
{
"provider": "woocommerce",
"external_account_id": "https://tienda.example.com",
"display_name": "WooCommerce de ejemplo",
"roles": {
"catalog": "source",
"base_price": "source",
"inventory": "destination",
"order_ingress": "enabled",
"external_order_fulfillment": "moku"
}
}
MOKU_REQUEST_JSON;
$curl = curl_init($url);
if ($curl === false) {
throw new RuntimeException('curl_init');
}
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
'Accept: application/json',
'Authorization: Bearer ' . $token,
'Idempotency-Key: demo_connection_001',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($curl);
if ($response === false) {
$message = curl_error($curl);
curl_close($curl);
throw new RuntimeException($message);
}
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
curl_close($curl);
$result = json_decode($response, false, 512, JSON_THROW_ON_ERROR);
$failed = $status < 200 || $status >= 300;
if ($failed) {
fwrite(STDERR, "HTTP {$status}\n");
}
fwrite(
$failed ? STDERR : STDOUT,
json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR) . PHP_EOL
);
exit($failed ? 1 : 0);Respuesta 201 application/json
{
"data": {
"id": "con_demo_woocommerce",
"vendor_id": "vendor_demo_ceramica",
"provider": "woocommerce",
"external_account_id": "https://tienda.example.com",
"display_name": "WooCommerce de ejemplo",
"status": "active",
"roles": {
"catalog": "source",
"base_price": "source",
"inventory": "destination",
"order_ingress": "enabled",
"external_order_fulfillment": "moku"
},
"health": {
"state": "unknown",
"last_observed_at": null,
"last_success_at": null,
"blockers": []
},
"active_listing_count": 0,
"active_reservation_count": 0,
"unresolved_external_order_count": 0,
"active_channel_fulfillment_count": 0,
"version": 1,
"created_at": "2026-08-26T09:00:00.000Z",
"updated_at": "2026-08-26T09:00:00.000Z"
}
}
Mapear publicaciones
Un channel_listing relaciona un listing externo con un inventory_item_id estable. Guarda los IDs de ambos sistemas; no uses SKU como identidad.
Seguir la guía de producto a stock
Cambiar roles o desconectar
Reemplazar roles exige que no queden listings activos, reservas, pedidos externos sin resolver ni fulfillment del canal. Pausa, reconcilia, deja los contadores en cero y envía expected_version a PUT /connections/{id}/roles. Consulta GET /authority después del cambio.