{
  "active": false,
  "connections": {
    "R\u00e9ception de Document": {
      "main": [
        [
          {
            "node": "Classification OCR",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Classification OCR": {
      "main": [
        [
          {
            "node": "Enregistrement Airtable",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Enregistrement Airtable": {
      "main": [
        [
          {
            "node": "V\u00e9rif Document Expir\u00e9",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "V\u00e9rif Document Expir\u00e9": {
      "main": [
        [
          {
            "node": "Alerte Document Expir\u00e9",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Scanner Quotidien": {
      "main": [
        [
          {
            "node": "Liste Documents",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Liste Documents": {
      "main": [
        [
          {
            "node": "V\u00e9rifier Expiration Imminente",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "V\u00e9rifier Expiration Imminente": {
      "main": [
        [
          {
            "node": "Traiter Individuellement",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Traiter Individuellement": {
      "main": [
        [
          {
            "node": "Envoyer Alerte Expiration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "D\u00e9clencheur Hebdomadaire": {
      "main": [
        [
          {
            "node": "Scraper Veille R\u00e9glementaire",
            "type": "main",
            "index": 0
          },
          {
            "node": "Liste Formateurs",
            "type": "main",
            "index": 0
          },
          {
            "node": "Liste Indicateurs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Liste Indicateurs": {
      "main": [
        [
          {
            "node": "Liste Documents pour Audit",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Liste Documents pour Audit": {
      "main": [
        [
          {
            "node": "Analyse de Conformit\u00e9",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Analyse de Conformit\u00e9": {
      "main": [
        [
          {
            "node": "Enregistrer Rapport",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Enregistrer Rapport": {
      "main": [
        [
          {
            "node": "Diffuser Rapport",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "V\u00e9rifier Seuil d'Alerte": {
      "main": [
        [
          {
            "node": "Envoi Alerte Conformit\u00e9",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Liste Formateurs": {
      "main": [
        [
          {
            "node": "V\u00e9rifier Certifications",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "V\u00e9rifier Certifications": {
      "main": [
        [
          {
            "node": "Traiter Par Formateur",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Traiter Par Formateur": {
      "main": [
        [
          {
            "node": "Alerte Expiration Certification",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Scraper Veille R\u00e9glementaire": {
      "main": [
        [
          {
            "node": "Analyser Changements",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Analyser Changements": {
      "main": [
        [
          {
            "node": "Si Changements D\u00e9tect\u00e9s",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Si Changements D\u00e9tect\u00e9s": {
      "main": [
        [
          {
            "node": "Enregistrer Veille",
            "type": "main",
            "index": 0
          },
          {
            "node": "Notification Veille",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "D\u00e9clencheur Nouvelle Formation": {
      "main": [
        [
          {
            "node": "Pr\u00e9parer Donn\u00e9es Formation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Regrouper Documents": {
      "main": [
        [
          {
            "node": "Enregistrer Documents",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "D\u00e9clencheur Formation Termin\u00e9e": {
      "main": [
        []
      ]
    },
    "Pr\u00e9parer \u00c9valuations": {
      "main": [
        [
          {
            "node": "Par Participant",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Par Participant": {
      "main": [
        [
          {
            "node": "Envoyer Questionnaire \u00c9valuation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Envoyer Questionnaire \u00c9valuation": {
      "main": [
        [
          {
            "node": "Enregistrer Envoi \u00c9valuation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "R\u00e9ception \u00c9valuation": {
      "main": [
        [
          {
            "node": "Analyser \u00c9valuation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Analyser \u00c9valuation": {
      "main": [
        [
          {
            "node": "Enregistrer R\u00e9sultats",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Enregistrer R\u00e9sultats": {
      "main": [
        [
          {
            "node": "V\u00e9rifier \u00c9valuation Insatisfaisante",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "V\u00e9rifier \u00c9valuation Insatisfaisante": {
      "main": [
        [
          {
            "node": "Cr\u00e9er Action Corrective",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Cr\u00e9er Action Corrective": {
      "main": [
        [
          {
            "node": "Notifier Responsable Qualit\u00e9",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "G\u00e9n\u00e9ration Rapports Mensuels": {
      "main": [
        [
          {
            "node": "R\u00e9cup\u00e9rer \u00c9valuations R\u00e9centes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "R\u00e9cup\u00e9rer \u00c9valuations R\u00e9centes": {
      "main": [
        [
          {
            "node": "G\u00e9n\u00e9rer Rapport Mensuel",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When clicking \u2018Test workflow\u2019": {
      "main": [
        [
          {
            "node": "Pr\u00e9parer \u00c9valuations",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "createdAt": "2025-05-22T12:39:17.913Z",
  "id": "p4n8rgT4RWrRAj6a",
  "isArchived": false,
  "meta": null,
  "name": "qualiopi",
  "nodes": [
    {
      "parameters": {
        "path": "document-reception",
        "options": {}
      },
      "name": "R\u00e9ception de Document",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 1,
      "position": [
        -1300,
        -360
      ],
      "id": "11dd27f0-a9e7-417f-9016-23e403bb134e"
    },
    {
      "parameters": {
        "functionCode": "// Analyse et classement par OCR\nconst document = items[0].json;\nconst documentContent = document.content || \"\";\n\n// Simulation d'une analyse OCR & IA\nlet category = \"Divers\";\nlet indicators = [];\n\nif(documentContent.includes(\"convention\") || document.type === \"convention\") {\n  category = \"Conventions\";\n  indicators = [\"1.1\", \"1.2\"];\n} else if(documentContent.includes(\"attestation\") || document.type === \"attestation\") {\n  category = \"Attestations\";\n  indicators = [\"3.1\", \"3.2\"];\n} else if(documentContent.includes(\"programme\") || document.type === \"programme\") {\n  category = \"Programmes\";\n  indicators = [\"2.1\"];\n} else if(documentContent.includes(\"\u00e9valuation\") || document.type === \"evaluation\") {\n  category = \"\u00c9valuations\";\n  indicators = [\"2.3\", \"7.2\"];\n} else if(documentContent.includes(\"formateur\") || document.type === \"cv\") {\n  category = \"Formateurs\";\n  indicators = [\"5.1\", \"5.2\"];\n}\n\nreturn [\n  {\n    json: {\n      ...document,\n      category,\n      relatedIndicators: indicators,\n      classificationDate: new Date().toISOString(),\n      isClassified: true\n    }\n  }\n];"
      },
      "name": "Classification OCR",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        -1100,
        -360
      ],
      "id": "4cf1a4d7-2c9d-4795-a708-690c215ce430"
    },
    {
      "parameters": {
        "application": {
          "__rl": true,
          "mode": "url",
          "value": ""
        },
        "table": "Documents Qualiopi"
      },
      "name": "Enregistrement Airtable",
      "type": "n8n-nodes-base.airtable",
      "typeVersion": 1,
      "position": [
        -900,
        -360
      ],
      "id": "ba0ceb55-b412-4202-8913-cf809dda34e1"
    },
    {
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{$json.status}}",
              "operation": "equals",
              "value2": "expired"
            }
          ]
        }
      },
      "name": "V\u00e9rif Document Expir\u00e9",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        -700,
        -360
      ],
      "id": "2be724cb-ed99-4f87-b99f-98e2d5f2aebc"
    },
    {
      "parameters": {
        "chatId": "={{$env.TELEGRAM_CHAT_ID}}",
        "text": "\u26a0\ufe0f DOCUMENT EXPIR\u00c9 \u26a0\ufe0f\n\nLe document \"{{$json.reference}}\" de type \"{{$json.type}}\" est expir\u00e9 depuis le {{$json.expirationDate}}.\n\nVeuillez le renouveler pour maintenir la conformit\u00e9 Qualiopi.",
        "additionalFields": {}
      },
      "name": "Alerte Document Expir\u00e9",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1,
      "position": [
        -500,
        -460
      ],
      "id": "9715e9c3-94e0-483f-8368-af22bd7ee722"
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {}
          ]
        }
      },
      "name": "Scanner Quotidien",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1,
      "position": [
        -1300,
        -60
      ],
      "id": "8ab21f7e-2dcf-41bc-80e7-631a74b696d7"
    },
    {
      "parameters": {
        "application": {
          "__rl": true,
          "mode": "url",
          "value": ""
        },
        "table": "Documents Qualiopi"
      },
      "name": "Liste Documents",
      "type": "n8n-nodes-base.airtable",
      "typeVersion": 1,
      "position": [
        -1100,
        -60
      ],
      "id": "8de4e7d4-ce88-4da7-bca2-dcee53896908"
    },
    {
      "parameters": {
        "functionCode": "// V\u00e9rifier les docs qui vont bient\u00f4t expirer (dans les 30 jours)\nconst documents = items;\nconst today = new Date();\nconst warningDocs = [];\n\nfor (const doc of documents) {\n  if (doc.json.expirationDate) {\n    const expDate = new Date(doc.json.expirationDate);\n    const diffTime = expDate - today;\n    const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));\n    \n    if (diffDays <= 30 && diffDays > 0) {\n      warningDocs.push({\n        json: {\n          ...doc.json,\n          daysUntilExpiration: diffDays\n        }\n      });\n    }\n  }\n}\n\nreturn warningDocs;"
      },
      "name": "V\u00e9rifier Expiration Imminente",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        -900,
        -60
      ],
      "id": "328cf05b-785b-4186-b7dd-78a7c37b0791"
    },
    {
      "parameters": {
        "batchSize": 1,
        "options": {}
      },
      "name": "Traiter Individuellement",
      "type": "n8n-nodes-base.splitInBatches",
      "typeVersion": 1,
      "position": [
        -700,
        -60
      ],
      "id": "4647a618-851e-42f4-bca4-4588e1847a52"
    },
    {
      "parameters": {
        "subject": "Alerte Qualiopi - Document expirant dans {{$json.daysUntilExpiration}} jours",
        "text": "Bonjour,\n\nLe document \"{{$json.reference}}\" de type \"{{$json.type}}\" va expirer dans {{$json.daysUntilExpiration}} jours.\n\nCe document est li\u00e9 aux indicateurs Qualiopi suivants : {{$json.relatedIndicators.join(\", \")}}.\n\nMerci de pr\u00e9voir son renouvellement pour maintenir la conformit\u00e9.\n\nCordialement,\nSyst\u00e8me d'automatisation Qualiopi",
        "options": {}
      },
      "name": "Envoyer Alerte Expiration",
      "type": "n8n-nodes-base.emailSend",
      "typeVersion": 1,
      "position": [
        -500,
        -60
      ],
      "id": "b7426775-37e9-43ca-a21d-770ef9def40a"
    },
    {
      "parameters": {
        "application": {
          "__rl": true,
          "mode": "url",
          "value": ""
        },
        "table": "Indicateurs Qualiopi"
      },
      "name": "Liste Indicateurs",
      "type": "n8n-nodes-base.airtable",
      "typeVersion": 1,
      "position": [
        -1100,
        140
      ],
      "id": "428511c1-92f9-4002-b002-8c4bc75b5b42"
    },
    {
      "parameters": {
        "application": {
          "__rl": true,
          "mode": "url",
          "value": ""
        },
        "table": "Documents Qualiopi"
      },
      "name": "Liste Documents pour Audit",
      "type": "n8n-nodes-base.airtable",
      "typeVersion": 1,
      "position": [
        -900,
        140
      ],
      "id": "1d9f80ba-f317-4851-bab8-29fde22be001"
    },
    {
      "parameters": {
        "functionCode": "// Analyse de conformit\u00e9\nconst indicators = items[0].json.data; // tous les indicateurs\nconst documents = items[1].json.data; // tous les documents\n\nconst conformityReport = [];\nlet globalConformity = 0;\n\n// Pour chaque indicateur, v\u00e9rifier les documents associ\u00e9s\nfor (const indicator of indicators) {\n  const indicatorCode = indicator.fields.Code;\n  const requiredDocs = indicator.fields.DocumentsRequis?.split(',').map(d => d.trim()) || [];\n  \n  // Compter combien de types requis sont couverts\n  let coveredDocsCount = 0;\n  let associatedDocs = [];\n  \n  for (const reqDoc of requiredDocs) {\n    const matchingDocs = documents.filter(d => {\n      return d.fields.type === reqDoc || \n             (d.fields.relatedIndicators && d.fields.relatedIndicators.includes(indicatorCode));\n    });\n    \n    if (matchingDocs.length > 0) {\n      coveredDocsCount++;\n      associatedDocs = [...associatedDocs, ...matchingDocs.map(d => d.fields.reference)];\n    }\n  }\n  \n  // Calculer le taux de conformit\u00e9 pour cet indicateur\n  const conformityRate = requiredDocs.length > 0 \n    ? Math.round((coveredDocsCount / requiredDocs.length) * 100) \n    : 100;\n  \n  conformityReport.push({\n    indicatorCode,\n    indicatorName: indicator.fields.Libelle,\n    conformityRate,\n    requiredDocs,\n    associatedDocs,\n    status: conformityRate >= 100 ? 'conforme' : conformityRate >= 70 ? 'partiel' : 'non-conforme'\n  });\n  \n  // Ajouter \u00e0 la conformit\u00e9 globale\n  globalConformity += conformityRate;\n}\n\n// Moyenne de conformit\u00e9 globale\nglobalConformity = Math.round(globalConformity / indicators.length);\n\n// Trier par priorit\u00e9 (non-conformes en premier)\nconformityReport.sort((a, b) => a.conformityRate - b.conformityRate);\n\nreturn [\n  {\n    json: {\n      reportDate: new Date().toISOString(),\n      globalConformity,\n      globalStatus: globalConformity >= 90 ? 'conforme' : globalConformity >= 70 ? '\u00e0 am\u00e9liorer' : 'critique',\n      detailedReport: conformityReport\n    }\n  }\n];"
      },
      "name": "Analyse de Conformit\u00e9",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        -700,
        140
      ],
      "id": "3c6c5977-0899-452c-a854-106299bd119d"
    },
    {
      "parameters": {
        "application": {
          "__rl": true,
          "mode": "url",
          "value": ""
        },
        "table": "Rapports Mensuels"
      },
      "name": "Enregistrer Rapport",
      "type": "n8n-nodes-base.airtable",
      "typeVersion": 1,
      "position": [
        -500,
        140
      ],
      "id": "4a4f5af0-c425-4892-af93-2212c8db3ea8"
    },
    {
      "parameters": {
        "conditions": {
          "number": [
            {
              "value1": "={{$json.globalConformity}}",
              "value2": 80
            }
          ]
        }
      },
      "name": "V\u00e9rifier Seuil d'Alerte",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        20,
        -40
      ],
      "id": "a205b93e-4130-4a79-935d-5c628364ccc5"
    },
    {
      "parameters": {
        "subject": "\u26a0\ufe0f ALERTE QUALIOPI - Taux de conformit\u00e9 : {{$json.globalConformity}}%",
        "text": "=Rapport de conformit\u00e9 Qualiopi du {{$json.reportDate.split('T')[0]}}\n\nStatut global : {{$json.globalStatus.toUpperCase()}} ({{$json.globalConformity}}%)\n\nIndicateurs non conformes \u00e0 traiter en priorit\u00e9 :\n{% for item in $json.detailedReport %}{% if item.status === 'non-conforme' %}\n- {{item.indicatorCode}} ({{item.conformityRate}}%) : {{item.indicatorName}}\n  Documents manquants : {{ item.requiredDocs.filter(doc => !item.associatedDocs.includes(doc)).join(', ') }}\n{% endif %}{% endfor %}\n\nVeuillez prendre les mesures n\u00e9cessaires pour restaurer la conformit\u00e9 avant le prochain audit.",
        "options": {}
      },
      "name": "Envoi Alerte Conformit\u00e9",
      "type": "n8n-nodes-base.emailSend",
      "typeVersion": 1,
      "position": [
        240,
        -60
      ],
      "id": "69906582-dd01-45f2-b4cc-ba3b08c09d0d"
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks"
            }
          ]
        }
      },
      "name": "D\u00e9clencheur Hebdomadaire",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1,
      "position": [
        -1300,
        340
      ],
      "id": "e0b4c4e4-42d1-444e-9015-73689d4cc31e"
    },
    {
      "parameters": {
        "application": {
          "__rl": true,
          "mode": "url",
          "value": ""
        },
        "table": "Formateurs"
      },
      "name": "Liste Formateurs",
      "type": "n8n-nodes-base.airtable",
      "typeVersion": 1,
      "position": [
        -1100,
        340
      ],
      "id": "f988e7c6-49b0-442a-b441-c6982bc95644"
    },
    {
      "parameters": {
        "functionCode": "// V\u00e9rifier les certifications des formateurs qui expirent bient\u00f4t\nconst formateurs = items;\nconst today = new Date();\nconst alertFormateurs = [];\n\nfor (const formateur of formateurs) {\n  const certifications = formateur.json.certifications || [];\n  const alertCertifications = [];\n  \n  for (const cert of certifications) {\n    if (cert.expirationDate) {\n      const expDate = new Date(cert.expirationDate);\n      const diffTime = expDate - today;\n      const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));\n      \n      if (diffDays <= 60 && diffDays > 0) {\n        alertCertifications.push({\n          ...cert,\n          daysUntilExpiration: diffDays\n        });\n      }\n    }\n  }\n  \n  if (alertCertifications.length > 0) {\n    alertFormateurs.push({\n      json: {\n        formateurId: formateur.json.id,\n        nom: formateur.json.nom,\n        prenom: formateur.json.prenom,\n        email: formateur.json.email,\n        certifications: alertCertifications\n      }\n    });\n  }\n}\n\nreturn alertFormateurs;"
      },
      "name": "V\u00e9rifier Certifications",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        -900,
        340
      ],
      "id": "01ca9370-a585-45f3-a08f-eb14d2c3364c"
    },
    {
      "parameters": {
        "batchSize": 1,
        "options": {}
      },
      "name": "Traiter Par Formateur",
      "type": "n8n-nodes-base.splitInBatches",
      "typeVersion": 1,
      "position": [
        -700,
        340
      ],
      "id": "0cf116e5-cc08-47b6-808d-8ca28f911236"
    },
    {
      "parameters": {
        "subject": "Qualiopi - Alerte expiration de certification(s)",
        "text": "=Bonjour {{$json.prenom}},\n\nNous vous informons que les certifications suivantes arrivent \u00e0 expiration :\n\n{% for cert in $json.certifications %}\n- {{cert.nom}} : expire dans {{cert.daysUntilExpiration}} jours ({{cert.expirationDate.split('T')[0]}})\n{% endfor %}\n\nAfin de maintenir notre certification Qualiopi, merci de pr\u00e9voir le renouvellement de ces qualifications.\n\nCordialement,\nService Qualit\u00e9",
        "options": {}
      },
      "name": "Alerte Expiration Certification",
      "type": "n8n-nodes-base.emailSend",
      "typeVersion": 1,
      "position": [
        -500,
        340
      ],
      "id": "5528d62f-4944-4351-8f55-5e8306abcbd9"
    },
    {
      "parameters": {
        "url": "={{$env.VEILLE_QUALIOPI_URL}}",
        "options": {
          "fullResponse": true
        }
      },
      "name": "Scraper Veille R\u00e9glementaire",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 1,
      "position": [
        -1100,
        540
      ],
      "id": "4328189d-7c3e-42de-a750-c12c6e77eab7"
    },
    {
      "parameters": {
        "functionCode": "// Analyse des changements r\u00e9glementaires\nconst response = items[0].json;\nconst previousData = $getWorkflowStaticData('node', 'previousContent') || '';\nconst currentContent = response.body;\n\n// Si pas de contenu pr\u00e9c\u00e9dent, sauvegarder et sortir\nif (!previousData) {\n  $setWorkflowStaticData('node', 'previousContent', currentContent);\n  return [];\n}\n\n// D\u00e9tecter les changements\nif (previousData === currentContent) {\n  // Aucun changement\n  return [];\n}\n\n// Simuler une analyse IA des changements\nconst changes = {\n  detectedDate: new Date().toISOString(),\n  source: 'Veille automatique',\n  changesDetected: true,\n  summary: 'Nouvelles informations d\u00e9tect\u00e9es sur la r\u00e9glementation Qualiopi',\n  contentSnippet: currentContent.substring(0, 500) + '...',\n  importance: 'moyenne',\n  affectedCriteria: ['Crit\u00e8re 6', 'Crit\u00e8re 7']\n};\n\n// Sauvegarder le nouveau contenu\n$setWorkflowStaticData('node', 'previousContent', currentContent);\n\nreturn [{ json: changes }];"
      },
      "name": "Analyser Changements",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        -900,
        540
      ],
      "id": "7d18d7c0-0120-44bb-93af-2d79645ce3ca"
    },
    {
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{$json.changesDetected}}",
              "value2": true
            }
          ]
        }
      },
      "name": "Si Changements D\u00e9tect\u00e9s",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        -700,
        540
      ],
      "id": "9efe4807-8873-47d6-a4e6-1341a5471f7b"
    },
    {
      "parameters": {
        "application": {
          "__rl": true,
          "mode": "url",
          "value": ""
        },
        "table": "Veille R\u00e9glementaire"
      },
      "name": "Enregistrer Veille",
      "type": "n8n-nodes-base.airtable",
      "typeVersion": 1,
      "position": [
        -500,
        460
      ],
      "id": "95419556-b123-441f-b8a3-2de91158bf0e"
    },
    {
      "parameters": {
        "chatId": "={{$env.QUALIOPI_CHAT_GROUP}}",
        "text": "\ud83d\udd14 *ALERTE VEILLE R\u00c9GLEMENTAIRE*\n\n{{$json.summary}}\n\nCrit\u00e8res concern\u00e9s : {{$json.affectedCriteria.join(', ')}}\nImportance : {{$json.importance}}\n\nExtrait : {{$json.contentSnippet.substring(0, 200)}}...",
        "additionalFields": {}
      },
      "name": "Notification Veille",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1,
      "position": [
        -500,
        620
      ],
      "id": "5fd33602-5235-470d-b375-4707feb7dbfa"
    },
    {
      "parameters": {
        "path": "formation-creation",
        "options": {}
      },
      "name": "D\u00e9clencheur Nouvelle Formation",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 1,
      "position": [
        -1300,
        740
      ],
      "id": "ba826226-44f8-4c62-b229-10ac2f752dad"
    },
    {
      "parameters": {
        "functionCode": "// Pr\u00e9paration des donn\u00e9es de formation pour g\u00e9n\u00e9ration des documents\nconst sessionData = items[0].json;\n\n// Structurer les donn\u00e9es pour les templates de documents\nconst formationData = {\n  id: sessionData.id,\n  titre: sessionData.titre,\n  dateDebut: new Date(sessionData.dateDebut).toLocaleDateString('fr-FR'),\n  dateFin: new Date(sessionData.dateFin).toLocaleDateString('fr-FR'),\n  duree: sessionData.duree,\n  lieu: sessionData.lieu,\n  formateur: sessionData.formateur,\n  objectifs: sessionData.objectifs,\n  participants: sessionData.participants || [],\n  prix: sessionData.prix,\n  programme: sessionData.programme,\n  prerequis: sessionData.prerequis,\n  modalitesEvaluation: sessionData.modalitesEvaluation,\n  client: sessionData.client,\n  dateGeneration: new Date().toLocaleDateString('fr-FR')\n};\n\nreturn [{ json: formationData }];"
      },
      "name": "Pr\u00e9parer Donn\u00e9es Formation",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        -1100,
        740
      ],
      "id": "07d91c12-5325-4d93-8ac6-9cd307255be8"
    },
    {
      "parameters": {},
      "name": "G\u00e9n\u00e9rer Programme",
      "type": "n8n-nodes-base.documint",
      "typeVersion": 1,
      "position": [
        -900,
        640
      ],
      "id": "e80fdb25-9053-42c6-a1cd-3d2f4110dd71",
      "credentials": {}
    },
    {
      "parameters": {},
      "name": "G\u00e9n\u00e9rer Convention",
      "type": "n8n-nodes-base.documint",
      "typeVersion": 1,
      "position": [
        -900,
        740
      ],
      "id": "182b6d08-dbad-4c52-822f-d2b88607029d",
      "credentials": {}
    },
    {
      "parameters": {},
      "name": "G\u00e9n\u00e9rer Attestation",
      "type": "n8n-nodes-base.documint",
      "typeVersion": 1,
      "position": [
        -900,
        840
      ],
      "id": "94d77927-0be2-480e-8fdd-ecabf3c20a50",
      "credentials": {}
    },
    {
      "parameters": {
        "functionCode": "// Combinaison des r\u00e9sultats des documents g\u00e9n\u00e9r\u00e9s\nconst formationData = items[0].json;\nconst programmeDoc = items[1].json;\nconst conventionDoc = items[2].json;\nconst attestationDoc = items[3].json;\n\nreturn [{\n  json: {\n    formationId: formationData.id,\n    formationTitre: formationData.titre,\n    dateGeneration: new Date().toISOString(),\n    documents: {\n      programme: programmeDoc.url || programmeDoc.fileContent,\n      convention: conventionDoc.url || conventionDoc.fileContent,\n      attestation: attestationDoc.url || attestationDoc.fileContent,\n    },\n    participants: formationData.participants,\n    formateur: formationData.formateur,\n    client: formationData.client\n  }\n}];"
      },
      "name": "Regrouper Documents",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        -700,
        740
      ],
      "id": "299c0bd2-527d-4e32-a858-ea5c8ec73e50"
    },
    {
      "parameters": {
        "application": {
          "__rl": true,
          "mode": "url",
          "value": ""
        },
        "table": "Documents Formations"
      },
      "name": "Enregistrer Documents",
      "type": "n8n-nodes-base.airtable",
      "typeVersion": 1,
      "position": [
        -500,
        740
      ],
      "id": "d1b7be14-b24b-4e03-8485-45fab219234d"
    },
    {
      "parameters": {},
      "name": "Demande Signatures",
      "type": "n8n-nodes-base.signNow",
      "typeVersion": 1,
      "position": [
        -300,
        740
      ],
      "id": "8f51b0bb-e6bc-4ef2-9b41-fa9b99893c0f",
      "credentials": {}
    },
    {
      "parameters": {
        "path": "formation-completed",
        "options": {}
      },
      "name": "D\u00e9clencheur Formation Termin\u00e9e",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 1,
      "position": [
        -1300,
        940
      ],
      "id": "21b0ffaa-8781-4b2c-b226-cd5940f63cf7"
    },
    {
      "parameters": {
        "functionCode": "// Extraction des donn\u00e9es de formation compl\u00e9t\u00e9e\nconst formationData = items[0].json;\nconst participants = formationData.participants || [];\n\n// Cr\u00e9er un \u00e9l\u00e9ment par participant pour les \u00e9valuations\nconst participantsItems = [];\n\nfor (const participant of participants) {\n  participantsItems.push({\n    json: {\n      formationId: formationData.id,\n      formationTitre: formationData.titre,\n      dateFin: formationData.dateFin,\n      participant: participant,\n      formateur: formationData.formateur,\n      evaluationLink: `https://forms.example.com/evaluation?session=${formationData.id}&participant=${participant.id}`\n    }\n  });\n}\n\nreturn participantsItems;"
      },
      "name": "Pr\u00e9parer \u00c9valuations",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        -1100,
        940
      ],
      "id": "f41d5ced-7c58-43e3-b823-8f2dbe2e8dd4"
    },
    {
      "parameters": {
        "batchSize": 1,
        "options": {}
      },
      "name": "Par Participant",
      "type": "n8n-nodes-base.splitInBatches",
      "typeVersion": 1,
      "position": [
        -900,
        940
      ],
      "id": "0640ea41-9a61-4390-8c32-aa04631df1d4"
    },
    {
      "parameters": {
        "subject": "\u00c9valuation de la formation \"{{$json.formationTitre}}\"",
        "text": "=Bonjour {{$json.participant.prenom}},\n\nMerci d'avoir particip\u00e9 \u00e0 notre formation \"{{$json.formationTitre}}\".\n\nDans le cadre de notre d\u00e9marche qualit\u00e9 Qualiopi, nous vous invitons \u00e0 compl\u00e9ter l'\u00e9valuation \u00e0 chaud de cette session en cliquant sur le lien suivant :\n\n{{$json.evaluationLink}}\n\nVotre retour nous est pr\u00e9cieux pour am\u00e9liorer continuellement nos formations.\n\nCordialement,\nL'\u00e9quipe p\u00e9dagogique",
        "options": {}
      },
      "name": "Envoyer Questionnaire \u00c9valuation",
      "type": "n8n-nodes-base.emailSend",
      "typeVersion": 1,
      "position": [
        -700,
        940
      ],
      "id": "228417d3-0da7-40c2-9df5-68a6546d9139"
    },
    {
      "parameters": {
        "application": {
          "__rl": true,
          "mode": "url",
          "value": ""
        },
        "table": "Suivi \u00c9valuations"
      },
      "name": "Enregistrer Envoi \u00c9valuation",
      "type": "n8n-nodes-base.airtable",
      "typeVersion": 1,
      "position": [
        -500,
        940
      ],
      "id": "fca617ca-3b4c-418b-b4cb-92fd736c6dc9"
    },
    {
      "parameters": {
        "path": "evaluation-received",
        "options": {}
      },
      "name": "R\u00e9ception \u00c9valuation",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 1,
      "position": [
        -1300,
        1140
      ],
      "id": "78420ff2-584f-4135-a810-e546883a6e56"
    },
    {
      "parameters": {
        "functionCode": "// Traitement de l'\u00e9valuation re\u00e7ue\nconst evaluationData = items[0].json;\n\n// Calculer un score global\nconst scores = evaluationData.scores || {};\nlet totalScore = 0;\nlet count = 0;\n\nfor (const key in scores) {\n  if (typeof scores[key] === 'number') {\n    totalScore += scores[key];\n    count++;\n  }\n}\n\nconst averageScore = count > 0 ? Math.round((totalScore / count) * 10) / 10 : 0;\n\n// Identifier les points d'am\u00e9lioration\nconst lowScoreThreshold = 3;\nconst improvementAreas = [];\n\nfor (const key in scores) {\n  if (typeof scores[key] === 'number' && scores[key] <= lowScoreThreshold) {\n    improvementAreas.push(key);\n  }\n}\n\n// Pr\u00e9parer le r\u00e9sum\u00e9\nreturn [{\n  json: {\n    ...evaluationData,\n    averageScore,\n    improvementAreas,\n    status: averageScore >= 4 ? 'satisfaisant' : averageScore >= 3 ? 'moyen' : 'insatisfaisant',\n    dateReception: new Date().toISOString(),\n    commentaireAnalyse: improvementAreas.length > 0 \n      ? `Points \u00e0 am\u00e9liorer : ${improvementAreas.join(', ')}` \n      : 'Aucun point critique identifi\u00e9'\n  }\n}];"
      },
      "name": "Analyser \u00c9valuation",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        -1100,
        1140
      ],
      "id": "5cb90d1a-6d51-43a0-9ef6-c506ed4935d4"
    },
    {
      "parameters": {
        "application": {
          "__rl": true,
          "mode": "url",
          "value": ""
        },
        "table": "R\u00e9sultats \u00c9valuations"
      },
      "name": "Enregistrer R\u00e9sultats",
      "type": "n8n-nodes-base.airtable",
      "typeVersion": 1,
      "position": [
        -900,
        1140
      ],
      "id": "5726e624-3079-4259-b4bc-1c7f6a8ef936"
    },
    {
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{$json.status}}",
              "operation": "equals",
              "value2": "insatisfaisant"
            }
          ]
        }
      },
      "name": "V\u00e9rifier \u00c9valuation Insatisfaisante",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        -700,
        1140
      ],
      "id": "3bae5c39-a142-4cbd-a7e8-29018dcd22d8"
    },
    {
      "parameters": {
        "authentication": "airtableTokenApi",
        "application": {
          "__rl": true,
          "mode": "url",
          "value": ""
        },
        "table": "Actions Correctives"
      },
      "name": "Cr\u00e9er Action Corrective",
      "type": "n8n-nodes-base.airtable",
      "typeVersion": 1,
      "position": [
        -500,
        1040
      ],
      "id": "c4e46022-3019-4dee-82e4-95af77650f95",
      "credentials": {
        "airtableTokenApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "subject": "\u26a0\ufe0f Alerte Qualiopi - \u00c9valuation insatisfaisante",
        "text": "=Alerte qualit\u00e9 formation\n\nUne \u00e9valuation insatisfaisante a \u00e9t\u00e9 re\u00e7ue pour la formation \"{{$json.formationTitre}}\".\n\nScore moyen : {{$json.averageScore}}/5\n\nPoints d'am\u00e9lioration identifi\u00e9s :\n{% for area in $json.improvementAreas %}- {{area}}\n{% endfor %}\n\nCommentaires du participant :\n{{$json.commentaire}}\n\nUne action corrective a \u00e9t\u00e9 automatiquement cr\u00e9\u00e9e dans le syst\u00e8me.\nMerci de la traiter conform\u00e9ment \u00e0 notre proc\u00e9dure qualit\u00e9 Qualiopi.",
        "options": {}
      },
      "name": "Notifier Responsable Qualit\u00e9",
      "type": "n8n-nodes-base.emailSend",
      "typeVersion": 1,
      "position": [
        -300,
        1040
      ],
      "id": "f6a4b3e0-59d0-47d4-aa8f-3302524c90d3"
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "months"
            }
          ]
        }
      },
      "name": "G\u00e9n\u00e9ration Rapports Mensuels",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1,
      "position": [
        -1300,
        1340
      ],
      "id": "39fe0936-70f2-4bc0-ac1e-96157441381d"
    },
    {
      "parameters": {
        "application": {
          "__rl": true,
          "mode": "url",
          "value": ""
        },
        "table": "R\u00e9sultats \u00c9valuations"
      },
      "name": "R\u00e9cup\u00e9rer \u00c9valuations R\u00e9centes",
      "type": "n8n-nodes-base.airtable",
      "typeVersion": 1,
      "position": [
        -1100,
        1340
      ],
      "id": "c688adb7-dd38-4470-acf9-d97a31488ac1"
    },
    {
      "parameters": {
        "functionCode": "// G\u00e9n\u00e9rer le rapport mensuel d'\u00e9valuation\nconst evaluations = items[0].json.data;\nconst now = new Date();\nconst monthAgo = new Date();\nmonthAgo.setMonth(now.getMonth() - 1);\n\n// Filtrer les \u00e9valuations du dernier mois\nconst recentEvaluations = evaluations.filter(eval => {\n  const evalDate = new Date(eval.fields.dateReception);\n  return evalDate >= monthAgo && evalDate <= now;\n});\n\n// Calculer les statistiques\nlet totalScore = 0;\nconst formationScores = {};\nconst formateurScores = {};\nconst improvementAreasCount = {};\n\nrecentEvaluations.forEach(eval => {\n  // Score moyen global\n  totalScore += eval.fields.averageScore || 0;\n  \n  // Scores par formation\n  const formationId = eval.fields.formationId;\n  if (!formationScores[formationId]) {\n    formationScores[formationId] = {\n      titre: eval.fields.formationTitre,\n      scores: [],\n      total: 0,\n      count: 0\n    };\n  }\n  formationScores[formationId].scores.push(eval.fields.averageScore);\n  formationScores[formationId].total += eval.fields.averageScore;\n  formationScores[formationId].count++;\n  \n  // Scores par formateur\n  const formateurId = eval.fields.formateurId;\n  if (!formateurScores[formateurId]) {\n    formateurScores[formateurId] = {\n      nom: eval.fields.formateurNom,\n      scores: [],\n      total: 0,\n      count: 0\n    };\n  }\n  formateurScores[formateurId].scores.push(eval.fields.averageScore);\n  formateurScores[formateurId].total += eval.fields.averageScore;\n  formateurScores[formateurId].count++;\n  \n  // Points d'am\u00e9lioration\n  (eval.fields.improvementAreas || []).forEach(area => {\n    if (!improvementAreasCount[area]) {\n      improvementAreasCount[area] = 0;\n    }\n    improvementAreasCount[area]++;\n  });\n});\n\n// Calcul des moyennes\nconst globalAverage = recentEvaluations.length > 0 ? Math.round((totalScore / recentEvaluations.length) * 10) / 10 : 0;\n\n// Classement des formations par score\nconst formationRanking = Object.keys(formationScores)\n  .map(id => ({\n    id,\n    titre: formationScores[id].titre,\n    average: formationScores[id].count > 0 ? Math.round((formationScores[id].total / formationScores[id].count) * 10) / 10 : 0\n  }))\n  .sort((a, b) => b.average - a.average);\n\n// Classement des formateurs par score\nconst formateurRanking = Object.keys(formateurScores)\n  .map(id => ({\n    id,\n    nom: formateurScores[id].nom,\n    average: formateurScores[id].count > 0 ? Math.round((formateurScores[id].total / formateurScores[id].count) * 10) / 10 : 0\n  }))\n  .sort((a, b) => b.average - a.average);\n\n// Points d'am\u00e9lioration les plus fr\u00e9quents\nconst topImprovementAreas = Object.keys(improvementAreasCount)\n  .map(area => ({ area, count: improvementAreasCount[area] }))\n  .sort((a, b) => b.count - a.count)\n  .slice(0, 5);\n\n// Rapport final\nreturn [{\n  json: {\n    periode: `${monthAgo.toLocaleDateString()} - ${now.toLocaleDateString()}`,\n    dateGeneration: now.toISOString(),\n    nombreEvaluations: recentEvaluations.length,\n    scoreMoyenGlobal: globalAverage,\n    tendance: 0, // \u00c0 calculer avec l'historique\n    meilleuresFormations: formationRanking.slice(0, 3),\n    formationsAmeliorer: formationRanking.slice(-3).reverse(),\n    meilleursFormateurs: formateurRanking.slice(0, 3),\n    pointsAmelioration: topImprovementAreas,\n    qualiopiStatus: globalAverage >= 4 ? 'conforme' : globalAverage >= 3.5 ? '\u00e0 surveiller' : 'non-conforme'\n  }\n}];"
      },
      "name": "G\u00e9n\u00e9rer Rapport Mensuel",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        -900,
        1340
      ],
      "id": "f4a356b0-cc71-481d-ac32-09da600d6ab5"
    },
    {
      "parameters": {},
      "name": "G\u00e9n\u00e9rer PDF Rapport",
      "type": "n8n-nodes-base.documint",
      "typeVersion": 1,
      "position": [
        -700,
        1340
      ],
      "id": "47a645b4-aaef-475e-ba70-8205c94d2cc6",
      "credentials": {}
    },
    {
      "parameters": {
        "subject": "Rapport mensuel Qualiopi - {{$json.periode}}",
        "text": "=Bonjour,\n\nVeuillez trouver ci-joint le rapport mensuel de conformit\u00e9 Qualiopi pour la p\u00e9riode {{$json.periode}}.\n\nPoints cl\u00e9s :\n- Score moyen global : {{$json.scoreMoyenGlobal}}/5\n- Nombre d'\u00e9valuations trait\u00e9es : {{$json.nombreEvaluations}}\n- Statut Qualiopi : {{$json.qualiopiStatus}}\n\nFormations les mieux \u00e9valu\u00e9es :\n{% for formation in $json.meilleuresFormations %}- {{formation.titre}} ({{formation.average}}/5)\n{% endfor %}\n\nPoints d'am\u00e9lioration principaux :\n{% for point in $json.pointsAmelioration %}- {{point.area}} (mentionn\u00e9 {{point.count}} fois)\n{% endfor %}\n\nCordialement,\nSyst\u00e8me d'assurance qualit\u00e9 Qualiopi",
        "options": {}
      },
      "name": "Diffuser Rapport",
      "type": "n8n-nodes-base.emailSend",
      "typeVersion": 1,
      "position": [
        -300,
        140
      ],
      "id": "f7794e01-f004-4e26-a52d-4df6413de396"
    },
    {
      "parameters": {},
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        -1540,
        840
      ],
      "id": "51e04c82-1bfe-4530-b262-8def22256bdd",
      "name": "When clicking \u2018Test workflow\u2019"
    }
  ],
  "settings": {
    "executionOrder": "v1"
  },
  "staticData": null,
  "tags": [],
  "triggerCount": 0,
  "updatedAt": "2025-05-22T12:50:53.000Z",
  "versionId": "cd685b54-4b43-41c9-b572-3e7fca58875c"
}