Appearance
Comunicação e Notificações
Visão Geral
O módulo de Comunicação (Domain/Communication) oferece notificações in-app, mural de pedidos de oração em tempo real, alertas de aniversariantes e envio de e-mail em massa com templates.
Broadcasting: Laravel Reverb + Echo para atualizações em tempo real.
Notificações In-App
Infraestrutura
- Tabela
notifications— tabela nativa do Laravel (UUID) no banco do tenant - NotificationPreference — preferências por membro, canal (
database/mail) e categoria (prayer/birthday/general/event) - HasDynamicChannels trait — permite que notificações respeitem as preferências do membro
Sino de Notificação (Header)
O componente NotificationBell fica no header e:
- Mostra badge com contagem de não lidas
- Dropdown com últimas 5 notificações ao clicar
- Atualiza em tempo real via Echo (canal privado por user)
- JSON endpoints:
GET /notifications/unread-count,GET /notifications/latest
Página de Notificações
Rota: /notificationsPermissão: notification.view (todos os roles)
Lista paginada de notificações com marcar como lida individual ou todas.
Preferências
Rota: /notification-preferencesPermissão: notification.manage (todos os roles)
Grid de toggles: categorias (linhas) × canais (colunas). Rota de update via composite key: PATCH /notification-preferences/{channel}/{category}.
Feature Flag
notifications — habilitado para todos os planos.
Mural de Pedidos de Oração
Models
PrayerRequest (prayer_requests)
| Campo | Tipo | Descrição |
|---|---|---|
| ulid | string(26) | Identificador público |
| member_id | FK → members | Quem postou |
| content | text | Conteúdo do pedido |
| is_anonymous | boolean | Postou anonimamente |
| status | enum | pending, approved, rejected |
| approved_by | FK → users | Moderador que aprovou |
| approved_at | timestamp | Data de aprovação |
PrayerSupport (prayer_supports) — pivot "Estou orando"
| Campo | Tipo | Descrição |
|---|---|---|
| prayer_request_id | FK | Pedido |
| member_id | FK | Quem está orando |
Unique constraint: [prayer_request_id, member_id]
Fluxo
- Membro posta pedido (opcionalmente anônimo) → status
pending - Moderador aprova/rejeita
- Aprovação dispara broadcast
PrayerRequestApproved→ aparece no mural em tempo real - Membros clicam "Estou orando" → toggle via
TogglePrayerSupportAction→ broadcastPrayerSupportUpdated
Throttle
Máximo 5 pedidos por dia por membro (PrayerRequestThrottledException).
Rotas
| Método | Rota | Ação |
|---|---|---|
| GET | /prayer-wall | Lista aprovados + pendentes (moderador) |
| POST | /prayer-wall | Criar pedido |
| PATCH | /prayer-wall/{ulid}/approve | Aprovar |
| PATCH | /prayer-wall/{ulid}/reject | Rejeitar |
| DELETE | /prayer-wall/{ulid} | Excluir (próprio ou moderador) |
| POST | /prayer-wall/{ulid}/support | Toggle "Estou orando" |
Permissões
| Permissão | Roles |
|---|---|
prayer.view | membro |
prayer.create | membro |
prayer.moderate | admin-igreja, admin-congregacao, lider-grupo |
Feature Flag
prayer_wall — habilitado para todos os planos.
Aniversariantes
Lógica
Usa data_nascimento do Member. Query cross-DB compatível (SQLite para testes, MySQL para produção) via strftime/MONTH()+DAY().
Actions
GetBirthdayMembersAction— períodos:today,week,monthSendBirthdayNotificationsAction— notifica roles de liderança
Widget no Dashboard
Card "Aniversariantes da Semana" com avatar/iniciais, nome, data e idade.
Job Agendado
SendDailyBirthdayNotifications — diário às 07:00, itera todos os tenants com feature birthday_alerts.
Feature Flag
birthday_alerts — planos Crescimento e Expansão.
E-mail em Massa
Models
EmailTemplate (email_templates)
| Campo | Tipo | Descrição |
|---|---|---|
| ulid | string(26) | Identificador público |
| name | string | Nome do template |
| slug | string | Slug único |
| subject_template | string | Assunto com |
| body_template | text | Body com (NÃO Blade) |
| variables | json | Lista de variáveis disponíveis |
5 templates pré-definidos (seed): Comunicado Geral, Convite para Evento, Aviso Importante, Boletim Semanal, Boas-vindas a Novo Membro.
MassEmail (mass_emails)
| Campo | Tipo | Descrição |
|---|---|---|
| ulid | string(26) | Identificador público |
| email_template_id | FK | Template usado |
| subject | string | Assunto renderizado |
| body | text | Conteúdo renderizado |
| filters | json | Critérios de audiência |
| status | enum | draft, sending, sent, failed |
| total_recipients | int | Total destinatários |
| sent_count / failed_count | int | Contadores |
MassEmailRecipient (mass_email_recipients) — rastreio por destinatário.
Fluxo (Wizard 4 Steps)
- Selecionar template
- Preencher variáveis + assunto
- Selecionar audiência (grupos + filtros: sexo, idade, congregação, status)
- Preview + contagem → enviar
Audiência
ResolveAudienceAction aceita filtros com ULIDs (nunca IDs):
group_ulids— grupossexo— M/Fidade_min/idade_max— faixa etáriastatus_membro— situação na igreja
Queue
Bus::batch() com chunks de 50 recipients. Callbacks atualizam status do MassEmail.
Permissões
| Permissão | Roles |
|---|---|
mass_email.view | admin-igreja, admin-congregacao, secretario |
mass_email.create | admin-igreja, admin-congregacao, secretario |
mass_email.manage | admin-igreja |
Feature Flag
mass_email — planos Crescimento e Expansão.
Feature Trial (Painel Central)
Mudança no extra_features
O campo extra_features do Tenant evoluiu de array de strings para array de objetos com expiração opcional:
php
// Formato: [{'feature': 'mass_email', 'expires_at': '2026-04-15'}, ...]Tenant::hasFeature() suporta ambos os formatos (backward compat).
Actions (Domain/Shared)
GrantFeatureTrialAction— ativa feature com ou sem data de expiraçãoRevokeFeatureAction— remove featureCleanExpiredFeaturesAction— job diário às 00:30, remove trials vencidos
Painel Admin Central
| Rota | Ação |
|---|---|
GET /admin/tenants/{tenant}/features | Lista features do tenant |
POST /admin/tenants/{tenant}/features | Ativar feature/trial |
DELETE /admin/tenants/{tenant}/features/{feature} | Desativar |
GET /admin/plans/{plan}/features | Editar features do plano |
PUT /admin/plans/{plan}/features | Salvar features do plano |
Broadcasting (Reverb + Echo)
Setup
- Backend: Laravel Reverb (server), config em
config/broadcasting.php - Frontend: Laravel Echo + pusher-js em
resources/js/echo.ts - Auth: rota manual
POST /broadcasting/auth(nãoBroadcast::routes()que sobrescreve middleware) - Authorizer customizado com axios (envia CSRF + cookies)
Canais
| Canal | Tipo | Uso |
|---|---|---|
App.Domain.User.Models.User.{ulid} | Privado | Notificações do user |
prayer-wall.{tenantId} | Privado | Atualizações do mural |
Variáveis de Ambiente
env
BROADCAST_CONNECTION=reverb
REVERB_APP_ID=...
REVERB_APP_KEY=...
REVERB_APP_SECRET=...
REVERB_HOST=reverb # hostname Docker interno
REVERB_PORT=8080
VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
VITE_REVERB_HOST="localhost" # "localhost" para o browser
VITE_REVERB_PORT="${REVERB_PORT}"
VITE_REVERB_SCHEME="${REVERB_SCHEME}"Sidebar — Grupo Comunicação
| Item | Ícone | Rota | Permissão |
|---|---|---|---|
| Notificações | Bell | notifications.index | notification.view |
| Mural de Oração | BookHeart | prayer-wall.index | prayer.view |
| E-mail em Massa | mass-emails.index | mass_email.view | |
| Preferências | Settings | notification-preferences.index | notification.manage |