File size: 3,552 Bytes
71b8eb2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# ALERTS.md — Módulo de alertas

Historial de alertas de precio disparadas por el job `processAlerts`. Las alertas se crean automáticamente cuando `yesPrice >= alertThreshold` en la watchlist del usuario.

**Todos los endpoints requieren `Authorization: Bearer <token>`.**

---

## Endpoints

### `GET /api/v1/alerts`

Lista el historial de alertas del usuario, paginado y ordenado por `sentAt` DESC.

**Query params**

| Param | Tipo | Default | Descripción |
|---|---|---|---|
| `limit` | int (1-100) | `20` | Máximo de resultados |
| `offset` | int | `0` | Paginación por offset |

**Respuesta `200`**

```json
{
  "ok": true,
  "data": [
    {
      "id": 4,
      "userId": 1,
      "marketId": "559677",
      "type": "price_threshold",
      "message": "<b>Price Alert</b>\nWill Hillary Clinton win the 2028 Democratic presidential nomination?\nYES: 0.8% ≥ threshold 0.1%",
      "sentAt": "2026-05-16T09:38:00.033Z",
      "market": {
        "id": "559677",
        "question": "Will Hillary Clinton win the 2028 Democratic presidential nomination?"
      }
    }
  ]
}
```

**Campos**

| Campo | Tipo | Descripción |
|---|---|---|
| `id` | int | ID de la alerta |
| `userId` | int | Usuario que la recibió |
| `marketId` | string | Mercado que la disparó |
| `type` | string | Tipo de alerta (`price_threshold`) |
| `message` | string | Mensaje enviado (formato HTML para Telegram) |
| `sentAt` | ISO 8601 | Timestamp de envío |
| `market.question` | string | Pregunta del mercado (para mostrar en UI) |

---

## Flujo de creación

Las alertas **no se crean desde el frontend** — solo se leen. El flujo de creación es:

1. `POST /api/v1/watchlist` con `alertThreshold` → guarda umbral en DB.
2. Job `processAlerts` (cada minuto): evalúa `yesPrice >= alertThreshold` para cada watchlist entry con threshold definido.
3. Si se cumple y no hay alerta reciente (< 5 min): crea `Alert` + envía Telegram + emite `price_alert` por socket.

---

## Socket — evento `price_alert`

**Nombre del evento:** `price_alert`

**Payload**

```json
{
  "marketId": "559677",
  "type": "price_threshold",
  "message": "<b>Price Alert</b>\n..."
}
```

**Uso en el frontend**

```js
socket.on('price_alert', ({ marketId, message }) => {
  // mostrar notificación toast
});
```

---

## Integración Telegram

Para recibir alertas vía Telegram:

1. Crear un bot en [@BotFather](https://t.me/BotFather) y obtener el `TELEGRAM_BOT_TOKEN`.
2. El usuario debe iniciar conversación con el bot y obtener su `chatId`.
3. Configurar `telegramChatId` en el registro `User` (actualmente solo vía `prisma studio` o seed).
4. Añadir `TELEGRAM_BOT_TOKEN` al `.env`.

Si `TELEGRAM_BOT_TOKEN` no está configurado o el usuario no tiene `telegramChatId`, el envío se omite silenciosamente (la `Alert` se crea igualmente en DB).

---

## Ejemplos `curl`

```bash
TOKEN=$(curl -s -X POST http://localhost:7860/api/v1/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"email":"admin@polysignal.test","password":"Admin123!"}' | jq -r '.data.token')

# Listar alertas (últimas 10)
curl -s -H "Authorization: Bearer $TOKEN" \
  "http://localhost:7860/api/v1/alerts?limit=10" | jq

# Segunda página
curl -s -H "Authorization: Bearer $TOKEN" \
  "http://localhost:7860/api/v1/alerts?limit=10&offset=10" | jq
```

---

## Códigos de error

| HTTP | Código | Cuándo |
|---|---|---|
| `400` | `VALIDATION_ERROR` | Params inválidos (limit > 100) |
| `401` | `UNAUTHORIZED` | Sin token o token inválido |
| `500` | `INTERNAL` | Error inesperado |