# Deep dive #11b — Frontend PR workflow v2 (cd-frontend-dev → cd-system)

**Status**: ✅ DONE (2026-05-04 v2 + sincronización bidireccional)
**Frente master**: #11 NOW (extensión del fix de CI del 2026-05-02)
**Tiempo invertido**: ~2 hr (decisión + implementación inicial + sync inverso + docs)
**Doc operativo**: [`docs/bewpro2.0/infrastructure/06-frontend-pr-workflow.md`](../infrastructure/06-frontend-pr-workflow.md)
**Deep dive previo (deploy.yml)**: [`02-github-ci.md`](./02-github-ci.md)
**Pendiente activación**: setear secret `CD_FRONTEND_DEV_PAT` en cd-system (instrucciones al pie)

---

## Contexto

Tras el fix del deploy.yml (deep dive #11 del 2026-05-02), el flujo de CI quedó funcional pero la integración con `cd-frontend-dev` (repo separado para devs frontend que no tienen PHP/Composer local) abría PRs directamente contra `main` con una branch única por push (`frontend-sync/<run-id>`). Esto generaba dos problemas operativos identificados durante una verificación end-to-end:

1. **Riesgo de blast radius**: el PR llegaba a `main`, una sola revisión humana entre el bot y la branch productiva (`cd-system`).
2. **Ruido en el inbox**: cada push del dev frontend = un PR nuevo. Un dev iterando con 5 commits = 5 PRs simultáneos en cd-system.

## Decisiones tomadas

### 1. Introducir branch buffer `staging-frontend`

| Branch | Rol | Workflows que escucha |
|--------|-----|------------------------|
| `staging-frontend` | Zona de revisión de cambios frontend | Ninguno — sin side effects |
| `main` | Trunk listo para release | `build-image.yml` (rebuild Docker) |
| `cd-system` | Productiva | `deploy.yml` (SSH a VPS1 → bewpro.com) |

**Razón**: separar "cambios revisados" de "trunk listo" de "producción" hace cada gate explícito. Un merge a `staging-frontend` no tiene consecuencias inmediatas — permite mergear con confianza para validar visualmente sin presión de "esto va a deploy".

### 2. Branch fija `frontend-sync/pending` en lugar de `frontend-sync/<run-id>`

`peter-evans/create-pull-request@v6` detecta automáticamente que la branch existe y un PR está abierto, entonces **actualiza** el PR en vez de crear uno nuevo. Resultado:

- 5 pushes consecutivos del dev = 1 PR que va engordando.
- Maintainer mergea cuando termina la iteración.
- `delete-branch: true` borra `frontend-sync/pending` post-merge → próximo push abre PR fresco.

**Trade-off aceptado**: si dos devs frontend pushean en paralelo, contaminan el mismo PR. Mitigación actual: convención de "1 dev en vuelo a la vez". Mitigación futura: branch por-dev (`frontend-sync/${{ github.actor }}`).

### 3. Título y body del PR fijos (no dependen del commit message)

Antes: título = `[Frontend] <commit message>` → si era multilínea, GitHub Actions concatenaba sin formato (caso PR #6 con título de 6 líneas).

Después: título fijo = `[Frontend] cambios pendientes desde cd-frontend-dev`. El detalle de qué cambió vive en el body (acumulado por los N pushes).

## Cambios técnicos aplicados

### En `cd-system` (vía `gh api`)

```bash
# Crear branch staging-frontend desde main HEAD
SHA=$(gh api repos/LACOMPANIADIGITAL/cd-system/git/refs/heads/main -q '.object.sha')
gh api repos/LACOMPANIADIGITAL/cd-system/git/refs -X POST \
  -f ref="refs/heads/staging-frontend" -f sha="$SHA"
# → SHA: 2ad8a00e6a77ea54a09dc0b00d1219cb6cdad178
```

### En `cd-frontend-dev` (commit `5a8c2ed`)

`/.github/workflows/sync-to-main.yml` — diff:

```diff
- - name: Checkout cd-system
+ - name: Checkout cd-system (branch staging-frontend)
    uses: actions/checkout@v4
    with:
      repository: LACOMPANIADIGITAL/cd-system
+     ref: staging-frontend
      token: ${{ secrets.CD_SYSTEM_PAT }}
      path: cd-system-repo

- - name: Crear PR en cd-system
+ - name: Crear/actualizar PR en cd-system
    uses: peter-evans/create-pull-request@v6
    with:
      ...
+     base: staging-frontend
-     branch: frontend-sync/${{ github.run_id }}
+     branch: frontend-sync/pending
-     title: "[Frontend] ${{ github.event.head_commit.message }}"
+     title: "[Frontend] cambios pendientes desde cd-frontend-dev"
```

## Anatomía del flujo v2

```
─── Etapa 1: AUTO ──────────────────────────────────────
dev push cd-frontend-dev/main
  → workflow Sync to cd-system corre (~30s)
  → checkout cd-system @ staging-frontend
  → cp resources/** + public/** modificados
  → peter-evans crea/actualiza PR contra staging-frontend
     (branch frontend-sync/pending, label frontend)

─── Etapa 2: MANUAL — review + merge a staging ──────────
maintainer:
  gh pr checkout <N> → validá local
  gh pr merge <N> --squash --delete-branch
  → cambios viven en staging-frontend
  → cero workflows disparados

─── Etapa 3a: MANUAL — promote staging → main ─────────
gh pr create --base main --head staging-frontend ...
gh pr merge <N> --merge
  → push a main
  → dispara build-image.yml (rebuild Docker GHCR ~3-5 min)

─── Etapa 3b: MANUAL — promote main → cd-system ───────
gh pr create --base cd-system --head main ...
gh pr merge <N> --merge
  → push a cd-system
  → dispara deploy.yml (SSH a VPS1 → bewpro.com ~1m11s)
```

## Validación

Al cierre del deep dive, **falta el smoke test end-to-end** de v2:

- [ ] Push trivial desde cd-frontend-dev/main para verificar que el PR se abre contra `staging-frontend` con branch `frontend-sync/pending` (no contra `main` con `frontend-sync/<run-id>`).
- [ ] Segundo push consecutivo para verificar que **actualiza** el PR existente en vez de crear uno nuevo.
- [ ] Merge → verificar que `delete-branch: true` borra `frontend-sync/pending`.
- [ ] Tercer push para verificar que abre PR fresco.

Plus PR #7 (legacy v1 contra `main`, branch `frontend-sync/25328278941`) sigue abierto y no auto-adapta — pendiente decisión: mergear, cerrar o re-apuntar base.

## Mantenimiento futuro

### Si la branch `staging-frontend` se borra accidentalmente

```bash
SHA=$(gh api repos/LACOMPANIADIGITAL/cd-system/git/refs/heads/main -q '.object.sha')
gh api repos/LACOMPANIADIGITAL/cd-system/git/refs -X POST \
  -f ref="refs/heads/staging-frontend" -f sha="$SHA"
gh workflow run sync-to-main.yml --repo LACOMPANIADIGITAL/cd-frontend-dev --ref main
```

### Si llegan 2 devs frontend simultáneos y chocan en `frontend-sync/pending`

Opciones:
1. **Convención humana** (Slack): "1 dev en vuelo a la vez". Funciona <3 devs.
2. **Branch por-dev**: cambiar `branch: frontend-sync/pending` → `branch: frontend-sync/${{ github.actor }}`. Cada dev tiene su PR. Más PRs en inbox pero sin colisión.
3. **Cron de auto-merge** (más sofisticado): si el PR no tiene cambios pendientes hace >X minutos, mergear automáticamente.

### Bumpear `peter-evans/create-pull-request@v6 → @v7` antes de 2026-06-02

GitHub fuerza Node 24 desde esa fecha. v7 ya soporta. Cambio:

```yaml
- uses: peter-evans/create-pull-request@v6
+ uses: peter-evans/create-pull-request@v7
```

Idem `actions/checkout@v4` (verificar si v5 ya existe en esa fecha).

## Decisiones registradas

| Pregunta | Respuesta | Por qué |
|----------|-----------|---------|
| ¿`base: main` o branch dedicada? | **`staging-frontend`** | Separar revisión de promote |
| ¿Branch única por push o fija? | **Fija (`pending`)** | Evitar ruido N pushes = N PRs |
| ¿Título dinámico (commit message) o fijo? | **Fijo** | El PR acumula N pushes, el último mensaje no es representativo |
| ¿Squash o merge en etapa 1→2? | **Squash** | History de cd-system limpia |
| ¿Squash o merge en 3a/3b? | **Merge / fast-forward** | Trazabilidad de promote a producción |
| ¿Promover `cd-system` automático tras `main`? | **Manual** | Producción debe ser decisión consciente |
| ¿Convención multi-dev? | **1 dev en vuelo (humano)** por ahora | <3 devs frontend hoy |

## Resultados / KPIs

| Métrica | v1 (antes) | v2 (ahora) |
|---------|-----------|------------|
| PRs abiertos por push | 1 | 0 (si ya hay uno) o 1 (si no) |
| Niveles de gate antes de producción | 1 (merge a main) + manual fast-forward | 3 explícitos (staging-frontend → main → cd-system) |
| Ruido potencial con 5 pushes seguidos | 5 PRs | 1 PR engordando |
| Capacidad de probar PR sin afectar nada | Solo `gh pr checkout` local | `gh pr checkout` + branch staging dedicada |
| Workflow lines | 75 | 78 (cambio mínimo) |

## Referencias cruzadas

- Deep dive previo: [`02-github-ci.md`](./02-github-ci.md) — fix del deploy.yml
- Doc operativo completo: [`../infrastructure/06-frontend-pr-workflow.md`](../infrastructure/06-frontend-pr-workflow.md) (incluye comandos, troubleshooting, bloque IA)
- Guía dev frontend: [`../repo-frontend-dev/guia-de-uso.md`](../repo-frontend-dev/guia-de-uso.md) — actualizada a v2 (2026-05-04)
- Anatomía de un demo en cd-frontend-dev: [`../repo-frontend-dev/anatomia-de-un-demo.md`](../repo-frontend-dev/anatomia-de-un-demo.md)
- Arquitectura del sync (3 capas): [`../repo-frontend-dev/sync-architecture.md`](../repo-frontend-dev/sync-architecture.md)

---

## Adición 2026-05-04 — Sincronización bidireccional

Tras el primer release de v2 se identificó un riesgo concreto: el sync era **unidireccional** (dev → maestro). Si el maintainer editaba paths sincronizables directo en cd-system (ej. fix urgente al header de un demo), el cambio NO bajaba a `cd-frontend-dev`. El dev frontend, al hacer push de su trabajo local, sobrescribía el cambio del maintainer con su versión vieja → drift silencioso, fix perdido.

**Solución**: workflow inverso `cd-system/.github/workflows/sync-to-frontend-dev.yml` que detecta pushes a la branch productiva tocando paths sincronizables y los propaga directo (sin PR) a `cd-frontend-dev/main`.

### Corrección 2026-05-05: trigger en `cd-system`, no `main`

Al deployar v2 se descubrió que en este repo `main` y `cd-system` tienen histories no relacionadas (initial commits distintos). El equipo trabajó siempre en `cd-system` y `main` quedó abandonada. Por lo tanto:

- **Trigger del sync inverso**: `branches: [cd-system]` (no `main`).
- **Branch base de los PRs auto del dev frontend**: `staging-frontend`, basada en `cd-system` HEAD (resetada el 2026-05-05).
- **Flujo real (2 etapas, no 3)**:
  1. dev push cd-frontend-dev/main → PR auto en cd-system contra `staging-frontend`.
  2. maintainer mergea staging-frontend → cd-system → dispara `deploy.yml` (producción) + `sync-to-frontend-dev.yml` (broadcast a cd-frontend-dev) + `build-image.yml` (rebuild Docker).

**Por qué push directo y no PR**: el cambio ya pasó revisión humana en cd-system vía PR de la etapa 3a. Doble revisión sería ruido.

**Anti-loop**: ambos workflows skipean commits del otro (filter por mensaje de commit).

### Activación pendiente — paso manual del maintainer

```bash
# 1. Crear PAT en GitHub:
#    avatar → Settings → Developer settings → Personal access tokens → Tokens (classic)
#    → New token → scope: repo → Expiration: 12 meses

# 2. Setear el secret en cd-system:
echo "<paste-token-acá>" | gh secret set CD_FRONTEND_DEV_PAT --repo LACOMPANIADIGITAL/cd-system

# 3. Smoke test:
#    a. Tocar un archivo sincronizable en cd-system/main (ej. resources/views/layout/front/headers/demo-restaurant.blade.php)
#    b. git commit -m "test: smoke sync-to-frontend-dev"
#    c. git push origin main
#    d. Verificar:
gh run list --repo LACOMPANIADIGITAL/cd-system --workflow=sync-to-frontend-dev.yml --limit 1
gh api repos/LACOMPANIADIGITAL/cd-frontend-dev/commits/main -q '.commit.message' | head -1
#    Debería decir: "chore: sync desde cd-system @ <sha>"
```

Si no se setea el PAT, el workflow corre y falla silenciosamente en el step "Checkout cd-frontend-dev". No afecta producción ni el flujo dev → maestro (que sigue OK con `CD_SYSTEM_PAT` ya seteado).

---

*Cerrado: 2026-05-04* — v2 con sync bidireccional. Pendiente: setear `CD_FRONTEND_DEV_PAT` para activar sync inverso.
