|
@@ -1,255 +1,170 @@
|
|
|
-# Plano de Implementação — Push Notifications Marketing
|
|
|
|
|
|
|
+# Push Notifications — Marketing
|
|
|
|
|
|
|
|
-## Visão Geral
|
|
|
|
|
-
|
|
|
|
|
-Sistema de push notifications de marketing para os apps Prestador e Cliente, usando Firebase Cloud Messaging (FCM) via `kreait/laravel-firebase`. Scheduler dispara 3x/dia (08h, 13h, 19h), cada notificação tem sua própria condição de elegibilidade e cooldown controlado por log no banco.
|
|
|
|
|
|
|
+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. Condições de Elegibilidade
|
|
|
|
|
-
|
|
|
|
|
-### PROVIDER
|
|
|
|
|
|
|
+## 1. Fronteira: marketing vs transacional
|
|
|
|
|
|
|
|
-| Label | Condição | Intervalo |
|
|
|
|
|
-|---|---|---|
|
|
|
|
|
-| `provider_reforco_educativo_1` | `approval_status = accepted` + nunca recebeu nenhum reforço educativo **OU** o último foi o #2 há ≥3 semanas | Alterna com #2 a cada 3 semanas |
|
|
|
|
|
-| `provider_reforco_educativo_2` | `approval_status = accepted` + último reforço recebido foi o #1 há ≥3 semanas | Alterna com #1 a cada 3 semanas |
|
|
|
|
|
-| `provider_motivacional_1` | `approval_status = accepted` + não recebeu esta msg há ≥7 dias | Semanal |
|
|
|
|
|
|
|
+A distinção não é por pasta nem por nome, e sim por **estar ou não registrado em
|
|
|
|
|
+`PushNotificationDispatcher::all()`**:
|
|
|
|
|
|
|
|
-### CLIENT
|
|
|
|
|
|
|
+- **Marketing** → está no array `all()`. O scheduler varre `eligibleUsers()`,
|
|
|
|
|
+ aplica cooldown e envia.
|
|
|
|
|
+- **Transacional** → **nã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.
|
|
|
|
|
|
|
|
-| Label | Condição | Cooldown categoria | Cooldown msg |
|
|
|
|
|
-|---|---|---|---|
|
|
|
|
|
-| `cliente_marketing_1..4` | `registration_complete = true` | 7 dias | 28 dias |
|
|
|
|
|
-| `cliente_recorrencia_1..3` | ≥1 agendamento `status = finished` + sem agendamento `finished` nos últimos 30 dias | 7 dias | 28 dias |
|
|
|
|
|
-| `cliente_educativo_1..4` | `registration_complete = true` (todos) | 7 dias | 28 dias |
|
|
|
|
|
-| `cliente_educ_conversao_1..2` | Nunca agendou com `schedule_type = custom` **OU** último foi há >30 dias | 14 dias | 28 dias |
|
|
|
|
|
-| `cliente_social_proof_1..2` | Nunca teve agendamento `status = finished` **OU** último foi há >30 dias | 14 dias | 28 dias |
|
|
|
|
|
-| `cliente_motivacional_1..3` | `registration_complete = true` (todos) | 7 dias | 28 dias |
|
|
|
|
|
-| `cliente_contextual_sexta` | `registration_complete = true` + hoje é sexta-feira | 7 dias | — |
|
|
|
|
|
-| `cliente_contextual_segunda` | `registration_complete = true` + hoje é segunda-feira | 7 dias | — |
|
|
|
|
|
-| `cliente_contextual_visita` | `registration_complete = true` (broadcast geral) | 7 dias | 28 dias |
|
|
|
|
|
|
|
+Categorias de marketing: `marketing`, `manual`.
|
|
|
|
|
+Categorias transacionais: `agenda`, `transacional`.
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-## 2. Conteúdo das Notificações
|
|
|
|
|
-
|
|
|
|
|
-### PROVIDER — Reforço Educativo
|
|
|
|
|
-
|
|
|
|
|
-| Label | Título | Corpo |
|
|
|
|
|
-|---|---|---|
|
|
|
|
|
-| `provider_reforco_educativo_1` | Como funcionam os pedidos Sob Medida | Eles são enviados para várias diaristas ao mesmo tempo. Aceite rápido para garantir. |
|
|
|
|
|
-| `provider_reforco_educativo_2` | Dica importante | Pedidos Sob Medida são compartilhados. A confirmação acontece por ordem de aceite. |
|
|
|
|
|
-
|
|
|
|
|
-### PROVIDER — Motivacional
|
|
|
|
|
-
|
|
|
|
|
-| Label | Título | Corpo |
|
|
|
|
|
-|---|---|---|
|
|
|
|
|
-| `provider_motivacional_1` | Fique de olho 👀 | Novos pedidos Sob Medida surgem o tempo todo no app. |
|
|
|
|
|
-
|
|
|
|
|
-### CLIENT — Marketing
|
|
|
|
|
-
|
|
|
|
|
-| Label | Título | Corpo |
|
|
|
|
|
-|---|---|---|
|
|
|
|
|
-| `cliente_marketing_1` | Casa limpa sem esforço | Encontre uma diarista disponível em poucos minutos. |
|
|
|
|
|
-| `cliente_marketing_2` | Precisando de ajuda hoje? | Veja diaristas disponíveis perto de você. |
|
|
|
|
|
-| `cliente_marketing_3` | Menos preocupação, mais tempo | Agende sua próxima diária agora mesmo. |
|
|
|
|
|
-| `cliente_marketing_4` | Agenda cheia? | Uma diarista pode resolver isso hoje. |
|
|
|
|
|
|
|
+## 2. As 3 pushes automáticas
|
|
|
|
|
|
|
|
-### CLIENT — Recorrência
|
|
|
|
|
|
|
+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.
|
|
|
|
|
|
|
|
-| Label | Título | Corpo |
|
|
|
|
|
-|---|---|---|
|
|
|
|
|
-| `cliente_recorrencia_1` | Hora da próxima diária? | Faz um tempo desde sua última limpeza 😊 |
|
|
|
|
|
-| `cliente_recorrencia_2` | Rotina em dia | Que tal agendar sua próxima diária? |
|
|
|
|
|
-| `cliente_recorrencia_3` | Casa limpa dura pouco | Garanta sua próxima diária no app. |
|
|
|
|
|
|
|
+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`.
|
|
|
|
|
|
|
|
-### CLIENT — Educativo
|
|
|
|
|
|
|
+| 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** |
|
|
|
|
|
|
|
|
-| Label | Título | Corpo |
|
|
|
|
|
-|---|---|---|
|
|
|
|
|
-| `cliente_educativo_1` | Você sabia? | No Diária, o pagamento só é liberado após o serviço concluído. |
|
|
|
|
|
-| `cliente_educativo_2` | Mais segurança | Você acompanha todo o serviço direto pelo app. |
|
|
|
|
|
-| `cliente_educativo_3` | Dica importante | Avaliações ajudam a manter a qualidade das diaristas. |
|
|
|
|
|
-| `cliente_educativo_4` | Transparência | Você vê perfil, avaliações e valores antes de contratar. |
|
|
|
|
|
|
|
+O envio único de `cliente_inativo` é garantido por um `whereDoesntHave` sobre
|
|
|
|
|
+`push_notification_logs` dentro do próprio `eligibleUsers()` — não por cooldown.
|
|
|
|
|
|
|
|
-### CLIENT — Educativo + Conversão
|
|
|
|
|
|
|
+### Conteúdo
|
|
|
|
|
|
|
|
| Label | Título | Corpo |
|
|
| Label | Título | Corpo |
|
|
|
|---|---|---|
|
|
|---|---|---|
|
|
|
-| `cliente_educ_conversao_1` | Sob Medida funciona assim | Seu pedido é enviado para várias diaristas disponíveis. |
|
|
|
|
|
-| `cliente_educ_conversao_2` | Quer mais chances de aceite? | Pedidos Sob Medida aumentam a rapidez na confirmação. |
|
|
|
|
|
|
|
+| `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? |
|
|
|
|
|
|
|
|
-### CLIENT — Social Proof
|
|
|
|
|
|
|
+---
|
|
|
|
|
|
|
|
-| Label | Título | Corpo |
|
|
|
|
|
-|---|---|---|
|
|
|
|
|
-| `cliente_social_proof_1` | Clientes como você | Já estão usando o Diária para facilitar a rotina. |
|
|
|
|
|
-| `cliente_social_proof_2` | Diaristas bem avaliadas | Veja profissionais recomendadas perto de você. |
|
|
|
|
|
|
|
+## 3. Fluxo automático
|
|
|
|
|
|
|
|
-### CLIENT — Motivacional
|
|
|
|
|
|
|
+```
|
|
|
|
|
+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
|
|
|
|
|
+```
|
|
|
|
|
|
|
|
-| Label | Título | Corpo |
|
|
|
|
|
-|---|---|---|
|
|
|
|
|
-| `cliente_motivacional_1` | Sua casa merece cuidado | E você merece tempo livre. |
|
|
|
|
|
-| `cliente_motivacional_2` | Chegue em casa e relaxe | A limpeza fica por nossa conta. |
|
|
|
|
|
-| `cliente_motivacional_3` | Menos bagunça, mais bem-estar | Agende sua diária quando quiser. |
|
|
|
|
|
|
|
+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()`.
|
|
|
|
|
|
|
|
-### CLIENT — Contextual
|
|
|
|
|
|
|
+---
|
|
|
|
|
|
|
|
-| Label | Título | Corpo |
|
|
|
|
|
-|---|---|---|
|
|
|
|
|
-| `cliente_contextual_sexta` | Sexta chegando | Que tal deixar a casa pronta pro fim de semana? |
|
|
|
|
|
-| `cliente_contextual_segunda` | Segunda organizada | Comece a semana com a casa limpa. |
|
|
|
|
|
-| `cliente_contextual_visita` | Visita marcada? | Uma diarista pode ajudar hoje. |
|
|
|
|
|
|
|
+## 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`.
|
|
|
|
|
|
|
|
-## 3. Estrutura de Arquivos — Backend
|
|
|
|
|
|
|
+| 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 |
|
|
|
|
|
|
|
|
```
|
|
```
|
|
|
-app/
|
|
|
|
|
-├── Enums/
|
|
|
|
|
-│ ├── PushNotificationTargetEnum.php
|
|
|
|
|
-│ └── PushNotificationCategoryEnum.php
|
|
|
|
|
-│
|
|
|
|
|
-├── Models/
|
|
|
|
|
-│ ├── DeviceToken.php
|
|
|
|
|
-│ └── PushNotificationLog.php
|
|
|
|
|
-│
|
|
|
|
|
-├── Services/
|
|
|
|
|
-│ ├── PushNotificationService.php # envia via FCM (kreait)
|
|
|
|
|
-│ └── PushNotificationDispatcher.php # orquestra o ciclo completo de envio
|
|
|
|
|
-│
|
|
|
|
|
-├── Notifications/
|
|
|
|
|
-│ └── Push/
|
|
|
|
|
-│ ├── BasePushNotification.php # abstract com LABEL, CATEGORY, TARGET, title(), body(), eligibleUsers()
|
|
|
|
|
-│ │
|
|
|
|
|
-│ ├── Prestador/
|
|
|
|
|
-│ │ ├── ReforcoEducativo/
|
|
|
|
|
-│ │ │ ├── ReforcoEducativo1Push.php
|
|
|
|
|
-│ │ │ └── ReforcoEducativo2Push.php
|
|
|
|
|
-│ │ └── Motivacional/
|
|
|
|
|
-│ │ └── Motivacional1Push.php
|
|
|
|
|
-│ │
|
|
|
|
|
-│ └── Cliente/
|
|
|
|
|
-│ ├── Marketing/
|
|
|
|
|
-│ │ ├── Marketing1Push.php
|
|
|
|
|
-│ │ ├── Marketing2Push.php
|
|
|
|
|
-│ │ ├── Marketing3Push.php
|
|
|
|
|
-│ │ └── Marketing4Push.php
|
|
|
|
|
-│ ├── Recorrencia/
|
|
|
|
|
-│ │ ├── Recorrencia1Push.php
|
|
|
|
|
-│ │ ├── Recorrencia2Push.php
|
|
|
|
|
-│ │ └── Recorrencia3Push.php
|
|
|
|
|
-│ ├── Educativo/
|
|
|
|
|
-│ │ ├── Educativo1Push.php
|
|
|
|
|
-│ │ ├── Educativo2Push.php
|
|
|
|
|
-│ │ ├── Educativo3Push.php
|
|
|
|
|
-│ │ └── Educativo4Push.php
|
|
|
|
|
-│ ├── EducativoConversao/
|
|
|
|
|
-│ │ ├── EducativoConversao1Push.php
|
|
|
|
|
-│ │ └── EducativoConversao2Push.php
|
|
|
|
|
-│ ├── SocialProof/
|
|
|
|
|
-│ │ ├── SocialProof1Push.php
|
|
|
|
|
-│ │ └── SocialProof2Push.php
|
|
|
|
|
-│ ├── Motivacional/
|
|
|
|
|
-│ │ ├── Motivacional1Push.php
|
|
|
|
|
-│ │ ├── Motivacional2Push.php
|
|
|
|
|
-│ │ └── Motivacional3Push.php
|
|
|
|
|
-│ └── Contextual/
|
|
|
|
|
-│ ├── ContextualSextaPush.php
|
|
|
|
|
-│ ├── ContextualSegundaPush.php
|
|
|
|
|
-│ └── ContextualVisitaPush.php
|
|
|
|
|
-│
|
|
|
|
|
-├── Tasks/
|
|
|
|
|
-│ └── SendPushNotificationsTask.php
|
|
|
|
|
-│
|
|
|
|
|
-└── Http/Controllers/Api/
|
|
|
|
|
- └── DeviceTokenController.php
|
|
|
|
|
|
|
+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
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
----
|
|
|
|
|
-
|
|
|
|
|
-## 4. Banco de Dados
|
|
|
|
|
|
|
+`ManualPush` **não** é registrada em `PushNotificationDispatcher::all()` — mesmo
|
|
|
|
|
+padrão das transacionais.
|
|
|
|
|
|
|
|
-### `device_tokens`
|
|
|
|
|
-```
|
|
|
|
|
-id bigint unsigned PK
|
|
|
|
|
-user_id FK → users.id
|
|
|
|
|
-token string (FCM device token)
|
|
|
|
|
-platform enum: android | ios
|
|
|
|
|
-app_type enum: prestador | cliente
|
|
|
|
|
-active boolean default true
|
|
|
|
|
-created_at / updated_at
|
|
|
|
|
-```
|
|
|
|
|
|
|
+Vai para fila porque `sendToUsers()` faz um `sendMulticast` síncrono por
|
|
|
|
|
+usuário; um lote grande estouraria o timeout da request HTTP.
|
|
|
|
|
|
|
|
-### `push_notification_logs`
|
|
|
|
|
-```
|
|
|
|
|
-id bigint unsigned PK
|
|
|
|
|
-label string (ex: 'cliente_marketing_1')
|
|
|
|
|
-user_id FK → users.id
|
|
|
|
|
-target string (prestador | cliente)
|
|
|
|
|
-category string (marketing | recorrencia | educativo | ...)
|
|
|
|
|
-sent_at timestamp
|
|
|
|
|
-created_at / updated_at
|
|
|
|
|
-```
|
|
|
|
|
|
|
+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. Rotas de API
|
|
|
|
|
|
|
+## 5. Infra compartilhada
|
|
|
|
|
|
|
|
-```
|
|
|
|
|
-POST /api/device-tokens → registra token (login / abertura do app)
|
|
|
|
|
-DELETE /api/device-tokens/{token} → remove token (logout)
|
|
|
|
|
-```
|
|
|
|
|
|
|
+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.php` — `prestador` | `cliente`
|
|
|
|
|
+- `app/Enums/PushNotificationCategoryEnum.php` — `marketing`, `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`
|
|
|
|
|
|
|
|
-## 6. Scheduler
|
|
|
|
|
|
|
+### Tabelas
|
|
|
|
|
|
|
|
-Em `bootstrap/app.php`:
|
|
|
|
|
|
|
+`device_tokens`: `user_id`, `token` (unique), `platform` (android|ios),
|
|
|
|
|
+`app_type` (prestador|cliente), `active`.
|
|
|
|
|
|
|
|
-```php
|
|
|
|
|
-$schedule->call(new SendPushNotificationsTask)->dailyAt('08:00');
|
|
|
|
|
-$schedule->call(new SendPushNotificationsTask)->dailyAt('13:00');
|
|
|
|
|
-$schedule->call(new SendPushNotificationsTask)->dailyAt('19:00');
|
|
|
|
|
-```
|
|
|
|
|
|
|
+`push_notification_logs`: `label`, `user_id`, `target`, `category`, `sent_at`.
|
|
|
|
|
+`category` é `string` sem cast de enum — logs de categorias já descontinuadas
|
|
|
|
|
+continuam legíveis.
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-## 7. Frontend — Ambos os Apps
|
|
|
|
|
|
|
+## 6. Fronteira: o que NÃO é marketing
|
|
|
|
|
|
|
|
-### Instalações necessárias (prestador e cliente)
|
|
|
|
|
-```
|
|
|
|
|
-@capacitor/push-notifications
|
|
|
|
|
-```
|
|
|
|
|
|
|
+Pushes transacionais (não alterar ao mexer em marketing):
|
|
|
|
|
|
|
|
-### Arquivos a criar em cada app
|
|
|
|
|
-```
|
|
|
|
|
-src/boot/push-notifications.ts → solicita permissão, captura token, registra via API
|
|
|
|
|
-src/services/pushNotificationService.ts → abstrai chamadas à API de tokens
|
|
|
|
|
-```
|
|
|
|
|
|
|
+- `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`.
|
|
|
|
|
|
|
|
-### Firebase
|
|
|
|
|
-- Criar projeto Firebase (ou dois projetos separados)
|
|
|
|
|
-- Registrar app Android e iOS de cada um
|
|
|
|
|
-- Baixar `google-services.json` (Android) e `GoogleService-Info.plist` (iOS) para cada app
|
|
|
|
|
-- Configurar `capacitor.config.ts` de cada app
|
|
|
|
|
|
|
+**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.
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-## 8. Ordem de Implementação
|
|
|
|
|
-
|
|
|
|
|
-1. Instalar `kreait/laravel-firebase` no backend
|
|
|
|
|
-2. Criar migrations (`device_tokens` e `push_notification_logs`)
|
|
|
|
|
-3. Criar Enums (`PushNotificationTargetEnum`, `PushNotificationCategoryEnum`)
|
|
|
|
|
-4. Criar Models (`DeviceToken`, `PushNotificationLog`)
|
|
|
|
|
-5. Criar `DeviceTokenController` + rotas
|
|
|
|
|
-6. Criar `PushNotificationService` (integração FCM)
|
|
|
|
|
-7. Criar `BasePushNotification` (classe abstrata)
|
|
|
|
|
-8. Criar todas as 24 classes de push notifications
|
|
|
|
|
-9. Criar `PushNotificationDispatcher` (lógica de cooldown + orquestração)
|
|
|
|
|
-10. Criar `SendPushNotificationsTask` + registrar no scheduler
|
|
|
|
|
-11. Frontend Prestador: instalar lib + boot + service
|
|
|
|
|
-12. Frontend Cliente: instalar lib + boot + service
|
|
|
|
|
|
|
+## 7. Apps cliente/prestador
|
|
|
|
|
+
|
|
|
|
|
+O backend nunca envia `data` payload e nenhum dos dois apps lê
|
|
|
|
|
+`notification.data` — **nã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`.
|