Ir al contenido

Backend del Kanban

Server actions para la gestión del tablero Kanban, columnas y tareas. Siguen el patrón de Clean Architecture: Action → Service → Repository.

Capa Archivo
Action server/board/actions/board-actions.ts
Service server/board/service/board-service.ts
Repository server/board/repository/board-repository.ts
Action Descripción
getBoardAction(workspaceId) Obtiene el primer board del workspace con columnas + tareas + asignados. Devuelve null si no es miembro
getBoardByIdAction(boardId) Obtiene un board concreto por ID (usado en /kanban/[boardId]). Verifica membresía
listBoardsAction(workspaceId) Lista los boards del workspace (para la sección “Boards” de la sidebar)
createBoardAction(workspaceId, locale) Crea un board con 3 columnas por defecto (To Do, In Progress, Done). Verifica membresía
renameBoardAction(boardId, name) Renombra un board. Valida nombre 1-50 caracteres
deleteBoardAction(boardId) Elimina un board y devuelve { workspaceId, remainingCount }

El board se obtiene con eager loading de toda la jerarquía:

Board → Column[] → Task[] → TaskAssignee[] → User
→ Creator (User)
→ Activities (TaskActivity[])

Al crear un board, se generan automáticamente 3 columnas con colores por defecto.

import { createBoardAction } from "@/server/board/actions/board-actions";
const result = await createBoardAction(workspaceId, locale);
if (result.success) {
// Board creado con columnas: To Do, In Progress, Done
router.replace(
`/${locale}/workspaces/${workspaceId}/kanban/${result.board.id}`,
);
}

Nota: Esta acción se usa desde el empty state del KanbanBoard (cuando un workspace no tiene ningún board) y desde el botón + de la sección “Boards” de la sidebar.

Capa Archivo
Action server/column/actions/column-actions.ts
Service server/column/service/column-service.ts
Repository server/column/repository/column-repository.ts
Action Descripción
createColumnAction(boardId, workspaceId, name) Crea columna. Valida máximo 7 columnas. Asigna color y posición automáticos
updateColumnNameAction(columnId, workspaceId, name) Renombra columna
updateColumnColorAction(columnId, workspaceId, color) Cambia color del header
deleteColumnAction(columnId, boardId, workspaceId) Elimina columna. Valida mínimo 3 columnas. Normaliza posiciones
reorderColumnsAction(columns[], workspaceId) Reordena columnas (reservado para futuro)
Regla Implementación
Máximo 7 columnas column-service.ts: validateCanAddColumn()
Mínimo 3 columnas column-actions.ts: deleteColumnAction()
Solo miembros del workspace Action verifica membresía con findUserWithProfile
Capa Archivo
Action server/task/actions/task-actions.ts
Service server/task/service/task-service.ts
Repository server/task/repository/task-repository.ts
Action Descripción
createTaskAction(columnId, workspaceId, data) Crea tarea con validación Zod. Asigna posición automática. Asigna usuario si se provee
updateTaskAction(taskId, workspaceId, data) Actualiza título, descripción, prioridad, dueDate, startDate y estimatedHours
moveTaskAction(taskId, newColumnId, newPosition, workspaceId) Mueve tarea entre columnas. Normaliza posiciones en origen y destino
moveTaskToBacklogAction(taskId, workspaceId) Mueve una tarea al backlog (columnId = null)
deleteTaskAction(taskId, workspaceId) Elimina tarea. Normaliza posiciones de la columna
toggleAssigneeAction(taskId, userId, workspaceId) Asigna/desasigna usuario (toggle)
Regla Implementación
Título requerido (1-200 chars) Zod schema en createTaskAction
Descripción opcional (max 2000) Zod schema en createTaskAction
Prioridad: LOW | MEDIUM | HIGH | URGENT Tipo TaskPriority de Prisma + Zod enum
estimatedHours positivo Zod schema en createTaskAction / updateTaskAction
Solo miembros del workspace Action verifica membresía

Las tareas usan un campo position: Float reordenado como enteros consecutivos (0, 1, 2, ...). Cada vez que se mueve o elimina una tarea, se normalizan las posiciones de la columna afectada. Una tarea en backlog tiene columnId: null y position: 0.

Cada cambio en una tarea registra un TaskActivity (enum TaskAction: TASK_CREATED, TASK_UPDATED, TASK_MOVED, TASK_ASSIGNED, TASK_UNASSIGNED, TASK_DELETED). Con fieldName/oldValue/newValue se puede mostrar el cambio exacto (“prioridad: MEDIUM → HIGH”).

  • Dónde vive: server/task/service/task-service.ts registra el historial dentro de las mismas operaciones.
  • Cómo se lee: getBoardByIdWithDetails incluye activities de cada tarea para el TaskActivityDialog del frontend.
Action Archivo Descripción
getWorkspaceMembersAction(workspaceId) server/workspace/actions/workspace-actions.ts Lista miembros con rol para asignaciones
deleteWorkspaceAction(workspaceId, locale) server/workspace/actions/workspace-actions.ts Elimina workspace. Solo rol OWNER

Todas las server actions siguen esta estructura:

export async function ejemploAction(param: string) {
const userId = await requireAuthedUserId();
const user = await findUserWithProfile(userId);
const isMember = user?.memberships.some(
(m) => m.workspace.id === workspaceId,
);
if (!isMember) return { success: false, error: "NOT_MEMBER" };
try {
// lógica de negocio
revalidatePath("/", "layout");
return { success: true, data };
} catch (e) {
return { success: false, error: "ERROR_CODE" };
}
}
  • Validación de membresía en cada action (no hay middlewares)
  • Optimistic updates en el cliente: la UI se actualiza antes de la respuesta del servidor
  • revalidatePath para mantener el cache de Next.js sincronizado
  • Manejo de errores con códigos tipados (CreateColumnResult, UpdateColumnResult, etc.)