# Handover — MSC Tracker Vercel + Supabase + VPS Worker

Fecha: 2026-06-08
Operador: Ignacio
Estado: producción en transición validable. El sistema viejo del VPS NO fue discontinuado.

## Resumen ejecutivo

MSC Tracker quedó migrado a una arquitectura donde el producto principal vive en Vercel + Supabase, y el VPS queda como worker/scraper estable. El contrato externo es API/worker on-demand: el consumidor guarda sus propios datos.

- App principal: `https://msc-vercel-supabase.vercel.app`
- Base API externa: `https://msc-vercel-supabase.vercel.app/msc-api`
- API + Explorer humano: `https://msc-vercel-supabase.vercel.app/msc-api/docs/api`
- OpenAPI estándar: `https://msc-vercel-supabase.vercel.app/openapi.json`
- Supabase project: `msc-tracker`, ref `qcabllkblcwofkeuhdvd`, region `sa-east-1`
- Código Vercel: `/home/ubuntu/msc-vercel-supabase`
- Worker VPS: `worker.service` en `127.0.0.1:9090`
- API legacy/bridge VPS: `api.service` en `127.0.0.1:9091`; expuesto públicamente solo bajo Tailscale Funnel `/msc-worker-bridge`
- Limpieza screenshots VPS: `cleanup-msc-shots.timer`, TTL 15 días

## Objetivo funcional para Andres / CRON PHP / IA

Andres o su IA NO deben usar Tailscale ni el hostname de la tailnet. Deben usar la API pública HTTPS de Vercel. Cada llamada a `/track/{container}` ejecuta el worker; Andrés decide frecuencia y persistencia:

```text
GET https://msc-vercel-supabase.vercel.app/msc-api/track/{container}
Header: X-Api-Key: msc_...
```

Para que una IA/agente entienda la API:

```text
GET https://msc-vercel-supabase.vercel.app/msc-api/llm/context
Header: X-Api-Key: msc_...
```

La API externa usa exclusivamente tokens `msc_...` en header `X-Api-Key`. No usar credenciales web para scripts.


## Decisión de producto actual — API/worker on-demand

La decisión vigente es que MSC Tracker para Andrés/CRON PHP/IA NO es un sistema de almacenamiento histórico ni scheduler de negocio.

Nosotros ofrecemos:

```text
API pública estable + worker/scraper MSC
```

El consumidor externo decide:

- cada cuánto llama;
- qué containers consulta;
- dónde y cómo guarda JSON/screenshot/histórico;
- política de reintentos.

Contrato principal:

```text
GET /msc-api/track/{container}
Header: X-Api-Key: msc_...
```

Cada hit a `/track/{container}` intenta una consulta nueva vía worker. No es cache/latest.

La UI/panel, `containers.last_data` y `track_history` son herramientas internas/admin/debug. No son contrato público para Andrés ni reemplazan su base de datos. Analytics conserva screenshot solo para los últimos hits por container y poda screenshots viejos.

## Arquitectura actual

```text
Usuario admin / Ignacio
  -> https://msc-vercel-supabase.vercel.app/msc-api/login
  -> Vercel UI/API
  -> Supabase Postgres
  -> VPS API bridge via `/msc-worker-bridge`
  -> VPS worker Camoufox
  -> MSC website/API

Andres / CRON PHP / IA
  -> https://msc-vercel-supabase.vercel.app/msc-api/track/{container}
  -> X-Api-Key: msc_...
  -> Vercel API
  -> Supabase token validation + history
  -> VPS API bridge via `/msc-worker-bridge`
  -> VPS worker Camoufox
```

### Vercel

Proyecto Vercel: `msc-vercel-supabase`.

Código:

```text
/home/ubuntu/msc-vercel-supabase/api/app.js
/home/ubuntu/msc-vercel-supabase/public/static/index.html
/home/ubuntu/msc-vercel-supabase/public/static/users.html
/home/ubuntu/msc-vercel-supabase/public/static/login.html
/home/ubuntu/msc-vercel-supabase/public/static/app.js
/home/ubuntu/msc-vercel-supabase/public/static/style-vps2.css
/home/ubuntu/msc-vercel-supabase/public/static/doc/*.html
/home/ubuntu/msc-vercel-supabase/vercel.json
```

Notas:

- `style-vps2.css` es el CSS activo.
- Se eliminó el experimento glass/navpill del build activo.
- Se mantiene UX de navegación con prefetch/transición ligera en `public/static/app.js`.
- El look & feel volvió al estilo VPS/terminal limpio.

### Supabase

Project ref: `qcabllkblcwofkeuhdvd`.

Tablas principales:

```text
app_users       users web/admin con password bcrypt
sessions        sesiones web por cookie
api_tokens      tokens msc_... hasheados, scopes track/context/ops según uso
containers      lista persistente + último run completo
track_requests  log liviano de requests/status/error
track_history   historial JSON-only, sin screenshots base64
```

RLS está habilitado. La app Vercel usa `service_role` server-side. No exponer service role al cliente.


### Exposición Tailscale/Funnel actual

La ruta pública vieja `https://miopenclaw-vnic.tail9799d2.ts.net/msc-api` fue removida del Funnel. Ya no debe usarse ni estar pública.

La ruta que queda para Vercel → VPS es:

```text
https://miopenclaw-vnic.tail9799d2.ts.net/msc-worker-bridge
```

Esa ruta requiere API token interno en `X-Api-Key`; endpoints bridge de logs/status requieren scope `ops`, y track requiere scope `track`.

### VPS

Servicios:

```bash
sudo systemctl status api.service
sudo systemctl status worker.service
sudo systemctl status nginx.service
sudo systemctl status cleanup-msc-shots.timer
```

- `worker.service`: scraper Camoufox real.
- `api.service`: API legacy + bridge interno usado por Vercel.
- `nginx.service`: sigue activo por compat interna/local.
- `cleanup-msc-shots.timer`: cleanup diario de `/home/ubuntu/msc-navigator/site/shots`, TTL 15 días.

## Secretos

No guardar secretos en handover/docs.

Bitwarden items relevantes:

```text
Vercel + Supabase Tokens
MSC Tracker Supabase Vercel
```

`MSC Tracker Supabase Vercel` contiene, entre otros:

- Supabase URL
- Supabase anon key
- Supabase service role key
- Vercel URL
- Worker bridge base
- Worker bridge key

No imprimir valores.

## API externa esperada

### Track container

```text
GET /msc-api/track/{container}
Header: X-Api-Key: msc_...
Scope requerido: track
```

Ejemplo:

```bash
curl -s \
  -H "X-Api-Key: msc_TU_TOKEN" \
  https://msc-vercel-supabase.vercel.app/msc-api/track/MEDU9730548
```

Respuesta esperada:

```json
{
  "json": {
    "container": "MEDU9730548",
    "events": []
  },
  "screenshot": "base64 o null",
  "screenshot_format": "png"
}
```

El screenshot puede ser `null` si el worker responde desde cache o no lo produce.

### LLM context

```text
GET /msc-api/llm/context
Header: X-Api-Key: msc_...
Scope requerido: context
```

Devuelve metadata machine-readable para que IA/agentes sepan cómo usar la API on-demand.

### OpenAPI / API Explorer

```text
GET /openapi.json
GET /msc-api/docs/api
```

`/docs/api` contiene la referencia y el API Explorer inline; `/docs/api` solo redirige allí. Permite:

- cargar API key en sessionStorage del navegador;
- elegir endpoint;
- completar path params;
- ver query/filtros disponibles;
- generar URL/cURL;
- ejecutar request;
- ver status, duración y response.

## Política de screenshots y storage

Decisión actual:

- `containers.last_data`: guarda el último run completo del container, incluyendo `screenshot` base64 si vino.
- `track_history`: historial técnico interno. Conserva screenshots de los últimos hits por container para analytics/debug; screenshots más viejos se reemplazan por marcador:

```json
"screenshot": "[omitted: screenshot stored only in containers.last_data]"
```

- No se guarda historial infinito de screenshots; solo últimos hits por container para analytics/debug.
- Screenshot = solo último run por container.
- No se guardan screenshots en Supabase Storage por ahora.
- No se persisten automáticamente archivos de screenshot para este flujo Vercel.
- Cleanup de `/home/ubuntu/msc-navigator/site/shots` sigue activo, aunque ese directorio pertenece a otro flujo/navigator.

Implicancia: storage queda razonable porque solo hay un screenshot base64 por container persistente. Si en el futuro hay cientos/miles de containers o screenshots muy grandes, migrar a storage con URL/TTL.

## Panel principal esperado

URL:

```text
https://msc-vercel-supabase.vercel.app/msc-api/
```

Requiere login web por cookie.

Secciones esperadas:

1. Panel interno
2. Probar worker on-demand
3. Worker

### Containers

- Al cargar, muestra loader `Cargando containers...`.
- Status `200` se muestra como `OK` verde, no `200` rojo.
- Frecuencia de actualización se configura con input numérico:

```text
Frecuencia [360] min
```

- Valores permitidos: 5 a 1440 minutos.
- Botón `Trackear ahora` guarda último run completo en `containers.last_data` y registra historial JSON-only en `track_history`.
- Botón `Ver data` abre modal.
- En modal:
  - resumen usa `last_data.json` si existe;
  - `Ver JSON completo` muestra `last_data` entero, incluyendo screenshot si vino;
  - `Ver screenshot` muestra SOLO el screenshot del último run guardado;
  - `Ver screenshot` NO debe llamar a MSC ni disparar `/api/track`;
  - si no hay screenshot en último run, debe decir que no hay screenshot guardado y pedir usar `Trackear ahora`.

### Track rápido

- Usa sesión web/cookie, NO API key de Andres.
- Endpoint interno:

```text
POST /msc-api/api/track
```

- Llama worker bridge y devuelve response completo al usuario.
- Registra `track_history` JSON-only.

### Worker

- Al cargar, muestra loader `Chequeando worker...`.
- `worker: OK/DOWN` debe reflejar status real vía bridge.
- Botón se llama `Ver logs`, no `Ver logs filtrados`.
- Debe mostrar logs reales de `worker.service` obtenidos desde VPS bridge:

```text
GET VPS /bridge/worker/logs
```

- No debe mostrar placeholders tipo `Vercel app activa...`.

## Docs visibles esperadas

URL principal:

```text
/msc-api/docs
```

Cards esperados:

- CRON PHP
- API Explorer
- Referencia de API
- Errores y troubleshooting
- Operación y estructura

No debe aparecer:

- `Para empezar`
- `Help center (nueva tab)`
- `Para Andres`
- `API admin (JSON)`
- secciones `Deshabilitados`
- menciones visibles a `magic-login`, `handover.md`, `llms.txt`, `llms-full.txt`
- `SwaggerUI`
- `msc-public-proxy`

### CRON PHP

URL correcta:

```text
/msc-api/docs/cron-php
```

Alias viejo permitido por compatibilidad:

```text
/msc-api/docs/andres
```

La UI ya no debe linkear a `/docs/andres`.

Contenido debe explicar:

- endpoint `/track/{container}`;
- PHP/curl example;
- delay/frecuencia recomendada;
- que la frecuencia del cron se configura afuera;
- para varios containers, usar delay random entre llamadas;
- ejemplo `sleep(random_int(20, 90));`.

`/msc-api/docs/start` debe devolver 404.

## Cambios principales realizados durante esta sesión

1. Se creó app Vercel `msc-vercel-supabase`.
2. Se creó Supabase project `msc-tracker` (`qcabllkblcwofkeuhdvd`).
3. Se creó schema Supabase: `app_users`, `sessions`, `api_tokens`, `containers`, `track_requests`, `track_history`.
4. Se migraron users y containers desde SQLite/state del VPS a Supabase.
5. Se mantuvo el worker del VPS como scraper estable.
6. Se creó bridge token interno para Vercel → VPS.
7. Se extendió bridge del VPS con status/logs reales del worker bajo scope `ops`.
8. Se portó el frontend real del VPS a Vercel.
9. Se descartó una UI mínima inicial porque no respetaba look & feel del VPS.
10. Se probó un tema glass inspirado en Aave y luego se hizo rollback visual al look VPS.
11. Se mantuvieron mejoras UX: prefetch y transición suave entre páginas.
12. Se arreglaron márgenes del panel principal (`.wrap`).
13. Se reemplazó dropdown de frecuencia por input numérico con indicador `min`.
14. Se corrigió status `200` string para que se muestre `OK` verde.
15. Se creó API Explorer integrado en `/docs/api`; se eliminó Swagger UI y el standalone redundante de la ruta humana.
16. Se creó y mantuvo OpenAPI JSON en `/openapi.json`.
17. Se formalizó política de screenshot: último run completo en `containers.last_data`, historial sin screenshot.
18. Se corrigió `Ver screenshot` para NO llamar a MSC; solo muestra último screenshot guardado.
19. Se corrigió Worker logs para mostrar logs reales, no placeholder.
20. Se eliminaron docs/links/textos obsoletos (`Para empezar`, disabled endpoints, proxy viejo, etc.).
21. Se limpió el repo Vercel de CSS experimentales no referenciados.
22. Se mantuvieron activos `api.service`, `worker.service`, `nginx.service`, `cleanup-msc-shots.timer`.

## Validaciones ya realizadas

No reemplazan al E2E final, pero se hicieron durante implementación:

- Vercel login responde 200.
- `/msc-api/` sin sesión redirige a login.
- Panel autenticado responde 200.
- Users autenticado admin responde 200.
- Containers API responde 200 con cookie.
- `/msc-api/track/...` sin token responde 401.
- `/msc-api/llm/context` sin token responde 401.
- `/openapi.json` responde 200.
- `/msc-api/docs/api` responde 200 autenticado y muestra referencia + API Explorer inline; `/msc-api/docs/api` redirige allí.
- `/msc-api/docs/start` responde 404.
- `/msc-api/docs` no contiene `Para empezar`.
- Worker bridge logs reales responde 200.
- Servicios VPS activos.
- Tokens temporales de auditoría fueron borrados.
- Grep local/remoto no encontró textos obsoletos principales luego de limpieza.

## Checklist E2E para el próximo agente

El próximo agente debe ejecutar esta revisión de punta a punta. No asumir que está OK por este handover.

### 0. Preflight

1. Leer este handover completo.
2. Confirmar servicios VPS:

```bash
systemctl is-active api.service worker.service nginx.service cleanup-msc-shots.timer
```

3. Confirmar Vercel app responde:

```bash
curl -I https://msc-vercel-supabase.vercel.app/msc-api/login
curl -I https://msc-vercel-supabase.vercel.app/openapi.json
```

4. Confirmar Supabase project accesible con token de Bitwarden, sin imprimir secretos.

### 1. Login/panel web

1. Entrar a `/msc-api/login`.
2. Login con credenciales admin reales de Ignacio.
3. Ver panel principal.
4. Confirmar loaders aparecen brevemente para containers/worker.
5. Confirmar containers listan sin layout roto.
6. Confirmar frecuencia muestra `Frecuencia [n] min`.
7. Confirmar no hay `Para empezar` en Docs.
8. Confirmar `Docs` → cards esperados.
9. Confirmar `Usuarios` carga users/tokens.
10. Confirmar cambio de password modal existe pero no probar sin autorización explícita de Ignacio.

### 2. Containers

1. Crear container de prueba válido si Ignacio lo autoriza.
2. Probar frecuencia con valor inválido (<5 o >1440), debe rechazar.
3. Probar frecuencia válida.
4. Ejecutar `Trackear ahora` en un container autorizado.
5. Confirmar status `OK` verde.
6. Abrir `Ver data`.
7. Confirmar resumen correcto.
8. Abrir `Ver JSON completo`.
9. Confirmar si hubo screenshot, aparece dentro del JSON completo como `screenshot` y `screenshot_format`.
10. Click `Ver screenshot`.
11. Confirmar NO dispara nuevo track ni muestra wording `generado ahora`.
12. Confirmar muestra último screenshot guardado o mensaje claro de no disponible.
13. Confirmar `track_history` guardó data sin screenshot base64.
14. Confirmar `containers.last_data` guardó último run completo.

### 3. Track rápido

1. Usar `Track rápido` con un container válido.
2. Confirmar usa sesión web y NO pide API key.
3. Confirmar devuelve JSON completo al panel.
4. Confirmar registra `track_history` JSON-only.

### 4. Worker

1. Confirmar `worker: OK`.
2. Click `Ver logs`.
3. Confirmar logs reales de `worker.service`.
4. Confirmar no aparece placeholder `Vercel app activa`.
5. Confirmar no dice `logs filtrados`.

### 5. API tokens

1. Desde `/users`, crear token temporal para user no-admin o user de prueba con scope `track,context`.
2. Copiar token una sola vez.
3. Probar `/msc-api/docs/api` con el token.
4. Probar `/llm/context`, debe 200.
5. Probar `/track/{container}`, debe 200 o error real del worker/MSC, no auth error.
6. Revocar token.
7. Confirmar el mismo token revocado devuelve 401.
8. Borrar usuario/token de prueba.

### 6. API Explorer

1. Entrar a `/msc-api/docs/api`.
2. Pegar API key.
3. Click `Usar key`; status debe cambiar a `API key cargada`.
4. Seleccionar `Track container`.
5. Ver path params y query/filtros.
6. Ejecutar endpoint.
7. Confirmar muestra status HTTP, duración, response.
8. Ver cURL generado.
9. Ver schema.
10. Seleccionar `LLM context` y ejecutar.
11. Confirmar no hay Swagger UI feo ni JS roto.

### 7. OpenAPI

1. `GET /openapi.json` debe devolver OpenAPI 3.1.
2. Debe incluir security scheme `ApiKeyAuth` header `X-Api-Key`.
3. Debe incluir paths `/track/{container}` y `/llm/context`.
4. Debe documentar screenshot base64 opcional.

### 8. Docs

1. `/msc-api/docs` no debe mostrar `Para empezar`.
2. `/msc-api/docs/start` debe 404.
3. `/msc-api/docs/cron-php` debe 200.
4. `/msc-api/docs/andres` puede 200 como alias legacy, pero la UI no debe linkearlo.
5. CRON PHP debe explicar delay/frecuencia.
6. No debe haber secciones `Deshabilitados`.
7. No debe haber menciones visibles a magic-login/handover/llms.
8. No debe haber URLs viejas `msc-public-proxy`.

### 9. Storage

1. Verificar conteos/tamaños aproximados en Supabase.
2. Confirmar `track_history.data` no contiene screenshots base64 reales.
3. Confirmar `containers.last_data` puede contener solo último screenshot.
4. Confirmar no se guarda historial de screenshots.
5. Confirmar `cleanup-msc-shots.timer` sigue activo en VPS.

### 10. Seguridad

1. API externa solo por `X-Api-Key`.
2. Scripts externos no usan login/password.
3. Service role Supabase no aparece en repo ni HTML.
4. Worker bridge key no aparece en HTML.
5. Endpoints bridge VPS requieren scope `ops`.
6. `/users` requiere admin cookie.
7. Tokens se guardan hasheados.
8. Revocación funciona.

### 11. Textos/UX final

Hacer grep local y, si es posible, remoto autenticado para asegurar que no aparezcan:

```text
Para empezar
Help center (nueva tab)
Para Andres
API admin (JSON)
SwaggerUI
msc-public-proxy
Screenshot generado ahora
No se guarda en Supabase
Ver logs filtrados
logs filtrados
healthchecks ocultos
Vercel app activa
/docs/start
magic-login
llms.txt
handover.md
```

### 12. Rollback

Si algo crítico falla:

- App vieja VPS sigue activa detrás de `api.service`.
- No borrar Supabase ni Vercel hasta validar con Ignacio.
- Revertir deploy Vercel desde dashboard o `vercel rollback` si corresponde.
- No tocar worker salvo necesidad.

## Comandos útiles

### Vercel deploy

```bash
cd /home/ubuntu/msc-vercel-supabase
# obtener VERCEL_TOKEN desde Bitwarden item "Vercel + Supabase Tokens"
vercel --token "$VERCEL_TOKEN" --prod --yes --force
```

### Validación rápida

```bash
curl -s -o /dev/null -w 'login=%{http_code}\n' https://msc-vercel-supabase.vercel.app/msc-api/login
curl -s -o /dev/null -w 'openapi=%{http_code}\n' https://msc-vercel-supabase.vercel.app/openapi.json
curl -s -o /dev/null -w 'api_docs=%{http_code}\n' -L https://msc-vercel-supabase.vercel.app/msc-api/docs/api
```

### VPS logs

```bash
sudo journalctl -u api.service -n 50 --no-pager
sudo journalctl -u worker.service -n 80 --no-pager
```

### Worker health

```bash
curl -s http://127.0.0.1:9090/health
```

## Pendientes recomendados

1. Ejecutar checklist E2E completo con Ignacio.
2. Crear token definitivo para CRON PHP/IA cuando Ignacio lo decida.
3. No agregar scheduler/polling automático para Andrés salvo nueva decisión explícita: el consumidor externo decide frecuencia y guarda sus datos.
4. Considerar storage externo con TTL si screenshots crecen demasiado.
5. Considerar alerting para worker/API bridge.
6. Considerar rate limit explícito en Vercel API por token/IP.
