# API PedidosYa — Referencia para el Cliente

Base URL: `https://{tu-dominio}/api/pedidosya`  
Header requerido en todos los endpoints: `X-PedidosYa-Token: <token>`

---

## Endpoints

| Método | URL | Descripción |
|---|---|---|
| POST | `/estimate` | Cotizar un envío |
| POST | `/estimate/{estimateId}/confirm` | Confirmar una cotización |
| POST | `/shipping` | Crear un envío directo (sin cotización previa) |

---

## POST `/estimate`

Devuelve opciones de precio y tiempo para un envío.

### Request
```json
{
  "referenceId": "ORDER-001",
  "isTest": true,
  "notificationMail": "cliente@example.com",
  "items": [
    {
      "type": "STANDARD",
      "value": 500.00,
      "description": "Celular Samsung",
      "sku": "PHONE-001",
      "quantity": 1,
      "volume": 8.50,
      "weight": 0.5
    }
  ],
  "waypoints": [
    {
      "type": "PICK_UP",
      "addressStreet": "Plaza Independencia 755",
      "city": "Montevideo",
      "latitude": -34.905988,
      "longitude": -56.199592,
      "phone": "+59898765432",
      "name": "Tienda Central"
    },
    {
      "type": "DROP_OFF",
      "addressStreet": "La Cumparsita 1475",
      "city": "Montevideo",
      "latitude": -34.9138414,
      "longitude": -56.1837661,
      "phone": "+59812345678",
      "name": "Juan Pérez",
      "collectMoney": 650.00
    }
  ]
}
```

### Campos

| Campo | Tipo | Req | Descripción |
|---|---|---|---|
| `referenceId` | string | **Sí** | ID interno del pedido |
| `isTest` | boolean | No | `true` para pruebas. Default: `false` |
| `notificationMail` | email | No | Email de notificaciones |
| `items` | array | **Sí** | Productos (1–100 ítems) |
| `items[].type` | string | **Sí** | Siempre `"STANDARD"` |
| `items[].value` | number | **Sí** | Valor del producto |
| `items[].description` | string | **Sí** | Nombre del producto |
| `items[].quantity` | integer | **Sí** | Cantidad |
| `items[].volume` | number | No | Volumen en dm³ |
| `items[].weight` | number | No | Peso en kg |
| `waypoints` | array | **Sí** | Exactamente 2: PICK_UP y DROP_OFF |
| `waypoints[].type` | string | **Sí** | `"PICK_UP"` o `"DROP_OFF"` |
| `waypoints[].addressStreet` | string | **Sí** | Dirección |
| `waypoints[].city` | string | **Sí** | Ciudad |
| `waypoints[].latitude` | number | No | Recomendado para evitar errores |
| `waypoints[].longitude` | number | No | Recomendado para evitar errores |
| `waypoints[].phone` | string | **Sí** | Teléfono de contacto |
| `waypoints[].name` | string | **Sí** | Nombre del contacto |
| `waypoints[].instructions` | string | No | Instrucciones para el motorista |
| `waypoints[].collectMoney` | number | No | Monto a cobrar (solo DROP_OFF) |

### Response — 200 OK

Devuelve las ofertas disponibles. Guardar el `estimateId` para el siguiente paso.

```json
{
  "estimateId": "520644687847685483",
  "referenceId": "ORDER-001",
  "isTest": true,
  "deliveryOffers": [
    {
      "deliveryOfferId": "679db4989914e48805f3303eb29c4dec",
      "deliveryMode": "EXPRESS",
      "estimatedPickUpTime": "2026-03-28T17:00:00Z",
      "confirmationTimeLimit": "2026-03-28T17:12:40Z",
      "totalDeliveryTimeMinutes": 45,
      "pricing": {
        "subTotal": 131.36,
        "taxes": 0,
        "total": 131.36,
        "currency": "UYU"
      }
    }
  ]
}
```

> La cotización **expira a los 15 minutos**. Si confirmas luego de ese tiempo, el sistema pedirá el motorista de todas formas usando los datos originales.

---

## POST `/estimate/{estimateId}/confirm`

Confirma una cotización y solicita el motorista.

### URL Parameter
- `estimateId`: el `estimateId` obtenido en `/estimate` (solo dígitos)

### Request
```json
{
  "deliveryOfferId": "679db4989914e48805f3303eb29c4dec",
  "prepTime": 30
}
```

### Campos

| Campo | Tipo | Req | Descripción |
|---|---|---|---|
| `deliveryOfferId` | string | No | ID de la oferta elegida. Sin este campo, PedidosYa usa la primera disponible |
| `prepTime` | integer (0–180) | No | Minutos de preparación del pedido |

### Comportamiento según `prepTime`

| Condición | HTTP | Descripción |
|---|---|---|
| Sin `prepTime` o `prepTime ≤ 12` | **200** | Motorista solicitado de inmediato |
| `prepTime > 12` | **202** | Motorista se pide automáticamente más tarde |

El tiempo de espera antes de pedir el motorista es: `waitMinutes = prepTime - 12`  
*(Ejemplo: `prepTime = 30` → espera 18 min)*

### Response — 200 OK (despacho inmediato)
```json
{
  "estimateId": "520644687847685483",
  "shippingId": "15562302281527156893283",
  "confirmationCode": "9869921368",
  "status": "CONFIRMED",
  "referenceId": "ORDER-001",
  "isTest": true,
  "shareLocationUrl": "https://example.pedidosya.com.uy/tracking/MTUE1NTY=",
  "route": {
    "deliveryMode": "EXPRESS",
    "estimatedPickUpTime": "2026-03-28T17:00:00Z",
    "pricing": { "total": 131.36, "currency": "UYU" }
  },
  "createdAt": "2026-03-28T16:48:00Z"
}
```

### Response — 202 Accepted (despacho programado)
```json
{
  "estimateId": "520644687847685483",
  "shippingId": null,
  "confirmationCode": null,
  "status": "PENDING_DISPATCH",
  "referenceId": "ORDER-001",
  "isTest": true,
  "route": {
    "deliveryMode": "EXPRESS",
    "estimatedPickUpTime": "2026-03-28T17:00:00Z",
    "pricing": { "total": 131.36, "currency": "UYU" }
  },
  "createdAt": "2026-03-28T16:48:00Z",
  "_scheduled": true,
  "dispatch_at": "2026-03-28T17:06:00Z",
  "prep_time": 30,
  "wait_minutes": 18
}
```

> Cuando `status = "PENDING_DISPATCH"`, el `shippingId` será `null` hasta que el job se ejecute en background. El motorista se pedirá a la hora indicada en `dispatch_at`.

---

## POST `/shipping`

Crea un envío en un solo paso, sin cotización previa.

### Request
```json
{
  "referenceId": "ORDER-002",
  "isTest": true,
  "prepTime": 25,
  "notificationMail": "cliente@example.com",
  "items": [
    {
      "type": "STANDARD",
      "value": 500.00,
      "description": "Celular Samsung",
      "sku": "PHONE-001",
      "quantity": 1,
      "volume": 8.50,
      "weight": 0.5
    }
  ],
  "waypoints": [
    {
      "type": "PICK_UP",
      "addressStreet": "Plaza Independencia 755",
      "city": "Montevideo",
      "latitude": -34.905988,
      "longitude": -56.199592,
      "phone": "+59898765432",
      "name": "Tienda Central"
    },
    {
      "type": "DROP_OFF",
      "addressStreet": "La Cumparsita 1475",
      "city": "Montevideo",
      "latitude": -34.9138414,
      "longitude": -56.1837661,
      "phone": "+59812345678",
      "name": "Juan Pérez",
      "collectMoney": 650.00
    }
  ]
}
```

### Campos adicionales respecto a `/estimate`

| Campo | Tipo | Req | Descripción |
|---|---|---|---|
| `deliveryTime` | datetime UTC | No | Hora de entrega deseada. Sin este campo: lo antes posible |
| `prepTime` | integer (0–180) | No | Igual que en confirm: si `> 12` el despacho se programa |
| `requirements.includeDeliveryFee` | boolean | No | Incluir costo de envío en el cobro al cliente |

### Response — 200 OK (despacho inmediato)
```json
{
  "shippingId": "15562302281527156893283",
  "confirmationCode": "9869921368",
  "status": "CONFIRMED",
  "referenceId": "ORDER-002",
  "isTest": true,
  "shareLocationUrl": "https://example.pedidosya.com.uy/tracking/MTUE1NTY=",
  "route": {
    "deliveryMode": "EXPRESS",
    "estimatedPickUpTime": "2026-03-28T17:00:00Z",
    "pricing": { "total": 130.00, "currency": "UYU" }
  },
  "createdAt": "2026-03-28T16:48:00Z"
}
```

### Response — 202 Accepted (despacho programado)
```json
{
  "shippingId": null,
  "confirmationCode": null,
  "status": "PENDING_DISPATCH",
  "referenceId": "ORDER-002",
  "isTest": true,
  "route": null,
  "createdAt": "2026-03-28T16:48:00Z",
  "_scheduled": true,
  "dispatch_at": "2026-03-28T17:01:00Z",
  "prep_time": 25,
  "wait_minutes": 13
}
```

---

## Errores

| HTTP | `code` | Descripción |
|---|---|---|
| 400 | `MISSING_TOKEN` | Falta el header `X-PedidosYa-Token` |
| 400 | `INVALID_ESTIMATE_ID` | El `estimateId` contiene caracteres no numéricos |
| 400 | `VALIDATION_ERROR` | Campos requeridos ausentes o inválidos |
| 400 | `WAYPOINTS_NOT_FOUND` | PedidosYa no encontró coordenadas para la dirección |
| 403 | — | Token inválido o sin permisos |
| 404 | `ESTIMATE_NOT_FOUND` | El `estimateId` no existe en el sistema |
| 409 | `ALREADY_DISPATCHED` | El pedido ya fue despachado |
| 500 | `INTERNAL_ERROR` | Error interno |

### Ejemplo de error
```json
{
  "status": 400,
  "code": "VALIDATION_ERROR",
  "message": "Error en los datos de entrada",
  "errors": {
    "items.0.value": ["The items.0.value must be a number."]
  }
}
```

---

## Flujos típicos

### Con cotización previa
```
POST /estimate          →  elegir oferta, guardar estimateId
POST /estimate/{id}/confirm  →  200 (ahora) o 202 (programado)
```

### Sin cotización previa
```
POST /shipping          →  200 (ahora) o 202 (programado)
```

---

## Notas rápidas

- Enviar siempre `latitude` y `longitude` para evitar errores de geocodificación.
- Una cotización expira a los **15 minutos**. Confirmar antes de ese tiempo.
- Con `status: PENDING_DISPATCH` el `shippingId` llega `null`; el motorista se pide automáticamente en `dispatch_at`.
- Guardar `shippingId` y `confirmationCode` de las respuestas 200 para seguimiento.
- Usar `shareLocationUrl` para que el destinatario pueda rastrear al motorista.
- `_scheduled: true` en la respuesta indica que el pedido está en cola.
