# Solicitudes de cambio

Cómo el trabajo de sesión llega a la rama predeterminada mediante revisión.

Canonical page: https://beeuni.ai/docs/es/work/change-requests

Una solicitud de cambio (CR) combina una rama de git con otra. Beeuni crea una CR desde la rama de una sesión (`head_ref`) hacia la rama predeterminada del proyecto (`base_ref`, normalmente `main`). La fila de la CR contiene metadatos; la combinación, el diff y las comprobaciones de conflictos se ejecutan como operaciones reales de git en el repositorio del proyecto. Una CR es la única forma de que el trabajo de una sesión llegue a la rama predeterminada.

<a id="why-work-goes-through-a-cr"></a>

## Por qué el trabajo pasa por una CR

Una sesión se ejecuta en un entorno aislado y en su propia rama, cuyo nombre corresponde al ID de la sesión. El entorno aislado no dura para siempre, pero la rama sí: git es el único registro duradero del trabajo de una sesión. Cada sesión nueva parte de la rama predeterminada. Hasta que una CR se combine, el trabajo permanece en su propia rama, sin revisar e invisible para los demás agentes, activadores y colaboradores. Esto se aplica a todos los cambios: código, agentes, habilidades y el manifiesto (`beeuni.yaml`), sin excepciones.

<a id="the-agent-mandate"></a>

## Obligación del agente

Un agente debe abrir una CR para incorporar cualquier cambio a la rama predeterminada del proyecto. El agente no combina su propia CR: combinarla es decisión del usuario. Sigue este procedimiento:

1. Confirma los cambios en la rama de la sesión (`$BEEUNI_BRANCH_NAME`). Haz confirmaciones pequeñas y funcionales. No reescribas el historial ni fuerces el envío.
2. Envía la rama: `git push origin HEAD`.
3. Abre la CR: `beeuni cr open --title "..." --description "..."`. En el entorno aislado de una sesión, `--head` y `--session` usan `$BEEUNI_BRANCH_NAME` y `$BEEUNI_SESSION_ID` como valores predeterminados. `--base` usa como valor predeterminado la rama predeterminada del proyecto.
4. Dile al usuario el número de la CR para que pueda revisarla.
5. Detente. No combines la CR tú mismo.

<a id="anti-patterns"></a>

### Malas prácticas

- **Forzar el envío a la rama predeterminada.** Esto rompe el proceso de revisión, aunque el backend lo permita.
- **«Está en mi rama, descárgala tú».** La rama de la sesión desaparece cuando se detiene el entorno aislado, a menos que una CR la haya combinado antes.
- **Enviar el cambio como archivo, texto pegado o archivo comprimido.** El sistema de CR ya resuelve este problema.

<a id="data-model"></a>

## Modelo de datos

Las CR se almacenan en la tabla `change_requests`.

| Columna | Tipo | Notas |
| --- | --- | --- |
| `cr_id` | uuid | Clave principal. Identificador de la API REST. |
| `project_id` | uuid | Proyecto al que pertenece la CR. |
| `number` | integer | Número visible dentro del proyecto (`#1`, `#2`…). Único por proyecto. Nunca se reutiliza. |
| `title` | text | Obligatorio. |
| `description` | text | Valor predeterminado: una cadena vacía. |
| `base_ref` | text | Rama en la que se combina. Normalmente `main`. |
| `head_ref` | text | Rama desde la que se combina. En una sesión, es el ID de la sesión (un UUID). |
| `status` | enum | `open`, `merged` o `closed`. |
| `head_commit_sha` | text | En las CR abiertas, se actualiza en cada lectura con la referencia actual de `head_ref`. En las CR combinadas, se captura al momento de la combinación. |
| `base_commit_sha` | text | Misma regla, para `base_ref`. |
| `origin_session_id` | text | Sesión que abrió la CR. Se establece en null si se elimina esa sesión. |
| `created_by` | uuid | Usuario que creó la CR. |
| `merged_at` / `merged_by` | timestamp / uuid | Cuándo y quién combinó la CR. |
| `merge_commit_sha` | text | Confirmación de combinación. Es igual a `head_commit_sha` si se hizo un avance rápido. |
| `closed_at` / `closed_by` | timestamp / uuid | Cuándo y quién cerró la CR sin combinarla. |
| `metadata` | jsonb | Contiene `requested_changes`, una lista de entradas `{text, by, at}` añadidas por `POST /:crId/request-changes`. Las CR no tienen una tabla de comentarios aparte. |
| `created_at` / `updated_at` | timestamp | Se establece al crear. Se actualiza con cada cambio de estado o actualización de SHA. |

Un índice único en `(project_id, number)` permite hacer referencia a una CR por su número corto en lugar de su UUID.

<a id="lifecycle"></a>

## Ciclo de vida

```
open ──(merge)──▶ merged      (terminal)
open ──(close)──▶ closed ──(reopen)──▶ open
```

- `open` es el estado inicial.
- `closed` es reversible. `POST /:crId/reopen` lo devuelve a `open`.
- `merged` es terminal. No se puede volver a abrir ni cerrar una CR combinada. En su lugar, abre una CR nueva sobre el estado combinado.

Beeuni no permite crear una CR cuya rama no tenga confirmaciones posteriores a la rama predeterminada. Esto suele significar que el agente confirmó los cambios localmente, pero nunca los envió. Envía las confirmaciones y vuelve a crear la CR.

<a id="sha-refresh"></a>

## Actualización de SHA

Para una CR abierta, Beeuni actualiza `head_commit_sha` y `base_commit_sha` con las ramas actuales en cada `GET`. Si no se puede acceder al repositorio o falta una rama, Beeuni omite la actualización y devuelve los últimos metadatos conocidos de la CR. Una CR combinada conserva los SHA capturados al momento de combinarla: Beeuni no vuelve a actualizarlos.

<a id="merge-mechanics"></a>

## Mecánica de combinación

`POST /v1/projects/:projectId/change-requests/:crId/merge` ejecuta estos pasos.

1. Beeuni lee el manifiesto (`beeuni.yaml`) desde `head_ref` y lo valida con el esquema del manifiesto. Si una rama no tiene manifiesto, se acepta. Si el manifiesto no es válido, devuelve `422` con `code: "MANIFEST_INVALID"` y detiene la combinación.
2. Beeuni adelanta `base_ref` si `head_ref` está estrictamente por delante.
3. De lo contrario, Beeuni crea una confirmación de combinación. El mensaje predeterminado es `Merge CR #<n>: <title>` y puedes sustituirlo con `message` en el cuerpo de la solicitud. El autor de la confirmación es `Beeuni <noreply@beeuni.com>`.
4. Si hay un conflicto, devuelve `409` con la lista de conflictos. Comprueba la misma lista con `GET /:crId/merge-preview` antes de combinar.
5. Si todo sale bien, Beeuni establece `status` en `merged`, registra `merged_at`, `merged_by` y `merge_commit_sha`, e invalida la caché de git del proyecto.

Si se intenta combinar una CR que no está `open`, se devuelve `409`.

<a id="merge-preview"></a>

### Vista previa de la combinación

`GET /:crId/merge-preview` devuelve:

| Campo | Tipo | Significado |
| --- | --- | --- |
| `base_sha` | string | Referencia actual de `base_ref`. |
| `head_sha` | string | Referencia actual de `head_ref`. |
| `merge_base` | string \| null | Ancestro común. Es null si los historiales no están relacionados. |
| `is_up_to_date` | boolean | `head_ref` ya está completamente combinado en `base_ref`. |
| `can_merge` | boolean | No hay conflictos. |
| `can_fast_forward` | boolean | `head_ref` está estrictamente por delante de `base_ref`. |
| `conflicts` | string[] | Rutas de archivos que entrarían en conflicto. |

<a id="rest-api"></a>

## API REST

Todas las rutas están bajo `/v1/projects/:projectId/change-requests`.

| Método | Ruta | Notas |
| --- | --- | --- |
| GET | `/` | `?status=open\|merged\|closed\|all`. Si no se aplica ningún filtro, devuelve todos los estados. |
| POST | `/` | Cuerpo: `{title, description?, head_ref, base_ref?, session_id?}`. Devuelve `201`. |
| GET | `/:crId` | Devuelve la CR. Actualiza los SHA como efecto secundario. |
| PATCH | `/:crId` | Edita `title` o `description`. `409` si no es `open`. |
| GET | `/:crId/diff` | Parche unificado: lista de archivos, adiciones y eliminaciones. |
| GET | `/:crId/merge-preview` | Consulta la sección «Vista previa de la combinación». |
| POST | `/:crId/merge` | Cuerpo: `{message?}`. `422` si el manifiesto no es válido. `409` si hay conflictos o si no es `open`. |
| POST | `/:crId/close` | `409` si ya está `merged`. |
| POST | `/:crId/reopen` | `409` si no está `closed`. |
| POST | `/:crId/request-changes` | Cuerpo: `{feedback}`. Añade a `metadata.requested_changes` y reactiva el agente de la sesión de origen. `409` si no es `open`. |

`POST /` rechaza una `head_ref` que no tenga confirmaciones posteriores a `base_ref`: `422 code: "CR_HEAD_NOT_AHEAD"`. Este es el error que recibe un agente si abre una CR antes de enviar su rama.

<a id="authorization"></a>

### Autorización

Las rutas de lectura requieren acceso de lectura al proyecto. Las rutas de escritura requieren acceso de escritura. Además, cada acción de escritura requiere una capacidad, indicada a continuación tanto para un token completo como para el token con permisos limitados de una sesión.

| Acción | Capacidad |
| --- | --- |
| Abrir una CR | `project.gitops.push` |
| Solicitar cambios | `project.review.act` |
| Combinar | `project.gitops.merge` |

Se requiere una capacidad por acción, tanto para un token completo como para el token con permisos limitados de una sesión. Abrir y combinar son permisos independientes, por lo que un token puede abrir solicitudes de cambio sin tener permiso para combinarlas: ese es el mecanismo que hace cumplir la obligación del agente descrita antes.

`project.cr.open` y `project.cr.merge` eran los nombres anteriores al cambio para `project.gitops.push` y `project.gitops.merge`: la misma capacidad con un segundo nombre. Un `beeuni.yaml` que aún incluya uno de ellos sigue funcionando (el permiso se reescribe con el nombre vigente al resolverlo), pero usa el nombre `gitops` en cualquier elemento nuevo.

Una sesión nunca puede combinar una solicitud de cambio que ella misma abrió, independientemente de los permisos que se le hayan concedido. Esta regla es estructural, no una capacidad.
