{
  "name": "Emargement automatise et relance des apprenants non signataires",
  "nodes": [
    {
      "id": "68387f49-be6c-4e0d-a10f-f9160696fb21",
      "name": "Note setup",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -460,
        -320
      ],
      "parameters": {
        "color": 4,
        "width": 520,
        "height": 260,
        "content": "## 1. Feuille d'emargement (Google Sheets)\n\nCreez un onglet **Emargement** avec une ligne par apprenant et par session :\n\n`id_emargement` | `id_session` | `date_session` | `titre_session` | `nom_apprenant` | `email_apprenant` | `lien_signature` | `signe_le` | `relances` | `derniere_relance` | `statut`\n\nRemplacez `REMPLACER_PAR_ID_DU_GOOGLE_SHEET` dans les 3 nodes Google Sheets."
      },
      "typeVersion": 1
    },
    {
      "id": "6b1c843e-886c-4a5b-acb1-164bb1d70312",
      "name": "Note relances",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        180,
        -320
      ],
      "parameters": {
        "color": 3,
        "width": 520,
        "height": 260,
        "content": "## 2. Relances automatiques\n\nLe node Code detecte les apprenants qui n'ont pas signe :\n- session deja commencee depuis plus de 4 h\n- moins de 3 relances deja envoyees\n- au maximum 1 relance par jour\n\nAdaptez `MAX_RELANCES` et `DELAI_HEURES` a votre process."
      },
      "typeVersion": 1
    },
    {
      "id": "d004b330-2ff5-409a-a201-be5479c7ae28",
      "name": "Note signature",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -460,
        300
      ],
      "parameters": {
        "color": 5,
        "width": 520,
        "height": 220,
        "content": "## 3. Reception des signatures\n\nBranchez votre page / outil de signature sur le webhook `POST /emargement-signature`.\n\nPayload attendu :\n`{ id_emargement, email_apprenant, signature_url, horodatage, ip }`"
      },
      "typeVersion": 1
    },
    {
      "id": "9f01fb76-0996-409a-a3ec-920fc263691a",
      "name": "Chaque jour a 18h",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -420,
        -20
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 18 * * 1-5"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "a1063e94-a703-4f11-a24a-408fe5297df0",
      "name": "Lire la feuille d'emargement",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -200,
        -20
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Emargement"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "REMPLACER_PAR_ID_DU_GOOGLE_SHEET"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "ae964b8d-a49a-4874-a503-11141064770f",
      "name": "Detecter les non-signataires",
      "type": "n8n-nodes-base.code",
      "position": [
        20,
        -20
      ],
      "parameters": {
        "jsCode": "// Detecte les apprenants qui n'ont pas signe leur feuille d'emargement.\n// Entree : une ligne par apprenant et par session (onglet Emargement).\nconst MAX_RELANCES = 3;\nconst DELAI_HEURES = 4;   // on attend 4 h apres le debut de la session\nconst ESPACEMENT_H = 20;  // 1 relance par jour maximum\n\nconst maintenant = new Date();\nconst sortie = [];\n\nfor (const item of $input.all()) {\n  const l = item.json;\n\n  if (!l.email_apprenant) continue;\n  if (l.signe_le && String(l.signe_le).trim() !== '') continue;\n\n  const debut = new Date(l.date_session);\n  if (isNaN(debut.getTime())) continue;\n\n  const heures = (maintenant - debut) / 3600000;\n  if (heures < DELAI_HEURES) continue;\n\n  const dejaRelance = Number(l.relances || 0);\n  if (dejaRelance >= MAX_RELANCES) continue;\n\n  if (l.derniere_relance) {\n    const derniere = new Date(l.derniere_relance);\n    if (!isNaN(derniere.getTime()) && (maintenant - derniere) / 3600000 < ESPACEMENT_H) continue;\n  }\n\n  const niveau = dejaRelance + 1;\n\n  sortie.push({\n    json: {\n      ...l,\n      niveau_relance: niveau,\n      derniere_relance_calculee: maintenant.toISOString(),\n      retard_heures: Math.round(heures),\n      est_derniere_relance: niveau >= MAX_RELANCES,\n    },\n  });\n}\n\nreturn sortie;"
      },
      "typeVersion": 2
    },
    {
      "id": "6aee5878-0228-47c6-ab47-2cb35b159331",
      "name": "Relance a envoyer ?",
      "type": "n8n-nodes-base.if",
      "position": [
        240,
        -20
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "d8fa0dc9-5331-406f-a337-6da57ca67723",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json.email_apprenant }}",
              "rightValue": ""
            },
            {
              "id": "0cd21f2f-c82b-45cd-aa4e-0d7eba014142",
              "operator": {
                "type": "number",
                "operation": "gte"
              },
              "leftValue": "={{ $json.niveau_relance }}",
              "rightValue": 1
            }
          ]
        },
        "looseTypeValidation": true
      },
      "typeVersion": 2.2
    },
    {
      "id": "a9cdf242-f1c1-4178-ab17-3e44fe8d848a",
      "name": "Ignorer la ligne",
      "type": "n8n-nodes-base.noOp",
      "position": [
        460,
        140
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "d46612c7-431e-4abb-aac7-0fd65ba04964",
      "name": "Preparer le message de relance",
      "type": "n8n-nodes-base.set",
      "position": [
        460,
        -140
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "2895c3a9-be90-4591-a055-d3472ad1365b",
              "name": "destinataire",
              "type": "string",
              "value": "={{ $json.email_apprenant }}"
            },
            {
              "id": "23d7a450-f652-432e-a1da-c8127a7acfaa",
              "name": "objet",
              "type": "string",
              "value": "=Signature manquante - {{ $json.titre_session }} ({{ $json.niveau_relance }}/3)"
            },
            {
              "id": "075b883e-8e50-496d-a61d-ed2f064c1f95",
              "name": "lien",
              "type": "string",
              "value": "={{ $json.lien_signature || 'https://votre-of.fr/emargement?e=' + encodeURIComponent($json.id_emargement) }}"
            },
            {
              "id": "8b062333-44d5-41ac-a020-4dee8b30e5d7",
              "name": "corps_html",
              "type": "string",
              "value": "=<p>Bonjour {{ $json.nom_apprenant }},</p><p>Votre feuille d'&eacute;margement pour la session <strong>{{ $json.titre_session }}</strong> du {{ $json.date_session }} n'est pas encore sign&eacute;e.</p><p>La signature est une pi&egrave;ce obligatoire du dossier de formation (art. L6353-1 du Code du travail) et conditionne la prise en charge du financeur.</p><p><a href=\"{{ $json.lien }}\">Signer mon &eacute;margement</a></p>{{ $json.est_derniere_relance ? '<p><strong>Il s\\'agit de la derni&egrave;re relance automatique.</strong> Sans signature, votre r&eacute;f&eacute;rent sera alert&eacute;.</p>' : '' }}<p>L'&eacute;quipe p&eacute;dagogique</p>"
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "62c73804-8b82-4efc-a0a5-3d30c0f3d69f",
      "name": "Relancer l'apprenant",
      "type": "n8n-nodes-base.gmail",
      "position": [
        680,
        -140
      ],
      "parameters": {
        "sendTo": "={{ $json.destinataire }}",
        "subject": "={{ $json.objet }}",
        "message": "={{ $json.corps_html }}",
        "options": {
          "appendAttribution": false
        },
        "emailType": "html",
        "resource": "message",
        "operation": "send"
      },
      "typeVersion": 2.1,
      "webhookId": "735682eb-3a69-4098-aaed-11a3a7b45f38"
    },
    {
      "id": "ec66243d-9b11-4bed-a983-66596901e1fa",
      "name": "Historiser la relance",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        900,
        -140
      ],
      "parameters": {
        "columns": {
          "value": {
            "id_emargement": "={{ $json.id_emargement }}",
            "relances": "={{ $json.niveau_relance }}",
            "derniere_relance": "={{ $json.derniere_relance_calculee }}",
            "statut": "=relance_{{ $json.niveau_relance }}"
          },
          "schema": [
            {
              "id": "id_emargement",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "id_emargement",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "relances",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "relances",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "derniere_relance",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "derniere_relance",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "statut",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "statut",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "id_emargement"
          ]
        },
        "options": {},
        "operation": "appendOrUpdate",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Emargement"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "REMPLACER_PAR_ID_DU_GOOGLE_SHEET"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "0df8513b-f1c7-4d68-a5be-0050c40d5ed8",
      "name": "Derniere relance ?",
      "type": "n8n-nodes-base.if",
      "position": [
        1120,
        -140
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "f63d0017-3294-4e97-a4f4-40b550347776",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.est_derniere_relance }}",
              "rightValue": ""
            }
          ]
        },
        "looseTypeValidation": true
      },
      "typeVersion": 2.2
    },
    {
      "id": "5fcfbe8a-9dd5-4962-ac14-aef54eadef56",
      "name": "Alerter le referent pedagogique",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1340,
        -240
      ],
      "parameters": {
        "sendTo": "referent@votre-of.fr",
        "subject": "=Emargement non signe - {{ $json.titre_session }}",
        "message": "=<p>L'apprenant <strong>{{ $json.nom_apprenant }}</strong> ({{ $json.email_apprenant }}) n'a pas sign&eacute; son &eacute;margement pour la session {{ $json.titre_session }} du {{ $json.date_session }}.</p><p>3 relances automatiques ont &eacute;t&eacute; envoy&eacute;es. Une r&eacute;gularisation manuelle est n&eacute;cessaire avant la facturation.</p>",
        "options": {
          "appendAttribution": false
        },
        "emailType": "html",
        "resource": "message",
        "operation": "send"
      },
      "typeVersion": 2.1,
      "webhookId": "c08dc260-2701-48d5-a1f2-6a69bcf24ad1"
    },
    {
      "id": "7abd269a-944d-4a53-adcc-4ed54be9cdca",
      "name": "Relance suivante planifiee",
      "type": "n8n-nodes-base.noOp",
      "position": [
        1340,
        -40
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "f101a24c-3419-42a5-ad9f-a85141ddb210",
      "name": "Signature recue",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -420,
        400
      ],
      "parameters": {
        "path": "emargement-signature",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2,
      "webhookId": "1458ad07-d9de-44f8-a312-7f0ada5f043c",
      "onError": "continueRegularOutput"
    },
    {
      "id": "d2b4438c-071b-4140-aded-ba857a775b5e",
      "name": "Normaliser la signature",
      "type": "n8n-nodes-base.set",
      "position": [
        -200,
        400
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "06d4dcbe-4232-4b80-a4fb-3f0bd7bd58c9",
              "name": "id_emargement",
              "type": "string",
              "value": "={{ $json.body.id_emargement || $json.query.id_emargement }}"
            },
            {
              "id": "48478519-21b7-4096-af8c-921702850c55",
              "name": "email_apprenant",
              "type": "string",
              "value": "={{ $json.body.email_apprenant }}"
            },
            {
              "id": "fa6632fc-a903-4ce9-ade8-2fa3105ea6fb",
              "name": "signature_url",
              "type": "string",
              "value": "={{ $json.body.signature_url }}"
            },
            {
              "id": "a1144b68-1c2f-446a-a427-b33b08554523",
              "name": "signe_le",
              "type": "string",
              "value": "={{ $json.body.horodatage || $now.toISO() }}"
            },
            {
              "id": "27cca2bb-7c81-4f12-a6bb-089bdcdeeda7",
              "name": "ip_signature",
              "type": "string",
              "value": "={{ $json.body.ip || '' }}"
            }
          ]
        },
        "includeOtherFields": false
      },
      "typeVersion": 3.4
    },
    {
      "id": "d73f9740-d72b-4fcd-a0c9-5ee1404effe4",
      "name": "Enregistrer la signature",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        20,
        400
      ],
      "parameters": {
        "columns": {
          "value": {
            "id_emargement": "={{ $json.id_emargement }}",
            "signe_le": "={{ $json.signe_le }}",
            "signature_url": "={{ $json.signature_url }}",
            "statut": "signe"
          },
          "schema": [
            {
              "id": "id_emargement",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "id_emargement",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "signe_le",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "signe_le",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "signature_url",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "signature_url",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "statut",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "statut",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "id_emargement"
          ]
        },
        "options": {},
        "operation": "appendOrUpdate",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Emargement"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "REMPLACER_PAR_ID_DU_GOOGLE_SHEET"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "37b5eca4-858d-4e48-ab03-a3f09a713ee2",
      "name": "Confirmer la signature",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        240,
        400
      ],
      "parameters": {
        "options": {},
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ status: 'ok', id_emargement: $json.id_emargement }) }}"
      },
      "typeVersion": 1.1
    }
  ],
  "pinData": {},
  "connections": {
    "Chaque jour a 18h": {
      "main": [
        [
          {
            "node": "Lire la feuille d'emargement",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Lire la feuille d'emargement": {
      "main": [
        [
          {
            "node": "Detecter les non-signataires",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Detecter les non-signataires": {
      "main": [
        [
          {
            "node": "Relance a envoyer ?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Relance a envoyer ?": {
      "main": [
        [
          {
            "node": "Preparer le message de relance",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Ignorer la ligne",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Preparer le message de relance": {
      "main": [
        [
          {
            "node": "Relancer l'apprenant",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Relancer l'apprenant": {
      "main": [
        [
          {
            "node": "Historiser la relance",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Historiser la relance": {
      "main": [
        [
          {
            "node": "Derniere relance ?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Derniere relance ?": {
      "main": [
        [
          {
            "node": "Alerter le referent pedagogique",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Relance suivante planifiee",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Signature recue": {
      "main": [
        [
          {
            "node": "Normaliser la signature",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normaliser la signature": {
      "main": [
        [
          {
            "node": "Enregistrer la signature",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Enregistrer la signature": {
      "main": [
        [
          {
            "node": "Confirmer la signature",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "609a807d-c954-4000-aeac-c1a187c69dd6",
  "meta": {
    "instanceId": "5f7c1a2b9d3e4f60a8b7c6d5e4f3021998af77bc12de34ff56aa7788bb99ccdd",
    "templateCredsSetupCompleted": true
  },
  "tags": []
}
