Tablero Kanban
El tablero Kanban es el corazón del producto: drag & drop, multi-board, CRUD de columnas y tareas, toolbar de filtros y ordenación, historial de actividad y descripciones en markdown.
Rutas y boards
Sección titulada «Rutas y boards»/kanban → primer board del workspace (getBoardAction)/kanban/[boardId] → board concreto (getBoardByIdAction)El CRUD de boards (listar, crear, renombrar, eliminar) vive en la sección “Boards” de la sidebar:
- Crear: botón
+→createBoardAction - Renombrar: lápiz → input inline →
renameBoardAction - Eliminar: papelera → confirmación →
deleteBoardAction - Cada board enlaza a
/kanban/[boardId]y se marca como activo con bordeprimary.
Arquitectura
Sección titulada «Arquitectura»El tablero usa @dnd-kit para el drag & drop con tres niveles:
DndContext (KanbanBoard)├── Droppable (KanbanColumn × N)│ └── SortableContext│ └── useSortable (TaskCard × N)└── DragOverlay (copia visual mientras se arrastra)Por qué @dnd-kit
Sección titulada «Por qué @dnd-kit»- react-beautiful-dnd está deprecado (no soporta React 18+)
- @dnd-kit es la alternativa moderna con API similar
- Soporta React 19, es modular (core + sortable + utilities ~20KB), y permite DnD entre contenedores
Componentes
Sección titulada «Componentes»KanbanBoard
Sección titulada «KanbanBoard»components/kanban/KanbanBoard.tsx
Componente principal (client component). Obtiene el board (getBoardAction o getBoardByIdAction), los miembros con getWorkspaceMembersAction, y orquesta todo el estado.
| Prop | Tipo | Descripción |
|---|---|---|
params |
Promise<{ workspaceId: string; boardId?: string }> |
Parámetros de ruta |
Estado local:
board— estado con optimistic updates (se actualiza en cliente antes de la server action)activeTask— tarea arrastrada (para DragOverlay)members— miembros del workspace para asignacionessearchQuery/priorityFilter/assigneeFilter— filtros de la toolbarcolumnWidth/cardDensity/cardFontSize/showDescriptions/fillWidth— preferencias de vistacolumnSortState— ordenación por columna (default | priority | dueDate | title | createdAt, asc/desc)editingTask— tarea en edición (abreTaskFormDialog)
Preferencias de vista se persisten en localStorage bajo la clave syncro.kanban.view.v1.
Callbacks que pasa a las columnas:
| Callback | Server Action |
|---|---|
handleColumnNameChange |
updateColumnNameAction |
handleColorChange |
updateColumnColorAction |
handleDeleteColumn |
deleteColumnAction |
handleDeleteTask |
deleteTaskAction |
handlePriorityChange |
updateTaskAction |
handleAssigneeChange |
toggleAssigneeAction |
handleMoveToBacklog |
moveTaskToBacklogAction |
handleAddColumn |
createColumnAction |
handleEditTask |
abre TaskFormDialog (edit) |
KanbanToolbar
Sección titulada «KanbanToolbar»components/kanban/KanbanToolbar.tsx
Barra de controles por encima del tablero:
| Control | Comportamiento |
|---|---|
| Búsqueda | Filtra por título y descripción |
| Filtro prioridad | Multiselect LOW/MEDIUM/HIGH/URGENT con contador |
| Filtro asignado | Multiselect por miembro del workspace |
| Ancho de columna | compact / normal / wide / extraWide |
| Densidad de tarjeta | compact / normal / roomy |
| Tamaño de letra | compact / normal / large / extraLarge |
| Descripciones | Muestra/oculta descripciones en las tarjetas |
| Fill width | Columnas ocupan todo el ancho disponible |
| Limpiar filtros | Solo aparece si hay filtros activos |
Los menús desplegables usan createPortal y se posicionan con position: fixed bajo el trigger.
KanbanColumn
Sección titulada «KanbanColumn»components/kanban/KanbanColumn.tsx
Columna dropeable con menú contextual, ordenación propia y estados de filtrado.
| Prop | Tipo | Descripción |
|---|---|---|
id |
string |
ID de la columna |
name |
string |
Nombre visible |
color |
string | null |
Color de fondo del header |
tasks |
TaskData[] |
Tareas (ya filtradas/ordenadas) |
workspaceId |
string |
Para server actions |
members |
Member[] |
Miembros del workspace |
searchQuery / priorityFilter / assigneeFilter |
— | Estado de filtrado (para vaciado de columna) |
sortBy / sortDir |
— | Ordenación actual de la columna |
onSortChange |
(columnId, field, dir) => void |
Cambiar ordenación |
widthClass / densityClass / cardDensity / fontSize |
— | Preferencias de vista |
onMoveToBacklog |
(taskId) => void |
Mover tarea al backlog |
Características:
- Header con color dinámico, nombre editable inline, contador de tareas y menú de ordenación
- Menú (
MoreHorizontal) con selector de 7 colores, ordenación y opción de eliminar - Confirm dialog al eliminar si tiene tareas
- Botones “Añadir tarea” (
TaskFormDialog) y “Añadir desde backlog” (AddBacklogTaskDialog) useDroppablede @dnd-kit con feedback visual al hoverSortableContextcon estrategia vertical
TaskCard
Sección titulada «TaskCard»components/kanban/TaskCard.tsx
Card de tarea arrastrable (sortable).
| Prop | Tipo | Descripción |
|---|---|---|
id |
string |
ID de la tarea |
title |
string |
Título |
description |
string | null |
Descripción (markdown, expandible) |
priority |
"LOW" | "MEDIUM" | "HIGH" | "URGENT" |
Prioridad |
creatorName |
string | null |
Creador de la tarea |
createdAt |
Date | string | null |
Fecha de creación |
assigneeNames / assigneeIds |
string[] |
Asignados |
members / workspaceId |
— | Para dropdown de asignación |
isOverlay |
boolean |
Modo copia (DragOverlay) |
showDescriptions / density / fontSize |
— | Preferencias de vista |
onEdit |
(taskId) => void |
Abrir edición |
onMoveToBacklog |
(taskId) => void |
Mover a backlog |
Características:
useSortablede @dnd-kit con feedback visual durante drag- Dropdown inline de prioridad LOW→MEDIUM→HIGH→URGENT
- Dropdown de asignación con avatares (toggle on/off) y
AvatarStack - Descripción en markdown expandible (icono chevron)
- Creador + fecha de creación, confirm inline al eliminar, editar y mover a backlog
TaskFormDialog
Sección titulada «TaskFormDialog»components/tasks/TaskFormDialog.tsx
Modal de crear y editar tareas (sustituye al antiguo CreateTaskDialog).
| Prop | Tipo | Descripción |
|---|---|---|
mode |
"create" | "edit" |
Modo del modal |
workspaceId |
string |
Para server action |
columnId |
string (solo create) |
Columna destino |
task |
Task (solo edit) |
Tarea a editar |
members |
Member[] |
Miembros para asignación |
onClose / onSaved |
() => void |
Cerrar / refrescar |
Campos:
- Título (requerido, max 200)
- Descripción con MarkdownEditor (vista previa + modal de ayuda)
- Prioridad (Baja/Media/Alta/Urgente)
- Asignado (dropdown con avatares)
- Fecha inicio / fecha límite (
dueDate,startDate) - Horas estimadas (
estimatedHours)
TaskActivityDialog
Sección titulada «TaskActivityDialog»components/kanban/TaskActivityDialog.tsx
Modal con el historial de actividad de una tarea. Cada entrada muestra el usuario, el tipo de acción (TaskAction con icono y color) y, cuando aplica, el cambio de campo (fieldName/oldValue/newValue). Carga con getTaskActivityAction.
AddBacklogTaskDialog
Sección titulada «AddBacklogTaskDialog»components/kanban/AddBacklogTaskDialog.tsx
Modal para añadir una tarea del backlog a una columna. Lista las tareas sin columna (getUnallocatedTasksAction), permite buscar, y al confirmar mueve la tarea con moveTaskAction.
ConfirmDialog
Sección titulada «ConfirmDialog»components/kanban/ConfirmDialog.tsx
Modal de confirmación reutilizable.
| Prop | Tipo | Default | Descripción |
|---|---|---|---|
title |
string |
— | Título del modal |
message |
string |
— | Mensaje descriptivo |
confirmLabel |
string |
"Eliminar" |
Texto del botón confirmar |
cancelLabel |
string |
"Cancelar" |
Texto del botón cancelar |
destructive |
boolean |
true |
Estilo rojo de peligro |
onConfirm |
() => void |
— | Acción al confirmar |
onCancel |
() => void |
— | Acción al cancelar |
KanbanSkeleton
Sección titulada «KanbanSkeleton»components/kanban/KanbanSkeleton.tsx
Skeleton de carga del tablero mientras se obtienen los datos.
Flujo de drag & drop
Sección titulada «Flujo de drag & drop»- DragStart: se identifica la tarea arrastrada y se muestra en
DragOverlay - DragMove: auto-scroll horizontal si el puntero se acerca a los bordes del contenedor
- DragOver: optimistic update (con debounce de 50ms para evitar el bucle infinito cuando el puntero alterna entre columnas A/B cada frame)
- DragEnd: se calcula la posición final, se aplica el optimistic update y se llama a
moveTaskActioncon la nueva columna y posición - Si no hay cambio real (misma columna y posición), no se llama a la server action
Fixes aplicados (v0.5.0):
bugfix/kanban-drag-infinite-loop(#117): el drag-over se debouncea y solo muta estado cuando cambia de columnafix/kanban-drag-drop-gap(#113): el hueco de arrastre sigue al puntero al mover tarjetas entre columnas- Estrategia de ordenación
columnAwareVerticalSortingStrategy(lib/kanban/columnSortingStrategy.ts)
Las posiciones se normalizan (0, 1, 2, ...) tanto en servidor como en cliente.
Filtros y ordenación
Sección titulada «Filtros y ordenación»Los filtros (búsqueda, prioridad, asignado) se aplican antes de ordenar en getSortedTasks. El orden por defecto es la position; alternativas: prioridad (LOW→URGENT), dueDate, title o createdAt, con dirección asc/desc por columna. Cuando una columna queda vacía por filtros, se muestra un mensaje con “Limpiar filtros”.
Descripciones en markdown
Sección titulada «Descripciones en markdown»Las descripciones se escriben y renderizan en markdown:
MarkdownEditor— editor con barra de herramientas y vista previaMarkdownView— render conreact-markdown+remark-gfm+rehype-rawMarkdownHelpModal— ayuda de sintaxis
Fix #118: las tablas markdown ya no desbordan la tarjeta (el contenedor controla el overflow).
Validaciones
Sección titulada «Validaciones»| Regla | Lugar |
|---|---|
| Mínimo 3 columnas por board | Server action deleteColumnAction |
| Máximo 7 columnas por board | UI deshabilita botón + server validateCanAddColumn |
| Columna no vacía → confirm dialog antes de eliminar | UI |
| Título de tarea requerido (max 200) | Zod en createTaskAction |
estimatedHours positivo |
Zod en createTaskAction / updateTaskAction |
| Solo miembros del workspace pueden crear/mover tareas | Server action verifica membresía |
Todas las claves bajo el namespace kanban en messages/{es,en}.json (más backlog para el diálogo de tareas y markdown para el editor):
| Clave | Propósito |
|---|---|
kanban.title |
Título del tablero |
kanban.addColumn |
Botón añadir columna |
kanban.newColumnName |
Nombre por defecto de nueva columna |
kanban.columnColor |
Selector de color |
kanban.editColumnName |
Tooltip editar nombre |
kanban.deleteColumn |
Eliminar columna |
kanban.confirmDeleteColumnTitle/Message |
Confirmación con conteo de tareas |
kanban.addTask / addTaskFromBacklog |
Añadir tarea / desde backlog |
kanban.noTasks |
Estado vacío de columna |
kanban.noBoardTitle/Desc + createBoard |
Estado sin tablero |
kanban.searchTasks / filterPriority / filterAssignee / clearFilters |
Toolbar |
kanban.columnWidth / width* |
Ancho de columna |
kanban.cardDensity / density* |
Densidad |
kanban.fontSize / font* |
Tamaño de letra |
kanban.showDescriptions / fillWidth |
Controles de vista |
kanban.priority* / assignee* / unassigned / noMembers |
Prioridades y asignaciones |
kanban.boards / newBoard / renameBoard / deleteBoard |
Sección de boards en sidebar |
kanban.create / creating / cancel / loading |
Diálogos |