# n8n workflows para Leonex (v2.1 — fix env vars + timezone)

Workflows JSON listos para importar en tu instancia de n8n. Los dos workflows
del v1 (Bridge intraday cada 15m + Pipeline diario llamando `/api/refresh`)
fueron **archivados** porque generaban Errors de 10-30 min cada ejecución y
colgaban el lock del server.

El v2 inicial (1h + fire-and-forget) sufrió DOS bugs en producción:

1. **`access to env vars denied`**: el HTTP node usaba `{{ $env.LEONEX_URL }}`
   pero n8n self-hosted bloquea el acceso a env vars por defecto. Fix v2.1:
   URL hardcoded `https://leonex.187.127.86.200.nip.io`.
2. **Cron interpretado en `America/New_York`**: la instancia n8n
   (EasyPanel) usa NY timezone por defecto, no UTC, ignorando el
   `settings.timezone` del workflow. Esto provocaba que el cron
   `0 14-20 * * 1-5` (pensado como UTC) corriera 14:00-20:00 NY =
   18:00-00:00 UTC, **fuera de market hours US**. Fix v2.1: cron en
   hora NY (08:00 NY daily y 10-16 NY intraday).

## Workflow 1 — Pipeline diario

**Fichero**: `n8n_daily_pipeline_workflow.json`

**Qué hace**: cada día laborable a las **08:00 NY** (= 12:00 UTC = 14:00 hora
España) llama `POST /api/refresh-async`. El servidor responde
`{ok: true, status: "started"}` en <1s y ejecuta el pipeline en background
(~45 min). n8n no espera al pipeline — solo confirma que arrancó.

**Cron**: `0 8 * * 1-5` (hora NY).

## Workflow 2 — Bridge intraday

**Fichero**: `n8n_bridge_intraday_workflow.json`

**Qué hace**: cada hora en punto entre las **10:00 y 16:00 NY** (= 14:00-20:00
UTC = 16:00-22:00 hora España), de lunes a viernes, llama
`POST /api/bridge-intraday?mode=execute&fetch=0`.

Son 7 disparos al día. El endpoint termina en 30-90s porque NO re-descarga
datos — solo evalúa las promovidas con los datos del cache del último pipeline
diario, y manda órdenes Alpaca paper si alguna dispara.

**Cron**: `0 10-16 * * 1-5` (hora NY).

## Cómo importarlos

### Pasos para cada workflow

1. n8n → menú **Workflows** → botón **Add Workflow**.
2. En el workflow vacío, esquina superior derecha → **Import from File**.
3. Selecciona el JSON correspondiente (`n8n_daily_pipeline_workflow.json` o
   `n8n_bridge_intraday_workflow.json`).
4. Verifica que aparecen los nodos. Click sobre el nodo HTTP y comprueba que
   la URL es `https://leonex.187.127.86.200.nip.io/...` (NO `{{$env...}}`).
5. Click sobre el nodo Cron y comprueba la **Expression** (debe ser
   `0 8 * * 1-5` para el daily, `0 10-16 * * 1-5` para el intraday). El
   campo **Timezone** debe ser `America/New_York` o quedar en default
   (la instancia n8n ya está en NY).
6. Botón **Save** → toggle **Active** (verde) → **Publish**.

### Primer disparo manual de prueba

Ya con el workflow activo, pulsa **Execute workflow** una vez para confirmar
que llama bien al server. Mira los nodos: deben quedar todos en verde.

- El Daily debe responder en <1s con `{ok: true, status: "started"}` y rama
  verde de "Log: pipeline lanzado".
- El Intraday debe responder en 30-90s con `{ok: true, mode: "execute", ...}`
  y rama verde de "Log success".

Si el HTTP queda en rojo, lee el mensaje. Errores conocidos:

- `access to env vars denied` → la URL todavía tiene `{{ $env.X }}`,
  edítala y hardcoded.
- `ENOTFOUND` o `ECONNREFUSED` → server caído o URL mal.
- `404 Not Found` → endpoint mal (verifica `/api/refresh-async` y
  `/api/bridge-intraday?mode=execute&fetch=0`).

## Si ya tienes los workflows importados (fix in-place, sin reimport)

Si los importaste antes del v2.1 y te están fallando con `access to env vars
denied`, no hace falta reimportar. Edita cada workflow así:

**Workflow Daily**:
1. Click sobre el nodo `POST /api/refresh-async` → campo **URL** →
   reemplaza por: `https://leonex.187.127.86.200.nip.io/api/refresh-async`
2. Click sobre el nodo Cron → campo **Expression** → reemplaza por:
   `0 8 * * 1-5`
3. Save → Publish.

**Workflow Intraday**:
1. Click sobre el nodo `POST /api/bridge-intraday...` → campo **URL** →
   reemplaza por: `https://leonex.187.127.86.200.nip.io/api/bridge-intraday?mode=execute&fetch=0`
2. Click sobre el nodo Cron → campo **Expression** → reemplaza por:
   `0 10-16 * * 1-5`
3. Save → Publish.

Después de cada uno, **Execute workflow** manualmente para verificar.

## Cambios vs versiones anteriores

| Aspecto | v1 (archivado) | v2 (roto) | v2.1 (actual) |
|---|---|---|---|
| Daily endpoint | `/api/refresh` (sync) | `/api/refresh-async` | `/api/refresh-async` |
| Daily cron expr | `0 12 * * 1-5` UTC | `0 12 * * 1-5` UTC | `0 8 * * 1-5` NY |
| Daily timezone real | UTC | NY (cron a las 16:00 UTC, fuera de hora) | NY (cron a las 12:00 UTC, correcto) |
| Intraday cron expr | `*/15 * * * *` | `0 14-20 * * 1-5` UTC | `0 10-16 * * 1-5` NY |
| Intraday timezone real | global | NY (18-00 UTC, fuera de market) | NY (14-20 UTC, dentro de market) |
| URL del HTTP node | `={{ $env.LEONEX_URL }}` | `={{ $env.LEONEX_URL }}` (rompe) | hardcoded |
| Intraday fetch param | `mode=execute` | `mode=execute&fetch=0` | `mode=execute&fetch=0` |
| Tiempo medio ejecución | 10 min+ | error 10-700ms | 30-90 s |

## Si necesitas re-descargar datos intraday manualmente

El bridge intraday ya no descarga. Para forzar una descarga incremental:

- Botón **"Re-download intraday (30m/15m/5m)"** del dashboard (sección Data
  Coverage), o
- `POST /api/bridge-intraday?mode=execute&fetch=1` (incluye descarga 1h/4h
  incremental, tarda ~3-8 min).

El cron diario completo se encarga de mantener los datos al día sin necesidad
de forzar descargas extra.
