Files
finiquitos-guatemala/Flujo de n8n: Envío de Finiquitos.json

398 lines
23 KiB
JSON

{
"nodes": [
{
"parameters": {},
"id": "e909153a-a0ed-454b-8795-60483cd6c1cd",
"name": "Manual Trigger",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
368,
2000
]
},
{
"parameters": {
"url": "https://sheets.googleapis.com/v4/spreadsheets/1Y9XhRjSSxtymV7Nm0gYbC5lLuGD2zPTc7IVb8PxoxjY/values/CONTROL_ENVIOS!A:L",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "googleSheetsOAuth2Api",
"options": {}
},
"id": "174cb623-eaf2-4839-a519-80e6cfbac019",
"name": "Leer CONTROL_ENVIOS",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
624,
2000
],
"retryOnFail": true,
"maxTries": 3,
"waitBetweenTries": 5000,
"credentials": {
"googleSheetsOAuth2Api": {
"id": "K0hDZh3a85MpOHCs",
"name": "Google Sheets account 2"
}
}
},
{
"parameters": {
"jsCode": "const response = $input.first().json;\nconst values = response.values || [];\n\nif (!Array.isArray(values) || values.length < 2) {\n throw new Error('No se recibieron filas desde CONTROL_ENVIOS. Revisa el ID del Google Sheet y el nombre de la hoja.');\n}\n\nfunction normalizeKey(value) {\n return String(value || '')\n .trim()\n .toLowerCase()\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '');\n}\n\nfunction normalizeText(value) {\n return String(value || '')\n .trim()\n .toUpperCase()\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '');\n}\n\nfunction colToLetter(colNumber) {\n let temp = '';\n let letter = '';\n\n while (colNumber > 0) {\n temp = (colNumber - 1) % 26;\n letter = String.fromCharCode(temp + 65) + letter;\n colNumber = (colNumber - temp - 1) / 26;\n }\n\n return letter;\n}\n\nfunction getValue(row, possibleKeys) {\n for (const key of possibleKeys) {\n const direct = row[key];\n const normalized = row[normalizeKey(key)];\n\n if (direct !== undefined && direct !== null && String(direct).trim() !== '') {\n return String(direct).trim();\n }\n\n if (normalized !== undefined && normalized !== null && String(normalized).trim() !== '') {\n return String(normalized).trim();\n }\n }\n\n return '';\n}\n\nconst headers = values[0].map(header => String(header || '').trim());\nconst headerMap = {};\n\nheaders.forEach((header, index) => {\n headerMap[normalizeKey(header)] = {\n name: header,\n index,\n letter: colToLetter(index + 1)\n };\n});\n\nfunction requireHeader(nameOptions, label) {\n for (const option of nameOptions) {\n const normalized = normalizeKey(option);\n if (headerMap[normalized]) {\n return headerMap[normalized];\n }\n }\n\n throw new Error(`No encontré la columna requerida: ${label}.`);\n}\n\nconst colNo = requireHeader(['NO.', 'NO', 'No'], 'NO.');\nconst colHoja = requireHeader(['Hoja'], 'Hoja');\nconst colNombre = requireHeader(['Nombre'], 'Nombre');\nconst colEmail = requireHeader(['Email personal', 'Email Personal', 'Correo personal'], 'Email personal');\nconst colPdfFileId = requireHeader(['PDF File ID'], 'PDF File ID');\nconst colPdfUrl = requireHeader(['PDF URL'], 'PDF URL');\nconst colEstado = requireHeader(['Estado envío', 'Estado Envio'], 'Estado envío');\nconst colObservacion = requireHeader(['Observación', 'Observacion'], 'Observación');\n\nconst rows = values.slice(1).map((row, index) => {\n const obj = {\n __rowNumber: index + 2\n };\n\n headers.forEach((header, colIndex) => {\n const value = row[colIndex] !== undefined ? String(row[colIndex]).trim() : '';\n obj[header] = value;\n obj[normalizeKey(header)] = value;\n });\n\n return obj;\n});\n\nconst emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/i;\n\nconst candidatos = rows\n .map(row => {\n const rowNumber = row.__rowNumber;\n const noCaso = getValue(row, ['NO.', 'NO', 'No']);\n const hoja = getValue(row, ['Hoja']);\n const nombre = getValue(row, ['Nombre']).replace(/\\s+/g, ' ').trim();\n const emailPersonal = getValue(row, ['Email personal', 'Email Personal', 'Correo personal']).toLowerCase();\n const pdfFileId = getValue(row, ['PDF File ID']);\n let pdfUrl = getValue(row, ['PDF URL']);\n const estadoEnvio = getValue(row, ['Estado envío', 'Estado Envio']);\n const observacionActual = getValue(row, ['Observación', 'Observacion']);\n\n if (!pdfUrl && pdfFileId) {\n pdfUrl = `https://drive.google.com/file/d/${pdfFileId}/view?usp=drivesdk`;\n }\n\n return {\n rowNumber,\n noCaso,\n hoja,\n nombre,\n emailPersonal,\n pdfFileId,\n pdfUrl,\n estadoEnvio,\n observacionActual,\n estadoRange: `CONTROL_ENVIOS!${colEstado.letter}${rowNumber}`,\n observacionRange: `CONTROL_ENVIOS!${colObservacion.letter}${rowNumber}`\n };\n })\n .filter(row => normalizeText(row.estadoEnvio) === 'LISTO_PARA_ENVIAR');\n\nif (candidatos.length === 0) {\n throw new Error('No hay filas con Estado envío = LISTO_PARA_ENVIAR. No se enviará ningún correo.');\n}\n\nconst errores = [];\nconst seenEmails = new Map();\n\nfor (const row of candidatos) {\n if (!row.nombre) {\n errores.push(`Fila ${row.rowNumber}: falta Nombre.`);\n }\n\n if (!row.emailPersonal) {\n errores.push(`Fila ${row.rowNumber} / ${row.nombre}: falta Email personal.`);\n } else if (!emailRegex.test(row.emailPersonal)) {\n errores.push(`Fila ${row.rowNumber} / ${row.nombre}: email inválido \"${row.emailPersonal}\".`);\n } else if (row.emailPersonal.includes('@gamil.')) {\n errores.push(`Fila ${row.rowNumber} / ${row.nombre}: email sospechoso con dominio gamil.com \"${row.emailPersonal}\".`);\n }\n\n if (!row.pdfFileId) {\n errores.push(`Fila ${row.rowNumber} / ${row.nombre}: falta PDF File ID.`);\n }\n\n if (!row.pdfUrl) {\n errores.push(`Fila ${row.rowNumber} / ${row.nombre}: falta PDF URL y no se pudo construir desde PDF File ID.`);\n }\n\n const key = row.emailPersonal.toLowerCase();\n if (key) {\n if (seenEmails.has(key)) {\n errores.push(`Email duplicado \"${row.emailPersonal}\" en filas ${seenEmails.get(key)} y ${row.rowNumber}. Revisar antes de enviar.`);\n } else {\n seenEmails.set(key, row.rowNumber);\n }\n }\n\n const nombreNormalizado = normalizeText(row.nombre);\n const hojaNormalizada = normalizeText(row.hoja);\n\n if (nombreNormalizado.includes('IRMA ALEJANDRA VENTURA') || hojaNormalizada === 'L29') {\n errores.push(`SEGURIDAD: Irma Alejandra Ventura / L29 aparece como LISTO_PARA_ENVIAR en la fila ${row.rowNumber}. No se permite continuar.`);\n }\n}\n\nif (errores.length > 0) {\n throw new Error('Validación detenida. Corrige estos puntos antes de enviar:\\n\\n' + errores.join('\\n'));\n}\n\nconst logoUrl = 'https://dbit.digitalcompass.agency/storage/v1/object/public/public-assets/GLM_completo.png';\n\nreturn candidatos.map(row => ({\n json: {\n ...row,\n logoUrl,\n subject: `${row.nombre} | Detalles de finiquito`,\n attachmentName: `${row.nombre} - Detalles de finiquito.pdf`\n }\n}));"
},
"id": "27af60fe-1661-41eb-98c0-470ee0dd82ad",
"name": "Validar y preparar envíos",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
912,
2000
],
"notesInFlow": true,
"notes": "Valida filas LISTO_PARA_ENVIAR, rechaza errores de correo/PDF, excluye Irma/L29 por seguridad y prepara un item por colaborador."
},
{
"parameters": {
"url": "=https://www.googleapis.com/drive/v3/files/{{$json.pdfFileId}}?alt=media&supportsAllDrives=true",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "googleDriveOAuth2Api",
"options": {
"response": {
"response": {
"responseFormat": "file"
}
}
}
},
"id": "7c7623b8-01db-4859-a450-09da467176b6",
"name": "Descargar PDF",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1728,
2016
],
"retryOnFail": true,
"maxTries": 3,
"waitBetweenTries": 5000,
"credentials": {
"googleDriveOAuth2Api": {
"id": "g23xdGLZRzBGqKgH",
"name": "Isaac - Google Drive"
}
}
},
{
"parameters": {
"jsCode": "const items = $input.all();\n\nfunction escapeHtml(value) {\n return String(value ?? '')\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;');\n}\n\nreturn items.map(item => {\n const source = item.json;\n const binary = item.binary || {};\n\n const nombre = escapeHtml(source.nombre);\n const pdfUrl = escapeHtml(source.pdfUrl || '#');\n const logoUrl = escapeHtml(source.logoUrl || 'https://dbit.digitalcompass.agency/storage/v1/object/public/public-assets/GLM_completo.png');\n\n const html = `\n<!DOCTYPE html>\n<html lang=\"es\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Detalles de finiquito</title>\n</head>\n\n<body style=\"margin:0; padding:0; background-color:#F5F5F5; font-family:Arial, Helvetica, sans-serif; color:#4A4A4A;\">\n\n <div style=\"display:none; max-height:0; overflow:hidden; opacity:0; color:transparent;\">\n Tu detalle de finiquito laboral está adjunto a este correo.\n </div>\n\n <table role=\"presentation\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\" style=\"background-color:#F5F5F5; margin:0; padding:24px 0;\">\n <tr>\n <td align=\"center\" style=\"padding:0 12px;\">\n\n <table role=\"presentation\" width=\"680\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\" style=\"width:680px; max-width:680px; background-color:#FFFFFF; border:1px solid #D0D0D0; border-collapse:collapse;\">\n\n <tr>\n <td style=\"padding:30px 34px 22px 34px; background-color:#FFFFFF;\">\n <img \n src=\"${logoUrl}\" \n alt=\"GomezLee Marketing\" \n width=\"250\" \n style=\"display:block; width:250px; max-width:250px; height:auto; border:0; outline:none; text-decoration:none;\"\n >\n </td>\n </tr>\n\n <tr>\n <td style=\"height:4px; background-color:#6CC24A; line-height:4px; font-size:4px;\">&nbsp;</td>\n </tr>\n\n <tr>\n <td style=\"padding:34px 34px 36px 34px; background-color:#FFFFFF;\">\n\n <h1 style=\"margin:0 0 22px 0; font-size:28px; line-height:36px; color:#4F758B; font-weight:700; font-family:Arial, Helvetica, sans-serif;\">\n Detalles de finiquito\n </h1>\n\n <p style=\"margin:0 0 18px 0; font-size:15px; line-height:25px; color:#4A4A4A;\">\n Hola ${nombre},\n </p>\n\n <p style=\"margin:0 0 18px 0; font-size:15px; line-height:25px; color:#4A4A4A;\">\n Adjunto encontrarás el detalle de tu finiquito laboral correspondiente a tu proceso de finalización con GomezLee Marketing.\n </p>\n\n <p style=\"margin:0 0 24px 0; font-size:15px; line-height:25px; color:#4A4A4A;\">\n Te pedimos por favor revisar la información con atención y aprobar por esta vía. Si tienes alguna duda o necesitas validar algún dato, puedes responder a este correo para que el equipo correspondiente pueda darte seguimiento. Luego de tu aprobación te estaremos contactando para el pago.\n </p>\n\n <p style=\"margin:0 0 20px 0; font-size:15px; line-height:25px; color:#4A4A4A;\">\n El documento está adjunto a este correo. También puedes abrirlo desde el siguiente enlace:\n </p>\n\n <table role=\"presentation\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\" style=\"margin:0 0 30px 0;\">\n <tr>\n <td style=\"background-color:#4F758B; border-bottom:4px solid #6CC24A;\">\n <a \n href=\"${pdfUrl}\" \n target=\"_blank\" \n style=\"display:inline-block; padding:14px 22px; color:#FFFFFF; font-size:14px; font-weight:700; text-decoration:none; font-family:Arial, Helvetica, sans-serif;\"\n >\n Abrir documento\n </a>\n </td>\n </tr>\n </table>\n\n <table role=\"presentation\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\" style=\"margin:0 0 28px 0; border-collapse:collapse;\">\n <tr>\n <td style=\"border-left:4px solid #6CC24A; padding:12px 16px; background-color:#F5F5F5; color:#4A4A4A; font-size:13px; line-height:21px;\">\n Este documento contiene información confidencial. Por favor no lo compartas fuera de los canales correspondientes.\n </td>\n </tr>\n </table>\n\n <p style=\"margin:0; font-size:15px; line-height:25px; color:#4A4A4A;\">\n Saludos cordiales,<br>\n <strong style=\"color:#4F758B;\">GomezLee Marketing</strong>\n </p>\n\n </td>\n </tr>\n\n </table>\n\n </td>\n </tr>\n </table>\n\n</body>\n</html>\n`;\n\n if (binary.data) {\n binary.data.fileName = source.attachmentName || `${source.nombre} - Detalles de finiquito.pdf`;\n binary.data.mimeType = 'application/pdf';\n }\n\n return {\n json: {\n ...source,\n html\n },\n binary\n };\n});"
},
"id": "2ec1eb01-e956-42a1-a0e8-d52cbc8c109b",
"name": "Construir HTML por empleado",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2016,
2016
],
"notesInFlow": true,
"notes": "Construye el correo real para cada persona: Hola + nombre correspondiente, logo GLM real, botón y adjunto PDF."
},
{
"parameters": {
"sendTo": "={{$json[\"emailPersonal\"]}}",
"subject": "={{$json[\"subject\"]}}",
"message": "={{$json[\"html\"]}}",
"options": {
"attachmentsUi": {
"attachmentsBinary": [
{}
]
},
"senderName": "Rocío Galvez",
"replyTo": "rgalvez@gomezleemarketing.com"
}
},
"id": "9cffb51e-ed58-42ce-8044-e41365053790",
"name": "Enviar correo a colaborador",
"type": "n8n-nodes-base.gmail",
"typeVersion": 2.1,
"position": [
2288,
2016
],
"webhookId": "5d0344ac-b2d4-4279-a7d3-e5a3f12a282d",
"retryOnFail": true,
"maxTries": 3,
"waitBetweenTries": 5000,
"credentials": {
"gmailOAuth2": {
"id": "UDcO1FLJqA453V2D",
"name": "Gmail account 3"
}
}
},
{
"parameters": {
"jsCode": "const enviados = $input.all();\nconst fuentes = $('Construir HTML por empleado').all();\n\nif (enviados.length !== fuentes.length) {\n throw new Error(`La cantidad de correos enviados (${enviados.length}) no coincide con la cantidad preparada (${fuentes.length}).`);\n}\n\nreturn enviados.map((item, index) => {\n const source = fuentes[index].json;\n const fecha = new Date().toISOString();\n\n return {\n json: {\n ...source,\n gmailId: item.json.id || item.json.messageId || '',\n sentAt: fecha,\n observacionEnvio: `Correo enviado por n8n el ${fecha}. Acceso de lectura otorgado al PDF y documento adjunto.`\n }\n };\n});"
},
"id": "528e4272-351a-4e38-b9e8-704aeba503ad",
"name": "Preparar actualización de CONTROL_ENVIOS",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
3472,
2000
]
},
{
"parameters": {
"method": "PUT",
"url": "==https://sheets.googleapis.com/v4/spreadsheets/1Y9XhRjSSxtymV7Nm0gYbC5lLuGD2zPTc7IVb8PxoxjY/values/{{$json.estadoRange}}?valueInputOption=USER_ENTERED",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "googleSheetsOAuth2Api",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ { values: [[ \"ENVIADO\" ]] } }}",
"options": {}
},
"id": "a3f1ea0c-8c3c-456d-ab7a-e1a6d0e10565",
"name": "Marcar ENVIADO",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
3744,
1920
],
"notesInFlow": true,
"retryOnFail": true,
"maxTries": 3,
"waitBetweenTries": 5000,
"credentials": {
"googleSheetsOAuth2Api": {
"id": "K0hDZh3a85MpOHCs",
"name": "Google Sheets account 2"
}
},
"notes": "Actualiza la columna Estado envío a ENVIADO solo después de que Gmail confirma el envío."
},
{
"parameters": {
"jsCode": "const fuentes = $('Preparar actualización de CONTROL_ENVIOS').all();\nconst respuestas = $input.all();\n\nif (respuestas.length !== fuentes.length) {\n throw new Error(`La cantidad de actualizaciones de estado (${respuestas.length}) no coincide con la cantidad enviada (${fuentes.length}).`);\n}\n\nreturn respuestas.map((item, index) => ({\n json: fuentes[index].json\n}));"
},
"id": "df873088-035e-4b3e-9d99-94b0d1d15e6f",
"name": "Restaurar datos tras marcar estado",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
4032,
2000
]
},
{
"parameters": {
"method": "PUT",
"url": "==https://sheets.googleapis.com/v4/spreadsheets/1Y9XhRjSSxtymV7Nm0gYbC5lLuGD2zPTc7IVb8PxoxjY/values/{{$json.observacionRange}}?valueInputOption=USER_ENTERED",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "googleSheetsOAuth2Api",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ { values: [[ $json.observacionEnvio ]] } }}",
"options": {}
},
"id": "691abc7d-6ded-49aa-a805-d94efa62703a",
"name": "Actualizar observación",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
4304,
2000
],
"retryOnFail": true,
"maxTries": 3,
"waitBetweenTries": 5000,
"credentials": {
"googleSheetsOAuth2Api": {
"id": "K0hDZh3a85MpOHCs",
"name": "Google Sheets account 2"
}
}
},
{
"parameters": {
"jsCode": "const fuentes = $('Preparar actualización de CONTROL_ENVIOS').all();\n\nreturn [\n {\n json: {\n estado: 'ENVIO_MASIVO_COMPLETADO',\n totalCorreosEnviados: fuentes.length,\n enviados: fuentes.map(item => ({\n nombre: item.json.nombre,\n email: item.json.emailPersonal,\n hoja: item.json.hoja,\n pdfFileId: item.json.pdfFileId,\n gmailId: item.json.gmailId || ''\n })),\n nota: 'Se otorgó acceso de lectura al PDF antes de cada envío. Se actualizó CONTROL_ENVIOS a ENVIADO para los registros procesados.',\n fecha: new Date().toISOString()\n }\n }\n];"
},
"id": "ea9a0369-8b23-4816-a1f0-f41f2206fbcb",
"name": "Resumen final",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
4592,
2000
]
},
{
"parameters": {
"content": "## 1) Lectura y validación\n\nLee CONTROL_ENVIOS y prepara solo las filas con:\n\n**Estado envío = LISTO_PARA_ENVIAR**\n\nAquí se validan los datos obligatorios antes de continuar:\n\n- Nombre del colaborador\n- Email personal\n- PDF File ID\n- PDF URL\n- Estado de envío\n\nSi alguna fila tiene datos incompletos o inválidos, el flujo se detiene para evitar errores en el envío.",
"height": 464,
"width": 784
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
304,
1712
],
"id": "505b4875-c922-4b2b-8caf-5a2b543ee660",
"name": "Sticky Note"
},
{
"parameters": {
"content": "## 2) Acceso al PDF + correo personalizado\n\nPor cada colaborador, el flujo primero otorga acceso de lectura al PDF correspondiente.\n\nLuego descarga el PDF y construye el correo HTML personalizado con:\n\n**Hola [Nombre del colaborador],**\n\nCada persona recibe únicamente su propio documento adjunto y su propio enlace de Drive.",
"height": 384,
"width": 1312,
"color": 5
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
1440,
1824
],
"id": "23630222-28f7-4675-b6f5-0fe0c5951e5c",
"name": "Sticky Note1"
},
{
"parameters": {
"content": "## 3) Registro posterior al envío\n\nDespués de enviar el correo correctamente, el flujo actualiza CONTROL_ENVIOS.\n\nMarca la fila como:\n\n**ENVIADO**\n\nTambién actualiza la observación para dejar trazabilidad del envío.\n\nEjecutar este workflow únicamente cuando Administración / Nómina den el visto bueno final.",
"height": 528,
"width": 1520,
"color": 4
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
3280,
1696
],
"id": "9386eef4-0dd9-4651-ac46-550be3c703e7",
"name": "Sticky Note2"
}
],
"connections": {
"Manual Trigger": {
"main": [
[
{
"node": "Leer CONTROL_ENVIOS",
"type": "main",
"index": 0
}
]
]
},
"Leer CONTROL_ENVIOS": {
"main": [
[
{
"node": "Validar y preparar envíos",
"type": "main",
"index": 0
}
]
]
},
"Validar y preparar envíos": {
"main": [
[
{
"node": "Descargar PDF",
"type": "main",
"index": 0
}
]
]
},
"Descargar PDF": {
"main": [
[
{
"node": "Construir HTML por empleado",
"type": "main",
"index": 0
}
]
]
},
"Construir HTML por empleado": {
"main": [
[
{
"node": "Enviar correo a colaborador",
"type": "main",
"index": 0
}
]
]
},
"Enviar correo a colaborador": {
"main": [
[
{
"node": "Preparar actualización de CONTROL_ENVIOS",
"type": "main",
"index": 0
}
]
]
},
"Preparar actualización de CONTROL_ENVIOS": {
"main": [
[
{
"node": "Marcar ENVIADO",
"type": "main",
"index": 0
}
]
]
},
"Marcar ENVIADO": {
"main": [
[
{
"node": "Restaurar datos tras marcar estado",
"type": "main",
"index": 0
}
]
]
},
"Restaurar datos tras marcar estado": {
"main": [
[
{
"node": "Actualizar observación",
"type": "main",
"index": 0
}
]
]
},
"Actualizar observación": {
"main": [
[
{
"node": "Resumen final",
"type": "main",
"index": 0
}
]
]
}
},
"pinData": {},
"meta": {
"templateCredsSetupCompleted": true,
"instanceId": "b4b77b17af092830e794eef639ce2f6d7daccf7eddc075060b03b3b6545aac70"
}
}