# PedidosYaController — Flujo de métodos

## `estimate` — Cotizar despacho

```
Cliente → POST /pedidosya/estimate
│         con: referenceId, items, waypoints, deliveryTime? (hora moto en LOCAL)
│
├─ Validar X-PedidosYa-Token header
├─ Validar campos (referenceId, items, waypoints, etc.)
│
├─ ¿Viene deliveryTime?
│   ├─ Paso 1: POST /v3/shippings/estimates (sin deliveryTime, ASAP)
│   │   └─ Extrae drivingMinutes via resolveDrivingMinutes()
│   │
│   └─ Paso 2: POST /v3/shippings/estimates
│       └─ deliveryTime ajustado = clientDeliveryTime + drivingMinutes
│
└─ Sin deliveryTime:
    └─ POST /v3/shippings/estimates (ASAP, una sola llamada)
│
├─ Por cada oferta recibida:
│   └─ Agrega estimatedPickupTimeUTC
│       ├─ Si viene estimatedPickUpTime → lo usa directamente
│       └─ Si viene deliveryTime → lo devuelve tal cual (es la hora de pickup del cliente)
│
├─ Si respuesta exitosa y tiene estimateId:
│   └─ Guarda PedidosYaEstimate en BD
│       └─ request_data incluye _adjusted_delivery_time y _driving_minutes_used si aplica
│
└─ Retorna respuesta PedidosYa tal cual (con estimatedPickupTimeUTC agregado)
```

---

## `confirmEstimate` — Confirmar cotización y pedir motorista

```
Cliente → POST /pedidosya/estimates/{estimateId}/confirm
│         con: deliveryOfferId?
│
├─ Validar token, estimateId (solo números), campos
├─ Buscar PedidosYaEstimate en BD → 404 si no existe
│
├─ Resolver confirmationTimeLimit desde delivery_offers guardadas:
│   ├─ Si viene deliveryOfferId → usa el límite de esa oferta
│   └─ Si no → usa el límite más próximo entre todas las ofertas
│   (fallback: created_at + 15 min si no hay ningún límite)
│
├─ ¿Expiró? (now > confirmationTimeLimit)
│   └─ Sí → createDirectShippingFromExpiredEstimate() [automático, transparente]
│       ├─ Reconstruye body con datos guardados (items, waypoints, reference_id, etc.)
│       ├─ Si delivery_time es futuro → lo ajusta (clientDeliveryTime + _driving_minutes_used)
│       ├─ Si delivery_time ya pasó → envía ASAP sin deliveryTime
│       ├─ POST /v3/shippings directamente
│       ├─ Guarda PedidosYaShipment (source='direct_from_expired_estimate')
│       └─ Retorna respuesta de PedidosYa (shippingId normal, transparente para el cliente)
│
└─ No expiró → Confirmar ahora:
    ├─ POST /v3/shippings/estimates/{estimateId}/confirm
    ├─ Si exitoso → guarda PedidosYaShipment en BD (source='estimate_confirm')
    └─ Retorna respuesta de PedidosYa
```

---

## `createDirectShippingFromExpiredEstimate` — Fallback automático por expiración

```
Invocado internamente por confirmEstimate cuando confirmationTimeLimit fue superado
│
├─ Recupera datos del estimate guardado: reference_id, items, waypoints, is_test, notification_mail
├─ Lee _driving_minutes_used desde request_data (guardado en la cotización original)
│
├─ ¿Tenía deliveryTime?
│   ├─ Sí, y sigue siendo futuro → adjustedDeliveryTime = clientDeliveryTime + _driving_minutes_used
│   └─ Sí, pero ya pasó → envía sin deliveryTime (ASAP)
│
├─ POST /v3/shippings con datos reconstruidos
├─ Guarda PedidosYaShipment (source='direct_from_expired_estimate', linkeado al estimate_id original)
└─ Retorna respuesta de PedidosYa tal cual (shippingId normal)
```

---



```
Cliente → POST /pedidosya/shippings
│         con: referenceId, items, waypoints, deliveryTime? (hora moto en LOCAL)
│
├─ Validar token y campos
│
├─ ¿Viene deliveryTime?
│   ├─ Paso 1: POST /v3/shippings/estimates (sin deliveryTime, ASAP)
│   │   ├─ Si falla → retorna error inmediatamente
│   │   └─ Extrae drivingMinutes via resolveDrivingMinutes()
│   │
│   └─ Paso 2: POST /v3/shippings
│       └─ deliveryTime ajustado = clientDeliveryTime + drivingMinutes
│
└─ Sin deliveryTime:
    └─ POST /v3/shippings directamente (ASAP)
│
├─ Si exitoso → guarda PedidosYaShipment en BD
│   ├─ source = 'direct_shipping'
│   ├─ delivery_time = clientDeliveryTime (original)
│   └─ request_data incluye _original_delivery_time y _driving_minutes_used si aplica
│
└─ Retorna respuesta de PedidosYa
```

---

## `resolveDrivingMinutes` — Cascada de cálculo

```
resolveDrivingMinutes(offer, routesDistance)
│
├─ offer.estimatedDrivingTime presente → retorna ese valor (minutos exactos)
├─ offer.totalDeliveryTimeMinutes presente → retorna max(5, totalDeliveryTimeMinutes - 5)
├─ routesDistance > 0 → retorna ceil(routesDistance / 350)  [≈ 20 km/h]
└─ ninguno → retorna DRIVING_FALLBACK_MINUTES (20)
```

La oferta usada es siempre la EXPRESS si existe; si no, la primera disponible.

---

## Por qué se ajusta `deliveryTime`

El cliente envía `deliveryTime` = "hora en que el moto debe llegar al LOCAL (pickup)".  
PedidosYa interpreta `deliveryTime` = "hora en que el moto debe llegar al CLIENTE (dropoff)".

Por lo tanto:
```
deliveryTime enviado a PedidosYa = clientDeliveryTime + drivingMinutes
```

### Ejemplo completo

**Escenario:** tienda en Montevideo Centro, cliente en Pocitos, ~2.5 km.

```
1. Cliente envía:
   deliveryTime = "2026-04-01T18:30:00Z"   ← "quiero el moto aquí a las 18:30"

2. Paso 1 — cotización ASAP (sin deliveryTime):
   PedidosYa responde oferta EXPRESS:
     estimatedDrivingTime    = null          ← EXPRESS a veces no lo incluye
     totalDeliveryTimeMinutes = 35
     route.distance           = 2500 m

3. resolveDrivingMinutes():
   ├─ estimatedDrivingTime?        → no
   ├─ totalDeliveryTimeMinutes?    → sí → max(5, 35 - 5) = 30 min
   └─ drivingMinutes = 30

4. Ajuste:
   adjustedDeliveryTime = 18:30 + 30 min = "2026-04-01T19:00:00Z"

5. Paso 2 — cotización/envío real enviado a PedidosYa:
   deliveryTime = "2026-04-01T19:00:00Z"   ← "moto en casa del cliente a las 19:00"

6. Guardado en BD:
   delivery_time              = "2026-04-01T18:30:00Z"   ← valor original del cliente
   _adjusted_delivery_time    = "2026-04-01T19:00:00Z"   ← lo que se envió a PedidosYa
   _driving_minutes_used      = 30
```

**Otro escenario** con `estimatedDrivingTime` disponible (p. ej. oferta SCHEDULED):

```
   estimatedDrivingTime     = 17            ← minutos exactos de conducción
   drivingMinutes           = 17            ← prioridad 1

   18:30 + 17 min = "2026-04-01T18:47:00Z"  enviado a PedidosYa
```

**Fallback por distancia** (ningún campo de tiempo en la oferta):

```
   route.distance = 4200 m
   drivingMinutes  = ceil(4200 / 350) = 12 min

   18:30 + 12 min = "2026-04-01T18:42:00Z"  enviado a PedidosYa
```

---

## Constantes relevantes

| Constante | Valor | Descripción |
|---|---|---|
| `DRIVING_FALLBACK_MINUTES` | `20` | Tiempo de viaje fallback si no hay datos suficientes |
| expiración estimate | `offer.confirmationTimeLimit` | Límite real de la oferta elegida (fallback: `created_at + 15 min`) |
