// lib/tasksService.ts import { dolibarrFetch } from "./dolibarrClient"; import { DolibarrTask, Task, mapDolibarrTask, CreateTaskData, UpdateTaskData } from "@/types/task"; /** * Obtener todas las tareas de un proyecto específico * * Nota: El endpoint /projects/{id}/tasks de Dolibarr no devuelve tareas correctamente, * por lo que obtenemos todas las tareas y filtramos por fk_project en el cliente. * Se usa limit=10000 para evitar problemas de paginación por defecto (100). */ export async function getTasksByProjectId(projectId: number): Promise { try { // Usar limit alto + sortfield para obtener todas las tareas y evitar paginación const dolibarrTasks: DolibarrTask[] = await dolibarrFetch( `tasks?limit=10000&sortfield=t.rowid&sortorder=ASC` ); // Si no hay tareas, devolver array vacío if (!dolibarrTasks || !Array.isArray(dolibarrTasks)) { return []; } // Filtrar tareas que pertenecen a este proyecto const projectTasks = dolibarrTasks.filter( task => String(task.fk_project) === String(projectId) ); // Mapear las tareas al formato de la UI return projectTasks.map(mapDolibarrTask); } catch (error) { console.error('Error fetching tasks for project:', projectId, error); // Si el error es 404 (no hay tareas), devolver array vacío if (error instanceof Error && error.message.includes('404')) { return []; } throw error; } } /** * Obtener todas las tareas (sin filtro de proyecto) * Endpoint: /tasks */ export async function getAllTasks(): Promise { try { const dolibarrTasks: DolibarrTask[] = await dolibarrFetch( 'tasks?limit=10000&sortfield=t.rowid&sortorder=ASC' ); if (!dolibarrTasks || !Array.isArray(dolibarrTasks)) { return []; } return dolibarrTasks.map(mapDolibarrTask); } catch (error) { console.error('Error fetching all tasks:', error); throw error; } } /** * Obtener una tarea específica por ID * Endpoint: /tasks/{id} */ export async function getTaskById(taskId: number): Promise { try { const dolibarrTask: DolibarrTask = await dolibarrFetch(`tasks/${taskId}`); return mapDolibarrTask(dolibarrTask); } catch (error) { console.error('Error fetching task:', taskId, error); return null; } } /** * Obtener datos crudos de Dolibarr para una tarea */ export async function getDolibarrTaskById(taskId: number): Promise { try { return await dolibarrFetch(`tasks/${taskId}`); } catch (error) { console.error('Error fetching Dolibarr task:', taskId, error); return null; } } /** * Calcular estadísticas de las tareas de un proyecto */ export function calculateTaskStats(tasks: Task[]) { const total = tasks.length; const completed = tasks.filter(t => t.status === '2').length; const inProgress = tasks.filter(t => t.status === '1').length; const draft = tasks.filter(t => t.status === '0').length; const totalPlannedHours = tasks.reduce((sum, t) => sum + t.plannedHours, 0); const totalWorkedHours = tasks.reduce((sum, t) => sum + t.workedHours, 0); const avgProgress = total > 0 ? Math.round(tasks.reduce((sum, t) => sum + t.progress, 0) / total) : 0; const highPriority = tasks.filter(t => t.priority === '3').length; const overdue = tasks.filter(t => { if (!t.endDate && !t.plannedEndDate) return false; const endDate = t.endDate || t.plannedEndDate; return endDate && new Date(endDate) < new Date() && t.status !== '2'; }).length; return { total, completed, inProgress, draft, totalPlannedHours, totalWorkedHours, avgProgress, highPriority, overdue, completionRate: total > 0 ? Math.round((completed / total) * 100) : 0, }; } // ==================== FUNCIONES DE ESCRITURA (CREATE/UPDATE/DELETE) ==================== /** * Crear una nueva tarea * Endpoint: POST /tasks * @returns La tarea creada con su ID */ export async function createTask(data: CreateTaskData): Promise { try { // Limpiar datos undefined/null para la API const cleanData = Object.fromEntries( Object.entries(data).filter(([, v]) => v !== undefined && v !== null && v !== '') ); const response = await dolibarrFetch('tasks', { method: 'POST', body: JSON.stringify(cleanData), }); // Dolibarr devuelve el ID de la tarea creada const taskId = typeof response === 'object' ? response.id || response : response; // Obtener la tarea completa const createdTask = await getTaskById(Number(taskId)); if (!createdTask) { throw new Error('No se pudo obtener la tarea creada'); } return createdTask; } catch (error) { console.error('Error creating task:', error); throw error; } } /** * Actualizar una tarea existente * Endpoint: PUT /tasks/{id} * @returns La tarea actualizada */ export async function updateTask(taskId: number, data: UpdateTaskData): Promise { try { // Limpiar datos undefined/null para la API const cleanData = Object.fromEntries( Object.entries(data).filter(([, v]) => v !== undefined && v !== null) ); await dolibarrFetch(`tasks/${taskId}`, { method: 'PUT', body: JSON.stringify(cleanData), }); // Obtener la tarea actualizada const updatedTask = await getTaskById(taskId); if (!updatedTask) { throw new Error('No se pudo obtener la tarea actualizada'); } return updatedTask; } catch (error) { console.error('Error updating task:', taskId, error); throw error; } } /** * Eliminar una tarea * Endpoint: DELETE /tasks/{id} */ export async function deleteTask(taskId: number): Promise { try { await dolibarrFetch(`tasks/${taskId}`, { method: 'DELETE', }); } catch (error) { console.error('Error deleting task:', taskId, error); throw error; } } /** * Validar (cambiar estado a 1) una tarea * Endpoint: POST /tasks/{id}/validate */ export async function validateTask(taskId: number): Promise { try { await dolibarrFetch(`tasks/${taskId}/validate`, { method: 'POST', body: JSON.stringify({ notrigger: 0 }), }); const task = await getTaskById(taskId); if (!task) { throw new Error('No se pudo obtener la tarea validada'); } return task; } catch (error) { console.error('Error validating task:', taskId, error); throw error; } }