From 4fb1f618d23009bf75cb2fea3844d94a9cacb6e9 Mon Sep 17 00:00:00 2001 From: Isaac_Aracena Date: Fri, 7 Aug 2026 14:40:34 +0000 Subject: [PATCH] Subir archivos a "/" --- ...os GLM - Buscar contactos en BambooHR.json | 299 +++++++ ...to de Impuestos GLM - Google Calendar.json | 824 ++++++++++++++++++ ...M - Recordatorios Gmail y Google Chat.json | 603 +++++++++++++ 3 files changed, 1726 insertions(+) create mode 100644 Seguimiento de Impuestos GLM - Buscar contactos en BambooHR.json create mode 100644 Seguimiento de Impuestos GLM - Google Calendar.json create mode 100644 Seguimiento de Impuestos GLM - Recordatorios Gmail y Google Chat.json diff --git a/Seguimiento de Impuestos GLM - Buscar contactos en BambooHR.json b/Seguimiento de Impuestos GLM - Buscar contactos en BambooHR.json new file mode 100644 index 0000000..9edce69 --- /dev/null +++ b/Seguimiento de Impuestos GLM - Buscar contactos en BambooHR.json @@ -0,0 +1,299 @@ +{ + "name": "Seguimiento de Impuestos GLM - Buscar contactos en BambooHR", + "nodes": [ + { + "parameters": { + "httpMethod": "POST", + "path": "seguimiento-impuestos-bamboohr-contacto", + "responseMode": "responseNode", + "options": {} + }, + "type": "n8n-nodes-base.webhook", + "typeVersion": 2.1, + "position": [ + -1040, + 80 + ], + "id": "83895861-f8e5-48bb-8eac-128ad55295f6", + "name": "Webhook - Buscar contacto BambooHR", + "webhookId": "seguimiento-impuestos-bamboohr-contacto" + }, + { + "parameters": { + "jsCode": "let body = $json.body ?? $json;\nif (typeof body === 'string') {\n try { body = JSON.parse(body); }\n catch { body = {}; }\n}\nconst fullName = String(body.full_name ?? body.name ?? '').trim().replace(/\\s+/g, ' ');\nconst valid = fullName.split(' ').filter(Boolean).length >= 2 && fullName.length >= 5;\nreturn [{\n json: {\n valid,\n full_name: fullName,\n requested_by: String(body.requested_by ?? '').trim(),\n message: valid ? '' : 'Escribe el nombre y apellido de la persona.',\n },\n}];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + -800, + 80 + ], + "id": "b3415338-ef9e-4dfc-9f95-133caf07e9e4", + "name": "Validar solicitud" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 2 + }, + "conditions": [ + { + "id": "ac176ddc-1e99-4d7b-b9f9-bea5a4182baa", + "leftValue": "={{ $json.valid }}", + "rightValue": true, + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.2, + "position": [ + -560, + 80 + ], + "id": "b9a78df7-ddbd-4b20-9bad-0912bd6e7c41", + "name": "Solicitud válida" + }, + { + "parameters": { + "method": "POST", + "url": "https://glm.bamboohr.com/api/v1/reports/custom?format=JSON&onlyCurrent=false", + "authentication": "genericCredentialType", + "genericAuthType": "httpBasicAuth", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": { + "title": "Información completa de empleados BambooHR - Seguimiento de Impuestos", + "fields": [ + "firstName", + "middleName", + "lastName", + "displayName", + "department", + "division", + "location", + "customPosicion-Cliente", + "hireDate", + "originalHireDate", + "status", + "employeeNumber", + "workEmail", + "terminationDate", + "supervisor", + "startDate", + "jobTitle", + "employmentStatus", + "email", + "country", + "city", + "address1" + ] + }, + "options": { + "response": { + "response": { + "responseFormat": "json" + } + }, + "timeout": 300000 + } + }, + "id": "9ddaf55d-54a9-4059-bc9b-3142cef78ecb", + "name": "Generar reporte completo BambooHR", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + -304, + 0 + ], + "retryOnFail": true, + "maxTries": 3, + "waitBetweenTries": 2000, + "credentials": { + "httpBasicAuth": { + "id": "7VrpNZ2jBLmiJ35q", + "name": "BambooHR GLM Full Access" + } + } + }, + { + "parameters": { + "jsCode": "const query = String($('Validar solicitud').item.json.full_name ?? '').trim();\nconst response = $json ?? {};\nconst employees = Array.isArray(response.employees)\n ? response.employees\n : Array.isArray(response.data)\n ? response.data\n : Array.isArray(response)\n ? response\n : [];\n\nconst normalize = (value) => String(value ?? '')\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .toLowerCase()\n .replace(/[^a-z0-9\\s]/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n\nconst tokens = (value) => normalize(value).split(' ').filter((token) => token.length > 1);\n\nconst levenshtein = (a, b) => {\n if (!a) return b.length;\n if (!b) return a.length;\n const previous = Array.from({ length: b.length + 1 }, (_, index) => index);\n for (let i = 1; i <= a.length; i += 1) {\n let diagonal = previous[0];\n previous[0] = i;\n for (let j = 1; j <= b.length; j += 1) {\n const old = previous[j];\n const cost = a[i - 1] === b[j - 1] ? 0 : 1;\n previous[j] = Math.min(previous[j] + 1, previous[j - 1] + 1, diagonal + cost);\n diagonal = old;\n }\n }\n return previous[b.length];\n};\n\nconst similarity = (a, b) => {\n const longest = Math.max(a.length, b.length, 1);\n return 1 - levenshtein(a, b) / longest;\n};\n\nconst tokenF1 = (aTokens, bTokens) => {\n if (!aTokens.length || !bTokens.length) return 0;\n const a = new Set(aTokens);\n const b = new Set(bTokens);\n const common = [...a].filter((token) => b.has(token)).length;\n if (!common) return 0;\n const precision = common / b.size;\n const recall = common / a.size;\n return (2 * precision * recall) / (precision + recall);\n};\n\nconst countryAliases = [\n { code: 'DO', name: 'República Dominicana', aliases: ['dominican republic', 'republica dominicana', 'santo domingo', 'dominicana', 'rep dom'] },\n { code: 'PR', name: 'Puerto Rico', aliases: ['puerto rico'] },\n { code: 'JM', name: 'Jamaica', aliases: ['jamaica'] },\n { code: 'TT', name: 'Trinidad y Tobago', aliases: ['trinidad and tobago', 'trinidad & tobago', 'trinidad y tobago', 'trinidad', 'tobago'] },\n { code: 'SV', name: 'El Salvador', aliases: ['el salvador', 'salvador'] },\n { code: 'GT', name: 'Guatemala', aliases: ['guatemala'] },\n { code: 'HN', name: 'Honduras', aliases: ['honduras'] },\n { code: 'CR', name: 'Costa Rica', aliases: ['costa rica'] },\n { code: 'CO', name: 'Colombia', aliases: ['colombia'] },\n { code: 'MX', name: 'México', aliases: ['mexico', 'méxico'] },\n { code: 'NI', name: 'Nicaragua', aliases: ['nicaragua'] },\n { code: 'PA', name: 'Panamá', aliases: ['panama', 'panamá'] },\n];\n\nconst resolveCountry = (employee) => {\n const haystack = normalize([\n employee.country,\n employee.location,\n employee.city,\n employee.division,\n employee.department,\n employee.address1,\n ].filter(Boolean).join(' '));\n\n if (/\\bregional\\b|\\bregion\\b/.test(haystack)) {\n return { code: 'REG', name: 'Regional' };\n }\n\n for (const country of countryAliases) {\n if (country.aliases.some((alias) => haystack.includes(normalize(alias)))) {\n return { code: country.code, name: country.name };\n }\n }\n\n return { code: 'REG', name: 'Regional' };\n};\n\nconst resolveArea = (employee) => {\n const text = normalize([\n employee.department,\n employee.division,\n employee.jobTitle,\n employee['customPosicion-Cliente'],\n ].filter(Boolean).join(' '));\n\n if (/nomina|payroll|compensacion|compensation|beneficios|benefits/.test(text)) return 'nomina';\n if (/administracion|administrativo|administrative|finance|finanzas|contabilidad|accounting|impuesto|tax|tesoreria|treasury|conciliacion|compras|procurement/.test(text)) return 'admin';\n return 'other';\n};\n\n\nconst queryNormalized = normalize(query);\nconst queryTokens = tokens(query);\nconst candidates = [];\n\nfor (const employee of employees) {\n const constructedName = [employee.firstName, employee.middleName, employee.lastName]\n .filter(Boolean)\n .join(' ')\n .replace(/\\s+/g, ' ')\n .trim();\n const displayName = String(employee.displayName ?? '').trim();\n const fullName = displayName || constructedName;\n if (!fullName) continue;\n\n const nameVariants = [...new Set([fullName, constructedName, displayName].filter(Boolean))];\n let score = 0;\n for (const variant of nameVariants) {\n const normalizedVariant = normalize(variant);\n const variantTokens = tokens(variant);\n let current = 0;\n if (normalizedVariant === queryNormalized) current = 100;\n else if (normalizedVariant.startsWith(queryNormalized)) current = 91;\n else if (normalizedVariant.includes(queryNormalized)) current = 85;\n else if (queryNormalized.includes(normalizedVariant)) current = 78;\n\n const f1 = tokenF1(queryTokens, variantTokens);\n const editSimilarity = similarity(queryNormalized, normalizedVariant);\n current = Math.max(current, f1 * 82, editSimilarity * 76);\n if (queryTokens.every((token) => variantTokens.includes(token))) current += 8;\n score = Math.max(score, current);\n }\n\n const status = String(employee.status ?? employee.employmentStatus ?? '').trim();\n if (/active|activo/i.test(status)) score += 3;\n if (score < 45) continue;\n\n const country = resolveCountry(employee);\n const email = String(employee.workEmail || employee.email || '').trim().toLowerCase();\n\n candidates.push({\n id: String(employee.id ?? employee.employeeId ?? employee.employeeNumber ?? fullName),\n employeeNumber: String(employee.employeeNumber ?? ''),\n fullName,\n displayName: displayName || fullName,\n email,\n jobTitle: String(employee.jobTitle ?? employee['customPosicion-Cliente'] ?? '').trim(),\n department: String(employee.department ?? '').trim(),\n division: String(employee.division ?? '').trim(),\n location: String(employee.location ?? employee.city ?? '').trim(),\n countryCode: country.code,\n countryName: country.name,\n area: resolveArea(employee),\n status,\n score: Math.min(100, Math.round(score)),\n matchQuality: score >= 95 ? 'exacta' : score >= 72 ? 'alta' : 'media',\n });\n}\n\nconst unique = new Map();\nfor (const candidate of candidates.sort((a, b) => {\n const activeA = /active|activo/i.test(a.status) ? 1 : 0;\n const activeB = /active|activo/i.test(b.status) ? 1 : 0;\n return b.score - a.score || activeB - activeA || a.fullName.localeCompare(b.fullName);\n})) {\n const key = candidate.id || candidate.email || normalize(candidate.fullName);\n if (!unique.has(key)) unique.set(key, candidate);\n}\n\nreturn [{\n json: {\n ok: true,\n query,\n count: Math.min(unique.size, 8),\n candidates: [...unique.values()].slice(0, 8),\n },\n}];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + -48, + 0 + ], + "id": "4742718e-f429-4096-a974-f811e4cd5a91", + "name": "Buscar coincidencias robustas" + }, + { + "parameters": { + "respondWith": "json", + "responseBody": "={{ $json }}", + "options": { + "responseCode": 200, + "responseHeaders": { + "entries": [ + { + "name": "Access-Control-Allow-Origin", + "value": "*" + }, + { + "name": "Cache-Control", + "value": "no-store" + } + ] + } + } + }, + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1.4, + "position": [ + 208, + 0 + ], + "id": "d7250da6-9a93-42c8-b7c8-c28adadbe7f1", + "name": "Responder coincidencias" + }, + { + "parameters": { + "respondWith": "json", + "responseBody": "={{ { ok: false, message: $json.message, candidates: [] } }}", + "options": { + "responseCode": 400, + "responseHeaders": { + "entries": [ + { + "name": "Access-Control-Allow-Origin", + "value": "*" + } + ] + } + } + }, + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1.4, + "position": [ + -304, + 192 + ], + "id": "1ed906bf-3e3c-42ea-9978-df44387c1f44", + "name": "Responder solicitud inválida" + }, + { + "parameters": { + "content": "## BÚSQUEDA DE CONTACTOS EN BAMBOOHR\n\nRecibe desde la aplicación el nombre de una persona y busca coincidencias dentro del directorio actualizado de empleados de BambooHR.\n\n- Valida que la solicitud incluya un nombre válido antes de consultar BambooHR.\n- Devuelve una respuesta controlada cuando faltan datos o la solicitud es incorrecta.\n- Obtiene el reporte completo de empleados con su información laboral y de contacto.\n- Normaliza nombres, espacios, mayúsculas y caracteres especiales para realizar una búsqueda más confiable.\n- Identifica coincidencias exactas y aproximadas para reducir errores por nombres incompletos o diferencias de escritura.\n- Devuelve a la aplicación las personas encontradas para agregarlas automáticamente o permitir que el usuario seleccione la coincidencia correcta.", + "height": 576, + "width": 1552, + "color": 6 + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + -1136, + -224 + ], + "id": "549c09fb-34a0-44d8-b5a7-7cb71f804025", + "name": "Sticky Note" + } + ], + "pinData": {}, + "connections": { + "Webhook - Buscar contacto BambooHR": { + "main": [ + [ + { + "node": "Validar solicitud", + "type": "main", + "index": 0 + } + ] + ] + }, + "Validar solicitud": { + "main": [ + [ + { + "node": "Solicitud válida", + "type": "main", + "index": 0 + } + ] + ] + }, + "Solicitud válida": { + "main": [ + [ + { + "node": "Generar reporte completo BambooHR", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Responder solicitud inválida", + "type": "main", + "index": 0 + } + ] + ] + }, + "Generar reporte completo BambooHR": { + "main": [ + [ + { + "node": "Buscar coincidencias robustas", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar coincidencias robustas": { + "main": [ + [ + { + "node": "Responder coincidencias", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "active": true, + "settings": { + "executionOrder": "v1", + "binaryMode": "separate" + }, + "versionId": "4cc439c7-5187-44ab-a0ca-dc0230cdfcbc", + "meta": { + "instanceId": "b4b77b17af092830e794eef639ce2f6d7daccf7eddc075060b03b3b6545aac70" + }, + "id": "nZ53koYjMAYN5tTe", + "tags": [] +} \ No newline at end of file diff --git a/Seguimiento de Impuestos GLM - Google Calendar.json b/Seguimiento de Impuestos GLM - Google Calendar.json new file mode 100644 index 0000000..833fe49 --- /dev/null +++ b/Seguimiento de Impuestos GLM - Google Calendar.json @@ -0,0 +1,824 @@ +{ + "name": "Seguimiento de Impuestos GLM - Google Calendar", + "nodes": [ + { + "parameters": { + "httpMethod": "POST", + "path": "seguimiento-impuestos-google-calendar", + "responseMode": "responseNode", + "options": {} + }, + "type": "n8n-nodes-base.webhook", + "typeVersion": 2.1, + "position": [ + -1648, + 432 + ], + "id": "c1faefdb-cec7-4565-9397-f2ff1487dfba", + "name": "Webhook - Google Calendar", + "webhookId": "seguimiento-impuestos-google-calendar" + }, + { + "parameters": { + "jsCode": "let body = $json.body ?? $json;\nif (typeof body === 'string') {\n try { body = JSON.parse(body); }\n catch { body = {}; }\n}\nconst action = String(body.action ?? '').trim().toLowerCase();\nconst payload = body.payload ?? {};\nconst obligationId = String(payload.obligation_id ?? body.obligation_id ?? '').trim();\nconst allowed = ['obligation.created', 'obligation.updated', 'obligation.deleted'];\nreturn [{\n json: {\n valid: allowed.includes(action) && Boolean(obligationId),\n action,\n obligation_id: obligationId,\n message: allowed.includes(action)\n ? (obligationId ? '' : 'Falta obligation_id.')\n : 'Acción no soportada.',\n },\n}];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + -1408, + 432 + ], + "id": "a052bc4b-6571-4105-8d41-634152a727b9", + "name": "Preparar solicitud" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 2 + }, + "conditions": [ + { + "id": "771f008a-734d-436d-9122-448758a2263a", + "leftValue": "={{ $json.valid }}", + "rightValue": true, + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.2, + "position": [ + -1168, + 432 + ], + "id": "8b860c5e-b8f6-4d90-9829-a192c5bd807e", + "name": "Solicitud válida" + }, + { + "parameters": { + "method": "POST", + "url": "https://dbit.digitalcompass.agency/rest/v1/rpc/tax_google_calendar_payload", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "apikey", + "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q" + }, + { + "name": "Authorization", + "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ { p_obligation_id: $json.obligation_id } }}", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + -912, + 336 + ], + "id": "b20b6a2b-6ec1-4e6c-a8eb-5d10404ab80e", + "name": "Supabase - Obtener obligación y asistentes" + }, + { + "parameters": { + "jsCode": "const source = $('Preparar solicitud').item.json;\nconst value = $json;\n\nconst row = Array.isArray(value)\n ? value[0]\n : Array.isArray(value?.data)\n ? value.data[0]\n : value;\n\nif (!row?.obligation_id) {\n return [\n {\n json: {\n ...source,\n operation: 'noop',\n reason: 'No se encontró la obligación.',\n },\n },\n ];\n}\n\nconst formatDateDMY = (value) => {\n const iso = String(value ?? '').trim();\n const match = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(iso);\n\n if (!match) {\n return iso;\n }\n\n const [, year, month, day] = match;\n return `${day}-${month}-${year}`;\n};\n\nconst attendees = Array.isArray(row.attendees)\n ? [\n ...new Set(\n row.attendees\n .map((email) => String(email ?? '').trim().toLowerCase())\n .filter(Boolean),\n ),\n ]\n : [];\n\nconst start = `${row.due_date}T08:00:00-04:00`;\nconst end = `${row.due_date}T09:00:00-04:00`;\n\nconst categoryLabel =\n row.category === 'nomina'\n ? 'Nómina'\n : 'Administración';\n\nconst summary =\n `Impuestos · ${row.country_name} · ${row.description}`;\n\nconst description = [\n 'Seguimiento de Impuestos GLM',\n '',\n `País: ${row.country_name}`,\n `Categoría: ${categoryLabel}`,\n `Fecha límite: ${formatDateDMY(row.due_date)}`,\n `Obligación: ${row.description}`,\n '',\n 'Abrir aplicación: https://digitalcompass.agency/calendario-impuestos/',\n].join('\\n');\n\nconst calendarBody = {\n summary,\n description,\n\n start: {\n dateTime: start,\n timeZone: 'America/Santo_Domingo',\n },\n\n end: {\n dateTime: end,\n timeZone: 'America/Santo_Domingo',\n },\n\n attendees: attendees.map((email) => ({\n email,\n })),\n\n reminders: {\n useDefault: false,\n overrides: [\n {\n method: 'email',\n minutes: 0,\n },\n {\n method: 'popup',\n minutes: 0,\n },\n ],\n },\n\n extendedProperties: {\n private: {\n glmTaxObligationId: row.obligation_id,\n glmTaxCountryCode: row.country_code,\n glmTaxCategory: row.category,\n },\n },\n};\n\nlet operation = 'create';\n\nif (\n source.action === 'obligation.deleted' ||\n row.active === false\n) {\n operation = row.google_event_id\n ? 'delete'\n : 'noop';\n} else if (row.google_event_id) {\n operation = 'update';\n}\n\nreturn [\n {\n json: {\n ...source,\n ...row,\n attendees,\n start,\n end,\n summary,\n calendar_description: description,\n calendar_body: calendarBody,\n calendar_id: row.calendar_id || 'primary',\n operation,\n },\n },\n];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + -656, + 336 + ], + "id": "86e2c9f1-46cb-42b0-83a2-43d420b6a85f", + "name": "Preparar payload Calendar" + }, + { + "parameters": { + "jsCode": "return $input.all().filter((item) => item.json.operation === 'create');" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 64, + -64 + ], + "id": "2d6990ec-a638-463b-8364-7d6a77b9bb9b", + "name": "Preparar crear" + }, + { + "parameters": { + "jsCode": "return $input.all().filter((item) => item.json.operation === 'update');" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 80, + 352 + ], + "id": "868962b2-1894-41e4-9028-598b96a9ccc1", + "name": "Preparar actualizar" + }, + { + "parameters": { + "jsCode": "return $input.all().filter((item) => item.json.operation === 'delete');" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 192, + 1088 + ], + "id": "57b9d7e3-084a-4d95-8ba2-309b83a71551", + "name": "Preparar eliminar" + }, + { + "parameters": { + "jsCode": "return $input.all().filter((item) => item.json.operation === 'noop');" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 272, + 1344 + ], + "id": "8ffaf543-58e2-45c6-bcd3-0e56d9c18bc7", + "name": "Preparar sin acción" + }, + { + "parameters": { + "method": "POST", + "url": "https://www.googleapis.com/calendar/v3/calendars/primary/events?sendUpdates=all", + "authentication": "predefinedCredentialType", + "nodeCredentialType": "googleCalendarOAuth2Api", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ $json.calendar_body }}", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 320, + -64 + ], + "id": "6a998d06-8660-4b98-9f5b-58af9d92bda1", + "name": "Google Calendar - Crear evento", + "credentials": { + "googleCalendarOAuth2Api": { + "id": "QabWebE9XbAY72tU", + "name": "Google Calendar account" + } + } + }, + { + "parameters": { + "method": "PATCH", + "url": "={{ 'https://www.googleapis.com/calendar/v3/calendars/primary/events/' + encodeURIComponent($json.google_event_id) + '?sendUpdates=all' }}", + "authentication": "predefinedCredentialType", + "nodeCredentialType": "googleCalendarOAuth2Api", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ $json.calendar_body }}", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 336, + 352 + ], + "id": "7831e93c-af0a-4505-80c0-c9707d5abff0", + "name": "Google Calendar - Actualizar evento", + "credentials": { + "googleCalendarOAuth2Api": { + "id": "QabWebE9XbAY72tU", + "name": "Google Calendar account" + } + } + }, + { + "parameters": { + "method": "DELETE", + "url": "={{ 'https://www.googleapis.com/calendar/v3/calendars/primary/events/' + encodeURIComponent($json.google_event_id) + '?sendUpdates=all' }}", + "authentication": "predefinedCredentialType", + "nodeCredentialType": "googleCalendarOAuth2Api", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 448, + 1088 + ], + "id": "d43bb286-0ce0-4e44-a9d9-5be15e325877", + "name": "Google Calendar - Eliminar evento", + "credentials": { + "googleCalendarOAuth2Api": { + "id": "QabWebE9XbAY72tU", + "name": "Google Calendar account" + } + } + }, + { + "parameters": { + "jsCode": "const source = $('Preparar crear').item.json; return [{ json: { ...source, google_event_id: String($json.id ?? ''), google_response: $json, sync_action: 'create', sync_status: 'active' } }];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 576, + -64 + ], + "id": "3bc4fa25-5ab2-4f01-a36f-bb3ab678cff7", + "name": "Preparar registro creado" + }, + { + "parameters": { + "jsCode": "const source = $('Preparar actualizar').item.json; return [{ json: { ...source, google_event_id: source.google_event_id, google_response: $json, sync_action: 'update', sync_status: 'active' } }];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 592, + 352 + ], + "id": "19961094-0e2b-4b63-8afe-5996a8fe2d46", + "name": "Preparar registro actualizado" + }, + { + "parameters": { + "jsCode": "const source = $('Preparar eliminar').item.json; return [{ json: { ...source, google_event_id: source.google_event_id, google_response: $json ?? {}, sync_action: 'delete', sync_status: 'deleted' } }];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 704, + 1088 + ], + "id": "2108e3aa-935b-4fa3-ad66-1ddce0676212", + "name": "Preparar registro eliminado" + }, + { + "parameters": { + "method": "POST", + "url": "https://dbit.digitalcompass.agency/rest/v1/rpc/tax_record_google_calendar_sync", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "apikey", + "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q" + }, + { + "name": "Authorization", + "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ { p_obligation_id: $('Preparar registro creado').item.json.obligation_id, p_google_event_id: $('Preparar registro creado').item.json.google_event_id, p_calendar_id: 'primary', p_attendees: $('Preparar registro creado').item.json.attendees, p_status: $('Preparar registro creado').item.json.sync_status, p_last_action: $('Preparar registro creado').item.json.sync_action, p_provider_response: $('Preparar registro creado').item.json.google_response } }}", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 832, + -64 + ], + "id": "bf61e834-6dd9-4838-b74a-927839a91ddc", + "name": "Supabase - Registrar creación Calendar" + }, + { + "parameters": { + "method": "POST", + "url": "https://dbit.digitalcompass.agency/rest/v1/rpc/tax_record_google_calendar_sync", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "apikey", + "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q" + }, + { + "name": "Authorization", + "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ { p_obligation_id: $('Preparar registro actualizado').item.json.obligation_id, p_google_event_id: $('Preparar registro actualizado').item.json.google_event_id, p_calendar_id: 'primary', p_attendees: $('Preparar registro actualizado').item.json.attendees, p_status: $('Preparar registro actualizado').item.json.sync_status, p_last_action: $('Preparar registro actualizado').item.json.sync_action, p_provider_response: $('Preparar registro actualizado').item.json.google_response } }}", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 848, + 352 + ], + "id": "78bda986-5713-427a-8852-d791dbc8a1cc", + "name": "Supabase - Registrar actualización Calendar" + }, + { + "parameters": { + "method": "POST", + "url": "https://dbit.digitalcompass.agency/rest/v1/rpc/tax_record_google_calendar_sync", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "apikey", + "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q" + }, + { + "name": "Authorization", + "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ { p_obligation_id: $('Preparar registro eliminado').item.json.obligation_id, p_google_event_id: $('Preparar registro eliminado').item.json.google_event_id, p_calendar_id: 'primary', p_attendees: $('Preparar registro eliminado').item.json.attendees, p_status: $('Preparar registro eliminado').item.json.sync_status, p_last_action: $('Preparar registro eliminado').item.json.sync_action, p_provider_response: $('Preparar registro eliminado').item.json.google_response } }}", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 960, + 1088 + ], + "id": "b05991ba-18fa-498a-a52e-f3b4bf296793", + "name": "Supabase - Registrar eliminación Calendar" + }, + { + "parameters": { + "respondWith": "json", + "responseBody": "={{ { ok: true, message: 'Evento creado en Google Calendar.' } }}", + "options": { + "responseCode": 200, + "responseHeaders": { + "entries": [ + { + "name": "Access-Control-Allow-Origin", + "value": "*" + }, + { + "name": "Cache-Control", + "value": "no-store" + } + ] + } + } + }, + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1.4, + "position": [ + 1088, + -64 + ], + "id": "8237b91a-ec81-4e45-9641-9138e354febe", + "name": "Responder creación" + }, + { + "parameters": { + "respondWith": "json", + "responseBody": "={{ { ok: true, message: 'Evento actualizado en Google Calendar.' } }}", + "options": { + "responseCode": 200, + "responseHeaders": { + "entries": [ + { + "name": "Access-Control-Allow-Origin", + "value": "*" + }, + { + "name": "Cache-Control", + "value": "no-store" + } + ] + } + } + }, + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1.4, + "position": [ + 1104, + 352 + ], + "id": "954c7fd8-82d7-4460-9e0e-53c5e1bb30be", + "name": "Responder actualización" + }, + { + "parameters": { + "respondWith": "json", + "responseBody": "={{ { ok: true, message: 'Evento eliminado de Google Calendar.' } }}", + "options": { + "responseCode": 200, + "responseHeaders": { + "entries": [ + { + "name": "Access-Control-Allow-Origin", + "value": "*" + }, + { + "name": "Cache-Control", + "value": "no-store" + } + ] + } + } + }, + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1.4, + "position": [ + 1216, + 1088 + ], + "id": "c4542b04-2b4e-49da-b189-ed6d4fda092d", + "name": "Responder eliminación" + }, + { + "parameters": { + "respondWith": "json", + "responseBody": "={{ { ok: true, message: $json.reason || 'No había cambios que sincronizar.' } }}", + "options": { + "responseCode": 200, + "responseHeaders": { + "entries": [ + { + "name": "Access-Control-Allow-Origin", + "value": "*" + } + ] + } + } + }, + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1.4, + "position": [ + 528, + 1344 + ], + "id": "8a17b617-3311-47a9-9ac6-153894215600", + "name": "Responder sin acción" + }, + { + "parameters": { + "respondWith": "json", + "responseBody": "={{ { ok: false, message: $json.message } }}", + "options": { + "responseCode": 400, + "responseHeaders": { + "entries": [ + { + "name": "Access-Control-Allow-Origin", + "value": "*" + } + ] + } + } + }, + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1.4, + "position": [ + -912, + 608 + ], + "id": "328640f6-5d22-46a0-89dc-46580ec2f6a8", + "name": "Responder solicitud inválida" + }, + { + "parameters": { + "content": "## 1. VALIDACIÓN Y PREPARACIÓN\n\nRecibe desde la aplicación una solicitud relacionada con un evento fiscal y prepara toda la información necesaria para procesarla.\n\n- Normaliza los datos recibidos y determina la acción solicitada.\n- Valida que la obligación, las fechas y los parámetros obligatorios sean correctos.\n- Devuelve una respuesta controlada cuando la solicitud es inválida.\n- Consulta en Supabase la obligación y las personas que deben asistir al evento.\n- Construye el payload final que se utilizará para crear, actualizar, eliminar o ignorar el evento en Google Calendar.", + "height": 640, + "width": 1248, + "color": 3 + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + -1744, + 144 + ], + "id": "11ca21e4-8360-45fe-a2d3-6ce1cccde67a", + "name": "Sticky Note" + }, + { + "parameters": { + "content": "## 2. CREACIÓN Y ACTUALIZACIÓN\n\nGestiona la creación de nuevos eventos y la actualización de eventos existentes en Google Calendar.\n\n- Prepara los datos específicos según la operación solicitada.\n- Crea o actualiza el evento con sus fechas, descripción y asistentes.\n- Normaliza la respuesta devuelta por Google Calendar.\n- Registra en Supabase el identificador del evento y el resultado de la sincronización.\n- Devuelve a la aplicación una confirmación de la operación realizada.", + "height": 864, + "width": 1360, + "color": 5 + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + -64, + -288 + ], + "id": "60ca5c9c-62fa-40ac-93a2-653efbe41002", + "name": "Sticky Note1" + }, + { + "parameters": { + "content": "## 3. ELIMINACIÓN Y CONTROL SIN ACCIÓN\n\nGestiona la eliminación de eventos y los casos en los que no es necesario modificar Google Calendar.\n\n- Prepara y ejecuta la eliminación del evento correspondiente.\n- Registra en Supabase que el evento fue eliminado y limpia su referencia.\n- Devuelve una confirmación cuando la eliminación finaliza correctamente.\n- Detecta solicitudes que no requieren crear, actualizar ni eliminar.\n- Responde de forma controlada sin realizar cambios innecesarios en el calendario.", + "height": 624, + "width": 1328 + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + 96, + 880 + ], + "id": "d38de08e-0f21-43a6-a75f-8ebc5be4dd34", + "name": "Sticky Note2" + } + ], + "pinData": {}, + "connections": { + "Webhook - Google Calendar": { + "main": [ + [ + { + "node": "Preparar solicitud", + "type": "main", + "index": 0 + } + ] + ] + }, + "Preparar solicitud": { + "main": [ + [ + { + "node": "Solicitud válida", + "type": "main", + "index": 0 + } + ] + ] + }, + "Solicitud válida": { + "main": [ + [ + { + "node": "Supabase - Obtener obligación y asistentes", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Responder solicitud inválida", + "type": "main", + "index": 0 + } + ] + ] + }, + "Supabase - Obtener obligación y asistentes": { + "main": [ + [ + { + "node": "Preparar payload Calendar", + "type": "main", + "index": 0 + } + ] + ] + }, + "Preparar payload Calendar": { + "main": [ + [ + { + "node": "Preparar crear", + "type": "main", + "index": 0 + }, + { + "node": "Preparar actualizar", + "type": "main", + "index": 0 + }, + { + "node": "Preparar eliminar", + "type": "main", + "index": 0 + }, + { + "node": "Preparar sin acción", + "type": "main", + "index": 0 + } + ] + ] + }, + "Preparar crear": { + "main": [ + [ + { + "node": "Google Calendar - Crear evento", + "type": "main", + "index": 0 + } + ] + ] + }, + "Google Calendar - Crear evento": { + "main": [ + [ + { + "node": "Preparar registro creado", + "type": "main", + "index": 0 + } + ] + ] + }, + "Preparar registro creado": { + "main": [ + [ + { + "node": "Supabase - Registrar creación Calendar", + "type": "main", + "index": 0 + } + ] + ] + }, + "Supabase - Registrar creación Calendar": { + "main": [ + [ + { + "node": "Responder creación", + "type": "main", + "index": 0 + } + ] + ] + }, + "Preparar actualizar": { + "main": [ + [ + { + "node": "Google Calendar - Actualizar evento", + "type": "main", + "index": 0 + } + ] + ] + }, + "Google Calendar - Actualizar evento": { + "main": [ + [ + { + "node": "Preparar registro actualizado", + "type": "main", + "index": 0 + } + ] + ] + }, + "Preparar registro actualizado": { + "main": [ + [ + { + "node": "Supabase - Registrar actualización Calendar", + "type": "main", + "index": 0 + } + ] + ] + }, + "Supabase - Registrar actualización Calendar": { + "main": [ + [ + { + "node": "Responder actualización", + "type": "main", + "index": 0 + } + ] + ] + }, + "Preparar eliminar": { + "main": [ + [ + { + "node": "Google Calendar - Eliminar evento", + "type": "main", + "index": 0 + } + ] + ] + }, + "Google Calendar - Eliminar evento": { + "main": [ + [ + { + "node": "Preparar registro eliminado", + "type": "main", + "index": 0 + } + ] + ] + }, + "Preparar registro eliminado": { + "main": [ + [ + { + "node": "Supabase - Registrar eliminación Calendar", + "type": "main", + "index": 0 + } + ] + ] + }, + "Supabase - Registrar eliminación Calendar": { + "main": [ + [ + { + "node": "Responder eliminación", + "type": "main", + "index": 0 + } + ] + ] + }, + "Preparar sin acción": { + "main": [ + [ + { + "node": "Responder sin acción", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "active": true, + "settings": { + "executionOrder": "v1", + "binaryMode": "separate" + }, + "versionId": "5b0b0ad1-26c8-4965-b5d1-3bda834ae3a4", + "meta": { + "instanceId": "b4b77b17af092830e794eef639ce2f6d7daccf7eddc075060b03b3b6545aac70" + }, + "id": "4ysmdoCtcyBHB0pW", + "tags": [] +} \ No newline at end of file diff --git a/Seguimiento de Impuestos GLM - Recordatorios Gmail y Google Chat.json b/Seguimiento de Impuestos GLM - Recordatorios Gmail y Google Chat.json new file mode 100644 index 0000000..34d0237 --- /dev/null +++ b/Seguimiento de Impuestos GLM - Recordatorios Gmail y Google Chat.json @@ -0,0 +1,603 @@ +{ + "name": "Seguimiento de Impuestos GLM - Recordatorios Gmail y Google Chat", + "nodes": [ + { + "parameters": { + "rule": { + "interval": [ + { + "field": "cronExpression", + "expression": "0 8 * * *" + } + ] + } + }, + "type": "n8n-nodes-base.scheduleTrigger", + "typeVersion": 1.2, + "position": [ + -32, + 1456 + ], + "id": "d9890ecf-a9ba-4604-9a68-03568e80bbe5", + "name": "Diario 8:00 AM" + }, + { + "parameters": {}, + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [ + -32, + 1616 + ], + "id": "58d5e31d-10bb-4dec-af43-d3ea3225a4ad", + "name": "Ejecutar manualmente" + }, + { + "parameters": { + "httpMethod": "POST", + "path": "seguimiento-impuestos", + "options": {} + }, + "type": "n8n-nodes-base.webhook", + "typeVersion": 2.1, + "position": [ + -32, + 1808 + ], + "id": "b1a94a40-005a-419c-9d3d-697b95d16749", + "name": "Webhook seguimiento-impuestos", + "webhookId": "seguimiento-impuestos-glm" + }, + { + "parameters": { + "jsCode": "function dateInTimeZone(timeZone) {\n const parts = new Intl.DateTimeFormat('en-US', {\n timeZone,\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n }).formatToParts(new Date());\n const values = Object.fromEntries(parts.map((part) => [part.type, part.value]));\n return `${values.year}-${values.month}-${values.day}`;\n}\n\nconst input = $json ?? {};\nconst runDate = input.run_date || dateInTimeZone('America/Santo_Domingo');\n\nreturn [{\n json: {\n run_date: runDate,\n source: input.source || 'schedule',\n },\n}];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 224, + 1536 + ], + "id": "da51143c-6cbd-4ea1-a49e-d0c4b097b952", + "name": "Preparar ejecución" + }, + { + "parameters": { + "jsCode": "const body = $json.body ?? $json;\nconst action = String(body.action ?? '').trim().toLowerCase();\n\nif (!['run_now', 'run_reminders', 'ejecutar_recordatorios'].includes(action)) {\n return [];\n}\n\nreturn [{\n json: {\n run_date: body.run_date || body.date || '',\n source: 'webhook',\n },\n}];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 224, + 1808 + ], + "id": "0863e7a7-bc95-42f6-982f-f09f859ec6d6", + "name": "Validar ejecución por Webhook" + }, + { + "parameters": { + "method": "POST", + "url": "https://dbit.digitalcompass.agency/rest/v1/rpc/tax_due_reminders", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "apikey", + "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q" + }, + { + "name": "Authorization", + "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ { p_run_date: $json.run_date } }}", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 480, + 1616 + ], + "id": "00dfa1be-5e07-4860-8322-d9f490ac4e4a", + "name": "Supabase - Recordatorios pendientes" + }, + { + "parameters": { + "jsCode": "const htmlEscape = (value) => String(value ?? '')\n .replace(/&/g, '&')\n .replace(//g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n\nconst rows = [];\nfor (const item of $input.all()) {\n const value = item.json;\n if (Array.isArray(value)) rows.push(...value);\n else if (Array.isArray(value?.data)) rows.push(...value.data);\n else if (Array.isArray(value?.body)) rows.push(...value.body);\n else if (value?.obligation_id) rows.push(value);\n}\n\nconst logoUrl = 'https://home.digitalcompass.agency/glm-logo-completo.png';\nconst appUrl = 'https://digitalcompass.agency/calendario-impuestos/';\n\nconst formatDate = (iso) => {\n const [year, month, day] = String(iso).split('-').map(Number);\n return new Intl.DateTimeFormat('es-DO', {\n timeZone: 'UTC',\n day: '2-digit',\n month: 'long',\n year: 'numeric',\n }).format(new Date(Date.UTC(year, month - 1, day)));\n};\n\nreturn rows.map((row) => {\n const dueDate = formatDate(row.due_date);\n const isSameDay = row.alert_type === 'same_day';\n const reminderLabel = isSameDay ? 'Vence hoy' : 'Recordatorio previo';\n const categoryLabel = row.category === 'nomina' ? 'Nómina' : 'Administración';\n const firstName = String(row.contact_name ?? '').trim().split(/\\s+/)[0] || 'equipo';\n const subject = `${isSameDay ? 'Vence hoy' : 'Recordatorio'} · ${row.country_name} · ${row.description}`;\n\n const emailHtml = `\n\n\n \n \n \n
\n \n \n \n \n \n
\"GomezLee
 
\n
${htmlEscape(reminderLabel)}
\n

Seguimiento de Impuestos GLM

\n

Hola ${htmlEscape(firstName)}, este es el recordatorio correspondiente a la siguiente obligación tributaria:

\n \n \n \n \n \n
País${htmlEscape(row.country_name)}
Categoría${htmlEscape(categoryLabel)}
Fecha límite${htmlEscape(dueDate)}
Obligación${htmlEscape(row.description)}
\n

Este aviso forma parte del seguimiento automático: miércoles anterior y fecha límite, incluidos sábados y domingos.

\n \n
GOMEZLEE MARKETING  |  Seguimiento de Impuestos GLM  |  Uso interno
\n
\n \n`.trim();\n\n const googleChatText = [\n `*${reminderLabel.toUpperCase()} · SEGUIMIENTO DE IMPUESTOS GLM*`,\n '',\n `Hola ${firstName},`,\n '',\n `*País:* ${row.country_name}`,\n `*Categoría:* ${categoryLabel}`,\n `*Fecha límite:* ${dueDate}`,\n `*Obligación:* ${row.description}`,\n '',\n 'Seguimiento automático: miércoles anterior y fecha límite, incluidos sábados y domingos.',\n '',\n `Abrir calendario: ${appUrl}`,\n ].join('\\n');\n\n return {\n json: {\n ...row,\n email_subject: subject,\n email_html: emailHtml,\n google_chat_text: googleChatText,\n },\n };\n});" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 736, + 1616 + ], + "id": "780bc937-c373-43cc-9343-e3c73d076934", + "name": "Construir mensajes GLM" + }, + { + "parameters": { + "jsCode": "return $input.all().filter((item) => item.json.email_enabled === true && String(item.json.contact_email ?? '').trim());" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 1008, + 1472 + ], + "id": "343dc1d3-cb5b-452c-b5d4-f69583b15280", + "name": "Preparar correos" + }, + { + "parameters": { + "sendTo": "={{ $json.contact_email }}", + "subject": "={{ $json.email_subject }}", + "message": "={{ $json.email_html }}", + "options": { + "appendAttribution": false + } + }, + "type": "n8n-nodes-base.gmail", + "typeVersion": 2.1, + "position": [ + 1264, + 1472 + ], + "id": "7f16857a-3674-4255-9674-cc040682f518", + "name": "Gmail - Enviar recordatorio", + "webhookId": "16bfebbd-f4e3-4b1e-aba2-028fecf98da1", + "credentials": { + "gmailOAuth2": { + "id": "UDcO1FLJqA453V2D", + "name": "Gmail account 3" + } + } + }, + { + "parameters": { + "method": "POST", + "url": "https://dbit.digitalcompass.agency/rest/v1/rpc/tax_record_notification", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "apikey", + "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q" + }, + { + "name": "Authorization", + "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ { p_obligation_id: $('Preparar correos').item.json.obligation_id, p_contact_id: $('Preparar correos').item.json.contact_id, p_alert_date: $('Preparar correos').item.json.alert_date, p_channel: 'email', p_status: 'sent', p_provider_response: $json } }}", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 1520, + 1472 + ], + "id": "bea9fc06-b8de-4091-bde0-d7be8c18e4cd", + "name": "Supabase - Registrar correo" + }, + { + "parameters": { + "jsCode": "return $input.all().filter((item) => item.json.google_chat_enabled === true && String(item.json.contact_email ?? '').trim());" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 1008, + 1744 + ], + "id": "3addf2bd-60c2-493c-8919-56b603aad7e8", + "name": "Preparar Google Chat" + }, + { + "parameters": { + "method": "POST", + "url": "https://chat.googleapis.com/v1/spaces:setup", + "authentication": "predefinedCredentialType", + "nodeCredentialType": "googleChatOAuth2Api", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ { space: { spaceType: 'DIRECT_MESSAGE', singleUserBotDm: false }, memberships: [ { member: { name: 'users/' + $json.contact_email, type: 'HUMAN' } } ] } }}", + "options": { + "timeout": 30000 + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 1264, + 1744 + ], + "id": "630cdc84-f7aa-4db8-8356-942056f3ac22", + "name": "Google Chat - Abrir DM", + "retryOnFail": true, + "maxTries": 3, + "waitBetweenTries": 1500, + "credentials": { + "googleChatOAuth2Api": { + "id": "V4eVQvEoy5QcBvUu", + "name": "Chat account" + } + }, + "onError": "continueRegularOutput" + }, + { + "parameters": { + "jsCode": "const source = $('Preparar Google Chat').item.json;\nconst spaceName = String($json.name ?? '').trim();\nreturn [{ json: { ...source, chat_space_name: spaceName, chat_setup_ok: Boolean(spaceName), google_chat_status: spaceName ? 'pending' : 'failed', google_chat_provider_response: $json } }];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 1520, + 1744 + ], + "id": "1002151d-cb96-46c1-9868-3e9aca7846c0", + "name": "Evaluar DM Google Chat" + }, + { + "parameters": { + "jsCode": "return $input.all().filter((item) => item.json.chat_setup_ok === true);" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 1776, + 1632 + ], + "id": "5978dd34-7cb2-46be-9536-65c22b7f06a4", + "name": "Preparar envío Google Chat" + }, + { + "parameters": { + "jsCode": "return $input.all().filter((item) => item.json.chat_setup_ok !== true);" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 1776, + 1888 + ], + "id": "cbc9f8d3-35f8-48a2-9251-c85c96d15be0", + "name": "Preparar fallo DM Google Chat" + }, + { + "parameters": { + "method": "POST", + "url": "={{ 'https://chat.googleapis.com/v1/' + $json.chat_space_name + '/messages' }}", + "authentication": "predefinedCredentialType", + "nodeCredentialType": "googleChatOAuth2Api", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ { text: $json.google_chat_text, createMessageNotificationOptions: { notificationType: 'NOTIFICATION_TYPE_FORCE_NOTIFY' } } }}", + "options": { + "timeout": 30000 + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 2032, + 1632 + ], + "id": "d4d67181-4079-4a46-9fd7-a10afe57db71", + "name": "Google Chat - Enviar recordatorio", + "retryOnFail": true, + "maxTries": 3, + "waitBetweenTries": 1500, + "credentials": { + "googleChatOAuth2Api": { + "id": "V4eVQvEoy5QcBvUu", + "name": "Chat account" + } + }, + "onError": "continueRegularOutput" + }, + { + "parameters": { + "jsCode": "const source = $('Preparar envío Google Chat').item.json;\nconst sent = Boolean($json.name) && !$json.error;\nreturn [{ json: { ...source, google_chat_status: sent ? 'sent' : 'failed', google_chat_provider_response: $json } }];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 2288, + 1632 + ], + "id": "6398165b-9e0a-483f-9db6-71f0d85a69d0", + "name": "Evaluar envío Google Chat" + }, + { + "parameters": { + "method": "POST", + "url": "https://dbit.digitalcompass.agency/rest/v1/rpc/tax_record_notification", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "apikey", + "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q" + }, + { + "name": "Authorization", + "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ { p_obligation_id: $json.obligation_id, p_contact_id: $json.contact_id, p_alert_date: $json.alert_date, p_channel: 'google_chat', p_status: $json.google_chat_status, p_provider_response: $json.google_chat_provider_response } }}", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 2544, + 1744 + ], + "id": "29a93cc5-c157-4199-81c1-1fed7664edc6", + "name": "Supabase - Registrar Google Chat" + }, + { + "parameters": { + "content": "## RECORDATORIOS DE IMPUESTOS POR GMAIL Y GOOGLE CHAT\n\nEjecuta diariamente el proceso de recordatorios fiscales y notifica a los contactos correspondientes por correo electrónico y mensaje directo de Google Chat.\n\n- Puede iniciarse automáticamente a las 8:00 a. m., manualmente o desde el webhook de la aplicación.\n- Valida y normaliza el origen de la ejecución antes de continuar.\n- Consulta en Supabase las obligaciones que requieren recordatorio y sus destinatarios.\n- Construye mensajes personalizados con el formato corporativo de GLM.\n- Envía los recordatorios por Gmail y registra en Supabase el resultado de cada correo.\n- Abre o identifica la conversación directa del destinatario en Google Chat.\n- Envía el recordatorio por Google Chat cuando el DM está disponible.\n- Evalúa cada intento y registra tanto los envíos exitosos como los errores para mantener trazabilidad.", + "height": 1184, + "width": 2880, + "color": "#204750" + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + -144, + 896 + ], + "id": "8c9fe360-936b-4e86-bd17-5a396dd09419", + "name": "Sticky Note" + } + ], + "pinData": {}, + "connections": { + "Diario 8:00 AM": { + "main": [ + [ + { + "node": "Preparar ejecución", + "type": "main", + "index": 0 + } + ] + ] + }, + "Ejecutar manualmente": { + "main": [ + [ + { + "node": "Preparar ejecución", + "type": "main", + "index": 0 + } + ] + ] + }, + "Webhook seguimiento-impuestos": { + "main": [ + [ + { + "node": "Validar ejecución por Webhook", + "type": "main", + "index": 0 + } + ] + ] + }, + "Preparar ejecución": { + "main": [ + [ + { + "node": "Supabase - Recordatorios pendientes", + "type": "main", + "index": 0 + } + ] + ] + }, + "Validar ejecución por Webhook": { + "main": [ + [ + { + "node": "Preparar ejecución", + "type": "main", + "index": 0 + } + ] + ] + }, + "Supabase - Recordatorios pendientes": { + "main": [ + [ + { + "node": "Construir mensajes GLM", + "type": "main", + "index": 0 + } + ] + ] + }, + "Construir mensajes GLM": { + "main": [ + [ + { + "node": "Preparar correos", + "type": "main", + "index": 0 + }, + { + "node": "Preparar Google Chat", + "type": "main", + "index": 0 + } + ] + ] + }, + "Preparar correos": { + "main": [ + [ + { + "node": "Gmail - Enviar recordatorio", + "type": "main", + "index": 0 + } + ] + ] + }, + "Gmail - Enviar recordatorio": { + "main": [ + [ + { + "node": "Supabase - Registrar correo", + "type": "main", + "index": 0 + } + ] + ] + }, + "Preparar Google Chat": { + "main": [ + [ + { + "node": "Google Chat - Abrir DM", + "type": "main", + "index": 0 + } + ] + ] + }, + "Google Chat - Abrir DM": { + "main": [ + [ + { + "node": "Evaluar DM Google Chat", + "type": "main", + "index": 0 + } + ] + ] + }, + "Evaluar DM Google Chat": { + "main": [ + [ + { + "node": "Preparar envío Google Chat", + "type": "main", + "index": 0 + }, + { + "node": "Preparar fallo DM Google Chat", + "type": "main", + "index": 0 + } + ] + ] + }, + "Preparar envío Google Chat": { + "main": [ + [ + { + "node": "Google Chat - Enviar recordatorio", + "type": "main", + "index": 0 + } + ] + ] + }, + "Google Chat - Enviar recordatorio": { + "main": [ + [ + { + "node": "Evaluar envío Google Chat", + "type": "main", + "index": 0 + } + ] + ] + }, + "Evaluar envío Google Chat": { + "main": [ + [ + { + "node": "Supabase - Registrar Google Chat", + "type": "main", + "index": 0 + } + ] + ] + }, + "Preparar fallo DM Google Chat": { + "main": [ + [ + { + "node": "Supabase - Registrar Google Chat", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "active": true, + "settings": { + "executionOrder": "v1", + "binaryMode": "separate", + "availableInMCP": true, + "timeSavedMode": "fixed", + "errorWorkflow": "puF4LUczoSz3hcek", + "timezone": "America/Santo_Domingo", + "callerPolicy": "workflowsFromSameOwner" + }, + "versionId": "ab159702-cf57-4e47-8de5-95da61a56b9b", + "meta": { + "templateCredsSetupCompleted": true, + "instanceId": "b4b77b17af092830e794eef639ce2f6d7daccf7eddc075060b03b3b6545aac70" + }, + "id": "cFu3Xx23KQBKfr0M", + "tags": [] +} \ No newline at end of file