# Integración técnica GitHub ↔ Asana — Nexus33

**Fecha de implementación:** 29 de julio de 2026
**Autor de la implementación:** Hector García (vía sesión asistida con Claude Code)
**Propósito:** documentar cada paso técnico para que cualquiera pueda revisar, ajustar, depurar, o extender esto a un repo/proyecto nuevo sin tener que re-derivarlo desde cero.

---

## 1. Arquitectura — qué existe realmente

Hay **dos integraciones distintas** entre Asana y GitHub. Solo una de las dos es la que realmente automatiza algo hoy:

| | App oficial "GitHub" de Asana | Automatización propia (la que funciona) |
|---|---|---|
| Dónde se activó | Asana → Ajustes → Aplicaciones → GitHub → Conectar (por proyecto) | GitHub Actions, un workflow por repo |
| Qué hace | Permite pegar manualmente la URL de un PR en el campo "Aplicaciones" de una tarea | Comenta automáticamente en la tarea de Asana correcta, sin intervención humana |
| Requiere copiar/pegar | Sí (se descartó por esto) | No |
| Estado actual | Queda conectada pero no se usa activamente | **Esta es la que está en producción** |

Todo lo que sigue en este documento describe la **automatización propia**.

---

## 2. Cómo funciona (flujo técnico)

1. Un desarrollador abre/aprueba/mergea un PR cuyo **título** contiene un ticket con el patrón `NEXDEV-[0-9]+` (ej. `NEXDEV-123`), sin importar mayúsculas/minúsculas.
2. Un GitHub Action (`.github/workflows/link-asana-task.yml`, presente en cada uno de los 12 repos) se dispara con el evento correspondiente.
3. El Action extrae el ticket del título con `grep -oiE 'NEXDEV-[0-9]+'`.
4. Llama a la API de Asana:
   - `GET /projects/{project_gid}/custom_field_settings` → busca el campo personalizado llamado exactamente `NEXDEV` y obtiene su `gid`.
   - `GET /projects/{project_gid}/tasks?opt_fields=name,custom_fields` (paginado) → recorre las tareas del proyecto "Desarrollo" buscando la que tenga ese campo con el mismo valor que el ticket extraído.
   - `POST /tasks/{task_gid}/stories` → agrega un comentario en la tarea con el evento ocurrido.
5. El comentario en Asana varía según el evento (ver tabla abajo).

**Nota importante:** esta búsqueda NO usa el endpoint de búsqueda avanzada de Asana (`/workspaces/{gid}/tasks/search`), porque ese requiere plan premium y no es confiable en plan Starter. En su lugar, se listan las tareas del proyecto directamente y se filtra en el propio script — funciona en cualquier plan pago de Asana.

---

## 3. Datos de configuración (IDs reales, no cambian salvo que se reestructure Asana)

| Dato | Valor |
|---|---|
| Workspace de Asana (nexus33.com) | `1208690669624843` |
| Proyecto "Desarrollo" (gid) | `1209109605438289` |
| Nombre del campo personalizado en Asana | `NEXDEV` (texto libre, formato `NEXDEV-###`) |
| Nombre del secreto en GitHub | `ASANA_PAT` (repository secret, uno por repo) |
| Nombre del check obligatorio en branch protection | `Validar ticket Asana` |

Si en algún momento se reorganiza el proyecto de Asana (se borra y se crea de nuevo, por ejemplo), estos `gid` cambian y hay que actualizar `ASANA_PROJECT_GID` en el workflow de los 12 repos.

---

## 4. Los 3 workflows involucrados (qué hace cada uno)

### 4.1 `.github/workflows/check-asana-ticket.yml` — el candado obligatorio
- Se dispara con: `pull_request: [opened, edited, synchronize, reopened]`.
- Verifica que el título del PR tenga `NEXDEV-[0-9]+` (insensible a mayúsculas).
- Si falta, falla el check (`exit 1`) — y como está en `required_status_checks` de branch protection, **bloquea el botón de Merge**.
- No habla con Asana para nada, es puramente un candado del lado de GitHub.

### 4.2 `.github/workflows/link-asana-task.yml` — el que comenta en Asana
- Se dispara con: `pull_request: [opened, closed]` y `pull_request_review: [submitted]`.
- Busca la tarea en Asana y comenta según el evento:

| Evento | Condición | Comentario en Asana |
|---|---|---|
| `pull_request.opened` | — | `PR abierto por <usuario>` |
| `pull_request_review.submitted` | `review.state == approved` | `Aprobado por <usuario>` |
| `pull_request_review.submitted` | `review.state == changes_requested` | `Cambios solicitados por <usuario>` |
| `pull_request.closed` | `merged == true` | `Mergeado a <rama base> por <usuario que mergeo>` |
| `pull_request.closed` | `merged == false` | `Cerrado sin mergear` |

- **No es un check obligatorio** — si falla (ej. Asana está caído, o no encuentra la tarea), no bloquea el merge, solo se salta el paso con un `::warning::`.
- Si el secreto `ASANA_PAT` no está configurado en el repo, se salta silenciosamente (`exit 0`) sin marcar error — así no rompe repos donde todavía no se configuró.

### 4.3 `.github/workflows/auto-assign-pr.yml` — asignación automática
- Se dispara con: `pull_request: [opened]`.
- Asigna el PR (campo "Assignee", no "Reviewer") a quien lo abrió, usando el token automático `secrets.GITHUB_TOKEN` (no necesita `ASANA_PAT` ni ningún secreto configurado a mano).
- Es editable manualmente después, esto solo pone el valor por defecto.
- No tiene relación con Asana, es puramente organizativo del lado de GitHub.

---

## 5. El secreto `ASANA_PAT` — cómo se generó y se instaló

1. En Asana: **perfil → Ajustes → Aplicaciones → "Ver la consola del desarrollador" → Create new token.**
2. El token se pegó manualmente (nunca por chat/API) en cada uno de los 12 repos: **`Settings → Secrets and variables → Actions → New repository secret`**, nombre exacto `ASANA_PAT`.
3. *(Detalle técnico de cómo se automatizó la carga la primera vez, por si se repite):* GitHub exige que el valor de un secreto se suba cifrado con la llave pública del repo (`crypto_box_seal` de libsodium) — se hizo con un script de Node.js usando el paquete `libsodium-wrappers`:
   - `GET /repos/{owner}/{repo}/actions/secrets/public-key` → devuelve `key` (base64) y `key_id`.
   - Cifrar el valor del token con esa llave pública (`sodium.crypto_box_seal`).
   - `PUT /repos/{owner}/{repo}/actions/secrets/ASANA_PAT` con `{ encrypted_value, key_id }`.
   - El token nunca se imprimió en ningún log ni salida de consola durante este proceso.

---

## 6. Cómo agregar un repo NUEVO a esta integración (runbook)

1. **Confirmar que GitHub Actions está habilitado en el repo.** Ver sección 8 — este fue el bug más grande que encontramos: viene deshabilitado por defecto en cuentas personales. Revisar en `Settings → Actions → General`, o vía API: `GET /repos/{owner}/{repo}/actions/permissions` debe devolver `"enabled": true`.
2. **Copiar los 3 archivos de workflow** de cualquier repo existente (ej. `nexus33-frontend`) a `.github/workflows/` del repo nuevo:
   - `check-asana-ticket.yml`
   - `link-asana-task.yml`
   - `auto-assign-pr.yml`
3. **Agregar el secreto `ASANA_PAT`** en el repo nuevo (mismo token de Asana que ya existe, no hace falta generar uno nuevo — es el mismo workspace).
4. **Confirmar los nombres de rama** del repo nuevo (`master` vs `main`, `development` vs `develop`) y ajustar branch protection en consecuencia.
5. **Configurar branch protection** en las ramas principal y de desarrollo:
   - `required_pull_request_reviews.required_approving_review_count = 1`
   - `required_pull_request_reviews.dismiss_stale_reviews = true`
   - `required_pull_request_reviews.require_code_owner_reviews = false`
   - `enforce_admins = true`
   - `required_status_checks.contexts = ["Validar ticket Asana"]`, `strict = false`
   - `allow_force_pushes = false`, `allow_deletions = false`
6. **Ojo con el problema de arranque:** el PR que introduce estos workflows por primera vez **no puede pasar su propio check** (GitHub no ejecuta un workflow nuevo en el mismo PR que lo agrega). Hay que desactivar temporalmente `required_status_checks` (ponerlo en `null`), aprobar y mergear ese PR inicial, y **recién ahí** volver a activar el required check. Ver sección 8 para más detalle de por qué pasa esto.
7. Si el ticket de este repo nuevo corresponde a otro proyecto de Asana (no "Desarrollo"), hay que ajustar `ASANA_PROJECT_GID` en `link-asana-task.yml` para ese repo — hoy el valor está fijo, no es dinámico por PR.

---

## 7. Cómo agregar un proyecto de Asana NUEVO (si algún día se necesita)

Hoy la automatización busca únicamente en el proyecto "Desarrollo" (`1209109605438289`). Si se necesita que otro proyecto de Asana también participe:

- **Opción simple:** duplicar la lógica de búsqueda en el script para recorrer una lista de `project_gid` en vez de uno solo, y detenerse en el primero donde encuentre coincidencia.
- Cada proyecto nuevo necesita su propio campo personalizado equivalente a `NEXDEV` (puede tener otro nombre/prefijo, ej. `COMPAX-123` para `compax-frontend`), y el script tendría que aceptar qué prefijo buscar según el repo.
- Esto no está construido todavía — es una extensión pendiente, no algo que ya soporte el código actual.

---

## 8. Troubleshooting — problemas reales que ya nos pasaron

### 8.1 "El PR está aprobado pero no me deja mergear" (`mergeable_state: blocked`)
**Causa más común:** GitHub Actions está deshabilitado a nivel de repo. Nos pasó en 9 de los 12 repos el primer día.
**Diagnóstico:** `GET /repos/{owner}/{repo}/actions/permissions` → si `enabled: false`, ese es el problema. El check obligatorio nunca corre, entonces nunca reporta éxito, entonces GitHub deja el PR eternamente "esperando".
**Arreglo:** `PUT /repos/{owner}/{repo}/actions/permissions` con `{"enabled": true}`. Para los PRs que ya estaban abiertos y atascados, hace falta generar un evento nuevo para que el workflow corra por primera vez — la forma más simple es editar la descripción del PR (dispara el evento `edited`), no hace falta cerrar y reabrir.

### 8.2 "Agregué un workflow nuevo pero el check nunca corre en ese mismo PR"
**Causa:** comportamiento esperado de GitHub Actions — un workflow que se agrega en un PR no se activa para ESE PR, porque todavía no existe en la rama base al momento de evaluar el evento. Sí va a funcionar para cualquier PR que se abra DESPUÉS de que este se mergee.
**Arreglo:** desactivar temporalmente `required_status_checks` (ponerlo `null`) antes de mergear el PR que introduce el workflow, y reactivarlo después. Es 100% reversible, no rompe nada — el PR simplemente no puede autoevaluarse a sí mismo la primera vez.

### 8.3 "Configuré `required_status_checks` con el nombre del check y ahora nada pasa"
**Causa:** el nombre en `contexts` tiene que coincidir exactamente con el nombre del check tal como lo reporta GitHub Actions (el campo `name:` del job en el YAML). Si no coincide ni una coma, el check nunca se satisface.
**Arreglo:** revisar en la pestaña "Checks" de cualquier PR cuál es el nombre exacto que aparece, y usar ese mismo texto literal en `required_status_checks.contexts`. Se puede corregir en cualquier momento desde branch protection sin perder nada — no es destructivo.

### 8.4 El comentario en Asana no aparece
**Posibles causas, en orden de probabilidad:**
1. El repo no tiene el secreto `ASANA_PAT` configurado todavía (el workflow se salta el paso silenciosamente, revisar el log del run en la pestaña Actions).
2. El título del PR no tiene el patrón `NEXDEV-###` reconocible.
3. No existe ninguna tarea en el proyecto "Desarrollo" con ese valor exacto en el campo personalizado `NEXDEV`.
4. GitHub Actions deshabilitado en ese repo (ver 8.1).

---

## 9. Seguridad

- El token `ASANA_PAT` vive únicamente como secreto cifrado de GitHub Actions — nunca se guardó en texto plano en ningún archivo del repo, chat, ni commit.
- El workflow que lo usa (`link-asana-task.yml`) no es un check obligatorio, así que un fallo de autenticación con Asana nunca bloquea el trabajo de desarrollo.
- Si se necesita rotar el token (ej. alguien deja la empresa y era el dueño del token), hay que generarlo de nuevo desde la cuenta de Asana correspondiente y volver a subirlo como secreto en los 12 repos — no hay un paso intermedio que dependa de una sola persona salvo quien administre Asana.