Despliega una nueva versión coordinada de SecondMind en los 3 artefactos del ecosistema (hosting web + desktop Tauri + mobile Android vía Capacitor) cuando el user acumuló cambios y pide "actualizar versión", "nueva release", "deploy de todo", "actualizar app escritorio y móvil", "release alineado", "bump versión en todo el ecosistema", o variantes. Úsala SIEMPRE que la intención sea publicar una versión nueva a producción que impacte los 3 frentes a la vez — bumpea los 5 archivos de versión atómicamente, respeta el orden crítico (hosting ANTES del tag de git), y monitorea el workflow de GitHub Actions hasta que termine. NO la uses para deploys parciales (solo hosting, solo functions, solo rules) ni para hotfixes que no tocan desktop/mobile — esos son comandos puntuales, no un release coordinado.
Instal·la
npx skillscat add sebasgc0399/secondmind/release-ecosystem Instal·la des del registre de SkillsCat.
Release Ecosystem — SecondMind
Coordina el despliegue sincronizado de las 3 distribuciones (web, desktop, mobile) para que queden en la misma versión numerada. La web sale por Firebase Hosting (manual), desktop y mobile los publica GitHub Actions cuando se pushea un tag v*.
La skill existe porque el pipeline tiene un orden crítico no obvio (hosting debe publicar antes del tag para que desktop/mobile no lleguen a usuarios apuntando a una web que todavía sirve la versión anterior) y porque hay 5 archivos de versión que bumpear atómicamente — olvidarse uno produce builds desincronizados que el user detecta recién al descargar.
Cuándo activarte
Sí:
- "Actualizar versión / nueva release / release alineado" tras acumular features o cambios.
- "Deploy de todo" o "bump de ecosistema".
- El user quiere que web + desktop + mobile queden coordinados en la misma versión.
No (estos son tareas puntuales, no requieren skill):
- "Deploy hosting" solo →
npm run deploy. - "Deploy functions" solo →
npm run deploy:functions. - "Deploy rules" solo →
npm run deploy:rules. - Hotfix que solo toca hosting y no necesita reflejarse en desktop/mobile (ej. cambio de copy que no impacta la app empacada).
- Bump de versión sin intención de liberar (ej. bump experimental local).
Pre-flight checks (antes de tocar nada)
Estos 4 checks bloquean o abren conversación. No saltear ninguno — cada uno salvó un release en el pasado.
1. Working tree limpio
git status --shortSi devuelve algo, parar. El bump debe ir en su propia branch limpia sin mezclarse con trabajo en curso. Preguntar al user qué hacer con los cambios sin commitear.
2. Branch actual = main
git branch --show-currentSi no es main, preguntar si querés arrancar el release desde ahí igual (no suele ser buena idea — un release desde una feature branch es una señal de que algo está mal en el flujo).
3. Último tag vs HEAD — ¿hay cambios reales?
git tag --sort=-v:refname | head -1 # último tag
git log <último-tag>..HEAD --oneline # commits desde entoncesClasificar los commits:
- Runtime changes (feat, fix que toca
src/, refactor de código real) → release justificado. - Solo docs / types / chore → preguntar al user explícitamente si es un "release de alineación" intencional (ej. mantener los 3 artefactos en la misma versión numerada después de un período de solo trabajo en docs) o si hay algo sin mergear que debería entrar primero.
Precedente: v0.1.7 fue un release de alineación válido tras 23 commits doc-only. Pero la conversación fue explícita — no asumir.
4. Bump target
Preguntar al user:
- Patch (
X.Y.Z+1) — fix, docs, alineación, cambios menores. Default razonable. - Minor (
X.Y+1.0) — feature nueva visible al usuario. - Major (
X+1.0.0) — breaking change o hito grande.
Confirmar el número final antes de seguir (0.1.7 → 0.1.8, por ejemplo).
Archivos a bumpear (5, todos atómicos en un commit)
Esta lista es exhaustiva. Olvidarse uno deja el release desincronizado.
| Archivo | Campo | Nota |
|---|---|---|
package.json |
"version" |
Web + fuente que toma Vite en el build |
package-lock.json |
"version" (raíz) y packages[""].version |
Se desincroniza vs package.json si no lo tocás |
src-tauri/tauri.conf.json |
"version" |
Versión que ve el updater de Tauri y aparece en "About" |
src-tauri/Cargo.toml |
version = "..." en [package] |
Versión del crate Rust |
src-tauri/Cargo.lock |
Entry con name = "secondmind" → línea version |
Sincroniza con Cargo.toml. Sin esto Cargo refuseará algunos builds reproducibles |
NO tocar android/app/build.gradle. El workflow deriva versionName y versionCode del tag vía:
VERSION_NAME="${GITHUB_REF_NAME#v}" # v0.1.7 → 0.1.7
VERSION_CODE=$((MAJOR * 10000 + MINOR * 100 + PATCH)) # 0.1.7 → 107
./gradlew assembleRelease -PversionName=$VERSION_NAME -PversionCode=$VERSION_CODEAndroid toma la versión del tag, no del archivo. Tocar build.gradle es ruido innecesario.
Flujo (10 pasos)
Ejecutar en este orden. Cada paso tiene un "por qué" que justifica su posición.
Paso 1 — Crear branch de release
git checkout -b chore/release-v<X.Y.Z>Por qué branch: el hook PreToolUse bloquea edits en main con exit 2. Además mantiene el bump commit aislado para poder revertirlo si algo sale mal antes del tag.
Paso 2 — Bumpear los 5 archivos
Editar package.json, package-lock.json, src-tauri/tauri.conf.json, src-tauri/Cargo.toml, src-tauri/Cargo.lock subiendo el campo version al número acordado.
Verificar con git diff --stat — deben aparecer exactamente 5 archivos, ~6 inserciones/6 deleciones.
Paso 2.5 — Entrada de changelog (condicional, user-facing)
En el mismo commit del bump (no después), decidir según el contenido del release:
- ¿Tiene cambios user-facing? → Sí: agregar la entrada al catálogo de novedades (Feature 59):
- Appendear
{ version: 'X.Y.Z', key: 'v0XY' }al FINAL deCHANGELOG_ENTRIESensrc/lib/changelog.ts. El orden del array = orden de release (invariante F60): el historial consultable (/settings/changelog) lista el catálogo conreverse()= newest-first sinsemver.compare(), así que el append-al-final es lo que mantiene el orden correcto. Solo versiones LIBERADAS en el registry — esta es la entrada de la versión que liberás ahora; nunca sembrar drafts de versiones futuras. - Agregar
changelog.v0XY.title+changelog.v0XY.items(los highlights reales del release) ensrc/locales/es/translation.jsonysrc/locales/en/translation.json. - Regenerar los tipos i18n con
npx i18next-cli types(NOextract) y luego correr Prettier sobresrc/types/resources.d.ts(el comando lo genera con comillas dobles; el repo lo quiere en formato Prettier y el hook no corre en comandos shell).
- Appendear
- No (patch interno, sin nada visible para el usuario) → omitir. Sin entrada para esa versión el modal queda mudo by design:
useWhatsNewhace silent-advance (avanzalastSeenVersionsin mostrar nada).
La mecánica exacta —normalización de la key, tipado del array i18n, el comando de regen de tipos, y el caveat crítico de NUNCA correr i18next-cli extract (purga keys)— vive en Spec/gotchas/i18n.md y en el registro SPEC-feature-59. Seguir esas; no reinventarla acá.
Por qué acá y no después del tag (CRÍTICO): la entrada se bundlea en build-time — el define __APP_VERSION__ del bundle web y el binario/APK que CI arma desde el tag. Si se taggea primero y se agrega la entrada después, el build liberado NO la tiene → el modal queda mudo en silencio (no hay error; simplemente no aparece). Por eso va junto a los 5 archivos de versión, en el commit del bump, antes del hosting deploy (Paso 6) y del tag (Paso 7).
Paso 3 — Commit atómico
git add package.json package-lock.json src-tauri/Cargo.toml src-tauri/Cargo.lock src-tauri/tauri.conf.json
git commit -m "chore(release): bump version X.Y.Z → A.B.C
<1-2 líneas explicando qué incluye este release o si es de alineación>
<trailer Co-Authored-By definido en CLAUDE.md § SDD step 4>"Si agregaste entrada de changelog (Paso 2.5): sumá también src/lib/changelog.ts, src/locales/{es,en}/translation.json y src/types/resources.d.ts al git add — todo en el mismo commit del bump, para que el build liberado la lleve. En ese caso el commit final ya no son 5 archivos sino 5 (bump) + los del changelog: es esperado, no un error — el «exactamente 5» del Paso 2 verifica solo el bump aislado.
Paso 4 — Merge --no-ff a main
git checkout main
git merge --no-ff chore/release-v<X.Y.Z> -m "Merge chore/release-v<X.Y.Z> — <resumen>"
git branch -d chore/release-v<X.Y.Z>Paso 5 — Push main (sin tag todavía)
git push origin mainPor qué antes del tag: el workflow corre en GitHub, clona el repo al estado del tag. Si el tag existiera antes de que main tenga el commit del bump, el clone tomaría código viejo. Push primero, tag después.
Paso 6 — Deploy hosting
npm run deploy # = npm run build && firebase deploy --only hosting:app (multi-target: SOLO la app)Este paso tarda ~1 min (build Vite + upload Firebase).
Multi-target (post-SPEC-63):
npm run deployestá acotado a--only hosting:appa propósito — el Hosting es multi-site (app + landing). La landing del apexgetsecondmind.cose deploya por separado connpm run deploy:landingy NO entra en el release coordinado (no se versiona con los 3 artefactos del ecosistema). Nunca usarfirebase deploy --only hostingpelado: post-array deployaría AMBOS sites.
Por qué hosting ANTES del tag (crítico): el tag dispara GitHub Actions que construye desktop + mobile. Si esos artefactos llegan a usuarios (updater de Tauri, Firebase App Distribution) apuntando a una versión de web que todavía no está publicada, hay desync de minutos durante el cual la app nueva puede fallar en features que dependan del bundle web nuevo (ej. CSP, Firebase config, rutas).
Esperar a que el deploy termine y confirme Hosting URL: https://secondmind.web.app. Validar en el navegador si hay dudas.
Por qué la verificación apunta al
.web.appy no al dominio público (app.getsecondmind.co) — a propósito, NO cambiar: el.web.appresuelve directo a Firebase Hosting, sin Cloudflare en el medio, así que la lectura del deploy (Hosting URL / hash del bundle publicado) es siempre directa y confiable. El dominio público, si algún día se proxia por Cloudflare, podría devolver lecturas stale del cache del edge. Por eso la validación del release usa el.web.appcomo fuente de verdad del deploy (la migración del origen web aapp.getsecondmind.co—Fase A, 2026-06-17— no cambia esta verificación).
Paso 7 — Crear y pushear tag
git tag -a v<X.Y.Z> -m "SecondMind v<X.Y.Z> — <resumen del release>"
git push origin v<X.Y.Z>Esto dispara .github/workflows/release.yml. Verificar que el run arrancó:
gh run list --workflow=release.yml --limit 3El run más reciente debe tener estado in_progress o queued y referenciar el tag recién pusheado.
Paso 8 — Monitorear el workflow
gh run watch <run-id> --exit-statusIdealmente en background para no bloquear. Tiempo típico: ~10 minutos.
Dos jobs corren en paralelo:
release-tauri(windows-latest, Node 20): genera MSI + NSIS firmados + crea GitHub Release conlatest.jsonfirmado. El updater endpoint (https://github.com/sebasgc0399/SecondMind/releases/latest/download/latest.json) detecta la release y los usuarios reciben el update al abrir la app (o en el check programado).release-capacitor(ubuntu-latest, Node 22 + JDK 21): buildea APK release firmado + lo sube a Firebase App Distribution grupoowner. El user recibe notificación push/email.
Paso 9 — Verificar éxito
Cuando gh run watch termina con exit 0:
gh run view <run-id> --json status,conclusion,jobs --jq '{status, conclusion, jobs: [.jobs[] | {name, conclusion}]}'Ambos jobs deben estar success. Si alguno falló, ver ## Troubleshooting abajo.
Además verificar manualmente:
- GitHub Release creado:
https://github.com/sebasgc0399/SecondMind/releases/tag/v<X.Y.Z>— debe tener los .msi, .nsis, ylatest.json. - Firebase App Distribution: el user debería recibir notificación. Si no, revisar la consola de Firebase.
Paso 10 — Cerrar
Informar al user:
- Versión publicada.
- Hosting ya sirve la nueva en
https://secondmind.web.app. - Updater desktop detectará la nueva en el próximo check (o manual desde Settings).
- APK disponible en Firebase App Distribution.
- Tiempo total del release.
Gotchas del pipeline (no hace falta aplicarlos — ya están en .github/workflows/release.yml, pero saberlos ayuda a debuggear)
Ya resueltos en el workflow actual. Canon de todos en Spec/gotchas/tauri-desktop.md — acá solo etiqueta + síntoma:
- Capacitor 8 exige Node 22 + JDK 21 — § "Capacitor 8 exige Node >=22 y JDK 21 en el runner Android". Si falla el job Android tras migrar Capacitor, empezar por ahí.
gradlewsin bit executable — § "gradlewpierde el bit executable trasgit checkoutenubuntu-latest". Síntoma:/bin/sh: ./gradlew: Permission denied..env.productionexplícito por job — § "tauri-action NO propaga env vars del stepenv:al subprocessbeforeBuildCommanden Windows runners". Síntoma: sign-in roto solo en bundles de CI.- Updater trata HTTP 404 como error — § "Tauri
plugin-updatertrata HTTP 404 como error". Solo visible pre-primer-release. versionCodesemver-encoded derivado del tag — § "versionName/versionCodedel APK via Gradle props-Pderivados del tag". No tocarbuild.gradlemanualmente.
Mecanismo de versión/purga (cómo llega la versión nueva al usuario)
Resumen mínimo — el canon vive en los gotchas, no acá:
- Web: el SW de workbox (
vite-plugin-pwa,skipWaiting: false) precachea el bundle; el usuario recibe la versión nueva vía prompt delUpdateBanner. En native el SW no se registra (gate por mount enUpdateBanner.tsx). - Desktop (Tauri):
src-tauri/src/version_check.rscomparaversion.txtenapp_local_data_dir()pre-WebView; en mismatch ejecuta la purga nuclear (clear_all_browsing_data()) → un release con version bump cura los desktops trabados con bundle viejo. - Android: sin equivalente — una instalación trabada con SW viejo solo se cura con uninstall+reinstall manual. Las instalaciones nuevas quedan limpias porque el SW nunca se registra en native.
- Implicación de release: un cambio client-side necesita rebuild nativo en los 3 frentes, no solo deploy web — el bundle empacado de desktop/mobile no se actualiza con el hosting.
Canon: Spec/gotchas/pwa-offline.md § "El SW no debe registrarse en native" (mecanismo completo + curas por plataforma) y Spec/gotchas/tauri-desktop.md § "Tauri 2.10 clear_all_browsing_data() es nuclear sin parámetros".
Troubleshooting
Un job del workflow falla
gh run view <run-id> --log-failed | tail -100Causas típicas:
- Secrets faltantes o expirados: revisar
Settings → Secrets and variables → Actionsen GitHub. La lista canónica está enrelease.yml(ver § "Secrets del repo" abajo). - APK no se sube a Firebase App Distribution: suele ser
FIREBASE_SERVICE_ACCOUNT_KEYexpirado. Regenerar en la consola de Firebase. - Tauri signing falla: verificar
TAURI_SIGNING_PRIVATE_KEYy su password. Nunca cambiar la key generada — los updates existentes la verifican contrapubkeyentauri.conf.json. Si rotás la key, los updates ya desplegados no se podrán instalar (se quedan en la última versión).
Rehacer un release (tag ya pusheado con error)
gh release delete v<X.Y.Z> --cleanup-tag --yesBorra Release + tag remoto atómicamente. Después corregir lo que haya que corregir y volver a pushear el tag. tauri-action no recrea releases; este es el único path limpio.
Advertencia: si el release ya se distribuyó (usuarios con updater activo lo recibieron), rehacerlo no los deshace. Solo vale la pena si el problema se detecta antes de que alguien descargue.
Hosting desplegado pero tag no pushea
Estado intermedio limpio: los usuarios web ya tienen la nueva versión, desktop/mobile siguen en la anterior. No es catastrófico. Diagnosticar el problema y pushear el tag cuando esté resuelto — el hosting ya deployado no se rompe.
Secrets del repo (referencia)
No hay que configurarlos en cada release — ya están. La lista canónica es la que consume .github/workflows/release.yml (steps env: + heredocs .env.production de cada job) — esta skill no mantiene una copia: al diagnosticar, leerla del workflow. Los valores viven en GitHub → Settings → Secrets and variables → Actions.
Gotcha asociado: toda nueva VITE_* del frontend requiere bumpear el heredoc .env.production de cada job que la consume — Spec/gotchas/tauri-desktop.md § "Toda nueva VITE_* requiere bumpear el heredoc".
Qué NO hace esta skill
- No toca Cloud Functions. Si el release incluye cambios en
src/functions/, hay que corrernpm run deploy:functionspor separado (antes o después del release, no importa, deploy independiente). - No toca Firestore rules. Mismo criterio:
npm run deploy:rules. - No ejecuta tests. Asume que los cambios ya pasaron CI/tests locales antes de pedir el release. Si el user pide "release y además correr tests", los tests van antes, separados.
- No escribe release notes automáticas. El body del commit de merge + el mensaje del tag son el único "changelog" generado. GitHub Release body es genérico (lo genera el workflow). Si el user quiere release notes curadas, las redacta él y las paso al
-mdel tag.
Referencias en el repo
.github/workflows/release.yml— la source of truth del pipeline.src-tauri/tauri.conf.json— configuración del updater y bundle.android/app/build.gradle— leeversionNameyversionCodecomo Gradle props (derivadas del tag).Spec/ESTADO-ACTUAL.mdsección "Auto-Updater + Releases (Features 8-9)" — historial de gotchas resueltos y decisiones del pipeline.