# 🗺️ Miau Triagem — Roadmap

**Stack:** Node.js · Express · better-sqlite3 · Evolution API · Vite + React + Tailwind

---

## Milestones

### M1 — Bot Operacional ✅
Bot no WhatsApp, pipeline IA, msgs no banco.
> *Feito. Tudo entregue.*

### M2 — Login Admin ✅
Autenticação com token, sessão protegida.
> *Feito. Tudo entregue.*

### M3 — Visualizar Conversas ← **agora**

| Lado | Issues | O que entregar |
|------|--------|---------------|
| 🛠️ **Backend** @klimadev | #11-#14 | API REST de conversas |
| 🎨 **Frontend** @MnicG | #15-#16 | Dashboard + Chat UI conectados à API |

---

## 📡 API Contract v1 — Conversas

### Autenticação
Toda requisição envia: `Authorization: Bearer <token>`

### `GET /api/conversations`
Lista todas conversas com última mensagem.

**Response 200:**
```json
[
  {
    "id": 1,
    "remoteJid": "5511999999999@s.whatsapp.net",
    "name": "João",
    "status": "active",
    "needsHuman": false,
    "lastMessage": "Qual o valor?",
    "updatedAt": "2026-07-09T10:30:00.000Z"
  }
]
```

| Campo | Tipo | Descrição |
|-------|------|-----------|
| `id` | int | ID único |
| `remoteJid` | string | JID do WhatsApp |
| `name` | string | Nome do contato |
| `status` | `"active"` ou `"closed"` | Se ainda aceita msg |
| `needsHuman` | boolean | LLM pediu HANDOFF |
| `lastMessage` | string | Texto da última mensagem |
| `updatedAt` | ISO string | Última atividade |

> `name` pode ser extraído do Evolution API ou do contato salvo. Se não tiver, vira "Desconhecido".

**Response 401:**
```json
{ "error": "Unauthorized" }
```

---

### GET /api/conversations/:id/messages
Histórico de mensagens de uma conversa.

**Response 200:**
```json
[
  {
    "id": 1,
    "role": "user",
    "content": "Olá, quanto custa o serviço?",
    "createdAt": "2026-07-09T10:25:00.000Z"
  },
  {
    "id": 2,
    "role": "bot",
    "content": "Olá! Valores a partir de R$ 99,90...",
    "createdAt": "2026-07-09T10:25:05.000Z"
  }
]
```

| Campo | Tipo | Descrição |
|-------|------|-----------|
| `id` | int | ID único |
| `role` | `"user"`·`"bot"`·`"human"` | Quem escreveu |
| `content` | string | Texto |
| `createdAt` | ISO string | Timestamp |

Ordenado por `createdAt` ASC (mais antiga primeiro).

**Response 404:**
```json
{ "error": "Conversation not found" }
```

---

### POST /api/conversations/:id/reply
Responde como humano. **Desativa** o LLM nessa conversa — bot não responde mais automaticamente até reativarem.

**Request body:**
```json
{ "text": "Vou transferir para o setor financeiro" }
```

**Response 200:**
```json
{ "ok": true }
```

**Response 400:**
```json
{ "error": "text is required" }
```

**Response 404:**
```json
{ "error": "Conversation not found" }
```

---

### POST /api/conversations/:id/activate
Reativa LLM na conversa. Daí o bot volta a responder automaticamente.

**Response 200:**
```json
{ "ok": true }
```

**Response 404:**
```json
{ "error": "Conversation not found" }
```

---

## 🧪 Como @MnicG testa sem backend pronto

Usa **mock service** no frontend. O fetch intercepta e devolve dados falsos:

```js
// src/services/mockApi.js
const MOCK_CONVERSATIONS = [
  { id: 1, remoteJid: "5511999999999@s.whatsapp.net", name: "João",
    status: "active", needsHuman: true, lastMessage: "Qual o valor?",
    updatedAt: new Date().toISOString() },
  { id: 2, remoteJid: "5511888888888@s.whatsapp.net", name: "Maria",
    status: "active", needsHuman: false, lastMessage: "Obrigado!",
    updatedAt: new Date().toISOString() },
  { id: 3, remoteJid: "5511777777777@s.whatsapp.net", name: "",
    status: "closed", needsHuman: false, lastMessage: "OK",
    updatedAt: new Date().toISOString() },
]

const MOCK_MESSAGES = (convId) => [
  { id: 1, role: "user", content: "Olá, quanto custa?", createdAt: new Date(Date.now()-60000).toISOString() },
  { id: 2, role: "bot", content: "Olá! A partir de R$99,90.", createdAt: new Date(Date.now()-55000).toISOString() },
  { id: 3, role: "user", content: "Quero contratar", createdAt: new Date(Date.now()-30000).toISOString() },
  { id: 4, role: "human", content: "Vou transferir", createdAt: new Date(Date.now()-10000).toISOString() },
]

// Pode alternar entre mock e real trocando VITE_USE_MOCK=true no .env
const USE_MOCK = import.meta.env.VITE_USE_MOCK === "true"

export async function fetchConversations() {
  if (USE_MOCK) return MOCK_CONVERSATIONS
  const res = await fetch("/api/conversations", { headers: { Authorization: `Bearer ${localStorage.getItem("token")}` }})
  if (!res.ok) throw new Error("Failed to fetch")
  return res.json()
}

export async function fetchMessages(id) {
  if (USE_MOCK) return MOCK_MESSAGES(id)
  const res = await fetch(`/api/conversations/${id}/messages`, { headers: { Authorization: `Bearer ${localStorage.getItem("token")}` }})
  if (!res.ok) throw new Error("Failed to fetch")
  return res.json()
}

export async function sendReply(id, text) {
  if (USE_MOCK) return { ok: true }
  const res = await fetch(`/api/conversations/${id}/reply`, {
    method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${localStorage.getItem("token")}` },
    body: JSON.stringify({ text })
  })
  if (!res.ok) throw new Error("Failed to reply")
  return res.json()
}
```

Cada função tem mock + real. Trocando `VITE_USE_MOCK=true` no `.env` ele desenvolve a UI inteira sem backend rodando.

---

### 🎯 Regra de Ouro

> Ambos seguem **exatamente** esta spec. Se precisar mudar algo, abre issue, discute, atualiza o contrato. Só depois codifica a mudança.

---

## M4 — Produção
SSE · rate limit · health check · .env.example · deploy.

---

---

### M5 — Onboarding WhatsApp (Instâncias Evolution) ← **próximo**

| Lado | Issues | O que entregar |
|------|--------|---------------|
| 🛠️ **Backend** @klimadev | #18-#23 | API REST de instâncias Evolution |
| 🎨 **Frontend** @MnicG | #24 | Wizard de conexão WhatsApp |

---

## 📡 API Contract v2 — Instâncias Evolution

### Autenticação
Toda requisição envia: `Authorization: Bearer <token>`

### `GET /api/instances`
Lista todas instâncias Evolution.

**Response 200:**
```json
[
  {
    "name": "teste",
    "status": "open",
    "owner": "555199309404",
    "messages": 9800,
    "chats": 45,
    "createdAt": "2026-07-01T10:00:00.000Z"
  }
]
```

### `POST /api/instances`
Cria nova instância.

**Request body:**
```json
{
  "name": "vendedor-joao",
  "number": "555199309404"
}
```

**Response 201:**
```json
{ "ok": true, "name": "vendedor-joao" }
```

**Response 400:**
```json
{ "error": "name is required" }
```

### `GET /api/instances/:name/qr`
Gera QR code para conectar instância.

**Response 200:**
```json
{ "qrcode": "data:image/png;base64,..." }
```

### `GET /api/instances/:name/pairing`
Gera pairing code (notificação no celular).

**Query:** `?number=5511999999999`

**Response 200:**
```json
{ "code": "ABC-123", "pairingCode": true }
```

**Response 400:**
```json
{ "error": "number is required" }
```

### `GET /api/instances/:name/status`
Estado da conexão.

**Response 200:**
```json
{
  "name": "teste",
  "state": "open",
  "status": "connected",
  "owner": "555199309404"
}
```

### `DELETE /api/instances/:name`
Remove instância permanentemente ⚠️

**Response 200:**
```json
{ "ok": true }
```

---

> **Milestones seguem:** M3 → M4 → M5