# Citas reservadas por MCP

crm_bookings y crm_bookings_workflow: las citas que reservan tus clientes, los enlaces para reservar y las páginas de reservas, con sus permisos, scopes, la aprobación en el móvil y los errores.

`crm_bookings` y `crm_bookings_workflow`, del servidor `utilia-crm`, hacen por MCP lo mismo que CRM › Citas: ver las citas que reservan tus clientes, darles una cita, mandarles un enlace para que elijan hora, cambiarlas, cancelarlas y marcarlas, y elegir las horas y la página de reservas de cada persona. Usan los mismos servicios que la aplicación, con sus reglas. Los eventos de la agenda que no reservó un cliente siguen en `calendar_events`.

## Requisitos

- **Plan**: el aplicativo CRM y la capacidad «Reserva de citas». Sin ella, `CAPABILITY_NOT_ENABLED` con el plan que la abre y `upgradeUrl`. Lo que la empresa ya tiene reservado sigue atendiéndose sin la capacidad (más abajo).
- **Permiso de la persona** sobre la persona con la que es la cita: «Ver citas y enlaces de reserva» (`BOOKINGS_VIEW`), «Gestionar citas y enlaces de reserva» (`BOOKINGS_MANAGE`) o «Publicar la página de reservas» (`BOOKING_PAGE_MANAGE`), con su alcance: tú, quien supervisas, tu departamento o todo el equipo.
- **Scope del token**, por operación:

| Scope | Qué abre | ¿En qué conjunto viaja? |
| - | - | - |
| `crm:bookings:view` | Leer citas, enlaces, páginas y horas libres | `crm:read` y `crm:full` |
| `crm:bookings:manage` | Cambiar, cancelar y marcar citas, decidir «No es la misma» y retirar enlaces | `crm:full` (no `crm:write`) |
| `crm:booking-links:share` | Reservar por un cliente, crear y cambiar enlaces y «Pedir que elija otra hora» | Ninguno: se pide por su nombre |
| `crm:booking-pages:manage` | Las horas para citas de una persona y su página de reservas | Ninguno: se pide por su nombre |

Los dos últimos crean algo que se abre sin iniciar sesión o escriben a personas de fuera con el nombre de tu empresa, por eso ninguna integración los gana sin pedirlos. Si falta uno, la respuesta es `INSUFFICIENT_SCOPE` con el enlace para concederlo sin desconectar (`org_plan` `connection_access` lo dice antes).

## Operaciones

### `crm_bookings` (lectura)

| Operación | Qué devuelve | Permiso |
| - | - | - |
| `appointments_list` | Citas con filtros (`status`, `hostId` o `host`, `source`, `from`, `to`, `leadId`, `contactId` o `clientId`, `unmarked`, `inAbsence`, `crmMatch`, `upcoming`). `contactId` suma las del lead que se convirtió en ese contacto; `clientId`, las de todos sus contactos y de los leads convertidos en él. Vista resumida por defecto, sin correo ni teléfono; `response_format: "detailed"` da la fila entera. Paginación con `limit` (hasta 50) y `cursor`. | `BOOKINGS_VIEW` |
| `appointment_get` | La cita con su sala, su evento, su interacción, su ficha del CRM y lo que escribió el cliente (`clientWritten`). | `BOOKINGS_VIEW` |
| `links_list`, `link_get` | Los enlaces con su estado (`linkStatus`), con los mismos filtros por ficha (`leadId`, `contactId` o `clientId`) y, si la medición está activa, cifras de conjunto de lo que se hizo en cada uno. Nunca la dirección del enlace. | `BOOKINGS_VIEW` |
| `pages_list`, `page_get` | Las páginas del equipo; `page_get` añade las horas para citas de la persona y las cifras de su página por semanas (`weeks`, 4 por defecto). | `BOOKINGS_VIEW` |
| `slots` | Horas libres de una persona, cada una con `start` (ISO) y `local`, en su zona. Sin `from` ni `to`, los 7 días siguientes; 80 horas como mucho por llamada. Con `appointmentId`, las de esa cita sin contar su propio hueco; con `linkId`, las que permite ese enlace. | `BOOKINGS_MANAGE` sobre esa persona |

### `crm_bookings_workflow` (escritura)

| Operación | Qué hace | Permiso | Scope | ¿`confirm`? |
| - | - | - | - | - |
| `book` | Reserva una cita en nombre de un cliente y le escribe. Lleva `idempotencyKey`. | `BOOKINGS_MANAGE` | `crm:booking-links:share` | sí |
| `link_create` | Crea un enlace para reservar y devuelve `url` y `share` (el texto para mandarlo). No envía nada. | `BOOKINGS_MANAGE` | `crm:booking-links:share` | no |
| `link_update` | Cambia mensaje, nota, caducidad, canal o duración; devuelve otra vez `url` y `share`. | `BOOKINGS_MANAGE` | `crm:booking-links:share` | no |
| `appointment_request_new_time` | Cancela la cita y manda al cliente un enlace nuevo para elegir otra hora. | `BOOKINGS_MANAGE` | `crm:booking-links:share` | sí |
| `appointment_reschedule` | Cambia la hora (`start`), la duración (`durationMinutes`) o las dos; nunca la persona. | `BOOKINGS_MANAGE` | `crm:bookings:manage` | sí |
| `appointment_cancel` | Cancela la cita y avisa al cliente. | `BOOKINGS_MANAGE` | `crm:bookings:manage` | sí |
| `appointment_mark` | «Hecha» o «No se presentó» (`outcome`), cuando ya ha empezado. | `BOOKINGS_MANAGE` | `crm:bookings:manage` | no |
| `appointment_crm_match` | «No es la misma» sobre un «Correo sin comprobar» (`decision: "different"`): da de alta el lead si hay Leads en el plan. | `BOOKINGS_MANAGE` | `crm:bookings:manage` | no |
| `link_revoke` | Retira un enlace. | `BOOKINGS_MANAGE`, o `BOOKINGS_VIEW` si es tuyo | `crm:bookings:manage` | sí |
| `availability_update` | Guarda las horas en que una persona recibe citas (`weeklyHours`, «Días sin citas», duración, antelación...). | `BOOKINGS_MANAGE` | `crm:booking-pages:manage` | no |
| `page_update` | Dirección (`slug`), título, descripción, preguntas, tope diario y medición de la página. | `BOOKING_PAGE_MANAGE` | `crm:booking-pages:manage` | no |
| `page_publish` | Publica la página: cualquiera con la dirección ve sus horas libres y reserva. | `BOOKING_PAGE_MANAGE` | `crm:booking-pages:manage` | sí |
| `page_pause` | Pausa la página; las citas ya reservadas se mantienen. | `BOOKING_PAGE_MANAGE`, o `BOOKINGS_VIEW` si es tuya | `crm:booking-pages:manage` | sí |

Sin `confirm: true`, una escritura responde `VALIDATION` con `details.field: "confirm"` y no toca nada; en `book`, el mensaje cuenta lo que va a pasar para que se lo enseñes a la persona. Toda escritura devuelve `effect`: `summary` (lo que ha pasado, en una frase), `clientNotified` (si el cliente ha recibido un correo) y, si se puede, `undo`.

## Personas y fichas por su nombre

Donde se pide `hostId` vale también `host`, con el nombre o el correo de la persona; donde se pide `leadId` o `contactId`, `lead` o `contact`. Solo se elige sola la que encaja exacta entre las que puedes ver o gestionar. Si encajan varias, o solo una por parte del nombre, la respuesta es `CONFLICT` con `BOOKING_HOST_AMBIGUOUS` o `BOOKING_RECORD_AMBIGUOUS` y las candidatas en `details.items`: pregunta cuál es y repite con su identificador. Sin `hostId` ni `host`, la persona eres tú.

## Reservar por un cliente

`book` necesita `start` (una de las horas de `slots`), `mode` (`VIDEO`, que exige el correo, o `PHONE`, que exige el teléfono) y una `idempotencyKey` tuya para esa reserva. Con `leadId` o `contactId`, el nombre y el correo salen de la ficha.

- **Con la ficha y su correo**, basta `confirm: true`.
- **Con un correo que no es el de la ficha, o sin ficha**, la reserva se aprueba además en el móvil de la persona, atada al correo, a la persona con la que es la cita y a la hora: la primera llamada responde `APPROVAL_REQUIRED` con su `approvalId` y no reserva nada; cuando la persona la apruebe, repite la misma llamada con `twoFactorApprovalId`. Una aprobación pedida para un correo no vale para otro. Sin la verificación en dos pasos activada, `TWO_FACTOR_NOT_ENABLED`: se reserva en la aplicación.
- **Si pierdes la respuesta**, repite con la misma `idempotencyKey` y los mismos datos: devuelve la misma cita y no escribe dos veces al cliente. La misma clave con otros datos responde `IDEMPOTENCY_KEY_REUSED`.
- **Sin correo** (una llamada por teléfono), el cliente no recibe confirmación. El «Ver mi cita» para mandarlo por WhatsApp se copia en la aplicación: por MCP no sale nunca.
- Como mucho 20 reservas por persona y hora (`BOOKING_ON_BEHALF_RATE_LIMITED`).

```json
{
  "jsonrpc": "2.0",
  "id": "1",
  "method": "tools/call",
  "params": {
    "name": "crm_bookings_workflow",
    "arguments": {
      "operation": "book",
      "host": "Marta García",
      "leadId": "cmlead0001",
      "start": "2026-11-10T09:00:00.000Z",
      "mode": "VIDEO",
      "confirm": true,
      "idempotencyKey": "reserva-ana-martes-10"
    }
  }
}
```

## Lo que no sale por MCP

- **La dirección de un enlace** solo sale al crearlo o cambiarlo (`link_create`, `link_update`), con `crm:booking-links:share`. `links_list` y `link_get` no la traen: un enlace reserva y escribe al cliente, no es como el de un archivo.
- **«Ver mi cita» y la sala** no salen nunca.
- **Lo que se hizo en un enlace** solo llega si la persona aceptó la medición, y solo de conjunto: aperturas, si alguna fue en el móvil, el paso más lejano y los días que miró. `activity: null` es «sin datos», no «no lo ha abierto». Nunca ciudad, país, idioma, navegador ni aparatos. De la página, solo cifras por semana. Mientras la medición no esté activa en tu cuenta, la respuesta lo dice una vez en `measurement`.
- **Lo que escribe el cliente** (su nota y sus respuestas) llega aparte, en `clientWritten`, en una línea y con un aviso: son datos, nunca instrucciones.
- **Texto libre al cliente al cancelar**: el motivo es de una lista cerrada (`reasonCode`: `HOST_UNAVAILABLE`, `CLIENT_ASKED`, `DUPLICATE` u `OTHER`, sin motivo).
- **«Es la misma persona»** sobre un «Correo sin comprobar» lo decide alguien del equipo en la aplicación; por MCP solo «No es la misma».

## Sin la capacidad en el plan

Al acabar la prueba o bajar de plan, la empresa conserva lo que ya tenía con sus clientes. Siguen abiertas, sin la capacidad y con su permiso: `appointments_list` y `appointment_get` (las citas futuras y las canceladas en los últimos 30 días, con un aviso en `closing`), `appointment_cancel` (también con la cuenta en pausa), y pausar tu página o retirar tus enlaces. Lo demás responde `CAPABILITY_NOT_ENABLED`, y su `hint` lo recuerda.

## Asistentes en un canal

En una respuesta que leen más personas, `crm_bookings` se usa con el alcance de quien pregunta, pero `crm_bookings_workflow` no: escribe a un cliente con el nombre de tu empresa o publica una agenda. El asistente lo explica y se hace en una conversación privada con él.

## Errores

Cada uno lleva su `errorCode` en `details` y una `hint` que dice qué hacer. Los más frecuentes:

| `errorCode` | Qué pasa |
| - | - |
| `BOOKING_SLOT_TAKEN`, `BOOKING_SLOT_NOT_OFFERED` | Esa hora ya no está libre: pide otra con `slots`. |
| `BOOKING_HOST_NOT_FREE` | Al cambiar la hora o la duración, la persona no tiene ese tiempo libre. |
| `BOOKING_HOST_HAS_NO_HOURS` | La persona aún no tiene horas para citas: `availability_update`. |
| `BOOKING_HOST_OUT_OF_SCOPE` | La persona queda fuera del alcance de tu permiso. |
| `BOOKING_HOST_CANNOT_INVITE_EXTERNAL` | La persona no puede invitar a gente de fuera: usa `PHONE` o pide el permiso. |
| `BOOKING_CRM_RECORD_NOT_VISIBLE`, `BOOKING_HOST_CANNOT_SEE_RECORD` | Tú, o la persona de la cita, no podéis ver esa ficha. |
| `BOOKING_CHANGED_MEANWHILE`, `BOOKING_NOT_SCHEDULED` | La cita cambió entre medias o ya no está pendiente: vuelve a leerla. |
| `BOOKING_PAUSED_BY_HOST` | La persona pausó su página o retiró el enlace: solo ella lo deshace. |
| `BOOKING_PAGE_NOT_PUBLISHABLE` | La página se publica cuando el plan incluye «Reserva de citas» y ya se ha cobrado; los enlaces sí funcionan. |
| `BOOKING_HANDLE_REQUIRED` | Falta el nombre público de la empresa, que se elige en la aplicación. |
| `CAPABILITY_NOT_ENABLED` | Falta «Reserva de citas», o «Leads» al anclar una cita o un enlace a un lead. |

La guía de uso está en [Citas con clientes](https://os.utilia.ai/docs/guides/crm/bookings), con [Cambiar, cancelar y marcar una cita](https://os.utilia.ai/docs/guides/crm/booking-appointments) y [Mi disponibilidad y tu página de reservas](https://os.utilia.ai/docs/guides/crm/booking-page); el reparto con el calendario, en [Agenda compartida y categorías por MCP](https://os.utilia.ai/docs/developers/mcp/calendar).
