# 09 — Sistema de Push Notifications

## Visão Geral

O sistema usa a **Expo Push Notification API** como intermediário, que por sua vez entrega para APNs (Apple) e FCM (Google). Isso simplifica a configuração — não é necessário configurar FCM ou APNs separadamente.

```
App → Expo SDK → Expo Push Service → APNs (iOS) / FCM (Android) → Dispositivo
```

---

## Componentes do Sistema

| Componente | Localização | Responsabilidade |
|------------|-------------|-----------------|
| `NotificationService.js` | `mobile-app/services/` | Registro de token, handler de clique |
| `register_push_token.php` | `webapp/api/` | Armazena token no BD |
| `send_notification.php` | `webapp/api/` | Envia push + grava histórico |
| `get_notifications.php` | `webapp/api/` | Lista notificações do usuário |
| `NotificationsScreen.js` | `mobile-app/screens/` | Tela de histórico de notificações |
| Sino no `DashboardScreen.js` | `mobile-app/screens/` | Badge com contagem não lida |
| Tabela `push_tokens` | BD cliente | Tokens por usuário |
| Tabela `notificacoes` | BD cliente | Histórico persistente |

---

## Fluxo de Registro de Token

```
1. Usuário faz login com sucesso (LoginScreen.js)
2. import('../services/NotificationService').then(...)  [importação dinâmica, background]
3. registerForPushNotifications(user.usuario_id, cliente_codigo)

Dentro de registerForPushNotifications():
   ├── Device.isDevice === false → ignora (simulador)
   ├── Notifications.getPermissionsAsync() → verifica permissão atual
   ├── Permissão !== 'granted' → Notifications.requestPermissionsAsync()
   ├── Notifications.getExpoPushTokenAsync({ projectId })
   │     └── Retorna: "ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]"
   ├── POST /register_push_token.php { user_id, token, platform, cliente }
   │     └── Upsert no push_tokens (UNIQUE KEY on token)
   └── Platform.OS === 'android' → setNotificationChannelAsync('default', ...)
```

---

## Fluxo de Envio de Notificação (Backend)

```php
sendPushNotification($conn, $user_ids, $tipo, $titulo, $mensagem, $referencia_id)

Passos internos:
1. Normalizar $user_ids para array único não-vazio
2. Para cada $uid:
   → INSERT INTO notificacoes (usuario_id, tipo, titulo, mensagem, referencia_id, data_cadastro)
3. SELECT token FROM push_tokens WHERE usuario_id IN ($ids_str)
4. Montar array de messages Expo:
   [
     {
       "to": "ExponentPushToken[xxx]",
       "sound": "default",
       "title": "Título",
       "body": "Mensagem",
       "data": { "tipo": "tarefa", "referencia_id": 42 }
     }
   ]
5. Enviar para https://exp.host/--/api/v2/push/send em lotes de 100
   → cURL POST com Content-Type: application/json
   → timeout: 10 segundos (non-blocking para não atrasar o request do usuário)
```

---

## Gatilhos de Notificação

| # | Evento | Arquivo | Destinatário | Título | Tipo |
|---|--------|---------|-------------|--------|------|
| 1 | Tarefa criada para usuário específico | `task_actions.php` → `create_task` | `usuariodemandado_id` | "Nova Tarefa Atribuída" | `tarefa` |
| 2 | Tarefa criada para distribuição | `task_actions.php` → `create_task` | Usuários da `divisao` com cargo IN(1,2,3,4) | "Nova Tarefa para Distribuição" | `tarefa` |
| 3 | Afazer criado para outro usuário | `task_actions.php` → `add_todo` | `assigned_id` (apenas se ≠ `user_id`) | "Novo Afazer Atribuído" | `afazer` |
| 4 | Status → "Em análise" (3,5,11,14) | `task_actions.php` → `update_task_status` | Recursos do projeto com cargo=N | "Tarefa para Revisão" | `revisao` |
| 5 | Status → "Analisada" (4,6,13,15) | `task_actions.php` → `update_task_status` | Recursos do projeto com cargo=N-1 | "Tarefa Revisada" | `revisao` |
| 6 | Demanda criada para outro usuário | `create_demand.php` | `usuariodemandado_id` (se ≠ `user_id`) | "Nova Demanda Recebida" | `demanda` |

---

## Configuração de Notificação no Foreground

```js
// NotificationService.js
Notifications.setNotificationHandler({
  handleNotification: async () => ({
    shouldShowAlert: true,   // Exibe alerta visual mesmo com app aberto
    shouldPlaySound: true,   // Som de notificação
    shouldSetBadge: true,    // Badge numérico no ícone
  }),
});
```

---

## Deep Link ao Tocar na Notificação

```js
// handleNotificationNavigation(data, navigationRef)

data.tipo === 'tarefa' | 'afazer' | 'revisao'
  → navigation.navigate('TaskDetails', { tarefa_id: data.referencia_id })

data.tipo === 'demanda'
  → navigation.navigate('DemandDetails', { tarefarapida_id: data.referencia_id })
```

---

## Tela de Notificações (NotificationsScreen)

**Localização:** `mobile-app/screens/NotificationsScreen.js`

**Comportamento:**
- Busca `GET /get_notifications.php?user_id=X&cliente=Y` ao montar
- As notificações são marcadas como `lida=1` ao carregar a lista (exceto com `?unread_only=1`)
- Ao tocar: navega para o item via `handlePress(item)`
- Empty state: ícone `notifications-off-outline` + texto

**Badge no Dashboard:**
```js
// DashboardScreen.js - dentro de fetchDashboardData():
fetch(`${API_URL}/get_notifications.php?user_id=X&unread_only=1`)
→ setUnreadNotifications(data.unread_count)
→ Exibido como badge vermelho no botão do sino
```

---

## Configuração no app.json

```json
{
  "expo": {
    "plugins": [
      ["expo-notifications", {
        "icon": "./assets/icon.png",
        "color": "#ffffff"
      }]
    ]
  }
}
```

---

## Banco de Dados das Notificações

### `push_tokens`
```sql
CREATE TABLE push_tokens (
  push_token_id INT AUTO_INCREMENT PRIMARY KEY,
  usuario_id INT NOT NULL,
  token VARCHAR(500) NOT NULL,
  platform VARCHAR(10) NOT NULL DEFAULT 'ios',
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  UNIQUE KEY unique_token (token),
  INDEX idx_usuario (usuario_id)
);
```

### `notificacoes`
```sql
CREATE TABLE notificacoes (
  notificacao_id INT AUTO_INCREMENT PRIMARY KEY,
  usuario_id INT NOT NULL,
  tipo VARCHAR(50) NOT NULL,
  titulo VARCHAR(200) NOT NULL,
  mensagem VARCHAR(500) NOT NULL,
  referencia_id INT DEFAULT NULL,
  lida TINYINT NOT NULL DEFAULT 0,
  data_cadastro DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_usuario_lida (usuario_id, lida)
);
```

---

## Limitações e Considerações

1. **Dispositivo físico obrigatório:** Push notifications não funcionam em simuladores/emuladores
2. **Expo projectId:** Necessário registrar o projeto em [expo.dev](https://expo.dev) para usar `getExpoPushTokenAsync`
3. **Tokens obsoletos:** Se o usuário desinstala e reinstala o app, um novo token é gerado. O upsert por token único garante que o token antigo seja substituído
4. **Sem retry:** O `curl_exec` tem timeout de 10s; falhas de envio não são reconhecidas (fire and forget)
5. **Notificações no histórico:** Mesmo que o push falhe (sem token, dispositivo offline), a notificação é gravada na tabela `notificacoes` e aparece na tela do sino
