---
id: "0063"
title: "kanban: sistema de stickers (emojis) sobre cards"
status: pendiente
type: feature
domain:
  - kanban
scope: multi-app
priority: media
depends: []
blocks: []
related: []
created: 2026-05-17
updated: 2026-05-17
tags: []
---
# 0063 — kanban: sistema de stickers (emojis) sobre cards

## APP Metadata

| Campo | Valor |
|-------|-------|
| **ID** | 0063 |
| **Estado** | pendiente |
| **Prioridad** | media |
| **Tipo** | feature — `apps/kanban/` |

## Dependencias

- Ninguna. Aplica TBD obligatorio (`.claude/rules/apps_tbd.md`): trabajar en `issue/0063-stickers`, merge `--no-ff` a master.

## Objetivo

Permitir que el usuario "decore" visualmente las cards del tablero pegandoles emojis (stickers) con transparencia. Util para marcar estados visualmente sin abusar de tags ni colores: 🔥 (urgente), ✅ (revisado), ⚠️ (cuidado), 🚀 (lanzado), ❤️ (importante), 💀 (bloqueado), etc.

## Contexto

`apps/kanban/` es una app Go que embebe un frontend React + Mantine v9 + dnd-kit. El tablero esta en `frontend/src/App.tsx` y cada card se renderiza en `frontend/src/components/KanbanCard.tsx`. Las cards ya tienen color (`color`), tags, asignado, bloqueo, historial y descripcion. Faltan **stickers** como capa decorativa libre, separada del modelo de tags.

UX deseada:

1. Usuario hace clic en boton "Stickers" de la toolbar del board (`App.tsx:920`).
2. Aparece picker con lista de emojis predefinidos.
3. Usuario selecciona uno → entra en "modo pegar sticker" (cursor cambia, indicador visible).
4. Hace clic sobre cualquier card → el emoji se pinta encima de la card con opacidad (~0.6).
5. ESC sale del modo. Click derecho sobre un sticker existente lo borra.
6. Stickers persisten en BD para que sobrevivan recargas y todos los usuarios los vean.

## Decisiones de diseno (a confirmar antes de implementar)

| Decision | Recomendacion default | Alternativas |
|----------|-----------------------|--------------|
| Persistencia | BD (campo `stickers JSON` en cards) | localStorage por usuario |
| Cantidad por card | Multiples apilados | Uno solo |
| Posicion | Arrastrable dentro de la card | Esquina/centrado fijo |
| Tamaño / opacidad | Fijos: 48px, 0.6 | Configurables por sticker |
| Fuente emojis | Lista hardcoded ~20 | `emoji-mart`, picker nativo |
| UX modo activo | Cursor especial + ESC sale | Toggle persistente |
| Borrado | Click derecho sobre sticker | Modo "quitar sticker" |

Confirmar con usuario antes de Fase 1. Si elige defaults, proceder.

## Arquitectura

### Archivos afectados

**Backend Go (`apps/kanban/`):**
- `migrations/00X_add_stickers.sql` (NEW) — `ALTER TABLE cards ADD COLUMN stickers TEXT NOT NULL DEFAULT '[]'`.
- `db.go` — leer/escribir columna `stickers` en `Card`.
- `handlers.go` — endpoint `PATCH /api/cards/:id/stickers` (body: `{"stickers":[{emoji,x,y}]}`). Reusar el patron de `updateCard`.
- `tools.go` — exponer mutacion al chat agent si aplica (opcional).

**Frontend (`apps/kanban/frontend/src/`):**
- `types.ts` — añadir `Sticker { emoji: string; x: number; y: number; }` y `Card.stickers: Sticker[]`.
- `api.ts` (NEW endpoint helper) — `updateCardStickers(id, stickers)`.
- `App.tsx` — boton "Stickers" en toolbar (`:920`), estado `pickerOpen`, `activeSticker` (emoji seleccionado en modo pegar), handler global ESC, handler `onCardSticker(cardId, x, y)`.
- `components/KanbanCard.tsx` — render overlay absoluto de `card.stickers` (z-index alto, pointer-events: none salvo en modo borrado), cuando hay `activeSticker` cambiar cursor + capturar onClick para añadir sticker en coords relativas.
- `components/StickerPicker.tsx` (NEW) — Popover con grid de emojis predefinidos.

**Pure / impure split:**
- Pure: util `relativeCoords(event, cardEl) → {x,y}` (frontend, en `components/format.ts` o nuevo `stickers.ts`).
- Impure: handler de PATCH HTTP, mutacion de BD, broadcast de cambio si hay realtime.

### Funciones del registry a reutilizar

Auditar antes de escribir:

```bash
sqlite3 registry.db "SELECT id, description FROM functions WHERE id IN (SELECT id FROM functions_fts WHERE functions_fts MATCH 'name:emoji OR description:emoji OR name:sticker OR description:sticker');"
```

Probable: nada existe. Si `relativeCoords` resulta reusable → delegar a `fn-constructor` y crear `dom_relative_coords_ts_core` o similar (evaluar tras decision UX).

## Tareas

### Fase 1 — confirmar diseno
1.1 Confirmar con usuario las 7 decisiones de la tabla. Si elige defaults, seguir.
1.2 Auditar registry: `fn search "emoji"`, `fn search "sticker"`. Documentar reutilizacion en este issue.

### Fase 2 — backend
2.1 Crear migracion `migrations/00X_add_stickers.sql`.
2.2 Tipo Go `Sticker { Emoji string; X, Y float64 }` en `db.go`. `Card.Stickers []Sticker` con custom marshalling JSON ↔ TEXT.
2.3 Endpoint `PATCH /api/cards/:id/stickers` en `handlers.go`. Validar payload (emoji no vacio, x/y en [0,1]).
2.4 Aplicar migracion al arrancar (mecanismo existente de `apps/kanban/migrations`).

### Fase 3 — frontend tipos + api
3.1 Añadir `Sticker` y `Card.stickers` en `types.ts`.
3.2 Añadir `updateCardStickers(id, stickers)` en `api.ts`.

### Fase 4 — UI: picker + modo activo
4.1 Crear `components/StickerPicker.tsx` con lista hardcoded (≈20 emojis: 🔥⭐✅⚠️🚀💀🎯❤️👀💡📌🐛✨🎉🙏🤔😅🚧🟢🔴).
4.2 En `App.tsx`: nuevo state `activeSticker: string | null`. Boton "Stickers" con `IconMoodSmile` o similar de `@tabler/icons-react`. Popover con `StickerPicker`. Listener global `keydown` ESC → `setActiveSticker(null)`.
4.3 Indicador visual del modo activo (banner o cursor custom).

### Fase 5 — render + interaccion en cards
5.1 En `KanbanCard.tsx`: render overlay absoluto de `card.stickers` (cada uno como `<span style={{position:'absolute', left:x*100+'%', top:y*100+'%', fontSize:48, opacity:.6, pointerEvents: deleteMode ? 'auto' : 'none'}}>{emoji}</span>`).
5.2 Cuando `activeSticker !== null`, en `onClick` de la Paper de la card calcular coords relativas (`event.offsetX / cardEl.offsetWidth`, idem Y, clamp [0,1]), pushear nuevo sticker a `card.stickers`, llamar `updateCardStickers`, optimistic update.
5.3 Click derecho sobre un sticker → quitar de la lista + persistir.

### Fase 6 — tests + verificacion
6.1 `tools_test.go`: extender (o nuevo `stickers_test.go`) con test de PATCH `/api/cards/:id/stickers` (insertar 2, borrar 1, leer card).
6.2 Manual smoke test: boton aparece, picker funciona, sticker se pega, persiste tras F5, click derecho borra, ESC sale del modo.
6.3 `fn doctor uses-functions` no debe regresionar para `kanban_go_tools`.

### Fase 7 — docs y cleanup
7.1 Si se reutilizo o creo funcion del registry → declararla en `apps/kanban/app.md` (`uses_functions`).
7.2 Una linea en `apps/kanban/app.md` describiendo la feature.
7.3 `fn index` y verificar `fn show kanban_go_tools`.

## Ejemplo de uso

```
1. Usuario clica "Stickers" en toolbar del board
2. Picker abre, usuario clica 🔥
3. Cursor pasa a "modo pegar", banner: "Modo sticker: 🔥 — ESC para salir"
4. Usuario clica en card "Migrar usuarios"
   → 🔥 aparece donde clico, opacidad 0.6, persistido
5. Usuario sigue clicando → mas 🔥 se acumulan
6. Pulsa ESC → modo off, cursor normal
7. F5 → stickers siguen ahi
8. Click derecho sobre 🔥 → desaparece
```

## Riesgos

- **Hit-test confuso**: si los stickers absorben pointer events, rompen drag-and-drop de la card. Mitigacion: `pointerEvents: 'none'` salvo cuando estamos en modo borrado.
- **Coords absolutas vs %**: si guardamos px y la card cambia de ancho (resize de columna), el sticker se sale. Guardar como % (`x,y ∈ [0,1]`).
- **Coleccion en chat tool agent**: si el chat agent expone tools que hacen `updateCard` con payload completo, debe respetar el campo `stickers` y no sobrescribirlo a null.
- **Migracion de BD en prod (laptop + casa)**: la migracion corre en cada arranque idempotente, OK. Pero confirmar antes en prod local del usuario.
- **WIP en kanban (issue 0058)**: este issue toca `app.md` igualmente — coordinar para no pisar el sync pendiente de `uses_functions`.

## Prerequisitos

- Confirmar las 7 decisiones de diseno con el usuario (Fase 1).
- TBD: rama `issue/0063-stickers` desde `master` actualizado.
