Flujo de deploy (staging → prod)
Desde 2026-06-04 hay un pipeline de deploy con dos ambientes. La regla de oro:
Staging se despliega solo. Producción NUNCA se despliega sola. Web y API van a prod con un botón manual cada uno, y tú eliges el orden.
push a `staging` ──► STAGING (automático) · API: GitHub Actions → tcgcards-api-staging · Web: Vercel → staging.tcgcards.cl (basic auth)
↓ pruebas todo en staging ✓
merge la RAMA del feature →`main` (NO despliega nada · NUNCA `staging`→`main`)
↓ pulsas los botones, en el orden que quieras
Actions "Deploy API → PROD" ──► tcgcards-api (Cloud Run) Actions "Deploy Web → PROD" ──► tcgcards.cl (Vercel)Modelo de ramas (la regla de oro)
Sección titulada «Modelo de ramas (la regla de oro)»main es la fuente de verdad de prod. staging es solo una rama de despliegue = main + lo que tenga de test-only. Las tres reglas:
- Los features salen de
mainy se integran amain(vía PR). Integrar amaines seguro porquemainno se despliega solo. - Para probar en staging: la rama del feature →
staging(git merge feat/...sobrestaging+ push) → staging se despliega. - NUNCA
staging → main.
El orden en la práctica (más seguro): primero feat → staging y pruebas en staging; recién cuando funciona, feat → main para prod. Probar antes de integrar a main evita meter a la fuente de verdad algo no validado.
Qué vive solo en staging (y por qué)
Sección titulada «Qué vive solo en staging (y por qué)»| Pieza | Repo | Dónde vive | Por qué |
|---|---|---|---|
test-login (entrar como usuario ficticio sin Google) | web | solo staging (commit que nunca se integra a main) | Es un bypass de auth. Aunque está gateado fail-closed (VERCEL_ENV !== 'production' + el provider ni se registra), por política no toca el bundle de prod. |
seam MP_FAKE_PAYMENTS (pagos sintéticos para la suite E2E) | api | main, inerte | Menos sensible (fake de pagos), auditado y con cobertura de unit-tests en CI. Fail-closed: en prod el flag está apagado y, si se prendiera, el server no arranca (exige no-prod + DB con “staging”). |
El test-login es la razón concreta por la que nunca se fusiona staging→main: ese commit
vive en staging para siempre y jamás debe llegar a prod.
Pasar a STAGING (automático)
Sección titulada «Pasar a STAGING (automático)»Cuándo: cada vez que quieres probar un cambio en un ambiente real sin tocar prod.
- Trabajas en una rama de feature (o directo en
staging). - Llevas los cambios a la rama
stagingdel repo correspondiente:Ventana de terminal git checkout staginggit merge tu-rama # o trabajas directo sobre staginggit push origin staging - El push dispara el deploy a staging automáticamente:
- API (
tcgcards-api): GitHub Actions → workflow “Deploy API → STAGING” → ejecuta CI (lint+test+build) → compila y sube la imagen a Artifact Registry →gcloud run deploy tcgcards-api-staging. - Web (
tcgcards-web): Vercel (nativo) detecta el push astaging→ build → publica enstaging.tcgcards.cl.
- API (
Cómo verificar:
- API: GitHub →
tcgcards-api→ Actions → el run de “Deploy API → STAGING” en verde. Y:Ventana de terminal curl -s -o /dev/null -w "%{http_code}\n" \https://tcgcards-api-staging-1033181994095.us-central1.run.app/api/v1/health# Esperado: 200 - Web: abrir
https://staging.tcgcards.cl→ pide basic auth (usuariostaging+ la clave configurada enSTAGING_BASIC_AUTH). Adentro, navegas como en prod pero contra la DB/credenciales de staging.
Pasar a PROD (manual, con tu OK)
Sección titulada «Pasar a PROD (manual, con tu OK)»Cuándo: ya probaste en staging y quieres llevar el cambio a los usuarios.
1. Promover el código a main
Sección titulada «1. Promover el código a main»Fusionas la rama del feature (NO staging) en main:
git checkout maingit merge feat/tu-feature # ⬅ la RAMA del cambio, NUNCA `git merge staging`git push origin main2. Pulsar los botones — tú eliges el orden
Sección titulada «2. Pulsar los botones — tú eliges el orden»A veces conviene la API primero (si el cambio web depende de un endpoint nuevo), a veces la web primero. Son independientes:
- API → PROD: GitHub →
tcgcards-api→ pestaña Actions → “Deploy API → PROD (manual)” → Run workflow → branchmain→ Run. - Web → PROD: GitHub →
tcgcards-web→ pestaña Actions → “Deploy Web → PROD (manual)” → Run workflow → branchmain→ Run.
Cada botón ejecuta CI primero; si lint/test/build fallan, no despliega.
Cómo verificar:
- API:
Ventana de terminal gcloud run revisions list --service tcgcards-api \--region us-central1 --project api-cards-prod --limit 1 \--format="value(metadata.name, metadata.creationTimestamp)"# Debe aparecer una revisión nueva (timestamp de recién).curl -s -o /dev/null -w "%{http_code}\n" \https://tcgcards-api-1033181994095.us-central1.run.app/api/v1/health# Esperado: 200 - Web: abrir
https://tcgcards.clen incógnito → ver el cambio. En el log del run,vercel deployimprimeAliased https://tcgcards.cl.
Promoción selectiva (algo solo en staging, o mandar UNA sola cosa a prod)
Sección titulada «Promoción selectiva (algo solo en staging, o mandar UNA sola cosa a prod)»¿Quieres probar un cambio en staging pero nunca mandarlo a prod, mientras sigues mandando otras cosas a prod? Sí se puede. La clave:
A prod llega SOLO lo que integras a
main. Lo que esté enstagingpero nunca se integre amain, nunca llega a prod — aunque viva en staging para siempre.
Por eso, la forma robusta de promover es fusionar la rama del cambio en main, no la
rama staging entera. (Fusionar staging→main solo es seguro cuando todo lo que hay
en staging va a prod.)
Ejemplo — hoy pruebo A (solo staging); mañana pruebo B y mando solo B a prod:
# HOY — A a staging, sin prodgit checkout staging && git merge feat/A && git push origin staging # staging despliega A; pruebas# (no ejecutas el botón de prod → A queda en staging y en feat/A; prod sigue sin A)
# MAÑANA — B a staging y solo B a prodgit checkout staging && git merge feat/B && git push origin staging # staging = A + B; pruebas Bgit checkout main && git merge feat/B && git push origin main # ⬅ la rama de B, NO "merge staging"# → botón "Deploy → PROD". Solo B va a prod; A nunca (nunca fusionaste feat/A en main).Dos aclaraciones:
- El entorno de staging corre UNA sola versión (el estado de la rama
staging). “Dejar A en staging” = A está ahí hasta que despliegues otra cosa; no es que A y B convivan como dos deploys separados. staginges desechable. Si se llena de experimentos que nunca van a prod y la quieres limpiar (o probar B aislado, sin A encima), reséteala a prod:Ventana de terminal git checkout staging && git reset --hard main && git merge feat/B && git push --force origin staging
Rollback (si un deploy de prod sale mal)
Sección titulada «Rollback (si un deploy de prod sale mal)»Web (Vercel): Vercel → tcgcards-web → Deployments → el deploy anterior bueno → “Promote to Production” (instantáneo, sin rebuild).
API (Cloud Run): apuntar el tráfico a la revisión anterior:
gcloud run revisions list --service tcgcards-api \ --region us-central1 --project api-cards-prod --limit 5gcloud run services update-traffic tcgcards-api \ --to-revisions=<REVISION_ANTERIOR>=100 \ --region us-central1 --project api-cards-prodCrear/actualizar un Cloud Run Job + Scheduler
Sección titulada «Crear/actualizar un Cloud Run Job + Scheduler»Los botones de deploy solo despliegan el servicio (tcgcards-api). Los Cloud Run Jobs
—tareas que corren por agenda, como refresh-prices o auto-price (la marca visible es
“Precios dinámicos”)— NO los crea el pipeline de deploy: se crean/actualizan a mano con
gcloud. Este es el procedimiento tal como se montó auto-price el 2026-06-30.
1. Sacar la imagen de prod del servicio
Sección titulada «1. Sacar la imagen de prod del servicio»gcloud run services describe tcgcards-api \ --region us-central1 --project api-cards-prod \ --format="value(spec.template.spec.containers[0].image)"# → ...docker.pkg.dev/.../tcgcards-api@sha256:<SHA> (cópiala como <IMG>)2. Crear (o actualizar) el Job
Sección titulada «2. Crear (o actualizar) el Job»gcloud run jobs create <NOMBRE> \ --image="<IMG>" \ --command=node \ --args="dist/jobs/<X>.js" \ --region us-central1 --project api-cards-prod \ --service-account=1033181994095-compute@developer.gserviceaccount.com \ --memory=512Mi --cpu=1 \ --task-timeout=10800 --max-retries=0 \ --set-secrets="MONGODB_URI=mongodb-uri:latest,ADMIN_TOKEN=admin-token:latest" \ --set-env-vars="..." # mismas env (placeholders) que refresh-prices3. Darle permiso para que el Scheduler lo invoque
Sección titulada «3. Darle permiso para que el Scheduler lo invoque»Sin esto, el Scheduler falla con PERMISSION_DENIED:
gcloud run jobs add-iam-policy-binding <NOMBRE> \ --region us-central1 --project api-cards-prod \ --member="serviceAccount:1033181994095-compute@developer.gserviceaccount.com" \ --role="roles/run.invoker"4. Crear el Scheduler (cron) que dispara el Job
Sección titulada «4. Crear el Scheduler (cron) que dispara el Job»gcloud scheduler jobs create http <NOMBRE>-daily \ --location us-central1 --project api-cards-prod \ --schedule="..." --time-zone="America/Santiago" \ --uri="https://us-central1-run.googleapis.com/apis/run.googleapis.com/v1/namespaces/api-cards-prod/jobs/<NOMBRE>:run" \ --http-method=POST \ --oauth-service-account-email="1033181994095-compute@developer.gserviceaccount.com"5. Probar que dispara una ejecución
Sección titulada «5. Probar que dispara una ejecución»gcloud scheduler jobs run <NOMBRE>-daily --location us-central1 --project api-cards-prodgcloud run jobs executions list --job <NOMBRE> \ --region us-central1 --project api-cards-prod --limit 1# Debe aparecer una ejecución nueva (timestamp de recién).Troubleshooting (lecciones del montaje)
Sección titulada «Troubleshooting (lecciones del montaje)»- El botón falla en el job
ci: el deploy gatea en CI. Si lint/test/build están rojos, arréglalo y solo entonces despliega. (No es que “el deploy falló”: no llegó a desplegar a propósito.) - Web prod compila en la infra de Vercel (
vercel deploy --prod, sin--prebuilt). NO se compila local en CI porquenext buildnecesita las System env vars de Vercel (ej.VERCEL_URL) y falla conERR_INVALID_URL. - API usa
--imageen CI (build+push de la imagen a Artifact Registry, luegogcloud run deploy --image), no--source. Así el deployer no necesita Cloud Build ni permisos de storage de proyecto. (En local,scripts/deploy-staging.sh/deploy-prod.shcaen a--sourcesi no hayIMAGE.) react-hooks/refs/ React Compiler: algunos errores de lint son falsos positivos (pasar un objeto ref a un hijo, leer una prop no-ref de un hook). Se suprimen con// eslint-disable-next-line react-hooks/refs+ justificación, no se debilita el lint global.
Referencia rápida
Sección titulada «Referencia rápida»| Pieza | Valor |
|---|---|
| Web prod | tcgcards.cl (Vercel, branch main, auto-deploy apagado → solo botón) |
| Web staging | staging.tcgcards.cl (Vercel, branch staging, basic auth + noindex) |
| API prod | tcgcards-api (Cloud Run) · https://tcgcards-api-1033181994095.us-central1.run.app |
| API staging | tcgcards-api-staging (Cloud Run) · https://tcgcards-api-staging-1033181994095.us-central1.run.app |
| Auth GitHub→GCP | Workload Identity Federation (sin keys), atado al repo + environment |
Variables/secrets de repo (ya configurados):
tcgcards-api(Variables):GCP_WIF_PROVIDER,GCP_DEPLOYER_SA,STAGING_MP_MODE,STAGING_ADMIN_EMAIL.tcgcards-web(Secret):VERCEL_TOKEN. (Variables):VERCEL_ORG_ID,VERCEL_PROJECT_ID.
Runbook técnico completo + setup de una sola vez (WIF, rollout en orden, seguridad):
tcgcards-api/docs/deployment-flow.md en el repo.