🔁 Reintento de llamadas COD pendientes
Archivo: /Users/user/rifai-agents/agentes/voice/retell-retry-pending.ts · Plist: com.rifai.voice-retry-pending · Horario: cada 2h (StartInterval 7200s), solo dentro de 10:00–22:00 Madrid y nunca en domingo.
Qué hace
Reintenta las llamadas de confirmación COD que quedaron en estado PENDIENTE (no contestó, comunica, colgó pronto, etc.). Toma la cola de reintentos que llena el webhook server.ts cuando una llamada acaba sin confirmar, y vuelve a llamar al cliente hasta un máximo de 3 intentos. Si entre intentos el pedido ya pasó a confirmado/rechazado/problema, lo saca de la cola. Es la segunda oportunidad del flujo de confirmación de pedidos.
Cómo funciona
1. Carga .env y comprueba la ventana horaria (isCallableNow()); sale si no es llamable.
2. Lee la cola de reintentos vencidos con getRetriesDue() (filas con done=0, attempts<3 y next_retry_at <= now).
3. Por cada item: si ya tiene 3 intentos lo marca como hecho y pasa; si no, recupera el pedido fresco de Shopify.
4. Si el pedido ya está confirmado/rechazado/problema, lo marca done y sigue. Si no hay teléfono válido, idem.
5. Prepara las variables de voz en español y relanza la llamada con createOutboundCall.
6. Si la llamada arranca (id), registra el inicio en SQLite (recordCallStart). El incremento del contador de intentos lo hace el webhook al volver a encolar. Throttle 2s entre llamadas.
7. Imprime 📊 Reintentos lanzados: N. No notifica por Telegram (lo hace el webhook al cerrar cada llamada).
Datos/APIs
- Shopify Admin REST (
2024-10): GET del pedido por id. Vars:SHOPIFY_STORE,SHOPIFY_ACCESS_TOKEN. - Vapi (
lib/vapi-client.ts): outbound call. Vars:VAPI_API_KEY,VAPI_ASSISTANT_ID_COD_CONFIRM,VAPI_PHONE_NUMBER_ID(oVAPI_FROM_NUMBER). - SQLite
data/voice-calls.db(tablaretry_queue+calls):getRetriesDue,markRetryDone,recordCallStart. - Helpers:
lib/phone-normalizer.ts,lib/spanish-converter.ts.
Cómo probarlo
cd /Users/user/rifai-agents && npx tsx agentes/voice/retell-retry-pending.ts
Esperado: cabecera 🔁 Retry Pending COD ..., el nº de reintentos en cola y, si hay, 📲 Reintento X/3 ... por cada uno, terminando en 📊 Reintentos lanzados: N. Fuera de horario sale con ⏸. La cola solo tiene contenido si antes hubo llamadas que el webhook marcó PENDIENTE; con la cola vacía imprime 0 reintentos en cola y no llama. OJO: si hay items vencidos, lanza llamadas reales de pago.
Si se rompe / recuperar
launchctl unload ~/Library/LaunchAgents/com.rifai.voice-retry-pending.plist
launchctl load ~/Library/LaunchAgents/com.rifai.voice-retry-pending.plist
Logs: /Users/user/rifai-agents/logs/voice-retry-pending.out.log y .err.log. Si "no reintenta": comprobar que el webhook (voice-server) esté vivo y esté encolando PENDIENTE (queueForRetry), revisar la tabla retry_queue en data/voice-calls.db, y el horario Madrid.
Cómo replicarlo
- Carga
.envestándar. - Cola de reintentos en SQLite (
retry_queue) alimentada por el webhook; selección de vencidos (next_retry_at <= now,attempts<3). - Cliente Vapi + assistant COD + número saliente.
- Normalizador de teléfono + conversor español + ventana horaria.
- Acceso Shopify Admin (lectura de pedido + tags).
- Plist launchd con
StartInterval7200.