PLANO_PUSH.md 7.6 KB

Push Notifications — Marketing

Documento de referência do sistema de push de marketing. As pushes transacionais (agendamento, pagamento, aprovação de cadastro) não são descritas aqui — ver a seção "Fronteira" no fim.


1. Fronteira: marketing vs transacional

A distinção não é por pasta nem por nome, e sim por estar ou não registrado em PushNotificationDispatcher::all():

  • Marketing → está no array all(). O scheduler varre eligibleUsers(), aplica cooldown e envia.
  • Transacionalnão está no array. Tem eligibleUsers() devolvendo Collection vazia e cooldowns 0, e é disparada por chamada direta a PushNotificationService::sendToUser() a partir do serviço de domínio.

Categorias de marketing: marketing, manual. Categorias transacionais: agenda, transacional.


2. As 3 pushes automáticas

Todas de cliente, categoria marketing, categoryCooldownDays() = 0 — as três condições são mutuamente exclusivas por construção, então cooldown de categoria só atrapalharia.

Definições:

  • "agendamento / pedido" = qualquer linha em schedules do cliente, em qualquer status, contada por created_at. Cobre Sob Medida, já que custom_schedules é filha de schedules.
  • "primeiro acesso" = users.created_at.
Label Classe Gatilho Repetição
cliente_abandono_funil Cliente/Marketing/AbandonoFunilPush Cadastro há ≥ 48h e nenhum agendamento nunca a cada 7 dias
cliente_recorrencia_quebrada Cliente/Marketing/RecorrenciaQuebradaPush Tem agendamento, o último foi há ≥ 30 e < 90 dias a cada 30 dias
cliente_inativo Cliente/Marketing/ClienteInativoPush Tem agendamento, o último foi há ≥ 90 dias 1 vez por cliente, para sempre

O envio único de cliente_inativo é garantido por um whereDoesntHave sobre push_notification_logs dentro do próprio eligibleUsers() — não por cooldown.

Conteúdo

Label Título Corpo
cliente_abandono_funil Casa limpa sem esforço! ✨ Notei que você olhou nosso app! Que tal garantir sua próxima faxina? Toque e agende.
cliente_recorrencia_quebrada Já faz um mês... 🗓️ Sua casa merece aquele cuidado de novo. Encontre seu diarista favorito e agende em menos de 2 minutos!
cliente_inativo Que saudade de ver tudo brilhando! 🫧 Faz tempo que você não passa por aqui. Vamos renovar o ambiente?

3. Fluxo automático

bootstrap/app.php  →  dailyAt('10:00')
  └─ SendPushNotificationsTask::__invoke()        (try/catch + Log::error)
       └─ PushNotificationDispatcher::dispatch()
            foreach (all() as $notification):
              1. eligibleUsers()
              2. applyCooldowns()                 (categoria + label)
              3. PushNotificationService::sendToUsers()
                   - aborta se !push_notifications_enabled
                   - busca device_tokens (user_id + app_type = target + active)
                   - sendMulticast (channel_id 'diaria', priority high)
                   - desativa tokens inválidos
                   - se ≥1 sucesso → grava push_notification_logs

Para adicionar uma push de marketing: criar a classe em app/Notifications/Push/Cliente/Marketing/ estendendo BasePushNotification, implementar os 6 métodos abstratos, sobrescrever os cooldowns, e registrá-la em PushNotificationDispatcher::all().


4. Push manual (backoffice)

Módulo "Pushs" no menu do backoffice: o operador escolhe a origem (cliente/prestador), seleciona N usuários daquela origem, digita título e descrição e envia. Sem cooldown, com log próprio (categoria manual), e respeitando o opt-out push_notifications_enabled.

Peça Arquivo
Notificação com título/corpo dinâmicos app/Notifications/Push/Manual/ManualPush.php
Envio assíncrono (chunks de 50) app/Jobs/SendManualPushJob.php
Destinatários / envio / histórico app/Services/ManualPushService.php
Endpoints app/Http/Controllers/PushNotificationController.php
Validação app/Http/Requests/ManualPushRequest.php
Rotas routes/authRoutes/push_notification.php
Permissão scope push.notification (bits 259) nos seeders
GET  /api/push-notifications/recipients?target=cliente|prestador   permission:push.notification,view
GET  /api/push-notifications/history                               permission:push.notification,view
POST /api/push-notifications/send                                  permission:push.notification,add

ManualPush não é registrada em PushNotificationDispatcher::all() — mesmo padrão das transacionais.

Vai para fila porque sendToUsers() faz um sendMulticast síncrono por usuário; um lote grande estouraria o timeout da request HTTP.

Frontend (sfp_front_vue_diarista_backoffice): src/pages/pushNotification/PushNotificationsPage.vue, src/components/pushNotification/PushRecipientsSelect.vue, src/api/pushNotification.js, src/router/routes/pushNotification.route.js, entrada em src/stores/navigation.js.


5. Infra compartilhada

Usada tanto por marketing quanto por transacional — nenhuma assinatura pública aqui pode mudar sem revisar as duas pontas.

  • app/Services/PushNotificationService.php — envio FCM + log + desativação de tokens
  • app/Notifications/Push/BasePushNotification.php — contrato das notificações
  • app/Enums/PushNotificationTargetEnum.phpprestador | cliente
  • app/Enums/PushNotificationCategoryEnum.phpmarketing, manual, agenda, transacional
  • app/Models/DeviceToken.php, app/Models/PushNotificationLog.php
  • app/Services/DeviceTokenService.php + DeviceTokenController (POST/DELETE /device-tokens)
  • Flag de opt-out: users.push_notifications_enabled

Tabelas

device_tokens: user_id, token (unique), platform (android|ios), app_type (prestador|cliente), active.

push_notification_logs: label, user_id, target, category, sent_at. category é string sem cast de enum — logs de categorias já descontinuadas continuam legíveis.


6. Fronteira: o que NÃO é marketing

Pushes transacionais (não alterar ao mexer em marketing):

  • app/Notifications/Push/Cliente/Agendamento/PrestadorAceitouPush, PrestadorRecusouPush, PrestadorCancelouPush
  • app/Notifications/Push/Prestador/Agendamento/NewPushRequest, NovaOportunidadePush, ClienteAceitouPush, ClienteRecusouPush, ClienteCancelouPush, CodigoNaoPreenchidoPush
  • app/Notifications/Push/Prestador/Pagamento/ClienteEfetuouPagamentoPush
  • app/Notifications/Push/Prestador/Transacional/CadastroAprovadoPush

Call sites: ScheduleService, CustomScheduleService, ProviderService, NotifyProvidersOfNewOpportunityJob, SendOpportunityPushJob. Cobertura: tests/Feature/ProviderApprovalPushTest.php.

Gotcha ao disparar push a partir de um serviço de domínio: resolver PushNotificationService com app(PushNotificationService::class) dentro do try, nunca por injeção no construtor — injetar faz o container instanciar o Messaging do Firebase, e uma credencial inválida derruba o CRUD inteiro. Sempre try/catch (\Throwable) + Log::error: falha de push não pode derrubar a operação de negócio.


7. Apps cliente/prestador

O backend nunca envia data payload e nenhum dos dois apps lê notification.datanão existe roteamento de push por tipo. Adicionar ou remover tipos de push no backend não exige mudança nos apps. Os únicos acoplamentos reais são o channel_id: 'diaria' (criado no boot src/boot/push-notifications.js de cada app) e o app_type gravado em device_tokens.