@natyapp/meta 1.6.6 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/copilot-instructions.md +1540 -0
- package/README.md +513 -40
- package/dist/elements/index.d.ts +1 -0
- package/dist/elements/index.js +1 -0
- package/dist/elements/mediaTemplate.d.ts +45 -0
- package/dist/elements/mediaTemplate.js +47 -0
- package/dist/elements/textTemplate.d.ts +12 -1
- package/dist/elements/textTemplate.js +13 -6
- package/dist/index.d.ts +7 -2
- package/dist/index.js +11 -2
- package/dist/interfaces/IConnection.d.ts +2 -2
- package/dist/interfaces/ILog.d.ts +2 -2
- package/dist/interfaces/ILogger.d.ts +62 -0
- package/dist/interfaces/ILogger.js +2 -0
- package/dist/interfaces/ISdk.d.ts +4 -2
- package/dist/interfaces/IWebhook.d.ts +2 -2
- package/dist/interfaces/index.d.ts +1 -0
- package/dist/interfaces/index.js +1 -0
- package/dist/queue/messageQueue.d.ts +1 -1
- package/dist/queue/messageQueue.js +45 -0
- package/dist/routes/webhooks/methods/connection.js +78 -11
- package/dist/routes/webhooks/methods/messages.js +18 -3
- package/dist/services/axiosInstances.d.ts +14 -5
- package/dist/services/axiosInstances.js +111 -23
- package/dist/services/middlewares/validations.d.ts +2 -2
- package/dist/services/middlewares/validations.js +1 -2
- package/dist/services/mutations/connection.js +1 -1
- package/dist/services/mutations/logs.js +1 -1
- package/dist/services/mutations/messages.js +1 -1
- package/dist/services/mutations/validation.d.ts +1 -1
- package/dist/services/mutations/validation.js +1 -1
- package/dist/services/mutations/webhooks.js +1 -1
- package/dist/types/logs.d.ts +1 -1
- package/dist/types/requestTypes.d.ts +2 -0
- package/dist/useCases/connection/index.d.ts +9 -9
- package/dist/useCases/connection/index.js +156 -7
- package/dist/useCases/events/NatyEvents.d.ts +4 -2
- package/dist/useCases/events/NatyEvents.js +13 -1
- package/dist/useCases/log/index.d.ts +5 -5
- package/dist/useCases/log/index.js +65 -3
- package/dist/useCases/message/whatsappResponse.d.ts +48 -22
- package/dist/useCases/message/whatsappResponse.js +672 -105
- package/dist/useCases/messages/index.d.ts +5 -5
- package/dist/useCases/messages/index.js +66 -3
- package/dist/useCases/sdk/index.d.ts +8 -4
- package/dist/useCases/sdk/index.js +38 -6
- package/dist/useCases/webhook/index.d.ts +9 -9
- package/dist/useCases/webhook/index.js +154 -7
- package/dist/utils/consoleLogger.d.ts +20 -0
- package/dist/utils/consoleLogger.js +51 -0
- package/dist/utils/index.d.ts +6 -0
- package/dist/utils/index.js +6 -0
- package/dist/utils/loggerContext.d.ts +57 -0
- package/dist/utils/loggerContext.js +90 -0
- package/dist/utils/methodContext.d.ts +34 -0
- package/dist/utils/methodContext.js +48 -0
- package/dist/utils/parseError.d.ts +12 -0
- package/dist/utils/parseError.js +27 -3
- package/dist/utils/pinoAdapter.d.ts +30 -0
- package/dist/utils/pinoAdapter.js +68 -0
- package/dist/utils/sanitize.d.ts +42 -0
- package/dist/utils/sanitize.js +120 -0
- package/dist/utils/tryCatch.d.ts +10 -1
- package/dist/utils/tryCatch.js +40 -5
- package/docs/01-visao-geral.md +355 -0
- package/docs/02-contexto-negocio.md +596 -0
- package/docs/03-arquitetura.md +925 -0
- package/docs/04-fluxos-funcionais.md +887 -0
- package/docs/05-integracoes.md +960 -0
- package/docs/06-entidades.md +849 -0
- package/docs/07-guia-pratico.md +1133 -0
- package/docs/08-troubleshooting.md +816 -0
- package/docs/README.md +125 -0
- package/examples/logger-example.ts +279 -0
- package/package.json +2 -2
- /package/dist/{Entities → entities}/Logs.d.ts +0 -0
- /package/dist/{Entities → entities}/Logs.js +0 -0
- /package/dist/{Entities → entities}/connection.d.ts +0 -0
- /package/dist/{Entities → entities}/connection.js +0 -0
- /package/dist/{Entities → entities}/errorLogs.d.ts +0 -0
- /package/dist/{Entities → entities}/errorLogs.js +0 -0
- /package/dist/{Entities → entities}/index.d.ts +0 -0
- /package/dist/{Entities → entities}/index.js +0 -0
- /package/dist/{Entities → entities}/messages.d.ts +0 -0
- /package/dist/{Entities → entities}/messages.js +0 -0
- /package/dist/{Entities → entities}/webhooks.d.ts +0 -0
- /package/dist/{Entities → entities}/webhooks.js +0 -0
- /package/dist/{Entities → entities}/whatsappMessage.d.ts +0 -0
- /package/dist/{Entities → entities}/whatsappMessage.js +0 -0
- /package/dist/{Errors → errors}/Either.d.ts +0 -0
- /package/dist/{Errors → errors}/Either.js +0 -0
- /package/dist/{Errors → errors}/ErrorHandling.d.ts +0 -0
- /package/dist/{Errors → errors}/ErrorHandling.js +0 -0
- /package/dist/{Errors → errors}/index.d.ts +0 -0
- /package/dist/{Errors → errors}/index.js +0 -0
|
@@ -0,0 +1,960 @@
|
|
|
1
|
+
# 05 - Integrações com Sistemas Externos
|
|
2
|
+
|
|
3
|
+
[⬅️ Voltar ao Índice](README.md)
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 📋 Sumário
|
|
8
|
+
- [Visão Geral das Integrações](#-visão-geral-das-integrações)
|
|
9
|
+
- [Meta WhatsApp Business API (Graph API)](#-meta-whatsapp-business-api-graph-api)
|
|
10
|
+
- [Naty Meta API (Backend Proprietário)](#-naty-meta-api-backend-proprietário)
|
|
11
|
+
- [Express.js (Integração Opcional)](#-expressjs-integração-opcional)
|
|
12
|
+
- [Diagramas de Integração](#-diagramas-de-integração)
|
|
13
|
+
- [Rate Limits e Throttling](#-rate-limits-e-throttling)
|
|
14
|
+
- [Segurança e Autenticação](#-segurança-e-autenticação)
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 🌐 Visão Geral das Integrações
|
|
19
|
+
|
|
20
|
+
O SDK Naty Meta integra-se com três sistemas principais:
|
|
21
|
+
|
|
22
|
+
```mermaid
|
|
23
|
+
graph TB
|
|
24
|
+
subgraph "SDK Naty Meta"
|
|
25
|
+
SDK[NatyMeta Core]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
subgraph "API Proprietária"
|
|
29
|
+
NATY[Naty Meta API<br/>Gestão de Conexões]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
subgraph "API Meta/Facebook"
|
|
33
|
+
GRAPH[Meta Graph API v18.0<br/>WhatsApp Business]
|
|
34
|
+
OAUTH[Meta OAuth API<br/>Token Management]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
subgraph "Framework Web (Opcional)"
|
|
38
|
+
EXPRESS[Express.js<br/>Webhook Server]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
SDK -->|CRUD Conexões<br/>CRUD Webhooks<br/>CRUD Logs| NATY
|
|
42
|
+
SDK -->|Envio Mensagens<br/>Upload/Download Mídia| GRAPH
|
|
43
|
+
SDK -->|Refresh Tokens| OAUTH
|
|
44
|
+
SDK -->|Registra Rotas<br/>Webhook Routes| EXPRESS
|
|
45
|
+
|
|
46
|
+
EXPRESS -->|Recebe Webhooks| GRAPH
|
|
47
|
+
|
|
48
|
+
style SDK fill:#4CAF50,color:#fff
|
|
49
|
+
style NATY fill:#2196F3,color:#fff
|
|
50
|
+
style GRAPH fill:#FF9800,color:#fff
|
|
51
|
+
style OAUTH fill:#FFC107
|
|
52
|
+
style EXPRESS fill:#9C27B0,color:#fff
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## 📱 Meta WhatsApp Business API (Graph API)
|
|
58
|
+
|
|
59
|
+
### Informações Gerais
|
|
60
|
+
|
|
61
|
+
| Propriedade | Valor |
|
|
62
|
+
|-------------|-------|
|
|
63
|
+
| **Base URL** | `https://graph.facebook.com/v18.0` |
|
|
64
|
+
| **Protocolo** | HTTPS (REST API) |
|
|
65
|
+
| **Autenticação** | Bearer Token (OAuth 2.0) |
|
|
66
|
+
| **Formato** | JSON |
|
|
67
|
+
| **Documentação Oficial** | [developers.facebook.com/docs/whatsapp](https://developers.facebook.com/docs/whatsapp/cloud-api) |
|
|
68
|
+
|
|
69
|
+
### Endpoints Utilizados
|
|
70
|
+
|
|
71
|
+
#### 1. Envio de Mensagens
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
POST /{phone-number-id}/messages
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Headers:**
|
|
78
|
+
```http
|
|
79
|
+
Authorization: Bearer {access_token}
|
|
80
|
+
Content-Type: application/json
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Body (Texto simples):**
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"messaging_product": "whatsapp",
|
|
87
|
+
"recipient_type": "individual",
|
|
88
|
+
"to": "5511999999999",
|
|
89
|
+
"type": "text",
|
|
90
|
+
"text": {
|
|
91
|
+
"preview_url": false,
|
|
92
|
+
"body": "Olá! Esta é uma mensagem de teste."
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**Body (Botões):**
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"messaging_product": "whatsapp",
|
|
101
|
+
"to": "5511999999999",
|
|
102
|
+
"type": "interactive",
|
|
103
|
+
"interactive": {
|
|
104
|
+
"type": "button",
|
|
105
|
+
"body": {
|
|
106
|
+
"text": "Escolha uma opção:"
|
|
107
|
+
},
|
|
108
|
+
"action": {
|
|
109
|
+
"buttons": [
|
|
110
|
+
{
|
|
111
|
+
"type": "reply",
|
|
112
|
+
"reply": {
|
|
113
|
+
"id": "opt_1",
|
|
114
|
+
"title": "Opção 1"
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
"type": "reply",
|
|
119
|
+
"reply": {
|
|
120
|
+
"id": "opt_2",
|
|
121
|
+
"title": "Opção 2"
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
]
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Response (Sucesso):**
|
|
131
|
+
```json
|
|
132
|
+
{
|
|
133
|
+
"messaging_product": "whatsapp",
|
|
134
|
+
"contacts": [
|
|
135
|
+
{
|
|
136
|
+
"input": "5511999999999",
|
|
137
|
+
"wa_id": "5511999999999"
|
|
138
|
+
}
|
|
139
|
+
],
|
|
140
|
+
"messages": [
|
|
141
|
+
{
|
|
142
|
+
"id": "wamid.HBgNNTUxMTk4ODg4ODg4OBUCABIYIDNBNjdGMzQ5QzE5MEFCMTY1MjU0"
|
|
143
|
+
}
|
|
144
|
+
]
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Códigos de Erro Comuns:**
|
|
149
|
+
|
|
150
|
+
| Código | Erro | Significado |
|
|
151
|
+
|--------|------|-------------|
|
|
152
|
+
| 400 | `invalid_parameter` | Parâmetro inválido no payload |
|
|
153
|
+
| 401 | `invalid_token` | Access token inválido ou expirado |
|
|
154
|
+
| 403 | `rate_limit_issues` | Limite de mensagens atingido |
|
|
155
|
+
| 404 | `resource_not_found` | Phone number ID não encontrado |
|
|
156
|
+
| 429 | `too_many_requests` | Rate limit excedido |
|
|
157
|
+
| 500 | `internal_error` | Erro interno da Meta |
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
#### 2. Upload de Mídia
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
POST /{phone-number-id}/media
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Headers:**
|
|
168
|
+
```http
|
|
169
|
+
Authorization: Bearer {access_token}
|
|
170
|
+
Content-Type: multipart/form-data
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**Body:**
|
|
174
|
+
```
|
|
175
|
+
--boundary
|
|
176
|
+
Content-Disposition: form-data; name="file"; filename="image.jpg"
|
|
177
|
+
Content-Type: image/jpeg
|
|
178
|
+
|
|
179
|
+
[binary data]
|
|
180
|
+
--boundary
|
|
181
|
+
Content-Disposition: form-data; name="messaging_product"
|
|
182
|
+
|
|
183
|
+
whatsapp
|
|
184
|
+
--boundary--
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
**Response:**
|
|
188
|
+
```json
|
|
189
|
+
{
|
|
190
|
+
"id": "1234567890"
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
#### 3. Download de Mídia (Obter URL)
|
|
197
|
+
|
|
198
|
+
```
|
|
199
|
+
GET /{media-id}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
**Headers:**
|
|
203
|
+
```http
|
|
204
|
+
Authorization: Bearer {access_token}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**Response:**
|
|
208
|
+
```json
|
|
209
|
+
{
|
|
210
|
+
"url": "https://mmg.whatsapp.net/d/f/AqFmkQ...",
|
|
211
|
+
"mime_type": "image/jpeg",
|
|
212
|
+
"sha256": "e7b6c8f9a2d3...",
|
|
213
|
+
"file_size": 52431,
|
|
214
|
+
"id": "1234567890"
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
⚠️ **Nota:** A URL tem validade de ~5 minutos.
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
#### 4. Download de Mídia (Binário)
|
|
223
|
+
|
|
224
|
+
```
|
|
225
|
+
GET {media-url}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
**Headers:**
|
|
229
|
+
```http
|
|
230
|
+
Authorization: Bearer {access_token}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
**Response:** Binary data (arquivo)
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
#### 5. Informações da Conta
|
|
238
|
+
|
|
239
|
+
```
|
|
240
|
+
GET /{phone-number-id}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
**Headers:**
|
|
244
|
+
```http
|
|
245
|
+
Authorization: Bearer {access_token}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
**Response:**
|
|
249
|
+
```json
|
|
250
|
+
{
|
|
251
|
+
"verified_name": "Minha Empresa LTDA",
|
|
252
|
+
"display_phone_number": "+55 11 99999-9999",
|
|
253
|
+
"quality_rating": "GREEN",
|
|
254
|
+
"id": "123456789012345",
|
|
255
|
+
"name_status": "APPROVED",
|
|
256
|
+
"status": "CONNECTED"
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
#### 6. Exchange Token (Refresh)
|
|
263
|
+
|
|
264
|
+
```
|
|
265
|
+
POST /oauth/access_token
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
**Query Parameters:**
|
|
269
|
+
```
|
|
270
|
+
grant_type=fb_exchange_token
|
|
271
|
+
&client_id={app-id}
|
|
272
|
+
&client_secret={app-secret}
|
|
273
|
+
&fb_exchange_token={short-lived-token}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
**Response:**
|
|
277
|
+
```json
|
|
278
|
+
{
|
|
279
|
+
"access_token": "EAAxxxxxxxxxxxx",
|
|
280
|
+
"token_type": "bearer",
|
|
281
|
+
"expires_in": 5184000
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
### Rate Limits
|
|
288
|
+
|
|
289
|
+
| Tier | Limite Diário | Requisitos |
|
|
290
|
+
|------|---------------|------------|
|
|
291
|
+
| **Tier 1** | 1.000 mensagens/dia | Número recém-registrado |
|
|
292
|
+
| **Tier 2** | 10.000 mensagens/dia | Envio de 1.000 msgs em 7 dias |
|
|
293
|
+
| **Tier 3** | 100.000 mensagens/dia | Envio de 10.000 msgs em 7 dias |
|
|
294
|
+
| **Unlimited** | Sem limite | Aprovação manual Meta (enterprise) |
|
|
295
|
+
|
|
296
|
+
**Recomendação:** Implementar throttling na aplicação para evitar atingir limites.
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
### Webhooks da Meta
|
|
301
|
+
|
|
302
|
+
A Meta envia webhooks para os eventos:
|
|
303
|
+
|
|
304
|
+
**Eventos Suportados:**
|
|
305
|
+
- `messages` - Mensagem recebida
|
|
306
|
+
- `message_status` - Status de entrega (sent, delivered, read, failed)
|
|
307
|
+
- `account_update` - Atualização na conta (quality rating, limites)
|
|
308
|
+
- `phone_number_quality_update` - Mudança no quality rating
|
|
309
|
+
|
|
310
|
+
**Payload de Webhook (Mensagem Recebida):**
|
|
311
|
+
|
|
312
|
+
<details>
|
|
313
|
+
<summary>Ver payload completo (clique para expandir)</summary>
|
|
314
|
+
|
|
315
|
+
```json
|
|
316
|
+
{
|
|
317
|
+
"object": "whatsapp_business_account",
|
|
318
|
+
"entry": [
|
|
319
|
+
{
|
|
320
|
+
"id": "WHATSAPP_BUSINESS_ACCOUNT_ID",
|
|
321
|
+
"changes": [
|
|
322
|
+
{
|
|
323
|
+
"value": {
|
|
324
|
+
"messaging_product": "whatsapp",
|
|
325
|
+
"metadata": {
|
|
326
|
+
"display_phone_number": "5511999999999",
|
|
327
|
+
"phone_number_id": "123456789012345"
|
|
328
|
+
},
|
|
329
|
+
"contacts": [
|
|
330
|
+
{
|
|
331
|
+
"profile": {
|
|
332
|
+
"name": "João Silva"
|
|
333
|
+
},
|
|
334
|
+
"wa_id": "5511888888888"
|
|
335
|
+
}
|
|
336
|
+
],
|
|
337
|
+
"messages": [
|
|
338
|
+
{
|
|
339
|
+
"from": "5511888888888",
|
|
340
|
+
"id": "wamid.HBgNNTUxMTk4ODg4ODg4OBUCABIYIDNBNjdGMzQ5QzE5MEFCMTY1MjU0",
|
|
341
|
+
"timestamp": "1709740800",
|
|
342
|
+
"type": "text",
|
|
343
|
+
"text": {
|
|
344
|
+
"body": "Olá, preciso de ajuda!"
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
]
|
|
348
|
+
},
|
|
349
|
+
"field": "messages"
|
|
350
|
+
}
|
|
351
|
+
]
|
|
352
|
+
}
|
|
353
|
+
]
|
|
354
|
+
}
|
|
355
|
+
```
|
|
356
|
+
</details>
|
|
357
|
+
|
|
358
|
+
**Webhook Verification (GET Request):**
|
|
359
|
+
|
|
360
|
+
```http
|
|
361
|
+
GET /webhooks?hub.mode=subscribe&hub.challenge=CHALLENGE_TOKEN&hub.verify_token=YOUR_VERIFY_TOKEN
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
**Resposta esperada:**
|
|
365
|
+
```
|
|
366
|
+
CHALLENGE_TOKEN
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
## 🔧 Naty Meta API (Backend Proprietário)
|
|
372
|
+
|
|
373
|
+
### Informações Gerais
|
|
374
|
+
|
|
375
|
+
| Propriedade | Valor |
|
|
376
|
+
|-------------|-------|
|
|
377
|
+
| **Base URL** | `process.env.API_META` (ex: `https://api.meta.naty.app/v1`) |
|
|
378
|
+
| **Protocolo** | HTTPS (REST API) |
|
|
379
|
+
| **Autenticação** | Header `x-access-token: {appToken}` |
|
|
380
|
+
| **Formato** | JSON |
|
|
381
|
+
|
|
382
|
+
### Endpoints Utilizados
|
|
383
|
+
|
|
384
|
+
#### 1. Validação de App Token
|
|
385
|
+
|
|
386
|
+
```
|
|
387
|
+
POST /validate/validateAppToken
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
**Headers:**
|
|
391
|
+
```http
|
|
392
|
+
Content-Type: application/json
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
**Body:**
|
|
396
|
+
```json
|
|
397
|
+
{
|
|
398
|
+
"appToken": "naty_app_token_xyz"
|
|
399
|
+
}
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
**Response (Sucesso):**
|
|
403
|
+
```json
|
|
404
|
+
{
|
|
405
|
+
"valid": true,
|
|
406
|
+
"data": {
|
|
407
|
+
"appId": "app_123",
|
|
408
|
+
"companyId": "company_abc"
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
**Response (Falha):**
|
|
414
|
+
```json
|
|
415
|
+
{
|
|
416
|
+
"valid": false,
|
|
417
|
+
"error": "Invalid token"
|
|
418
|
+
}
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
---
|
|
422
|
+
|
|
423
|
+
#### 2. CRUD de Conexões
|
|
424
|
+
|
|
425
|
+
**Criar Conexão:**
|
|
426
|
+
```
|
|
427
|
+
POST /connection
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
**Headers:**
|
|
431
|
+
```http
|
|
432
|
+
x-access-token: {appToken}
|
|
433
|
+
Content-Type: application/json
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
**Body:**
|
|
437
|
+
```json
|
|
438
|
+
{
|
|
439
|
+
"companyId": "abc123",
|
|
440
|
+
"phoneNumberId": "123456789012345",
|
|
441
|
+
"accessToken": "EAAxxxxxxxxxxxx",
|
|
442
|
+
"appId": "123456789",
|
|
443
|
+
"appSecret": "abcdef1234567890",
|
|
444
|
+
"accountId": "987654321",
|
|
445
|
+
"businessId": "456789123",
|
|
446
|
+
"phoneNumber": "+5511999999999"
|
|
447
|
+
}
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
**Response:**
|
|
451
|
+
```json
|
|
452
|
+
{
|
|
453
|
+
"id": "conn_xyz789",
|
|
454
|
+
"companyId": "abc123",
|
|
455
|
+
"phoneNumberId": "123456789012345",
|
|
456
|
+
"isActive": true,
|
|
457
|
+
"createdAt": "2026-02-06T10:00:00.000Z"
|
|
458
|
+
}
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
---
|
|
462
|
+
|
|
463
|
+
**Buscar Conexão:**
|
|
464
|
+
```
|
|
465
|
+
GET /connection?companyId={companyId}&phoneNumberId={phoneNumberId}
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
**Headers:**
|
|
469
|
+
```http
|
|
470
|
+
x-access-token: {appToken}
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
**Response:**
|
|
474
|
+
```json
|
|
475
|
+
{
|
|
476
|
+
"_id": "65e1234567890abcdef",
|
|
477
|
+
"id": "conn_xyz789",
|
|
478
|
+
"companyId": "abc123",
|
|
479
|
+
"phoneNumberId": "123456789012345",
|
|
480
|
+
"accessToken": "EAAxxxxxxxxxxxx",
|
|
481
|
+
"appId": "123456789",
|
|
482
|
+
"appSecret": "abcdef1234567890",
|
|
483
|
+
"isActive": true,
|
|
484
|
+
"createdAt": "2026-02-06T10:00:00.000Z",
|
|
485
|
+
"updatedAt": "2026-02-06T10:30:00.000Z"
|
|
486
|
+
}
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
---
|
|
490
|
+
|
|
491
|
+
**Atualizar Conexão:**
|
|
492
|
+
```
|
|
493
|
+
PUT /connection/{connectionId}
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
**Headers:**
|
|
497
|
+
```http
|
|
498
|
+
x-access-token: {appToken}
|
|
499
|
+
Content-Type: application/json
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
**Body:**
|
|
503
|
+
```json
|
|
504
|
+
{
|
|
505
|
+
"accessToken": "EAAyyyyyyyyyy"
|
|
506
|
+
}
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
---
|
|
510
|
+
|
|
511
|
+
**Deletar Conexão (Soft Delete):**
|
|
512
|
+
```
|
|
513
|
+
DELETE /connection/{connectionId}
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
**Response:**
|
|
517
|
+
```json
|
|
518
|
+
{
|
|
519
|
+
"success": true,
|
|
520
|
+
"message": "Connection marked as inactive"
|
|
521
|
+
}
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
---
|
|
525
|
+
|
|
526
|
+
#### 3. CRUD de Webhooks
|
|
527
|
+
|
|
528
|
+
**Criar Webhook:**
|
|
529
|
+
```
|
|
530
|
+
POST /webhook
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
**Body:**
|
|
534
|
+
```json
|
|
535
|
+
{
|
|
536
|
+
"companyId": "abc123",
|
|
537
|
+
"connection": {
|
|
538
|
+
"url": "https://myapp.com/webhooks/connections",
|
|
539
|
+
"active": true
|
|
540
|
+
},
|
|
541
|
+
"messages": {
|
|
542
|
+
"url": "https://myapp.com/webhooks/messages",
|
|
543
|
+
"active": true
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
---
|
|
549
|
+
|
|
550
|
+
**Buscar Webhook:**
|
|
551
|
+
```
|
|
552
|
+
GET /webhook?companyId={companyId}
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
**Response:**
|
|
556
|
+
```json
|
|
557
|
+
{
|
|
558
|
+
"id": "wh_abc123",
|
|
559
|
+
"companyId": "abc123",
|
|
560
|
+
"connection": {
|
|
561
|
+
"url": "https://myapp.com/webhooks/connections",
|
|
562
|
+
"active": true
|
|
563
|
+
},
|
|
564
|
+
"messages": {
|
|
565
|
+
"url": "https://myapp.com/webhooks/messages",
|
|
566
|
+
"active": true
|
|
567
|
+
},
|
|
568
|
+
"isActive": true
|
|
569
|
+
}
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
---
|
|
573
|
+
|
|
574
|
+
#### 4. CRUD de Mensagens
|
|
575
|
+
|
|
576
|
+
**Salvar Mensagem:**
|
|
577
|
+
```
|
|
578
|
+
POST /message
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
**Body:**
|
|
582
|
+
```json
|
|
583
|
+
{
|
|
584
|
+
"companyId": "abc123",
|
|
585
|
+
"phoneNumberId": "123456789012345",
|
|
586
|
+
"sender": "5511888888888",
|
|
587
|
+
"message": {
|
|
588
|
+
"id": "wamid.xxx",
|
|
589
|
+
"type": "text",
|
|
590
|
+
"timestamp": "1709740800",
|
|
591
|
+
"text": { "body": "Olá!" }
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
---
|
|
597
|
+
|
|
598
|
+
**Buscar Mensagens:**
|
|
599
|
+
```
|
|
600
|
+
GET /messages?companyId={companyId}&phoneNumberId={phoneNumberId}&limit=50
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
**Response:**
|
|
604
|
+
```json
|
|
605
|
+
{
|
|
606
|
+
"messages": [
|
|
607
|
+
{
|
|
608
|
+
"id": "msg_123",
|
|
609
|
+
"companyId": "abc123",
|
|
610
|
+
"sender": "5511888888888",
|
|
611
|
+
"message": { "type": "text", "text": { "body": "Olá!" } },
|
|
612
|
+
"createdAt": "2026-02-06T10:00:00.000Z"
|
|
613
|
+
}
|
|
614
|
+
],
|
|
615
|
+
"total": 150,
|
|
616
|
+
"page": 1
|
|
617
|
+
}
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
---
|
|
621
|
+
|
|
622
|
+
#### 5. Logs
|
|
623
|
+
|
|
624
|
+
**Criar Log:**
|
|
625
|
+
```
|
|
626
|
+
POST /logs
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
**Body:**
|
|
630
|
+
```json
|
|
631
|
+
{
|
|
632
|
+
"type": "Access",
|
|
633
|
+
"owner": "app_123",
|
|
634
|
+
"url": "/messages",
|
|
635
|
+
"method": "POST",
|
|
636
|
+
"remoteIP": "192.168.1.100",
|
|
637
|
+
"objectData": { "messageId": "wamid.xxx" }
|
|
638
|
+
}
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
---
|
|
642
|
+
|
|
643
|
+
**Criar Log de Erro:**
|
|
644
|
+
```
|
|
645
|
+
POST /logs/error
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
**Body:**
|
|
649
|
+
```json
|
|
650
|
+
{
|
|
651
|
+
"connectionId": "conn_xyz789",
|
|
652
|
+
"errorFrom": "send_message",
|
|
653
|
+
"statusCode": 401,
|
|
654
|
+
"error": {
|
|
655
|
+
"message": "Invalid token",
|
|
656
|
+
"stack": "Error: Invalid token\n at ..."
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
---
|
|
662
|
+
|
|
663
|
+
### Códigos de Status
|
|
664
|
+
|
|
665
|
+
| Código | Significado |
|
|
666
|
+
|--------|-------------|
|
|
667
|
+
| 200 | Sucesso |
|
|
668
|
+
| 201 | Recurso criado |
|
|
669
|
+
| 400 | Bad request (payload inválido) |
|
|
670
|
+
| 401 | Unauthorized (appToken inválido) |
|
|
671
|
+
| 404 | Recurso não encontrado |
|
|
672
|
+
| 500 | Erro interno do servidor |
|
|
673
|
+
|
|
674
|
+
---
|
|
675
|
+
|
|
676
|
+
## ⚙️ Express.js (Integração Opcional)
|
|
677
|
+
|
|
678
|
+
### Propósito
|
|
679
|
+
|
|
680
|
+
O SDK pode integrar-se a uma aplicação Express existente para:
|
|
681
|
+
- Registrar rotas de webhook automaticamente
|
|
682
|
+
- Receber webhooks da Meta
|
|
683
|
+
- Processar eventos e emitir para a aplicação
|
|
684
|
+
|
|
685
|
+
### Integração
|
|
686
|
+
|
|
687
|
+
**Arquivo:** [`src/routes/webhooks/index.ts`](../src/routes/webhooks/index.ts)
|
|
688
|
+
|
|
689
|
+
```typescript
|
|
690
|
+
import express from 'express';
|
|
691
|
+
import { NatyMeta } from '@natyapp/meta';
|
|
692
|
+
|
|
693
|
+
const app = express();
|
|
694
|
+
app.use(express.json());
|
|
695
|
+
|
|
696
|
+
// Integrar SDK ao Express
|
|
697
|
+
const sdk = new NatyMeta();
|
|
698
|
+
await sdk.connect({
|
|
699
|
+
appToken: 'seu-app-token',
|
|
700
|
+
app: app, // Passa instância Express
|
|
701
|
+
pathname: '/webhooks' // Base path
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
// Rotas registradas automaticamente:
|
|
705
|
+
// - GET /webhooks/messages
|
|
706
|
+
// - POST /webhooks/messages
|
|
707
|
+
// - GET /webhooks/connections
|
|
708
|
+
// - POST /webhooks/connections
|
|
709
|
+
|
|
710
|
+
app.listen(3000, () => {
|
|
711
|
+
console.log('Servidor rodando na porta 3000');
|
|
712
|
+
});
|
|
713
|
+
```
|
|
714
|
+
|
|
715
|
+
### Rotas Expostas
|
|
716
|
+
|
|
717
|
+
#### 1. POST /webhooks/messages
|
|
718
|
+
|
|
719
|
+
**Recebe:** Mensagens enviadas por clientes.
|
|
720
|
+
|
|
721
|
+
**Handler:** [`src/routes/webhooks/methods/messages.ts`](../src/routes/webhooks/methods/messages.ts)
|
|
722
|
+
|
|
723
|
+
**Comportamento:**
|
|
724
|
+
1. Valida payload da Meta
|
|
725
|
+
2. Cria `WhatsappMessage` entity
|
|
726
|
+
3. Instancia `WhatsappResponse` para resposta
|
|
727
|
+
4. Emite evento `'message'`
|
|
728
|
+
5. Retorna 200 OK
|
|
729
|
+
|
|
730
|
+
---
|
|
731
|
+
|
|
732
|
+
#### 2. GET /webhooks/messages
|
|
733
|
+
|
|
734
|
+
**Recebe:** Verificação inicial da Meta.
|
|
735
|
+
|
|
736
|
+
**Query Params:**
|
|
737
|
+
- `hub.mode=subscribe`
|
|
738
|
+
- `hub.challenge={token}`
|
|
739
|
+
- `hub.verify_token={verify_token}`
|
|
740
|
+
|
|
741
|
+
**Comportamento:**
|
|
742
|
+
1. Valida `hub.verify_token`
|
|
743
|
+
2. Retorna `hub.challenge`
|
|
744
|
+
|
|
745
|
+
---
|
|
746
|
+
|
|
747
|
+
#### 3. POST /webhooks/connections
|
|
748
|
+
|
|
749
|
+
**Recebe:** Verificação de conexão WhatsApp.
|
|
750
|
+
|
|
751
|
+
**Comportamento:**
|
|
752
|
+
1. Extrai `connectionId` do payload
|
|
753
|
+
2. Busca conexão na Naty API
|
|
754
|
+
3. Obtém info da conta na Meta API
|
|
755
|
+
4. Emite evento `'connection'`
|
|
756
|
+
5. Retorna 200 OK
|
|
757
|
+
|
|
758
|
+
---
|
|
759
|
+
|
|
760
|
+
#### 4. GET /webhooks/connections
|
|
761
|
+
|
|
762
|
+
**Recebe:** Verificação inicial (similar a messages).
|
|
763
|
+
|
|
764
|
+
---
|
|
765
|
+
|
|
766
|
+
## 📊 Diagramas de Integração
|
|
767
|
+
|
|
768
|
+
### Diagrama de Sequência - Envio de Mensagem
|
|
769
|
+
|
|
770
|
+
```mermaid
|
|
771
|
+
sequenceDiagram
|
|
772
|
+
participant App as Aplicação
|
|
773
|
+
participant SDK as SDK Naty
|
|
774
|
+
participant Naty as Naty API
|
|
775
|
+
participant Meta as Meta Graph API
|
|
776
|
+
participant WA as WhatsApp
|
|
777
|
+
participant Client as Cliente Final
|
|
778
|
+
|
|
779
|
+
App->>SDK: send_text({ to, message })
|
|
780
|
+
|
|
781
|
+
SDK->>Naty: GET /connection<br/>(buscar credenciais)
|
|
782
|
+
Naty-->>SDK: { accessToken, appId, appSecret }
|
|
783
|
+
|
|
784
|
+
SDK->>Meta: POST /oauth/access_token<br/>(refresh token)
|
|
785
|
+
Meta-->>SDK: { access_token: newToken }
|
|
786
|
+
|
|
787
|
+
SDK->>Naty: PUT /connection<br/>(salvar novo token)
|
|
788
|
+
|
|
789
|
+
SDK->>Meta: POST /{phoneNumberId}/messages<br/>{ to, type: 'text', text }
|
|
790
|
+
Meta->>WA: Processa mensagem
|
|
791
|
+
WA->>Client: Entrega via WhatsApp
|
|
792
|
+
Meta-->>SDK: { messages: [{ id }] }
|
|
793
|
+
|
|
794
|
+
SDK->>Naty: POST /logs<br/>(registrar operação)
|
|
795
|
+
|
|
796
|
+
SDK-->>App: Either.right({ id })
|
|
797
|
+
```
|
|
798
|
+
|
|
799
|
+
---
|
|
800
|
+
|
|
801
|
+
### Diagrama de Sequência - Recebimento de Mensagem
|
|
802
|
+
|
|
803
|
+
```mermaid
|
|
804
|
+
sequenceDiagram
|
|
805
|
+
participant Client as Cliente Final
|
|
806
|
+
participant WA as WhatsApp
|
|
807
|
+
participant Meta as Meta Webhook
|
|
808
|
+
participant Express as Express/Webhook
|
|
809
|
+
participant SDK as SDK Events
|
|
810
|
+
participant App as Aplicação
|
|
811
|
+
|
|
812
|
+
Client->>WA: Envia mensagem
|
|
813
|
+
WA->>Meta: Processa
|
|
814
|
+
|
|
815
|
+
Meta->>Express: POST /webhooks/messages<br/>{ entry: [...] }
|
|
816
|
+
|
|
817
|
+
Express->>Express: Parse payload
|
|
818
|
+
Express->>SDK: emit('message', data)
|
|
819
|
+
|
|
820
|
+
SDK->>App: Listener recebe evento
|
|
821
|
+
|
|
822
|
+
App->>App: Processa lógica<br/>(chatbot, IA, etc.)
|
|
823
|
+
|
|
824
|
+
App->>SDK: whatsappResponse.send_text()
|
|
825
|
+
SDK->>Meta: POST /messages
|
|
826
|
+
Meta->>WA: Envia resposta
|
|
827
|
+
WA->>Client: Recebe resposta
|
|
828
|
+
|
|
829
|
+
Express-->>Meta: 200 OK
|
|
830
|
+
```
|
|
831
|
+
|
|
832
|
+
---
|
|
833
|
+
|
|
834
|
+
## ⏱ Rate Limits e Throttling
|
|
835
|
+
|
|
836
|
+
### Limites da Meta
|
|
837
|
+
|
|
838
|
+
| Recurso | Limite |
|
|
839
|
+
|---------|--------|
|
|
840
|
+
| **Mensagens** | Varia por Tier (1k - 100k/dia) |
|
|
841
|
+
| **API Calls** | ~200 requisições/hora por token |
|
|
842
|
+
| **Upload Mídia** | 100 MB/dia por número |
|
|
843
|
+
| **Webhook Retries** | 3 tentativas com exponential backoff |
|
|
844
|
+
|
|
845
|
+
### Estratégias de Throttling
|
|
846
|
+
|
|
847
|
+
**1. Queue de Mensagens (Recomendado)**
|
|
848
|
+
|
|
849
|
+
```typescript
|
|
850
|
+
import PQueue from 'p-queue';
|
|
851
|
+
|
|
852
|
+
// Fila com limite de 10 mensagens/segundo
|
|
853
|
+
const queue = new PQueue({
|
|
854
|
+
interval: 1000,
|
|
855
|
+
intervalCap: 10
|
|
856
|
+
});
|
|
857
|
+
|
|
858
|
+
// Adicionar envios à fila
|
|
859
|
+
const sendthrottled = (to: string, message: string) => {
|
|
860
|
+
return queue.add(() => whatsapp.send_text({ to, message }));
|
|
861
|
+
};
|
|
862
|
+
|
|
863
|
+
// Uso
|
|
864
|
+
await sendThrottled('5511999999999', 'Mensagem 1');
|
|
865
|
+
await sendThrottled('5511888888888', 'Mensagem 2');
|
|
866
|
+
// ...
|
|
867
|
+
```
|
|
868
|
+
|
|
869
|
+
**2. Retry com Exponential Backoff**
|
|
870
|
+
|
|
871
|
+
```typescript
|
|
872
|
+
async function sendWithRetry(to: string, message: string, maxRetries = 3) {
|
|
873
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
874
|
+
const result = await whatsapp.send_text({ to, message });
|
|
875
|
+
|
|
876
|
+
if (result.isRight()) {
|
|
877
|
+
return result;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
// Se erro 429 (rate limit), aguardar antes de retry
|
|
881
|
+
if (result.value.statusCode === 429) {
|
|
882
|
+
const waitTime = Math.pow(2, i) * 1000; // 1s, 2s, 4s
|
|
883
|
+
console.log(`Rate limit hit. Waiting ${waitTime}ms...`);
|
|
884
|
+
await new Promise(resolve => setTimeout(resolve, waitTime));
|
|
885
|
+
} else {
|
|
886
|
+
return result; // Outro erro, não retry
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
return left(new Error('Max retries exceeded'));
|
|
891
|
+
}
|
|
892
|
+
```
|
|
893
|
+
|
|
894
|
+
---
|
|
895
|
+
|
|
896
|
+
## 🔒 Segurança e Autenticação
|
|
897
|
+
|
|
898
|
+
### Hierarquia de Credenciais
|
|
899
|
+
|
|
900
|
+
```mermaid
|
|
901
|
+
graph TD
|
|
902
|
+
A[appToken<br/>Naty Platform] --> B[companyId<br/>Tenant]
|
|
903
|
+
B --> C1[Connection 1<br/>accessToken Meta]
|
|
904
|
+
B --> C2[Connection 2<br/>accessToken Meta]
|
|
905
|
+
|
|
906
|
+
C1 --> D1[phoneNumberId 1<br/>WhatsApp Number]
|
|
907
|
+
C2 --> D2[phoneNumberId 2<br/>WhatsApp Number]
|
|
908
|
+
|
|
909
|
+
style A fill:#E3F2FD
|
|
910
|
+
style B fill:#BBDEFB
|
|
911
|
+
style C1 fill:#90CAF9
|
|
912
|
+
style C2 fill:#90CAF9
|
|
913
|
+
style D1 fill:#64B5F6,color:#fff
|
|
914
|
+
style D2 fill:#64B5F6,color:#fff
|
|
915
|
+
```
|
|
916
|
+
|
|
917
|
+
### Boas Práticas
|
|
918
|
+
|
|
919
|
+
1. **Nunca commitar tokens:**
|
|
920
|
+
```bash
|
|
921
|
+
# .env
|
|
922
|
+
NATY_APP_TOKEN=seu_token_aqui
|
|
923
|
+
API_META=https://api.meta.naty.app/v1
|
|
924
|
+
```
|
|
925
|
+
|
|
926
|
+
2. **Rotacionar tokens periodicamente:**
|
|
927
|
+
- Tokens Meta expiram em 60 dias
|
|
928
|
+
- Refresh automático implementado pelo SDK
|
|
929
|
+
|
|
930
|
+
3. **Validar webhooks da Meta:**
|
|
931
|
+
```typescript
|
|
932
|
+
// Verificar se requisição vem da Meta
|
|
933
|
+
const validateWebhook = (req: Request) => {
|
|
934
|
+
const signature = req.headers['x-hub-signature-256'];
|
|
935
|
+
// Validar HMAC SHA256 com app secret
|
|
936
|
+
// (Implementado internamente no SDK)
|
|
937
|
+
};
|
|
938
|
+
```
|
|
939
|
+
|
|
940
|
+
4. **HTTPS obrigatório:**
|
|
941
|
+
- Webhooks devem usar HTTPS
|
|
942
|
+
- Certificado SSL válido
|
|
943
|
+
|
|
944
|
+
5. **Logging de acessos:**
|
|
945
|
+
- Todas operações são logadas automaticamente
|
|
946
|
+
- Logs contêm IP, timestamp, ação
|
|
947
|
+
|
|
948
|
+
---
|
|
949
|
+
|
|
950
|
+
## 🔗 Próximos Passos
|
|
951
|
+
|
|
952
|
+
Com as integrações compreendidas, explore:
|
|
953
|
+
|
|
954
|
+
- **[Entidades](06-entidades.md)** - Estrutura de dados detalhada
|
|
955
|
+
- **[Guia Prático](07-guia-pratico.md)** - Exemplos práticos de integração
|
|
956
|
+
- **[Troubleshooting](08-troubleshooting.md)** - Resolver problemas de integração
|
|
957
|
+
|
|
958
|
+
---
|
|
959
|
+
|
|
960
|
+
[⬅️ Anterior: Fluxos Funcionais](04-fluxos-funcionais.md) | [⬆️ Voltar ao Índice](README.md) | [➡️ Próximo: Entidades](06-entidades.md)
|