fad4006f60
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
74 lines
3.1 KiB
Markdown
74 lines
3.1 KiB
Markdown
---
|
|
id: "0092"
|
|
title: "kanban: archivo automatico para cards en columnas Done con +30 dias"
|
|
status: pendiente
|
|
type: feature
|
|
domain:
|
|
- kanban
|
|
scope: multi-app
|
|
priority: media
|
|
depends: []
|
|
blocks: []
|
|
related: []
|
|
created: 2026-05-17
|
|
updated: 2026-05-17
|
|
tags: []
|
|
---
|
|
|
|
## Problema
|
|
|
|
Las columnas Done acumulan decenas de cards completadas que ya no participan en el trabajo activo. Lastran el board, gastan render (incluso con el split memoizado por perf) y obligan a scroll horizontal hasta el infinito sin aportar valor diario.
|
|
|
|
## Solucion
|
|
|
|
Archive `archived_at` por card (independiente de papelera `deleted_at`). Card archivada sale del board pero sigue en BD. Cajon "Hecho (archivo)" en el sidebar bajo Papelera con boton para sacar una card de vuelta a su columna Done.
|
|
|
|
### Migration
|
|
|
|
`apps/kanban/backend/migrations/012_card_archived.sql`:
|
|
|
|
```sql
|
|
ALTER TABLE cards ADD COLUMN archived_at TEXT;
|
|
```
|
|
|
|
`NULL` = card activa. ISO timestamp = card archivada.
|
|
|
|
### Backend
|
|
|
|
- `Card.ArchivedAt *string` json `archived_at`.
|
|
- `ListCardsWithTime` filtra `archived_at IS NULL`.
|
|
- `ArchiveCard(id)`, `UnarchiveCard(id)`, `ListArchivedCards()`.
|
|
- `AutoArchiveDoneOlderThan(d)`: UPDATE cards SET archived_at=now WHERE archived_at IS NULL AND deleted_at IS NULL AND column_id IN (SELECT id FROM columns WHERE is_done=1) AND id IN (SELECT card_id FROM card_column_history WHERE exited_at IS NULL AND entered_at < cutoff).
|
|
- `maybeAutoArchive(db)` con throttle `archiveSweepEvery = 30m` y `archiveAfter = 30d`. Disparado lazy desde `handleGetBoard`.
|
|
- Rutas:
|
|
- `GET /api/archive`
|
|
- `POST /api/cards/{id}/archive`
|
|
- `POST /api/cards/{id}/unarchive`
|
|
|
|
### Frontend
|
|
|
|
- Tipo `Card.archived_at: string | null`.
|
|
- `api.listArchive()` / `api.archiveCard(id)` / `api.unarchiveCard(id)`.
|
|
- `App` state `archive`, `archiveOpen`, `reloadArchive`, `handleArchiveCard`, `handleUnarchiveCard`. `reloadArchive` se llama tras login + tras archivar/desarchivar.
|
|
- UI: cajon "Hecho (archivo)" bajo "Papelera" en el sidebar. Toggle similar (`Badge` con conteo, `IconChevronRight/Down`). Solo boton "Restaurar" (`IconArrowBackUp`). No hay "purgar permanentemente" desde el archivo — solo borrado via card normal en su columna.
|
|
- `KanbanCard`: nuevo `Menu.Item` "Archivar" (`IconArchive` teal) condicionado a `isDone && onArchive` antes del divisor + Borrar.
|
|
|
|
### Tests
|
|
|
|
- Playwright `e2e/archive.spec.ts`: archivar manual via menu de card en columna done, asserta desaparece del board, abre cajon "Hecho", des-archiva, asserta vuelve al board.
|
|
- Auto-archive 30d sin test e2e (timing real no es practico). Cubierto por audit manual del flag `archiveAfter` y la query SQL.
|
|
|
|
## Criterios de aceptacion
|
|
|
|
- [ ] Cards con +30 dias en columna Done se auto-archivan tras la primera lectura del board (max 30m).
|
|
- [ ] Cajon "Hecho (archivo)" visible bajo Papelera. Muestra conteo.
|
|
- [ ] Boton "Restaurar" devuelve la card a su columna sin reset de historial.
|
|
- [ ] Menu de card en columna Done muestra "Archivar" para archivado manual.
|
|
- [ ] Cards archivadas NO aparecen en `/api/board`.
|
|
- [ ] E2E `archive.spec.ts` pasa.
|
|
|
|
## Rama / commits
|
|
|
|
- Rama: `issue/0092-kanban-done-archive`
|
|
- Merge `--no-ff` a master.
|