# PedidosYa — Shipping Status Webhook (Callback)

> Referencia oficial: https://developers.pedidosya.com/courier-api/v3#tag/Callback/paths/~1your-callback-url/post

---

## Descripción

PedidosYa invoca el callback configurado cada vez que ocurre un evento en un shipping order.

**Configuración:** El callback URL y el `authorizationKey` se configuran vía `PUT /v3/webhooks-configuration`.

**Endpoint configurado en Kuup:**
```
POST /webhook/pedidosya
Authorization: Bearer {token_sanctum}
```

**Seguridad:** PedidosYa envía los headers `Authorization` y `x-api-key` con el valor del `authorizationKey` configurado. Se debe validar en el endpoint.

> **Importante:** Si el endpoint no responde con HTTP 200, PedidosYa reintentará la invocación **una vez más después de 10 minutos**. No se garantiza el orden de llegada de los callbacks.

---

## Ciclo de estados (`data.status`)

| Status | Descripción |
|---|---|
| `CONFIRMED` | Shipping order confirmado, esperando despacho. |
| `IN_PROGRESS` | Transporte asignado. |
| `NEAR_PICKUP` | Transporte cerca del punto de retiro. |
| `PICKED_UP` | Transporte retiró los items del pedido. |
| `NEAR_DROPOFF` | Transporte cerca del punto de entrega. |
| `COMPLETED` | Transporte entregó los items. |
| `CANCELLED` | Shipping order cancelado por cualquier razón. Incluye `cancelCode` y `cancelReason`. |
| `EXPIRED` | Shipping order expirado. |
| `SECURITY_CODE_CREATED` | Se generó un código de seguridad (PIN) para la entrega. |

---

## Mapeo a `delivery_requests`

| Status recibido | Campo actualizado | Fuente del timestamp |
|---|---|---|
| `IN_PROGRESS` (primera vez) | `pickup_started_at` | `generated` |
| `NEAR_PICKUP` | `pickup_eta` | `data.estimatedPickUpTime` |
| `PICKED_UP` | `arrived_at_store_at` | `generated` |
| `NEAR_DROPOFF` | `dropoff_started_at` | `generated` |
| `COMPLETED` | `arrived_at_customer_at`, `delivered_at` | `generated` |
| `CANCELLED` / `EXPIRED` | `canceled_at`, `cancellation_reason` | `generated` |
| `SECURITY_CODE_CREATED` | `pincode` | `data.securityCode` |
| cualquier estado | `last_status`, `webhook_count`, `last_webhook_at` | siempre |

> Los timestamps **solo se guardan la primera vez** para evitar sobreescrituras por callbacks duplicados.

---

## Estructura del payload (nivel raíz)

| Campo | Tipo | Descripción |
|---|---|---|
| `topic` | string | Siempre `"SHIPPING_STATUS"`. |
| `id` | string ≤255 | Identificador del shipping order en PedidosYa. |
| `referenceId` | string ≤255 | ID de referencia interna del cliente (merchant). |
| `confirmationCode` | string | Código de confirmación. No siempre presente (ausente en `CANCELLED`, `NEAR_PICKUP`, `EXPIRED`). |
| `generated` | datetime ISO 8601 | Fecha/hora UTC en que se generó el mensaje. |
| `transmitted` | datetime ISO 8601 | Fecha/hora UTC en que se transmitió el mensaje. |
| `isTest` | boolean | Presente y `true` si es una orden de prueba. |
| `data` | object | Información adicional según el topic. Ver **Data Object**. |

---

## Data Object

| Campo | Tipo | Presente en | Descripción |
|---|---|---|---|
| `status` | string | Siempre | Estado actual del shipping order. |
| `cancelCode` | string | Solo `CANCELLED` | Código de cancelación. Ver tabla de códigos abajo. |
| `cancelReason` | string ≤255 | Solo `CANCELLED` | Mensaje en español del motivo de cancelación. |
| `estimatedPickUpTime` | datetime ISO 8601 | `CONFIRMED`, `IN_PROGRESS`, `NEAR_PICKUP` | Estimación dinámica de hora de retiro (UTC). |
| `estimatedDropOffTime` | datetime ISO 8601 | `CONFIRMED`, `IN_PROGRESS`, `NEAR_PICKUP`, `PICKED_UP`, `NEAR_DROPOFF` | Estimación dinámica de hora de entrega (UTC). |
| `securityType` | string | `SECURITY_CODE_CREATED` | Tipo de seguridad: `PIN`. |
| `securityCode` | string | `SECURITY_CODE_CREATED` | Código de seguridad generado para la entrega. |

---

## Códigos de cancelación (`cancelCode`)

| Código | Mensaje en español |
|---|---|
| `ADDRESS_DATA_MISSING` | Rider no encuentra el pickup/dropoff |
| `NO_RIDER_AVAILABLE` | No hay cadete disponible en este momento |
| `OUT_OF_DELIVERY_ZONE` | Fuera de área de cobertura del servicio |
| `DELAYED_DELIVERY_SCHEDULE` | Cancelado debido a horario de entrega retrasado |
| `COORDINATE_ERROR` | Coordenadas no concuerdan con la dirección ingresada |
| `PACKAGE_DAMAGE_LOOSE` | Se produjo un problema con el producto o paquete |
| `ORDER_NOT_DELIVERED` | Pedido no entregado |
| `INAPPROPRIATE_CONDUCT` | Cancelado por problemas con el rider |
| `UNREACHABLE_RIDER` | Cancelado por problemas con el rider |
| `TYC_PACKAGE_CONTRADICTION` | Pedido incorrecto. Paquete o producto no respeta TyC. |
| `PURCHASE_REQUESTED` | Pedido realizado por error |
| `USER_CANNOT_PAY` | Solicitud de envío pendiente de pago. El usuario no puede pagar el pedido. |
| `COUPON_NOT_APPLIED` | No fue posible aplicar el cupón. |
| `DUPLICATED_ORDER` | Pedido duplicado |
| `UNREACHABLE_USER_DROPOFF` | No es posible contactar al cliente en Punto de Entrega |
| `SUSPICIOUS_CLIENT` | Pedido incorrecto. |
| `USER_CANCELLED` | Cancelado a solicitud del usuario |
| `TECHNICAL_PROBLEM` | Cancelado por problemas técnicos |
| `BAD_WEATHER` | Condiciones climáticas adversas |
| `UNREACHABLE_USER_PICKUP` | No es posible contactar al cliente en Punto de Retiro |
| `CONTENT_WRONG` | Producto despachado no es correcto. |
| `ORDER_MODIFICATION` | No es posible modificar punto de origen o destino |
| `OUT_OF_FLEET_TIME` | Fuera de horario de servicio |
| `TEST_ORDER` | Orden de prueba - TEST |
| `CONTENT_WRONG_RIDER` | Producto despachado no es correcto |

---

## Ejemplos de payload

### CONFIRMED
```json
{
  "topic": "SHIPPING_STATUS",
  "id": "ASDF-12345678",
  "confirmationCode": "9869921368",
  "referenceId": "Client Internal Reference",
  "generated": "2020-06-24T19:00:00Z",
  "transmitted": "2020-06-24T19:08:00Z",
  "data": {
    "status": "CONFIRMED",
    "estimatedPickUpTime": "2020-06-24T19:32:28Z",
    "estimatedDropOffTime": "2020-06-24T19:45:28Z"
  }
}
```

### IN_PROGRESS
```json
{
  "topic": "SHIPPING_STATUS",
  "id": "ASDF-12345678",
  "confirmationCode": "9869921368",
  "referenceId": "Client Internal Reference",
  "generated": "2020-06-24T19:00:00Z",
  "transmitted": "2020-06-24T19:08:00Z",
  "data": {
    "status": "IN_PROGRESS",
    "estimatedPickUpTime": "2020-06-24T19:32:28Z",
    "estimatedDropOffTime": "2020-06-24T19:45:28Z"
  }
}
```

### NEAR_PICKUP
```json
{
  "topic": "SHIPPING_STATUS",
  "id": "ASDF-12345678",
  "referenceId": "Client Internal Reference",
  "generated": "2020-06-24T19:00:00Z",
  "transmitted": "2020-06-24T19:08:00Z",
  "data": {
    "status": "NEAR_PICKUP",
    "estimatedPickUpTime": "2020-06-24T19:32:28Z",
    "estimatedDropOffTime": "2020-06-24T19:45:28Z"
  }
}
```

### PICKED_UP
```json
{
  "topic": "SHIPPING_STATUS",
  "id": "ASDF-12345678",
  "confirmationCode": "9869921368",
  "referenceId": "Client Internal Reference",
  "generated": "2020-06-24T19:00:00Z",
  "transmitted": "2020-06-24T19:08:00Z",
  "data": {
    "status": "PICKED_UP",
    "estimatedDropOffTime": "2020-06-24T19:45:28Z"
  }
}
```

### NEAR_DROPOFF
```json
{
  "topic": "SHIPPING_STATUS",
  "id": "ASDF-12345678",
  "confirmationCode": "9869921368",
  "referenceId": "Client Internal Reference",
  "generated": "2020-06-24T19:00:00Z",
  "transmitted": "2020-06-24T19:08:00Z",
  "data": {
    "status": "NEAR_DROPOFF",
    "estimatedDropOffTime": "2020-06-24T19:45:28Z"
  }
}
```

### COMPLETED
```json
{
  "topic": "SHIPPING_STATUS",
  "id": "ASDF-12345678",
  "confirmationCode": "9869921368",
  "referenceId": "Client Internal Reference",
  "generated": "2020-06-24T19:00:00Z",
  "transmitted": "2020-06-24T19:08:00Z",
  "data": {
    "status": "COMPLETED"
  }
}
```

### CANCELLED
```json
{
  "topic": "SHIPPING_STATUS",
  "id": "ASDF-12345678",
  "referenceId": "Client Internal Reference",
  "generated": "2020-06-24T19:00:00Z",
  "transmitted": "2020-06-24T19:08:00Z",
  "data": {
    "status": "CANCELLED",
    "cancelCode": "NO_RIDER_AVAILABLE",
    "cancelReason": "No hay cadete disponible en este momento"
  }
}
```

### EXPIRED
```json
{
  "topic": "SHIPPING_STATUS",
  "id": "ASDF-12345678",
  "referenceId": "Client Internal Reference",
  "generated": "2020-06-24T19:00:00Z",
  "transmitted": "2020-06-24T19:08:00Z",
  "data": {
    "status": "EXPIRED"
  }
}
```

### SECURITY_CODE_CREATED
```json
{
  "topic": "SHIPPING_STATUS",
  "id": "15562302281527156893283",
  "referenceId": "6543216354",
  "confirmationCode": "9869921368",
  "generated": "2023-03-01T10:27:17Z",
  "transmitted": "2023-03-01T10:27:17Z",
  "isTest": true,
  "data": {
    "status": "SECURITY_CODE_CREATED",
    "estimatedPickUpTime": "2023-03-01T15:00:00Z",
    "estimatedDropOffTime": "2023-03-01T15:00:00Z",
    "securityType": "PIN",
    "securityCode": "65421"
  }
}
```

---

## Notas importantes

- El `id` del payload corresponde al `delivery_id` en `delivery_requests`.
- El `referenceId` corresponde al ID interno de Kuup (`external_sale_uuid` o `quote_id` según el flujo).
- `estimatedPickUpTime` y `estimatedDropOffTime` son **dinámicos** — se actualizan con cada callback.
- `SECURITY_CODE_CREATED` llega antes del retiro cuando se requiere PIN para la entrega.
- Los estados `EXPIRED` y `SECURITY_CODE_CREATED` **no tienen** `confirmationCode`.
- La implementación en `WebhookDeliveryStatusController::processPedidosYa()` está pendiente de completar una vez se reciban logs reales de producción.
