The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"name": "Agendamiento",
"nodes": [
{
"parameters": {
"resource": "messages-api",
"instanceName": "={{ $('Start').item.json.Instance }}",
"remoteJid": "={{ $('Start').item.json.CallerID }}",
"messageText": "=\ud83d\uddd1\ufe0f Tu turno fue cancelado correctamente.",
"options_message": {}
},
"type": "n8n-nodes-evolution-api.evolutionApi",
"typeVersion": 1,
"position": [
896,
2192
],
"id": "03cd0353-a37a-4bac-a72b-d2564eaf10da",
"name": "cancelacion",
"credentials": {
"evolutionApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "398f759c-6c2e-4e17-8723-237c215b9358",
"leftValue": "={{ $('Start').item.json.message.toUpperCase() }}",
"rightValue": "NO",
"operator": {
"type": "string",
"operation": "equals",
"name": "filter.operator.equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
672,
2192
],
"id": "df1cd286-d3a4-4b1e-8301-b9729fa350b8",
"name": "If2"
},
{
"parameters": {
"resource": "messages-api",
"instanceName": "={{ $('Start').item.json.Instance }}",
"remoteJid": "={{ $('Start').item.json.CallerID }}",
"messageText": "=\u2705 \u00a1Turno confirmado!\n\nTu consulta qued\u00f3 agendada para:\n\n\ud83d\udcc5 {{ $('Get_date_inicio').item.json.dateInicio.toDateTime().toFormat('dd/MM/yyyy') }}\n\ud83d\udd52 {{ $('Get_date_inicio').item.json.dateInicio.toDateTime().toLocaleString({timeStyle: 'short'}) }}",
"options_message": {}
},
"type": "n8n-nodes-evolution-api.evolutionApi",
"typeVersion": 1,
"position": [
2224,
1968
],
"id": "50cd15f5-9a6d-4546-9d22-2cff4ddaf162",
"name": "Confirmacion",
"credentials": {
"evolutionApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "get",
"propertyName": "DoctorId",
"key": "={{ $('Start').item.json.CallerID }}-DoctorId",
"keyType": "string",
"options": {}
},
"type": "n8n-nodes-base.redis",
"typeVersion": 1,
"position": [
1344,
1968
],
"id": "218b41e9-15c2-4e25-8f9b-8c04b656f283",
"name": "Get_DoctorId2",
"credentials": {
"redis": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "get",
"propertyName": "dateFin",
"key": "={{ $('Start').item.json.CallerID }}-fechaHoraFin",
"options": {}
},
"type": "n8n-nodes-base.redis",
"typeVersion": 1,
"position": [
1120,
1968
],
"id": "89adb109-e9c0-41f8-8eea-586f5cc895e6",
"name": "Get_date_fin",
"credentials": {
"redis": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "get",
"propertyName": "dateInicio",
"key": "={{ $('Start').item.json.CallerID }}-fechaHoraInicio",
"options": {}
},
"type": "n8n-nodes-base.redis",
"typeVersion": 1,
"position": [
896,
1968
],
"id": "f7b6c9ce-fe24-4834-b623-c1d8ac706f41",
"name": "Get_date_inicio",
"credentials": {
"redis": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "e2f2a6a2-e6a8-40b0-a429-d1ce21795dfa",
"leftValue": "={{ $('Start').item.json.message.toUpperCase() }}",
"rightValue": "SI",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
448,
2064
],
"id": "835df039-028d-4630-8887-8bb043b5ac5c",
"name": "If1"
},
{
"parameters": {
"operation": "get",
"propertyName": "HorariosDisponibles",
"key": "={{ $('Start').item.json.CallerID }}-HorariosDisponibles",
"keyType": "list",
"options": {}
},
"type": "n8n-nodes-base.redis",
"typeVersion": 1,
"position": [
672,
1776
],
"id": "80c220e5-bf31-4034-a949-04d6db94bdbc",
"name": "get_HorariosDisponibles",
"credentials": {
"redis": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "get",
"propertyName": "DoctorId",
"key": "={{ $('Start').item.json.CallerID }}-DoctorId",
"keyType": "string",
"options": {}
},
"type": "n8n-nodes-base.redis",
"typeVersion": 1,
"position": [
1120,
1776
],
"id": "a0b44e15-1bb7-4371-b06b-45bec1df05a8",
"name": "Get_DoctorId1",
"credentials": {
"redis": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// Input del usuario (ej: \"1\", \"2\", etc.)\nconst choiceRaw = $('Start').first().json.message;\nconst index = parseInt(choiceRaw, 10);\n\n// Validar input num\u00e9rico\nif (isNaN(index) || index < 1) {\n return [{\n json: {\n valido: false,\n mensaje: '\u274c Opci\u00f3n inv\u00e1lida. Respond\u00e9 con el n\u00famero del horario.'\n }\n }];\n}\n\n// Horarios disponibles (array de \"HH:mm\")\nconst horariosDisponibles = $('get_HorariosDisponibles').first().json.HorariosDisponibles;\n\n// Validar que est\u00e9 dentro del array\nif (index > horariosDisponibles.length) {\n return [{\n json: {\n valido: false,\n mensaje: '\u274c El horario seleccionado no existe.'\n }\n }];\n}\n\n// Hora elegida (HH:mm)\nconst horaElegida = horariosDisponibles[index - 1];\n\n// Fecha base ISO\nconst fechaBaseISO = $('Get_date').first().json.fecha_selected;\nconst inicioElegido = new Date(fechaBaseISO);\n\n// Setear hora elegida\nconst [hh, mm] = horaElegida.split(':').map(Number);\ninicioElegido.setUTCHours(hh, mm, 0, 0);\n\n// Duraci\u00f3n\nconst duracion = $('GetDoctor1').first().json.duracion_consulta;\nconst finElegido = new Date(inicioElegido);\nfinElegido.setMinutes(finElegido.getMinutes() + duracion);\n\n// Mensaje de confirmaci\u00f3n\nconst mensaje =\n`\ud83d\udcc5 *Confirmaci\u00f3n de turno*\n\n\ud83d\udc68\u200d\u2695\ufe0f Doctor: ${$('GetDoctor1').first().json.nombre}\n\ud83d\udc64 Paciente: ${$('get_name').first().json.name}\n\ud83d\udcc6 Fecha: ${inicioElegido.toLocaleDateString('es-ES')}\n\u23f0 Hora: ${horaElegida}\n\u231b Duraci\u00f3n: ${duracion} minutos\n\n\u00bfLos datos son correctos?\nRespond\u00e9 *SI* para confirmar o *NO* para cancelar.`;\n\n// OK\nreturn [{\n json: {\n valido: true,\n fecha_hora_inicio: inicioElegido.toISOString(),\n fecha_hora_fin: finElegido.toISOString(),\n mensaje\n }\n}];\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1776,
1776
],
"id": "1ab028bc-0f84-4b5e-9a4a-cf16e29d1d1f",
"name": "Code in JavaScript3"
},
{
"parameters": {
"resource": "messages-api",
"instanceName": "={{ $('Start').item.json.Instance }}",
"remoteJid": "={{ $('Start').item.json.CallerID }}",
"messageText": "={{ $json.mensaje }}",
"options_message": {}
},
"type": "n8n-nodes-evolution-api.evolutionApi",
"typeVersion": 1,
"position": [
2000,
1776
],
"id": "d675d475-e86d-42e2-9838-264e19887087",
"name": "Hora1",
"credentials": {
"evolutionApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "get",
"dataTableId": {
"__rl": true,
"value": "hMRPl1eK7rwrIssD",
"mode": "list",
"cachedResultName": "Doctor",
"cachedResultUrl": "/projects/2FKcChTlWLe9b3wY/datatables/hMRPl1eK7rwrIssD"
},
"matchType": "allConditions",
"filters": {
"conditions": [
{
"keyValue": "={{ $('Get_DoctorId1').item.json.DoctorId }}"
}
]
}
},
"type": "n8n-nodes-base.dataTable",
"typeVersion": 1.1,
"position": [
1344,
1776
],
"id": "8b6e446c-20ae-43b1-84e4-cb8760c066f2",
"name": "GetDoctor1",
"alwaysOutputData": true,
"executeOnce": true
},
{
"parameters": {
"operation": "set",
"key": "={{ $('Start').item.json.CallerID }}-fechaHoraInicio",
"value": "={{ $('Code in JavaScript3').item.json.fecha_hora_inicio }}"
},
"type": "n8n-nodes-base.redis",
"typeVersion": 1,
"position": [
2224,
1776
],
"id": "f9db1d53-88c6-4194-9c9d-44a6a1402862",
"name": "Set_date_inicio",
"credentials": {
"redis": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "set",
"key": "={{ $('Start').item.json.CallerID }}-fechaHoraFin",
"value": "={{ $('Code in JavaScript3').item.json.fecha_hora_fin }}"
},
"type": "n8n-nodes-base.redis",
"typeVersion": 1,
"position": [
2448,
1776
],
"id": "93e9027b-0866-4778-bfbd-b5b8ffc2af67",
"name": "Set_date_fin",
"credentials": {
"redis": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "set",
"key": "={{ $('Start').item.json.CallerID }}-state",
"value": "Confirmacion",
"keyType": "string"
},
"type": "n8n-nodes-base.redis",
"typeVersion": 1,
"position": [
2672,
1776
],
"id": "0f2af1d0-bb54-4cc1-8520-7af248f79a67",
"name": "Set_confirmacion",
"credentials": {
"redis": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "get",
"propertyName": "fecha_selected",
"key": "={{ $('Start').item.json.CallerID }}-fecha",
"options": {}
},
"type": "n8n-nodes-base.redis",
"typeVersion": 1,
"position": [
896,
1776
],
"id": "192c1d39-4a3b-410f-a50f-0592797130af",
"name": "Get_date",
"credentials": {
"redis": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"resource": "messages-api",
"instanceName": "={{ $('Start').item.json.Instance }}",
"remoteJid": "={{ $('Start').item.json.CallerID }}",
"messageText": "=No pude reconocer la fecha que enviaste.\nPor favor, escribila en el formato dd/MM/yyyy (por ejemplo: 15/03/2026).",
"options_message": {}
},
"type": "n8n-nodes-evolution-api.evolutionApi",
"typeVersion": 1,
"position": [
896,
1584
],
"id": "848f466e-3a1a-4e5f-81ef-f0f8ed5c569d",
"name": "FechaInvalida",
"credentials": {
"evolutionApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "const texto = $('Start').first().json.message;\n\n// 1. Validar formato dd/MM/yyyy con regex estricta\nconst regex = /^(0[1-9]|[12][0-9]|3[01])\\/(0[1-9]|1[0-2])\\/\\d{4}$/;\n\nif (!regex.test(texto)) {\n return [{ json: { valido: false, motivo: 'Formato inv\u00e1lido (dd/MM/yyyy)' } }];\n}\n\n// 2. Validar que la fecha exista realmente\nconst [dia, mes, anio] = texto.split('/').map(Number);\nconst fecha = new Date(anio, mes - 1, dia);\n\nconst fechaValida =\n fecha.getFullYear() === anio &&\n fecha.getMonth() === mes - 1 &&\n fecha.getDate() === dia;\n\nif (!fechaValida) {\n return [{ json: { valido: false, motivo: 'La fecha no existe' } }];\n}\n\n// 3. Fecha v\u00e1lida\nreturn [{\n json: {\n valido: true,\n fecha_original: texto,\n fecha_date: fecha.toISOString()\n }\n}];\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
448,
1456
],
"id": "f7fb175a-4785-40b8-9463-b47921bd5f54",
"name": "Code in JavaScript1"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 3
},
"conditions": [
{
"id": "60e72980-9c72-416d-ab7f-402b7d4a2a2a",
"leftValue": "={{ $json.valido }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "or"
},
"looseTypeValidation": true,
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
672,
1456
],
"id": "5907609a-35ff-49ab-b08b-e413f869d362",
"name": "Is_Date"
},
{
"parameters": {
"resource": "messages-api",
"instanceName": "={{ $('Start').item.json.Instance }}",
"remoteJid": "={{ $('Start').item.json.CallerID }}",
"messageText": "=Lo siento, {{ $json.nombre.includes('Dr.')? 'el ' : \"la \"}}{{ $json.nombre}} no atiende el d\u00eda seleccionado. ",
"options_message": {}
},
"type": "n8n-nodes-evolution-api.evolutionApi",
"typeVersion": 1,
"position": [
2240,
1392
],
"id": "6e7f61f1-7bae-4c0e-8ab5-95e5a48b7846",
"name": "NoAtiende",
"credentials": {
"evolutionApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "get",
"propertyName": "State",
"key": "={{ $json.CallerID }}-state",
"keyType": "string",
"options": {}
},
"type": "n8n-nodes-base.redis",
"typeVersion": 1,
"position": [
0,
1296
],
"id": "f17969e1-6717-4bb5-80ab-eaf1440ae80c",
"name": "Redis",
"credentials": {
"redis": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"resource": "messages-api",
"instanceName": "={{ $('Start').item.json.Instance }}",
"remoteJid": "={{ $('Start').item.json.CallerID }}",
"messageText": "=La fecha que indicaste corresponde a un d\u00eda que ya pas\u00f3.\nPor favor, env\u00edame una fecha posterior a hoy en el formato dd/MM/yyyy (por ejemplo: 25/03/2026).",
"options_message": {}
},
"type": "n8n-nodes-evolution-api.evolutionApi",
"typeVersion": 1,
"position": [
1120,
1488
],
"id": "283885dd-2d45-49dc-b3a5-0d47903224a7",
"name": "FechaPasada",
"credentials": {
"evolutionApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "c8ac0187-bd06-4451-934f-957207aaa36c",
"leftValue": "={{ $json.State }}",
"rightValue": "Nombre",
"operator": {
"type": "string",
"operation": "empty",
"singleValue": true
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "Nombre"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "3523c819-6dff-4377-b084-b5b70bde7049",
"leftValue": "={{ $json.State }}",
"rightValue": "Nombre",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "inicio"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "5399f39b-7616-4435-9f48-34fd4dc7ed13",
"leftValue": "={{ $json.State }}",
"rightValue": "Inicio",
"operator": {
"type": "string",
"operation": "equals",
"name": "filter.operator.equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "Doctor"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "a4be1f08-0898-41bc-bea6-ff912f3ac52e",
"leftValue": "={{ $json.State }}",
"rightValue": "Fecha",
"operator": {
"type": "string",
"operation": "equals",
"name": "filter.operator.equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "Fecha"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "503f4fe4-b574-41c4-9054-b6230c1a2d77",
"leftValue": "={{ $json.State }}",
"rightValue": "Hora",
"operator": {
"type": "string",
"operation": "equals",
"name": "filter.operator.equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "Hora"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "ba4575ca-6523-4e12-9e77-3e658141c2f9",
"leftValue": "={{ $json.State }}",
"rightValue": "Confirmacion",
"operator": {
"type": "string",
"operation": "equals",
"name": "filter.operator.equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "Confirmacion"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.switch",
"typeVersion": 3.4,
"position": [
224,
1232
],
"id": "ba6731e7-a651-45ac-80cd-f639747f2f23",
"name": "Fase"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "1e924288-3c7c-4f79-9bca-2883bde79c02",
"leftValue": "={{ $json.fecha_date }}",
"rightValue": "={{ $now }}",
"operator": {
"type": "dateTime",
"operation": "after"
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
896,
1392
],
"id": "5de41d1e-f320-4449-b8c6-54c0ab9115e0",
"name": "Is_Valid_Date"
},
{
"parameters": {
"operation": "get",
"propertyName": "DoctorId",
"key": "={{ $('Start').item.json.CallerID }}-DoctorId",
"keyType": "string",
"options": {}
},
"type": "n8n-nodes-base.redis",
"typeVersion": 1,
"position": [
1120,
1296
],
"id": "990c061b-f971-4fc0-8124-b2980643c4d0",
"name": "Get_DoctorId",
"credentials": {
"redis": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "get",
"dataTableId": {
"__rl": true,
"value": "Vh1ixrYCpUuuKazI",
"mode": "list",
"cachedResultName": "Horario",
"cachedResultUrl": "/projects/2FKcChTlWLe9b3wY/datatables/Vh1ixrYCpUuuKazI"
},
"matchType": "allConditions",
"filters": {
"conditions": [
{
"keyName": "doctor_id",
"keyValue": "={{ $json.DoctorId }}"
},
{
"keyName": "dia_semana",
"keyValue": "={{ $('Code in JavaScript1').item.json.fecha_date.toDateTime().extract(\"weekday\") }}"
}
]
}
},
"type": "n8n-nodes-base.dataTable",
"typeVersion": 1.1,
"position": [
1344,
1296
],
"id": "4d1997a3-5ca1-42e2-9766-b8d9c147b2e3",
"name": "GetHorario",
"alwaysOutputData": true
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "6fbcb57f-c881-468d-a6b5-e1720df81894",
"leftValue": "={{ $('GetHorario').item.json.dia_semana }}",
"rightValue": "",
"operator": {
"type": "number",
"operation": "notEmpty",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
2016,
1296
],
"id": "726b8df8-99bc-4d30-b461-2d88f1d92bde",
"name": "Is_attending that date"
},
{
"parameters": {
"jsCode": "const appointments = $('GetTurnos').all().map(i => i.json);\nconst horarios = $('GetHorario').all().map(i => i.json);\n\nconst doctorNombre = $('GetDoctor').first().json.nombre;\nconst duracion_consulta = $('GetDoctor').first().json.duracion_consulta; // minutos\nconst fechaISO = $('Code in JavaScript1').first().json.fecha_date;\n\nconst fecha = new Date(fechaISO);\nconst diaSemana = fecha.getUTCDay(); // 0=domingo ... 6=s\u00e1bado\nlet prefix;\nconst horariosDelDia = horarios.filter(h => h.dia_semana === diaSemana);\nif(doctorNombre.startsWith(\"Dr.\")){\n prefix = 'el';\n} else {\n prefix = 'la';\n}\nconst citasReservadas = appointments\n .filter(a => a.estado === 'reservado')\n .map(a => ({\n inicio: new Date(a.fecha_hora_inicio),\n fin: new Date(a.fecha_hora_fin),\n }));\n\nconst disponibles = [];\n\nfor (const h of horariosDelDia) {\n let hora = h.hora_inicio;\n\n while (hora < h.hora_fin) {\n const horas = Math.floor(hora / 100);\n const minutos = hora % 100;\n\n const inicioSlot = new Date(fecha);\n inicioSlot.setUTCHours(horas, minutos, 0, 0);\n\n const finSlot = new Date(inicioSlot);\n finSlot.setMinutes(finSlot.getMinutes() + duracion_consulta);\n\n const ocupado = citasReservadas.some(c =>\n inicioSlot < c.fin && finSlot > c.inicio\n );\n\n if (!ocupado && finSlot.getUTCHours() * 100 + finSlot.getUTCMinutes() <= h.hora_fin) {\n disponibles.push(inicioSlot.toISOString().substring(11, 16));\n }\n\n // \u23ed avanzar seg\u00fan duraci\u00f3n real\n const totalMin = minutos + duracion_consulta;\n hora = horas * 100 + totalMin;\n\n // corregir overflow de minutos (> 60)\n if (hora % 100 >= 60) {\n hora += Math.floor((hora % 100) / 60) * 40;\n }\n }\n}\n\nconst emojis = ['1\ufe0f\u20e3','2\ufe0f\u20e3','3\ufe0f\u20e3','4\ufe0f\u20e3','5\ufe0f\u20e3','6\ufe0f\u20e3','7\ufe0f\u20e3','8\ufe0f\u20e3','9\ufe0f\u20e3','\ud83d\udd1f'];\nconst fechaTexto = fecha.toLocaleDateString('es-ES');\nlet mensaje;\n\nif (disponibles.length === 0) {\n mensaje = `\u274c No hay horarios disponibles para ${prefix} ${doctorNombre} el ${fechaTexto}.\\n\\n` +\n `Por favor, eleg\u00ed otra fecha o contact\u00e1 con recepci\u00f3n.`;\n} else {\n const emojis = ['1\ufe0f\u20e3','2\ufe0f\u20e3','3\ufe0f\u20e3','4\ufe0f\u20e3','5\ufe0f\u20e3','6\ufe0f\u20e3','7\ufe0f\u20e3','8\ufe0f\u20e3','9\ufe0f\u20e3','\ud83d\udd1f'];\n\n mensaje = `Estos son los horarios disponibles para ${prefix} ${doctorNombre} el ${fechaTexto}:\\n\\n`;\n\n disponibles.forEach((h, i) => {\n mensaje += `${emojis[i] || '\u23f1\ufe0f'} ${h}\\n`;\n });\n\n mensaje += `\\nRespond\u00e9 escribiendo el numero de la hora exacta que prefieras. \\n(Ejemplo: 2)`;\n}\n\nreturn [\n {\n json: {\n mensaje,\n horariosDisponibles: disponibles,\n hayDisponibilidad: disponibles.length > 0,\n duracion_consulta\n }\n }\n];\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2240,
1200
],
"id": "8dfec529-7912-4dc8-b0b0-027eb56e336c",
"name": "Code in JavaScript"
},
{
"parameters": {
"operation": "get",
"dataTableId": {
"__rl": true,
"value": "wA0dcDuSPu44zLSy",
"mode": "list",
"cachedResultName": "Appointment",
"cachedResultUrl": "/projects/2FKcChTlWLe9b3wY/datatables/wA0dcDuSPu44zLSy"
},
"matchType": "allConditions",
"filters": {
"conditions": [
{
"keyName": "doctor_id",
"keyValue": "={{ $('Get_DoctorId').item.json.DoctorId }}"
},
{
"keyName": "fecha_hora_inicio",
"condition": "gte",
"keyValue": "={{ $('Is_Valid_Date').item.json.fecha_date.toDateTime() }}"
},
{
"keyName": "fecha_hora_fin",
"condition": "lt",
"keyValue": "={{ $('Is_Valid_Date').item.json.fecha_date.toDateTime().plus(1,\"days\") }}"
}
]
}
},
"type": "n8n-nodes-base.dataTable",
"typeVersion": 1.1,
"position": [
1568,
1296
],
"id": "165159f4-28c7-4994-968d-7e61e373d6aa",
"name": "GetTurnos",
"alwaysOutputData": true
},
{
"parameters": {
"resource": "messages-api",
"instanceName": "={{ $('Start').item.json.Instance }}",
"remoteJid": "={{ $('Start').item.json.CallerID }}",
"messageText": "={{ $json.mensaje }}",
"options_message": {}
},
"type": "n8n-nodes-evolution-api.evolutionApi",
"typeVersion": 1,
"position": [
2464,
1200
],
"id": "abe68636-b861-497a-b576-d5c13bd27c41",
"name": "Hora",
"credentials": {
"evolutionApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "get",
"dataTableId": {
"__rl": true,
"value": "hMRPl1eK7rwrIssD",
"mode": "list",
"cachedResultName": "Doctor",
"cachedResultUrl": "/projects/2FKcChTlWLe9b3wY/datatables/hMRPl1eK7rwrIssD"
},
"matchType": "allConditions",
"filters": {
"conditions": [
{
"keyValue": "={{ $('Get_DoctorId').item.json.DoctorId }}"
}
]
}
},
"type": "n8n-nodes-base.dataTable",
"typeVersion": 1.1,
"position": [
1792,
1296
],
"id": "28e168d4-65d2-4b8e-bb72-ebf33d7fa285",
"name": "GetDoctor",
"alwaysOutputData": true,
"executeOnce": true
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "ebe600fb-d3ac-4886-9b64-109ecbede96a",
"leftValue": "={{ $('Code in JavaScript').item.json.hayDisponibilidad }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
2688,
1200
],
"id": "1f0cf5da-59db-44b9-80a3-44c556cf5e23",
"name": "If"
},
{
"parameters": {
"operation": "set",
"key": "={{ $('Start').item.json.CallerID }}-fecha",
"value": "={{ $('Code in JavaScript1').item.json.fecha_date }}"
},
"type": "n8n-nodes-base.redis",
"typeVersion": 1,
"position": [
3136,
1200
],
"id": "cf9bc7db-7198-4922-8ddc-d197420184d9",
"name": "Set_date",
"credentials": {
"redis": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "set",
"key": "={{ $('Start').item.json.CallerID }}-state",
"value": "Hora",
"keyType": "string"
},
"type": "n8n-nodes-base.redis",
"typeVersion": 1,
"position": [
3360,
1200
],
"id": "db7763e6-d003-424d-aa34-4161cb7c2c5f",
"name": "Set_hora",
"credentials": {
"redis": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "push",
"list": "={{ $('Start').item.json.CallerID }}-HorariosDisponibles",
"messageData": "={{ $('Code in JavaScript').item.json.horariosDisponibles }}",
"tail": true
},
"type": "n8n-nodes-base.redis",
"typeVersion": 1,
"position": [
2912,
1200
],
"id": "78f0f456-8825-4340-9767-ec0b5c5ba3ef",
"name": "Save_Horarios",
"credentials": {
"redis": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"resource": "messages-api",
"instanceName": "={{ $('Start').item.json.Instance }}",
"remoteJid": "={{ $('Start').item.json.CallerID }}",
"messageText": "={{ $json.message }}",
"options_message": {}
},
"type": "n8n-nodes-evolution-api.evolutionApi",
"typeVersion": 1,
"position": [
1344,
1104
],
"id": "eec7e697-d101-4501-ae21-49cd3278fb35",
"name": "NumeroInvalido",
"credentials": {
"evolutionApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "const choice = $('Start').first().json.message;\n\ntry {\n const number = parseInt(choice, 10);\n\n if (isNaN(number)) {\n throw new Error('El valor no es un n\u00famero v\u00e1lido');\n }\n\n const doctores = $input.first().json.arrayDoctores;\n\n for (let index = 0; index < doctores.length; index++) {\n if (number - 1 === index) {\n return doctores[index];\n }\n }\n\n throw new Error('\u00cdndice fuera de rango');\n\n} catch (error) {\n return {\n error: true,\n message: error.message\n };\n}\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
896,
1008
],
"id": "4c236bf6-11b0-4727-92fc-fc65b1fc4e50",
"name": "Code in JavaScript2"
},
{
"parameters": {
"operation": "get",
"propertyName": "=arrayDoctores",
"key": "={{ $('Start').item.json.CallerID }}-doctores_disponibles",
"options": {}
},
"type": "n8n-nodes-base.redis",
"typeVersion": 1,
"position": [
448,
1008
],
"id": "bfff2885-c053-41d9-b72f-bf488328c9df",
"name": "Get_Array_Doctores",
"credentials": {
"redis": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "def45316-a4eb-44ff-921b-0e7b79655ded",
"name": "arrayDoctores",
"value": "={{ $json.arrayDoctores.parseJson() }}",
"type": "array"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
672,
1008
],
"id": "61575137-c653-4012-9728-69589f7e99b5",
"name": "str->arr"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "8a3c61a2-e6dc-4380-8532-9b16aecb61e6",
"leftValue": "={{ $json.error }}",
"rightValue": false,
"operator": {
"type": "boolean",
"operation": "false",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
1120,
1008
],
"id": "ecfb69d2-515e-493c-9fbe-9893936585df",
"name": "Is_selection_valid"
},
{
"parameters": {
"resource": "messages-api",
"instanceName": "={{ $('Start').item.json.Instance }}",
"remoteJid": "={{ $('Start').item.json.CallerID }}",
"messageText": "=Perfecto \ud83d\udc4d\nElegiste a {{ $json.nombre }}.\n\n\u00bfPara qu\u00e9 d\u00eda quer\u00e9s agendar la consulta?\n\n\ud83d\udcc5 Escrib\u00ed la fecha en formato DD/MM/AAAA.",
"options_message": {}
},
"type": "n8n-nodes-evolution-api.evolutionApi",
"typeVersion": 1,
"position": [
1344,
912
],
"id": "9f8a3d15-4f47-4501-a5d3-5900cd48b0d3",
"name": "Fecha",
"credentials": {
"evolutionApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "set",
"key": "={{ $('Start').item.json.CallerID }}-state",
"value": "Fecha",
"keyType": "string"
},
"type": "n8n-nodes-base.redis",
"typeVersion": 1,
"position": [
1568,
912
],
"id": "3c0c99bd-2ab8-48b0-b28e-7d13da2abb65",
"name": "Set_fecha",
"credentials": {
"redis": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "set",
"key": "={{ $('Start').item.json.CallerID }}-DoctorId",
"value": "={{ $('Code in JavaScript2').item.json.id }}",
"keyType": "string"
},
"type": "n8n-nodes-base.redis",
"typeVersion": 1,
"position": [
1792,
912
],
"id": "59ed0ed5-4ef6-4b43-ab3f-d65799c6950f",
"name": "Set_DoctorId",
"credentials": {
"redis": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "set",
"key": "={{ $('Parsear mensaje de inicio').item.json.numero }}-state",
"value": "Inicio",
"keyType": "string"
},
"type": "n8n-nodes-base.redis",
"typeVersion": 1,
"position": [
1344,
720
],
"id": "fe502097-a0b3-473f-a7c5-5d150e699662",
"name": "Set_inicio",
"credentials": {
"redis": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "get",
"dataTableId": {
"__rl": true,
"value": "hMRPl1eK7rwrIssD",
"mode": "list",
"cachedResultName": "Doctor",
"cachedResultUrl": "/projects/2FKcChTlWLe9b3wY/datatables/hMRPl1eK7rwrIssD"
}
},
"type": "n8n-nodes-base.dataTable",
"typeVersion": 1.1,
"position": [
672,
720
],
"id": "16d81b24-e876-45f5-ae7b-eda239cfbf9a",
"name": "Get Doctores"
},
{
"parameters": {
"resource": "messages-api",
"instanceName": "={{ $json.instancia }}",
"remoteJid": "={{ $json.numero }}",
"messageText": "={{ $json.mensaje }}",
"options_message": {}
},
"type": "n8n-nodes-evolution-api.evolutionApi",
"typeVersion": 1,
"position": [
1120,
720
],
"id": "384d2c06-7224-43e9-bf88-709d1438dfcd",
"name": "Inicio",
"credentials": {
"evolutionApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "set",
"key": "={{ $json.data.key.remoteJid }}-doctores_disponibles",
"value": "={{ $('Parsear mensaje de inicio').item.json.doctoresIncluidos.toJsonString() }}"
},
"type": "n8n-nodes-base.redis",
"typeVersion": 1,
"position": [
1568,
720
],
"id": "1f80eb97-9853-4543-8a24-f8e93b8d27ed",
"name": "Array_Doctores",
"credentials": {
"redis": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "/**\n * Entrada esperada:\n * items = [\n * { json: { nombre, especialidad, id, ... } },\n * ...\n * ]\n */\n\n// Obtener doctores desde los items de entrada\nconst doctoresOriginales = items.map(item => item.json);\n\n// Si no hay doctores\nif (doctoresOriginales.length === 0) {\n return [\n {\n json: {\n mensaje: \"\u26a0\ufe0f No hay doctores disponibles en este momento.\",\n numero: $('Start').first().json.CallerID_firstItem,\n instancia: $('Start').first().json._firstItem['Nombre de instancia'],\n doctoresIncluidos: [] // array vac\u00edo porque no hay doctores\n }\n }\n ];\n}\n\n// Filtrar doctores duplicados por id\nconst seenIds = new Set();\nconst doctores = [];\n\ndoctoresOriginales.forEach(doctor => {\n if (!seenIds.has(doctor.id)) {\n seenIds.add(doctor.id);\n doctores.push(doctor);\n }\n});\n\n// Construcci\u00f3n del mensaje para WhatsApp\nlet mensaje = \"Por favor, eleg\u00ed el doctor con quien quer\u00e9s agendar:\\n\\n\";\n\ndoctores.forEach((doctor, index) => {\n mensaje += `${index + 1}\ufe0f\u20e3 ${doctor.nombre} \u2013 ${doctor.especialidad}\\n`;\n});\n\n// Retorno para n8n, incluyendo los doctores que se mostraron en el mensaje\nreturn [\n {\n json: {\n mensaje: mensaje.trim(),\n numero: $('Start').first().json.CallerID,\n instancia: $('Start').first().json.Instance,\n doctoresIncluidos: doctores // array de doctores mostrados en el mensaje\n }\n }\n];\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
896,
720
],
"id": "06632a50-cb3c-48f7-9cee-33d755b2a21c",
"name": "Parsear mensaje de inicio"
},
{
"parameters": {
"workflowInputs": {
"values": [
{
"name": "CallerID"
},
{
"name": "message"
},
{
"name": "Instance"
}
]
}
},
"id": "f85e3538-bf2a-4642-806f-1f344515070c",
"typeVersion": 1.1,
"name": "Start",
"type": "n8n-nodes-base.executeWorkflowTrigger",
"position": [
-448,
1664
]
},
{
"parameters": {
"workflowId": {
"__rl": true,
"value": "9npekj3aPag37ifbW5rTQ",
"mode": "list",
"cachedResultUrl": "/workflow/9npekj3aPag37ifbW5rTQ",
"cachedResultName": "Deletion_of_Redis"
},
"workflowInputs": {
"mappingMode": "defineBelow",
"value": {
"CallerID": "={{ $('Start').item.json.CallerID }}"
},
"matchingColumns": [
"CallerID"
],
"schema": [
{
"id": "CallerID",
"displayName": "CallerID",
"required": false,
"defaultMatch": false,
"display": true,
"canBeUsedToMatch": true,
"type": "string",
"removed": false
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": true
},
"options": {}
},
"type": "n8n-nodes-base.executeWorkflow",
"typeVersion": 1.3,
"position": [
2448,
2032
],
"id": "891dbe9e-f965-40a3-b453-b7f8fc933872",
"name": "Call 'DeleteKeys'"
},
{
"parameters": {
"resource": "messages-api",
"instanceName": "={{ $('Start').item.json.Instance }}",
"remoteJid": "={{ $('Start').item.json.CallerID }}",
"messageText": "=Lo siento, no entend\u00ed tu respuesta.\n\nPor favor, respond\u00e9 con:\nSI \ud83d\udc49 para confirmar el turno \nNO \ud83d\udc49 para cancelarlo",
"options_message": {}
},
"type": "n8n-nodes-evolution-api.evolutionApi",
"typeVersion": 1,
"position": [
896,
2384
],
"id": "fb72aab8-b648-492d-ae29-c0249e741b81",
"name": "cancelacion1",
"credentials": {
"evolutionApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"resource": "messages-api",
"instanceName": "={{ $('Start').item.json.Instance }}",
"remoteJid": "={{ $('Start').item.json.CallerID }}",
"messageText": "=\ud83d\uddd3\ufe0f Elegiste *Agendar una cita*.\n\nPor favor, escrib\u00ed tu *nombre completo* para continuar con el proceso.\n\nSi en cualquier momento dese\u00e1s cancelar, escrib\u00ed *CANCELAR*.",
"options_message": {}
},
"type": "n8n-nodes-evolution-api.evolutionApi",
"typeVersion": 1,
"position": [
448,
528
],
"id": "26c5fc40-a776-43d0-be97-65725169d21c",
"name": "Inicio1",
"credentials": {
"evolutionApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "set",
"key": "={{ $('Start').item.json.CallerID }}-state",
"value": "Nombre",
"keyType": "string"
},
"type": "n8n-nodes-base.redis",
"typeVersion": 1,
"position": [
672,
528
],
"id": "48d886b1-a0ba-4049-aa47-168e009d85bd",
"name": "Set_Nombre",
"credentials": {
"redis": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "set",
"key": "={{ $('Start').item.json.CallerID }}-name",
"value": "={{ $('Start').item.json.message.toTitleCase()}}"
},
"type": "n8n-nodes-base.redis",
"typeVersion": 1,
"position": [
448,
720
],
"id": "bbfa8e1f-a72c-4a99-90d2-c6dab7e32e2d",
"name": "set name",
"credentials": {
"redis": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"dataTableId": {
"__rl": true,
"value": "wA0dcDuSPu44zLSy",
"mode": "list",
"cachedResultName": "Appointment",
"cachedResultUrl": "/projects/2FKcChTlWLe9b3wY/datatables/wA0dcDuSPu44zLSy"
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"doctor_id": "={{ $('Get_DoctorId2').item.json.DoctorId }}",
"paciente_telefono": "={{ $('Start').item.json.CallerID.replaceAll('@s.whatsapp.net', '') }}",
"fecha_hora_inicio": "={{ $('Get_date_inicio').item.json.dateInicio.toDateTime() }}",
"fecha_hora_fin": "={{ $('Get_date_fin').item.json.dateFin.toDateTime() }}",
"estado": "reservado",
"origen": "Whatsapp",
"paciente_nombre": "={{ $('get_name1').item.json.name }}",
"calendar_id": "={{ $json.id }}"
},
"matchingColumns": [],
"schema": [
{
"id": "doctor_id",
"displayName": "doctor_id",
"required": false,
"defaultMatch": false,
"display": true,
"type": "number",
"readOnly": false,
"removed": false
},
{
"id": "paciente_nombre",
"displayName": "paciente_nombre",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": false
},
{
"id": "paciente_telefono",
"displayName": "paciente_telefono",
"required": false,
"defaultMatch": false,
"display": true,
"type": "number",
"readOnly": false,
"removed": false
},
{
"id": "fecha_hora_inicio",
"displayName": "fecha_hora_inicio",
"required": false,
"defaultMatch": false,
"display": true,
"type": "dateTime",
"readOnly": false,
"removed": false
},
{
"id": "fecha_hora_fin",
"displayName": "fecha_hora_fin",
"required": false,
"defaultMatch": false,
"display": true,
"type": "dateTime",
"readOnly": false,
"removed": false
},
{
"id": "estado",
"displayName": "estado",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": false
},
{
"id": "origen",
"displayName": "origen",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": false
},
{
"id": "calendar_id",
"displayName": "calendar_id",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": false
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {}
},
"type": "n8n-nodes-base.dataTable",
"typeVersion": 1.1,
"position": [
2000,
1968
],
"id": "7c58cfd6-6a5d-453b-af5b-f102fce13dae",
"name": "Save_appointment",
"alwaysOutputData": true,
"executeOnce": true
},
{
"parameters": {
"operation": "get",
"propertyName": "name",
"key": "={{ $('Start').item.json.CallerID }}-name",
"options": {}
},
"type": "n8n-nodes-base.redis",
"typeVersion": 1,
"position": [
448,
1776
],
"id": "17c59ca0-afd0-4fd7-aa39-3ac0dee06bef",
"name": "get_name",
"credentials": {
"redis": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "get",
"propertyName": "name",
"key": "={{ $('Start').item.json.CallerID }}-name",
"options": {}
},
"type": "n8n-nodes-base.redis",
"typeVersion": 1,
"position": [
672,
1968
],
"id": "71c8623f-71db-4619-982e-4d085fb35f39",
"name": "get_name1",
"credentials": {
"redis": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "d810d814-5e9f-4fc7-801a-329aa1ec768c",
"leftValue": "={{ $json.message.toUpperCase() }}",
"rightValue": "CANCELAR",
"operator": {
"type": "string",
"operation": "notEquals"
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
-224,
1664
],
"id": "14a707dd-59a2-4471-97ca-b8f8dce53b6b",
"name": "If3"
},
{
"parameters": {
"calendar": {
"__rl": true,
"value": "={{ $json.calendar_id }}",
"mode": "id"
},
"start": "={{ $('Get_date_inicio').item.json.dateInicio.toDateTime().setZone('America/Buenos_Aires').plus(3, 'hours') }}",
"end": "={{ $('Get_date_fin').item.json.dateFin.toDateTime().setZone('America/Buenos_Aires').plus(3, 'hours') }}",
"additionalFields": {
"description": "=Nombre del paciente: {{ $('get_name1').item.json.name }}\nNumero de telefono: {{ $('Start').item.json.CallerID.replace('@s.whatsapp.net','') }}\n",
"showMeAs": "opaque",
"summary": "=Turno \"{{ $('get_name1').item.json.name }}\""
}
},
"type": "n8n-nodes-base.googleCalendar",
"typeVersion": 1.3,
"position": [
1776,
1968
],
"id": "a3cda16a-1861-450d-b70b-20638398c908",
"name": "Create an event",
"credentials": {
"googleCalendarOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "get",
"dataTableId": {
"__rl": true,
"value": "hMRPl1eK7rwrIssD",
"mode": "list",
"cachedResultName": "Doctor",
"cachedResultUrl": "/projects/2FKcChTlWLe9b3wY/datatables/hMRPl1eK7rwrIssD"
},
"filters": {
"conditions": [
{
"keyValue": "={{ $json.DoctorId }}"
}
]
}
},
"type": "n8n-nodes-base.dataTable",
"typeVersion": 1.1,
"position": [
1552,
1968
],
"id": "8a4ac19f-44db-4918-8455-196e2a979d33",
"name": "Get row(s)"
}
],
"connections": {
"Redis": {
"main": [
[
{
"node": "Fase",
"type": "main",
"index": 0
}
]
]
},
"Get Doctores": {
"main": [
[
{
"node": "Parsear mensaje de inicio",
"type": "main",
"index": 0
}
]
]
},
"Inicio": {
"main": [
[
{
"node": "Set_inicio",
"type": "main",
"index": 0
}
]
]
},
"Fecha": {
"main": [
[
{
"node": "Set_fecha",
"type": "main",
"index": 0
}
]
]
},
"Code in JavaScript1": {
"main": [
[
{
"node": "Is_Date",
"type": "main",
"index": 0
}
]
]
},
"Set_inicio": {
"main": [
[
{
"node": "Array_Doctores",
"type": "main",
"index": 0
}
]
]
},
"Code in JavaScript2": {
"main": [
[
{
"node": "Is_selection_valid",
"type": "main",
"index": 0
}
]
]
},
"Parsear mensaje de inicio": {
"main": [
[
{
"node": "Inicio",
"type": "main",
"index": 0
}
]
]
},
"Get_Array_Doctores": {
"main": [
[
{
"node": "str->arr",
"type": "main",
"index": 0
}
]
]
},
"str->arr": {
"main": [
[
{
"node": "Code in JavaScript2",
"type": "main",
"index": 0
}
]
]
},
"Is_selection_valid": {
"main": [
[
{
"node": "Fecha",
"type": "main",
"index": 0
}
],
[
{
"node": "NumeroInvalido",
"type": "main",
"index": 0
}
]
]
},
"Is_Valid_Date": {
"main": [
[
{
"node": "Get_DoctorId",
"type": "main",
"index": 0
}
],
[
{
"node": "FechaPasada",
"type": "main",
"index": 0
}
]
]
},
"Is_Date": {
"main": [
[
{
"node": "Is_Valid_Date",
"type": "main",
"index": 0
}
],
[
{
"node": "FechaInvalida",
"type": "main",
"index": 0
}
]
]
},
"Set_fecha": {
"main": [
[
{
"node": "Set_DoctorId",
"type": "main",
"index": 0
}
]
]
},
"Get_DoctorId": {
"main": [
[
{
"node": "GetHorario",
"type": "main",
"index": 0
}
]
]
},
"GetHorario": {
"main": [
[
{
"node": "GetTurnos",
"type": "main",
"index": 0
}
]
]
},
"Set_DoctorId": {
"main": [
[]
]
},
"Is_attending that date": {
"main": [
[
{
"node": "Code in JavaScript",
"type": "main",
"index": 0
}
],
[
{
"node": "NoAtiende",
"type": "main",
"index": 0
}
]
]
},
"GetTurnos": {
"main": [
[
{
"node": "GetDoctor",
"type": "main",
"index": 0
}
]
]
},
"Code in JavaScript": {
"main": [
[
{
"node": "Hora",
"type": "main",
"index": 0
}
]
]
},
"GetDoctor": {
"main": [
[
{
"node": "Is_attending that date",
"type": "main",
"index": 0
}
]
]
},
"Hora": {
"main": [
[
{
"node": "If",
"type": "main",
"index": 0
}
]
]
},
"If": {
"main": [
[
{
"node": "Save_Horarios",
"type": "main",
"index": 0
}
]
]
},
"Set_date": {
"main": [
[
{
"node": "Set_hora",
"type": "main",
"index": 0
}
]
]
},
"Fase": {
"main": [
[
{
"node": "Inicio1",
"type": "main",
"index": 0
}
],
[
{
"node": "set name",
"type": "main",
"index": 0
}
],
[
{
"node": "Get_Array_Doctores",
"type": "main",
"index": 0
}
],
[
{
"node": "Code in JavaScript1",
"type": "main",
"index": 0
}
],
[
{
"node": "get_name",
"type": "main",
"index": 0
}
],
[
{
"node": "If1",
"type": "main",
"index": 0
}
]
]
},
"get_HorariosDisponibles": {
"main": [
[
{
"node": "Get_date",
"type": "main",
"index": 0
}
]
]
},
"Save_Horarios": {
"main": [
[
{
"node": "Set_date",
"type": "main",
"index": 0
}
]
]
},
"Get_DoctorId1": {
"main": [
[
{
"node": "GetDoctor1",
"type": "main",
"index": 0
}
]
]
},
"Code in JavaScript3": {
"main": [
[
{
"node": "Hora1",
"type": "main",
"index": 0
}
]
]
},
"GetDoctor1": {
"main": [
[
{
"node": "Code in JavaScript3",
"type": "main",
"index": 0
}
]
]
},
"Hora1": {
"main": [
[
{
"node": "Set_date_inicio",
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
evolutionApigoogleCalendarOAuth2Apiredis
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
How this works
This workflow streamlines appointment scheduling by automating the process of booking, confirming, or cancelling slots with healthcare providers, saving time for busy clinic administrators and patients alike. It integrates seamlessly with WhatsApp via the Evolution API to handle real-time notifications and Google Calendar to sync availability, ensuring no double-bookings occur. The key step involves retrieving doctor schedules from Redis storage to check and reserve open times based on user requests, making coordination effortless without manual calendars.
Use this workflow for high-volume practices needing instant WhatsApp-based scheduling to reduce phone calls and errors. Avoid it for one-off personal bookings, as its event-driven complexity suits ongoing operations better. Common variations include adapting it for service businesses by swapping doctor IDs for stylist slots or adding email confirmations alongside WhatsApp.
About this workflow
Agendamiento. Uses n8n-nodes-evolution-api, redis, dataTable, executeWorkflowTrigger. Event-driven trigger; 60 nodes.
Source: https://github.com/ignacioelizeche/BackOffice-Odonto/blob/13b047f47b8a3442a521b3ff148aff5b26d8f464/N8N/Agendamiento.json — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
Prevent concurrent workflow runs using Redis. Uses executeWorkflowTrigger, manualTrigger, stickyNote, executeWorkflow. Event-driven trigger; 43 nodes.
This workflow sets a small "lock" value in Redis so that only one copy of a long job can run at the same time. If another trigger fires while the job is still busy, the workflow sees the lock, stops e
This implementation aggregates incoming data into a Redis list from potentially concurrent workflow executions. It buffers the data for a set period before a single execution retrieves and processes t
This workflow provides a reusable error handling, audit logging, and observability pattern for n8n workflows using two n8n custom Data Tables: and .
If you're in need of a quick and dirty cache that doesn't need anything other than the current version of N8N, boy do I have a dodgy script for you to try!