519 lines
15 KiB
Markdown
519 lines
15 KiB
Markdown
# 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*
|