---
name: release-semver
description: Versionado semántico (SemVer). Cuándo bumpar major/minor/patch, changelogs convencionales, estrategia de tags y proceso de release completo.
version: "1.0.2"
evolved: true
evolved-from: "1.0.1"
evolved-at: "2026-05-02"
evolved-by: "aprender"
evolved-note: "Sección nueva: publish a múltiples registries (npmjs + GitHub Packages) — republish-only pattern y auth GitHub Packages no soporta npm login"
herramientasPermitidas: [Read, Bash]
exclusiones:
  - "No cargar para versionar el sistema SWL — el bump de versión de swl-ses sigue el checklist de 15 ubicaciones documentado en `/swl:release`; este skill cubre SemVer general para proyectos de usuario, no el proceso interno de release del sistema."
  - "No cargar para decidir el tipo de commit individual (feat/fix/refactor) — el mensaje de commit se elige al hacer commit; este skill se carga cuando se necesita decidir el número de versión del release o crear el proceso completo de release."
  - "No cargar para proyectos internos sin versionado público ni consumidores externos — si no hay consumidores del paquete que dependen de compatibilidad, la disciplina SemVer completa puede ser excesiva; usar `git-workflow.md` para el flujo de commits."
  - "No cargar si el proceso de release ya está automatizado en CI con `semantic-release` — en ese caso el número de versión se calcula automáticamente desde los commits y cargar este skill solo agrega duplicación."
evolvable: true  # default para skill estandar
---
# Versionado Semántico (SemVer)

## Principio fundamental

Un número de versión tiene la forma `MAJOR.MINOR.PATCH` (ej. `2.4.1`). Cada segmento comunica
intención de compatibilidad a los consumidores del paquete o servicio.

---

## Cuándo bumpar cada segmento

### PATCH — arreglos compatibles hacia atrás

Incrementa PATCH cuando:
- Se corrige un bug sin cambiar la interfaz pública.
- Se corrige documentación o typos en código que afectan comportamiento.
- Se mejoran mensajes de error sin cambiar el tipo de excepción.
- Se actualizan dependencias de parche (`requests 2.31.0 → 2.31.1`).

```
1.4.2 → 1.4.3   # fix: corrige cálculo incorrecto en factura
```

### MINOR — funcionalidad nueva compatible hacia atrás

Incrementa MINOR cuando:
- Se agrega un endpoint, método o parámetro opcional nuevo.
- Se agrega una funcionalidad que los consumidores pueden ignorar.
- Se depreca algo (pero aún funciona).
- Se actualiza una dependencia minor que agrega capacidades.

```
1.4.3 → 1.5.0   # feat: agrega exportación a PDF
```

Al subir MINOR, el PATCH siempre regresa a 0.

### MAJOR — cambios incompatibles hacia atrás (breaking changes)

Incrementa MAJOR cuando:
- Se elimina un endpoint, método o campo.
- Se cambia el tipo o contrato de un parámetro existente.
- Se cambia el comportamiento esperado de forma que rompe integraciones existentes.
- Se cambia la autenticación/autorización de forma incompatible.

```
1.5.0 → 2.0.0   # feat!: migra autenticación de JWT a OAuth2
```

Al subir MAJOR, MINOR y PATCH regresan a 0.

### Versión 0.x.y — desarrollo inicial

Mientras la versión MAJOR sea `0`, la API se considera inestable. Cualquier MINOR puede
contener breaking changes. Cuando el software está listo para producción, se lanza `1.0.0`.

---

## Commits Convencionales

Los commits convencionales generan el changelog automáticamente y determinan el bump.

### Formato

```
<tipo>[scope opcional][! para breaking]: <descripción corta>

[cuerpo opcional]

[footer opcional: BREAKING CHANGE: descripción]
```

### Tipos y su impacto en versión

| Tipo       | Impacto versión | Descripción                                      |
|------------|----------------|--------------------------------------------------|
| `fix`      | PATCH           | Corrección de bug                                |
| `feat`     | MINOR           | Nueva funcionalidad                              |
| `feat!`    | MAJOR           | Nueva funcionalidad con breaking change          |
| `fix!`     | MAJOR           | Fix con breaking change                          |
| `refactor` | ninguno         | Refactorización sin cambio de comportamiento     |
| `chore`    | ninguno         | Tareas de mantenimiento (CI, deps, config)       |
| `docs`     | ninguno         | Documentación únicamente                         |
| `test`     | ninguno         | Agregar o corregir tests                         |
| `perf`     | PATCH           | Mejora de rendimiento sin cambio de interfaz     |
| `ci`       | ninguno         | Cambios en pipelines de CI/CD                    |
| `style`    | ninguno         | Formato, espacios, punto y coma (sin lógica)     |

### Ejemplos de commits bien escritos

```bash
git commit -m "fix(auth): corrige expiración incorrecta de tokens de refresco"
git commit -m "feat(reportes): agrega exportación a Excel con formato condicional"
git commit -m "feat(api)!: cambia paginación de offset a cursor

BREAKING CHANGE: el parámetro 'page' fue reemplazado por 'cursor'.
Los clientes deben actualizar sus integraciones."
```

---

## Proceso de release

### Paso 1 — Preparar la rama de release

```bash
# En main, crear rama release
git checkout -b release/2.1.0

# Actualizar versión en archivos del proyecto
# Python: pyproject.toml, __version__
# Node: package.json
# Java: pom.xml o build.gradle
```

### Paso 2 — Generar CHANGELOG

Usar `conventional-changelog` o `git-cliff` para generar el changelog automáticamente:

```bash
# Con git-cliff (recomendado)
git cliff --tag v2.1.0 --output CHANGELOG.md

# Con conventional-changelog
npx conventional-changelog -p conventionalcommits -i CHANGELOG.md -s
```

Estructura esperada del CHANGELOG:

```markdown
## [2.1.0] - 2026-03-25

### Nuevas funcionalidades
- feat(reportes): agrega exportación a Excel (#234)
- feat(usuarios): agrega campo de foto de perfil (#241)

### Correcciones
- fix(auth): corrige expiración incorrecta de tokens (#238)
- fix(factura): redondeo incorrecto en descuentos (#240)
```

### Paso 3 — Commit de versión y tag

```bash
# Commit del bump de versión
git add pyproject.toml CHANGELOG.md
git commit -m "chore(release): v2.1.0"

# Crear tag anotado (no ligero)
git tag -a v2.1.0 -m "Release v2.1.0"

# Subir rama y tag
git push origin release/2.1.0
git push origin v2.1.0
```

### Paso 4 — Merge a main y develop

```bash
git checkout main
git merge --no-ff release/2.1.0
git push origin main

git checkout develop
git merge --no-ff release/2.1.0
git push origin develop

git branch -d release/2.1.0
```

---

## Estrategia de tags

### Tags anotados vs. ligeros

Siempre usar **tags anotados** (`git tag -a`) para releases. Los tags ligeros son solo para
marcado temporal interno. Los tags anotados incluyen autor, fecha y mensaje — esto es esencial
para `git describe` y herramientas de release.

### Convención de nombres

```
v{MAJOR}.{MINOR}.{PATCH}           # release estable:    v2.1.0
v{MAJOR}.{MINOR}.{PATCH}-rc.{N}    # release candidate:  v2.1.0-rc.1
v{MAJOR}.{MINOR}.{PATCH}-beta.{N}  # beta pública:       v2.1.0-beta.2
v{MAJOR}.{MINOR}.{PATCH}-alpha.{N} # alpha interna:      v2.1.0-alpha.1
```

### Listar y filtrar tags

```bash
git tag --sort=-v:refname         # Lista tags en orden semver descendente
git tag -l "v2.*"                 # Solo tags de la serie 2.x
git describe --tags --abbrev=0    # Último tag del commit actual
```

---

## Publish a múltiples registries (mirror dual)

Cuando un paquete se publica al mismo tiempo en dos registries (típicamente
npmjs.org como canónico y GitHub Packages como mirror), la coordinación de
versiones tiene reglas distintas a un publish simple.

### NUNCA: reintentar la misma versión cuando uno de los registries ya la aceptó

**Problema**: el publish dual falló en uno de los dos registries pero el otro
quedó publicado correctamente. La intuición lleva a "republicar la misma versión"
después de arreglar el problema. Esto NO funciona: ningún registry permite
sobreescribir una versión ya publicada (es la garantía de inmutabilidad de
paquetes). El publish al registry que ya tiene esa versión devuelve:

```
npm error You cannot publish over the previously published versions: X.Y.Z
```

```
# MAL — reintentar 1.1.0 porque GitHub Packages la tiene pero npmjs no
npm publish --registry=https://registry.npmjs.org/        # podría funcionar
npm publish --registry=https://npm.pkg.github.com         # FALLA: ya existe 1.1.0
```

```
# BIEN — bumpear PATCH y publicar solo al registry faltante
# 1.1.0 → 1.1.1 en package.json + plugin.json + lock + headers de docs
node scripts/publicar.js --solo-npmjs                     # solo al que falta
```

**Regla**: si un publish dual falla en el registry A pero queda publicado en B,
bumpear PATCH inmediatamente y publicar solo a A. Documentar en CHANGELOG que
es un republish exclusivo de coordinación entre registries (sin cambios funcionales).

### NUNCA: usar `npm login` con GitHub Packages

**Problema**: GitHub Packages NO soporta `npm login` (ni el flujo web OAuth ni el
fallback CouchDB de creación de usuarios). Ejecutar `npm login --registry=https://npm.pkg.github.com`
devuelve 404 en `/-/v1/login` y luego 403 en el `PUT /-/user/...`. La autenticación
a GitHub Packages se hace EXCLUSIVAMENTE con un Personal Access Token de GitHub
configurado como `_authToken` directamente en `~/.npmrc`.

```bash
# MAL — esto siempre devuelve 403
npm login --registry=https://npm.pkg.github.com

# BIEN — agregar el PAT al ~/.npmrc manualmente
echo "//npm.pkg.github.com/:_authToken=ghp_xxxxxxxx" >> ~/.npmrc
npm whoami --registry=https://npm.pkg.github.com   # → tu-usuario-github
```

El token requiere los scopes `read:packages` y `write:packages` en GitHub
(Settings → Developer settings → Personal access tokens).

### SIEMPRE: diagnosticar auth con `npm whoami` antes de `npm login`

**Cuándo aplicar**: cuando un publish falla con "no autenticado" o 401/403.
**Beneficio**: distingue entre "sin token", "token expirado", "cuenta sin permiso
al scope" y "registry equivocado" sin abrir el flujo interactivo de login.

```bash
# Diagnóstico estructurado
npm whoami --registry=https://registry.npmjs.org/

# Resultado posible 1: nombre de usuario → autenticado correctamente
# Resultado posible 2: 401 Unauthorized → token expirado/inválido
#   → fix: npm login --registry=https://registry.npmjs.org/
# Resultado posible 3: 404 → registry incorrecto
# Resultado posible 4: nombre distinto al esperado → cuenta sin permiso
#   → fix: verificar dueño del scope con
#     npm owner ls @scope/paquete --registry=https://registry.npmjs.org/
```

Ningún publisher debería hacer `npm login` sin antes hacer `npm whoami`. El whoami
es no-destructivo y revela la causa raíz; el login interactivo solo cubre el caso
de token inválido.

---

## Herramientas recomendadas

| Herramienta            | Uso                                          |
|------------------------|----------------------------------------------|
| `git-cliff`            | Generación de CHANGELOG desde commits        |
| `commitizen`           | Wizard interactivo para commits convencionales |
| `semantic-release`     | Release automatizado desde CI                |
| `bump2version`         | Bump de versión en archivos Python           |
| `conventional-commits` | Spec oficial de commits convencionales       |

---

## Anti-patrones a evitar

- **Nunca reutilizar un tag**: Si el release `v2.1.0` se publicó, no se puede mover ese tag.
  Si hay un error, publicar `v2.1.1`.
- **Nunca usar tags ligeros en releases**: No aportan metadata y dificultan trazabilidad.
- **Nunca saltarse MAJOR por miedo**: Si hay breaking change, es MAJOR. Ocultarlo como MINOR
  rompe la confianza de los consumidores.
- **No mezclar tipos de commit en uno solo**: Un commit = un cambio lógico = un tipo.
- **No escribir changelogs manuales sin estructura**: Usar el formato estándar para que las
  herramientas puedan procesarlos.

## Cuándo NO cargar

- Se versiona el sistema SWL propio — ese proceso usa el checklist de 15 ubicaciones de `/swl:release`; este skill cubre SemVer general para proyectos de aplicación, no el flujo interno del sistema.
- Se elige el tipo de commit para un cambio individual — el tipo (`feat`, `fix`, `refactor`) se decide al redactar el mensaje del commit; cargar este skill es excesivo para esa decisión puntual.
- `semantic-release` ya está configurado en CI — si el release está automatizado, el número de versión se calcula desde los commits sin intervención manual; cargar este skill duplicaría el trabajo.
- El proyecto es una herramienta interna sin consumidores que dependan de compatibilidad de versión — SemVer completo compensa cuando hay consumidores que confían en los números de versión para evaluar breaking changes.

## Gotchas / Errores comunes no obvios

- **Tag ligero creado en lugar de anotado para el release**: `git tag v2.1.0` sin la flag `-a` crea un tag ligero que no incluye metadata de autor, fecha ni mensaje. Causa: olvidar la flag `-a` o la costumbre de usar tags ligeros para marcado temporal. Solución: siempre `git tag -a v2.1.0 -m "Release v2.1.0"` para releases — los tags anotados son los que `git describe` y las herramientas de changelog usan correctamente.
- **MAJOR evitado por considerar el breaking change "pequeño"**: el equipo sube de `1.4.2` a `1.5.0` un endpoint que cambió su contrato de respuesta. Causa: subestimar el impacto del breaking change para evitar el salto de versión mayor. Solución: si un consumidor tiene que actualizar su código para seguir funcionando, es MAJOR sin importar el tamaño del cambio — el anti-patrón "Nunca saltarse MAJOR por miedo" es explícito en el skill.
- **Changelog generado con `git cliff` sin configuración personalizada incluye commits de chore y docs**: el CHANGELOG.md resultante tiene 80% de entradas de limpieza que no interesan a los consumidores. Causa: `git cliff` sin `.cliff.toml` incluye todos los tipos de commit. Solución: crear `.cliff.toml` en el repo con la sección `[git]` filtrando solo `feat`, `fix`, `perf` y `BREAKING CHANGE` para el changelog de release público.
- **Rama `release/2.1.0` mergeada a `main` pero no a `develop`**: el próximo desarrollo parte de `develop` sin incluir los commits del release, generando conflictos en la siguiente rama de feature. Causa: el Paso 4 requiere merge a ambas ramas pero se omite el merge a `develop` por considerar que el release ya está en `main`. Solución: el proceso de release de 4 pasos del skill requiere merge a `main` Y a `develop` antes de eliminar la rama de release — ambos merges son parte del mismo paso atómico.
- **`npx <paquete> <comando>` sin `@latest` usa caché stale indefinidamente**: tras publicar una nueva versión, `npx swl-ses doctor` sigue ejecutando la primera versión que el usuario cacheó (por ejemplo v5.7.0 cuando ya hay v5.10.3 instalada). Causa: npx resuelve a la primera versión disponible y la cachea; sin `@latest` nunca vuelve a consultar el registry. Solución: SIEMPRE usar `npx paquete@latest <comando>` en mensajes post-install, hooks de alerta de nueva versión, docs del README, ejemplos en CHANGELOG y scripts de inicialización. Nunca `npx paquete <comando>` desnudo — incluso si el paquete acaba de publicarse; el caché de un desarrollador vecino puede tener una versión anterior desde hace meses. Un bump sin `@latest` en los mensajes post-install perpetúa el problema para todos los usuarios nuevos.
