n8n_3_CAD-BIM-conversor por lotes-tubería

Avanzado

Este es unDocument Extraction, Multimodal AIflujo de automatización del dominio deautomatización que contiene 82 nodos.Utiliza principalmente nodos como If, Set, Code, Merge, ManualTrigger. Conversión por lotes de archivos CAD/BIM a XLSX/DAE, con verificación e informes

Requisitos previos
  • Clave de API de OpenAI
  • Clave de API de Anthropic
  • Clave de API de Google Gemini
Vista previa del flujo de trabajo
Visualización de las conexiones entre nodos, con soporte para zoom y panorámica
Exportar flujo de trabajo
Copie la siguiente configuración JSON en n8n para importar y usar este flujo de trabajo
{
  "id": "u5MdPlYjhriENe3R",
  "meta": {
    "instanceId": "faa70e11b7175129a74fd834d3451fdc1862589b16d68ded03f91ca7b1ecca12"
  },
  "name": "n8n_3_CAD-BIM-Batch-Converter-Pipeline",
  "tags": [],
  "nodes": [
    {
      "id": "be5c0de2-0df1-4c35-8b0e-26f8d1911bda",
      "name": "Nota Adhesiva23",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1088,
        224
      ],
      "parameters": {
        "width": 376,
        "height": 368,
        "content": "## 📋 Quick Start Guide\n\n**1️⃣ Configure Settings**\nEdit \"Set Configuration Parameters\":\n- `converter_path`: Path to RvtExporter.exe\n- `source_folder`: Your CAD files location\n- `output_folder`: Where to save results\n- `file_extension`: .rvt, .ifc, .dwg, or .dgn\n\n**2️⃣ Run Pipeline**\nClick \"Execute Workflow\"\n\n**3️⃣ Get Results**\n✅ Excel data files\n✅ 3D DAE models\n✅ HTML report"
      },
      "typeVersion": 1
    },
    {
      "id": "d8a6fce0-af86-4769-8911-afb6c665f38d",
      "name": "Nota Adhesiva25",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1088,
        608
      ],
      "parameters": {
        "color": 2,
        "width": 380,
        "height": 224,
        "content": "## 🎓 Video Tutorials\n\n**📹 [CAD-BIM Pipeline Tutorial](https://www.youtube.com/watch?v=PMTZNRFjD6c)**\nComplete walkthrough of CAD-BIM automation\n\n**⚡ [Automated Validation](https://www.youtube.com/watch?v=p84AmP2dcvg)**\nStop manual BIM checking forever\n\n💡 **Pro tip:** Watch before first run!"
      },
      "typeVersion": 1
    },
    {
      "id": "1182ee14-cf89-45ee-96dc-d96a6864dcfa",
      "name": "Nota Adhesiva28",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1088,
        1168
      ],
      "parameters": {
        "color": 2,
        "width": 372,
        "height": 244,
        "content": "## 🆘 Support\n\n**Resources:**\n🌐 [DataDrivenConstruction.io](https://datadrivenconstruction.io)\n📧 support@datadrivenconstruction.io\n💬 [Community Forum](https://t.me/datadrivenconstruction)\n\n**Documentation:**\n📚 [n8n Docs](https://docs.n8n.io)\n🎥 [YouTube Channel](https://youtube.com/@datadrivenconstruction)"
      },
      "typeVersion": 1
    },
    {
      "id": "8bd00e43-cb87-489a-87b6-a6258262a5bf",
      "name": "Nota Adhesiva29",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1088,
        848
      ],
      "parameters": {
        "color": 2,
        "width": 380,
        "height": 304,
        "content": "## 🎯 Use Cases\n\n**Weekly Reports**\nAutomate Monday conversions\n\n**Quality Control**\nValidate before client delivery\n\n**Archive Projects**\nConvert old files to lightweight formats\n\n**BI Integration**\nFeed Excel data to Power BI"
      },
      "typeVersion": 1
    },
    {
      "id": "3ccefd7c-ade0-4125-952c-928115a49ebf",
      "name": "Nota Adhesiva31",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1088,
        112
      ],
      "parameters": {
        "width": 372,
        "height": 100,
        "content": "⭐ **If you find our tools helpful**, please consider starring our repository on [GitHub](https://github.com/datadrivenconstruction/cad2data-Revit-IFC-DWG-DGN-pipeline-with-conversion-validation-qto). **Your support helps us** improve and continue developing open solutions for the community!\n"
      },
      "typeVersion": 1
    },
    {
      "id": "c5388249-6dc3-4070-9057-2b5668251832",
      "name": "Nota Adhesiva",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -688,
        112
      ],
      "parameters": {
        "width": 520,
        "height": 1940,
        "content": "## 📋 Options Parameter Configuration Guide\n\n## 🎯 Overview\nThe Options parameter allows you to customize the export process with various flags and settings. Multiple options can be combined using spaces.\n\n## 🚀 Available Options\n\n### 📦 Export Modes\n| Mode   | Description |\n|------|-------------|\n| `basic`   |  Minimal export with essential data only |\n| `standard`   | Default export level with standard properties |\n| `complete`   | Full export including all available data |\n| `custom`   | Custom export using category file specification |\n\n### 🛠️ Feature Flags\n| Option | Description |\n|--------|-------------|\n| `bbox` | Add BoundingBox geometry of each element in XLSX |\n| `schedule` | Export all Schedules |\n| `sheets2pdf` | Export all Sheets to PDF format |\n| `[<output file>]` | Specify custom output file path |\n| `[<category file>]` | Text file with category names (required for custom mode) |\n\n### 🚫 Disable Options\n| Option | Description |\n|--------|-------------|\n| `-no-xlsx` | Disable export to .xlsx format |\n| `-no-collada` | Disable export to .dae format |\n\n---\n\n## 💡 Usage Examples\n\n### Example 1: **Basic Export with BoundingBox**\n```bash\nOptions: bbox basic\n```\n> Exports basic data with BoundingBox geometry included\n\n### Example 2: **Complete Export with Schedules**\n```bash\nOptions: complete schedule sheets2pdf\n```\n> Full data export including all schedules and sheets converted to PDF\n\n### Example 3: **Custom Mode with Category File**\n```bash\nOptions: custom categories.txt bbox\n```\n> Custom export using categories.txt file with BoundingBox geometry\n\n### Example 4: **Export Without Specific Formats**\n```bash\nOptions: standard -no-xlsx -no-collada\n```\n> Standard export excluding XLSX and Collada formats\n\n### Example 5: **Schedules Only Export**\n```bash\nOptions: schedule -no-collada\n```\n> Export only schedules without Collada format\n\n### Example 6: **Custom Output Path**\n```bash\nOptions: C:\\Output\\result.xlsx complete bbox\n```\n> Complete export with BoundingBox to specific output file\n\n---\n\n## ⚡ Quick Tips\n- **Combine multiple options** by separating them with spaces\n- **Order matters** - export mode should come before other options\n- **Custom mode** requires a category file to function properly\n- **Disable flags** can be used to exclude unwanted formats\n\n---\n\n## 📝 Notes\n- All options are case-sensitive\n- Invalid combinations will be ignored\n- Default behavior applies when no options are specified"
      },
      "typeVersion": 1
    },
    {
      "id": "2fe7360d-df50-4eb7-8799-441e7aed7a1a",
      "name": "Activador Manual",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        -80,
        704
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "614d8fe1-6c80-4cb0-b941-5aa3998a2321",
      "name": "Capturar Hora de Inicio de Canalización1",
      "type": "n8n-nodes-base.code",
      "position": [
        144,
        656
      ],
      "parameters": {
        "jsCode": "// Capture pipeline start time at the very beginning\nconst now = new Date();\nreturn [{\n  json: {\n    pipeline_start_time: now.toISOString(),\n    pipeline_start_timestamp: now.getTime(),\n    pipeline_start_message: `Pipeline started at ${now.toLocaleString()}`\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "e64288d1-9d53-4036-afde-437c9892a927",
      "name": "Combinar Inicio de Canalización con Config1",
      "type": "n8n-nodes-base.merge",
      "position": [
        688,
        672
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "combineBy": "combineByPosition"
      },
      "typeVersion": 3
    },
    {
      "id": "7b214d29-a619-4a41-8cfa-67f074470b89",
      "name": "Establecer Parámetros de Configuración1",
      "type": "n8n-nodes-base.set",
      "position": [
        416,
        512
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "converter-path",
              "name": "converter_path",
              "type": "string",
              "value": "C:\\Users\\Artem Boiko\\Desktop\\n8n\\cad2data-Revit-IFC-DWG-DGN-pipeline-with-conversion-validation-qto-main\\cad2data-Revit-IFC-DWG-DGN-pipeline-with-conversion-validation-qto-main\\DDC_Converter_Revit\\datadrivenlibs\\RvtExporter.exe"
            },
            {
              "id": "source-folder",
              "name": "source_folder",
              "type": "string",
              "value": "C:\\Users\\Artem Boiko\\Desktop\\n8n\\cad2data-Revit-IFC-DWG-DGN-pipeline-with-conversion-validation-qto-main\\cad2data-Revit-IFC-DWG-DGN-pipeline-with-conversion-validation-qto-main\\Sample_Projects"
            },
            {
              "id": "output-folder",
              "name": "output_folder",
              "type": "string",
              "value": "C:\\Users\\Artem Boiko\\Desktop\\n8n\\cad2data-Revit-IFC-DWG-DGN-pipeline-with-conversion-validation-qto-main\\cad2data-Revit-IFC-DWG-DGN-pipeline-with-conversion-validation-qto-main\\Sample_Projects"
            },
            {
              "id": "include-subfolders",
              "name": "include_subfolders",
              "type": "boolean",
              "value": false
            },
            {
              "id": "file-extension",
              "name": "file_extension",
              "type": "string",
              "value": ".rvt"
            },
            {
              "id": "a811f4ba-b7a5-4774-a1fa-90d9c43a0de6",
              "name": "options",
              "type": "string",
              "value": "basic schedule  -no-collada"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "53648934-0bb7-49d3-83f9-ab4280bb3ec9",
      "name": "Buscar Archivos CAD1",
      "type": "n8n-nodes-base.executeCommand",
      "position": [
        848,
        688
      ],
      "parameters": {
        "command": "=powershell -Command \"Get-ChildItem -LiteralPath '{{ $json.source_folder }}' -Filter '*{{ $json.file_extension }}' {{ $json.include_subfolders ? '-Recurse' : '' }} | Select-Object -ExpandProperty FullName\""
      },
      "typeVersion": 1
    },
    {
      "id": "894a4fae-cd2f-4418-b35b-fc6a06cda820",
      "name": "Combinar Config con Resultados de Búsqueda1",
      "type": "n8n-nodes-base.merge",
      "position": [
        1008,
        576
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "combineBy": "combineByPosition"
      },
      "typeVersion": 3
    },
    {
      "id": "cc6f17d1-4393-4c77-947a-53c42c01543c",
      "name": "Procesar Lista de Archivos1",
      "type": "n8n-nodes-base.code",
      "position": [
        1168,
        576
      ],
      "parameters": {
        "jsCode": "// Process file list with configuration and pipeline start time\nconst configData = $input.first().json || {};\nconst searchData = $input.last().json || {};\n\nconst output = searchData.stdout || '';\nconst stderr = searchData.stderr || '';\n\n// Preserve pipeline start time\nconst pipeline_start_time = configData.pipeline_start_time || '';\nconst pipeline_start_timestamp = configData.pipeline_start_timestamp || Date.now();\n\nconsole.log('=== Processing File List ===');\nconsole.log('Pipeline start time:', pipeline_start_time);\nconsole.log('Config data:', configData);\nconsole.log('Raw output:', output);\nconsole.log('Files found:', output ? output.split(/\\r?\\n/).filter(x => x.trim()).length : 0);\n\nconst config = {\n  converter_path: configData.converter_path || '',\n  source_folder: configData.source_folder || '',\n  output_folder: configData.output_folder || '',\n  include_subfolders: configData.include_subfolders || false,\n  file_extension: configData.file_extension || '.rvt',\n  options: configData.options || ''\n};\n\nif (!output || !output.trim()) {\n  return [{\n    json: {\n      files: [],\n      total_files: 0,\n      message: `No files found with extension '${config.file_extension}' in ${config.source_folder}`,\n      error: stderr || 'No output from search command',\n      config: config,\n      pipeline_start_time: pipeline_start_time,\n      pipeline_start_timestamp: pipeline_start_timestamp\n    }\n  }];\n}\n\nlet normalizedExtension = (config.file_extension || '').trim();\nif (normalizedExtension && !normalizedExtension.startsWith('.')) {\n  normalizedExtension = '.' + normalizedExtension;\n}\n\nlet rawPaths = [];\ntry {\n  rawPaths = output.trim().split(/\\r?\\n/)\n    .map(path => (path || '').trim())\n    .filter(path => path && path.length > 0);\n} catch (error) {\n  console.log('Error splitting output:', error);\n  return [{\n    json: {\n      files: [],\n      total_files: 0,\n      message: `Error processing search results: ${error.message}`,\n      config: config,\n      pipeline_start_time: pipeline_start_time,\n      pipeline_start_timestamp: pipeline_start_timestamp\n    }\n  }];\n}\n\nconst filePaths = rawPaths.filter(path => {\n  if (!path || !normalizedExtension) return false;\n  const lowerPath = path.toLowerCase();\n  const lowerExt = normalizedExtension.toLowerCase();\n  return lowerPath.endsWith(lowerExt);\n});\n\nif (filePaths.length === 0) {\n  return [{\n    json: {\n      files: [],\n      total_files: 0,\n      message: `No files found with extension '${normalizedExtension}' in ${config.source_folder}`,\n      config: config,\n      pipeline_start_time: pipeline_start_time,\n      pipeline_start_timestamp: pipeline_start_timestamp\n    }\n  }];\n}\n\nconst files = filePaths.map((filePath, index) => {\n  let fileName = 'unknown_file';\n  let fileNameWithoutExt = 'unknown_file';\n  \n  try {\n    if (filePath && typeof filePath === 'string') {\n      const normalizedPath = filePath.replace(/\\\\/g, '/');\n      const pathParts = normalizedPath.split('/');\n      fileName = pathParts[pathParts.length - 1] || 'unknown_file';\n      \n      console.log(`Processing file ${index + 1}: ${fileName} from path: ${filePath}`);\n      \n      const lastDotIndex = fileName.lastIndexOf('.');\n      if (lastDotIndex > 0) {\n        fileNameWithoutExt = fileName.substring(0, lastDotIndex);\n      } else {\n        fileNameWithoutExt = fileName;\n      }\n    }\n  } catch (error) {\n    console.log(`Error parsing file path ${filePath}:`, error);\n  }\n  \n  const extWithoutDot = normalizedExtension ? normalizedExtension.substring(1) : 'unknown';\n  \n  return {\n    index: index + 1,\n    file_path: filePath,\n    file_name: fileName,\n    file_name_without_ext: fileNameWithoutExt,\n    converter_path: config.converter_path,\n    expected_output: `${config.output_folder}\\\\${fileNameWithoutExt}_${extWithoutDot}.xlsx`,\n    expected_output_dae: `${config.output_folder}\\\\${fileNameWithoutExt}_${extWithoutDot}.dae`,\n    config: config,\n    options: config.options,\n    pipeline_start_time: pipeline_start_time,\n    pipeline_start_timestamp: pipeline_start_timestamp\n  };\n});\n\nconsole.log('Processed files:', files.map(f => f.file_name));\n\nreturn [{\n  json: {\n    files: files,\n    total_files: files.length,\n    message: `Found ${files.length} files to convert`,\n    config: config,\n    pipeline_start_time: pipeline_start_time,\n    pipeline_start_timestamp: pipeline_start_timestamp\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "357d21f1-ff0b-4308-99a7-8ab3140b6dde",
      "name": "Verificar si Existen Archivos1",
      "type": "n8n-nodes-base.if",
      "position": [
        1328,
        576
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "files-exist-condition",
              "operator": {
                "type": "number",
                "operation": "gt"
              },
              "leftValue": "={{ $json.files && $json.files.length || 0 }}",
              "rightValue": 0
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "84eff3b1-f5a8-4c84-82cb-2fa1d1bc23ea",
      "name": "Dividir Archivos para Procesamiento1",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        1520,
        560
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "files"
      },
      "typeVersion": 1
    },
    {
      "id": "0330e04c-9572-439d-b09f-aa932b5e81ad",
      "name": "Crear Directorio de Salida1",
      "type": "n8n-nodes-base.executeCommand",
      "position": [
        1696,
        656
      ],
      "parameters": {
        "command": "=mkdir \"{{ $json.config.output_folder }}\" 2>nul || echo Output directory ready",
        "executeOnce": false
      },
      "typeVersion": 1
    },
    {
      "id": "1cbf1ee9-0710-4145-a54e-0c4c73e177a6",
      "name": "Combinar Datos de Archivo1",
      "type": "n8n-nodes-base.merge",
      "position": [
        1856,
        576
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "combineBy": "combineByPosition"
      },
      "typeVersion": 3
    },
    {
      "id": "627097e5-5d07-4386-a9d7-22dffe7ba4ab",
      "name": "Capturar Hora de Inicio1",
      "type": "n8n-nodes-base.code",
      "position": [
        2032,
        576
      ],
      "parameters": {
        "jsCode": "// Capture conversion start time and preserve ALL data including pipeline start\nreturn $input.all().map(item => {\n  const json = {...item.json};\n  json.conversion_start_time = new Date().toISOString();\n  json.conversion_start_timestamp = Date.now();\n  console.log(`Starting conversion for: ${json.file_name} at ${json.conversion_start_time}`);\n  console.log(`Pipeline started at: ${json.pipeline_start_time}`);\n  return {json};\n});"
      },
      "typeVersion": 2
    },
    {
      "id": "edf42bdd-67ed-46d1-a522-cbc93538d894",
      "name": "Pequeña Demora",
      "type": "n8n-nodes-base.wait",
      "position": [
        2192,
        576
      ],
      "webhookId": "5a1cb608-ec3e-4832-a289-f6a52ef8bc7a",
      "parameters": {
        "unit": "milliseconds"
      },
      "typeVersion": 1
    },
    {
      "id": "1c4e14d2-f9cc-4b4f-85f0-7c0245c32b18",
      "name": "Ejecutar Conversión1",
      "type": "n8n-nodes-base.executeCommand",
      "position": [
        2352,
        688
      ],
      "parameters": {
        "command": "=\"{{ $json.converter_path }}\" \"{{ $json.file_path }}\" \"{{ $json.expected_output }}\" \"{{ $json.expected_output_dae }}\" {{ $json.options }}",
        "executeOnce": false
      },
      "typeVersion": 1,
      "continueOnFail": true
    },
    {
      "id": "f02f9f74-9b20-4857-92c3-4b0f4103f1b1",
      "name": "Calcular Tiempo de Conversión1",
      "type": "n8n-nodes-base.code",
      "position": [
        2512,
        688
      ],
      "parameters": {
        "jsCode": "// Calculate actual conversion time and rename outputs - preserve pipeline start time\nreturn $input.all().map(item => {\n  const json = {...item.json};\n  \n  // Rename stdout/stderr\n  json.conversion_stdout = json.stdout || '';\n  json.conversion_stderr = json.stderr || '';\n  json.conversion_exitCode = json.exitCode || 0;\n  delete json.stdout;\n  delete json.stderr;\n  delete json.exitCode;\n  \n  // Calculate actual processing time with decimal precision\n  if (json.conversion_start_timestamp) {\n    const endTime = Date.now();\n    const startTime = json.conversion_start_timestamp;\n    const processingTimeMs = endTime - startTime;\n    json.processing_time = processingTimeMs / 1000;\n    json.processing_time_ms = processingTimeMs;\n    json.conversion_end_time = new Date().toISOString();\n    json.conversion_end_timestamp = endTime;\n    console.log(`Conversion completed for: ${json.file_name} in ${json.processing_time.toFixed(2)} seconds`);\n  } else {\n    json.processing_time = 0;\n    json.processing_time_ms = 0;\n    console.warn(`No start timestamp found for: ${json.file_name}`);\n  }\n  \n  // Ensure pipeline start time is preserved\n  console.log(`Pipeline start preserved: ${json.pipeline_start_timestamp}`);\n  \n  return {json};\n});"
      },
      "typeVersion": 2
    },
    {
      "id": "9255e34b-be94-4630-8887-fc7a0aafd739",
      "name": "Combinar Datos Antes de Verificación1",
      "type": "n8n-nodes-base.merge",
      "position": [
        2672,
        576
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "combineBy": "combineByPosition"
      },
      "typeVersion": 3
    },
    {
      "id": "0e3dc51e-4891-4094-ad20-341424dd04df",
      "name": "Verificar Archivos de Salida y Obtener Tamaños1",
      "type": "n8n-nodes-base.executeCommand",
      "position": [
        2832,
        576
      ],
      "parameters": {
        "command": "=powershell -Command \"$xlsx='{{ $json.expected_output }}'; $dae='{{ $json.expected_output_dae }}'; $revit='{{ $json.file_path }}'; $xlsxExists=Test-Path -LiteralPath $xlsx; $daeExists=Test-Path -LiteralPath $dae; $revitExists=Test-Path -LiteralPath $revit; $xlsxSize=if($xlsxExists){(Get-Item -LiteralPath $xlsx).Length}else{0}; $daeSize=if($daeExists){(Get-Item -LiteralPath $dae).Length}else{0}; $revitSize=if($revitExists){(Get-Item -LiteralPath $revit).Length}else{0}; Write-Output ('XLSX_EXISTS:'+$xlsxExists+'|XLSX_SIZE:'+$xlsxSize+'|DAE_EXISTS:'+$daeExists+'|DAE_SIZE:'+$daeSize+'|REVIT_EXISTS:'+$revitExists+'|REVIT_SIZE:'+$revitSize)\"",
        "executeOnce": false
      },
      "typeVersion": 1
    },
    {
      "id": "8d038788-44e7-49be-a999-b97ac4f96fac",
      "name": "Completar Verificación de Archivo1",
      "type": "n8n-nodes-base.code",
      "position": [
        2992,
        576
      ],
      "parameters": {
        "jsCode": "// Complete file verification with processing time - preserve pipeline start time\nconst allInputs = $input.all();\nconst mergedDataItems = $('Merge Data Before Verification1').all();\n\nconsole.log(`=== Complete File Verification ===`);\nconsole.log(`Total verification inputs: ${allInputs.length}`);\nconsole.log(`Total merged data items: ${mergedDataItems.length}`);\n\nconst results = [];\n\n// Process each file\nallInputs.forEach((input, index) => {\n  const verificationData = input.json || {};\n  \n  // Find corresponding merged data by matching index\n  const mergedData = mergedDataItems[index]?.json || {};\n  \n  // Extract all necessary data from merged node\n  const fileName = mergedData.file_name || 'unknown_file';\n  const filePath = mergedData.file_path || '';\n  const expectedOutput = mergedData.expected_output || '';\n  const expectedOutputDae = mergedData.expected_output_dae || '';\n  const fileIndex = mergedData.index || index + 1;\n  const processingTime = mergedData.processing_time || 0;\n  const config = mergedData.config || {};\n  const conversionStartTime = mergedData.conversion_start_time || '';\n  const conversionEndTime = mergedData.conversion_end_time || '';\n  const conversionEndTimestamp = mergedData.conversion_end_timestamp || Date.now();\n  \n  // IMPORTANT: Preserve pipeline start time\n  const pipelineStartTime = mergedData.pipeline_start_time || '';\n  const pipelineStartTimestamp = mergedData.pipeline_start_timestamp || 0;\n\n  console.log(`Processing verification for file ${fileIndex}: ${fileName}`);\n  console.log('Actual processing time:', processingTime, 'seconds');\n  console.log('Pipeline start timestamp:', pipelineStartTimestamp);\n\n  // Get verification output from the current input\n  let verificationOutput = verificationData.stdout || verificationData.verification_output || '';\n\n  // Parse PowerShell output\n  let successXlsx = false;\n  let successDae = false;\n  let successRevit = false;\n  let fileSizeXlsx = 0;\n  let fileSizeDae = 0;\n  let fileSizeRevit = 0;\n\n  try {\n    if (verificationOutput && verificationOutput.includes('|')) {\n      const parts = verificationOutput.split('|');\n      \n      parts.forEach(part => {\n        if (part && part.includes('XLSX_EXISTS:')) {\n          successXlsx = part.includes('XLSX_EXISTS:True');\n        }\n        if (part && part.includes('XLSX_SIZE:')) {\n          const sizeStr = part.replace('XLSX_SIZE:', '').trim();\n          fileSizeXlsx = parseInt(sizeStr) || 0;\n        }\n        if (part && part.includes('DAE_EXISTS:')) {\n          successDae = part.includes('DAE_EXISTS:True');\n        }\n        if (part && part.includes('DAE_SIZE:')) {\n          const sizeStr = part.replace('DAE_SIZE:', '').trim();\n          fileSizeDae = parseInt(sizeStr) || 0;\n        }\n        if (part && part.includes('REVIT_EXISTS:')) {\n          successRevit = part.includes('REVIT_EXISTS:True');\n        }\n        if (part && part.includes('REVIT_SIZE:')) {\n          const sizeStr = part.replace('REVIT_SIZE:', '').trim();\n          fileSizeRevit = parseInt(sizeStr) || 0;\n        }\n      });\n    }\n  } catch (error) {\n    console.log('Error parsing verification output:', error);\n  }\n\n  const finalSuccess = successXlsx && successDae && fileSizeXlsx > 0 && fileSizeDae > 0;\n\n  const statusMessage = finalSuccess \n    ? `✅ [${fileIndex}] Successfully converted: ${fileName} (XLSX: ${Math.round(fileSizeXlsx/1024)}KB, DAE: ${Math.round(fileSizeDae/1024)}KB, ${processingTime.toFixed(1)}s)`\n    : `❌ [${fileIndex}] Failed to convert: ${fileName} (XLSX: ${successXlsx && fileSizeXlsx > 0 ? '✓' : '✗'}, DAE: ${successDae && fileSizeDae > 0 ? '✓' : '✗'}, ${processingTime.toFixed(1)}s)`;\n\n  const result = {\n    file_name: fileName,\n    file_path: filePath,\n    expected_output: expectedOutput,\n    expected_output_dae: expectedOutputDae,\n    success: finalSuccess,\n    success_xlsx: successXlsx && fileSizeXlsx > 0,\n    success_dae: successDae && fileSizeDae > 0,\n    processing_time: processingTime,\n    file_size: fileSizeXlsx + fileSizeDae,\n    file_size_xlsx: fileSizeXlsx,\n    file_size_dae: fileSizeDae,\n    file_size_revit: fileSizeRevit,\n    index: fileIndex,\n    status: finalSuccess ? 'converted' : 'failed',\n    message: statusMessage,\n    timestamp: new Date().toISOString(),\n    verification_output: verificationOutput,\n    config: config,\n    conversion_start_time: conversionStartTime,\n    conversion_end_time: conversionEndTime,\n    conversion_end_timestamp: conversionEndTimestamp,\n    pipeline_start_time: pipelineStartTime,\n    pipeline_start_timestamp: pipelineStartTimestamp\n  };\n\n  console.log(`Verification complete for ${fileName}: ${result.message}`);\n  results.push({ json: result });\n});\n\nconsole.log(`=== Returning ${results.length} verification results ===`);\nreturn results;"
      },
      "typeVersion": 2
    },
    {
      "id": "44336609-1655-4f5b-92b4-9b6165620036",
      "name": "Generar Reporte HTML1",
      "type": "n8n-nodes-base.code",
      "position": [
        3152,
        576
      ],
      "parameters": {
        "jsCode": "// Generate HTML Report with CORRECT pipeline timing and no-collada handling\nconst results = $input.all();\n\n// Get the current time as pipeline end\nconst pipelineEndTime = Date.now();\n\n// Find the pipeline start timestamp from the data\nlet pipelineStartTimestamp = null;\nlet config = {};\n\n// Extract timing and config from results\nfor (const result of results) {\n  if (result.json) {\n    // Get pipeline start timestamp\n    if (result.json.pipeline_start_timestamp && !pipelineStartTimestamp) {\n      pipelineStartTimestamp = result.json.pipeline_start_timestamp;\n      console.log('Found pipeline start timestamp:', pipelineStartTimestamp);\n    }\n    \n    // Get config\n    if (result.json.config && Object.keys(result.json.config).length > 0) {\n      config = result.json.config;\n    }\n  }\n}\n\n// Check if no-collada option is enabled\nconst noColladaEnabled = config.options && config.options.includes('no-collada');\nconsole.log('No-collada mode enabled:', noColladaEnabled);\n\n// Calculate total pipeline time in minutes\nlet totalPipelineMinutes = '0.00';\nif (pipelineStartTimestamp) {\n  const totalMs = pipelineEndTime - pipelineStartTimestamp;\n  totalPipelineMinutes = (totalMs / 60000).toFixed(2);\n  console.log('Pipeline timing:', {\n    start: pipelineStartTimestamp,\n    end: pipelineEndTime,\n    totalMs: totalMs,\n    totalMinutes: totalPipelineMinutes\n  });\n} else {\n  console.warn('No pipeline start timestamp found!');\n}\n\n// Use config output folder or fallback\nconst outputFolder = config.output_folder || 'C:\\\\temp';\n\n// Count successful and failed conversions\nlet totalFiles = 0;\nlet successfulConversions = 0;\nlet failedConversions = 0;\nlet totalInputSize = 0;\nlet totalOutputSize = 0;\n\nconst successfulFiles = [];\nconst failedFiles = [];\n\nresults.forEach((result, index) => {\n  const data = result.json || {};\n  totalFiles++;\n  \n  const processingTime = data.processing_time || 0;\n  const xlsxSuccess = data.success_xlsx || false;\n  const daeSuccess = data.success_dae || false;\n  const fileSizeRevit = data.file_size_revit || 0;\n  const fileSizeXlsx = data.file_size_xlsx || 0;\n  const fileSizeDae = data.file_size_dae || 0;\n  \n  totalInputSize += fileSizeRevit;\n  \n  // Modified success logic for no-collada mode\n  let isSuccess = false;\n  if (noColladaEnabled) {\n    // In no-collada mode, only XLSX matters\n    isSuccess = xlsxSuccess;\n    if (isSuccess) {\n      totalOutputSize += fileSizeXlsx;\n    }\n  } else {\n    // Normal mode: both XLSX and DAE must succeed\n    isSuccess = xlsxSuccess && daeSuccess;\n    if (isSuccess) {\n      totalOutputSize += fileSizeXlsx + fileSizeDae;\n    }\n  }\n  \n  if (isSuccess) {\n    successfulConversions++;\n    successfulFiles.push({\n      name: data.file_name || `File ${index + 1}`,\n      originalSize: Math.round(fileSizeRevit / 1024),\n      xlsxSize: Math.round(fileSizeXlsx / 1024),\n      daeSize: Math.round(fileSizeDae / 1024),\n      totalSize: Math.round((fileSizeXlsx + (noColladaEnabled ? 0 : fileSizeDae)) / 1024),\n      xlsxPath: data.expected_output || '',\n      daePath: data.expected_output_dae || '',\n      noCollada: noColladaEnabled\n    });\n  } else {\n    failedConversions++;\n    failedFiles.push({\n      name: data.file_name || `File ${index + 1}`,\n      originalSize: Math.round(fileSizeRevit / 1024),\n      xlsxStatus: xlsxSuccess ? '✓' : '✗',\n      daeStatus: daeSuccess ? '✓' : '✗'\n    });\n  }\n});\n\nconst successRate = totalFiles > 0 ? Math.round((successfulConversions / totalFiles) * 100) : 0;\n\n// Generate timestamp for filename\nconst now = new Date();\nconst timestamp = now.toISOString().slice(0,19).replace(/:/g, '-');\nconst fileName = `CAD_Conversion_Report_${timestamp}.html`;\nconst fullPath = `${outputFolder}\\\\${fileName}`.replace(/\\\\\\\\/g, '\\\\');\n\nconsole.log('Report summary:', {\n  totalFiles,\n  successfulConversions,\n  failedConversions,\n  successRate: successRate + '%',\n  totalPipelineTime: totalPipelineMinutes + ' minutes',\n  outputPath: fullPath,\n  noColladaMode: noColladaEnabled\n});\n\n// Create HTML content with professional styling\nconst htmlContent = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>CAD Project Batch Conversion Report</title>\n    <style>\n        * {\n            margin: 0;\n            padding: 0;\n            box-sizing: border-box;\n        }\n        \n        body { \n            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', Arial, sans-serif; \n            background: #f5f6fa;\n            color: #2c3e50;\n            line-height: 1.6;\n        }\n        \n        .container { \n            max-width: 1400px; \n            margin: 0 auto; \n            background: white; \n            box-shadow: 0 0 40px rgba(0, 0, 0, 0.08); \n        }\n        \n        .header { \n            background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);\n            color: white; \n            padding: 60px 40px;\n            position: relative;\n            overflow: hidden;\n        }\n        \n        .header::before {\n            content: '';\n            position: absolute;\n            top: -50%;\n            right: -10%;\n            width: 40%;\n            height: 200%;\n            background: rgba(255, 255, 255, 0.05);\n            transform: rotate(35deg);\n        }\n        \n        .header-content {\n            position: relative;\n            z-index: 1;\n        }\n        \n        .header h1 { \n            font-size: 2.8rem; \n            font-weight: 300;\n            letter-spacing: -1px;\n            margin-bottom: 15px;\n        }\n        \n        .header .subtitle { \n            font-size: 1.1rem;\n            opacity: 0.85;\n            font-weight: 400;\n        }\n        \n        .header .timestamp {\n            margin-top: 20px;\n            font-size: 0.95rem;\n            opacity: 0.7;\n        }\n        \n        .content {\n            padding: 40px;\n        }\n        \n        .metrics { \n            display: grid; \n            grid-template-columns: repeat(5, 1fr); \n            gap: 20px; \n            margin: -20px 0 40px 0;\n            position: relative;\n            z-index: 10;\n        }\n        \n        @media (max-width: 1200px) {\n            .metrics {\n                grid-template-columns: repeat(3, 1fr);\n            }\n        }\n        \n        @media (max-width: 768px) {\n            .metrics {\n                grid-template-columns: repeat(2, 1fr);\n            }\n        }\n        \n        @media (max-width: 480px) {\n            .metrics {\n                grid-template-columns: 1fr;\n            }\n        }\n        \n        .metric { \n            background: white;\n            padding: 30px 20px;\n            border-radius: 12px;\n            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);\n            transition: all 0.3s ease;\n            border: 1px solid #e8ecf1;\n            position: relative;\n            overflow: hidden;\n            text-align: center;\n        }\n        \n        .metric::before {\n            content: '';\n            position: absolute;\n            top: 0;\n            left: 0;\n            width: 100%;\n            height: 4px;\n            background: linear-gradient(90deg, #3b82f6 0%, #2563eb 100%);\n        }\n        \n        .metric:hover {\n            transform: translateY(-3px);\n            box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);\n        }\n        \n        .metric-value { \n            font-size: 2.2rem; \n            font-weight: 600;\n            color: #1e3c72;\n            margin-bottom: 8px;\n            line-height: 1;\n        }\n        \n        .metric-label { \n            font-size: 0.85rem; \n            color: #64748b;\n            font-weight: 500;\n            text-transform: uppercase;\n            letter-spacing: 0.5px;\n        }\n        \n        .metric.highlight {\n            background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);\n            border-color: #3b82f6;\n        }\n        \n        .metric.highlight::before {\n            background: linear-gradient(90deg, #10b981 0%, #059669 100%);\n        }\n        \n        .section { \n            margin: 40px 0;\n            background: white;\n            border-radius: 12px;\n            overflow: hidden;\n            box-shadow: 0 2px 15px rgba(0, 0, 0, 0.06);\n            border: 1px solid #e8ecf1;\n        }\n        \n        .section-header {\n            background: #f8fafc;\n            padding: 24px 32px;\n            border-bottom: 1px solid #e8ecf1;\n        }\n        \n        .section h2 { \n            color: #1e293b;\n            font-size: 1.4rem;\n            font-weight: 600;\n            display: flex;\n            align-items: center;\n            gap: 10px;\n        }\n        \n        .section-content {\n            padding: 0;\n        }\n        \n        table { \n            width: 100%; \n            border-collapse: collapse;\n        }\n        \n        th, td { \n            padding: 16px 24px;\n            text-align: left;\n            border-bottom: 1px solid #f1f5f9;\n        }\n        \n        th { \n            background: #f8fafc;\n            font-weight: 600;\n            color: #475569;\n            font-size: 0.875rem;\n            text-transform: uppercase;\n            letter-spacing: 0.5px;\n            position: sticky;\n            top: 0;\n            z-index: 1;\n        }\n        \n        tr:last-child td {\n            border-bottom: none;\n        }\n        \n        tr:hover { \n            background: #f8fafc;\n        }\n        \n        .status-success { \n            background: #10b981;\n            color: white;\n            padding: 6px 12px;\n            border-radius: 6px;\n            font-size: 0.75rem;\n            font-weight: 600;\n            display: inline-block;\n            text-transform: uppercase;\n            letter-spacing: 0.5px;\n        }\n        \n        .status-failed { \n            background: #ef4444;\n            color: white;\n            padding: 6px 12px;\n            border-radius: 6px;\n            font-size: 0.75rem;\n            font-weight: 600;\n            display: inline-block;\n            text-transform: uppercase;\n            letter-spacing: 0.5px;\n        }\n        \n        .status-skipped { \n            background: #6b7280;\n            color: white;\n            padding: 6px 12px;\n            border-radius: 6px;\n            font-size: 0.75rem;\n            font-weight: 600;\n            display: inline-block;\n            text-transform: uppercase;\n            letter-spacing: 0.5px;\n        }\n        \n        .config-section {\n            background: #f8fafc;\n            padding: 32px;\n            border-radius: 12px;\n            margin: 40px 0;\n        }\n        \n        .config-grid {\n            display: grid;\n            grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));\n            gap: 24px;\n            margin-top: 20px;\n        }\n        \n        .config-item {\n            display: flex;\n            align-items: flex-start;\n            gap: 16px;\n        }\n        \n        .config-label {\n            font-weight: 600;\n            color: #475569;\n            min-width: 140px;\n            font-size: 0.9rem;\n        }\n        \n        .config-value {\n            color: #1e293b;\n            font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;\n            background: white;\n            padding: 8px 12px;\n            border-radius: 6px;\n            font-size: 0.85rem;\n            word-break: break-all;\n            flex: 1;\n            border: 1px solid #e2e8f0;\n        }\n        \n        .footer { \n            background: #f8fafc;\n            padding: 40px;\n            text-align: center;\n            color: #64748b;\n            border-top: 1px solid #e8ecf1;\n        }\n        \n        .footer .company {\n            font-size: 1.1rem;\n            font-weight: 600;\n            color: #1e293b;\n            margin-bottom: 8px;\n        }\n        \n        .footer .version {\n            font-size: 0.85rem;\n            margin-top: 16px;\n            color: #94a3b8;\n        }\n        \n        .report-location {\n            background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);\n            border: 1px solid #3b82f6;\n            border-radius: 12px;\n            padding: 20px 28px;\n            margin: 30px 0;\n            text-align: center;\n            color: #1e3c72;\n            font-size: 0.95rem;\n        }\n        \n        .report-location strong {\n            color: #1e3c72;\n            font-weight: 600;\n        }\n        \n        .size-cell {\n            font-family: 'SF Mono', Monaco, monospace;\n            font-size: 0.9rem;\n        }\n        \n        .file-link {\n            color: #3b82f6;\n            text-decoration: none;\n            font-weight: 500;\n            transition: all 0.2s ease;\n            position: relative;\n            display: inline-block;\n            padding: 2px 0;\n        }\n        \n        .file-link:hover {\n            color: #2563eb;\n            text-decoration: underline;\n        }\n        \n        .tooltip {\n            position: relative;\n            display: inline-block;\n        }\n        \n        .tooltip .tooltiptext {\n            visibility: hidden;\n            width: 280px;\n            background-color: #333;\n            color: #fff;\n            text-align: center;\n            border-radius: 8px;\n            padding: 12px 16px;\n            position: absolute;\n            z-index: 1000;\n            bottom: 125%;\n            left: 50%;\n            margin-left: -140px;\n            opacity: 0;\n            transition: opacity 0.3s;\n            font-size: 0.85rem;\n            line-height: 1.4;\n            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);\n        }\n        \n        .tooltip .tooltiptext::after {\n            content: \"\";\n            position: absolute;\n            top: 100%;\n            left: 50%;\n            margin-left: -5px;\n            border-width: 5px;\n            border-style: solid;\n            border-color: #333 transparent transparent transparent;\n        }\n        \n        .tooltip:hover .tooltiptext {\n            visibility: visible;\n            opacity: 1;\n        }\n        \n        .no-collada-notice {\n            background: #fef3c7;\n            border: 1px solid #fbbf24;\n            border-radius: 8px;\n            padding: 16px;\n            margin: 20px 0;\n            color: #92400e;\n            font-size: 0.9rem;\n            text-align: center;\n        }\n        \n        .no-collada-notice strong {\n            color: #78350f;\n        }\n        \n        @media print {\n            body { background: white; }\n            .container { box-shadow: none; }\n            .metric { box-shadow: none; border: 1px solid #e5e7eb; }\n            .section { box-shadow: none; page-break-inside: avoid; }\n            .tooltip .tooltiptext { display: none; }\n        }\n    </style>\n</head>\n<body>\n    <div class=\"container\">\n        <div class=\"header\">\n            <div class=\"header-content\">\n                <h1>CAD Project Batch Conversion Report</h1>\n                <p class=\"subtitle\">Automated conversion pipeline execution summary</p>\n                <p class=\"timestamp\">${now.toLocaleString('en-US', { \n                    weekday: 'long', \n                    year: 'numeric', \n                    month: 'long', \n                    day: 'numeric', \n                    hour: '2-digit', \n                    minute: '2-digit' \n                })}</p>\n            </div>\n        </div>\n        \n        <div class=\"content\">\n            <div class=\"metrics\">\n                <div class=\"metric\">\n                    <div class=\"metric-value\">${totalFiles}</div>\n                    <div class=\"metric-label\">Total Files</div>\n                </div>\n                <div class=\"metric highlight\">\n                    <div class=\"metric-value\">${successRate}%</div>\n                    <div class=\"metric-label\">Success Rate</div>\n                </div>\n                <div class=\"metric\">\n                    <div class=\"metric-value\">${totalPipelineMinutes}</div>\n                    <div class=\"metric-label\">Total Time (Minutes)</div>\n                </div>\n                <div class=\"metric\">\n                    <div class=\"metric-value\">${Math.round(totalInputSize / 1048576)} MB</div>\n                    <div class=\"metric-label\">Total Input Size</div>\n                </div>\n                <div class=\"metric\">\n                    <div class=\"metric-value\">${Math.round(totalOutputSize / 1048576)} MB</div>\n                    <div class=\"metric-label\">Total Output Size</div>\n                </div>\n            </div>\n\n            <div class=\"report-location\">\n                <strong>Report Location:</strong> ${fullPath}\n            </div>\n\n            ${noColladaEnabled ? `\n            <div class=\"no-collada-notice\">\n                <strong>Note:</strong> DAE (Collada) file generation was skipped as per configuration (no-collada option enabled).\n            </div>\n            ` : ''}\n\n            ${successfulFiles.length > 0 ? `\n            <div class=\"section\">\n                <div class=\"section-header\">\n                    <h2>✅ Successfully Converted Files (${successfulFiles.length})</h2>\n                </div>\n                <div class=\"section-content\">\n                    <table>\n                        <thead>\n                            <tr>\n                                <th>File Name</th>\n                                <th>Original Size</th>\n                                <th>Excel Output</th>\n                                ${noColladaEnabled ? '<th>DAE Output</th>' : '<th>DAE Output</th>'}\n                                <th>Total Output</th>\n                            </tr>\n                        </thead>\n                        <tbody>\n                            ${successfulFiles.map(file => `\n                                <tr>\n                                    <td><strong>${file.name}</strong></td>\n                                    <td class=\"size-cell\">${file.originalSize} KB</td>\n                                    <td class=\"size-cell\">\n                                        <a href=\"file:///${file.xlsxPath.replace(/\\\\/g, '/')}\" class=\"file-link\" target=\"_blank\">${file.xlsxSize} KB</a>\n                                    </td>\n                                    <td class=\"size-cell\">\n                                        ${noColladaEnabled ? \n                                            '<span class=\"status-skipped\">SKIPPED</span>' : \n                                            `<div class=\"tooltip\">\n                                                <a href=\"file:///${file.daePath.replace(/\\\\/g, '/')}\" class=\"file-link\" target=\"_blank\">${file.daeSize} KB</a>\n                                                <span class=\"tooltiptext\">💡 Tip: Use the free CAD Assistant software for the best viewing experience of DAE files</span>\n                                            </div>`\n                                        }\n                                    </td>\n                                    <td class=\"size-cell\"><strong>${file.totalSize} KB</strong></td>\n                                </tr>\n                            `).join('')}\n                        </tbody>\n                    </table>\n                </div>\n            </div>\n            ` : ''}\n\n            ${failedFiles.length > 0 ? `\n            <div class=\"section\">\n                <div class=\"section-header\">\n                    <h2>❌ Failed Conversions (${failedFiles.length})</h2>\n                </div>\n                <div class=\"section-content\">\n                    <table>\n                        <thead>\n                            <tr>\n                                <th>File Name</th>\n                                <th>Original Size</th>\n                                <th>XLSX Status</th>\n                                <th>DAE Status</th>\n                            </tr>\n                        </thead>\n                        <tbody>\n                            ${failedFiles.map(file => `\n                                <tr>\n                                    <td><strong>${file.name}</strong></td>\n                                    <td class=\"size-cell\">${file.originalSize} KB</td>\n                                    <td><span class=\"status-${file.xlsxStatus === '✓' ? 'success' : 'failed'}\">${file.xlsxStatus === '✓' ? 'SUCCESS' : 'FAILED'}</span></td>\n                                    <td>${noColladaEnabled && file.xlsxStatus === '✓' ? \n                                        '<span class=\"status-skipped\">SKIPPED</span>' : \n                                        `<span class=\"status-${file.daeStatus === '✓' ? 'success' : 'failed'}\">${file.daeStatus === '✓' ? 'SUCCESS' : 'FAILED'}</span>`\n                                    }</td>\n                                </tr>\n                            `).join('')}\n                        </tbody>\n                    </table>\n                </div>\n            </div>\n            ` : ''}\n\n            <div class=\"config-section\">\n                <h2 style=\"color: #1e293b; font-size: 1.4rem; font-weight: 600; margin-bottom: 20px;\">\n                    ⚙️ Configuration Details\n                </h2>\n                <div class=\"config-grid\">\n                    <div class=\"config-item\">\n                        <span class=\"config-label\">Source Folder:</span>\n                        <span class=\"config-value\">${config.source_folder || 'Not specified'}</span>\n                    </div>\n                    <div class=\"config-item\">\n                        <span class=\"config-label\">Output Folder:</span>\n                        <span class=\"config-value\">${config.output_folder || 'Not specified'}</span>\n                    </div>\n                    <div class=\"config-item\">\n                        <span class=\"config-label\">File Extension:</span>\n                        <span class=\"config-value\">${config.file_extension || 'Not specified'}</span>\n                    </div>\n                    <div class=\"config-item\">\n                        <span class=\"config-label\">Include Subfolders:</span>\n                        <span class=\"config-value\">${config.include_subfolders ? 'Yes' : 'No'}</span>\n                    </div>\n                    <div class=\"config-item\">\n                        <span class=\"config-label\">Converter Options:</span>\n                        <span class=\"config-value\">${config.options || 'None'}</span>\n                    </div>\n                    <div class=\"config-item\">\n                        <span class=\"config-label\">Generated:</span>\n                        <span class=\"config-value\">${now.toLocaleString()}</span>\n                    </div>\n                    <div class=\"config-item\">\n                        <span class=\"config-label\">Workflow ID:</span>\n                        <span class=\"config-value\">FYFQhblt4gILLSpe</span>\n                    </div>\n                </div>\n            </div>\n        </div>\n\n        <div class=\"footer\">\n            <div class=\"company\">DataDrivenConstruction.io</div>\n            <div>Professional CAD Conversion Pipeline</div>\n            <div class=\"version\">Version 2.0 | Automated Batch Processing System</div>\n        </div>\n    </div>\n</body>\n</html>`;\n\n// Return data for saving and opening\nreturn [{\n    json: {\n        html_content: htmlContent,\n        file_name: fileName,\n        full_path: fullPath,\n        output_folder: outputFolder,\n        total_files: totalFiles,\n        successful_conversions: successfulConversions,\n        failed_conversions: failedConversions,\n        success_rate: successRate,\n        total_pipeline_time: totalPipelineMinutes,\n        config: config,\n        summary_message: `✅ Conversion Complete! ${successfulConversions}/${totalFiles} files converted successfully (${successRate}% success rate) in ${totalPipelineMinutes} minutes`\n    }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "21c9bd6d-e1a0-4697-b99d-c51e666af986",
      "name": "Preparar Datos Binarios1",
      "type": "n8n-nodes-base.code",
      "position": [
        3312,
        576
      ],
      "parameters": {
        "jsCode": "// Prepare binary data for file saving\nconst item = $input.first().json;\n\nconsole.log('Preparing binary data for file:', item.file_name);\nconsole.log('Full path:', item.full_path);\n\nif (!item.html_content) {\n  throw new Error('No HTML content found');\n}\n\nreturn [{\n  json: item,\n  binary: {\n    report: {\n      data: Buffer.from(item.html_content, 'utf-8').toString('base64'),\n      mimeType: 'text/html',\n      fileName: item.file_name,\n      fileExtension: 'html'\n    }\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "3ba8b465-5da4-4b79-83e2-2bdb63222b6b",
      "name": "Guardar Archivo HTML1",
      "type": "n8n-nodes-base.writeBinaryFile",
      "position": [
        3472,
        576
      ],
      "parameters": {
        "options": {},
        "fileName": "={{ $json.full_path }}",
        "dataPropertyName": "report"
      },
      "typeVersion": 1
    },
    {
      "id": "94de0bbb-402b-4dea-bcaf-f98fdc678048",
      "name": "Verificar y Preparar Ruta1",
      "type": "n8n-nodes-base.code",
      "position": [
        3632,
        576
      ],
      "parameters": {
        "jsCode": "// Verify file was saved and prepare for opening\nconst data = $input.first().json;\nconst fullPath = data.full_path || '';\n\nconsole.log('Preparing to open file:', fullPath);\n\n// Normalize path for Windows\nconst windowsPath = fullPath.replace(/\\\\\\\\/g, '\\\\').replace(/\\//g, '\\\\');\n\nreturn [{\n  json: {\n    ...data,\n    windows_path: windowsPath,\n    command_to_open: `cmd /c start \"\" \"${windowsPath}\"`\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "6558ac19-23c1-44c9-8b7b-3615052cd4a5",
      "name": "Abrir Reporte HTML1",
      "type": "n8n-nodes-base.executeCommand",
      "position": [
        3792,
        576
      ],
      "parameters": {
        "command": "=cmd /c start \"\" \"{{ $json.windows_path }}\""
      },
      "typeVersion": 1
    },
    {
      "id": "cdf12580-ade1-4ae0-a699-980aca156c98",
      "name": "Notificación de Finalización1",
      "type": "n8n-nodes-base.executeCommand",
      "position": [
        3952,
        576
      ],
      "parameters": {
        "command": "=echo 🏁 CAD Conversion Pipeline Completed Successfully! & echo 📋 Summary: {{ $json.summary_message }} & echo 📁 Report Location: {{ $json.windows_path }} & echo ✨ All processes finished successfully! & echo. & echo 🎯 Next Steps: & echo    1. Check the HTML report that opened in your browser & echo    2. Verify output files in the specified folder & echo    3. Review conversion results and metrics & echo. & echo ✅ Pipeline execution completed!",
        "executeOnce": false
      },
      "typeVersion": 1
    },
    {
      "id": "dc253ac4-c296-4a8c-897c-380e5e14b683",
      "name": "Respuesta Sin Archivos Encontrados1",
      "type": "n8n-nodes-base.set",
      "position": [
        1520,
        784
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "no-files-message",
              "name": "message",
              "type": "string",
              "value": "⚠️ No files found to convert with the specified parameters"
            },
            {
              "id": "no-files-details",
              "name": "details",
              "type": "string",
              "value": "Please check: 1) Source folder path exists, 2) File extension is correct, 3) Files exist in the specified location"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "ee8bb887-c553-412b-9be5-c865f08fbd82",
      "name": "Nota Adhesiva7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -144,
        112
      ],
      "parameters": {
        "color": 6,
        "width": 756,
        "height": 844,
        "content": "## 🏁 GROUP 1: INITIALIZATION\n\n**Nodes in this group:**\n• Manual Trigger\n• Capture Pipeline Start Time\n• Set Configuration Parameters\n• Merge Pipeline Start with Config\n\n**What happens here:**\n1️⃣ Pipeline starts manually or on schedule\n2️⃣ Current timestamp is captured for metrics\n3️⃣ All settings are loaded (paths, folders, etc.)\n4️⃣ Configuration merged with timing data\n\n💡 **Key:** This sets up everything needed for the conversion process"
      },
      "typeVersion": 1
    },
    {
      "id": "0ca6f619-b073-41b1-a248-0f2b62333773",
      "name": "Nota Adhesiva9",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        624,
        112
      ],
      "parameters": {
        "color": 5,
        "width": 1024,
        "height": 840,
        "content": "## 🔍 GROUP 2: FILE DISCOVERY\n\n**Nodes in this group:**\n• Find CAD Files\n• Merge Config with Search Results\n• Process File List\n• Check if Files Exist\n\n**What happens here:**\n1️⃣ PowerShell scans the source folder\n2️⃣ Filters files by extension (.rvt, .ifc, etc.)\n3️⃣ Creates a list with full paths\n4️⃣ Validates that files were found\n\n📁 **Output:** Array of file objects with paths\n❌ **If no files:** Pipeline stops gracefully"
      },
      "typeVersion": 1
    },
    {
      "id": "8dcff587-4f09-4e86-a065-bdd056be456d",
      "name": "Nota Adhesiva10",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1664,
        112
      ],
      "parameters": {
        "color": 5,
        "width": 316,
        "height": 844,
        "content": "## 🔄 GROUP 3: BATCH PREPARATION\n\n**Nodes in this group:**\n• Split Files for Processing\n• Create Output Directory\n• Merge File Data\n\n**What happens here:**\n1️⃣ File list is split into individual items\n2️⃣ Output directory is created (if needed)\n3️⃣ Each file gets conversion parameters\n\n🎯 **Purpose:** Enables parallel processing\n📊 **Data added:** Expected output paths for XLSX & DAE"
      },
      "typeVersion": 1
    },
    {
      "id": "ae685ee4-bbee-4b56-85e0-26f3da9053fd",
      "name": "Nota Adhesiva11",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2000,
        112
      ],
      "parameters": {
        "color": 5,
        "width": 800,
        "height": 840,
        "content": "## ⚡ GROUP 4: CONVERSION EXECUTION\n\n**Nodes in this group:**\n• Capture Start Time\n• Small Delay\n• Execute Conversion\n• Calculate Conversion Time\n\n**What happens here:**\n1️⃣ Timestamp captured for each file\n2️⃣ Small delay prevents overload\n3️⃣ RvtExporter.exe runs the conversion\n4️⃣ Processing time calculated\n\n🔧 **Command:** \n`RvtExporter.exe [input] [output.xlsx] [output.dae]`\n\n⏱️ **Conversion Time:** 1 Minute per 100Mb."
      },
      "typeVersion": 1
    },
    {
      "id": "5fe7b6d6-7a36-41ae-a431-a93803227a50",
      "name": "Nota Adhesiva15",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2816,
        112
      ],
      "parameters": {
        "color": 6,
        "width": 284,
        "height": 836,
        "content": "## ✅ GROUP 5: VALIDATION\n\n**Nodes in this group:**\n• Merge Data Before Verification\n• Verify Output Files and Get Sizes\n• Complete File Verification\n\n**What happens here:**\n1️⃣ All conversion data is consolidated\n2️⃣ PowerShell checks if outputs exist\n3️⃣ File sizes are measured\n4️⃣ Success/failure status determined\n\n**Success criteria:**\n• Both XLSX and DAE files exist\n• File sizes > 0 bytes\n• No conversion errors"
      },
      "typeVersion": 1
    },
    {
      "id": "2a199a8f-7206-4b18-9e87-f9dc4db56d8d",
      "name": "Nota Adhesiva16",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3120,
        112
      ],
      "parameters": {
        "width": 456,
        "height": 832,
        "content": "## 📊 GROUP 6: REPORTING\n\n**Nodes in this group:**\n• Generate HTML Report\n• Prepare Binary Data\n• Save HTML File\n\n**What happens here:**\n1️⃣ All results compiled into statistics\n2️⃣ Professional HTML report generated\n3️⃣ Report saved to output folder\n\n**Report includes:**\n• Success rate & metrics\n• Processing times\n• File sizes & links\n• Detailed conversion log"
      },
      "typeVersion": 1
    },
    {
      "id": "565d9855-8447-4c4e-ab57-b652a8733040",
      "name": "Nota Adhesiva17",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3600,
        112
      ],
      "parameters": {
        "width": 484,
        "height": 828,
        "content": "## 🎯 GROUP 7: FINALIZATION\n\n**Nodes in this group:**\n• Verify and Prepare Path\n• Open HTML Report\n• Final Completion Notice\n\n**What happens here:**\n1️⃣ Report path normalized for Windows\n2️⃣ HTML report opens in browser\n3️⃣ Success message displayed\n\n**Final output:**\n• Report auto-opens\n• Summary in console\n• All files ready for use"
      },
      "typeVersion": 1
    },
    {
      "id": "32324229-f786-4023-bfc1-bc01ad05575d",
      "name": "Nota Adhesiva30",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -144,
        976
      ],
      "parameters": {
        "color": 4,
        "width": 488,
        "height": 308,
        "content": "## 📝 Configuration Example\n\n```\nconverter_path: \nC:\\DDC_Converter\\RvtExporter.exe\nsource_folder: \nC:\\Projects\\Building_A\noutput_folder: \nC:\\Projects\\Converted\nfile_extension: .rvt\ninclude_subfolders: false\n```"
      },
      "typeVersion": 1
    },
    {
      "id": "62e0570c-238d-40e2-8b1f-f1f71ca8f034",
      "name": "Activador Programado1",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -80,
        560
      ],
      "parameters": {
        "rule": {
          "interval": [
            {}
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "637d319f-4c7d-4f14-8676-16e6a839812d",
      "name": "Nota Adhesiva2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        368,
        304
      ],
      "parameters": {
        "color": 4,
        "width": 192,
        "height": 384,
        "content": "## ⬇️ Only modify the variables here  \n— everything else works automatically"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "7eb4a013-6adf-4f71-b5d8-1c78163410dd",
  "connections": {
    "edf42bdd-67ed-46d1-a522-cbc93538d894": {
      "main": [
        [
          {
            "node": "1c4e14d2-f9cc-4b4f-85f0-7c0245c32b18",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "2fe7360d-df50-4eb7-8799-441e7aed7a1a": {
      "main": [
        [
          {
            "node": "614d8fe1-6c80-4cb0-b941-5aa3998a2321",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "53648934-0bb7-49d3-83f9-ab4280bb3ec9": {
      "main": [
        [
          {
            "node": "894a4fae-cd2f-4418-b35b-fc6a06cda820",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "3ba8b465-5da4-4b79-83e2-2bdb63222b6b": {
      "main": [
        [
          {
            "node": "94de0bbb-402b-4dea-bcaf-f98fdc678048",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "1cbf1ee9-0710-4145-a54e-0c4c73e177a6": {
      "main": [
        [
          {
            "node": "627097e5-5d07-4386-a9d7-22dffe7ba4ab",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "6558ac19-23c1-44c9-8b7b-3615052cd4a5": {
      "main": [
        [
          {
            "node": "cdf12580-ade1-4ae0-a699-980aca156c98",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "cc6f17d1-4393-4c77-947a-53c42c01543c": {
      "main": [
        [
          {
            "node": "357d21f1-ff0b-4308-99a7-8ab3140b6dde",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "627097e5-5d07-4386-a9d7-22dffe7ba4ab": {
      "main": [
        [
          {
            "node": "edf42bdd-67ed-46d1-a522-cbc93538d894",
            "type": "main",
            "index": 0
          },
          {
            "node": "9255e34b-be94-4630-8887-fc7a0aafd739",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "1c4e14d2-f9cc-4b4f-85f0-7c0245c32b18": {
      "main": [
        [
          {
            "node": "f02f9f74-9b20-4857-92c3-4b0f4103f1b1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "21c9bd6d-e1a0-4697-b99d-c51e666af986": {
      "main": [
        [
          {
            "node": "3ba8b465-5da4-4b79-83e2-2bdb63222b6b",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "357d21f1-ff0b-4308-99a7-8ab3140b6dde": {
      "main": [
        [
          {
            "node": "84eff3b1-f5a8-4c84-82cb-2fa1d1bc23ea",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "dc253ac4-c296-4a8c-897c-380e5e14b683",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "44336609-1655-4f5b-92b4-9b6165620036": {
      "main": [
        [
          {
            "node": "21c9bd6d-e1a0-4697-b99d-c51e666af986",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "0330e04c-9572-439d-b09f-aa932b5e81ad": {
      "main": [
        [
          {
            "node": "1cbf1ee9-0710-4145-a54e-0c4c73e177a6",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "94de0bbb-402b-4dea-bcaf-f98fdc678048": {
      "main": [
        [
          {
            "node": "6558ac19-23c1-44c9-8b7b-3615052cd4a5",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "f02f9f74-9b20-4857-92c3-4b0f4103f1b1": {
      "main": [
        [
          {
            "node": "9255e34b-be94-4630-8887-fc7a0aafd739",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "8d038788-44e7-49be-a999-b97ac4f96fac": {
      "main": [
        [
          {
            "node": "44336609-1655-4f5b-92b4-9b6165620036",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "84eff3b1-f5a8-4c84-82cb-2fa1d1bc23ea": {
      "main": [
        [
          {
            "node": "0330e04c-9572-439d-b09f-aa932b5e81ad",
            "type": "main",
            "index": 0
          },
          {
            "node": "1cbf1ee9-0710-4145-a54e-0c4c73e177a6",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "614d8fe1-6c80-4cb0-b941-5aa3998a2321": {
      "main": [
        [
          {
            "node": "7b214d29-a619-4a41-8cfa-67f074470b89",
            "type": "main",
            "index": 0
          },
          {
            "node": "e64288d1-9d53-4036-afde-437c9892a927",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "7b214d29-a619-4a41-8cfa-67f074470b89": {
      "main": [
        [
          {
            "node": "e64288d1-9d53-4036-afde-437c9892a927",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "9255e34b-be94-4630-8887-fc7a0aafd739": {
      "main": [
        [
          {
            "node": "0e3dc51e-4891-4094-ad20-341424dd04df",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "894a4fae-cd2f-4418-b35b-fc6a06cda820": {
      "main": [
        [
          {
            "node": "cc6f17d1-4393-4c77-947a-53c42c01543c",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "e64288d1-9d53-4036-afde-437c9892a927": {
      "main": [
        [
          {
            "node": "53648934-0bb7-49d3-83f9-ab4280bb3ec9",
            "type": "main",
            "index": 0
          },
          {
            "node": "894a4fae-cd2f-4418-b35b-fc6a06cda820",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "0e3dc51e-4891-4094-ad20-341424dd04df": {
      "main": [
        [
          {
            "node": "8d038788-44e7-49be-a999-b97ac4f96fac",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Preguntas frecuentes

¿Cómo usar este flujo de trabajo?

Copie el código de configuración JSON de arriba, cree un nuevo flujo de trabajo en su instancia de n8n y seleccione "Importar desde JSON", pegue la configuración y luego modifique la configuración de credenciales según sea necesario.

¿En qué escenarios es adecuado este flujo de trabajo?

Avanzado - Extracción de documentos, IA Multimodal

¿Es de pago?

Este flujo de trabajo es completamente gratuito, puede importarlo y usarlo directamente. Sin embargo, tenga en cuenta que los servicios de terceros utilizados en el flujo de trabajo (como la API de OpenAI) pueden requerir un pago por su cuenta.

Información del flujo de trabajo
Nivel de dificultad
Avanzado
Número de nodos82
Categoría2
Tipos de nodos17
Descripción de la dificultad

Adecuado para usuarios avanzados, flujos de trabajo complejos con 16+ nodos

Autor

Founder DataDrivenConstruction.io | AEC Tech Consultant & Automation Expert | Bridging Software and Construction

Enlaces externos
Ver en n8n.io

Compartir este flujo de trabajo

Categorías

Categorías: 34