Communitygithub.com

afresto-deploy

Guía de despliegue para el proyecto Afresto Next: backend Go, web React (Cloudflare), y mobile React Native (Expo). Incluye reglas de commit, migración de base de datos, y calidad de código.

¿Qué es afresto-deploy?

El skill `afresto-deploy` define el proceso completo de despliegue para el stack de Afresto Next: backend Go (GHCR + Watchtower), web React (Vite → Cloudflare Worker), y mobile React Native/Expo (EAS OTA). Está diseñado para un entorno multisesión, con reglas estrictas para evitar conflictos en el control de versiones (Git), como la adición selectiva de archivos y la verificación del estado del índice. Incluye pasos para migraciones de base de datos, control de calidad del backend Go (gofmt, go build, golangci-lint), y reglas para asegurar que el trabajo significativo se documente en GitHub Issues antes de codificar. El skill enfatiza la colaboración en equipo mediante pull requests, la verificación de despliegues (por ejemplo, código 502 tras merge), y el uso de worktrees limpios para builds web y OTA, evitando que cambios no commitados de otras sesiones se desplieguen accidentalmente.

Compatible con✓Claude Code~Codex CLI~Cursor
npx skills add Afresto-Next/afresto-claude-skills

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Desplegar Afresto Next

Pila: backend Go (push → GHCR → Watchtower ~5 min) · web React (Vite → Cloudflare Worker, manual wrangler deploy) · mobile RN/Expo en repositorio separado <repo-mobile> (EAS OTA). Despliegue a producción = siempre pedir permiso al usuario primero (él aprueba cada vez).

🔴 Regla #1 — este repositorio suele ser usado por MUCHOS AGENTES EN PESTAÑAS simultáneamente

git status frecuentemente muestra archivos modificados de otras sesiones. Por ello:

  • NO usar git add -a / git commit -a / git add . — arrastraría trabajo a medio hacer de otra sesión a tu commit.
  • Hacer stage SELECTIVO de cada archivo tuyo, luego verificar: git diff --cached --name-only — asegúrate de que no hay archivos extraños (comunes: web/src/pages/ChatPage.tsx, backend/internal/db/dbgen/models.go si no es regeneración tuya).
  • Archivos compartidos por dos sesiones (ej. ExamPage) y solo quieres tus hunks: no hagas stash/reset (arrancaría archivos de debajo de otro agente). Edita el índice de Git vía blob: git show :path > tmp → elimina sus hunks de tmp → SHA=$(git hash-object -w tmp) → git update-index --cacheinfo 100644,$SHA,path. El archivo en disco no se toca.
  • 🚨 La herramienta cd de Bash puede PERSISTIR desde cd .../web (o worktree) en llamadas anteriores. Luego git add backend/... FALLA con pathspec did not match (o hace stage del árbol equivocado). Antes de stage selectivo, cd <raíz-repo> && primero (o usa rutas absolutas). Verifica que git diff --cached --name-only muestre las rutas relativas a la raíz correctas (no ../backend, src/...).
  • Después del despliegue: revisa git status — asegúrate de que los archivos de trabajo de otras sesiones sigan intactos.

Puerta de calidad Go (antes de commit backend)

Equivalente a Pint + PHPStan al estilo Laravel para Go. Ejecuta localmente antes de hacer stage de archivos backend:

  • Formato: cd backend && gofmt -l . → debe estar VACÍO. Si hay contenido → gofmt -w . (auto-fix), haz stage del resultado. (Equivalente a pint --test.)
  • Build + vet: go build ./... && go vet ./... en verde.
  • Análisis estático (objetivo): golangci-lint run ./... (envuelve gofmt/govet/staticcheck/errcheck/ineffassign/unused/misspell). Equivalente a phpstan analyse.
  • ⚠️ Estado: puerta de CI aún no activa. Aún no hay backend/.golangci.yml ni .github/workflows/ci.yml, y staticcheck está desactualizado → por ahora formato+build+vet es manual. Plan para forzar en CI + configuración completa: docs/plan/00-tooling-quality-gate.md. Código generado internal/db/dbgen excluido de lint.
  • Las pruebas acompañan los cambios → skill afresto-testing (go test ./...; integration se saltan sin TEST_DATABASE_URL).

🔴 Regla #00 — el trabajo SIGNIFICATIVO va a un Issue de GitHub PRIMERO (desde el 5 de Sep 2026)

Se aplica antes de la Regla #0 (rama + PR): antes de tocar código alguno.

El objetivo no es la formalidad. Las solicitudes que llegan por chat solo las conocen dos partes — el equipo no puede leerlas, el propietario no tiene dónde asignarlas, y el módulo no deja rastro de quién hizo qué.

Cuándo es OBLIGATORIO el issue

Nueva funcionalidad · cambios que ve la escuela · cambios en esquema de BD · cualquier cosa que requiera decisión de producto · trabajo que abarque más de un PR.

Cuándo NO es necesario

Bug claro y acotado · corrección de texto/etiquetas · documentación · seguimiento directo de comentarios de revisión de PR.

Ante la duda → crea un issue. Es barato y se puede cerrar. Este umbral mantiene viva la regla: si cada solicitud trivial se convierte en issue, la gente empezará a saltársela en silencio, y una vez que se salta sin consecuencias, la regla muere.

Qué hace el agente

  1. Explora el código lo suficiente para completar "hallazgos iniciales" y encontrar bloqueos — no adivines.
  2. Crea el issue usando la plantilla .github/ISSUE_TEMPLATE/pekerjaan.md, pon la etiqueta modul:* (eso forma el registro por módulo), más butuh-keputusan si hay algo pendiente.
  3. DETENTE. No codees, no crees ramas, no te asignes a ti mismo. La asignación es acción del propietario; el equipo también necesita espacio para dar opiniones/revisar ideas.
  4. Reporta el número de issue al propietario, junto con las decisiones pendientes (si las hay).

🔑 El propietario puede saltársela diciendo "trabaja ahora" — el issue se crea igual como registro, el trabajo continúa. Esta regla establece el comportamiento por defecto, no encarcela.

Bloqueos descubiertos DURANTE el trabajo

Avisa antes de continuar: comenta en el issue (para que el equipo lo vea) y comunícalo al propietario. Si la decisión detiene el trabajo → añade la etiqueta butuh-keputusan. No te quedes en silencio eligiendo tu propia interpretación y siguiendo adelante — eso genera trabajo "terminado" pero incorrecto.

Cerrando el círculo

El PR debe incluir Closes #<número> en su cuerpo. Eso conecta issue → PR → persona, para que gh issue list --label modul:ujian --state all sea el registro de quién hizo qué en ese módulo. Sin esa línea, el rastro se pierde.

🔴 Regla #0 — NO hacer commit/push directamente a main (equipo de 4 personas, desde el 25 Jul 2026)

Todo el trabajo se hace mediante rama + Pull Request; solo el revisor fusiona a main (merge = desencadena despliegue).

  1. Antes de empezar: git checkout main && git pull — si hay migraciones nuevas, ejecuta las migraciones primero.
  2. Crea/cambia a una rama temática ANTES de tocar código:
    • funcionalidad → feat/<nombre> · bug → bugfix/<nombre> · bug con issue → nueva rama + estudia el issue.
  3. Commit selectivo → git push -u origin <rama> (NO a main).
  4. Abre PR hacia main: gh pr create si gh está logueado; si no, da al usuario la URL https://github.com/Afresto-Next/next/compare/main...<rama>?expand=1.
  5. Revisor aprueba y fusiona → despliegue. NO fusiones tu propio PR.
  • La sección "Push backend / Deploy WEB" más abajo = mecánica que ocurre DESPUÉS de que el PR se fusione a main (normalmente por quien está a cargo del despliegue), no es permiso para hacer push directo a main.
  • Los cambios de skill también pasan por PR (CODEOWNERS).

Secuencia de pasos

1. Commit (Herramienta Bash = Git Bash / sh, NO PowerShell)

  • Mensaje multilínea: heredoc git commit -q -F - <<'EOF' … EOF. NO uses @'…'@ (eso es here-string de PowerShell → el asunto se contamina como @ ...).
  • Termina el mensaje: Co-Authored-By: Claude Opus 4.8 <[email protected]>.
  • Advertencia "CRLF will be replaced by LF" = normal, ignórala.
  • Commit a tu rama de trabajo (feat/* · bugfix/*), no a main (ver Regla #0). El usuario hace commit cuando se le pida.

🔴 NO uses git add -A / git add <dir>/ — ya ha arrastrado archivos del usuario 3 veces

Este repositorio almacena muchos archivos de trabajo del usuario que aún no están trackeados: docs/bskap/ (imágenes+PDF), docs/analisis butir soal/, elibrary/, cloudflare/**/.wrangler/ (caché de cuenta). Una vez con -A, todos se incluyen.

Casos reales: git add docs/ arrastró PDF/xls/imágenes de BSKAP (25 Jul & 2 Ago); git add -A arrastró 446 archivos incluyendo wrangler-account.json (9 Ago).

git add ruta/al/archivo1 ruta/al/archivo2        # UNO POR UNO, siempre
git commit -q -F - <<'EOF'
...
EOF
git show --name-only --format="" HEAD | wc -l   # ¿coincide con lo que pretendías?

Si la cantidad no coincide: git reset --soft HEAD~1 && git reset, vuelve a hacer stage correctamente. Si ya se ha hecho push a tu propia rama, git push --force-with-lease es seguro.

2. Migración de BD — OBLIGATORIO en el commit TIP

.github/workflows/migrate.yml detecta migraciones mediante git diff --name-only HEAD~1 HEAD (solo el commit superior), se ejecuta después de que "Build & Push Docker Images" tenga éxito (workflow_run).

  • Si el push contiene una migración → la migración debe estar en el commit TIP (un commit con migración+código = seguro).
  • La migración se ejecuta automáticamente en la VM del runner self-hosted después del build. run --rm migrate sincroniza → el job FALLA GRAVEMENTE si hay error (no es silencioso).
  • Verifica la migración: revisa la pestaña Actions → "Migrate DB" en verde para ese commit (el CLI gh está disponible: gh run list --workflow="Migrate DB (self-hosted)" --limit 1 — el agente puede comprobarlo solo, sin necesidad de pedir al usuario). Watchtower reemplaza la imagen api en el siguiente poll (~5 min), casi siempre después de que la migración termine.
  • ANTES de hacer push de una migración, verifica el SQL mediante psql BEGIN; … ROLLBACK; en la BD local (v102 = prod). Consulta el skill afresto-db-change / trampa-ingeniería §4.

🔴 Número de migración: verifica JUSTO ANTES DE FUSIONAR, no al crear la rama

Los compañeros de equipo pueden fusionar otra serie de migraciones mientras tu rama está abierta. Los números duplicados hacen que goose ENTRE EN PÁNICO en sortAndConnectMigrations — no es un error SQL, por lo que es fácil de malinterpretar. ✅ No hay daño en los datos: el pánico ocurre al recopilar la lista, antes de ejecutar ninguna.

Caso real (9 Ago 2026): se eligió 00138 al crear la rama; al fusionar, el equipo ya usaba 00138–00140. La primera corrección lo renombró a 00140 y aún así chocó — porque se adivinó de nuevo. Cuenta, no adivines:

git pull                                        # OBLIGATORIO primero, para ver las migraciones de los compañeros
MAX=$(ls backend/migrations/ | sed 's/_.*//' | sort -n | tail -1)
NEXT=$(printf "%05d" $((10#$MAX + 1)))
ls backend/migrations/ | sed 's/_.*//' | sort | uniq -d   # DEBE ESTAR VACÍO

Si ya ocurrió: git mv a un nuevo número, PR pequeño, fusiona, luego vuelve a ejecutar el workflow "Migrate DB (self-hosted)" desde la pestaña Actions.

3. Push backend (DESPUÉS de que el PR se fusione a main — ver Regla #0)

Fusionar PR → main actualizado → GHCR → Watchtower ~5 min reemplaza la imagen. Sin cambios en backend = no es necesario esperar a Watchtower. (Al desarrollar: git push -u origin <rama> luego PR — no hagas push a main.)

⚠️ DESPUÉS de fusionar backend: VERIFICA 502 (ya ha ocurrido 6 veces)

Recrear el contenedor api/worker suele provocar una caída total — todos los contenedores Up pero todas las rutas 502. Prácticamente cada fusión de backend es un lanzamiento de dados.

curl -s -o /dev/null -w "%{http_code}
" --max-time 15 https://next.afresto.co/api/v1/auth/me
# 401 = SALUDABLE (pide autenticación) · 502 = caída

Si es 502 → Actions → "Ops — pulihkan API" → restart-docker (sin SSH, recupera en <1 minuto). No investigues la funcionalidad recién desplegada: 502 + contenedor Up = reiniciar el daemon Docker. Detalles: docs/ops-auto-pulih.md.

4. Desplegar WEB — desde WORKTREE LIMPIO (porque hay muchas pestañas de agente)

npm run build lee TODO el working tree → el trabajo a medio hacer de otras sesiones también se publica. Por lo tanto, construye y despliega desde un worktree en tu commit:

rm -rf /c/wtd 2>/dev/null; git worktree add --detach /c/wtd <SHA-de-tu-commit>
cd /c/wtd && git status --short   # debe estar VACÍO (limpio)
cd /c/wtd/web && npm ci && npm run build
cd /c/wtd/cloudflare/web-worker && npx wrangler deploy
cd <repo-projectTwo> && git worktree remove --force /c/wtd && git worktree prune
  • 🔥 La ruta del worktree DEBE SER CORTA (/c/wtd) — el bloc de notas + el nombre largo del PDF de calendario académico DKI → error en Windows "Filename too long" al hacer checkout.
  • Verifica que el bundle sea correcto si es necesario: grep -rl "<marca-de-tu-funcionalidad>" /c/wtd/web/dist/assets/*.js — pero ten cuidado con falsos positivos (una marca común podría venir de otra funcionalidad).
  • El caché de Cloudflare dura ~20 seg → prueba con Ctrl+F5. (200 pero tamaño ~397 bytes = fallback, no el activo real.)

5. Mobile OTA — repositorio separado, --environment OBLIGATORIO

cd "<repo-mobile>"
npx eas update --branch preview --environment preview --message "…" --non-interactive
  • 🔥 --environment OBLIGATORIO (preview/production) — sin él, la actualización va al entorno incorrecto/falla.
  • El repo mobile tiene remoto GitHub (Afresto-Next/afresto-next-mobile) → aplica Regla #0: rama → PR → fusionar, no hagas push directo a main.
  • OTA solo JS/activos. Cambios en módulo nativo → CRASH si se hace vía OTA → obligatorio reconstruir.

🔥 eas update envía el WORKING TREE, no el commit

Exactamente igual que npm run build en el paso 4 — todo el directorio de trabajo se incluye en el bundle, incluyendo el trabajo a medio hacer de otras sesiones. La marca * en la línea Commit <hash>* en el resultado de la publicación = árbol sucio.

OBLIGATORIO justo antes de publicar — imprime, luego compara con la línea Commit en el resultado:

echo "publicar desde: $(git branch --show-current) @ $(git log --oneline -1 --format=%h)"
git log --oneline HEAD..origin/main   # VACÍO = no hay trabajo de otros que se haya quedado atrás
  • Publica desde main, no desde una rama de funcionalidad. Desde una rama, el trabajo de otros que ya se ha fusionado DESAPARECE del canal — OTA reemplaza todo el bundle, no aplica parches.
  • Producción: aparta primero los cambios no commitados de otras sesiones (git stash push -- <archivos> … git stash pop). Producción no debe llevar código no revisado.

🚨 Las ramas pueden CAMBIAR durante el trabajo (muchas pestañas de agente, un solo clon)

Caso 20 Ago 2026: la publicación de preview claramente desde main @ d0938fa; el comando siguiente encontró que HEAD ya estaba en otra rama @ 8126a50 — otra sesión cambió de rama en el mismo clon. Producción llegó a ejecutar código de 3 PRs de más, sin ningún error.

➡️ No asumas que la rama sigue siendo la misma entre comandos. Vuelve a imprimir cada vez (el comando de arriba). ➡️ Prevención real: OTA desde WORKTREE, igual que el despliegue web en el paso 4 — otras sesiones no pueden cambiar tu rama:

rm -rf /c/wtm 2>/dev/null; git worktree add --detach /c/wtm <SHA-de-tu-commit>
cd /c/wtm && git status --short          # debe estar VACÍO
npm ci && npx eas update --branch <ch> --environment <env> --message "…" --non-interactive
cd <repo-mobile> && git worktree remove --force /c/wtm && git worktree prune

🪤 Ruta de worktree corta (/c/wtm) — misma razón que en el paso 4. 🪤 npm ci en el worktree lleva tiempo; para OTA pequeña, puedes quedarte en el clon principal siempre que la verificación de rama+commit de arriba se ejecute y los resultados coincidan.

🏢 GHCR y organización de GitHub — el repo ahora está en la org Afresto-Next

Los repos (next, afresto-next-mobile, afresto-claude-skills) se movieron de ristology/* → org Afresto-Next (20 Jul 2026). Runbook de transferencia completo: docs/runbook-transfer-next-org.md.

  • 🚨 Transferir repo a org = la ruta de GHCR sigue al propietario → Watchtower SE CONGELA EN SILENCIO. CI etiqueta la imagen ghcr.io/${GITHUB_REPOSITORY_OWNER}/afresto-next-{api,web,migrate} — el propietario sigue al dueño del repo. Se...

(truncado para traducción)

Skills relacionados