{
  "name": "ALLSoft — Recordatorio de citas",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "days",
              "triggerAtHour": 8
            }
          ]
        }
      },
      "id": "c1t4s0000-0001-4000-8000-000000000001",
      "name": "Todos los días a las 8",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.3,
      "position": [
        -620,
        300
      ],
      "notes": "Se dispara solo cada mañana. Cambia la hora según cuándo quieras que salgan los recordatorios."
    },
    {
      "parameters": {
        "jsCode": "// Tu agenda. Sustituye este bloque por un nodo de Google Sheets, Airtable o\n// tu base de datos: lo único que importa es que cada cita traiga estos campos.\nconst agenda = [\n  { cliente: 'María Restrepo',  telefono: '3001112233', correo: 'maria@ejemplo.com',  servicio: 'Control odontológico', dias: 1, hora: '09:30' },\n  { cliente: 'Jorge Betancur',  telefono: '3014445566', correo: '',                   servicio: 'Limpieza dental',       dias: 1, hora: '11:00' },\n  { cliente: 'Laura Gómez',     telefono: '',           correo: '',                   servicio: 'Ortodoncia — ajuste',   dias: 1, hora: '15:00' },\n  { cliente: 'Andrés Peña',     telefono: '3027778899', correo: 'andres@ejemplo.com', servicio: 'Valoración inicial',    dias: 5, hora: '10:00' },\n];\n\n// Solo interesan las de mañana. El resto se descarta aquí y no gasta ni una\n// llamada al modelo.\nconst hoy = new Date();\nconst manana = new Date(hoy.getTime() + 86400000);\nconst fecha = manana.toLocaleDateString('es-CO', {\n  weekday: 'long', day: 'numeric', month: 'long',\n});\n\nreturn agenda\n  .filter((c) => c.dias === 1)\n  .map((c) => ({\n    json: {\n      cliente: c.cliente,\n      telefono: c.telefono,\n      correo: c.correo,\n      servicio: c.servicio,\n      hora: c.hora,\n      fecha,\n      // El nombre de pila basta para un mensaje cercano sin sonar invasivo\n      nombre: c.cliente.split(' ')[0],\n    },\n  }));\n"
      },
      "id": "c1t4s0000-0002-4000-8000-000000000002",
      "name": "Agenda de mañana",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -400,
        300
      ],
      "notes": "Reemplaza este bloque por Google Sheets o tu base de datos. Deja los mismos nombres de campo."
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.groq.com/openai/v1/chat/completions",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "Bearer TU_GROQ_API_KEY"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ model: 'llama-3.3-70b-versatile', temperature: 0.7, max_tokens: 90, messages: [ { role: 'system', content: 'Eres la recepcion de un negocio de servicios en Colombia. Escribe UNA sola frase recordando la cita de manana: calida, breve y en tu-teo colombiano. NO escribas la fecha, la hora ni el nombre del servicio: eso se agrega despues automaticamente. No uses emojis. No pongas comillas. Maximo 25 palabras.' }, { role: 'user', content: 'El cliente se llama ' + $json.nombre + ' y viene por: ' + $json.servicio } ] }) }}",
        "options": {}
      },
      "id": "c1t4s0000-0003-4000-8000-000000000003",
      "name": "Escribir el saludo (Groq)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -160,
        300
      ],
      "onError": "continueErrorOutput",
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 2000,
      "notes": "Solo escribe el saludo. La fecha, la hora y el servicio se agregan después con datos reales."
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Junta lo que escribió la IA con los datos reales de la cita.\n//\n// Este nodo corre UNA VEZ POR CITA (modo \"Run Once for Each Item\"). Así\n// $('Agenda de mañana').item devuelve la cita que corresponde a esta\n// respuesta del modelo, sin depender del orden.\n//\n// REGLA IMPORTANTE: la fecha, la hora y el servicio NO salen del modelo. Se\n// pegan aquí desde la agenda. Al modelo solo se le pide el saludo, que es\n// donde aporta (tono cercano, sin sonar a plantilla). Si le dejaras escribir\n// la hora, algún día se inventaría una — y un cliente llegaría cuando no es.\nconst cita = $('Agenda de mañana').item.json;\n\nlet saludo;\ntry {\n  saludo = $input.item.json.choices[0].message.content.trim();\n} catch (e) {\n  saludo = `Hola ${cita.nombre}, te recordamos tu cita.`;\n}\n\n// Por si el modelo se pasa de largo, mete comillas o se va a varias líneas\nsaludo = saludo.replace(/^[\"']|[\"']$/g, '').split('\\n')[0].slice(0, 220);\n\nconst mensaje =\n  `${saludo}\\n\\n` +\n  `Fecha: ${cita.fecha}\\n` +\n  `Hora: ${cita.hora}\\n` +\n  `Servicio: ${cita.servicio}\\n\\n` +\n  `Si no puedes asistir, avísanos respondiendo este mensaje.`;\n\nreturn {\n  json: {\n    ...cita,\n    mensaje,\n    canal: cita.telefono ? 'whatsapp' : (cita.correo ? 'correo' : 'ninguno'),\n  },\n};\n"
      },
      "id": "c1t4s0000-0004-4000-8000-000000000004",
      "name": "Armar mensaje",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        80,
        200
      ],
      "notes": "Pega el saludo de la IA con los datos exactos de la agenda."
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "version": 2
          },
          "conditions": [
            {
              "id": "cont1",
              "leftValue": "={{ $json.canal }}",
              "rightValue": "ninguno",
              "operator": {
                "type": "string",
                "operation": "notEquals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "c1t4s0000-0005-4000-8000-000000000005",
      "name": "¿Hay cómo contactarlo?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        300,
        200
      ],
      "notes": "Sin teléfono ni correo no hay nada que enviar: esa cita pasa a llamarse a mano."
    },
    {
      "parameters": {
        "sendTo": "={{ $json.correo || 'recepcion@tu-dominio.com' }}",
        "subject": "={{ 'Recordatorio de tu cita — ' + $json.fecha }}",
        "emailType": "text",
        "message": "={{ $json.mensaje }}",
        "options": {}
      },
      "id": "c1t4s0000-0006-4000-8000-000000000006",
      "name": "Enviar recordatorio",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.2,
      "position": [
        540,
        120
      ],
      "notes": "Conecta tu Gmail. Para WhatsApp, cambia este nodo por el de tu proveedor y usa {{ $json.telefono }}."
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "m1",
              "name": "estado",
              "value": "llamar_a_mano",
              "type": "string"
            },
            {
              "id": "m2",
              "name": "cliente",
              "value": "={{ $json.cliente }}",
              "type": "string"
            },
            {
              "id": "m3",
              "name": "cita",
              "value": "={{ $json.fecha + ' ' + $json.hora + ' — ' + $json.servicio }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "c1t4s0000-0007-4000-8000-000000000007",
      "name": "Llamar a mano",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        540,
        300
      ],
      "notes": "Engánchalo a una hoja de cálculo o a Slack para que recepción sepa a quién llamar."
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "e1",
              "name": "estado",
              "value": "error_ia",
              "type": "string"
            },
            {
              "id": "e2",
              "name": "detalle",
              "value": "={{ $json.error || 'Groq no respondió' }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "c1t4s0000-0008-4000-8000-000000000008",
      "name": "Avisar del fallo",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        80,
        420
      ],
      "notes": "Si la IA falla, el recordatorio no sale y alguien tiene que enterarse."
    }
  ],
  "connections": {
    "Todos los días a las 8": {
      "main": [
        [
          {
            "node": "Agenda de mañana",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Agenda de mañana": {
      "main": [
        [
          {
            "node": "Escribir el saludo (Groq)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Escribir el saludo (Groq)": {
      "main": [
        [
          {
            "node": "Armar mensaje",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Avisar del fallo",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Armar mensaje": {
      "main": [
        [
          {
            "node": "¿Hay cómo contactarlo?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "¿Hay cómo contactarlo?": {
      "main": [
        [
          {
            "node": "Enviar recordatorio",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Llamar a mano",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {},
  "meta": {
    "instanceId": "allsoft-academia"
  },
  "tags": []
}
