# 🎂 Workflow: `hbdwsp` — Bot de Cumpleaños GLM (WhatsApp) **ID del Workflow:** `ZzH2eMPrsqlY7WPz` **Autor:** Eidan Then (`ethen@gomezleemarketing.com`) **Ăšltima actualizaciĂłn:** 12 de junio de 2026 **VersiĂłn activa:** `2d27b5f7-f5c4-41f9-b008-d8f3a50557a5` (v251) **Estado:** âś… Activo **Zona horaria:** America/Santo_Domingo (UTC-04:00) --- ## DescripciĂłn General Este workflow automatiza el envĂo de tarjetas de cumpleaños personalizadas al grupo de WhatsApp corporativo de GLM. Cada dĂa a las 10:00 AM, el sistema revisa los empleados registrados en BambooHR, filtra los que cumplen años ese dĂa, genera una tarjeta visual personalizada con su foto y la envĂa al grupo correspondiente. El flujo excluye automáticamente a empleados de paĂses no hispanos (Jamaica, Trinidad & Tobago, Barbados, etc.) y maneja casos donde el empleado no tiene fecha de cumpleaños o foto registrada. --- ## Arquitectura del Flujo ``` Schedule Trigger (10am) └── BambooHR (Reporte CSV #1509) └── Google Drive (Guardar CSV) └── Google Sheets (Leer datos) └── Pais? (Filtrar hispanos) ├── [TRUE] Obtener empleado (BambooHR) │ └── Merge (unir datos Sheets + Bamboo) │ └── Loop Over Items │ └── Tiene fecha? │ ├── [FALSE] → Wait3 → Loop (siguiente) │ └── [TRUE] → Coincide con fecha de hoy? │ ├── [FALSE] → Wait3 → Loop │ └── [TRUE] → Tiene foto? │ ├── [TRUE] → Foto Empleado URL │ └── [FALSE] → URL si no tiene foto (bizcocho) │ └── HTML (generar tarjeta) │ └── HTTP Request (screenshot browserless) │ └── Extract from File │ └── Enviar imagen WhatsApp │ └── Wait (3s) │ └── Loop (siguiente) └── [FALSE] Ignorado (paĂs no hispano) ``` --- ## Nodos del Workflow ### 1. Schedule Trigger - **Tipo:** `scheduleTrigger` - **ConfiguraciĂłn:** Se ejecuta todos los dĂas a las **10:00 AM** (hora Santo Domingo, UTC-4). - **Notas:** El trigger está pinned con fecha `2026-05-29` para pruebas manuales. --- ### 2. BambooHR (Reporte CSV) - **Tipo:** `bambooHr` — `companyReport` - **Reporte ID:** `1509` - **Formato:** CSV - **Credencial:** `Eidan - BambooHR account` - **FunciĂłn:** Descarga el listado completo de empleados activos con sus datos (incluyendo Birthday, Location, EEID, Work Email). --- ### 3. Google Drive (Guardar CSV) - **Tipo:** `googleDrive` — `update` - **Archivo destino:** `HBD.csv` (`1Jw9yOyIKB1QLcr9m8ObGP9PK0tC9z3n6`) - **Credencial:** `Eidan - Google Drive account` - **FunciĂłn:** Actualiza el archivo CSV en Google Drive con los datos más recientes de BambooHR. Sirve como respaldo y fuente de verdad para Google Sheets. --- ### 4. Google Sheets (Leer datos) - **Tipo:** `googleSheets` — read - **Spreadsheet:** `HBD` (`1p0bCvHAIHjobiPhDfmTshZWvRP6PWAmmTtSoX77lBmM`) - **Hoja:** `HBD` (gid: `759771456`) - **Credencial:** `Eidan - Google Sheets account` - **FunciĂłn:** Lee los datos procesados del spreadsheet. AquĂ se encuentran campos adicionales como `Location` que se usa para el filtro de paĂs. --- ### 5. Pais? (Filtro de paĂs) - **Tipo:** `if` - **LĂłgica:** `AND` de condiciones `notEquals` - **PaĂses excluidos (FALSE):** - Jamaica - Trinidad and Tobago / Trinidad & Tobago / Tobago - Barbados - Saint Thomas - St. Croix - **TRUE:** El empleado es de un paĂs hispano → el flujo continĂşa. - **FALSE:** El empleado es de un paĂs no hispano → se ignora silenciosamente. --- ### 6. Obtener empleado - **Tipo:** `bambooHr` — `get` - **Employee ID:** `{{ $json.EEID }}` - **Credencial:** `Eidan - BambooHR account` - **ConfiguraciĂłn:** `retryOnFail: true` - **FunciĂłn:** Obtiene el perfil completo del empleado desde BambooHR, incluyendo `photoUrl` y `photoUploaded`. --- ### 7. Merge - **Tipo:** `merge` — combine (enrich) - **Join por:** `workEmail` ↔ `Work Email` - **Modo:** `enrichInput1` (enriquece los datos de Bamboo con los de Google Sheets) - **FunciĂłn:** Combina en un solo objeto los datos de BambooHR (foto, cargo, etc.) con los de Google Sheets (Location, Birthday, etc.). --- ### 8. Loop Over Items - **Tipo:** `splitInBatches` - **FunciĂłn:** Procesa cada empleado de forma individual y secuencial. Al terminar con uno, vuelve al siguiente. Cuando no quedan más Ătems, el flujo termina. --- ### 9. Tiene fecha? - **Tipo:** `if` - **CondiciĂłn:** `!$json.Birthday` es `true` - **TRUE (sin fecha):** → `Wait3` → vuelve al loop (siguiente empleado). - **FALSE (tiene fecha):** → continĂşa hacia `Coincide con fecha de hoy?`. > ⚠️ **Nota:** La nota sticky original decĂa que el sistema avisarĂa a RRHH si falta la fecha, pero actualmente el `TRUE` solo hace un `Wait3` y continĂşa. Considerar agregar una notificaciĂłn a RRHH. --- ### 10. Coincide con fecha de hoy? - **Tipo:** `if` - **CondiciĂłn:** `$json.Birthday` equals `$now.format('MMMM dd')` (con espacios) - **ValidaciĂłn:** `loose` (tipos flexibles) - **`onError`:** `continueRegularOutput` - **TRUE:** La fecha coincide → continĂşa el flujo. - **FALSE:** No es el cumpleaños hoy → `Wait3` → siguiente empleado. > ⚠️ **Importante:** El `rightValue` tiene un espacio al final: `{{ $now.format('MMMM dd') }} `. Asegurarse de que los datos de Birthday en el CSV tambiĂ©n tengan ese formato (`MMMM dd`) para evitar falsos negativos. --- ### 11. Tiene foto? - **Tipo:** `if` - **CondiciĂłn:** `$json.photoUploaded == true` es `true` - **TRUE:** → `Foto Empleado URL` (usa la foto real del empleado) - **FALSE:** → `URL si no tiene foto` (usa imagen de bizcocho) --- ### 12. Foto Empleado URL / URL si no tiene foto - **Tipo:** `set` - **Foto real:** `{{ $json.photoUrl }}` (URL de BambooHR) - **Foto fallback:** `https://gomezleemarketing.com/wp-content/uploads/2026/03/hbdcake.png` - **Output:** Campo `photoUrl` que se pasa al nodo HTML. --- ### 13. HTML (GeneraciĂłn de tarjeta) - **Tipo:** `html` - **Dimensiones:** 1080 Ă— 1920 px (formato Instagram Stories / WhatsApp) - **Background:** `https://gomezleemarketing.com/wp-content/uploads/2026/03/Comunicacion-de-Cumpleanos-Background.jpg` - **Contenido dinámico:** - Foto del empleado (circular, 530px) - Nombre del empleado (`firstName`) - Fecha de cumpleaños formateada en español (ej: "12 de junio") - **Nota:** Los divs `.hello`, `.message`, `.position` y `.team` están vacĂos en la versiĂłn actual. Pueden ser rellenados con texto personalizado. --- ### 14. HTTP Request (Screenshot) - **Tipo:** `httpRequest` POST - **URL:** `https://pruebadetemplate-browserless.k11ytw.easypanel.host/chromium/screenshot` - **Token:** `8d66338953f117a7c29c55364c8c9e6d` - **Viewport:** `1080 Ă— 1920` - **Response format:** `file` (binario) - **FunciĂłn:** Convierte el HTML generado en una imagen PNG usando Browserless (Chromium headless) alojado en Easypanel. --- ### 15. Extract from File - **Tipo:** `extractFromFile` — `binaryToPropery` - **FunciĂłn:** Convierte el binario de la imagen en base64 (`data`) para enviarlo a la API de WhatsApp. --- ### 16. Enviar mensaje al whatsapp imagen - **Tipo:** `httpRequest` POST - **URL:** `https://wsp.gomezleemarketing.com/message/sendMedia/botsoporte` - **API Key:** `C267126ABB45-4C12-B626-6BAB1833F5D7` - **NĂşmero destino:** `120363404268176806@g.us` (Grupo oficial Motorola) - **Tipo de media:** `image` - **Media:** `{{ $json.data }}` (base64) - **Delay:** 2000ms --- ### 17. Wait (entre empleados) - **Tipo:** `wait` - **DuraciĂłn:** 3 segundos - **FunciĂłn:** Pausa entre el envĂo y la vuelta al loop para no saturar la API de WhatsApp. ### Wait3 (skip al siguiente) - **Tipo:** `wait` - **DuraciĂłn:** 1 segundo - **FunciĂłn:** Pequeña pausa antes de saltar al siguiente empleado cuando no cumple años o no tiene fecha. --- ## Subworkflow: Respaldo en Gitea Este subworkflow es independiente (trigger manual) y guarda/actualiza el JSON del workflow en Gitea como respaldo de versiĂłn. ### Nodos: | Nodo | FunciĂłn | |------|---------| | `Valores` | Define `repo_name: wsp-cumpleanos` y `file_name: hbd-panama-motorola` | | `Obtener archivo` | GET al repo Gitea para obtener el SHA actual del archivo | | `Get a workflow1/2` | Obtiene el JSON del propio workflow (`ZzH2eMPrsqlY7WPz`) vĂa API de n8n | | `Code in JavaScript` | Prepara payload con SHA para **actualizar** (PUT) | | `Code in JavaScript1` | Prepara payload sin SHA para **crear** (POST) | | `Actualizar archivo` | PUT al endpoint de Gitea (actualiza si ya existe) | | `Subir archivo` | POST al endpoint de Gitea (crea si no existe) | **Repositorio:** `https://git.digitalcompass.agency/EidanThen/wsp-cumpleanos` --- ## Requisitos de InstalaciĂłn ### Servicios externos requeridos | Servicio | Uso | Credencial | |----------|-----|------------| | **BambooHR** | Fuente de datos de empleados | `Eidan - BambooHR account` | | **Google Drive** | Almacenamiento del CSV actualizado | `Eidan - Google Drive account` | | **Google Sheets** | Lectura de datos procesados | `Eidan - Google Sheets account` | | **Browserless** (Easypanel) | Renderizado HTML → PNG | Token en URL | | **EvolutionAPI** (WhatsApp) | EnvĂo de imágenes al grupo | API Key en header | | **Gitea** (opcional) | Respaldo del workflow JSON | `Eidan - gitea` | | **n8n API** (opcional) | Leer JSON del workflow para backup | `Eidan - n8n account` | --- ### Paso 1: Configurar credenciales en n8n 1. Ve a **Settings → Credentials** en n8n. 2. Crea las siguientes credenciales: #### BambooHR - **Tipo:** BambooHR API - **API Key:** (tu API key de BambooHR) - **Subdomain:** `gomezleemarketing` #### Google Drive / Sheets - **Tipo:** Google OAuth2 - Autoriza con la cuenta `ethen@gomezleemarketing.com` - Aplica a ambos nodos (Drive y Sheets) #### Gitea (Header Auth) - **Tipo:** Header Auth - **Name:** `Authorization` - **Value:** `token TU_TOKEN_GITEA` --- ### Paso 2: Configurar Google Sheets El spreadsheet `HBD` debe tener al menos las columnas: | Columna | DescripciĂłn | |---------|-------------| | `EEID` | ID del empleado en BambooHR | | `Work Email` | Email corporativo (clave de join con Bamboo) | | `Birthday` | Fecha en formato `MMMM dd` (ej: `June 12`) | | `Location` | PaĂs del empleado (ej: `Dominican Republic`) | | `firstName` | Nombre del empleado | --- ### Paso 3: Configurar Browserless El workflow usa una instancia de Browserless alojada en Easypanel: ``` URL: https://pruebadetemplate-browserless.k11ytw.easypanel.host Token: 8d66338953f117a7c29c55364c8c9e6d ``` Si se despliega una nueva instancia, actualizar la URL y el token en el nodo **HTTP Request** (screenshot). **Para desplegar Browserless en Docker:** ```yaml services: browserless: image: ghcr.io/browserless/chromium:latest ports: - "3000:3000" environment: - TOKEN=tu_token_aqui ``` --- ### Paso 4: Configurar EvolutionAPI (WhatsApp) El workflow usa EvolutionAPI como intermediario para WhatsApp: - **Base URL:** `https://wsp.gomezleemarketing.com` - **Instancia:** `botsoporte` - **API Key:** configurada en el header `apikey` AsegĂşrate de que la instancia `botsoporte` estĂ© conectada y activa antes de ejecutar el workflow. --- ### Paso 5: Importar el workflow en n8n 1. En n8n, ve a **Workflows → Import from file/URL**. 2. Pega el JSON del workflow o importa desde Gitea. 3. Asigna las credenciales correctas a cada nodo. 4. Activa el workflow con el toggle. --- ## ConfiguraciĂłn del Grupo de WhatsApp | Entorno | NĂşmero del grupo | |---------|-----------------| | **ProducciĂłn (Motorola)** | `120363404268176806@g.us` | | **Pruebas** | `120363426353723539@g.us` | Para cambiar el grupo destino, editar el campo `number` en el nodo **Enviar mensaje al whatsapp imagen**. --- ## PersonalizaciĂłn de la Tarjeta HTML La tarjeta visual se genera en el nodo `HTML`. Los siguientes divs están disponibles para personalizaciĂłn: ```html