Skip to main content

Entornos y despliegue

El panel es una SPA de Vite desplegada en Vercel, más una función serverless para los reportes. El código entra por staging y llega a producción con una PR de staging a main. Esta página cubre los tres entornos, cómo se construye cada uno, qué hacen los workflows de GitHub y cómo el panel recoge un deploy nuevo sin romper las sesiones abiertas.

Entornos

El entorno lo decide VITE_APP_ENV. src/config/env.ts lo valida con Zod al arrancar y solo acepta tres valores:
EnvSchema.parse lanza una excepción si una variable obligatoria falta o no es válida. Por ejemplo, si VITE_APP_ENV no es uno de los tres valores o VITE_API_BASE_URL no es una URL. La app no llega a montarse y verás una página en blanco.
Los detalles de Sentry y PostHog por entorno están en Observabilidad.

Scripts

Cada modo de Vite carga su .env.<modo> (.env.localdev, .env.staging, .env.prod). Todos los .env* están en .gitignore. Para montar el tuyo, sigue el inicio rápido.
Vercel no usa los scripts build:*. Ejecuta un vite build genérico, con modo production, y no pasa por tsc. Por eso el entorno de un deploy lo decide la variable VITE_APP_ENV configurada en Vercel, no el modo de Vite. vite.config.ts activa el plugin de Sentry según VITE_APP_ENV por el mismo motivo. Antes se decidía por el modo, y en Vercel nunca se activaba.

Variables de entorno

Nunca pongas valores en la wiki ni en el repo. Estos son solo los nombres:
env.template no incluye las variables de PostHog ni las de Slack. Las de PostHog aparecen en src/config/env.ts. Las de Slack solo existen en el entorno de Vercel.

Flujo de ramas

  1. Crea tu rama desde staging. En el historial verás prefijos como feat/, fix/, chore/ y refactor/, normalmente con el área del panel (feat/feedback/report-images). También hay ramas generadas desde Linear (<usuario>/viv-XXXX-...).
  2. Abre la PR contra staging. El título sigue Conventional Commits y lleva el ID de Linear: feat(feedback): attach screenshots to panel reports (VIV-2787).
  3. Para publicar, abre una PR de staging a main. Cada merge a main es un deploy de producción.
El título de tu PR es lo que aparece en la notificación de deploy. GitHub guarda el título en el cuerpo del merge commit, y deploy-notify.yml lo lee de ahí. Escríbelo para que se entienda sin abrir la PR.

Vercel

vercel.json define dos cosas. Rewrite SPA. Cualquier ruta que no empiece por api/ y no contenga un punto se sirve con /index.html. Así React Router resuelve las rutas del panel. Los archivos estáticos (/version.json, /assets/...) se sirven tal cual. La exclusión de api/ deja pasar las peticiones a la función de reportes. Redirects temporales (permanent: false) para enlaces profundos del panel antiguo:

Función serverless

api/reports.ts se despliega como función de Vercel junto a la SPA. La documentación completa está en Reportes. Dos detalles de build:
  • tsconfig.api.json typechea api/ y src/core/feedback/. Está referenciado desde tsconfig.json, así que tsc -b lo incluye.
  • Los imports de api/ llevan la extensión .js explícita (./_lib/auth.js). El runtime de la función lo necesita. Si añades un import sin extensión, la función falla en Vercel aunque el typecheck pase.

Nombre de la release

vite.config.ts calcula el nombre de la release como panel-front@<sha>. El SHA sale de VERCEL_GIT_COMMIT_SHA. Si no existe, usa git rev-parse HEAD, y si eso falla, unknown. Ese nombre se usa en tres sitios:
  • Se inyecta en la app como la constante global __APP_RELEASE__.
  • Se escribe en version.json en la raíz del build.
  • Se usa como release de Sentry al subir los source maps.

Workflows de GitHub

CI (ci.yml)

Corre en cada PR y en cada push a staging y main. Un push nuevo a la misma rama cancela la ejecución anterior. Pasos, con Node 20:
  1. yarn install --frozen-lockfile
  2. npx tsc -b
  3. npx eslint .
  4. npx vite build, el mismo build genérico que ejecuta Vercel
Es la única verificación de tipos antes de producción, porque Vercel no ejecuta tsc.

Notificación de deploy (deploy-notify.yml)

Corre en cada push a main y avisa en Slack cuando producción sirve el commit nuevo.
1

Esperar a producción

Consulta /version.json en los dos dominios de producción (panel.vivla.com y panel.vivla.dev) cada 10 segundos, hasta 60 veces. Termina cuando alguno devuelve panel-front@<sha del push>.
2

Montar la lista de cambios

Lee con git log --merges los cuerpos de los merge commits entre el main anterior y el nuevo, es decir, los títulos de las PRs. Si hay más de uno, descarta el primero, que es la propia PR de staging a main. Se queda con un máximo de 10. Si no puede calcular el rango, usa la primera línea del mensaje del commit.
3

Publicar

Envía el mensaje con el webhook del secret SLACK_WEBHOOK_URL. Si el secret no existe, se salta el paso sin fallar.
El mensaje sigue el formato estándar de notificaciones de Slack:
  • Verde si producción sirvió el commit. Rojo si no lo hizo en 10 minutos.
  • Campos Commit, Author e Incluye.
  • Enlaces al panel, a la release en Sentry y a la ejecución del workflow.
El workflow consulta los dos dominios porque el panel está en transición hacia panel.vivla.com. Si ninguno sirve el commit, el enlace Panel apunta al último dominio que respondió, o a panel.vivla.com si no respondió ninguno.

CX Assistant (cx-assistant.yml)

Permite que alguien del equipo de CX pida un cambio pequeño en el panel y reciba una PR generada por Claude Code. Solo se lanza a mano con workflow_dispatch. No reacciona a pushes ni a PRs. Recibe cuatro entradas: issue_number (issue de GitHub de la solicitud), prompt (el cambio pedido), requester (email de quien lo pide) y base_branch (por defecto main).
1

Marcar el issue

Quita la etiqueta cx-request del issue y pone cx-processing.
2

Crear el issue en Linear

Crea un issue en Linear con el prompt y un enlace al issue de GitHub. Deja un comentario de “procesando”. Si Linear falla, solo emite un aviso y sigue.
3

Generar el cambio

Crea la rama cx-assistant/<issue> desde base_branch y ejecuta anthropics/claude-code-action. Las herramientas están limitadas: editar, escribir, leer y buscar archivos, más git diff, git status y git log. El prompt pide cambios mínimos y que se respeten las convenciones del repo.
4

Abrir la PR o avisar

Si hay cambios, hace commit, abre la PR [CX Assistant] #<issue> contra base_branch y etiqueta el issue con cx-pr-created. Si no hay cambios, etiqueta con cx-no-changes. Si algo falla, con cx-failed. En los tres casos comenta en el issue de GitHub y en el de Linear.
Usa los secrets ANTHROPIC_API_KEY y LINEAR_API_KEY. Las PRs siempre necesitan revisión de un desarrollador antes del merge.
base_branch vale main por defecto. Si quien lanza el workflow no lo cambia, la PR se abre directamente contra main y se salta staging. Revisa la rama base antes de mergear.

Recarga tras un deploy

Los chunks de la SPA llevan hash. Cada deploy borra los anteriores, y una pestaña abierta con la versión vieja rompe al navegar a una ruta que aún no había cargado. El panel lo resuelve con dos mecanismos. Ninguno muestra un aviso: los dos recargan la página en silencio.

Recarga en la siguiente navegación (useVersionCheck)

PortalRoot monta useVersionCheck, que consulta /version.json con cache: "no-store":
  • al volver a la pestaña (focus y visibilitychange),
  • cada 5 minutos.
Si la release publicada no coincide con __APP_RELEASE__, marca que hay una versión nueva. En el siguiente cambio de ruta hace window.location.reload(). Nunca recarga en mitad de lo que estás haciendo: espera a que navegues.
El chequeo solo corre dentro del área autenticada, donde está PortalRoot. Solo reacciona a cambios de pathname, no de query string. En yarn dev no hay version.json (se genera solo en el build), así que no hace nada.

Recuperación de chunks (lazyWithReload)

Todas las páginas del router se cargan con lazyWithReload en vez de React.lazy. Si el import() de un chunk falla:
  1. La primera vez guarda una marca en sessionStorage y recarga la página.
  2. Si tras recargar el chunk carga bien, borra la marca y envía a Sentry el mensaje informativo “Recovered from stale chunk after deploy”.
  3. Si vuelve a fallar con la marca puesta, la borra y relanza el error. Lo recoge el RouteErrorBoundary.
La marca evita bucles de recarga cuando el problema no es un deploy.

Anuncio del release

La skill de Claude Code .claude/skills/product-release no despliega nada. Te ayuda a redactar el anuncio en Slack de lo que acabas de publicar: qué cambia, a quién afecta y si hay que hacer algo. Parte de la plantilla del handbook de producto y devuelve el mensaje listo para pegar, con un checklist previo a la publicación.

Código