Recuperação de Carrinho Abandonado
O módulo de recuperação de carrinho (backend/src/cart-recovery/) detecta carrinhos abandonados pelo cliente e dispara até três mensagens de recuperação via WhatsApp, automaticamente, sem nenhuma ação manual do admin.
Visão Geral
| Característica | Detalhe |
|---|---|
| Módulo backend | backend/src/cart-recovery/ |
| Schemas | AbandonedCart, RecoveryAttempt |
| Scheduler | CartRecoveryScheduler — tick a cada 5 min |
| Canal | WhatsApp (Evolution API via WhatsAppService) |
| Endpoint público | POST /t/:slug/cart-recovery/heartbeat |
| Permissão admin | Nenhuma — totalmente automático |
Schemas
AbandonedCart (schemas/abandoned-cart.schema.ts)
| Campo | Tipo | Descrição |
|---|---|---|
tenant | ObjectId | Isolamento multi-tenant |
phone | string | E.164 (+5511999990000) — chave de upsert |
customerName | string? | Nome detectado no carrinho |
items | CartItemSnapshot[] | Snapshot dos itens no momento do último heartbeat |
cartValueBrl | number | Valor total do carrinho (R$) |
nudgesSent | number | Contador de nudges enviados (0–3) |
lastNudgeAt | Date? | Timestamp do último nudge |
recovered | boolean | true quando cliente finaliza pedido |
optedOut | boolean | true quando cliente responde STOP/PARAR |
deepLink | string | URL de retorno ao carrinho (/t/:slug/b/:pdvSlug/cart) |
tenantSlug | string | Desnormalizado para evitar join no scheduler |
Índices:
{ tenant, phone }único — upsert target- TTL 7 dias em
updatedAt— purga automática
RecoveryAttempt (schemas/recovery-attempt.schema.ts)
| Campo | Tipo | Descrição |
|---|---|---|
cart | ObjectId → AbandonedCart | |
attempt | number | 1 = 30 min · 2 = +1 h · 3 = +24 h |
sentAt | Date | |
channel | string | 'whatsapp' (padrão) |
messageId | string? | ID retornado pela Evolution API |
error | string? | Mensagem de erro em caso de falha |
Sequência de Nudges
Heartbeat recebido com itens + phone
│
▼ (updatedAt resetado)
┌─ 30 minutos ──► Nudge 1 ── "Esqueceu alguma coisa? Seu pedido está te esperando"
│
├─ +60 minutos ──► Nudge 2 ── "Últimas peças disponíveis — garanta o seu"
│
└─ +24 horas ───► Nudge 3 ── "Uma última chance: volte e ganhe [desconto]"Stop conditions:
recovered = true(pedido criado no mesmo(tenant, phone))optedOut = true(cliente respondeu STOP)nudgesSent >= 3
Heartbeat — Frontend Integration
O frontend envia um heartbeat a cada 30 segundos quando o carrinho tem itens e o cliente informou telefone:
POST /t/:slug/cart-recovery/heartbeat
Content-Type: application/json
{
"phone": "+5511999990000",
"customerName": "Ana Silva",
"items": [
{ "menuItemId": "...", "name": "X-Burguer", "qty": 1, "unitPrice": 25.90 }
],
"deepLink": "/t/demo/b/balcao/cart"
}Resposta: 204 No Content (sem corpo). Endpoint público — sem autenticação.
Opt-out
Quando o NotificationsService recebe a resposta do cliente com texto STOP, PARAR, CANCELAR ou equivalente, chama CartRecoveryService.markOptedOut(tenantId, phone), setando optedOut = true. Nenhum nudge adicional é enviado.
Integração com Pedido
Quando um pedido é criado (OrdersService.create), a sequência é interrompida via CartRecoveryService.markRecovered(tenantId, phone). O documento permanece no banco (para analytics) mas recovered = true previne novos nudges.
Configurações
Intervalos dos nudges são constantes em cart-recovery.service.ts:
const NUDGE_1_DELAY_MS = 30 * 60_000; // 30 min
const NUDGE_2_DELAY_MS = 60 * 60_000; // 1 h
const NUDGE_3_DELAY_MS = 24 * 60 * 60_000; // 24 hO scheduler roda a cada 5 minutos (CartRecoveryScheduler).