2026-01-09 17:39:19 +00:00
|
|
|
// types/project.ts
|
2026-03-27 08:27:42 +00:00
|
|
|
export type ProjectStatus = '0' | '1' | '2'; // 0: borrador, 1: activo, 2: cerrado
|
2025-12-11 10:50:09 +00:00
|
|
|
|
2026-01-09 17:39:19 +00:00
|
|
|
// Interface para los datos crudos que vienen de Dolibarr
|
|
|
|
|
export interface DolibarrProject {
|
|
|
|
|
id: string | number;
|
|
|
|
|
ref: string;
|
|
|
|
|
title: string;
|
|
|
|
|
description: string;
|
2026-01-28 12:00:17 +00:00
|
|
|
date_start: number | null; // timestamp (puede ser null)
|
|
|
|
|
date_end: number | null; // timestamp (puede ser null)
|
2026-01-09 17:39:19 +00:00
|
|
|
status: string;
|
|
|
|
|
statut: string;
|
|
|
|
|
budget_amount: string;
|
|
|
|
|
opp_amount: string; // opportunity amount (presupuesto estimado)
|
|
|
|
|
opp_percent: string; // porcentaje de progreso
|
|
|
|
|
user_author_id: string | number;
|
|
|
|
|
thirdparty_name?: string;
|
|
|
|
|
socid?: string | number;
|
|
|
|
|
public: string | number;
|
|
|
|
|
usage_opportunity: string | number;
|
|
|
|
|
usage_task: string | number;
|
2026-01-28 12:00:17 +00:00
|
|
|
date_c: number | null; // fecha creación (puede ser null)
|
|
|
|
|
date_m: number | null; // fecha modificación (puede ser null)
|
2026-01-09 17:39:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Interface para usar en la UI (formato normalizado)
|
2025-12-11 10:50:09 +00:00
|
|
|
export interface Project {
|
|
|
|
|
id: number;
|
2026-01-09 17:39:19 +00:00
|
|
|
ref: string;
|
2025-12-11 10:50:09 +00:00
|
|
|
name: string;
|
|
|
|
|
client: string;
|
|
|
|
|
status: ProjectStatus;
|
|
|
|
|
progress: number;
|
|
|
|
|
budget: number;
|
|
|
|
|
spent: number;
|
|
|
|
|
startDate: string;
|
|
|
|
|
endDate: string;
|
2026-01-09 17:39:19 +00:00
|
|
|
description: string;
|
|
|
|
|
createdAt: string;
|
|
|
|
|
updatedAt: string;
|
2026-01-31 13:50:07 +00:00
|
|
|
socid?: number; // ID del tercero/cliente
|
2026-01-09 17:39:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Configuración de estados - AHORA CON FUNCIÓN QUE RETORNA LAS CLASES COMPLETAS
|
|
|
|
|
export const STATUS_CONFIG: Record<ProjectStatus, { label: string; getColorClasses: () => string }> = {
|
|
|
|
|
'0': {
|
|
|
|
|
label: "Borrador",
|
|
|
|
|
getColorClasses: () => "bg-gray-100 text-gray-800 border-gray-200"
|
|
|
|
|
},
|
|
|
|
|
'1': {
|
2026-03-27 08:27:42 +00:00
|
|
|
label: "Activo",
|
2026-01-09 17:39:19 +00:00
|
|
|
getColorClasses: () => "bg-blue-100 text-blue-800 border-blue-200"
|
|
|
|
|
},
|
|
|
|
|
'2': {
|
|
|
|
|
label: "Cerrado",
|
|
|
|
|
getColorClasses: () => "bg-green-100 text-green-800 border-green-200"
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Función helper para obtener las clases de color según el estado
|
|
|
|
|
export function getStatusColorClasses(status: ProjectStatus): string {
|
|
|
|
|
const statusMap: Record<ProjectStatus, string> = {
|
|
|
|
|
'0': 'bg-gray-100 text-gray-800 border-gray-200',
|
|
|
|
|
'1': 'bg-blue-100 text-blue-800 border-blue-200',
|
|
|
|
|
'2': 'bg-green-100 text-green-800 border-green-200'
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return statusMap[status] || 'bg-gray-100 text-gray-800 border-gray-200';
|
2025-12-11 10:50:09 +00:00
|
|
|
}
|
|
|
|
|
|
2026-01-09 17:39:19 +00:00
|
|
|
// Función helper para convertir datos de Dolibarr a nuestro formato
|
|
|
|
|
export function mapDolibarrProject(dolibarr: DolibarrProject): Project {
|
|
|
|
|
const budget = parseFloat(dolibarr.budget_amount || '0');
|
|
|
|
|
const oppAmount = parseFloat(dolibarr.opp_amount || '0');
|
|
|
|
|
const progress = parseFloat(dolibarr.opp_percent || '0');
|
|
|
|
|
|
2026-01-28 12:00:17 +00:00
|
|
|
// Helper para convertir timestamp de Dolibarr (segundos) a fecha ISO
|
|
|
|
|
const timestampToDate = (timestamp: number | null | undefined): string => {
|
|
|
|
|
if (!timestamp || timestamp === 0) {
|
|
|
|
|
return new Date().toISOString().split('T')[0]; // Fecha actual por defecto
|
|
|
|
|
}
|
|
|
|
|
return new Date(timestamp * 1000).toISOString().split('T')[0];
|
|
|
|
|
};
|
|
|
|
|
|
2026-01-09 17:39:19 +00:00
|
|
|
return {
|
|
|
|
|
id: typeof dolibarr.id === 'string' ? parseInt(dolibarr.id) : dolibarr.id,
|
|
|
|
|
ref: dolibarr.ref,
|
|
|
|
|
name: dolibarr.title,
|
|
|
|
|
client: dolibarr.thirdparty_name || 'Sin cliente',
|
|
|
|
|
status: (dolibarr.status || dolibarr.statut) as ProjectStatus,
|
|
|
|
|
progress: progress,
|
|
|
|
|
budget: oppAmount || budget,
|
|
|
|
|
spent: (oppAmount || budget) * (progress / 100), // Calcular gastado según progreso
|
2026-01-28 12:00:17 +00:00
|
|
|
startDate: timestampToDate(dolibarr.date_start),
|
|
|
|
|
endDate: timestampToDate(dolibarr.date_end),
|
2026-01-09 17:39:19 +00:00
|
|
|
description: dolibarr.description || '',
|
2026-01-28 12:00:17 +00:00
|
|
|
createdAt: timestampToDate(dolibarr.date_c),
|
|
|
|
|
updatedAt: timestampToDate(dolibarr.date_m),
|
2026-01-31 13:50:07 +00:00
|
|
|
socid: dolibarr.socid ? (typeof dolibarr.socid === 'string' ? parseInt(dolibarr.socid) : dolibarr.socid) : undefined,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ==================== TIPOS PARA API DE CREAR/ACTUALIZAR ====================
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Datos para crear un nuevo proyecto
|
|
|
|
|
* Campos requeridos por Dolibarr: ref, title
|
|
|
|
|
*/
|
|
|
|
|
export interface CreateProjectData {
|
|
|
|
|
ref: string; // Referencia única (requerido)
|
|
|
|
|
title: string; // Nombre del proyecto (requerido)
|
|
|
|
|
description?: string;
|
|
|
|
|
date_start?: number; // timestamp en segundos
|
|
|
|
|
date_end?: number; // timestamp en segundos
|
|
|
|
|
opp_amount?: number; // presupuesto
|
|
|
|
|
opp_percent?: number; // progreso (0-100)
|
|
|
|
|
budget_amount?: number;
|
|
|
|
|
socid?: number; // ID del tercero/cliente
|
|
|
|
|
public?: number; // 0: privado, 1: público
|
|
|
|
|
usage_opportunity?: number;
|
|
|
|
|
usage_task?: number;
|
|
|
|
|
usage_bill_time?: number;
|
|
|
|
|
usage_organize_event?: number;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Datos para actualizar un proyecto existente
|
|
|
|
|
* Todos los campos son opcionales
|
|
|
|
|
*/
|
|
|
|
|
export interface UpdateProjectData {
|
|
|
|
|
ref?: string;
|
|
|
|
|
title?: string;
|
|
|
|
|
description?: string;
|
|
|
|
|
date_start?: number; // timestamp en segundos
|
|
|
|
|
date_end?: number; // timestamp en segundos
|
|
|
|
|
opp_amount?: number; // presupuesto
|
|
|
|
|
opp_percent?: number; // progreso (0-100)
|
|
|
|
|
budget_amount?: number;
|
|
|
|
|
socid?: number; // ID del tercero/cliente
|
|
|
|
|
status?: ProjectStatus;
|
|
|
|
|
public?: number;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Interface para el formulario de proyectos (UI)
|
|
|
|
|
* Usa formatos amigables
|
|
|
|
|
*/
|
|
|
|
|
export interface ProjectFormData {
|
|
|
|
|
name: string;
|
|
|
|
|
description: string;
|
|
|
|
|
status: ProjectStatus;
|
|
|
|
|
progress: number;
|
|
|
|
|
budget: number;
|
|
|
|
|
startDate: string; // YYYY-MM-DD
|
|
|
|
|
endDate: string; // YYYY-MM-DD
|
|
|
|
|
socid?: number; // ID del cliente
|
|
|
|
|
isPublic: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Helper para convertir fecha string a timestamp (segundos)
|
|
|
|
|
export function dateStringToTimestamp(dateStr: string | null | undefined): number | undefined {
|
|
|
|
|
if (!dateStr || dateStr === '') return undefined;
|
|
|
|
|
const date = new Date(dateStr);
|
|
|
|
|
if (isNaN(date.getTime())) return undefined;
|
|
|
|
|
return Math.floor(date.getTime() / 1000);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Helper para generar una referencia única para proyectos
|
|
|
|
|
function generateProjectRef(): string {
|
|
|
|
|
const timestamp = Date.now().toString(36).toUpperCase();
|
|
|
|
|
const random = Math.random().toString(36).substring(2, 6).toUpperCase();
|
|
|
|
|
return `PJ-${timestamp}-${random}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Convierte datos del formulario UI al formato para crear proyecto en Dolibarr
|
|
|
|
|
*/
|
|
|
|
|
export function formDataToCreateProject(formData: ProjectFormData): CreateProjectData {
|
|
|
|
|
return {
|
|
|
|
|
ref: generateProjectRef(),
|
|
|
|
|
title: formData.name,
|
|
|
|
|
description: formData.description || undefined,
|
|
|
|
|
date_start: dateStringToTimestamp(formData.startDate),
|
|
|
|
|
date_end: dateStringToTimestamp(formData.endDate),
|
|
|
|
|
opp_amount: formData.budget > 0 ? formData.budget : undefined,
|
|
|
|
|
opp_percent: formData.progress,
|
|
|
|
|
budget_amount: formData.budget > 0 ? formData.budget : undefined,
|
|
|
|
|
socid: formData.socid || undefined,
|
|
|
|
|
public: formData.isPublic ? 1 : 0,
|
|
|
|
|
usage_task: 1, // Habilitar tareas por defecto
|
|
|
|
|
usage_opportunity: 1,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Convierte datos del formulario UI al formato para actualizar proyecto en Dolibarr
|
|
|
|
|
*/
|
|
|
|
|
export function formDataToUpdateProject(formData: Partial<ProjectFormData>): UpdateProjectData {
|
|
|
|
|
const updateData: UpdateProjectData = {};
|
|
|
|
|
|
|
|
|
|
if (formData.name !== undefined) updateData.title = formData.name;
|
|
|
|
|
if (formData.description !== undefined) updateData.description = formData.description;
|
|
|
|
|
if (formData.status !== undefined) updateData.status = formData.status;
|
|
|
|
|
if (formData.progress !== undefined) updateData.opp_percent = formData.progress;
|
|
|
|
|
if (formData.budget !== undefined) {
|
|
|
|
|
updateData.opp_amount = formData.budget;
|
|
|
|
|
updateData.budget_amount = formData.budget;
|
|
|
|
|
}
|
|
|
|
|
if (formData.startDate !== undefined) {
|
|
|
|
|
updateData.date_start = dateStringToTimestamp(formData.startDate);
|
|
|
|
|
}
|
|
|
|
|
if (formData.endDate !== undefined) {
|
|
|
|
|
updateData.date_end = dateStringToTimestamp(formData.endDate);
|
|
|
|
|
}
|
|
|
|
|
if (formData.socid !== undefined) {
|
|
|
|
|
updateData.socid = formData.socid;
|
|
|
|
|
}
|
|
|
|
|
if (formData.isPublic !== undefined) {
|
|
|
|
|
updateData.public = formData.isPublic ? 1 : 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return updateData;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Convierte un proyecto existente a datos del formulario
|
|
|
|
|
*/
|
|
|
|
|
export function projectToFormData(project: Project): ProjectFormData {
|
|
|
|
|
return {
|
|
|
|
|
name: project.name,
|
|
|
|
|
description: project.description || '',
|
|
|
|
|
status: project.status,
|
|
|
|
|
progress: project.progress,
|
|
|
|
|
budget: project.budget,
|
|
|
|
|
startDate: project.startDate || '',
|
|
|
|
|
endDate: project.endDate || '',
|
|
|
|
|
socid: project.socid,
|
|
|
|
|
isPublic: true, // Por defecto público
|
2026-01-09 17:39:19 +00:00
|
|
|
};
|
|
|
|
|
}
|