feat: n8n initial commit — JavaScript (Node.js) n8n workflow automation + axios/cheerio/puppeteer/xlsx
This commit is contained in:
@@ -0,0 +1,518 @@
|
||||
# Voz → Calendario Nextcloud — Blueprint Completo
|
||||
**Versión:** 1.0
|
||||
**Fecha:** 2026-07-03
|
||||
**Autor:** Álvaro
|
||||
**n8n:** https://n8n.crewinghunters.com
|
||||
|
||||
---
|
||||
|
||||
## Visión General
|
||||
|
||||
Grabas un audio en el teléfono → se lo mandas al bot de Telegram → la IA local transcribe y entiende el evento → aparece en tu calendario de Nextcloud → te recuerda 30 minutos antes por Telegram.
|
||||
|
||||
```
|
||||
📱 Audio (teléfono)
|
||||
│
|
||||
▼ Telegram
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ n8n (Raspberry Pi 4 — Colombia) │
|
||||
│ │
|
||||
│ 1. Recibe audio de Telegram │
|
||||
│ 2. Descarga el archivo .ogg/.mp3 │
|
||||
│ 3. Llama a Whisper API (tu PC via Tailscale) │
|
||||
│ 4. Llama a Ollama (tu PC via Tailscale) │
|
||||
│ 5. Crea evento en Nextcloud Calendar (CalDAV) │
|
||||
│ 6. Confirma por Telegram │
|
||||
│ 7. Programa recordatorio 30min antes │
|
||||
└─────────────────────────────────────────────────┘
|
||||
│
|
||||
▼ Tailscale VPN (túnel privado gratis)
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ Tu PC (Windows) │
|
||||
│ ├── Ollama → AsistentePersonal / qwen2.5:14b │
|
||||
│ └── Whisper API → faster-whisper (Python) │
|
||||
└─────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Stack Tecnológico
|
||||
|
||||
| Componente | Herramienta | Dónde corre |
|
||||
|-----------|------------|------------|
|
||||
| Automatización | n8n | RPi4 Colombia |
|
||||
| Bot mensajería | Telegram Bot | Cloud (Telegram) |
|
||||
| Transcripción voz | faster-whisper | Tu PC (Windows) |
|
||||
| LLM extracción evento | Ollama / qwen2.5:14b | Tu PC (Windows) |
|
||||
| Calendario | Nextcloud CalDAV | RPi4 Colombia |
|
||||
| Túnel privado | Tailscale | PC + RPi4 |
|
||||
| API transcripción | FastAPI (Python) | Tu PC (Windows) |
|
||||
|
||||
---
|
||||
|
||||
## PASO 1 — Tailscale (conectar PC con RPi4)
|
||||
|
||||
Tailscale crea una red privada entre tus dispositivos. Una vez instalado, tu PC tendrá una IP tipo `100.x.x.x` accesible desde el RPi4 sin abrir puertos ni router.
|
||||
|
||||
### En tu PC (Windows)
|
||||
1. Descarga e instala Tailscale: https://tailscale.com/download/windows
|
||||
2. Inicia sesión con Google o email
|
||||
3. Anota tu IP Tailscale: aparece en el icono de la bandeja del sistema
|
||||
- Ejemplo: `100.64.1.10`
|
||||
|
||||
### En el RPi4 (via SSH desde Colombia)
|
||||
```bash
|
||||
# Conectar al RPi4
|
||||
ssh usuario@n8n.crewinghunters.com
|
||||
|
||||
# Instalar Tailscale en RPi4
|
||||
curl -fsSL https://tailscale.com/install.sh | sh
|
||||
|
||||
# Iniciar y autenticar (con la MISMA cuenta que en el PC)
|
||||
sudo tailscale up
|
||||
|
||||
# Verificar que ve tu PC
|
||||
tailscale ping 100.64.1.10 # ← IP de tu PC Tailscale
|
||||
```
|
||||
|
||||
✅ Si el ping responde, están conectados. El RPi4 puede hablar con tu PC.
|
||||
|
||||
---
|
||||
|
||||
## PASO 2 — Whisper API en tu PC
|
||||
|
||||
Instalamos faster-whisper y levantamos una API HTTP que n8n puede llamar.
|
||||
|
||||
### Instalar faster-whisper
|
||||
```bash
|
||||
# En tu PC (PowerShell)
|
||||
pip install faster-whisper fastapi uvicorn python-multipart
|
||||
```
|
||||
|
||||
### Crear el servidor API
|
||||
|
||||
Guarda este archivo en: `C:\whisper-api\server.py`
|
||||
|
||||
```python
|
||||
from fastapi import FastAPI, UploadFile, File
|
||||
from faster_whisper import WhisperModel
|
||||
import tempfile, os
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
# Carga el modelo una vez al iniciar (medium es bueno para español)
|
||||
# Opciones: "tiny", "base", "small", "medium", "large-v3"
|
||||
model = WhisperModel("medium", device="cpu", compute_type="int8")
|
||||
|
||||
@app.post("/transcribe")
|
||||
async def transcribe(file: UploadFile = File(...)):
|
||||
# Guardar audio temporalmente
|
||||
suffix = os.path.splitext(file.filename)[1] or ".ogg"
|
||||
with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as tmp:
|
||||
tmp.write(await file.read())
|
||||
tmp_path = tmp.name
|
||||
|
||||
# Transcribir
|
||||
segments, info = model.transcribe(tmp_path, language="es")
|
||||
text = " ".join([seg.text for seg in segments]).strip()
|
||||
|
||||
os.unlink(tmp_path)
|
||||
return {"text": text, "language": info.language}
|
||||
|
||||
@app.get("/health")
|
||||
def health():
|
||||
return {"status": "ok"}
|
||||
```
|
||||
|
||||
### Script para iniciar la API (guarda como `C:\whisper-api\start.bat`)
|
||||
```bat
|
||||
@echo off
|
||||
cd C:\whisper-api
|
||||
uvicorn server:app --host 0.0.0.0 --port 8765
|
||||
```
|
||||
|
||||
### Iniciar automáticamente con Windows
|
||||
1. Presiona `Win + R` → escribe `shell:startup`
|
||||
2. Crea un acceso directo a `start.bat` en esa carpeta
|
||||
3. La API estará disponible en: `http://100.64.1.10:8765` (via Tailscale)
|
||||
|
||||
### Verificar que funciona (desde RPi4)
|
||||
```bash
|
||||
# Desde el RPi4 via SSH
|
||||
curl http://100.64.1.10:8765/health
|
||||
# Debe responder: {"status":"ok"}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## PASO 3 — Exponer Ollama via Tailscale
|
||||
|
||||
Por defecto Ollama solo escucha en localhost. Hay que permitir que escuche en todas las interfaces.
|
||||
|
||||
### En tu PC (PowerShell como Administrador)
|
||||
```powershell
|
||||
# Agregar variable de entorno para Ollama
|
||||
[System.Environment]::SetEnvironmentVariable("OLLAMA_HOST", "0.0.0.0:11434", "Machine")
|
||||
|
||||
# Reiniciar Ollama (busca en la bandeja del sistema → clic derecho → Quit, luego reabre)
|
||||
```
|
||||
|
||||
### Verificar desde RPi4
|
||||
```bash
|
||||
curl http://100.64.1.10:11434/api/tags
|
||||
# Debe listar tus modelos
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## PASO 4 — Bot de Telegram
|
||||
|
||||
### Crear el bot
|
||||
1. Abre Telegram → busca `@BotFather`
|
||||
2. Escribe `/newbot`
|
||||
3. Nombre del bot: `Mi Calendario Personal` (o el que quieras)
|
||||
4. Username: `mi_calendario_alvaro_bot` (debe terminar en `_bot`)
|
||||
5. BotFather te da un **token** — guárdalo: `7123456789:AAF...`
|
||||
|
||||
### Obtener tu Chat ID
|
||||
1. Escribe cualquier mensaje a tu nuevo bot
|
||||
2. Abre en el navegador (reemplaza TOKEN):
|
||||
```
|
||||
https://api.telegram.org/botTOKEN/getUpdates
|
||||
```
|
||||
3. Busca `"id"` dentro de `"chat"` — ese número es tu **Chat ID**
|
||||
- Ejemplo: `123456789`
|
||||
|
||||
---
|
||||
|
||||
## PASO 5 — Nextcloud Calendar (CalDAV)
|
||||
|
||||
n8n se conecta al calendario de Nextcloud via CalDAV.
|
||||
|
||||
### Obtener la URL CalDAV
|
||||
En Nextcloud → Calendario → ⚙️ (engranaje junto al calendario) → "Copiar enlace privado"
|
||||
|
||||
La URL tiene esta forma:
|
||||
```
|
||||
https://tu-nextcloud.com/remote.php/dav/calendars/USUARIO/personal/
|
||||
```
|
||||
|
||||
### Credenciales
|
||||
- Usuario: tu usuario de Nextcloud
|
||||
- Contraseña: genera una **contraseña de aplicación** en Nextcloud:
|
||||
Configuración → Seguridad → Dispositivos y sesiones → "Crear nueva contraseña de aplicación"
|
||||
- Nombre: `n8n-calendario`
|
||||
|
||||
---
|
||||
|
||||
## PASO 6 — Organizar Carpetas en n8n
|
||||
|
||||
Antes de crear el workflow, organiza las carpetas:
|
||||
|
||||
1. Abre https://n8n.crewinghunters.com
|
||||
2. En el panel izquierdo → "Workflows" → "Add folder"
|
||||
3. Crea estas carpetas:
|
||||
|
||||
```
|
||||
📁 Prisa Yachts ← mueve el bot de WhatsApp aquí
|
||||
📁 Personal - Productividad
|
||||
📁 AIS Navigator
|
||||
📁 Pruebas / Sandbox
|
||||
```
|
||||
|
||||
4. El nuevo workflow irá en `📁 Personal - Productividad`
|
||||
|
||||
---
|
||||
|
||||
## PASO 7 — Workflow n8n Completo
|
||||
|
||||
Nombre del workflow: `🎙️ Voz → Calendario Nextcloud`
|
||||
Carpeta: `Personal - Productividad`
|
||||
|
||||
### Nodos del workflow (en orden):
|
||||
|
||||
```
|
||||
[Telegram Trigger]
|
||||
│
|
||||
▼
|
||||
[IF: ¿es audio?]
|
||||
│ sí
|
||||
▼
|
||||
[HTTP: Descargar audio de Telegram]
|
||||
│
|
||||
▼
|
||||
[HTTP: Whisper API → transcripción]
|
||||
│
|
||||
▼
|
||||
[HTTP: Ollama → extraer evento]
|
||||
│
|
||||
▼
|
||||
[Code: parsear JSON respuesta Ollama]
|
||||
│
|
||||
▼
|
||||
[HTTP: Crear evento CalDAV en Nextcloud]
|
||||
│
|
||||
▼
|
||||
[Telegram: Confirmar al usuario]
|
||||
│
|
||||
▼
|
||||
[Wait: esperar hasta 30min antes del evento]
|
||||
│
|
||||
▼
|
||||
[Telegram: Enviar recordatorio]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Nodo 1 — Telegram Trigger
|
||||
```
|
||||
Tipo: Telegram Trigger
|
||||
Credential: (crear nueva → Token del bot)
|
||||
Updates: message
|
||||
```
|
||||
|
||||
### Nodo 2 — IF: ¿Es audio?
|
||||
```
|
||||
Tipo: IF
|
||||
Condición: {{ $json.message.voice }} existe
|
||||
O {{ $json.message.audio }} existe
|
||||
```
|
||||
|
||||
### Nodo 3 — HTTP: Descargar audio de Telegram
|
||||
```
|
||||
Tipo: HTTP Request
|
||||
Método: GET
|
||||
URL: https://api.telegram.org/bot{{ $env.TELEGRAM_TOKEN }}/getFile
|
||||
Parámetros:
|
||||
file_id: {{ $json.message.voice.file_id }}
|
||||
```
|
||||
|
||||
Luego un segundo HTTP Request para descargar el archivo:
|
||||
```
|
||||
URL: https://api.telegram.org/file/bot{{ $env.TELEGRAM_TOKEN }}/{{ $json.result.file_path }}
|
||||
Respuesta: archivo binario
|
||||
```
|
||||
|
||||
### Nodo 4 — HTTP: Whisper API (transcripción)
|
||||
```
|
||||
Tipo: HTTP Request
|
||||
Método: POST
|
||||
URL: http://100.64.1.10:8765/transcribe ← IP Tailscale de tu PC
|
||||
Body: form-data
|
||||
file: [archivo de audio del nodo anterior]
|
||||
```
|
||||
|
||||
Resultado esperado:
|
||||
```json
|
||||
{ "text": "Reunión con Juan el viernes a las diez de la mañana en el puerto" }
|
||||
```
|
||||
|
||||
### Nodo 5 — HTTP: Ollama (extraer evento)
|
||||
```
|
||||
Tipo: HTTP Request
|
||||
Método: POST
|
||||
URL: http://100.64.1.10:11434/api/generate
|
||||
Body JSON:
|
||||
{
|
||||
"model": "qwen2.5:14b",
|
||||
"stream": false,
|
||||
"prompt": "Extrae el evento de esta frase y devuelve SOLO JSON válido sin explicaciones:\n\nFrase: '{{ $json.text }}'\n\nFecha actual: {{ $now.format('YYYY-MM-DD') }}, {{ $now.format('dddd') }}\n\nDevuelve exactamente este formato:\n{\"titulo\": \"...\", \"fecha\": \"YYYY-MM-DD\", \"hora_inicio\": \"HH:MM\", \"hora_fin\": \"HH:MM\", \"lugar\": \"...\", \"descripcion\": \"...\"}\n\nSi no hay hora fin, suma 1 hora a la hora inicio. Si no hay lugar, pon vacío."
|
||||
}
|
||||
```
|
||||
|
||||
### Nodo 6 — Code: Parsear respuesta Ollama
|
||||
```javascript
|
||||
// Extraer el JSON de la respuesta de Ollama
|
||||
const responseText = $input.item.json.response;
|
||||
|
||||
// Buscar el JSON dentro del texto (por si Ollama añade texto extra)
|
||||
const jsonMatch = responseText.match(/\{[\s\S]*\}/);
|
||||
if (!jsonMatch) throw new Error("Ollama no devolvió JSON válido: " + responseText);
|
||||
|
||||
const evento = JSON.parse(jsonMatch[0]);
|
||||
|
||||
// Construir fechas iCal (formato: 20260704T100000)
|
||||
const fechaInicio = evento.fecha.replace(/-/g, '') + 'T' + evento.hora_inicio.replace(':', '') + '00';
|
||||
const fechaFin = evento.fecha.replace(/-/g, '') + 'T' + evento.hora_fin.replace(':', '') + '00';
|
||||
|
||||
// UID único para el evento
|
||||
const uid = 'n8n-' + Date.now() + '@crewinghunters.com';
|
||||
|
||||
// Formato iCalendar (CalDAV)
|
||||
const ical = `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
PRODID:-//n8n//Calendario Voz//ES
|
||||
BEGIN:VEVENT
|
||||
UID:${uid}
|
||||
DTSTART:${fechaInicio}
|
||||
DTEND:${fechaFin}
|
||||
SUMMARY:${evento.titulo}
|
||||
LOCATION:${evento.lugar || ''}
|
||||
DESCRIPTION:${evento.descripcion || 'Creado por voz via n8n'}
|
||||
END:VEVENT
|
||||
END:VCALENDAR`;
|
||||
|
||||
return {
|
||||
json: {
|
||||
...evento,
|
||||
ical,
|
||||
uid,
|
||||
fechaInicio,
|
||||
fechaFin,
|
||||
// Para el recordatorio: fecha ISO completa
|
||||
fechaRecordatorio: new Date(evento.fecha + 'T' + evento.hora_inicio + ':00').toISOString()
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Nodo 7 — HTTP: Crear evento en Nextcloud (CalDAV)
|
||||
```
|
||||
Tipo: HTTP Request
|
||||
Método: PUT
|
||||
URL: https://tu-nextcloud.com/remote.php/dav/calendars/USUARIO/personal/{{ $json.uid }}.ics
|
||||
Autenticación: Basic Auth
|
||||
Usuario: tu_usuario_nextcloud
|
||||
Password: contraseña_de_aplicacion
|
||||
Headers:
|
||||
Content-Type: text/calendar; charset=utf-8
|
||||
Body: {{ $json.ical }}
|
||||
```
|
||||
|
||||
### Nodo 8 — Telegram: Confirmar
|
||||
```
|
||||
Tipo: Telegram
|
||||
Operación: Send Message
|
||||
Chat ID: {{ $env.TELEGRAM_CHAT_ID }}
|
||||
Texto:
|
||||
✅ *Evento creado en tu calendario*
|
||||
|
||||
📅 *{{ $json.titulo }}*
|
||||
🗓 {{ $json.fecha }}
|
||||
🕐 {{ $json.hora_inicio }} – {{ $json.hora_fin }}
|
||||
📍 {{ $json.lugar || 'Sin ubicación' }}
|
||||
|
||||
_Te recordaré 30 minutos antes_ ⏰
|
||||
```
|
||||
|
||||
### Nodo 9 — Wait (hasta 30min antes del evento)
|
||||
```
|
||||
Tipo: Wait
|
||||
Modo: Until specified time
|
||||
Fecha/Hora: {{ $json.fechaRecordatorio }}
|
||||
Offset: -30 minutos
|
||||
```
|
||||
|
||||
### Nodo 10 — Telegram: Recordatorio
|
||||
```
|
||||
Tipo: Telegram
|
||||
Operación: Send Message
|
||||
Chat ID: {{ $env.TELEGRAM_CHAT_ID }}
|
||||
Texto:
|
||||
⏰ *Recordatorio — en 30 minutos:*
|
||||
|
||||
📅 *{{ $json.titulo }}*
|
||||
🕐 {{ $json.hora_inicio }}
|
||||
📍 {{ $json.lugar || '' }}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## PASO 8 — Variables de Entorno en n8n
|
||||
|
||||
En n8n → Settings → Variables → Agregar:
|
||||
|
||||
| Variable | Valor |
|
||||
|---------|-------|
|
||||
| `TELEGRAM_TOKEN` | `7123456789:AAF...` (token del bot) |
|
||||
| `TELEGRAM_CHAT_ID` | `123456789` (tu chat ID) |
|
||||
| `WHISPER_URL` | `http://100.64.1.10:8765` |
|
||||
| `OLLAMA_URL` | `http://100.64.1.10:11434` |
|
||||
| `NEXTCLOUD_URL` | `https://tu-nextcloud.com` |
|
||||
| `NEXTCLOUD_USER` | `tu_usuario` |
|
||||
| `NEXTCLOUD_PASS` | `contraseña_de_aplicacion` |
|
||||
|
||||
---
|
||||
|
||||
## PASO 9 — Prueba Completa
|
||||
|
||||
1. Graba un audio en el teléfono diciendo:
|
||||
> *"Reunión con Federico el próximo martes a las tres de la tarde en el puerto de Miami"*
|
||||
|
||||
2. Envía el audio al bot de Telegram
|
||||
|
||||
3. En ~10-15 segundos deberías recibir:
|
||||
```
|
||||
✅ Evento creado en tu calendario
|
||||
📅 Reunión con Federico
|
||||
🗓 2026-07-07
|
||||
🕐 15:00 – 16:00
|
||||
📍 Puerto de Miami
|
||||
```
|
||||
|
||||
4. Verifica en Nextcloud Calendar que el evento aparece
|
||||
|
||||
5. Espera el recordatorio 30 min antes ✅
|
||||
|
||||
---
|
||||
|
||||
## Checklist de Instalación
|
||||
|
||||
### En tu PC (Windows)
|
||||
- [ ] Instalar Tailscale → anotar IP (`100.x.x.x`)
|
||||
- [ ] `pip install faster-whisper fastapi uvicorn python-multipart`
|
||||
- [ ] Crear `C:\whisper-api\server.py` (código arriba)
|
||||
- [ ] Crear `C:\whisper-api\start.bat`
|
||||
- [ ] Agregar `start.bat` al inicio de Windows
|
||||
- [ ] Iniciar la API y verificar: `curl http://localhost:8765/health`
|
||||
- [ ] Configurar Ollama para escuchar en `0.0.0.0`: variable `OLLAMA_HOST`
|
||||
|
||||
### En el RPi4 (via SSH)
|
||||
- [ ] `curl -fsSL https://tailscale.com/install.sh | sh`
|
||||
- [ ] `sudo tailscale up` → autenticar con la misma cuenta
|
||||
- [ ] Verificar ping a PC: `tailscale ping 100.x.x.x`
|
||||
- [ ] Verificar Whisper: `curl http://100.x.x.x:8765/health`
|
||||
- [ ] Verificar Ollama: `curl http://100.x.x.x:11434/api/tags`
|
||||
|
||||
### En Telegram
|
||||
- [ ] Crear bot con @BotFather → guardar token
|
||||
- [ ] Obtener tu Chat ID
|
||||
- [ ] Escribir un mensaje al bot para activarlo
|
||||
|
||||
### En Nextcloud
|
||||
- [ ] Copiar URL CalDAV del calendario
|
||||
- [ ] Crear contraseña de aplicación para n8n
|
||||
|
||||
### En n8n
|
||||
- [ ] Crear carpetas: Prisa Yachts / Personal / AIS Navigator / Sandbox
|
||||
- [ ] Crear variables de entorno (8 variables)
|
||||
- [ ] Crear workflow `🎙️ Voz → Calendario Nextcloud`
|
||||
- [ ] Agregar los 10 nodos en orden
|
||||
- [ ] Activar el workflow
|
||||
- [ ] Prueba con audio real
|
||||
|
||||
---
|
||||
|
||||
## Solución de Problemas
|
||||
|
||||
| Problema | Causa probable | Solución |
|
||||
|---------|---------------|---------|
|
||||
| n8n no llega a Whisper | Tailscale no conectado | `sudo tailscale up` en RPi4 |
|
||||
| Whisper tarda mucho | Modelo "medium" pesado en CPU | Cambiar a "small" en server.py |
|
||||
| Ollama no responde | OLLAMA_HOST no configurado | Reiniciar Ollama tras la variable |
|
||||
| Evento en fecha incorrecta | Ollama interpretó mal el día | Ajustar el prompt con más contexto |
|
||||
| CalDAV 401 Unauthorized | Contraseña de app incorrecta | Crear nueva contraseña en Nextcloud |
|
||||
| Audio no se procesa | Formato no soportado | Telegram envía .ogg — faster-whisper lo soporta |
|
||||
|
||||
---
|
||||
|
||||
## Mejoras Futuras (Fase 2)
|
||||
|
||||
- [ ] **Cancelar eventos por voz**: "Cancela la reunión del martes" → n8n borra el evento
|
||||
- [ ] **Consultar agenda**: "¿Qué tengo mañana?" → n8n lee el CalDAV y responde
|
||||
- [ ] **Recordatorio personalizable**: "Recuérdame 1 hora antes"
|
||||
- [ ] **Múltiples calendarios**: personal, trabajo, AIS Navigator
|
||||
- [ ] **Texto además de voz**: si escribes (no mandas audio), también funciona
|
||||
|
||||
---
|
||||
|
||||
*Blueprint generado: 2026-07-03 | AR Electronics / Álvaro*
|
||||
Reference in New Issue
Block a user