Resumen
El Team Chat es un widget de mensajería interna integrado en el home del Workspace (s69 · 18-05-2026). Funciona como un canal único de grupo estilo WhatsApp para los tres miembros del staff (Edu, Dani, Txell): solo texto, sin destinatarios explícitos, sin threading. Vive permanentemente visible como tercer widget del dashboard compartiendo zona con “Actividad reciente” y “Noticias” en un layout de 3 columnas.
Decisión de diseño: el plan original (Task #32) era un panel lateral colapsable en el header con badge de no leídos. Edu pidió el chat siempre visible como widget del home para mejor UX con un equipo de 3. Task #32 cancelada en consecuencia.
Arquitectura
Tablas D1 (migrations/0030_create_chat.sql)
| Tabla | Propósito |
|---|---|
chat_messages | Histórico completo de mensajes (id AUTOINCREMENT, author, body, created_at) |
chat_typing | Estado efímero “escribiendo…” (author PK, last_typed_at, TTL 5 s) |
- Índice
idx_chat_messages_created_at ON chat_messages(created_at DESC)para paginación eficiente. chat_typingusaINSERT … ON CONFLICT(author) DO UPDATEpara upsert atómico.- Migración aplicada en remote D1 antes del commit.
CF Pages Functions (backend)
| Archivo | Ruta HTTP | Métodos |
|---|---|---|
functions/api/chat/messages/index.ts | GET/POST /api/chat/messages | GET paginado + POST nuevo mensaje |
functions/api/chat/messages/[messageId].ts | DELETE /api/chat/messages/{id} | Borrar mensaje propio |
functions/api/chat/typing.ts | GET/POST /api/chat/typing | Indicador “escribiendo…” |
Seguridad v1: author validado contra ALLOWED_AUTHORS = {Edu, Dani, Txell}. Auth real delegada a CF Access + middleware. El DELETE verifica el autor real en DB antes de borrar (no solo el claim del cliente).
Paginación: GET /api/chat/messages?limit=N&before=ISO — devuelve mensajes created_at < before ORDER BY DESC, revertidos a ASC antes de enviar al cliente.
Widget React (src/components/widgets/TeamChat.tsx)
TeamChat
├── Lista scrollable (ref listRef)
│ ├── Carga inicial: 100 msgs
│ ├── Scroll-load hacia arriba: ?before=oldest.created_at
│ ├── Polling silencioso cada 15 s
│ └── Auto-scroll al final si nearBottom (<100px)
├── Indicador typing
│ ├── GET /api/chat/typing cada 3 s
│ └── POST /api/chat/typing throttled cada 3 s al teclear
└── Input
├── Enter → envía, Shift+Enter → nueva línea
├── Optimistic UI (id temporal negativo, sustituye al recibir POST 201)
└── Rollback al estado anterior en error
Identificación de autor: getCurrentUser() de lib/prefs — resuelve automáticamente sin selección manual.
Renderizado de URLs: regex /(https?:\/\/[^\s<>"]+)/g convierte URLs en <a> clicables con target="_blank".
Borrar mensaje propio: botón × visible en hover, solo para mensajes del autor actual y con id > 0 (no optimísticos). Llama DELETE /api/chat/messages/{id}?author=X.
Entorno local: si window.location.hostname === 'localhost', el widget muestra “Chat disponible en producción” y deshabilita toda la lógica de red.
Integración en el dashboard (src/components/variants/desktop/DesktopA.tsx)
| Antes (s68) | Después (s69) |
|---|---|
| Grid 2 columnas: WorklogFeed + NewsFeed | Grid 3 columnas: WorklogFeed + NewsFeed + TeamChat |
| WorklogFeed limit=5 | WorklogFeed limit=4 |
| — | Bento glow-violet con --grad-violet para TeamChat |
El widget TeamChat no es colapsable — siempre visible en el home.
Constantes de polling
| Constante | Valor | Propósito |
|---|---|---|
MSG_POLL_MS | 15 000 ms | Refresh silencioso de mensajes |
TYPING_POLL_MS | 3 000 ms | Consulta estado “escribiendo…” |
TYPING_PING_MS | 3 000 ms | Throttle del POST typing al teclear |
PAGE_SIZE | 100 | Mensajes por carga (inicial + páginas) |
TYPING_TTL_SECONDS | 5 | TTL en DB para typing activo |
MAX_BODY_LENGTH | 4 000 chars | Cap del body en backend |
Limitaciones conocidas
- Auth débil (v1): el
authorse pasa como query param / body claim. CF Access protege el acceso general, pero un miembro podría borrar mensajes de otro si manipula la request. Suficiente para equipo de 3 en entorno cerrado. - Sin notificaciones push: solo polling. No hay badge de no leídos (Task #32 cancelada).
- Sin threading ni reacciones: fuera de scope por diseño.
- Sin soporte multi-tenant: hardcoded a los 3 miembros del staff de CreaRack.
Véase también
- [[workspace—dashboard]]
- [[workspace—que-es-workspace]]
- [[entity—chat—endpoint—chat-messages-api]]