01 · Resumen ejecutivo

De laptop a producción sin improvisar

Este documento define cómo FARO Connect pasa de correr en la máquina del desarrollador a correr en ambientes controlados mostrables a un socio técnico, un piloto cliente o un inversor. Sin sobreactuar infraestructura, pero sin improvisar.

La regla central es directa: si FARO solo corre en una laptop, todavía no existe como plataforma. Puede existir como idea. Puede existir como demo. Pero no como producto serio que aguante una conversación profesional con un CTO o una decisión de inversión.

El objetivo del MVP no es infraestructura perfecta. Es infraestructura suficiente, reproducible, segura y no improvisada. Eso implica:

  • 5 ambientes con propósito distinto: local, test, staging, demo, production. Cada uno con dominio, base de datos, storage, email e IA configurados explícitamente.
  • Stack primario recomendado: Vercel para Next.js, Supabase (Postgres + Storage) o Neon para base de datos, Upstash para Redis, GitHub Actions para CI/CD, Sentry para errores. Alternativa unificada: Render o Railway si se prefiere menos vendors.
  • SemVer adaptado con sufijos de etapa: 0.1.0-demo0.3.0-mvp1.0.0. Cada deploy registra versión, commit, ambiente y metadata en ops.releases.
  • 3 tablas operativas nuevas en schema ops: releases, deployments, release_backups. Permiten trazabilidad completa sin necesitar herramientas externas.
  • 3 workflows GitHub Actions: CI (tests + lint + build, automático en PR), deploy staging (automático en main), deploy production (manual con approval).
  • Rollback plan + backup pre-release: rollback de código es trivial; rollback de datos es delicado. Por eso se evitan migraciones destructivas y se obliga backup antes de cada release a producción.

El dataset demo vive en Empresa Demo Cuyo S.A. y solo se carga en staging y demo (nunca en producción, bloqueado por guard FARO_DEMO_MODE). Los catálogos canónicos (tensiones, acciones, evidencias, KPIs, prompts IA, Score model) son release-sensitive: cambian con versión, no a mano en consola.

"Local demuestra que se puede construir. Staging demuestra que se puede compartir. Producción demuestra que se puede operar. Release management demuestra que se puede sostener."

Principio rector. Todo deploy debe ser repetible (sin pasos manuales ocultos), auditable (saber qué versión, cuándo, quién), reversible (poder volver atrás), validable (smoke tests post-deploy), seguro (secrets fuera del repo), observado (logs, errores, health checks) y separado por ambiente (local no es staging, staging no es producción).

02 · Los 5 ambientes

local · test · staging · demo · production

Cada ambiente tiene un propósito explícito, un dominio sugerido y un set de configuraciones por defecto. Mezclar ambientes es la forma clásica de romper producción con un seed demo o de mandar emails reales desde staging.

local Dev individual

Desarrollo en laptop

Datos
Sintéticos / demo
Dominio
http://localhost:3000
DB
Docker Postgres local
Redis
Docker Redis local
Email
Fake provider
IA
Fallback (sin coste)
Backups
No requeridos
Demo mode
true
Cada developer levanta su instancia con docker-compose up. Sin acceso externo.
test CI automático

Tests automáticos en CI

Datos
Sintéticos efímeros
Dominio
N/A · CI runner
DB
Postgres 16 service container
Redis
Redis 7 service container
Email
Fake / mock
IA
Mock determinista
Backups
No aplica (efímero)
Demo mode
true
Corre en GitHub Actions ante cada PR y push a main. Vida útil: minutos.
staging Pre-producción

Validación antes de producción

Datos
Sintéticos + dataset Empresa Demo Cuyo S.A.
Dominio
staging.faroconnect.app
DB
Supabase managed Postgres
Redis
Upstash Redis
Email
Sandbox + whitelist
IA
Fallback o real limitado
Backups
Diarios automáticos
Demo mode
true
Auto-deploy desde main. Acceso con login obligatorio + robots: noindex.
demo Cliente-facing

Demos a inversores / pilotos

Datos
Dataset Empresa Demo Cuyo S.A. · 6 meses · Score 66
Dominio
demo.farodireccion.com
DB
Supabase separado (no comparte con staging)
Redis
Upstash separado
Email
Sandbox con whitelist estricto
IA
Real limitado por budget
Backups
Diarios + pre-reset
Demo mode
true
Deploy manual controlado. Reset semanal del dataset para mostrarlo "limpio".
production Clientes reales

Empresas piloto / clientes

Datos
Reales · RLS activa · multi-tenant
Dominio
app.faroconnect.app
DB
Supabase dedicado · backups PITR
Redis
Upstash producción
Email
Resend / Postmark real
IA
Real según plan del cliente
Backups
Continuos · pre-release obligatorio
Demo mode
false · forzado
Deploy manual con approval. Monitoreo 30-60 min post-release.

Diferencias clave entre ambientes

Variablelocalteststagingdemoproduction
Datos realesNoNoNoNo
Dataset Empresa DemoNo
Debug logsAltoAltoControladoControladoBajo
IA realOpcionalMockLimitadaLimitadaSegún plan
Email realNoNoSandboxWhitelist
StorageFake localFakefaro-staging-*faro-demo-*faro-prod-*
Backups obligatoriosNoNoDiariosDiariosContinuos
RLS activo
SentryOpcionalNo
Deploy manualN/AAuto + manualManualApproval

RLS desde el día uno. Row Level Security está activo en test, staging, demo y production. No es "para después". Activarlo tarde suele ser cirugía sin anestesia: rompe queries en cadena. Ver seguridad-rls-mvp.html para policies por tabla.

03 · Stack recomendado

Vercel + Supabase + Upstash · primario

El MVP no necesita microservicios ni Kubernetes. Necesita un stack simple, gestionado, reproducible y con costos predecibles. La recomendación primaria balancea best-in-class por componente con costo bajo para etapa de validación.

Frontend + Backend
Vercel
Alternativa: Render · Railway · Fly.io

Next.js App + APIs + server actions. Edge functions, preview deploys por PR, dominios incluidos. Deploy a staging desde main en segundos.

USD 0-20/mes inicial
Base de datos
Supabase
Alternativa: Neon · Railway Postgres

PostgreSQL 15+ gestionado, RLS nativo, storage privado integrado, dashboards SQL. Permite separar proyectos por ambiente (staging, demo, production).

USD 0-25/mes por proyecto
Cache / Queue
Upstash Redis
Alternativa: Railway Redis · Render Redis

Redis serverless. Usado para queue de jobs (workers), cache de queries pesadas, locks de score recalculation, rate limiting.

USD 0-10/mes por ambiente
Workers
Render Worker
Alternativa: Railway · Fly.io machine

Procesos separados para jobs asincrónicos: workflow checker, score recalculation, notification dispatcher, weekly report generator, AI cache cleaner.

USD 7-15/mes por worker
Storage
Supabase Storage
Alternativa: AWS S3 · Cloudflare R2

Buckets privados por ambiente. Guarda evidencia (PDFs, screenshots), reportes generados, exports XLSX.

USD 0-10/mes inicial
Email
Resend
Alternativa: Postmark · SendGrid

Provider transaccional. API simple, dominio propio, templates HTML. Modo sandbox para staging/demo, real para producción.

USD 0-20/mes
Errores / observabilidad
Sentry
Plan: Developer free

Error tracking + performance + release health. Tags obligatorios: company_id, user_id, environment, release, module.

USD 0-26/mes
CI/CD
GitHub Actions
Incluido en plan repo

Workflows YAML versionados en repo. CI en cada PR, deploy staging automático desde main, deploy production con approval manual.

USD 0 (2000 min/mes free)

Trade-offs primario vs alternativa unificada

AspectoVercel + Supabase + Upstash (primario)Render o Railway (unificado)
Calidad por componenteBest-in-class · cada vendor especializadoBueno · pero generalista
OperaciónMás dashboards a mirar (3-4 paneles)Un solo panel
Setup inicial~2 horas (3 cuentas + DNS)~45 min (una cuenta)
Costos primer añoUSD 0-60/mes con tier freeUSD 20-80/mes
EscalabilidadAlta · cada componente escala soloMedia · escala todo junto
Vendor lock-inBajo · estándares abiertos (Postgres, Redis)Medio · workflow propio
Recomendado paraPre-piloto, MVP serio, pre-inversiónProof of concept, demo único

Arquitectura monolito modular primero

FARO no necesita microservicios todavía. Estrategia: una aplicación Next.js + workers separados + base PostgreSQL + Redis + storage. Separar en 12 servicios ahora es construir una fábrica antes de vender el primer producto.

ProcesoFunciónDeploy
webUI + APIs + server actionsVercel
workerJobs asincrónicos (score, reportes, alertas)Render worker / Railway worker
schedulerDisparar jobs programados (cron)Render cron / GitHub Actions schedule
dbPostgreSQL gestionadoSupabase
redisQueue + cache + locksUpstash
04 · Variables y secrets

.env por ambiente · secrets fuera del repo

Convención PREFIJO_NOMBRE con prefijos por dominio (APP_*, DATABASE_*, REDIS_*, STORAGE_*, EMAIL_*, AI_*, SENTRY_*, FARO_*). Toda variable sensible vive en provider secrets, nunca en Git.

Regla dura. Ningún secret debe vivir en Git. Archivos prohibidos en repo: .env, .env.local, .env.production, .env.staging.real, service-account.json, private-key.pem. El .gitignore los bloquea explícitamente y solo permite los .env.*.example.

Variables principales por ambiente

VariableTipolocalstagingdemoproduction
APP_ENVconfiglocalstagingdemoproduction
APP_URLpubliclocalhost:3000staging.faroconnect.appdemo.farodireccion.comapp.faroconnect.app
NODE_ENVconfigdevelopmentproductionproductionproduction
DATABASE_URLsecretDocker localSupabase stagingSupabase demoSupabase prod
REDIS_URLsecretDocker localUpstash stagingUpstash demoUpstash prod
AUTH_SECRETsecretchange-merandom 64 charsrandom 64 charsrandom 64 chars
SESSION_COOKIE_NAMEconfigfaro_sessionfaro_sessionfaro_sessionfaro_session
FARO_DEMO_MODEconfigtruetruetruefalse
FARO_DEFAULT_TIMEZONEconfigAmerica/Argentina/Mendoza
FARO_TEST_NOWconfigFecha congeladaFecha congeladaFecha congelada
STORAGE_PROVIDERconfigfakesupabasesupabasesupabase
STORAGE_BUCKET_EVIDENCEpublicfakefaro-staging-evidencefaro-demo-evidencefaro-prod-evidence
STORAGE_BUCKET_REPORTSpublicfakefaro-staging-reportsfaro-demo-reportsfaro-prod-reports
EMAIL_PROVIDERconfigfakeresend_sandboxresend_sandboxresend
EMAIL_API_KEYsecretResend test keyResend test keyResend prod key
EMAIL_FROMpublicFARO Connect <no-reply@farodireccion.com>
EMAIL_ALLOWED_DOMAINSconfigwhitelist demowhitelist demo* (todos)
AI_PROVIDERconfigfallbackfallbackopenai_limitedopenai
AI_API_KEYsecretlimited keyprod key
AI_MODEL_DEFAULTconfigfallbackfallbackgpt-4o-minigpt-4o
AI_DAILY_BUDGET_USDconfig00550
SENTRY_DSNsecretstaging DSNdemo DSNprod DSN
SENTRY_ENVIRONMENTconfigstagingdemoproduction
SENTRY_RELEASEconfigVersión SemVerVersión SemVerVersión SemVer
ENCRYPTION_KEYsecretrandomrandom 32random 32random 32
API_KEY_SALTsecretrandomrandom 32random 32random 32
LOG_LEVELconfigdebuginfoinfowarn

Gestión de secrets por ambiente

AmbienteDónde se guardan los secretsRotación recomendada
local.env.local no commiteado · cada developer maneja el suyoAl cambiar laptop
testGitHub Actions Secrets · scope repoCuando rota un team member
stagingVercel Environment Variables (encrypted at rest)Cada 90 días
demoVercel Environment Variables (encrypted at rest)Cada 90 días
productionVercel + Supabase + Doppler/1Password como secret managerCada 60 días o post-incidente

Pattern .env.example

Archivo versionado en repo que documenta toda variable esperada con valores placeholder. Sirve de contrato entre developers y de checklist al setup en cada ambiente.

▸ .env.example · raíz del repo · versionado
# ============================================================
# FARO Connect · .env.example
# Copiar a .env.local para desarrollo. NO commitear .env.local.
# ============================================================

# Application
APP_ENV=local
APP_URL=http://localhost:3000
NODE_ENV=development

# Database / Cache
DATABASE_URL=postgresql://faro:faro@localhost:54329/faro_demo
REDIS_URL=redis://localhost:63799

# FARO config
FARO_DEFAULT_TIMEZONE=America/Argentina/Mendoza
FARO_DEMO_MODE=true
FARO_TEST_NOW=2026-05-31T18:00:00-03:00

# Auth
AUTH_SECRET=change-me-in-real-env
SESSION_COOKIE_NAME=faro_session
ENCRYPTION_KEY=change-me-32-chars-random
API_KEY_SALT=change-me-32-chars-random

# Storage
STORAGE_PROVIDER=fake
STORAGE_BUCKET_EVIDENCE=faro-evidence-local
STORAGE_BUCKET_REPORTS=faro-reports-local

# Email
EMAIL_PROVIDER=fake
EMAIL_FROM=FARO Connect <no-reply@farodireccion.com>
EMAIL_ALLOWED_DOMAINS=farodireccion.com
EMAIL_API_KEY=

# AI
AI_PROVIDER=fallback
AI_MODEL_DEFAULT=fallback
AI_DAILY_BUDGET_USD=0
AI_API_KEY=

# Observability
SENTRY_DSN=
SENTRY_ENVIRONMENT=local
SENTRY_RELEASE=
LOG_LEVEL=debug

.gitignore mínimo

▸ .gitignore · raíz del repo
# Environment files
.env
.env.*
!.env.example
!.env.demo.example
!.env.staging.example
!.env.production.example

# Build artifacts
node_modules
.next
dist
build
coverage
playwright-report
test-results

# Logs
*.log
.DS_Store

# Credentials / keys
service-account*.json
*.pem
*.key
*.p12

Validación de env al boot

La app valida que toda variable requerida exista al arrancar. Si falta una, falla rápido con mensaje claro. Evita errores diferidos del estilo "todo OK hasta que llega el primer request".

▸ scripts/env/validate-env.ts
const requiredEnv = [
  "APP_ENV",
  "APP_URL",
  "DATABASE_URL",
  "REDIS_URL",
  "AUTH_SECRET"
];

export function validateEnv() {
  const missing = requiredEnv.filter((key) => !process.env[key]);

  if (missing.length > 0) {
    throw new Error(`MISSING_ENV_VARS: ${missing.join(", ")}`);
  }

  // Guard: producción jamás puede correr en modo demo
  if (
    process.env.APP_ENV === "production" &&
    process.env.FARO_DEMO_MODE === "true"
  ) {
    throw new Error("INVALID_ENV: FARO_DEMO_MODE cannot be true in production");
  }
}
05 · Versionado SemVer adaptado

MAJOR.MINOR.PATCH-etapa · convención FARO

FARO usa SemVer estándar MAJOR.MINOR.PATCH + sufijo de etapa (demo, mvp, staging, pilot). El sufijo comunica madurez sin tener que explicar el roadmap cada vez.

ParteUsoEjemplo bump
MAJORCambios incompatibles o release comercial importante0.x.x1.0.0
MINORNueva funcionalidad relevante hacia atrás compatible0.2.00.3.0
PATCHCorrecciones, ajustes menores, hotfix0.2.00.2.1
SufijoEtapa: -demo, -mvp, -staging, -pilot, -prod0.2.00.2.0-staging

Trayectoria recomendada FARO MVP

0.1.0-demo
Hecho

Demo local ejecutable. Corre en laptop con dataset Empresa Demo. No mostrable fuera del equipo.

0.2.0-staging
Actual

Demo staging privada. Accesible vía URL con login. Catálogos canónicos + demo dataset.

0.3.0-mvp
Próximo

MVP técnico completo: motor evaluador + score + workflows + reportes + alertas + IA fallback.

0.4.0-pilot
Roadmap

Piloto con empresa controlada. Dataset real, RLS productiva, monitoreo activo.

1.0.0
Roadmap

Primera versión comercial. Tres pilotos exitosos completos + onboarding self-service.

Tags git de release

Cada release se taguea con prefijo v. El tag es la fuente de verdad: si no hay tag, no hay release.

▸ bash · crear y publicar tag de release
# Crear tag anotado con mensaje
git tag -a v0.2.0-staging -m "FARO staging demo release · dataset Empresa Demo Cuyo S.A."

# Publicar tag a remoto
git push origin v0.2.0-staging

# Listar tags
git tag -l "v*" --sort=-version:refname

Release metadata

Cada deploy genera metadata estructurada que se guarda en ops.releases y se inyecta en headers HTTP + logs + Sentry. Permite responder "¿qué versión está corriendo ahora mismo en staging?" sin abrir el dashboard del provider.

▸ release.json · generado por scripts/release/create-release.ts
{
  "version": "0.2.0-staging",
  "commit_sha": "abc123def456",
  "branch": "main",
  "environment": "staging",
  "deployed_by": "github-actions",
  "deployed_at": "2026-05-31T18:00:00-03:00",
  "migration_version": "V001-V113",
  "score_model": "FARO_SCORE_MVP v1",
  "ai_prompts": ["AI-EXPLAIN-SCORE v1"],
  "demo_dataset": "EMP-DEMO v1",
  "catalogs": {
    "tensions": "TNS_CANONICAL v1",
    "actions": "ACT_CANONICAL v1",
    "evidence": "EVD_CANONICAL v1",
    "rules": "rules_mvp_v1"
  }
}

Activos versionados además del código

FARO no solo despliega código. Despliega reglas de dirección. Por eso versiona explícitamente los catálogos canónicos como parte del release metadata. Cambiar el Score model o un prompt sin bump de versión es la receta para no poder reproducir comportamiento histórico.

ActivoVersión actualDocumento NDA
Score modelFARO_SCORE_MVP v1motor-score-mvp.html
AI promptsAI-EXPLAIN-SCORE v1Pendiente FARO-AI-001
YAML rulesrules_mvp_v1reglas-mvp-yaml.html
Catálogo tensionesTNS_CANONICAL v1catalogo-tensiones-mvp.html
Catálogo accionesACT_CANONICAL v1catalogo-acciones-mvp.html
Catálogo evidenciasEVD_CANONICAL v1catalogo-evidencias-mvp.html
Demo datasetEMP-DEMO v1Pendiente FARO-DEMO-001
06 · DDL ops · 3 tablas

ops.releases · ops.deployments · ops.release_backups

Schema ops aislado del schema faro de dominio. Tres tablas que registran qué se desplegó, cuándo, dónde, cómo terminó y qué backup quedó disponible. Migraciones V110, V111, V112.

6.1 · ops.releases

Catálogo de releases creados (no necesariamente desplegados con éxito). Una release es la intención de subir una versión a un ambiente. El estado se actualiza según el ciclo de vida.

▸ V110__create_ops_releases.sql · PostgreSQL 15+
-- ============================================================
-- FARO-DEPLOY-001 · V110__create_ops_releases.sql
-- Catálogo de releases FARO
-- ============================================================

CREATE SCHEMA IF NOT EXISTS ops;

CREATE TABLE IF NOT EXISTS ops.releases (
  release_id            uuid PRIMARY KEY DEFAULT gen_random_uuid(),

  version               text NOT NULL,
  environment           text NOT NULL CHECK (
    environment IN ('local', 'dev', 'staging', 'demo', 'production')
  ),

  commit_sha            text NOT NULL,
  branch_name           text NOT NULL,

  release_title         text NOT NULL,
  release_notes         text NULL,

  migration_from        text NULL,
  migration_to          text NULL,

  score_model_version   text NULL,
  ai_prompt_versions    jsonb NOT NULL DEFAULT '[]'::jsonb,

  demo_dataset_version  text NULL,

  status                text NOT NULL DEFAULT 'created' CHECK (
    status IN ('created', 'deployed', 'failed', 'rolled_back', 'cancelled')
  ),

  created_by            text NULL,
  deployed_by           text NULL,

  created_at            timestamptz NOT NULL DEFAULT now(),
  deployed_at           timestamptz NULL,
  rolled_back_at        timestamptz NULL,

  metadata              jsonb NOT NULL DEFAULT '{}'::jsonb,

  CONSTRAINT uq_releases_version_env UNIQUE (version, environment)
);

CREATE INDEX IF NOT EXISTS idx_releases_env_time
ON ops.releases (environment, created_at DESC);

CREATE INDEX IF NOT EXISTS idx_releases_status
ON ops.releases (environment, status, created_at DESC);

COMMENT ON TABLE ops.releases IS
'Catálogo de releases FARO. Una release = intención de desplegar versión X en ambiente Y.';

6.2 · ops.deployments

Cada release puede generar múltiples deployments (web, worker, scheduler). Esta tabla guarda el detalle por proceso: estado, duración, health post-deploy, smoke test, errores y URL de logs.

▸ V111__create_ops_deployments.sql · PostgreSQL 15+
-- ============================================================
-- FARO-DEPLOY-001 · V111__create_ops_deployments.sql
-- Deployments individuales por proceso
-- ============================================================

CREATE TABLE IF NOT EXISTS ops.deployments (
  deployment_id     uuid PRIMARY KEY DEFAULT gen_random_uuid(),

  release_id        uuid NULL REFERENCES ops.releases(release_id),

  environment       text NOT NULL CHECK (
    environment IN ('dev', 'staging', 'demo', 'production')
  ),

  provider          text NOT NULL,            -- vercel, render, railway
  service_name      text NOT NULL,            -- web, worker, scheduler

  status            text NOT NULL CHECK (
    status IN (
      'pending',
      'building',
      'migrating',
      'deploying',
      'smoke_testing',
      'success',
      'failed',
      'rolled_back'
    )
  ),

  commit_sha        text NOT NULL,
  image_tag         text NULL,

  started_at        timestamptz NOT NULL DEFAULT now(),
  finished_at       timestamptz NULL,
  duration_ms       integer NULL,

  health_status     text NULL,             -- healthy, degraded, unhealthy
  smoke_status      text NULL,             -- passed, failed, skipped

  error_code        text NULL,
  error_message     text NULL,

  logs_url          text NULL,

  metadata          jsonb NOT NULL DEFAULT '{}'::jsonb
);

CREATE INDEX IF NOT EXISTS idx_deployments_env_time
ON ops.deployments (environment, started_at DESC);

CREATE INDEX IF NOT EXISTS idx_deployments_status
ON ops.deployments (environment, status, started_at DESC);

CREATE INDEX IF NOT EXISTS idx_deployments_release
ON ops.deployments (release_id);

COMMENT ON TABLE ops.deployments IS
'Deployment individual por proceso (web/worker/scheduler) de una release.';

6.3 · ops.release_backups

Para cada release a producción se exige backup pre-deploy. Esta tabla registra qué se respaldó, dónde quedó la referencia (URL/snapshot ID) y si la validación post-backup fue exitosa.

▸ V112__create_ops_release_backups.sql · PostgreSQL 15+
-- ============================================================
-- FARO-DEPLOY-001 · V112__create_ops_release_backups.sql
-- Backups pre-release para auditoría y rollback
-- ============================================================

CREATE TABLE IF NOT EXISTS ops.release_backups (
  release_backup_id  uuid PRIMARY KEY DEFAULT gen_random_uuid(),

  release_id         uuid NULL REFERENCES ops.releases(release_id),
  environment        text NOT NULL,

  backup_type        text NOT NULL CHECK (
    backup_type IN ('database', 'storage', 'config')
  ),

  provider           text NOT NULL,         -- supabase, s3, manual
  backup_reference   text NOT NULL,         -- snapshot ID, URL, archivo

  status             text NOT NULL CHECK (
    status IN ('pending', 'success', 'failed')
  ),

  created_at         timestamptz NOT NULL DEFAULT now(),

  metadata           jsonb NOT NULL DEFAULT '{}'::jsonb
);

CREATE INDEX IF NOT EXISTS idx_release_backups_env_time
ON ops.release_backups (environment, created_at DESC);

CREATE INDEX IF NOT EXISTS idx_release_backups_release
ON ops.release_backups (release_id);

COMMENT ON TABLE ops.release_backups IS
'Backups pre-release con referencia al provider y status de validación.';

Funciones de soporte

Dos helpers TypeScript que envuelven INSERT y UPDATE para que el código de aplicación no escriba SQL crudo a la tabla.

▸ scripts/release/create-release.ts
export async function createRelease(params: {
  client: any;
  version: string;
  environment: "dev" | "staging" | "demo" | "production";
  commitSha: string;
  branchName: string;
  releaseTitle: string;
  releaseNotes?: string;
}) {
  const result = await params.client.query(
    `
    INSERT INTO ops.releases (
      version,
      environment,
      commit_sha,
      branch_name,
      release_title,
      release_notes,
      status
    )
    VALUES ($1,$2,$3,$4,$5,$6,'created')
    RETURNING release_id
    `,
    [
      params.version,
      params.environment,
      params.commitSha,
      params.branchName,
      params.releaseTitle,
      params.releaseNotes ?? null
    ]
  );

  return result.rows[0].release_id;
}
▸ scripts/release/mark-deployed.ts
export async function markReleaseDeployed(params: {
  client: any;
  releaseId: string;
  deployedBy: string;
}) {
  await params.client.query(
    `
    UPDATE ops.releases
    SET
      status = 'deployed',
      deployed_by = $2,
      deployed_at = now()
    WHERE release_id = $1
    `,
    [params.releaseId, params.deployedBy]
  );
}
07 · Build · Docker · health

Dockerfile · workers · smoke tests pre-deploy

Build reproducible con pnpm + Node 20, Dockerfile separado para web y worker, scripts npm consolidados y health checks obligatorios. Sin esto, "funciona en mi máquina" se vuelve política empresarial.

Scripts npm consolidados

▸ package.json · sección scripts
{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "worker:start": "node dist/workers/runWorker.js",
    "scheduler:start": "node dist/workers/runScheduler.js",

    "db:migrate": "node scripts/db/migrate.ts",
    "db:migrate:test": "APP_ENV=test node scripts/db/migrate.ts",
    "db:seed:base": "node scripts/db/seed-base.ts",
    "db:seed:demo": "node scripts/db/seed-demo.ts",
    "db:seed:test": "node scripts/db/seed-test.ts",

    "test:unit": "vitest run",
    "test:sql": "node scripts/tests/run-sql-tests.ts",
    "test:api": "vitest run --config vitest.api.config.ts",
    "test:ui": "playwright test",
    "test:ci": "pnpm test:unit && pnpm test:sql && pnpm test:api",

    "qa:demo-check": "node scripts/qa/demo-check.ts",
    "lint": "eslint . --ext .ts,.tsx",
    "typecheck": "tsc --noEmit",

    "deploy:staging": "bash scripts/deploy/deploy-staging.sh",
    "deploy:demo": "bash scripts/deploy/deploy-demo.sh",
    "release:create": "node scripts/release/create-release.ts",
    "release:smoke": "node scripts/release/smoke-test.ts",
    "ops:backup:pre-release": "node scripts/ops/backup-pre-release.ts",
    "ops:workers:pause": "node scripts/ops/workers-pause.ts",
    "ops:workers:resume": "node scripts/ops/workers-resume.ts"
  }
}

Dockerfile web

▸ Dockerfile · imagen web Next.js
FROM node:20-alpine AS base

WORKDIR /app

RUN corepack enable

COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile

COPY . .

RUN pnpm build

EXPOSE 3000

HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
  CMD wget --no-verbose --tries=1 --spider http://localhost:3000/api/health || exit 1

CMD ["pnpm", "start"]

Dockerfile worker

▸ Dockerfile.worker · imagen procesos asincrónicos
FROM node:20-alpine AS worker

WORKDIR /app

RUN corepack enable

COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile

COPY . .

RUN pnpm build

CMD ["pnpm", "worker:start"]

Health checks obligatorios

Cinco endpoints que la app expone para que el provider (Vercel/Render), el load balancer y los scripts de smoke puedan validar estado.

EndpointRespondeUso
GET /api/health200 si app arrancóLiveness probe · provider lo pollea
GET /api/health/deep200 si app + DB + Redis OKReadiness · pre-routing
GET /api/v1/score/currentScore actual visibleSmoke test post-deploy
GET /api/v1/reports/weekly/latestÚltimo reporte semanalSmoke test reportes
GET /api/v1/ops/job-runsÚltimos jobs ejecutadosSmoke test workers

Smoke test post-deploy

Diez chequeos básicos que se corren contra el ambiente recién desplegado. Si alguno falla, el deploy se marca failed y se considera rollback automático.

▸ scripts/release/smoke-test.ts
export async function runSmokeTest(params: {
  baseUrl: string;
  email: string;
  password: string;
}) {
  // 1. Liveness
  const health = await fetch(`${params.baseUrl}/api/health`);
  if (!health.ok) {
    throw new Error("HEALTH_CHECK_FAILED");
  }

  // 2. Readiness (DB + Redis)
  const deep = await fetch(`${params.baseUrl}/api/health/deep`);
  if (!deep.ok) {
    throw new Error("DEEP_HEALTH_CHECK_FAILED");
  }

  // 3-10. Login demo + dashboard + score + reporte + worker job
  // (implementación completa: ver scripts/release/smoke-test.ts)

  return {
    ok: true,
    checks_passed: 10,
    duration_ms: 4200
  };
}
08 · GitHub Actions

3 workflows · CI · staging · production

Tres workflows YAML versionados en .github/workflows/. CI corre en cada PR. Staging se despliega automáticamente al merge a main. Production requiere approval manual de un reviewer con permiso explícito.

ci.yml Auto · PR + push main

CI · tests + lint + build

Levanta Postgres 16 + Redis 7 como service containers. Corre lint, typecheck, migraciones, seeds test, unit/SQL/API/UI tests, demo-check y build. Bloquea merge si algo falla.

▸ .github/workflows/ci.yml
name: FARO CI

on:
  pull_request:
    branches: [main]
  push:
    branches: [main]

jobs:
  quality:
    runs-on: ubuntu-latest

    services:
      postgres:
        image: postgres:16
        env:
          POSTGRES_USER: faro
          POSTGRES_PASSWORD: faro
          POSTGRES_DB: faro_test
        ports:
          - 5432:5432
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

      redis:
        image: redis:7
        ports:
          - 6379:6379

    env:
      DATABASE_URL: postgresql://faro:faro@localhost:5432/faro_test
      REDIS_URL: redis://localhost:6379
      APP_ENV: test
      FARO_DEMO_MODE: "true"
      AI_PROVIDER: fallback
      EMAIL_PROVIDER: fake
      STORAGE_PROVIDER: fake

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20

      - run: corepack enable
      - run: pnpm install --frozen-lockfile

      - name: Lint
        run: pnpm lint

      - name: Typecheck
        run: pnpm typecheck

      - name: Migrate test DB
        run: pnpm db:migrate:test

      - name: Seed test DB
        run: pnpm db:seed:test

      - name: Unit tests
        run: pnpm test:unit

      - name: SQL / RLS tests
        run: pnpm test:sql

      - name: API tests
        run: pnpm test:api

      - name: UI tests
        run: pnpm test:ui

      - name: Demo check
        run: pnpm qa:demo-check

      - name: Build
        run: pnpm build
deploy-staging.yml Auto · push main

Staging · auto-deploy desde main

Tras merge a main y CI verde, corre tests + build + migraciones staging + seed catálogos base + deploy app + smoke test. Reutiliza secrets de GitHub environment staging.

▸ .github/workflows/deploy-staging.yml
name: Deploy Staging

on:
  workflow_dispatch:
  push:
    branches: [main]

jobs:
  deploy-staging:
    runs-on: ubuntu-latest
    environment: staging

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20

      - run: corepack enable
      - run: pnpm install --frozen-lockfile

      - name: Run CI checks
        run: pnpm test:ci

      - name: Build
        run: pnpm build

      - name: Run migrations staging
        env:
          DATABASE_URL: ${{ secrets.STAGING_DATABASE_URL }}
          APP_ENV: staging
        run: pnpm db:migrate

      - name: Seed base catalogs
        env:
          DATABASE_URL: ${{ secrets.STAGING_DATABASE_URL }}
          APP_ENV: staging
        run: pnpm db:seed:base

      - name: Deploy app to Vercel
        env:
          VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
        run: npx vercel deploy --prod --token=$VERCEL_TOKEN

      - name: Smoke test staging
        env:
          STAGING_URL: ${{ secrets.STAGING_URL }}
        run: pnpm release:smoke -- --url $STAGING_URL
deploy-production.yml Approval required

Production · manual con approval

Disparado manualmente con input version (tag git). Valida tag, corre full test suite, backup pre-release, migraciones producción y deploy. Requiere approval de reviewer en GitHub environment production.

▸ .github/workflows/deploy-production.yml
name: Deploy Production

on:
  workflow_dispatch:
    inputs:
      version:
        description: "Release version (sin prefijo v)"
        required: true

jobs:
  deploy-production:
    runs-on: ubuntu-latest
    environment: production  # requiere approval

    steps:
      - uses: actions/checkout@v4

      - name: Validate release tag
        run: |
          git fetch --tags
          git rev-parse "refs/tags/v${{ github.event.inputs.version }}"

      - uses: actions/setup-node@v4
        with:
          node-version: 20

      - run: corepack enable
      - run: pnpm install --frozen-lockfile

      - name: Full test suite
        run: pnpm test:ci

      - name: Backup pre-release
        env:
          DATABASE_URL: ${{ secrets.PRODUCTION_DATABASE_URL }}
        run: pnpm ops:backup:pre-release

      - name: Run migrations production
        env:
          DATABASE_URL: ${{ secrets.PRODUCTION_DATABASE_URL }}
          APP_ENV: production
          FARO_DEMO_MODE: "false"
        run: pnpm db:migrate

      - name: Deploy production
        env:
          VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
        run: npx vercel deploy --prod --token=$VERCEL_TOKEN

      - name: Smoke production
        env:
          PRODUCTION_URL: ${{ secrets.PRODUCTION_URL }}
        run: pnpm release:smoke -- --url $PRODUCTION_URL

      - name: Mark release deployed
        run: pnpm release:mark-deployed -- --version ${{ github.event.inputs.version }}

GitHub Environments con approval. Los workflows staging, demo y production usan environments configurados en GitHub. production requiere approval manual de un reviewer designado (con permiso explícito en Settings → Environments). El reviewer ve qué versión se despliega, qué migraciones aplica y aprueba o rechaza.

09 · Rollback + backup

Plan rollback · backup pre-release · restore drill

El rollback de código es trivial. El rollback de datos es delicado. Por eso FARO evita migraciones destructivas en el MVP y exige backup antes de cada release a producción. El restore drill mensual valida que el backup efectivamente se puede restaurar.

Qué se puede rollbackear

ElementoRollbackMecanismo
App code (web)Versión anterior en Vercel (1 click)
Worker codeDeploy versión anterior + restart workers
Env varsProvider dashboard (revert)
Migración aditivaNo hace faltaColumna nueva nullable no rompe versión vieja
Migración destructivaDifícilRestore de backup pre-release
Seeds catálogoScript de re-seed con versión anterior
Datos clienteNo improvisarRestore selectivo + validación manual
StorageVersionadoBucket versioning + restore por archivo
Score snapshotsNo borrarRecalcular nueva versión, mantener histórico

Plan rollback básico · 10 pasos

  1. Detectar falla post-release via Sentry, health check o report manual.
  2. Clasificar severidad: SEV-1 (sistema caído), SEV-2 (función crítica rota), SEV-3 (degradación).
  3. Detener nuevos deploys · pausar workflow de staging/production en GitHub.
  4. Diagnosticar capa: ¿falla app, DB, worker o provider?
  5. Si es código, volver a imagen/build anterior (revert en Vercel · 30 seg).
  6. Si es migración, evaluar rollback script (si existe) o restore de backup pre-release.
  7. Si es worker, pausar cola si está duplicando efectos (idempotencia es opcional, no defensa).
  8. Correr smoke test contra la versión recuperada.
  9. Registrar incidente en ops.releases.status = rolled_back + entry en incident log.
  10. Postmortem si SEV-1/SEV-2: causa raíz, qué falló del CI, qué se mejora del proceso.

Script de rollback conceptual

▸ scripts/deploy/rollback.sh
#!/usr/bin/env bash
# Rollback FARO a versión previa con pausa de workers + smoke test
set -euo pipefail

VERSION_PREVIOUS="$1"
ENVIRONMENT="$2"

echo "Rolling back FARO ${ENVIRONMENT} to ${VERSION_PREVIOUS}"

# 1. Pausar workers para evitar efectos duplicados
echo "Pausing workers..."
pnpm ops:workers:pause --env "$ENVIRONMENT"

# 2. Deployar versión anterior
echo "Deploying previous version..."
pnpm provider:deploy --version "$VERSION_PREVIOUS" --env "$ENVIRONMENT"

# 3. Smoke test contra versión recuperada
echo "Running smoke test..."
pnpm release:smoke --env "$ENVIRONMENT"

# 4. Reanudar workers
echo "Resuming workers..."
pnpm ops:workers:resume --env "$ENVIRONMENT"

# 5. Marcar release como rolled_back en ops.releases
pnpm release:mark-rolled-back --version "$VERSION_PREVIOUS" --env "$ENVIRONMENT"

echo "Rollback completed"

Backup pre-release

Antes de cada deploy a producción se ejecutan estos pasos. Sin backup verificado, el deploy se aborta.

Backup 01

Snapshot DB

Supabase snapshot completo + dump SQL como fallback. Referencia guardada en ops.release_backups.

Backup 02

Storage critical

Bucket versioning activo. Snapshot de manifests de archivos para validar integridad post-deploy.

Backup 03

Config no-secret

Snapshot de env vars no secretas + feature flags actuales. Permite revertir configuración sin tocar provider.

Backup 04

Verificar último backup

Confirmar que el último backup automático corrió OK (no usar un backup viejo si los recientes están rotos).

Backup 05

Registrar en ops.release_backups

Insertar referencia (snapshot ID + provider + status) antes de marcar release como deploying.

Backup 06

Restore drill mensual

Una vez al mes, restaurar el último backup en ambiente sandbox y validar que la app levanta. Backup no validado = backup que no existe.

10 · Migraciones y seeds

Orden estricto · backward compat · guards por ambiente

Toda migración está versionada, corre en CI, pasa por staging antes de producción y exige backup si es destructiva. Los seeds demo viven detrás de un guard que bloquea su ejecución en producción accidentalmente.

6 reglas de migración

Regla 01

Versionada

Prefijo numérico ordenado (V001, V002, ...). No reutilizar números. No editar migraciones ya aplicadas.

Regla 02

Corre en CI

Cada migración nueva se aplica en el job de CI contra base test. Si falla, bloquea merge.

Regla 03

Staging antes de prod

Toda migración corre en staging primero. Si tarda 30 min en staging, es un dato real, no un susto en producción.

Regla 04

Destructiva exige backup

DROP COLUMN, DROP TABLE, UPDATE masivo: bloqueados sin backup pre-release validado.

Regla 05

Seeds demo guarded

Seeds de demo no corren en producción salvo bandera explícita. Guard SQL + script de assertion.

Regla 06

Catálogos base sí prod

Tensiones, acciones, evidencias y KPIs canónicos cargan en producción. Son contenido, no datos demo.

Tipos de migración por ambiente

TipoEjemploLocal / TestStagingProduction
Schema (DDL)Crear tabla
Patch aditivoAgregar columna nullable
Seed catálogoTensiones canónicas v1
Seed demoEmpresa Demo Cuyo S.A.No
Data migrationRecalcular score históricoCon control
DestructivaDROP columnaEvitarEvitarBackup obligatorio

Orden de deploy · 11 pasos

  1. CI en verde · lint + typecheck + tests + build OK.
  2. Build de imagen/artefacto reproducible.
  3. Backup pre-release validado (obligatorio en producción).
  4. Pausar workers si la migración cambia schema crítico (actions, score, reports).
  5. Aplicar migraciones en orden estricto.
  6. Aplicar seeds base (catálogos canónicos versionados).
  7. Deploy web (Vercel push).
  8. Deploy workers (Render rolling deploy).
  9. Reanudar workers y verificar primera ejecución.
  10. Smoke tests + health checks contra ambiente recién desplegado.
  11. Registrar release en ops.releases + monitorear 30-60 min post-deploy.

Backward compatibility · patrón expand/contract

Para evitar romper la versión anterior durante el deploy (cuando conviven temporalmente código viejo + nuevo), FARO usa el patrón expand → deploy → migrate data → contract.

Ejemplo · renombrar columna old_name a new_name. Mal: hacer RENAME y deploy. Bien: (1) Migración aditiva agrega new_name nullable. (2) Deploy app que escribe en ambos campos. (3) Migración de datos: copiar old_namenew_name. (4) Deploy app que solo lee new_name. (5) Migración contract: drop old_name. Cinco releases pequeñas en vez de uno grande con riesgo de downtime.

Guard de seeds demo

El seed demo valida explícitamente el ambiente antes de correr. Si está en producción, aborta con mensaje claro. Si FARO_DEMO_MODE no es true, también aborta. Cero margen para "ups, cargué la Empresa Demo en producción".

▸ scripts/db/seed-demo-guard.ts
export function assertDemoSeedAllowed() {
  const appEnv = process.env.APP_ENV;
  const demoMode = process.env.FARO_DEMO_MODE;

  if (appEnv === "production" || demoMode !== "true") {
    throw new Error(
      "DEMO_SEED_NOT_ALLOWED: demo seeds require FARO_DEMO_MODE=true and non-production environment"
    );
  }
}

// Uso en scripts/db/seed-demo.ts
import { assertDemoSeedAllowed } from "./seed-demo-guard";

assertDemoSeedAllowed();  // aborta si no corresponde

// ... resto del seed Empresa Demo Cuyo S.A.

Seeds por ambiente

Seedlocalteststagingdemoproduction
Catálogos base (TNS/ACT/EVD/KPI)
Roles + permisos base
Empresa Demo Cuyo S.A.OpcionalNo
Usuarios demoOpcionalNo
Dataset demo 6 mesesOpcionalNo
Prompts IA
Score model + pesos
11 · Storage · API · email · IA

Buckets separados · API versioning · provider switching

Cada subsistema externo (storage, email, IA) tiene configuración explícita por ambiente. Mezclar buckets staging y producción es la forma clásica de exponer reportes confidenciales por accidente.

Storage buckets por ambiente

AmbienteBucket evidenciasBucket reportesPublic/private
localfake (filesystem temp)fake (filesystem temp)Local solo
stagingfaro-staging-evidencefaro-staging-reportsPrivado · signed URLs
demofaro-demo-evidencefaro-demo-reportsPrivado · signed URLs
productionfaro-prod-evidencefaro-prod-reportsPrivado · signed URLs + encryption at rest

Nunca mezclar buckets staging y producción. Parece obvio. También parecía obvio no borrar producción, y sin embargo la historia del software está llena de "ups". El código de aplicación lee el bucket desde STORAGE_BUCKET_*, jamás hardcoded. Tests RLS validan que el cliente staging no puede acceder a recursos production.

API versioning

Todos los endpoints públicos usan prefijo /api/v1/. No romper contratos sin cambiar versión o mantener compatibilidad. Cuando se introduzca /api/v2/, v1 seguirá vivo al menos un release ciclo para dar tiempo a clientes integrados.

EndpointPropósitoVersionado
GET /api/v1/score/currentFARO Score actual + breakdownEstable
POST /api/v1/reports/weekly/generateDisparar generación reporte semanalEstable
POST /api/v1/ai/explain-scoreExplicación ejecutiva controlada con IAEstable · prompt versionado
GET /api/v1/tensions/activeTensiones disparadas activasEstable
GET /api/v1/actions/openAcciones abiertas asignadasEstable
GET /api/healthLiveness probeNo versionado · contrato fijo
GET /api/health/deepReadiness (DB + Redis)No versionado · contrato fijo

Email provider switching

Configurable por EMAIL_PROVIDER. En staging/demo se usa sandbox o whitelist estricto para evitar enviar emails reales a clientes por error.

AmbienteProviderModoWhitelist activa
localFake (logs a consola)OffN/A
testMock deterministaOffN/A
stagingResend sandboxSandboxfarodireccion.com
demoResend sandboxSandbox o whitelistLista cerrada por demo
productionResend / PostmarkRealOff (envía a todos)

IA provider switching

Configurable por AI_PROVIDER. La demo no debe depender de que el provider IA esté vivo. Siempre hay fallback (template explicativo estático) si la IA falla o el budget diario se acabó.

AmbienteProviderModel defaultBudget diario USDFallback
localFallback (estático)fallback0Templates fijos
testMock deterministamock0Respuestas fijas
stagingFallback o real limitadofallback0-2Templates fijos
demoReal limitadogpt-4o-mini5Templates fijos
productionReal según plan clientegpt-4o50+Templates fijos · retry exponencial
12 · Checklists

Staging · production · post-deploy

Tres checklists imprimibles para usar antes y después de cada release. Son la diferencia entre "creo que está bien" y "verifiqué que está bien".

CHECKLIST · Staging 14 items

Deploy staging · pre + post

  1. CI en verde · lint + typecheck + tests + build OK.
  2. Migraciones test OK contra base CI.
  3. Seeds base test OK (catálogos canónicos cargan).
  4. Build OK en runner CI.
  5. Deploy web a Vercel OK (URL responde 200).
  6. Deploy worker OK (Render running).
  7. Health endpoint OK · GET /api/health = 200.
  8. Demo seed cargado si aplica (Empresa Demo Cuyo S.A.).
  9. pnpm qa:demo-check OK · dataset coherente.
  10. Smoke login OK · usuario demo entra.
  11. Score visible en dashboard ejecutivo.
  12. Reporte semanal visible y descargable.
  13. Sentry sin errores críticos nuevos (5 min).
  14. Release registrada en ops.releases · status deployed.
CHECKLIST · Production 14 items

Deploy production · approval flow

  1. Release aprobada por reviewer en GitHub environment.
  2. CI completo OK · full test suite incluyendo UI tests.
  3. Staging validado · misma versión corriendo OK hace 24+ horas.
  4. Backup pre-release OK · referencia en ops.release_backups.
  5. Migraciones revisadas · no destructivas o con backup verificado.
  6. No demo seed · FARO_DEMO_MODE=false validado.
  7. Env vars verificadas · sin valores placeholder ni "change-me".
  8. Workers pausados si la migración cambia schema crítico.
  9. Deploy app · Vercel push y reload.
  10. Deploy workers · Render rolling deploy completo.
  11. Health endpoint OK · GET /api/health/deep = 200.
  12. Smoke production OK · 10 checks pasan.
  13. Monitoreo activo 30-60 min · ver sección post-deploy.
  14. Release notes cerradas y publicadas a stakeholders.
CHECKLIST · Post-deploy 10 señales

Monitoreo 30-60 min post-release

  1. API errors · tasa no sube respecto a baseline (Sentry).
  2. Latencia P95 · no sube fuerte respecto a hace 1 hora.
  3. DB connections · pool estable, sin saturación.
  4. Worker failures · no hay jobs en DLQ críticos.
  5. Score jobs · corren en su ventana programada.
  6. Report jobs · generación semanal corre normal.
  7. Sentry · sin errores nuevos críticos en 30 min.
  8. Logs · sin loops o spam inusual.
  9. Health · health checks pasan continuamente.
  10. Notificaciones · no se envían duplicadas (ver alertas).
13 · Aceptación · riesgos · roadmap

Criterios de cierre + qué bloquea + qué viene

FARO-DEPLOY-001 queda aceptado cuando se cumplen los 17 criterios funcionales y 15 técnicos. Se rechaza ante cualquiera de los 12 casos críticos. El roadmap define 6 fases de implementación progresiva.

Criterios de aceptación funcional · 17 items

OKAmbientes definidos · 5 con propósito explícito
OKVariables por ambiente · 25+ documentadas en tabla
OKSecrets fuera del repo · .gitignore + provider secrets
OKBuild reproducible · Dockerfile + pnpm lockfile
OKMigraciones controladas · numeradas + CI + staging
OKSeeds separados · por ambiente con guard
OKDemo seed bloqueado en prod · assertDemoSeedAllowed
OKDeploy staging · workflow + smoke automatizado
OKDeploy production · workflow + approval manual
OKSmoke post-deploy · 10 checks + retry exponencial
OKRollback definido · plan 10 pasos + script
OKRelease versioning · SemVer + sufijo etapa
OKRegistro de releases · ops.releases + ops.deployments
OKHealth checks · 5 endpoints obligatorios
OKWorkers considerados · Dockerfile + pausar/reanudar
OKStorage/email/IA por ambiente · provider switching
OKMonitoreo post-release · 10 señales · 30-60 min

Criterios de aceptación técnica · 15 artefactos

OK.env.example versionado
OK.env.demo.example versionado
OKDockerfile + Dockerfile.worker
OKScripts deploy scripts/deploy/*.sh
OKGitHub Actions ci.yml
OKGitHub Actions deploy-staging.yml
OKGitHub Actions deploy-production.yml
OKMigración V110 ops.releases
OKMigración V111 ops.deployments
OKMigración V112 ops.release_backups
OKEnv validation validate-env.ts
OKSmoke script smoke-test.ts
OKRelease notes template RELEASE_NOTES.md
OKRollback script rollback.sh
OKFeature flags ops.feature_flags integrado

Criterios de rechazo · bloqueantes

XCrítica · Secrets en repo
XCrítica · Producción permite demo seed
XAlta · No hay rollback definido
XAlta · No hay smoke post-deploy
XCrítica · No hay migraciones versionadas
XCrítica · Staging y prod comparten datos sin control
XAlta · Workers no considerados
XAlta · No hay health check
XAlta · Deploy depende de pasos manuales no documentados
XMedia · No se registra versión desplegada
XAlta · No hay backup pre-production
XAlta · Email real activo en demo sin whitelist

Riesgos y mitigaciones

RiesgoImpactoSeveridadMitigación
"Funciona en mi máquina"Bug en staging/prod que no se reproduce localAltaDocker + staging + CI con mismo Postgres/Redis
Migración rompe staging/prodDowntime + data lossCríticaCI + staging primero + backup pre-release
Seed demo en producciónDatos sintéticos contaminan datos realesCríticaGuard assertDemoSeedAllowed()
Secrets filtrados a GitCompromiso de DB/IA/emailCrítica.gitignore + secret manager + rotación
Worker viejo escribe esquema nuevoCorrupción de datosAltaPausar workers en migraciones críticas + idempotencia
Env var faltante en bootApp no arranca o falla diferidaMediavalidateEnv() al boot
AI provider caeDemo se ve "rota" en reuniónMediaFallback estático siempre disponible
Email manda a clientes reales por errorSpam o info confidencial filtradaAltaSandbox + whitelist en staging/demo
Storage bucket público por mistakeEvidencia confidencial expuestaCríticaBuckets privados por default + audit periódico
RLS rompe queries en prodApp degradada o caídaAltaTests RLS obligatorios en CI
Rollback imposible por migración destructivaRecovery via backup (downtime)AltaEvitar destructivas + backup obligatorio

Roadmap de implementación · 6 fases

Fase 1

Base deploy

  • Env examples versionados
  • Dockerfile + Dockerfile.worker
  • docker-compose local
  • validate-env
  • README deploy
Fase 2

CI/CD

  • ci.yml
  • test:ci consolidado
  • Build en CI
  • Migraciones test en CI
  • demo-check
Fase 3

Staging

  • Supabase staging
  • Upstash staging
  • Storage staging
  • Deploy staging workflow
  • Smoke staging
Fase 4

Release tracking

  • ops.releases
  • ops.deployments
  • create-release.ts
  • mark-deployed.ts
  • Release notes template
Fase 5

Demo environment

  • demo.farodireccion.com
  • Seed Empresa Demo
  • Usuarios demo
  • Reset semanal
  • Storyboard conectado
Fase 6

Production readiness

  • Backup pre-release
  • Production env
  • Manual approval flow
  • Rollback drill
  • Monitoring 30-60 min

Comandos comunes

Atajo de uso diario · todos los scripts arrancan desde la raíz del repo.

Setup inicial local
cp .env.example .env.local # copiar template
pnpm install # instalar deps
docker-compose up -d # Postgres + Redis
pnpm db:migrate # aplicar migraciones
pnpm db:seed:base # catálogos canónicos
pnpm db:seed:demo # Empresa Demo Cuyo S.A.
pnpm dev # arrancar app local
CI local (antes de PR)
pnpm lint && pnpm typecheck
pnpm test:ci # unit + sql + api
pnpm build # validar build
Release staging
git checkout main && git pull
git tag -a v0.3.0-staging -m "FARO MVP release"
git push origin v0.3.0-staging # dispara workflow
pnpm release:smoke -- --url https://staging.faroconnect.app
Release production (con approval)
gh workflow run deploy-production.yml -f version=0.4.0-pilot
# reviewer aprueba en GitHub UI
pnpm release:smoke -- --url https://app.faroconnect.app
Rollback emergencia
bash scripts/deploy/rollback.sh v0.3.9-pilot production
pnpm release:mark-rolled-back --version 0.4.0-pilot --env production
Ops diario
pnpm ops:backup:pre-release # snapshot DB
pnpm ops:workers:pause --env staging
pnpm ops:workers:resume --env staging
14 · Cross-references

Dónde se cruza el deploy con el resto del pack

El plan de deploy no vive solo. Estos son los puntos donde se conecta, depende o complementa con otros documentos NDA del pack.

IMPLEMENTA
modelo-sql.html

DDL completo del sistema. Aquí se agregan las 3 tablas del schema ops (releases, deployments, release_backups) como migraciones V110-V112.

RASTREA
estado-implementacion.html

Matriz de estado de las 46 tareas FARO. FARO-DEPLOY-001 figura como pieza P1 crítica para pasar de demo local a staging mostrable.

CONTEXTO
roadmap-multiindustria.html

Arquitectura modular que define qué se despliega a producción por industria. El versionado de catálogos atraviesa este roadmap.

EXIGE
seguridad-rls-mvp.html

RLS activa en todos los ambientes desde día uno. Tests RLS bloquean merge en CI. Sin esto, deploy a production no debe ocurrir.

VERSIONA
catalogo-tensiones-mvp.html

Catálogo canónico release-sensitive. Cambios al catálogo viajan por el mismo pipeline que el código (PR + CI + staging + release).

VERSIONA
catalogo-acciones-mvp.html

Catálogo de acciones release-sensitive. Idem tensiones: pipeline completo, no edición a mano en consola productiva.

VERSIONA
catalogo-evidencias-mvp.html

Catálogo de evidencias release-sensitive. Storage buckets configurados por ambiente reciben los archivos cargados según contrato.

VERSIONA
motor-score-mvp.html

Score model con versión propia (FARO_SCORE_MVP v1). Cambios al modelo se reflejan en release metadata + recálculo de snapshots históricos.

PENDIENTE FARO-OPS-001
observabilidad-ops-mvp.html

Observabilidad, jobs, errores y operación técnica. Define los health checks, Sentry config y monitoreo post-release que este documento exige.

PENDIENTE FARO-QA-001
qa-estrategia-mvp.html

Testing, calidad y validación MVP. Define los tests que CI corre antes de cada deploy (unit + SQL + API + UI + demo-check).

PENDIENTE FARO-GOV-001
gobierno-seguridad-mvp.html

Gobierno, seguridad, auditoría y permisos. Define los approvals de GitHub environment y la rotación de secrets para producción.

PENDIENTE FARO-DEMO-001
demo-ejecutable-mvp.html

Dataset Empresa Demo Cuyo S.A. + seed + storyboard. Lo que se carga en staging y demo pero nunca en producción.

15 · Próximos pasos

Qué falta para cerrar deploy operativo

Este documento define qué y cómo. Los pasos siguientes ejecutan la construcción real del pipeline y la sucesión de releases hasta producción piloto.

  1. FARO-DEPLOY-001.1 · Fase 1 base. Crear .env.example, Dockerfile, Dockerfile.worker, docker-compose.yml y validate-env.ts. Asegurar que cualquier developer puede levantar local en menos de 10 minutos.
  2. FARO-DEPLOY-001.2 · Fase 2 CI. Implementar .github/workflows/ci.yml con Postgres + Redis services. Configurar branch protection para main requiriendo CI verde.
  3. FARO-DEPLOY-001.3 · Fase 3 staging. Aprovisionar Supabase staging + Upstash staging + Vercel staging. Implementar deploy-staging.yml con smoke test automatizado. URL staging.faroconnect.app.
  4. FARO-DEPLOY-001.4 · Fase 4 release tracking. Aplicar migraciones V110-V112 (ops.releases, ops.deployments, ops.release_backups). Implementar create-release.ts y mark-deployed.ts integrados al workflow staging.
  5. FARO-DEPLOY-001.5 · Fase 5 demo. Aprovisionar ambiente demo separado con seed Empresa Demo Cuyo S.A. Implementar reset semanal automático. URL demo.farodireccion.com.
  6. FARO-DEPLOY-001.6 · Fase 6 production. Aprovisionar ambiente production con Supabase dedicado. Configurar GitHub environment production con approval manual. Implementar backup-pre-release.ts + restore drill. URL app.faroconnect.app.
  7. FARO-HANDOFF-001 · Paquete técnico para socio técnico / CTO / programador experto. Empaquetar todo lo construido (specs + código + ambientes + tests + demo URL) en un handoff ordenado para conversación profesional con un perfil técnico senior. Incluye NDA, README deploy, docker-compose local, CI status, demo accesible y tour guiado del repo.

Fase 7 implícita · restore drill mensual. Una vez en producción, agendar un restore drill mensual: tomar el último backup automático, restaurarlo en ambiente sandbox y validar que la app levanta sin errores. Un backup que no se restaura periódicamente es un backup que probablemente no funciona el día que se necesita.