diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..0ae83b7 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,64 @@ +version: '3.8' + +services: + mariadb: + image: mariadb:10.6 + container_name: dolibarr_db + restart: unless-stopped + environment: + MYSQL_ROOT_PASSWORD: root_password + MYSQL_DATABASE: dolibarr + MYSQL_USER: dolibarr + MYSQL_PASSWORD: dolibarr_password + volumes: + - mariadb_data:/var/lib/mysql + networks: + - dolibarr_network + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-proot_password"] + interval: 10s + timeout: 5s + retries: 5 + + dolibarr: + image: dolibarr/dolibarr:latest + container_name: dolibarr_app + restart: unless-stopped + depends_on: + mariadb: + condition: service_healthy + environment: + # Configuración de base de datos + DOLI_DB_HOST: mariadb + DOLI_DB_PORT: 3306 + DOLI_DB_NAME: dolibarr + DOLI_DB_USER: dolibarr + DOLI_DB_PASSWORD: dolibarr_password + + # Configuración de Dolibarr + DOLI_URL_ROOT: http://localhost:8200 + DOLI_ADMIN_LOGIN: admin + DOLI_ADMIN_PASSWORD: p4ssw0rd + + # Habilitar módulos necesarios + DOLI_MODULES: modProjet + + # Configuración adicional + DOLI_CRON: 0 + + volumes: + - dolibarr_documents:/var/www/documents + - dolibarr_custom:/var/www/html/custom + ports: + - "8200:80" + networks: + - dolibarr_network + +volumes: + mariadb_data: + dolibarr_documents: + dolibarr_custom: + +networks: + dolibarr_network: + driver: bridge diff --git a/image.png b/image.png deleted file mode 100644 index 4eb8d63..0000000 Binary files a/image.png and /dev/null differ diff --git a/scripts/seed-projects-via-api.js b/scripts/seed-projects-via-api.js index 59b2308..4edeb14 100644 --- a/scripts/seed-projects-via-api.js +++ b/scripts/seed-projects-via-api.js @@ -4,6 +4,11 @@ * Script para crear proyectos de prueba en Dolibarr usando la API Route * Este script usa la API Route segura de Next.js * + * Incluye: + * - Proyectos en diferentes estados (borrador, abierto, cerrado) + * - Tareas asociadas a cada proyecto + * - Limpieza de proyectos existentes antes de crear nuevos + * * REQUISITO: El servidor de Next.js debe estar corriendo (npm run dev) * Uso: node scripts/seed-projects-via-api.js */ @@ -12,6 +17,7 @@ const NEXT_API_URL = 'http://localhost:3000/api/dolibarr'; // Datos de proyectos de ejemplo con variedad de estados y presupuestos const projectsData = [ + // ==================== PROYECTOS ABIERTOS (EN PROGRESO) ==================== { title: "Desarrollo de App Móvil", ref: "PROJ-2024-001", @@ -20,15 +26,14 @@ const projectsData = [ opp_percent: 65, date_start: Math.floor(new Date('2024-01-15').getTime() / 1000), date_end: Math.floor(new Date('2024-06-30').getTime() / 1000), - }, - { - title: "Rediseño Web Corporativo", - ref: "PROJ-2024-002", - description: "Renovación completa del sitio web corporativo con nuevo diseño responsive y optimización SEO.", - opp_amount: 28000, - opp_percent: 90, - date_start: Math.floor(new Date('2023-11-01').getTime() / 1000), - date_end: Math.floor(new Date('2024-02-28').getTime() / 1000), + targetStatus: 'open', // Validar y dejar abierto + tasks: [ + { label: "Diseño de wireframes", progress: 100, priority: 2, planned_hours: 24, status: 'completed' }, + { label: "Desarrollo frontend React Native", progress: 80, priority: 3, planned_hours: 120, status: 'open' }, + { label: "Integración API backend", progress: 60, priority: 3, planned_hours: 80, status: 'open' }, + { label: "Implementar escáner QR", progress: 40, priority: 2, planned_hours: 40, status: 'open' }, + { label: "Testing y QA", progress: 20, priority: 2, planned_hours: 60, status: 'open' }, + ] }, { title: "Sistema de Facturación Automática", @@ -38,6 +43,14 @@ const projectsData = [ opp_percent: 35, date_start: Math.floor(new Date('2024-02-01').getTime() / 1000), date_end: Math.floor(new Date('2024-08-31').getTime() / 1000), + targetStatus: 'open', + tasks: [ + { label: "Análisis de requisitos", progress: 100, priority: 3, planned_hours: 16, status: 'completed' }, + { label: "Diseño de base de datos", progress: 100, priority: 2, planned_hours: 24, status: 'completed' }, + { label: "Módulo de generación PDF", progress: 50, priority: 3, planned_hours: 40, status: 'open' }, + { label: "Integración con pasarela de pago", progress: 10, priority: 2, planned_hours: 32, status: 'open' }, + { label: "Panel de administración", progress: 0, priority: 1, planned_hours: 48, status: 'draft' }, + ] }, { title: "Migración a Cloud AWS", @@ -47,15 +60,15 @@ const projectsData = [ opp_percent: 20, date_start: Math.floor(new Date('2024-03-01').getTime() / 1000), date_end: Math.floor(new Date('2024-12-31').getTime() / 1000), - }, - { - title: "Dashboard Analítico BI", - ref: "PROJ-2024-005", - description: "Creación de dashboard de Business Intelligence para análisis de ventas y KPIs en tiempo real.", - opp_amount: 38000, - opp_percent: 100, - date_start: Math.floor(new Date('2023-09-01').getTime() / 1000), - date_end: Math.floor(new Date('2023-12-15').getTime() / 1000), + targetStatus: 'open', + tasks: [ + { label: "Auditoría de infraestructura actual", progress: 100, priority: 3, planned_hours: 40, status: 'completed' }, + { label: "Diseño de arquitectura AWS", progress: 60, priority: 3, planned_hours: 32, status: 'open' }, + { label: "Configuración de VPC y networking", progress: 20, priority: 2, planned_hours: 24, status: 'open' }, + { label: "Migración de bases de datos", progress: 0, priority: 3, planned_hours: 48, status: 'draft' }, + { label: "Configuración de CI/CD", progress: 0, priority: 2, planned_hours: 32, status: 'draft' }, + { label: "Testing de carga", progress: 0, priority: 2, planned_hours: 24, status: 'draft' }, + ] }, { title: "API REST Microservicios", @@ -65,6 +78,15 @@ const projectsData = [ opp_percent: 50, date_start: Math.floor(new Date('2024-01-20').getTime() / 1000), date_end: Math.floor(new Date('2024-07-31').getTime() / 1000), + targetStatus: 'open', + tasks: [ + { label: "Diseño de arquitectura", progress: 100, priority: 3, planned_hours: 24, status: 'completed' }, + { label: "Servicio de autenticación", progress: 100, priority: 3, planned_hours: 40, status: 'completed' }, + { label: "Servicio de usuarios", progress: 80, priority: 2, planned_hours: 32, status: 'open' }, + { label: "Servicio de productos", progress: 60, priority: 2, planned_hours: 32, status: 'open' }, + { label: "Gateway API", progress: 30, priority: 3, planned_hours: 24, status: 'open' }, + { label: "Documentación OpenAPI", progress: 20, priority: 1, planned_hours: 16, status: 'open' }, + ] }, { title: "App de Gestión de Proyectos", @@ -74,6 +96,15 @@ const projectsData = [ opp_percent: 75, date_start: Math.floor(new Date('2023-12-01').getTime() / 1000), date_end: Math.floor(new Date('2024-05-31').getTime() / 1000), + targetStatus: 'open', + tasks: [ + { label: "Maquetación UI/UX", progress: 100, priority: 2, planned_hours: 32, status: 'completed' }, + { label: "Sistema de autenticación", progress: 100, priority: 3, planned_hours: 24, status: 'completed' }, + { label: "Tablero Kanban", progress: 100, priority: 3, planned_hours: 48, status: 'completed' }, + { label: "Vista Gantt", progress: 80, priority: 2, planned_hours: 40, status: 'open' }, + { label: "Sistema de notificaciones", progress: 50, priority: 2, planned_hours: 24, status: 'open' }, + { label: "Exportación de reportes", progress: 30, priority: 1, planned_hours: 20, status: 'open' }, + ] }, { title: "E-commerce con Marketplace", @@ -83,24 +114,16 @@ const projectsData = [ opp_percent: 15, date_start: Math.floor(new Date('2024-04-01').getTime() / 1000), date_end: Math.floor(new Date('2025-03-31').getTime() / 1000), - }, - { - title: "Sistema CRM Personalizado", - ref: "PROJ-2024-009", - description: "CRM a medida con automatización de marketing, gestión de leads y pipeline de ventas.", - opp_amount: 48000, - opp_percent: 0, - date_start: Math.floor(new Date('2024-05-01').getTime() / 1000), - date_end: Math.floor(new Date('2024-11-30').getTime() / 1000), - }, - { - title: "Portal de Empleados", - ref: "PROJ-2024-010", - description: "Portal interno para empleados con gestión de vacaciones, nóminas, formación y comunicación interna.", - opp_amount: 35000, - opp_percent: 100, - date_start: Math.floor(new Date('2023-10-01').getTime() / 1000), - date_end: Math.floor(new Date('2023-12-31').getTime() / 1000), + targetStatus: 'open', + tasks: [ + { label: "Investigación de mercado", progress: 100, priority: 2, planned_hours: 20, status: 'completed' }, + { label: "Diseño de experiencia de usuario", progress: 70, priority: 3, planned_hours: 40, status: 'open' }, + { label: "Catálogo de productos", progress: 20, priority: 2, planned_hours: 60, status: 'open' }, + { label: "Carrito de compras", progress: 0, priority: 3, planned_hours: 40, status: 'draft' }, + { label: "Integración Stripe/PayPal", progress: 0, priority: 3, planned_hours: 32, status: 'draft' }, + { label: "Panel de vendedores", progress: 0, priority: 2, planned_hours: 48, status: 'draft' }, + { label: "Sistema de reviews", progress: 0, priority: 1, planned_hours: 24, status: 'draft' }, + ] }, { title: "Sistema de Reservas Online", @@ -110,6 +133,14 @@ const projectsData = [ opp_percent: 45, date_start: Math.floor(new Date('2024-02-15').getTime() / 1000), date_end: Math.floor(new Date('2024-07-15').getTime() / 1000), + targetStatus: 'open', + tasks: [ + { label: "Diseño de flujo de reservas", progress: 100, priority: 2, planned_hours: 16, status: 'completed' }, + { label: "Calendario interactivo", progress: 80, priority: 3, planned_hours: 40, status: 'open' }, + { label: "Sistema de disponibilidad", progress: 60, priority: 3, planned_hours: 32, status: 'open' }, + { label: "Notificaciones por email/SMS", progress: 20, priority: 2, planned_hours: 24, status: 'open' }, + { label: "Integración de pagos", progress: 0, priority: 3, planned_hours: 28, status: 'draft' }, + ] }, { title: "App de Formación Online", @@ -119,22 +150,173 @@ const projectsData = [ opp_percent: 30, date_start: Math.floor(new Date('2024-03-10').getTime() / 1000), date_end: Math.floor(new Date('2024-10-31').getTime() / 1000), + targetStatus: 'open', + tasks: [ + { label: "Análisis de requisitos LMS", progress: 100, priority: 2, planned_hours: 20, status: 'completed' }, + { label: "Sistema de cursos y lecciones", progress: 50, priority: 3, planned_hours: 60, status: 'open' }, + { label: "Reproductor de vídeo", progress: 40, priority: 2, planned_hours: 32, status: 'open' }, + { label: "Sistema de evaluaciones", progress: 10, priority: 2, planned_hours: 40, status: 'open' }, + { label: "Generación de certificados", progress: 0, priority: 1, planned_hours: 24, status: 'draft' }, + { label: "Gamificación y badges", progress: 0, priority: 1, planned_hours: 32, status: 'draft' }, + ] + }, + + // ==================== PROYECTOS CERRADOS (COMPLETADOS) ==================== + { + title: "Rediseño Web Corporativo", + ref: "PROJ-2024-002", + description: "Renovación completa del sitio web corporativo con nuevo diseño responsive y optimización SEO.", + opp_amount: 28000, + opp_percent: 100, + date_start: Math.floor(new Date('2023-11-01').getTime() / 1000), + date_end: Math.floor(new Date('2024-02-28').getTime() / 1000), + targetStatus: 'closed', // Cerrar el proyecto + tasks: [ + { label: "Auditoría web actual", progress: 100, priority: 2, planned_hours: 16, status: 'completed' }, + { label: "Diseño nuevo branding", progress: 100, priority: 3, planned_hours: 32, status: 'completed' }, + { label: "Desarrollo frontend", progress: 100, priority: 3, planned_hours: 80, status: 'completed' }, + { label: "Optimización SEO", progress: 100, priority: 2, planned_hours: 24, status: 'completed' }, + { label: "Migración de contenidos", progress: 100, priority: 2, planned_hours: 20, status: 'completed' }, + { label: "Testing cross-browser", progress: 100, priority: 2, planned_hours: 16, status: 'completed' }, + ] + }, + { + title: "Dashboard Analítico BI", + ref: "PROJ-2024-005", + description: "Creación de dashboard de Business Intelligence para análisis de ventas y KPIs en tiempo real.", + opp_amount: 38000, + opp_percent: 100, + date_start: Math.floor(new Date('2023-09-01').getTime() / 1000), + date_end: Math.floor(new Date('2023-12-15').getTime() / 1000), + targetStatus: 'closed', + tasks: [ + { label: "Definición de KPIs", progress: 100, priority: 3, planned_hours: 16, status: 'completed' }, + { label: "Conexión a fuentes de datos", progress: 100, priority: 3, planned_hours: 32, status: 'completed' }, + { label: "Diseño de visualizaciones", progress: 100, priority: 2, planned_hours: 24, status: 'completed' }, + { label: "Desarrollo de dashboard", progress: 100, priority: 3, planned_hours: 48, status: 'completed' }, + { label: "Reportes automatizados", progress: 100, priority: 2, planned_hours: 20, status: 'completed' }, + { label: "Formación a usuarios", progress: 100, priority: 1, planned_hours: 8, status: 'completed' }, + ] + }, + { + title: "Portal de Empleados", + ref: "PROJ-2024-010", + description: "Portal interno para empleados con gestión de vacaciones, nóminas, formación y comunicación interna.", + opp_amount: 35000, + opp_percent: 100, + date_start: Math.floor(new Date('2023-10-01').getTime() / 1000), + date_end: Math.floor(new Date('2023-12-31').getTime() / 1000), + targetStatus: 'closed', + tasks: [ + { label: "Portal de noticias interno", progress: 100, priority: 2, planned_hours: 24, status: 'completed' }, + { label: "Gestión de vacaciones", progress: 100, priority: 3, planned_hours: 32, status: 'completed' }, + { label: "Visualización de nóminas", progress: 100, priority: 3, planned_hours: 28, status: 'completed' }, + { label: "Directorio de empleados", progress: 100, priority: 1, planned_hours: 16, status: 'completed' }, + { label: "Integración con RRHH", progress: 100, priority: 2, planned_hours: 24, status: 'completed' }, + ] + }, + { + title: "Migración ERP Legacy", + ref: "PROJ-2023-015", + description: "Migración completa del sistema ERP legacy a nueva plataforma con cero tiempo de inactividad.", + opp_amount: 95000, + opp_percent: 100, + date_start: Math.floor(new Date('2023-06-01').getTime() / 1000), + date_end: Math.floor(new Date('2023-11-30').getTime() / 1000), + targetStatus: 'closed', + tasks: [ + { label: "Mapeo de datos legacy", progress: 100, priority: 3, planned_hours: 40, status: 'completed' }, + { label: "Desarrollo de scripts migración", progress: 100, priority: 3, planned_hours: 60, status: 'completed' }, + { label: "Pruebas de migración", progress: 100, priority: 3, planned_hours: 48, status: 'completed' }, + { label: "Migración en producción", progress: 100, priority: 3, planned_hours: 24, status: 'completed' }, + { label: "Validación post-migración", progress: 100, priority: 2, planned_hours: 32, status: 'completed' }, + { label: "Documentación técnica", progress: 100, priority: 1, planned_hours: 16, status: 'completed' }, + ] + }, + + // ==================== PROYECTOS EN BORRADOR ==================== + { + title: "Sistema CRM Personalizado", + ref: "PROJ-2024-009", + description: "CRM a medida con automatización de marketing, gestión de leads y pipeline de ventas.", + opp_amount: 48000, + opp_percent: 0, + date_start: Math.floor(new Date('2024-05-01').getTime() / 1000), + date_end: Math.floor(new Date('2024-11-30').getTime() / 1000), + targetStatus: 'draft', // Dejar como borrador + tasks: [ + { label: "Definición de requisitos", progress: 0, priority: 3, planned_hours: 24, status: 'draft' }, + { label: "Diseño de arquitectura", progress: 0, priority: 2, planned_hours: 20, status: 'draft' }, + { label: "Módulo de contactos", progress: 0, priority: 2, planned_hours: 40, status: 'draft' }, + { label: "Pipeline de ventas", progress: 0, priority: 3, planned_hours: 48, status: 'draft' }, + { label: "Automatización de emails", progress: 0, priority: 2, planned_hours: 32, status: 'draft' }, + ] + }, + { + title: "App de Gestión de Flota", + ref: "PROJ-2024-013", + description: "Sistema de tracking GPS y gestión de flota vehicular con alertas y reportes de consumo.", + opp_amount: 58000, + opp_percent: 0, + date_start: Math.floor(new Date('2024-06-01').getTime() / 1000), + date_end: Math.floor(new Date('2024-12-31').getTime() / 1000), + targetStatus: 'draft', + tasks: [ + { label: "Estudio de viabilidad", progress: 0, priority: 2, planned_hours: 16, status: 'draft' }, + { label: "Integración GPS", progress: 0, priority: 3, planned_hours: 40, status: 'draft' }, + { label: "Dashboard de tracking", progress: 0, priority: 3, planned_hours: 48, status: 'draft' }, + { label: "Sistema de alertas", progress: 0, priority: 2, planned_hours: 24, status: 'draft' }, + ] }, ]; +/** + * Obtener todos los proyectos existentes + */ +async function getExistingProjects() { + try { + const response = await fetch(`${NEXT_API_URL}/projects`); + if (!response.ok) { + if (response.status === 404) return []; + throw new Error(`Error ${response.status}`); + } + return await response.json(); + } catch (error) { + console.log(' ⚠ No se pudieron obtener proyectos existentes'); + return []; + } +} + +/** + * Eliminar un proyecto + */ +async function deleteProject(projectId) { + try { + const response = await fetch(`${NEXT_API_URL}/projects/${projectId}`, { + method: 'DELETE', + }); + return response.ok; + } catch (error) { + return false; + } +} + /** * Crear un proyecto usando la API Route de Next.js */ async function createProject(projectData) { const url = `${NEXT_API_URL}/projects`; + // Limpiar datos que no van a la API de creación + const { tasks, targetStatus, ...apiData } = projectData; + try { const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', }, - body: JSON.stringify(projectData), + body: JSON.stringify(apiData), }); if (!response.ok) { @@ -167,12 +349,88 @@ async function validateProject(projectId) { body: JSON.stringify({ notrigger: 0 }), }); + return response.ok; + } catch (error) { + return false; + } +} + +/** + * Cerrar proyecto (cambiar estado a cerrado usando PUT) + */ +async function closeProject(projectId) { + const url = `${NEXT_API_URL}/projects/${projectId}`; + + try { + const response = await fetch(url, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ status: "2" }), + }); + + return response.ok; + } catch (error) { + return false; + } +} + +/** + * Crear una tarea para un proyecto + */ +async function createTask(projectId, taskData, index) { + const url = `${NEXT_API_URL}/tasks`; + + const timestamp = Date.now().toString(36).toUpperCase(); + const taskPayload = { + ref: `TK${projectId}-${index}-${timestamp}`, + label: taskData.label, + fk_project: projectId, + progress: taskData.progress || 0, + priority: String(taskData.priority || 0), + planned_workload: (taskData.planned_hours || 0) * 3600, // Convertir horas a segundos + }; + + try { + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(taskPayload), + }); + if (!response.ok) { - // Es normal que algunos proyectos no se puedan validar - return false; + return null; } - return true; + const taskId = await response.json(); + + // Si la tarea debe estar completada o abierta, validarla + if (taskData.status === 'completed' || taskData.status === 'open') { + await validateTask(taskId); + } + + return taskId; + } catch (error) { + return null; + } +} + +/** + * Validar tarea + */ +async function validateTask(taskId) { + try { + const response = await fetch(`${NEXT_API_URL}/tasks/${taskId}/validate`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ notrigger: 0 }), + }); + return response.ok; } catch (error) { return false; } @@ -193,7 +451,7 @@ async function main() { console.log('🔍 Verificando conexión con Next.js...'); try { const testResponse = await fetch('http://localhost:3000'); - if (!testResponse.ok && testResponse.status !== 404) { + if (!testResponse.ok && testResponse.status !== 404 && testResponse.status !== 307) { throw new Error('Server not responding'); } console.log('✅ Servidor Next.js está corriendo\n'); @@ -203,8 +461,30 @@ async function main() { process.exit(1); } + // ==================== LIMPIEZA DE PROYECTOS EXISTENTES ==================== + console.log('🧹 Limpiando proyectos existentes...'); + const existingProjects = await getExistingProjects(); + + if (existingProjects.length > 0) { + let deletedCount = 0; + for (const project of existingProjects) { + const projectId = project.id || project; + const deleted = await deleteProject(projectId); + if (deleted) { + deletedCount++; + process.stdout.write(`\r Eliminados: ${deletedCount}/${existingProjects.length}`); + } + await new Promise(resolve => setTimeout(resolve, 200)); + } + console.log(`\n ✅ ${deletedCount} proyectos eliminados\n`); + } else { + console.log(' ℹ No hay proyectos existentes\n'); + } + + // ==================== CREACIÓN DE NUEVOS PROYECTOS ==================== let successCount = 0; let errorCount = 0; + let tasksCreated = 0; const errors = []; for (let i = 0; i < projectsData.length; i++) { @@ -215,31 +495,60 @@ async function main() { // Indicador visual del progreso const progressBar = '▓'.repeat(Math.floor(percentage / 10)) + '░'.repeat(10 - Math.floor(percentage / 10)); - console.log(`[${num}/${projectsData.length}] ${project.title}`); + // Emoji según estado objetivo + const statusEmoji = project.targetStatus === 'closed' ? '🏁' : + project.targetStatus === 'draft' ? '📝' : '🔵'; + + console.log(`[${num}/${projectsData.length}] ${statusEmoji} ${project.title}`); console.log(` 📈 Progreso: [${progressBar}] ${percentage}%`); console.log(` 💰 Presupuesto: €${project.opp_amount.toLocaleString()}`); + console.log(` 🎯 Estado objetivo: ${project.targetStatus}`); try { // Crear proyecto - const result = await createProject(project); - console.log(` ✅ Creado con ID: ${result}`); + const projectId = await createProject(project); + console.log(` ✅ Proyecto creado con ID: ${projectId}`); - // Validar proyecto si tiene progreso > 0 - if (percentage > 0) { - const validated = await validateProject(result); + // Gestionar estado del proyecto + if (project.targetStatus === 'open' || project.targetStatus === 'closed') { + const validated = await validateProject(projectId); if (validated) { console.log(` ✓ Validado (estado: abierto)`); - } else { - console.log(` ⚠ Creado pero no validado (normal para algunos proyectos)`); + + // Si debe estar cerrado, cerrarlo + if (project.targetStatus === 'closed') { + const closed = await closeProject(projectId); + if (closed) { + console.log(` 🏁 Cerrado (completado)`); + } + } } } else { console.log(` 📋 Dejado como borrador`); } + // Crear tareas del proyecto + if (project.tasks && project.tasks.length > 0) { + console.log(` 📋 Creando ${project.tasks.length} tareas...`); + let projectTasksCreated = 0; + + for (let j = 0; j < project.tasks.length; j++) { + const task = project.tasks[j]; + const taskId = await createTask(projectId, task, j + 1); + if (taskId) { + projectTasksCreated++; + tasksCreated++; + } + await new Promise(resolve => setTimeout(resolve, 150)); + } + + console.log(` ✅ ${projectTasksCreated}/${project.tasks.length} tareas creadas`); + } + successCount++; // Pequeña pausa para no saturar la API - await new Promise(resolve => setTimeout(resolve, 500)); + await new Promise(resolve => setTimeout(resolve, 300)); } catch (error) { console.log(` ❌ Error: ${error.message}`); errors.push({ project: project.title, error: error.message }); @@ -253,8 +562,19 @@ async function main() { console.log('═'.repeat(60)); console.log('✨ PROCESO COMPLETADO\n'); console.log(`✅ Proyectos creados exitosamente: ${successCount}`); + console.log(`📋 Tareas creadas: ${tasksCreated}`); console.log(`❌ Proyectos con error: ${errorCount}`); + // Desglose por estado + const openProjects = projectsData.filter(p => p.targetStatus === 'open').length; + const closedProjects = projectsData.filter(p => p.targetStatus === 'closed').length; + const draftProjects = projectsData.filter(p => p.targetStatus === 'draft').length; + + console.log('\n📊 Desglose por estado:'); + console.log(` 🔵 Abiertos: ${openProjects}`); + console.log(` 🏁 Cerrados: ${closedProjects}`); + console.log(` 📝 Borradores: ${draftProjects}`); + if (errors.length > 0) { console.log('\n📋 Errores encontrados:'); errors.forEach(({ project, error }) => { diff --git a/types/project.ts b/types/project.ts index 4f9cc70..88b0e98 100644 --- a/types/project.ts +++ b/types/project.ts @@ -1,5 +1,5 @@ // types/project.ts -export type ProjectStatus = '0' | '1' | '2'; // 0: borrador, 1: validado/abierto, 2: cerrado +export type ProjectStatus = '0' | '1' | '2'; // 0: borrador, 1: activo, 2: cerrado // Interface para los datos crudos que vienen de Dolibarr export interface DolibarrProject { @@ -49,7 +49,7 @@ export const STATUS_CONFIG: Record "bg-gray-100 text-gray-800 border-gray-200" }, '1': { - label: "Abierto", + label: "Activo", getColorClasses: () => "bg-blue-100 text-blue-800 border-blue-200" }, '2': {