Kanban de Tareas + Attachments R2
User value
Módulo de Tareas como sistema completo de seguimiento visual. Equipo organiza trabajo en columnas Kanban (Pendiente / En curso / Completada), ve badge 📎 N con número de adjuntos por tarea, abre galería de imágenes o PDFs desde la tarjeta sin entrar al modal. Campo Resolución separa respuesta/decisión final de la descripción original.
Cómo usarla
- Vista Kanban: columnas a pantalla completa con borde izquierdo de color por estado (naranja pendiente, azul en curso, verde completada). Tarjetas con priority pill + título + badge adjuntos + assignee en una fila compacta.
- Filtro LED: filtra por assignee/status/priority desde cabecera.
- Adjuntar: modal edición (o creación), sección “Adjuntos” con ”+ Subir archivo”. Soporta PNG/JPG/WEBP/GIF/PDF, máx 10 MB.
- Galería: click
📎abre visor directamente. Navegación flechas‹/›o←/→con contadorn/total. Un único archivo salta al preview sin galería. - Preview: imágenes pantalla completa, PDFs iframe.
Esccierra preview antes que modal.
Implementación
Backend (CF Pages Functions + D1 + R2):
functions/api/tasks/index.ts: SELECT con subquery(SELECT COUNT(*) FROM task_attachments WHERE task_id = tasks.id) AS attachment_countevita round-trip extra.functions/api/tasks/[id]/index.ts: PUT gestiona timestamps transición (started_at,completed_at) solo en primer paso a cada fase; DELETE borra blobs R2 (best-effort) antes de que FK CASCADE elimine filas.functions/api/tasks/[id]/attachments.ts: GET sin exponer r2_key; POST aceptamultipart/form-data, valida MIME contra whitelist, genera keytasks/{id}/{uuid}.{ext}, escribe R2 antes de INSERT D1.functions/api/attachments/[id].ts: sirve blob con content-type;?download=1fuerzaContent-Disposition: attachment;410 Gonesi row existe pero objeto R2 no.migrations/0011_create_task_attachments.sql: tabla con FKtask_id CASCADE,r2_key UNIQUE, índices portask_idyuploaded_at DESC.wrangler.toml: bindingTASK_ATTACHMENTS→ buckettask-attachments.
Frontend:
TaskModal.tsx: tiposTask.attachment_count?yAttachment. Grid miniaturasauto-fill 120px; imágenes<img>, PDFs icono📄+ nombre.TaskTableView.tsx+ Kanban: badge📎 Nsiattachment_count > 0.
Decisiones
- R2 sobre D1 BLOB: adjuntos en R2 para no saturar límite D1 con binarios. D1 solo metadata + key.
- Whitelist MIME estricta: PNG/JPG/WEBP/GIF/PDF. Ampliar = editar Set en
attachments.ts. - Best-effort DELETE R2: si limpieza falla, no aborta. Se acepta blobs huérfanos antes que bloquear borrado.
attachment_counten listado: subquery correlacionada evita N+1 fetches.- Timestamps inmutables por fase:
started_at/completed_atsolo siNULL, preservando primera transición aunque tarea vuelva atrás.
Commits relacionados
| Hash | Mensaje |
|---|---|
fe8a912 | feat(tasks): attachments R2 con galería en modal |
ee63b9a | fix: mover [id].ts a [id]/index.ts para rutas anidadas |
9533304 | feat: rediseño Kanban + timestamps + filtro LEDs + modal 50% |
03dc126 | feat: adjuntar archivos desde modal de crear |
4dcddf0 | feat: campo Resolución separado de descripción |
c8e995d | feat: badge 📎 abre galería directa |
8443656 | feat: flechas ‹/› + ←/→ + contador n/total |
3a4b887 | feat: visor único sin rejilla miniaturas |
35b4e74 | style: textos más grandes en tarjeta Kanban |
be1423a | fix: badge 📎 también en Kanban y Calendar |
d51467d | fix: badge se actualiza tras subir/borrar |
95177f3 | fix: 1 archivo solo preview sin galería |
a79418b | fix: cerrar preview cierra galería detrás |
b470f8b | fix: preview via portal + stamps gated |
b53ee63 | fix: preview overlay visible + timeline compacta |
0c390ca | fix: chip Fin aparece sin refrescar |
Véase también
- [[concept—biblioteca—supercontexto]]
- [[feature—workspace—tasks-timeline]] — Timeline chips + Badge adjuntos en el Kanban
- [[feature—workspace—refresh-ui-2026]] — Refresh UI workspace donde vive el Kanban