chore: remove documentation files from PR

Documentation files removed as requested - implementation only.

https://claude.ai/code/session_01SoNUGBEWbJwWWws3F2fuzh
This commit is contained in:
Claude
2026-02-09 21:16:53 +00:00
parent ebc6557f63
commit a039291407
2 changed files with 0 additions and 923 deletions
-582
View File
@@ -1,582 +0,0 @@
# WhatsApp Device Migration - LID/PN (Identificadores)
## 📋 Índice
1. [O que é LID/PN](#o-que-é-lidpn)
2. [Por que existe](#por-que-existe)
3. [Como funciona](#como-funciona)
4. [Quando usa LID vs PN](#quando-usa-lid-vs-pn)
5. [Fluxo de Migração](#fluxo-de-migração)
6. [Como aparece nos logs](#como-aparece-nos-logs)
7. [Implementação técnica](#implementação-técnica)
---
## O que é LID/PN
### PN (Phone Number)
- **Formato**: `12345@s.whatsapp.net` ou `12345:5@s.whatsapp.net`
- **Significado**: Identificador baseado no número de telefone
- **Exemplo**: Se seu número é +55 11 98765-4321, o PN seria `5511987654321@s.whatsapp.net`
- **Com device**: `5511987654321:5@s.whatsapp.net` (device 5 = WhatsApp Web/Desktop)
### LID (Long-lived Identifier)
- **Formato**: `aaaaa@lid` ou `aaaaa:5@lid`
- **Significado**: Identificador de longa duração desvinculado do número de telefone
- **Exemplo**: `a1b2c3d4e5@lid`
- **Com device**: `a1b2c3d4e5:5@lid` (device 5 = WhatsApp Web/Desktop)
### Device Numbers
- **0**: Dispositivo principal (celular)
- **1-98**: Dispositivos companheiros (Web, Desktop, etc)
- **99**: Dispositivo "hosted" (companion device especial)
---
## Por que existe
### Problema Original (Apenas PN)
Quando o WhatsApp usava apenas números de telefone como identificadores:
- **Troca de número**: Usuário perde todo histórico de conversas criptografadas
- **Portabilidade**: Dificulta migração entre operadoras
- **Privacidade**: Número de telefone exposto em todos os metadados
- **Sessões Signal**: Precisavam ser recriadas ao trocar número
### Solução (LID)
Com LID, o WhatsApp pode:
- **Manter identidade**: Mesmo trocando de número, mantém o mesmo LID
- **Privacidade**: Não expõe número de telefone em todas comunicações
- **Migração suave**: Transição transparente PN → LID
- **Sessões preservadas**: Criptografia mantida mesmo com mudança de número
---
## Como funciona
### Arquitetura de Armazenamento
```
Database (SignalDataTypeMap['lid-mapping']):
├─ Forward Mapping: pn:{phoneUser} → {lidUser}
│ Exemplo: "pn:5511987654321" → "a1b2c3d4e5"
├─ Reverse Mapping: {lidUser}_reverse → {phoneUser}
│ Exemplo: "a1b2c3d4e5_reverse" → "5511987654321"
└─ Device List: device-list:{userId} → [devices...]
Exemplo: "device-list:5511987654321" → ["0", "1", "5", "99"]
```
### LRU Cache (In-Memory)
```javascript
Cache Configuration:
- TTL: 3 dias (259.200.000 ms)
- Max Size: 50.000 entradas
- Auto Purge: Ativado
- Update Age on Get: Sim (renova TTL ao acessar)
```
### Estrutura de Dados
```typescript
type LIDMapping = {
pn: string // "12345@s.whatsapp.net"
lid: string // "aaaaa@lid"
}
// Armazenamento
{
'lid-mapping': {
'pn:12345': 'aaaaa', // Forward
'aaaaa_reverse': '12345', // Reverse
},
'device-list': {
'12345': ['0', '1', '5', '99'] // Devices conhecidos
}
}
```
---
## Quando usa LID vs PN
### Usa PN (Phone Number)
1. **Primeiro contato**: Quando nunca conversou com o usuário
2. **Antes da migração**: Sistema ainda não recebeu o LID do servidor
3. **Fallback**: Se lookup do LID falhar
4. **Compatibilidade**: Dispositivos antigos que não suportam LID
### Usa LID (Long-lived ID)
1. **Após migração bem-sucedida**: Quando `migrateSession()` completou
2. **LID disponível**: Quando existe mapeamento em cache/database
3. **Usuário interagiu**: Depois que o usuário enviou/recebeu mensagem
4. **Prioridade**: LID sempre tem prioridade sobre PN quando disponível
### Decisão no Envio de Mensagem
```typescript
// Arquivo: messages-send.ts:346-370
const isLidUser = requestedLidUsers.has(user) // Usuário tem LID?
const finalJid = isLidUser
? jidEncode(user, item.server, item.device) // USA LID
: jidEncode(item.user, item.server, item.device) // USA PN
// Exemplo:
// isLidUser = true: "a1b2c3d4e5:5@lid"
// isLidUser = false: "5511987654321:5@s.whatsapp.net"
```
---
## Fluxo de Migração
### 1. Login/Conexão (Trigger Inicial)
```
socket.ts:1462-1487
1. Cliente conecta ao WhatsApp
└─> Envia credenciais
2. Servidor responde com SUCCESS
└─> <success lid="a1b2c3d4e5" /> ← SERVIDOR ENVIA O LID
3. Sistema armazena mapeamento próprio
└─> await lidMapping.storeLIDPNMappings([{
lid: "a1b2c3d4e5@lid",
pn: "5511987654321@s.whatsapp.net"
}])
4. Cria device list
└─> device-list[5511987654321] = ["0", "1", "5", "99"]
5. TRIGGER MIGRAÇÃO EM MASSA
└─> await signalRepository.migrateSession(
"5511987654321@s.whatsapp.net", // FROM (PN)
"a1b2c3d4e5@lid" // TO (LID)
)
```
### 2. Migração de Sessão Signal
```
libsignal.ts:484-635
migrateSession(fromPN, toLID):
├─ Validação
│ └─ Apenas suporta PN → LID (não LID → PN)
├─ Busca devices do device-list
│ └─ ["0", "1", "5", "99"]
├─ Filtra devices com sessão Signal existente
│ └─ [0, 5] (apenas esses têm sessão ativa)
├─ Cria operações de migração para cada device:
│ ├─ Device 0:
│ │ FROM: 5511987654321:0@s.whatsapp.net
│ │ TO: a1b2c3d4e5:0@lid
│ │
│ └─ Device 5:
│ FROM: 5511987654321:5@s.whatsapp.net
│ TO: a1b2c3d4e5:5@lid
├─ Executa transação atômica:
│ ├─ Copia sessão: session[a1b2c3d4e5:0@lid] = session[5511987654321:0@s.whatsapp.net]
│ ├─ Deleta antiga: session[5511987654321:0@s.whatsapp.net] = null
│ ├─ Copia sessão: session[a1b2c3d4e5:5@lid] = session[5511987654321:5@s.whatsapp.net]
│ └─ Deleta antiga: session[5511987654321:5@s.whatsapp.net] = null
├─ Cacheia migrações completadas (TTL: 7 dias)
│ └─ migratedSessionCache.add("5511987654321:0")
└─ Retorna: { migrated: 2, skipped: 0, total: 2 }
```
### 3. Descoberta de Devices no Envio
```
messages-send.ts:303-403
Ao enviar mensagem para usuário:
├─ Executa USyncQuery
│ └─> .withDeviceProtocol()
│ └─> .withLIDProtocol()
├─ Servidor responde com:
│ ├─ Device list: [0, 1, 5, 99]
│ ├─ LID: "a1b2c3d4e5"
│ └─ Identity keys de cada device
├─ Sistema armazena:
│ ├─ device-list[user] = [devices...]
│ └─> Permite migração em massa futura
└─ Prioriza uso de LID se disponível
```
### 4. USync Protocol (Busca de LID)
```
socket.ts:470-493
pnFromLIDUSync(jids: string[]):
├─ Cria USyncQuery
│ └─> .withLIDProtocol()
│ └─> .withContext('background')
├─ Adiciona usuários
│ └─> .withUser(new USyncUser().withId(jid))
├─ Executa query no servidor WhatsApp
│ └─> const results = await executeUSyncQuery(usyncQuery)
└─ Retorna mapeamentos:
[{
pn: "5511987654321@s.whatsapp.net",
lid: "a1b2c3d4e5@lid"
}]
```
---
## Como aparece nos logs
### Cenário 1: Primeiro Contato (Apenas PN)
```
[INFO] Sending message to: 5511987654321@s.whatsapp.net
[DEBUG] No LID mapping found, using PN
[DEBUG] Querying devices for: 5511987654321@s.whatsapp.net
[DEBUG] USyncQuery: withDeviceProtocol, withLIDProtocol
[INFO] Devices found: [0, 1, 5]
[INFO] LID received from server: a1b2c3d4e5
[DEBUG] Storing LID mapping: 5511987654321 → a1b2c3d4e5
[DEBUG] Storing device list: [0, 1, 5]
[INFO] Message encrypted for PN: 5511987654321:0@s.whatsapp.net
```
### Cenário 2: Após Interação (Usuário envia mensagem)
```
[INFO] Message received from: 5511987654321@s.whatsapp.net
[DEBUG] Checking for LID mapping...
[DEBUG] LID found in cache: a1b2c3d4e5@lid
[INFO] Triggering session migration...
[DEBUG] migrateSession(5511987654321@s.whatsapp.net → a1b2c3d4e5@lid)
[DEBUG] Found devices in device-list: [0, 1, 5]
[DEBUG] Migrating device 0: session copied and old deleted
[DEBUG] Migrating device 5: session copied and old deleted
[INFO] Migration completed: { migrated: 2, skipped: 0 }
[DEBUG] Cached migration for 7 days
```
### Cenário 3: Envio Subsequente (Usa LID)
```
[INFO] Sending message to: 5511987654321@s.whatsapp.net
[DEBUG] Checking for LID mapping...
[DEBUG] LID found in cache: a1b2c3d4e5@lid (cache hit)
[DEBUG] Using LID for encryption
[INFO] Message encrypted for LID: a1b2c3d4e5:0@lid
[INFO] Message encrypted for LID: a1b2c3d4e5:5@lid
[DEBUG] Encryption used migrated session (no re-establishment needed)
```
### Cenário 4: Cache Miss (Busca no Database)
```
[INFO] Sending message to: 5511988888888@s.whatsapp.net
[DEBUG] Cache miss for PN: 5511988888888
[DEBUG] Fetching from database: lid-mapping:pn:5511988888888
[DEBUG] Database hit: found LID mapping → b2c3d4e5f6
[DEBUG] Updating cache (TTL: 3 days)
[INFO] Using LID: b2c3d4e5f6:0@lid
```
### Cenário 5: USync Fallback (Não está no DB)
```
[INFO] Sending message to: 5511977777777@s.whatsapp.net
[DEBUG] Cache miss for PN: 5511977777777
[DEBUG] Database miss: no mapping found
[DEBUG] Triggering USync fetch...
[DEBUG] USyncQuery with LIDProtocol for: 5511977777777
[INFO] USync response received: LID = c3d4e5f6g7
[DEBUG] Storing LID mapping in database
[DEBUG] Updating cache
[INFO] Using LID: c3d4e5f6g7:0@lid
```
### Exemplo de Log Real Completo
```
2026-02-09 10:15:32 [socket.ts:1462] Connection success, lid=a1b2c3d4e5
2026-02-09 10:15:32 [lid-mapping.ts:245] storeLIDPNMappings: 1 mapping(s)
2026-02-09 10:15:32 [lid-mapping.ts:267] Phase 1: Validated 1, cache miss 1
2026-02-09 10:15:32 [lid-mapping.ts:305] Phase 3: Stored 1 new mapping(s)
2026-02-09 10:15:32 [socket.ts:1475] Stored device-list for user: 5511987654321
2026-02-09 10:15:32 [libsignal.ts:491] migrateSession: PN → LID
2026-02-09 10:15:32 [libsignal.ts:522] Found 2 devices: [0, 5]
2026-02-09 10:15:32 [libsignal.ts:589] Migrated device 0: a1b2c3d4e5:0@lid
2026-02-09 10:15:32 [libsignal.ts:589] Migrated device 5: a1b2c3d4e5:5@lid
2026-02-09 10:15:32 [libsignal.ts:627] Migration result: {migrated:2, skipped:0}
... usuário interage ...
2026-02-09 10:20:15 [messages-send.ts:346] Preparing message for 5511987654321
2026-02-09 10:20:15 [lid-mapping.ts:158] getLIDForPN: 5511987654321
2026-02-09 10:20:15 [lid-mapping.ts:164] Cache hit: a1b2c3d4e5
2026-02-09 10:20:15 [messages-send.ts:370] Using LID: a1b2c3d4e5:0@lid
2026-02-09 10:20:15 [messages-send.ts:370] Using LID: a1b2c3d4e5:5@lid
2026-02-09 10:20:15 [libsignal.ts:180] Encrypting with session: a1b2c3d4e5:0@lid
2026-02-09 10:20:15 [libsignal.ts:180] Encrypting with session: a1b2c3d4e5:5@lid
2026-02-09 10:20:15 [socket.ts:892] Message sent successfully
```
---
## Implementação Técnica
### Arquivos Principais
```
src/
├── Signal/
│ ├── lid-mapping.ts (1.166 linhas)
│ │ └─> LIDMappingStore: Cache LRU + Database + USync
│ │
│ └── libsignal.ts
│ └─> migrateSession(): Migração PN → LID
├── Socket/
│ ├── socket.ts
│ │ ├─> Inicialização: Armazena LID próprio
│ │ └─> Trigger migração no login
│ │
│ └── messages-send.ts
│ ├─> Device query com USyncQuery
│ └─> Decisão LID vs PN
├── Utils/
│ └── sync-action-utils.ts
│ └─> LID mapping events
└── Types/
└── Auth.ts
└─> type LIDMapping = { pn, lid }
```
### LIDMappingStore - Operações Principais
```typescript
class LIDMappingStore {
// 1. ARMAZENAR MAPEAMENTOS
async storeLIDPNMappings(mappings: LIDMapping[]): Promise<Result> {
// 3 fases: Validate → Fetch → Store
// Batch size: 100 (configurável)
// Retry: 3 tentativas com backoff exponencial
}
// 2. BUSCAR LID PARA PN (com device)
async getLIDForPN(pn: string): Promise<string | undefined> {
// Input: "5511987654321:5@s.whatsapp.net"
// Output: "a1b2c3d4e5:5@lid"
// Flow: Cache → Database → USync (fallback)
// Request coalescing: Deduplica requests concorrentes
}
// 3. BUSCAR PN PARA LID (reverse)
async getPNForLID(lid: string): Promise<string | undefined> {
// Input: "a1b2c3d4e5:5@lid"
// Output: "5511987654321:5@s.whatsapp.net"
// Flow: Cache → Database → USync (fallback)
}
// 4. OPERAÇÕES EM LOTE
async getLIDsForPNs(pns: string[]): Promise<Map<string, string>> {
// Processa múltiplos PNs em paralelo
// Batch size configurable
}
async getPNsForLIDs(lids: string[]): Promise<Map<string, string>> {
// Processa múltiplos LIDs em paralelo
}
}
```
### Request Coalescing (Deduplicação)
```typescript
// Problema: 10 chamadas simultâneas para o mesmo PN
const promises = Array(10).fill(null).map(() =>
lidMapping.getLIDForPN("5511987654321@s.whatsapp.net")
)
// Sem coalescing: 10 queries ao database ❌
// Com coalescing: 1 query compartilhada ✅
// Implementação:
pendingRequests.set(key, dbPromise)
// Requests subsequentes reutilizam a mesma Promise
return pendingRequests.get(key)
```
### Configuração de Ambiente
```bash
# LID Cache TTL (Tempo de vida)
BAILEYS_LID_CACHE_TTL_MS=259200000 # 3 dias (min: 60s, max: 30 dias)
# Tamanho máximo do cache
BAILEYS_LID_MAX_CACHE_SIZE=50000 # 50k entradas (min: 100, max: 1M)
# Batch size para operações em lote
BAILEYS_LID_BATCH_SIZE=100 # (min: 1, max: 1000)
# Retry attempts
BAILEYS_LID_RETRY_ATTEMPTS=3 # (min: 1, max: 10)
BAILEYS_LID_RETRY_DELAY_MS=1000 # 1s base + exponential backoff
# Features
BAILEYS_LID_CACHE_AUTO_PURGE=true # Auto cleanup de cache
BAILEYS_LID_UPDATE_AGE_ON_GET=true # Renova TTL ao acessar
BAILEYS_LID_METRICS=false # Estatísticas detalhadas
BAILEYS_LID_DEBUG=false # Logs de debug
```
### Estatísticas (Metrics)
```typescript
getStats() {
return {
cacheSize: 15234, // Entradas no cache
cacheHits: 45678, // Acertos no cache
cacheMisses: 1234, // Falhas de cache
cacheHitRate: 0.973, // 97.3% hit rate
databaseHits: 892, // Buscas no database
databaseMisses: 342, // Não encontrado no DB
usyncFetches: 342, // Fallback para USync
usyncFailures: 5, // Falhas no USync
mappingsStored: 16789, // Total armazenado
invalidMappings: 23, // Rejeitados (inválidos)
operationsInProgress: 3 // Operações ativas (UAF protection)
}
}
```
### Device Migration Cache
```typescript
// Cache de migrações completadas (evita re-processar)
migratedSessionCache = new Map<string, number>()
// Key: "5511987654321:0" (PN + device)
// Value: timestamp da migração
// TTL: 7 dias
const MIGRATION_CACHE_TTL = 7 * 24 * 60 * 60 * 1000
// Cleanup automático de entradas expiradas
setInterval(() => {
const now = Date.now()
for (const [key, timestamp] of migratedSessionCache.entries()) {
if (now - timestamp > MIGRATION_CACHE_TTL) {
migratedSessionCache.delete(key)
}
}
}, 60 * 60 * 1000) // Executa a cada 1 hora
```
---
## Resumo Executivo
### O Fluxo Completo em Etapas
```
1. LOGIN
└─> Servidor envia LID do usuário próprio
└─> Sistema armazena: PN ↔ LID (self mapping)
└─> Trigger migração de sessões Signal: PN → LID
2. PRIMEIRO ENVIO PARA CONTATO
└─> Usa PN (não tem LID ainda)
└─> USyncQuery descobre devices + LID
└─> Armazena LID mapping + device list
3. USUÁRIO INTERAGE (envia mensagem)
└─> Sistema detecta PN no from
└─> Busca LID (cache/database/usync)
└─> Trigger migração de sessão para esse contato
4. ENVIOS SUBSEQUENTES
└─> Usa LID (já migrado)
└─> Cache hit (rápido)
└─> Encriptação usa sessão LID
```
### Por que você vê PN e LID nos logs?
```
Momento 1: Primeira interação
├─ Sistema ainda usa PN
├─ Descobre LID via USync
└─ Logs mostram: "using PN" → "LID discovered"
Momento 2: Migração acontece
├─> Sessões Signal copiadas PN → LID
└─> Logs mostram: "migrated 2 devices"
Momento 3: Próximas mensagens
├─> Sistema usa LID
└─> Logs mostram: "using LID" (cache hit)
```
### Benefícios da Implementação
**Performance**
- Cache LRU com 97%+ hit rate
- Request coalescing evita duplicação
- Batch operations reduzem roundtrips
**Confiabilidade**
- Retry com exponential backoff
- Graceful degradation (fallback para PN)
- Transaction support para atomicidade
**Segurança**
- Sessões Signal preservadas
- Identity keys validados
- Detecção de device reinstall
**Escalabilidade**
- Cache configurável (até 1M entradas)
- Batch size ajustável
- Auto purge de dados antigos
**Observabilidade**
- Métricas detalhadas
- Debug logging configurável
- Statistics para monitoramento
---
## Conclusão
O sistema de **Device Migration com LID/PN** é uma implementação **production-grade** que permite ao WhatsApp migrar usuários de identificadores baseados em número de telefone (PN) para identificadores de longa duração (LID) de forma **transparente**, **eficiente** e **segura**.
A transição acontece automaticamente quando:
1. ✅ Usuário loga (recebe LID do servidor)
2. ✅ Envia mensagem (descobre LID do destinatário)
3. ✅ Recebe mensagem (descobre LID do remetente)
Após migração, **todas as comunicações usam LID**, preservando:
- 🔐 Sessões criptográficas Signal
- 🆔 Identidade mesmo com troca de número
- 🚀 Performance com cache inteligente
- 🔄 Compatibilidade com sistemas antigos (fallback PN)
**Nos logs, você verá**:
- **PN** → Primeira interação ou fallback
- **LID** → Após descoberta e migração (estado ideal)
-341
View File
@@ -1,341 +0,0 @@
# Implementação: Merge Automático LID/PN
## 📋 Resumo
Esta implementação resolve o problema de duplicação de conversas causado por identificadores LID (Long-lived ID) e PN (Phone Number) do WhatsApp, implementando merge automático na API (Opção A+).
## 🎯 Objetivos Alcançados
**Consolidação de eventos**: `lid-mapping.update` agora é bufferável
**Merge automático**: API detecta e notifica mudanças LID→PN
**Backward compatible**: ZPRO antigo continua funcionando
**Zero duplicação**: Consumidores recebem notificação para unificar chats
## 📝 Mudanças Implementadas
### 1. **Event Buffer** (`src/Utils/event-buffer.ts`)
#### Adicionado `lid-mapping.update` ao array de eventos bufferáveis:
```typescript
const BUFFERABLE_EVENT = [
// ... eventos existentes
'lid-mapping.update' // ← NOVO
] as const
```
**Benefício**: Eventos LID mapping agora são consolidados em batches junto com mensagens, reduzindo 50% de eventos separados.
#### Adicionado lógica de append para `lid-mapping.update`:
```typescript
case 'lid-mapping.update':
const lidMappings = eventData as BaileysEventMap['lid-mapping.update']
for (const mapping of lidMappings) {
const key = `${mapping.lid}-${mapping.pn}`
if (!data.lidMappings[key]) {
data.lidMappings[key] = mapping
}
}
break
```
#### Adicionado consolidação em `consolidateEvents()`:
```typescript
const lidMappingList = Object.values(data.lidMappings)
if (lidMappingList.length) {
map['lid-mapping.update'] = lidMappingList
}
```
#### Adicionado inicialização em `makeBufferData()`:
```typescript
return {
// ... campos existentes
lidMappings: {} // ← NOVO
}
```
---
### 2. **Tipos** (`src/Types/Events.ts`)
#### Adicionado `lidMappings` ao `BufferedEventData`:
```typescript
export type BufferedEventData = {
// ... campos existentes
lidMappings: { [key: string]: LIDMapping } // ← NOVO
}
```
---
### 3. **Tipos de Chat** (`src/Types/Chat.ts`)
#### Estendido `ChatUpdate` com campos de merge:
```typescript
export type ChatUpdate = Partial<
Chat & {
// ... campos existentes
/** indicates if this chat was merged from LID to PN */
merged?: boolean
/** previous chat ID before merge (LID format) */
previousId?: string
/** timestamp when the merge occurred */
mergedAt?: number
}
>
```
**Nota**: Campos são **opcionais** para manter backward compatibility.
---
### 4. **Socket de Chats** (`src/Socket/chats.ts`)
#### Implementado merge automático no handler `lid-mapping.update`:
```typescript
ev.on('lid-mapping.update', async (mappings) => {
try {
// 1. Armazenar mapeamentos (lógica existente)
const result = await signalRepository.lidMapping.storeLIDPNMappings(mappings)
// 2. NOVO: Notificar consumidores sobre merge
for (const mapping of mappings) {
const lidUser = jidNormalizedUser(mapping.lid)
const pnUser = jidNormalizedUser(mapping.pn)
if (lidUser && pnUser && lidUser !== pnUser) {
// Emite evento de chat update com metadados de merge
ev.emit('chats.update', [{
id: pnUser,
merged: true,
previousId: lidUser,
mergedAt: Date.now()
}])
}
}
} catch (error) {
logger.warn({ count: mappings.length, error }, 'Failed to store LID-PN mappings')
}
})
```
**Comportamento**:
1. API detecta novo mapeamento LID→PN
2. Armazena mapeamento internamente
3. Emite evento `chats.update` com campos `merged`, `previousId`, `mergedAt`
4. ZPRO recebe notificação e pode unificar chats localmente
---
## 🔄 Fluxo de Funcionamento
### Antes (Problema):
```
1. Mensagem chega com LID
└─> ZPRO cria chat: "123@lid"
2. Cliente interage, descobre PN
└─> API emite lid-mapping.update (evento SEPARADO)
└─> ZPRO recebe mensagem com PN
└─> ZPRO cria OUTRO chat: "5511999@s.whatsapp.net"
RESULTADO: 2 CONVERSAS DUPLICADAS ❌
```
### Depois (Solução):
```
1. Mensagem chega com LID
└─> ZPRO cria chat: "123@lid"
2. Cliente interage, descobre PN
└─> API detecta LID→PN mapping
└─> API emite em BATCH consolidado:
├─ messages.upsert (mensagens)
├─ lid-mapping.update (mapeamento)
└─ chats.update ({ merged: true, previousId: "123@lid" })
3. ZPRO recebe batch
└─> Detecta campo "merged: true"
└─> Renomeia chat de "123@lid" para "5511999@s.whatsapp.net"
└─> Unifica mensagens
RESULTADO: 1 CONVERSA UNIFICADA ✅
```
---
## 💻 Como ZPRO Deve Implementar
### Código Exemplo:
```typescript
// ZPRO precisa apenas escutar chats.update
sock.ev.on('chats.update', async (chats) => {
for (const chat of chats) {
// Detectar merge
if (chat.merged && chat.previousId) {
console.log(`Chat merged: ${chat.previousId}${chat.id}`)
// 1. Renomear chat no banco de dados
await database.chats.update(
{ id: chat.previousId },
{ id: chat.id }
)
// 2. Atualizar mensagens (opcional, se armazenadas separadamente)
await database.messages.updateMany(
{ chatId: chat.previousId },
{ chatId: chat.id }
)
// 3. Atualizar UI
updateChatInUI(chat.previousId, chat.id)
}
}
})
```
**Backward Compatibility**:
```typescript
// ZPRO ANTIGO (sem suporte a merge)
sock.ev.on('chats.update', async (chats) => {
for (const chat of chats) {
updateChat(chat.id, chat.unreadCount)
// Ignora campos "merged", "previousId" automaticamente ✅
}
})
// Funciona sem quebrar! Apenas não aproveita o merge.
```
---
## 📊 Performance
### Impacto de Performance (120 instâncias, 1200 msgs/dia):
| Métrica | Antes | Depois | Melhoria |
|---------|-------|--------|----------|
| **Eventos emitidos** | 2 separados | 1 batch | 50% ↓ |
| **Latência buffer** | N/A | +30-100ms | Imperceptível |
| **Processamento ZPRO** | 2 rodadas | 1 rodada | 50% ↓ |
| **Memória API** | N/A | +7MB/instância | Desprezível |
| **CPU** | N/A | <1% | Desprezível |
**Conclusão**: Zero impacto negativo, melhorias significativas em consolidação.
---
## ✅ Checklist de Testes
### Testes na API (Baileys):
- [x] `lid-mapping.update` é bufferável
- [x] Eventos consolidam em batch
- [x] `chats.update` contém campos `merged`, `previousId`, `mergedAt`
- [ ] Teste unitário de consolidação de buffer
- [ ] Teste de integração com 10-120 instâncias
- [ ] Benchmark de performance
### Testes no ZPRO:
- [ ] ZPRO recebe campo `merged: true`
- [ ] ZPRO renomeia chat corretamente
- [ ] ZPRO unifica mensagens
- [ ] Backward compatibility (ZPRO antigo não quebra)
- [ ] UI atualiza após merge
---
## 🚀 Deployment
### Fase 1: Deploy da API ✅
```bash
# Implementação completa
git checkout claude/lid-pn-auto-merge-implementation-c96aba
git push origin claude/lid-pn-auto-merge-implementation-c96aba
# Após review e merge
# Deploy em produção
```
### Fase 2: Atualização do ZPRO (Opcional)
```bash
# ZPRO pode atualizar quando quiser para aproveitar merge
# Não há pressa - backward compatible!
```
---
## 📚 Referências
- [DEVICE_MIGRATION_LID_PN.md](./DEVICE_MIGRATION_LID_PN.md) - Documentação completa sobre LID/PN
- [Análise de Performance](./DEVICE_MIGRATION_LID_PN.md#-teste-de-escala-10-a-120-instâncias) - Detalhes de performance
---
## 🎓 Notas Importantes
### 1. **Por que não fazer merge de mensagens na API?**
**Resposta**: Merge de mensagens seria muito complexo e custoso:
- API não tem acesso direto ao banco de mensagens do ZPRO
- Cada consumidor pode ter estrutura de dados diferente
- Melhor deixar consumidor decidir como mergear
### 2. **O que acontece se ZPRO não implementar o merge?**
**Resposta**: Continua funcionando, apenas com duplicação:
- ZPRO recebe eventos normalmente
- Campos `merged`, `previousId` são ignorados
- Sistema não quebra (backward compatible)
- Quando ZPRO implementar, merge será automático
### 3. **Quantos eventos ZPRO recebe?**
**Antes**:
```
1. messages.upsert (buffered)
2. lid-mapping.update (IMMEDIATE - não bufferável)
Total: 2 eventos em momentos diferentes
```
**Depois**:
```
1. Batch consolidado:
- messages.upsert
- lid-mapping.update
- chats.update (com merged: true)
Total: 1 evento batch
```
### 4. **Campos sem underscore**
Conforme solicitado, **TODOS os campos não usam `_` prefix**:
-`merged` (não `_merged`)
-`previousId` (não `_previousId`)
-`mergedAt` (não `_mergedAt`)
---
## 🔧 Troubleshooting
### Problema: ZPRO continua duplicando chats
**Solução**: ZPRO precisa implementar handler de `merged: true`
### Problema: Performance degradou
**Solução**: Verificar configuração de buffer:
```bash
BAILEYS_BUFFER_TIMEOUT_MS=30000 # 30s padrão
BAILEYS_BUFFER_MAX_SIZE=5000 # Limite de eventos
```
### Problema: Eventos não consolidam
**Solução**: Verificar se `lid-mapping.update` está em BUFFERABLE_EVENT
---
**Implementado por**: Claude (Opção A+)
**Data**: 2026-02-09
**Branch**: `claude/lid-pn-auto-merge-implementation-c96aba`