Saltar al contenido principal

Plan de implementación — Optrax V-Station

Documento técnico de implementación
Módulos de visualización en vivo, grabación continua 24/7, retención y evidencia legal

CampoValor
Versión2.0
FechaSeptiembre 2026
Productovstation (Optrax V-Station)
Referencia anteriorPlan Miraflores v1.0 (Jun 2026)
Estado arquitecturaProducción validada en vstation.optrax.io

Índice​

  1. Propósito y alcance
  2. Arquitectura general (v2)
  3. Estándares transversales
  4. Módulo A — Visualización en vivo
  5. Módulo B — Grabación continua
  6. Retención, ILM y evidencia legal
  7. Playback y operación en UI
  8. Integración Optrax / Core
  9. Despliegue e infraestructura
  10. Plan de implementación por fases
  11. Pruebas de aceptación
  12. Operación, monitoreo y mantenimiento
  13. Cambios respecto al plan v1.0
  14. Riesgos y mitigaciones
  15. Requerimientos de hardware y dimensionamiento
  16. Anexos

1. Propósito y alcance​

Este documento define la implementación técnica de vstation como plataforma de videovigilancia Optrax, actualizada a la arquitectura v2 desplegada en producción.

Alcance funcional​

MóduloDescripción
A — Visualización en vivoOperadores consultan cámaras RTSP en navegador vía WebRTC (LiveKit). Ingesta on-demand (solo cámaras visibles).
B — Grabación continuaMediaMTX hace pull RTSP directo desde cámaras/NVR, segmenta y sube a MinIO. vstation orquesta paths, retención e ILM.
Retención y evidenciaPolítica por grupo (retention_days), expiración ILM en MinIO, retención legal por objeto (Object Lock).
PlaybackRevisión de segmentos desde MinIO en la UI (timeline, menú de fragmentos, filtro por fecha).
TransversalAuth JWT, roles, salud de cámaras (gst-discoverer), integración Optrax Core, mapas, eventos temporales.

Fuera de alcance​

  • Planos eléctricos, rack físico y direccionamiento IP definitivo del cliente.
  • Sustitución del clúster MinIO distribuido de Miraflores (este documento describe la lógica de software; el dimensionamiento físico sigue en docs/capacity-recommendations.md).

2. Arquitectura general (v2)​

Principio rector​

Dos pipelines independientes — vivo ≠ grabación — sin duplicar tráfico ni procesos ffmpeg innecesarios en vstation.

┌──────────────────────────────────────────────────────────────────────────────┐
│ MÓDULO A — VIVO (on-demand) │
│ │
│ Cámara RTSP ──► GStreamer WHIP (vstation) ──► LiveKit SFU ──► Navegador │
│ solo cuando alguien abre la cámara │
└──────────────────────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────────────────────┐
│ MÓDULO B — GRABACIÓN (24/7, modo pull) │
│ │
│ Cámara RTSP ──► MediaMTX (pull path ingest/camera-{id}) │
│ │ record + hook minio.sh │
│ ▼ │
│ MinIO bucket vstation-lock (Object Lock) │
│ ▲ │
│ vstation ── API MediaMTX :9997 (alta/baja paths) │
│ ── ILM lifecycle rules por camera_groups.retention_days │
│ ── playback proxy / legal hold / purge workers │
└──────────────────────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────────────────────┐
│ PLANO DE CONTROL — vstation-app │
│ React + Express + PostgreSQL + Redis │ JWT │ workers de grabación/retención │
└──────────────────────────────────────────────────────────────────────────────┘

Componentes y roles​

ComponenteRolUbicación típica
vstation-appUI, API, ingesta WHIP, orquestación grabación, playback, ILMDroplet app (Docker)
PostgreSQLInventario cámaras, grupos, usuarios, permisos, metadataMismo droplet / HA
RedisEventos temporales de cámaras (1 h retención)Mismo droplet
LiveKitSFU WebRTC + WHIP IngressServidor dedicado o cloud
MediaMTXPull RTSP + grabación por segmentosServidor dedicado (ej. :8554 RTSP, :9997 API)
MinIOObject storage vstation-lock con Object LockServidor dedicado (:9000)
MartinMapas vectoriales self-hosted (Perú .mbtiles)Contenedor en droplet app
Nginx + CertbotTLS, reverse proxy, /api, WebSocket LiveKitDroplet app

Lo que ya NO forma parte de la arquitectura v2​

Eliminado / obsoletoSustituto
4 servidores "Optrax Recorder" con ffmpeg por 200 cámarasMediaMTX pull directo (0 ffmpeg en vstation)
Relay ffmpeg vstation → MediaMTX (VSTATION_RECORDING_MODE=relay)Modo pull (legacy solo si se exige)
Snapshots JPEG por cámara (GET /api/cameras/:id/snapshot)Eliminado
ffprobe como detector principal de codec/saludgst-discoverer-1.0 (+ fallback ffprobe)
Grabación vía egress LiveKit a MinIOPull MediaMTX → MinIO
Edición manual de mediamtx.yml por cada cámaraAPI MediaMTX sincronizada desde vstation

3. Estándares transversales​

Red y seguridad​

vstation no configura VLANs. La segmentación es infraestructura (switch/firewall del cliente o cloud). El software solo necesita conectividad IP + puertos abiertos según §3.1 y §15.0.2.

  • Firewall (obligatorio en todos los despliegues): API MediaMTX :9997 solo desde vstation; MinIO :9000 solo desde MediaMTX + vstation; PostgreSQL/Redis no expuestos a internet.
  • TLS: Certificados Let's Encrypt vía Certbot en Nginx (vstation.optrax.io).
  • Auth: JWT local + Auth Gateway / Keycloak (AUTH_GATEWAY_URL, auth.optrax.io); sesiones Express legacy; permisos granulares (view_camera, edit_camera, etc.). Al cambiar rol de usuario se resetean permisos al set del rol (resetUserPermissionsToRole).
  • Embed / integraciones: rutas /embed/ (iframe Patrol-Master, CSP en Nginx); API interna INTERNAL_SERVICE_KEY + /api/internal/cameras.
  • Telemetría: OpenTelemetry opcional (OTEL_* en docker-compose.yml).

3.1 Segmentación de red — qué ya está y qué falta​

ModeloEstadoCómo se segmentaAcción en vstation
Optrax cloud (prod actual)ImplementadoDroplets separados (app, LiveKit, MediaMTX, MinIO) + Cloud Firewall DO; red Docker interna vstation-net (app ↔ PG ↔ Redis ↔ Martin en el mismo host)Ninguna. Solo mantener reglas firewall y URLs en .env / nginx/conf.d/app.conf
On-prem / Miraflores (800+ cámaras)Depende del clienteVLANs L2 en core switch municipal (si ya existen del plan v1.0, reutilizar)Ninguna en software. Validar rutas RTSP y firewall entre VLANs

Producción Optrax actual (referencia — ya operativa):

ServicioHostSegmentación
vstation + Nginx64.23.135.236Público HTTPS :443; proxy a LiveKit y MinIO
LiveKit SFU146.190.159.52WebRTC vía /livekit-ws/ en Nginx
MediaMTX161.35.100.138API :9997; pull RTSP desde cámaras
MinIO146.190.47.200S3 :9000; acceso restringido por firewall

No hay VLAN L2 entre estos nodos: la separación es por servidor + reglas de firewall. Eso cumple el objetivo de aislar grabación, vivo y almacenamiento sin configurar VLANs en la app.

3.2 Implementación recomendada de VLANs (on-prem / Miraflores)​

Diseño óptimo para vstation v2: 5 VLANs + firewall L3 central. Aísla cámaras, grabación 24/7, vivo WebRTC, almacenamiento y operadores. Alineado con los dos pipelines independientes (§2) y con el dimensionamiento §15.

Si Miraflores ya tiene VLAN de videovigilancia (plan v1.0): conservar IDs/subredes existentes y mapear los roles de la tabla siguiente; no rediseñar L2. Solo añadir ACLs de §3.2.4.

Optrax cloud: no usar VLANs L2; el equivalente es §3.2.6 (VPC + Cloud Firewall).

3.2.1 Topología lógica​
┌─────────────────────────────────────────┐
VLAN 50 Operadores │ PCs videowall / estaciones de mando │
(Corp) │ Solo HTTPS :443 → vstation │
└──────────────────┬──────────────────────┘
│
┌──────────────────▼──────────────────────┐
│ Firewall L3 (pfSense / FortiGate) │
│ Único punto de routing inter-VLAN │
└──┬─────────┬─────────┬─────────┬────────┘
│ │ │ │
VLAN 21 App │ VLAN 22│ VLAN 20 │ VLAN 30 │
vstation+Nginx │ LiveKit │ MediaMTX│ MinIO │
WHIP + API │ SFU │ pull │ S3 │
│ │ │ │
└────┬────┴────┬────┘ │
│ │ │
│ RTSP │ S3 :9000 │
│ :554 │ │
┌───────▼─────────▼──────────────▼───────┐
VLAN 10 Cámaras/NVR │ 800 cámaras / NVR / switches PoE │
│ Sin internet · Sin acceso operadores │
└───────────────────────────────────────┘

VLAN 40 Gestión SSH, SNMP, cadvisor — solo desde jump host / VPN admin

Principio: ningún operador habla RTSP con cámaras. Todo pasa por vstation/LiveKit (vivo) o MediaMTX (grabación).

3.2.2 Tabla de VLANs (referencia Miraflores 800 cámaras)​
VLANNombreIDCIDR ejemploGatewayServicios / hosts
10VID-CAM1010.50.10.0/23.1Cámaras IP, NVR, switches PoE acceso. Estática/DHCP reservado por MAC
20VID-REC2010.50.20.0/24.1MediaMTX (pull 24/7). 1–2 servidores según §15.9
21VID-APP2110.50.21.0/24.1vstation (app, PG, Redis, Nginx). WHIP + orquestación MTX/MinIO
22VID-LIVE2210.50.22.0/24.1LiveKit SFU (WebRTC). Separado para escalar vivo sin tocar grabación
30VID-STO3010.50.30.0/24.1MinIO + backup. Sin ruta default a internet
40VID-MGMT4010.50.40.0/24.1Jump host, monitoreo, logs. Acceso admin vía VPN
50CORP-OPS(existente)Red municipal—Operadores. Solo :443 → VIP vstation en VLAN 21

Por qué 5 VLANs de vídeo y no 3: separar MediaMTX (20) de LiveKit (22) evita que picos de WebRTC (20 operadores × 10 tiles, §15.4) compitan con 800 Mbps sostenidos de grabación. MinIO (30) aislado impide exfiltración directa de evidencia.

3.2.3 Colocación de servidores y direccionamiento​
ServidorVLANIP ejemploRol
MediaMTX-012010.50.20.10Pull RTSP desde VLAN 10; upload segmentos → MinIO
vstation-012110.50.21.10UI, API, GStreamer WHIP, workers ILM
LiveKit-012210.50.22.10SFU; WHIP ingress desde vstation
MinIO-013010.50.30.10Bucket vstation-lock
Nginx VIP / DNS2110.50.21.20 o FQDN internoTLS terminado; proxy /livekit-ws/, /minio-proxy/

.env on-prem (IPs privadas):

MEDIAMTX_API_URL=http://10.50.20.10:9997
MEDIAMTX_MINIO_ENDPOINT=http://10.50.30.10:9000
LIVEKIT_URL=wss://vstation.municipio.local/livekit-ws/
LIVEKIT_API_URL=https://10.50.22.10
VSTATION_STUN_SERVER= # vacío — LiveKit en misma red privada
3.2.4 Matriz ACL (firewall inter-VLAN) — mínimo necesario​

Denegar todo por defecto; permitir solo lo siguiente:

OrigenDestinoPuerto(s)ProtocoloMotivo
VLAN 10 (cámaras)VLAN 20 (MTX)554TCPGrabación pull MediaMTX
VLAN 10 (cámaras)VLAN 21 (vstation)554TCPWHIP on-demand + gst-discoverer health
VLAN 21 (vstation)VLAN 20 (MTX)9997TCPAPI alta/baja paths grabación
VLAN 20 (MTX)VLAN 30 (MinIO)9000TCPUpload segmentos minio.sh
VLAN 21 (vstation)VLAN 30 (MinIO)9000TCPILM, legal hold, playback, purge
VLAN 21 (vstation)VLAN 22 (LiveKit)7880, 7881, UDP 50000-60000TCP/UDPWHIP + API LiveKit
VLAN 50 (operadores)VLAN 21 (vstation)443TCPUI HTTPS
VLAN 50 (operadores)VLAN 22 (LiveKit)UDP 50000-60000, TCP 443UDP/TCPWebRTC media (si no se tunela todo por Nginx)
VLAN 40 (mgmt)VLAN 20,21,22,3022TCPSSH admin
DenegarVLAN 10 ↔ VLAN 50**Cámaras ↔ operadores
DenegarVLAN 10 → VLAN 30**Cámaras ↔ MinIO
DenegarVLAN 50 → VLAN 30**Operadores ↔ almacenamiento directo
DenegarInternet → VLAN 10,20,30**Superficie de ataque

Puertos validados por tests unitarios (streamConnectivity.test.ts): RTSP 554, API MTX 9997, MinIO 9000, HTTPS 443 (§15.0.2).

3.2.5 QoS y capacidad de enlace​

Para 800 cámaras @ 1000 kbps (§15.9):

EnlaceAncho mínimoQoS recomendado
VLAN 10 → VLAN 20 (grabación)≥ 2 Gbps agregadoCola strict priority RTSP; DSCP AF41 en switches capa 3
VLAN 21 ↔ VLAN 22 (vivo pico)1 GbpsBest-effort o AF31; vivo es on-demand
VLAN 20/21 → VLAN 30 (MinIO)≥ 2 GbpsPrioridad escritura; NVMe en MinIO
VLAN 50 → VLAN 21 (operadores)100 Mbps agregadoSuficiente para 20×10 tiles WebRTC (~300 Mbps pico, §15.4)

Doble pull: cámaras en vivo + grabación reciben 2 sesiones RTSP desde VLAN 21 y 20. Dimensionar VLAN 10 con ~20 % headroom sobre bitrate nominal.

3.2.6 Equivalente cloud (Optrax — ya implementado)​

En DigitalOcean no hay VLAN L2; la misma lógica se logra así:

Rol VLAN on-premEquivalente Optrax prod
VID-APP (21)Droplet 64.23.135.236 + vstation-net Docker
VID-LIVE (22)Droplet LiveKit 146.190.159.52
VID-REC (20)Droplet MediaMTX 161.35.100.138
VID-STO (30)Droplet MinIO 146.190.47.200
Firewall L3Cloud Firewall por droplet (reglas §3.2.4)
VPC privado (opcional mejora)Mover tráfico MTX↔MinIO↔vstation a VPC private IP y cerrar :9000/:9997 en IP pública
3.2.7 Plan de implementación por fases (red)​
FaseTareaValidación
R0Crear VLANs 10,20,21,22,30,40 en core switch; routing solo en firewallping inter-VLAN bloqueado excepto reglas ACL
R1Desplegar MinIO (30) + MediaMTX (20); abrir 10→20:554, 20→30:9000npm run recording:smoke desde jump host en VLAN 21
R2Desplegar vstation (21) + LiveKit (22); abrir ACLs §3.2.4npm test + vivo WHIP 5 cámaras piloto
R3Conectar VLAN 10 (cámaras); import Optrax 800 cámarasPaths MTX activos = cámaras con recordingEnabled
R4Abrir VLAN 50 → 21:443 para operadoresPruebas aceptación §11 (A1–B5)
R5QoS en enlaces troncales hacia NVR; monitoreo cadvisorSin pérdida segmentos 24 h bajo carga
3.2.8 Checklist rápido​
  • Cámaras sin gateway a internet (VLAN 10)
  • MinIO sin IP pública; Object Lock activo
  • MediaMTX API :9997 no expuesta a operadores ni cámaras
  • VSTATION_STUN_SERVER vacío si LiveKit en VLAN 22 alcanzable desde vstation
  • NTP (chrony) sincronizado en VLAN 20,21,22,30
  • Documentar mapa IP → servicio para operaciones

3.3 Red Docker local (nodo app — ya implementado)​

En docker-compose.yml, los contenedores del mismo droplet comparten vstation-net. No sustituye VLANs entre servidores físicos; solo aísla PG/Redis del host.

Tiempo y nomenclatura​

  • NTP/chrony en todos los nodos (critical para correlación eventos ↔ segmentos).
  • Prefijo MinIO: recordings/camera-{id}/{yyyy}/{mm}/{dd}/segmento.mp4
  • Path MediaMTX grabación: ingest/camera-{id}

Contenedores Docker (producción)​

Servicios en docker-compose.yml:

ServicioImagen / buildPuerto
vstation-appBuild local (Dockerfile multi-stage + GStreamer WHIP)5001
vstation-dbpostgres:16-alpine5432
vstation-redisredis:7-alpine6379
vstation-martinghcr.io/maplibre/martin3000
optrax_nginxreverse proxy443
Observabilidadcadvisor, node-exporter, promtail, postgres-exportervarios

4. Módulo A — Visualización en vivo​

Flujo técnico​

Operador abre cámara en UI
→ vstation solicita token LiveKit
→ startRtspIngestForCamera (si no hay ingesta activa)
→ GStreamer: rtspsrc → depay → parse → pay → whipsink
→ LiveKit WHIP Ingress → room camera-{id}
→ Cliente WebRTC (livekit-client) reproduce en <video>
Operador cierra / desmonta tile
→ stopRtspIngestForCamera + desconexión sala

Configuración recomendada​

VSTATION_INGEST_MODE=whip
VSTATION_H265_PASSTHROUGH=false # true solo si todos los clientes soportan HEVC
VSTATION_STUN_SERVER=stun://stun.l.google.com:19302 # vacío si LiveKit en misma red

LIVEKIT_URL=wss://livekit.ejemplo.com
LIVEKIT_API_URL=https://livekit.ejemplo.com
LIVEKIT_API_KEY=...
LIVEKIT_API_SECRET=...
VariableValorMotivo
VSTATION_INGEST_MODEwhipMenor latencia; sin puerto RTMP intermedio
Perfil cámaraH.264 nativoEvita transcode (~45% CPU/cámara con H.265)
IngestaOn-demandSolo cámaras abiertas consumen CPU/red

Salud de cámaras (online/offline)​

  1. Primario: gst-discoverer-1.0 sobre URL RTSP (codec, presencia de vídeo).
  2. Fallback: ffprobe si discoverer no está disponible.
  3. Último recurso: probe TCP al puerto RTSP (menos fiable).
  4. Paquete Docker requerido: gstreamer1.0-plugins-base-apps (incluye gst-discoverer-1.0).

Worker periódico en server/utils/statusChecker.ts actualiza cameras.status e historial.

Dimensionamiento orientativo​

Ver docs/capacity-recommendations.md — escenario 20 operadores × 10 streams distintos = 200 ingestas WHIP ≈ 27 GB RAM / 21 cores en servidor de ingesta+SFU.


5. Módulo B — Grabación continua​

Modo recomendado: pull​

vstation no lanza ffmpeg por cámara. Sincroniza paths en MediaMTX vía API cada 30 s (reconcileRecordingPolicies):

recordingEnabled=true en grupo + storage MinIO configurado
→ PATCH ingest/camera-{id} { source: rtsp://..., record: true }
recordingEnabled=false o cámara disabled
→ DELETE ingest/camera-{id}

Configuración vstation​

VSTATION_RECORDING_MODE=pull
MEDIAMTX_RTSP_URL= # vacío en modo pull
MEDIAMTX_API_URL=http://mediamtx:9997
MEDIAMTX_API_USER=admin
MEDIAMTX_API_PASSWORD=...

MEDIAMTX_MINIO_ENDPOINT=http://minio:9000
MEDIAMTX_MINIO_ACCESS_KEY=...
MEDIAMTX_MINIO_SECRET_KEY=...
MEDIAMTX_MINIO_REGION=us-east-1

VSTATION_FASTSTART_WORKER=false # desactivar a escala (>500 cámaras)

Configuración MediaMTX (servidor de grabación)​

Elementos clave en mediamtx.yml:

api: yes
apiAddress: :9997

paths:
ingest/camera-~^([0-9]+)$:
record: yes
recordPath: /recordings/camera-%path/ %Y/%m/%d/%H-%M-%S
recordFormat: fmp4
runOnRecordSegmentComplete: /usr/local/bin/minio.sh $MTX_SEGMENT_PATH

El script minio.sh sube cada segmento al bucket vstation-lock con prefijo recordings/camera-{id}/....

Modo legacy relay (no recomendado a escala)​

VSTATION_RECORDING_MODE=relay
MEDIAMTX_RTSP_URL=rtsp://mediamtx:8554

Un proceso ffmpeg -c copy por cámara en vstation publica a {MEDIAMTX_RTSP_URL}/ingest/camera-{id}. Válido para decenas de cámaras; no para 800–1500.

Comparativa pull vs relay vs plan v1.0​

AspectoPlan v1.0 (4 recorders)Relay vstationPull v2 (actual)
Procesos grabación en vstation0 (recorders externos)1 × cámara0
Saltos de redCámara → recorder → MinIOCámara → vstation → MTX → MinIOCámara → MTX → MinIO
Control on/off desde UIManual por recorderAutomáticoAutomático vía API MTX
Escalabilidad 800+ camsDiseñado para elloNoSí (MTX dedicado)

Política de retención por grupo​

  • Campo BD: camera_groups.retention_days (1–365).
  • vstation sincroniza reglas ILM en MinIO por prefijo recordings/camera-{id}/.
  • Importante (fix v2): ILM se mantiene aunque recordingEnabled=false en el grupo — los segmentos ya subidos siguen expirando según política.

Capas de expiración​

MecanismoAlcanceMínimo
ILM MinIOTodos los objetos del prefijo sin legal hold1 día
Purge worker vstationFilas en tabla recordings (modo egress legacy)Según retention_days
Test purge (VSTATION_RETENTION_TEST_MINUTES=1)Solo dev/staging1 minuto

recordDeleteAfter en MediaMTX solo limpia disco local del nodo MTX; no es la retención de negocio.

AspectoComportamiento
DefaultSin legal hold (legal_hold=false)
Activación UIMenú fragmentos 🎬 → icono candado 🔒/🔓 (permiso edit_camera)
Segmentos pull (MinIO)PATCH /api/cameras/:id/recordings/object-legal-hold
Grabaciones BD (egress)PATCH /api/recordings/:id/legal-hold
EfectoObject Lock ON en MinIO → ILM no borra ese objeto
Requisito bucketObject Lock habilitado en vstation-lock

7. Playback y operación en UI​

Fuentes de fragmentos​

FuenteOrigenUso
source: minioListado directo S3 (modo pull)Producción actual
source: dbTabla recordings + validación MinIOEgress LiveKit legacy

Endpoint: GET /api/cameras/:id/recordings/window?start=&end=

ModoVentana máxima efectivaImplementación
Listado MinIO (pull, prod)73 h (menú UI 72 h)RECORDING_WINDOW_MAX_MS_MINIO en server/utils/recordingWindow.ts
Validación BD (egress legacy)6 hRECORDING_WINDOW_MAX_MS_DB — evita timeouts por HeadObject masivo

Respuesta incluye requestedStart, start (posible recorte), truncated, source: minio|db.

Reproductor en tile de cámara​

  • Timeline visible: ventana 60 min (eventos + posición playback).
  • Menú fragmentos: ventana de consulta 72 h.
  • Calendario en menú: filtra fragmentos por día (días con grabaciones en blanco, sin grabaciones en gris).
  • Candado por fragmento: retención legal sin salir del menú.
  • Click en fragmento: carga directa del segmento (fix ratio 72h vs 60min).

URLs de playback​

VSTATION_PLAYBACK_SIGNED_URLS=true
VSTATION_PLAYBACK_SIGNED_URL_TTL_SECONDS=900

Proxy: GET /api/cameras/:id/recordings/object-playback?key=...


8. Integración Optrax / Core​

Sincronización con core.optrax.io (Face-Dashboard):

EndpointFunción
GET /api/optrax/statusEstado de configuración
GET /api/optrax/previewPreview de cámaras sin importar
POST /api/optrax/syncImportar/actualizar cámaras
POST /api/optrax/sync-groupsSincronizar grupos desde Core
GET /api/optrax/playback/redirectDeep-link playback por external_id
GET /api/optrax/playback/resolveResolver external_id → cameraId (JSON)

Variables:

OPTRAX_API_URL=https://core.optrax.io
OPTRAX_API_TOKEN=...
OPTRAX_CAMERAS_PATH=/api/device-cameras

Al sincronizar, vstation respeta grupos, coordenadas y dispara reconcileRecordingPolicies para paths MTX.


9. Despliegue e infraestructura​

Referencia producción actual (Optrax)​

RecursoHostNotas
vstation64.23.135.236 / vstation.optrax.ioDocker Compose, rama main
MediaMTX161.35.100.138:9997API + grabación
MinIO146.190.47.200:9000Bucket vstation-lock
LiveKit146.190.159.52SFU; expuesto vía Nginx /livekit-ws/

Red: segmentación por droplet + firewall (§3.1). VLANs no aplican en cloud; diseño on-prem en §3.2.

Procedimiento de deploy​

# En servidor app
cd /root/vstation
git pull origin main
docker compose up -d --build

# Verificación
curl -s https://vstation.optrax.io/api/health
docker logs vstation-app --tail 50 | grep recording

Artefactos de infra en repositorio​

ArchivoUso
infra/mediamtx.example.ymlPlantilla MediaMTX grabación
infra/minio-upload.example.shHook upload segmentos
promtail-config.example.ymlCopiar a promtail-config.yml (gitignored)
docs/runbook-incidentes.mdRunbook operativo §12 / Fase 4
.github/workflows/test.ymlCI: npm test + npm run check

Checklist .env producción — grabación pull (Optrax)​

  • VSTATION_RECORDING_MODE=pull (prod Optrax)
  • MEDIAMTX_RTSP_URL vacío
  • MEDIAMTX_API_URL + credenciales API
  • MEDIAMTX_MINIO_* apuntando al MinIO de grabación
  • Grupos con recordingEnabled=true y retention_days definido
  • Bucket vstation-lock con Object Lock
  • Hook minio.sh activo en MediaMTX (servidor MTX; ver infra/)

Checklist .env producción — vivo (Optrax)​

  • VSTATION_INGEST_MODE=whip
  • LiveKit URL/API key/secret
  • Cámaras H.264 donde sea posible (continuo)
  • gst-discoverer-1.0 presente en imagen Docker (verificado en build)

10. Plan de implementación por fases​

Fase 0 — Infraestructura base (1–2 semanas)​

  • Droplet/servidor app: Docker, PostgreSQL, Redis, Nginx, TLS (Optrax)
  • Servidor LiveKit (SFU + WHIP ingress) (Optrax)
  • Servidor MediaMTX + script MinIO (Optrax; plantillas en infra/)
  • MinIO vstation-lock con Object Lock + ILM habilitado (Optrax)
  • Optrax cloud: segmentación por droplets + Cloud Firewall (§3.1 — ya implementado)
  • On-prem: implementar VLANs §3.2 (5 VLANs + ACL §3.2.4) o reutilizar las existentes del plan v1.0
  • Fases R0–R5 de red completadas (§3.2.7)
  • Reglas firewall puertos §3.2.4 / §15.0.2 verificadas en cada nodo

Fase 1 — vstation core (1 semana)​

  • Deploy vstation-app desde GitHub main (Optrax)
  • Setup inicial + usuario admin
  • Importar cámaras piloto (manual o Optrax sync)
  • Validar vivo WHIP (5–10 cámaras)
  • Validar salud gst-discoverer

Fase 2 — Grabación pull (1–2 semanas)​

  • Configurar VSTATION_RECORDING_MODE=pull
  • Activar recordingEnabled en grupo piloto
  • Verificar paths ingest/camera-* en MediaMTX API
  • Verificar objetos en MinIO bajo recordings/camera-{id}/
  • Validar playback en UI + timeline

Fase 3 — Retención y evidencia (1 semana)​

  • Definir retention_days por grupo
  • Confirmar reglas ILM en MinIO (mc ilm rule list)
  • Probar retención legal desde UI (candado)
  • Confirmar que ILM no borra objetos con legal hold ON

Fase 4 — Escala y operación (continuo)​

  • Import masivo Optrax (800+ cámaras) (Miraflores / cliente)
  • Ajustar recordSegmentDuration a 60s vía PATCH /api/mediamtx/config
  • Monitoreo cadvisor (compose); [ ] alertas Prometheus/reglas
  • Simulaciones: npm run stream:sim, npm run recording:sim
  • Runbook de incidentes → docs/runbook-incidentes.md

11. Pruebas de aceptación​

Automatizadas (CI local)​

npm test

Suite actual (CI: .github/workflows/test.yml — npm test + npm run check):

ÁreaArchivoImplicación HW (resumen)
gst-discoverer / codecserver/utils/gstDiscoverer.test.tsGStreamer + gst-discoverer en Docker; H.264 passthrough vs H.265 transcode/GPU
Conectividad RTSPserver/utils/streamConnectivity.test.tsFirewall 554/tcp; RTSP accesible desde vstation y MediaMTX
Ventana playback MinIO/BDserver/utils/recordingWindow.test.tsCoherencia menú 72 h (MinIO) vs validación BD 6 h
Segmentos MinIO / prefijos díaserver/utils/recordingSegments.test.tsKeys .fmp4, parseo timestamp MTX, prefijos listado 72 h
Modo grabación pull/relayserver/services/recordingMode.test.tsresolveRecordingMode según .env
Retención / ILM / purgeserver/services/recordingRetention.test.tsMinIO Object Lock; TB = f(cámaras, bitrate, días); legal hold = storage extra
Timeline / fragmentos UIclient/src/lib/timelineFragments.test.tsSin impacto servidor; carga en PC operador (decode vídeo)

Detalle completo de requerimientos derivados: §15.0.

Manuales — Módulo A (vivo)​

#PruebaCriterio
A1Abrir cámara RTSP H.264Video en menos de 5 s, badge Live
A2Cerrar tile / cambiar páginaIngesta se detiene (sin procesos huérfanos)
A310 cámaras simultáneasCPU/RAM dentro de plan
A4Cámara offlineEstado "Fuera de línea" en ≤2 ciclos de health check
A5Cámara H.265Transcode o passthrough según config; Firefox documentado

Manuales — Módulo B (grabación)​

#PruebaCriterio
B1Activar grabación en grupoPath MTX creado con record: true
B2Esperar 2–3 segmentosObjetos visibles en MinIO y menú fragmentos
B3Desactivar grabaciónPath MTX eliminado; segmentos previos permanecen
B4Playback click fragmentoEntra en modo PLAYBACK del segmento correcto
B5CalendarioDías con grabaciones en blanco; filtro por fecha funciona
#PruebaCriterio
R1Cambiar retention_daysILM actualizado en MinIO
R2Activar candado en fragmentoLegal hold ON en MinIO; objeto no expira
R3Quitar candadoILM puede expirar según edad

Smoke script grabación pull​

npm run recording:smoke

12. Operación, monitoreo y mantenimiento​

Logs clave​

docker logs vstation-app 2>&1 | grep -E '\[recording\]|\[camera\]|\[minio'

Mensajes esperados al arrancar:

[recording] Recording workers started (mode=pull + MinIO ILM)

Métricas recomendadas​

MétricaFuenteAlerta
CPU/RAM appcadvisor>80% sostenido
Paths MTX activosGET /v3/paths/list< cámaras con recording enabled
Espacio MinIOMinIO console / API>85%
ILM pendingMinIOobjetos sin expirar anómalos
LiveKit roomsLiveKit APIrooms huérfanas

Tareas periódicas​

FrecuenciaTarea
DiariaRevisar cámaras offline persistentes
SemanalVerificar espacio MinIO y healing
MensualRevisar reglas ILM vs grupos en BD
Por releasegit pull + docker compose up -d --build + health check

Ajuste segmentos MediaMTX (sin redeploy)​

PATCH /api/mediamtx/config
Authorization: Bearer <token>
Content-Type: application/json

{ "recordSegmentDuration": "60s" }

13. Cambios respecto al plan v1.0​

TemaPlan v1.0 (Miraflores)Arquitectura v2 (actual)
Grabación4 servidores recorder dedicadosMediaMTX pull centralizado
VivoMediaMTX → RTMP → LiveKit IngressGStreamer WHIP directo → LiveKit
Control de grabaciónPor servidor recordervstation ↔ API MediaMTX
Orquestación UIV-Studio separadovstation unificado (React)
Salud cámarasNo especificadogst-discoverer + fallback
SnapshotsImplícito en NVREliminado en vstation
Retención legalNo detalladoObject Lock + UI candado
PlaybackMetadata DB por segmentoListado MinIO + proxy
TestsManuales en plan39 unit tests automatizados

14. Riesgos y mitigaciones​

RiesgoImpactoMitigación
MediaMTX API inaccesibleNo alta/baja paths grabaciónMonitoreo + credenciales API; retry en worker
MinIO saturado (IOPS)Pérdida segmentosNVMe, segmentos 60s, EC en clúster
200+ ingestas WHIPCPU/RAM appServidor ingesta dedicado; H.264 nativo
H.265 en FirefoxPantalla negraTranscode o documentar browser soportado
Legal hold masivoStorage sin límiteProceso operativo; solo evidencia real
Ventana API en /recordings/windowBD limitada 6 h; MinIO hasta 73 hModo pull usa listado MinIO alineado al menú 72 h; calendario por día

15. Requerimientos de hardware y dimensionamiento​

Esta sección consolida tres fuentes de evidencia del repositorio:

FuenteComando / artefactoQué dimensiona
Tests unitariosnpm test (39/39 pass)Paquetes Docker, MinIO Object Lock, ILM, red RTSP, perfil codec
Simulación grabaciónnpm run recording:simRAM/CPU/red/disco MediaMTX + MinIO por N cámaras 24/7
Simulación vivonpm run stream:simRAM/CPU/red LiveKit + WHIP por operadores × tiles

Perfil de referencia: 500 cámaras grabando 24/7 + operadores viendo cámaras distintas (--layout varied). Escenarios 800 (Miraflores) y 1300 incluidos en matrices finales.

15.0 Requerimientos derivados de tests unitarios​

Suite CI (npm test, septiembre 2026): 39 tests, 19 suites, 0 fallos.

15.0.1 gstDiscoverer.test.ts — salud de cámara y perfil codec​

Test / comportamiento validadoRequerimiento de hardware / infra
Detección H.264 (normalizeVideoCodec, parseDiscovererOutput)Cámaras H.264 nativas → ingesta WHIP passthrough, 0 % GPU servidor, ~88 MB RAM/cámara activa
Detección H.265 / HEVCSi VSTATION_H265_PASSTHROUGH=true: decode solo Chrome/Edge (no Firefox). Si false: transcode GStreamer → ~45 % CPU/cámara; con 100 cámaras ≈ 45 cores → GPU NVENC/VAAPI obligatoria
VP8 y otros codecs detectadosNo soportados en pipeline CCTV actual; reconfigurar cámara a H.264
runGstDiscoverer retorna null si binario ausenteImagen Docker debe incluir gst-discoverer-1.0 (gstreamer1.0-plugins-base-apps). Sin él: fallback ffprobe (menor precisión)
Probe periódico por cámara (worker statusChecker)Conectividad RTSP saliente desde vstation-app hacia cada cámara/NVR; ancho de banda despreciable (~KB/s) pero 554/tcp debe estar abierto

Paquetes mínimos en contenedor vstation-app:

gstreamer1.0-tools
gstreamer1.0-plugins-base
gstreamer1.0-plugins-good
gstreamer1.0-plugins-bad # whipsink
gstreamer1.0-plugins-base-apps # gst-discoverer-1.0

15.0.2 streamConnectivity.test.ts — conectividad RTSP​

Test validadoRequerimiento de red / firewall
parseHostPort RTSP puerto 554 por defectoRegla firewall: 554/tcp cámara/NVR ↔ vstation (salud + vivo) y ↔ MediaMTX (grabación)
RTSP URL inválida → nullSin impacto HW; documentar formato rtsp://user:pass@host:554/path
defaultPortForProtocol RTMP 1935Solo si VSTATION_INGEST_MODE=rtmp (legacy): 1935/tcp cámara → vstation
Probe TCP fallbackPuerto destino accesible; no sustituye RTSP real en producción

VLAN vs firewall: estos tests validan conectividad IP/puerto, no VLAN IDs. En Optrax cloud la segmentación ya está resuelta por droplets (§3.1). VLANs L2 solo aplican en on-prem (§3.2) y las configura el switch del cliente, no vstation.

Matriz de puertos (firewall — independiente del modelo VLAN):

Origen → DestinoPuertoProtocoloUso
Cámara/NVR → MediaMTX554RTSP/TCPGrabación pull 24/7
Cámara/NVR → vstation554RTSP/TCPWHIP on-demand + health check
vstation → MediaMTX9997HTTPAPI alta/baja paths
MediaMTX → MinIO9000S3/HTTPUpload segmentos
vstation → MinIO9000S3/HTTPILM, legal hold, playback
Operador → LiveKit443WSS/WebRTCVisualización vivo
Operador → vstation443HTTPSUI + playback proxy
Test / regla validadaRequerimiento de hardware / capacidad
clampRetentionDays 1–365 díasDisco MinIO = cámaras × bitrate × retentionDays (ver §15.3 y §15.10)
buildVstationIlmRules — una regla ILM por cámaraMinIO soporta miles de reglas; a 800+ cámaras monitorizar latencia de PutBucketLifecycleConfiguration
collectCameraRetentionRules — ILM independiente de recordingEnabledReglas activas aunque grabación pausada; no reduce almacenamiento hasta purge manual
partitionLifecycleRules — preserva reglas ajenasBucket dedicado vstation-lock; no compartir con otros workloads ILM
purgeExpiredRecordings — borra objeto + marca BDWorker vstation: CPU/RAM mínimos; IOPS MinIO escala con segmentos/hora → preferir segmentos 60 s
Legal hold ON → no purge (no elimina grabaciones con legal hold)MinIO Object Lock habilitado en bucket (Compliance/Governance según política). Objetos retenidos no cuentan en ILM → planificar overhead de storage para evidencia
parseTestRetentionMinutesFromEnv — modo minutosSolo dev/staging (VSTATION_RETENTION_TEST_MINUTES); sin impacto en dimensionamiento producción
Fallo borrado MinIO → no marca deletedRed estable vstation ↔ MinIO; reintentos no deben saturar API S3

Fórmula almacenamiento grabación (H.264 @ bitrate B kbps, R días, N cámaras):

TB_total ≈ N × (B / 8) × 86_400 × R / 1e12

Ejemplo validado por simulación: 800 cams × 1000 kbps × 45 d → ~427 TB (con margen operativo).

15.0.4 timelineFragments.test.ts — playback UI (cliente)​

Test validadoImpacto hardware
Ratio ventana 60 min vs menú 72 hNinguno en servidor — lógica cliente
Calendario / filtro por día localNinguno en servidor
findFragmentCoveringTime / seek en fragmentoPlayback vía proxy vstation → MinIO: egress MinIO proporcional a operadores revisando grabaciones (típico ≪ vivo)

PC operador (playback + vivo): ver §15.6. Los tests confirman que la carga de timeline es despreciable frente al decode de vídeo.

15.0.5 Checklist hardware mínimo por subsistema (derivado de tests + sims)​

SubsistemaComponenteMínimo (tests)Recomendado (500 cams + 10 ops)
vstation-appCPU2 cores4 cores (sin ffmpeg grabación)
RAM2 GB4 GB (+ PG/Redis en mismo nodo: +2 GB)
Disco20 GB50 GB SSD
Paquetesgst-discoverer + GStreamer WHIPIgual + ffprobe fallback
MediaMTX grabaciónCPU1.5 cores / 200 cams5 cores / 500 cams
RAM5 GB / 200 cams12 GB / 500 cams
Red200 Mbps / 200 cams≥ 1 Gbps / 500 cams
MinIODiscoNVMe/SSDRAID/NVMe ≥ 750 MB/s escritura sostenida
FeatureObject Lock ONILM + versioning
Capacidadf(retention)178 TB @ 500×30d×1Mbps
LiveKit + WHIPCPU6 cores / 50 ingestas11 cores / 100 ingestas
RAM8 GB / 50 ingestas14 GB / 100 ingestas
GPU0 % (H.264 passthrough)0 %; NVENC si H.265 transcode
PC operadorRAM sistema8 GB16 GB
GPUDecode H.264 HWDedicada o iGPU reciente
Red20 Mbps50 Mbps

15.1 Premisas del escenario​

ParámetroValor de referencia
Cámaras grabando500 simultáneas
Modo grabaciónpull (MediaMTX directo; 0 ffmpeg en vstation)
Perfil vídeo grabaciónH.264, 1000–1500 kbps, 720p–1080p
Segmentos60 s (óptimo) o 30 s
Retención30 días (ajustar según retention_days del grupo)
VivoWHIP H.264 passthrough (VSTATION_INGEST_MODE=whip)
Layout operadoresVaried — cada operador ve cámaras diferentes
Ingesta vivoOn-demand — solo cámaras abiertas en pantalla

Nota de doble pull: una cámara que se graba y además se ve en vivo recibe dos consumidores RTSP (MediaMTX + GStreamer WHIP). Dimensionar la red de cámaras/NVR con ese solapamiento.

15.2 Topología de servidores recomendada (500 + vivo)​

Separar grabación y vivo en nodos distintos. vstation-app puede convivir con LiveKit en el nodo de operación.

┌──────────────── NODO GRABACIÓN ─────────────────┐
│ MediaMTX (500 paths) + MinIO vstation-lock │
│ 500–788 Mbps escritura sostenida │
└──────────────────────────────────────────────────┘

┌──────────────── NODO VIVO / APP ─────────────────┐
│ vstation-app + PostgreSQL + Redis + Nginx │
│ LiveKit SFU + WHIP ingress │
│ 50–200 ingestas WHIP según operadores activos │
└──────────────────────────────────────────────────┘

┌──────────────── PCs OPERADOR ────────────────────┐
│ Chrome/Edge, decode H.264 HW, 8–16 GB RAM │
└──────────────────────────────────────────────────┘

15.3 Grabación — 500 cámaras (MediaMTX pull directo)​

Comando de simulación:

npx tsx scripts/mediamtx-recording-sim.ts --cameras 500 --topology direct \
--bitrate-kbps 1000 --segment-sec 60s --retention-days 30
MétricaNecesario (mínimo)Óptimo (recomendado)
Servidor grabación RAM9 GB12 GB (+25 % headroom)
Servidor grabación CPU3.4 cores5 cores
GPU grabación0 %0 %
Red cámaras → MediaMTX500 Mbps≥ 1 Gbps dedicado
Escritura sostenida MinIO500 Mbps (~63 MB/s)NVMe/RAID ≥ 750 MB/s
Segmentos/hora (60 s)30 00030 000
Almacenamiento/día @ 1000 kbps5.1 TB5.1 TB
Almacenamiento 30 días154 TB178 TB (+margen)
vstation-app (orquestación)0.3 GB RAM, 0.1 coreMismo nodo app (sin carga ffmpeg)

Variante bitrate alto (1500 kbps, segmentos 30 s):

MétricaNecesarioÓptimo
Red ingress750 Mbps≥ 2 Gbps
Escritura MinIO788 Mbps (~94 MB/s)NVMe obligatorio
TB / 30 días232 TB267 TB
Segmentos/hora60 000Preferir 60 s → 30 000/h

15.4 Visualización simultánea — cámaras distintas por operador​

Comando (sin relay grabación — grabación va por nodo MTX aparte):

npx tsx scripts/stream-load-sim.ts --users 10 --streams 10 --layout varied \
--profile h264_passthrough --no-recording
Escenario operadorCámaras únicas vivoRAM servidor (necesario)CPU servidor (necesario)RAM óptima (+25 %)CPU óptima
5 × 10 tiles506 GB4.3 cores8 GB6 cores
10 × 10 tiles (ref.)10010.6 GB8.4 cores14 GB11 cores
20 × 10 tiles (pico)20026.6 GB20.7 cores34 GB27 cores

Desglose capas — escenario referencia 10 operadores × 10 cámaras (100 ingestas):

CapaRAMCPUGPU
Infra vstation (app + PG + Redis + Nginx)0.8 GB0.1 core0 %
Ingesta WHIP (100 cámaras)8.8 GB8.0 cores0 %
LiveKit SFU (100 suscripciones)1.0 GB0.3 core0 %
Total servidor vivo~10.6 GB~8.4 cores0 %

Ancho de banda vivo (100 cámaras @ 1500 kbps):

DirecciónMbps
Ingress cámaras → vstation/LiveKit~150
Egress SFU → operadores~150

Comparativa mosaico compartido (10 operadores, mismas 16 cámaras):

npx tsx scripts/stream-load-sim.ts --users 10 --streams 10 --layout shared \
--unique-cameras 16 --profile h264_passthrough --no-recording
MétricaValor
Ingestas WHIP16 (no 100)
RAM servidor óptima4 GB
CPU servidor óptima3 cores

→ Si todos ven el mismo mosaico, el dimensionamiento de vivo baja ~7× frente a varied.

15.5 Escenario combinado — 500 grabando + 10 operadores varied​

Configuración objetivo para despliegue tipo Miraflores reducido / piloto regional:

NodoFunciónRAM necesariaRAM óptimaCPU necesariaCPU óptimaRed
GrabaciónMTX + MinIO9 GB12 GB3.4 cores5 cores1 Gbps+
Vivo + Appvstation + LiveKit11 GB14 GB8.4 cores11 cores1 Gbps
Total infra—20 GB26 GB12 cores16 cores2 Gbps agregado
AlmacenamientoMinIO 30 d154 TB178 TB———

Red agregada estimada (con solapamiento ~100 cámaras vivo+grabación):

TramoMbps estimados
Cámaras → MediaMTX (500 grabando)~500
Cámaras → vstation WHIP (100 vivo extra)~150
SFU → operadores~150
Total pico aproximado~800 Mbps

15.6 Requerimientos por PC operador​

RecursoNecesarioÓptimo
RAM navegador5 GB (10 tiles)8 GB sistema
GPUDecode H.264 HWDedicada o iGPU reciente
CPU cliente~5 % (10 tiles)4+ cores
NavegadorChrome / EdgeÚltima versión estable
Red estación20 Mbps50 Mbps

Evitar Firefox si hay cámaras H.265 passthrough.

15.7 Perfiles a evitar a esta escala​

Anti-patrónImpacto en 500 cams
VSTATION_RECORDING_MODE=relay~17 GB RAM solo ffmpeg + 25+ cores en vstation
H.265 transcode en vivo~45 % CPU/cámara → 100 cams ≈ 45 cores solo ingesta
Segmentos 30 s @ 1500 kbps60 000 archivos/h → IOPS MinIO alto
VSTATION_FASTSTART_WORKER=truePicos CPU por re-mux MP4
LiveKit + MTX + MinIO en 1 solo servidorContención RAM/red/CPU

15.8 Configuración .env objetivo (500 + pull)​

# Grabación
VSTATION_RECORDING_MODE=pull
MEDIAMTX_RTSP_URL=
MEDIAMTX_API_URL=http://<mtx-grabacion>:9997
MEDIAMTX_MINIO_ENDPOINT=http://<minio>:9000

# Vivo
VSTATION_INGEST_MODE=whip
VSTATION_H265_PASSTHROUGH=false
LIVEKIT_URL=wss://...
LIVEKIT_API_URL=https://...

# Escala
VSTATION_FASTSTART_WORKER=false

MediaMTX — segmento recomendado:

PATCH /api/mediamtx/config
{ "recordSegmentDuration": "60s" }

15.9 Escenario Miraflores — 800 cámaras grabando 45 días​

Comando de simulación (perfil acordado Miraflores: H.264 @ 1000 kbps, segmentos 60 s):

npx tsx scripts/mediamtx-recording-sim.ts --cameras 800 --topology direct \
--bitrate-kbps 1000 --segment-sec 60s --retention-days 45
MétricaNecesario (mínimo)Óptimo (recomendado)
Servidor grabación RAM12.6 GB16 GB
Servidor grabación CPU5.4 cores7 cores
GPU grabación0 %0 %
Red cámaras → MediaMTX800 Mbps≥ 2 Gbps dedicado
Escritura sostenida MinIO800 Mbps (~100 MB/s)NVMe ≥ 1 GB/s
Segmentos/hora (60 s)48 00048 000
Almacenamiento/día8.2 TB8.2 TB
Almacenamiento 45 días371 TB427 TB (+margen)

Combinado con 10 operadores × 10 cámaras distintas (100 ingestas WHIP):

NodoRAM óptimaCPU óptimaRed
Grabación (MTX + MinIO)16 GB7 cores2 Gbps
Vivo + App14 GB11 cores1 Gbps
Total infra30 GB18 cores~2.5 Gbps agregado pico
Almacenamiento427 TB——

Doble pull: si las 100 cámaras en vivo también se graban, no suman paths extra en MTX (misma cámara), pero la red NVR recibe 2 consumidores RTSP por cámara solapada.

15.10 Matriz consolidada por escala (simulaciones sept 2026)​

Grabación continua — MediaMTX pull directo, H.264 @ 1000 kbps, segmentos 60 s:

CámarasRetenciónRAM srvCPU srvRed ingressTB almacenamientoSegmentos/h
20030 d7 GB2 cores0.5 Gbps72 TB12 000
50030 d12 GB5 cores1.3 Gbps178 TB30 000
80030 d16 GB7 cores2 Gbps285 TB48 000
80045 d16 GB7 cores2 Gbps427 TB48 000
130030 d24 GB12 cores3.2 Gbps462 TB78 000

Vivo on-demand — H.264 passthrough, layout varied (--no-recording):

Operadores × tilesIngestas únicasRAM óptimaCPU óptimaIngress+egress
5 × 10508 GB6 cores~75 Mbps
10 × 1010014 GB11 cores~150 Mbps
20 × 1020025 GB22 cores~300 Mbps

Vivo H.265 transcode (anti-patrón a escala):

Operadores × tilesRAM óptimaCPU óptimaGPU
10 × 10 (100 cams)30 GB60 coresNVENC recomendada

15.11 Validación en staging​

# Tests unitarios (39) — validar requisitos software antes de hardware
npm test

# Grabación por escala
npm run recording:sim
npx tsx scripts/mediamtx-recording-sim.ts --cameras 500 --topology direct --bitrate-kbps 1000 --segment-sec 60s
npx tsx scripts/mediamtx-recording-sim.ts --cameras 800 --topology direct --bitrate-kbps 1000 --segment-sec 60s --retention-days 45

# Vivo 10 operadores × 10 cámaras distintas
npm run stream:sim
npx tsx scripts/stream-load-sim.ts --users 10 --streams 10 --layout varied --no-recording

# Smoke grabación pull real
npm run recording:smoke

Medir en producción: cadvisor :8080, MediaMTX /v3/paths/list, iostat en nodos MinIO.


16. Anexos​

A. Variables de entorno completas​

Ver .env.example en el repositorio. Resumen por subsistema:

GrupoVariables principales
App/DBDATABASE_URL, SESSION_SECRET, JWT_SECRET, REDIS_URL
Auth GatewayAUTH_GATEWAY_URL, AUTH_GATEWAY_CLIENT_ID, KEYCLOAK_*, APP_ACCESS_ROLE
IntegraciónINTERNAL_SERVICE_KEY, VSTATION_PUBLIC_URL
OTELOTEL_SERVICE_NAME, OTEL_EXPORTER_OTLP_ENDPOINT
LiveKitLIVEKIT_URL, LIVEKIT_API_URL, LIVEKIT_API_KEY, LIVEKIT_API_SECRET
IngestaVSTATION_INGEST_MODE, VSTATION_H265_PASSTHROUGH, VSTATION_STUN_SERVER
GrabaciónVSTATION_RECORDING_MODE, MEDIAMTX_API_URL, MEDIAMTX_*, MEDIAMTX_MINIO_*
PlaybackVSTATION_PLAYBACK_SIGNED_URLS, VSTATION_PLAYBACK_SIGNED_URL_TTL_SECONDS
Retención testVSTATION_RETENTION_TEST_MINUTES (solo dev)
OptraxOPTRAX_API_URL, OPTRAX_API_TOKEN
MapasVITE_MAP_PROVIDER, VITE_MARTIN_URL, VITE_MARTIN_SOURCE

B. API endpoints principales​

MétodoRutaFunción
GET/api/healthHealth check
GET/api/camerasInventario
GET/api/cameras/:id/recordings/windowFragmentos playback
GET/api/cameras/:id/recordings/object-playbackStream segmento MinIO
PATCH/api/cameras/:id/recordings/object-legal-holdLegal hold segmento pull
PATCH/api/recordings/:id/legal-holdLegal hold fila BD
GET/api/mediamtx/configConfig global MTX
PATCH/api/mediamtx/configAjuste segment duration
POST/api/optrax/syncSync cámaras Core

C. Servicios backend (grabación)​

ArchivoResponsabilidad
recordingMode.tsDetecta pull/relay/off
mediamtxPullRecording.tsAlta/baja paths MTX
mediamtxProxy.tsCliente API MediaMTX
cameraRecording.tsFachada start/stop grabación
livekitRecording.tsWorkers política + retención BD
minioRetentionLifecycle.tsSync ILM MinIO
recordingRetention.tsLógica pura retención/purge
minioTestRetentionPurge.tsPurge test (dev)

D. Documentos relacionados​

DocumentoContenido
docs/capacity-recommendations.mdDimensionamiento extendido (200–1500 cámaras)
docs/plan-implementacion-vstation-v2.mdEste documento
docs/runbook-incidentes.mdRunbook MTX / MinIO / LiveKit
README.mdSetup desarrollo
.env.exampleReferencia completa de configuración

E. Scripts de simulación y tests​

Script / comandoEscenarioSalida HW
npm test39 unit tests§15.0 — paquetes, red, MinIO, codec
npm run recording:sim500 cams pull§15.3, §15.10
npm run stream:sim10×10 varied§15.4, §15.10
npm run recording:smokeE2E pull realValidación entorno
mediamtx-recording-sim.ts --cameras 800 --retention-days 45Miraflores§15.9

Documento generado a partir del estado del repositorio vstation (rama main, septiembre 2026). Validar siempre en staging antes de despliegues masivos.