Saltar al contenido principal

Horarios, domicilio y carrito

Todas estas rutas son públicas, salvo el cierre por modalidad. Úsalas en la tienda o en una app para mostrar al cliente qué puede pedir y cuándo.

Rutas​

MétodoRutaPermisoDesde
GET/order-typesPúblico1.6.0
PUT/order-types/{slug}/force-disableGestión1.11.0
GET/schedule/{order_type}/hoursPúblico1.6.0
GET/schedule/{order_type}/datesPúblico1.6.0
GET/schedule/{order_type}/slotsPúblico1.6.0
GET/schedule/{order_type}/statusPúblico1.6.0
GET/delivery/configPúblico1.7.0
POST/delivery/check-addressPúblico1.7.0
POST/delivery/feePúblico1.7.0
GET/delivery/minimumPúblico1.7.0
POST/delivery/check-minimumPúblico1.7.0
POST/cart/validatePúblico1.8.0

{order_type} es delivery (domicilio) o pickup (recogida). Una modalidad desconocida responde 400 rest_invalid_param. Los horarios usan siempre la zona horaria de Ajustes → Generales de WordPress.

Modalidades​

GET /order-types devuelve una lista con el estado de cada modalidad:

{
"slug": "delivery", "label": "Domicilio", "enabled": true, "force_disabled": false,
"open_now": true, "in_hours": true, "next_open": null,
"prep_time_minutes": 30, "slot_size_minutes": 30, "advance_days": 7,
"allow_now": true, "allow_asap": true, "max_per_slot": 8,
"date_selection_enabled": true, "time_selection_enabled": true
}

PUT /order-types/{slug}/force-disable con { "disabled": true } desactiva temporalmente una modalidad, como el interruptor «Desactivar temporalmente» de la app. Responde { slug, force_disabled }.

Horarios y franjas​

  • /hours: horario semanal, { order_type, hours: { monday: [{start, end}], ..., sunday: [] } }. Un tramo cuyo end es menor que start cruza la medianoche.
  • /dates?days=14: próximos días (1 a 90) con { date, weekday, open, ranges_count }.
  • /slots?date=AAAA-MM-DD: franjas del día, con cupo.
  • /status: { open, reason, force_disabled, in_hours, next_open, order_type }. reason puede ser null, not_enabled, emergency_closed, force_disabled u out_of_hours.
curl "https://tienda.example.test/wp-json/notiwoo/v1/schedule/delivery/slots?date=2026-10-01"
{
"success": true,
"data": {
"date": "2026-10-01", "weekday": "thursday",
"ranges": [ { "start": "11:00", "end": "15:00" } ],
"slots": [
{ "time": "11:00", "label": "11:00 am", "starts_at": "2026-10-01T11:00:00-05:00", "available": true, "taken": 0, "max": 8 },
{ "time": "11:30", "label": "11:30 am", "starts_at": "2026-10-01T11:30:00-05:00", "available": false, "taken": 8, "max": 8 }
],
"asap_available": false, "now_available": false, "order_type": "delivery"
}
}

Una franja se llena cuando taken alcanza max. Si max es null, no hay límite. Cuentan para el cupo los pedidos en estado pendiente, en espera, aceptando, en proceso y completado.

Configuración de domicilio​

GET /delivery/config devuelve la configuración pública: dirección y ubicación de la tienda, distancia máxima, modo de tarifa (fixed, per_km o distance por rangos), valores de tarifa, rangos, pedido mínimo por modalidad, moneda, y los indicadores has_api_key y has_browser_map_key. Nunca devuelve las claves de Google.

Comprobar una dirección​

POST /delivery/check-address con { "address": "..." }. El servidor calcula la distancia en coche con Google Routes API y guarda el resultado en caché durante una hora.

{ "deliverable": true, "reason": null, "distance_meters": 3200, "distance_km": 3.2, "fee": 5000, "fee_mode": "distance", "store_address": "Carrera 7 # 1-1, Bogotá", "max_distance_km": 15 }

Responde siempre 200. Si no hay cobertura, deliverable es false y reason explica el motivo: out_of_range, empty_address, no_api_key, no_store_address, request_failed, invalid_response, api_error o http_<código>.

Calcular la tarifa​

POST /delivery/fee

CampoNotas
order_typeObligatorio.
distance_kmSi lo envías, tiene prioridad sobre la dirección.
addressSe calcula la distancia con Google.
cart_totalTotal del carrito.

Responde { order_type, fee, mode, distance_meters, distance_km, distance_source, applies }. distance_source vale param, geocode o none.

Reglas de tarifa:

  • Fija: siempre el mismo valor.
  • Por kilómetro: kilómetros redondeados hacia arriba × valor por km, con un mínimo de 1 km.
  • Por rangos: el primer rango que contiene la distancia. Un límite compartido entre dos rangos pertenece al siguiente.
  • Recogida: 0.

Errores: 422 notiwoo_geocode_failed (no se pudo ubicar la dirección) y 400 notiwoo_invalid_order_type.

Pedido mínimo​

  • GET /delivery/minimum?order_type=delivery responde { order_type, amount, currency, currency_symbol, applies }.
  • POST /delivery/check-minimum con order_type y cart_subtotal responde { order_type, valid, minimum, current, missing }.

Validar el carrito​

POST /cart/validate hace en una sola llamada las mismas comprobaciones que el checkout: precios y extras, horario, mínimo, cobertura y disponibilidad. Responde siempre 200; los problemas llegan en data.errors.

CampoObligatorioNotas
order_typeSí
items[]No{product_id, variation_id, quantity, extra_selections[{category_id, extra_ids[]}], additional_comments}
addressNoPara comprobar la cobertura.
distance_kmNoSolo para vista previa: evita la llamada a Google. POST /orders no lo tiene en cuenta.
cart_subtotalNoSolo se usa si no envías items.
fulfillment_date, fulfillment_timeNoIgual que en POST /orders.
curl -X POST https://tienda.example.test/wp-json/notiwoo/v1/cart/validate \
-H "Content-Type: application/json" \
-d '{
"order_type": "delivery",
"address": "Calle 10 # 20-30, Bogotá",
"fulfillment_date": "2026-10-01",
"fulfillment_time": "12:30",
"items": [ { "product_id": 120, "quantity": 2,
"extra_selections": [ { "category_id": 18, "extra_ids": [91] } ] } ]
}'
{
"success": true,
"data": {
"valid": false,
"errors": [
{ "code": "below_minimum", "message": "Total del carrito por debajo del mínimo.",
"details": { "minimum": 30000, "current": 28000, "missing": 2000 } }
],
"checks": {
"pricing": { "valid": true, "subtotal": "28000.00", "items": [ { "index": 0, "unit_total": "14000.00", "line_total": "28000.00" } ] },
"schedule": { "valid": true, "reason": null },
"minimum": { "valid": false, "minimum": 30000, "current": 28000, "missing": 2000 },
"delivery": { "deliverable": true, "distance_km": 3.2, "fee": 5000 },
"products": [ { "product_id": 120, "available": true, "reasons": [] } ]
},
"canonical_subtotal": 28000
}
}

canonical_subtotal es el subtotal calculado por el servidor. Muéstralo en lugar de un total calculado en el cliente.

Códigos de errors[]​

CódigoSignificado
schedule_selection_invalidEl día u hora no se pueden elegir. details.reason precisa el motivo: out_of_hours, emergency_closed, date_required, time_required, slot_unavailable, date_out_of_range, asap_not_allowed…
below_minimumNo alcanza el pedido mínimo.
out_of_delivery_rangeLa dirección está fuera de la zona de cobertura.
product_unavailableProducto no encontrado, no publicado, desactivado o agotado.
notiwoo_product_out_of_stock, notiwoo_invalid_quantity, notiwoo_product_not_purchasable…Problemas de un artículo. details.item_index indica cuál.
notiwoo_extra_minimum_not_met, notiwoo_extra_maximum_exceededLos extras elegidos no respetan el mínimo o el máximo de su categoría.
notiwoo_extra_disabled, notiwoo_extra_wrong_category, notiwoo_extra_category_not_allowed, notiwoo_duplicate_extraEl extra no está activo, no pertenece a esa categoría, la categoría no aplica al producto, o está repetido.