Skip to content

fix(citas): agendar funciona end-to-end — Cal.com v2 + fecha real en el prompt - #5

Open
mfonseca10 wants to merge 1 commit into
santmun:mainfrom
mfonseca10:fix/appointments-real-dates
Open

fix(citas): agendar funciona end-to-end — Cal.com v2 + fecha real en el prompt#5
mfonseca10 wants to merge 1 commit into
santmun:mainfrom
mfonseca10:fix/appointments-real-dates

Conversation

@mfonseca10

@mfonseca10 mfonseca10 commented Aug 3, 2026

Copy link
Copy Markdown

Qué cambia

Hace que agendar citas funcione de verdad: la tool scheduleAppointment ahora usa el cliente Cal.com v2 que ya vive en integrations/calcom.ts (antes llamaba a la API v1 con payload incompleto), el eventTypeId y la zona horaria se resuelven en el servidor (el modelo ya no los inventa), se agrega modo de consulta de horarios libres (getAvailableSlots), y el system prompt ahora incluye la fecha/hora actual del negocio (currentDateLine) — sin eso, el LLM propone fechas de su época de entrenamiento. Guardia extra date_in_past en la tool y logging de fallos de Cal.com para diagnóstico con wrangler tail.

Por qué

Con un bot real en producción, el flujo de citas fallaba siempre, por tres capas:

  1. La tool llamaba api.cal.com/v1 sin timeZone/language/metadata → rechazo.
  2. El prompt nunca comunica el eventTypeId configurado → el modelo lo alucinaba.
  3. El modelo no sabe qué día es hoy → pedía slots para 2023-10-04 y Cal.com respondía vacío ("no hay horarios disponibles").

Con este fix, el flujo completo quedó verificado end-to-end: el bot ofrece los horarios reales del calendario y crea la reserva (confirmación + invitación de calendario al cliente).

Cómo lo probaste

  • pnpm test pasa (440 pruebas, incluye 6 nuevas del tool: booking v2, slots, fecha pasada, sin API key, sin event type)
  • pnpm typecheck limpio
  • Probado contra un bot real (Telegram + OpenAI + Cal.com): slots reales listados y booking confirmado con invitación de calendario

Checklist

  • No toqué la carpeta member/ (config de cada quien)
  • El PR es de un solo tema (citas funcionando end-to-end)
  • Si tu agente (Claude) hizo el PR, revisaste el diff tú mismo antes de abrirlo
  • No hay secrets ni API keys en el código

Encontrado y arreglado desplegando un bot real de nuestra agencia (SynapMex 🇲🇽). Gracias por Forja — seguiremos regresando mejoras.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added appointment availability lookup for a selected date, including up to 12 available time slots.
    • Added timezone-aware date context to scheduling interactions.
    • Added optional date and service selection for appointment requests.
  • Bug Fixes

    • Prevented bookings for dates in the past.
    • Improved handling of missing configuration, incomplete booking details, and scheduling service failures.
    • Updated appointment booking to use the latest Cal.com API behavior.

… servidor y fecha real en el prompt

- scheduleAppointment usa el cliente v2 de integrations/calcom.ts en vez de
  la llamada v1 incompleta: eventTypeId y timezone resueltos en el servidor
  (el modelo los alucinaba), modo de consulta de horarios libres, guardia
  date_in_past y logging de fallos para wrangler tail
- el system prompt inyecta la fecha/hora actual del negocio (currentDateLine):
  sin ella el LLM propone fechas de su entrenamiento (pedía slots de 2023)
- 6 pruebas del conector actualizadas/nuevas

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mfonseca10
mfonseca10 requested a review from santmun as a code owner August 3, 2026 05:53
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

Scheduling and date context

Layer / File(s) Summary
Timezone-aware date context
src/integrations/calcom.ts, src/system-prompt.ts
The system prompt accepts temporal context and uses the configured timezone to format the current date.
Appointment and availability flow
src/tools/scheduleAppointment.ts, src/integrations/calcom.ts
The appointment tool delegates Cal.com operations, supports slot lookup, validates dates and booking fields, and returns structured errors. Cal.com failures and responses now include truncated log details.
Appointment flow validation
test/tools/scheduleAppointment.test.ts
Tests cover API v2 booking, availability lookup, past-date rejection, configuration errors, and HTTP failure reasons.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Suggested reviewers: santmun

Sequence Diagram(s)

sequenceDiagram
  participant scheduleAppointmentTool
  participant CalcomIntegration
  participant CalComAPI
  scheduleAppointmentTool->>CalcomIntegration: Resolve timezone and event type
  scheduleAppointmentTool->>CalcomIntegration: Query slots or create booking
  CalcomIntegration->>CalComAPI: Send availability or booking request
  CalComAPI-->>CalcomIntegration: Return slots, booking, or HTTP failure
  CalcomIntegration-->>scheduleAppointmentTool: Return result or structured error
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed El título resume los cambios principales: migración a Cal.com v2 y adición de la fecha real al prompt.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
test/tools/scheduleAppointment.test.ts (1)

9-33: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Replace the hard-coded 2027-06-01 fixture with a relative future date.

Five tests in this file hard-code startTime/date values around 2027-06-01. The tool under test rejects any date earlier than "today" (Line 43 in src/tools/scheduleAppointment.ts). Given the current date, 2027-06-01 is less than a year away; once real time passes it, these tests will start failing the past-date guard rather than testing the intended behavior.

Compute a fixture date relative to new Date() (for example, one year out) instead of a fixed literal, so the suite stays valid regardless of when it runs.

+const futureDate = new Date();
+futureDate.setFullYear(futureDate.getFullYear() + 1);
+const futureDateStr = futureDate.toISOString().slice(0, 10);
+const futureStartTime = `${futureDateStr}T17:00:00-06:00`;

Then use futureStartTime/futureDateStr in place of the 2027-06-01 literals across the affected tests.

Also applies to: 35-48, 50-63, 77-94

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/tools/scheduleAppointment.test.ts` around lines 9 - 33, Replace the
fixed 2027-06-01 date literals in the affected tests with shared future fixtures
computed from new Date(), such as futureStartTime and futureDateStr representing
a date about one year ahead. Update the startTime/date inputs across the listed
test cases while preserving their existing assertions and behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/integrations/calcom.ts`:
- Around line 91-96: Update the Cal.com response logging in the slots request
flow to avoid raw response bodies: replace the !res.ok logging with HTTP status
plus a truncated non-PII error field such as error.code or error.message, and
change the success log to report only a concise slot-count summary instead of
JSON.stringify(body.data). Preserve the existing return behavior and locate
these changes around the slots response handling and createBooking-related error
paths.

In `@src/system-prompt.ts`:
- Line 191: Update the system prompt timezone assignment near today to reuse
calcomTimeZone(env) instead of duplicating the CALCOM_TIMEZONE trim/default
expression, and import calcomTimeZone from ../integrations/calcom. Keep the
existing currentDateLine behavior unchanged by passing the helper’s result to
it.

In `@src/tools/scheduleAppointment.ts`:
- Around line 25-26: Enforce strict zero-padded YYYY-MM-DD validation in the
scheduleAppointment schema’s date field, and validate the date portion extracted
from startTime before the past-date guard compares it with today. Reject
malformed or unpadded values before reaching the requestedDay < today
comparison, while preserving valid date and ISO datetime handling.

---

Nitpick comments:
In `@test/tools/scheduleAppointment.test.ts`:
- Around line 9-33: Replace the fixed 2027-06-01 date literals in the affected
tests with shared future fixtures computed from new Date(), such as
futureStartTime and futureDateStr representing a date about one year ahead.
Update the startTime/date inputs across the listed test cases while preserving
their existing assertions and behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0fc8fd65-e0d7-415c-8731-dee047168c8d

📥 Commits

Reviewing files that changed from the base of the PR and between 1f08537 and 5cafb17.

📒 Files selected for processing (4)
  • src/integrations/calcom.ts
  • src/system-prompt.ts
  • src/tools/scheduleAppointment.ts
  • test/tools/scheduleAppointment.test.ts

Comment on lines +91 to +96
if (!res.ok) {
console.warn(`[calcom] slots http_${res.status}:`, (await res.text()).slice(0, 300));
return { ok: false, reason: `http_${res.status}` };
}
const body = (await res.json()) as { data?: Record<string, Slot[]> };
console.log(`[calcom] slots ok eventType=${eventTypeId} date=${date} tz=${timeZone}:`, JSON.stringify(body.data ?? {}).slice(0, 300));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does the Cal.com API v2 /v2/bookings error response ever include the submitted attendee email or name in the error message?

💡 Result:

The Cal.com API v2 error response format does not explicitly include a dedicated field for the submitted attendee email [1][2][3]. According to the official documentation, Cal.com API v2 uses a consistent, structured error response that contains a status, code, and message [1][2][3]. The structure is as follows: { "status": "error", "error": { "code": "SOME_ERROR_CODE", "message": "A description of the error" } } While the error message string (in the "message" field) could technically contain dynamic content depending on the specific validation failure—such as missing required fields or invalid input—there is no systematic inclusion of the submitted attendee email in the standard error response schema [1][4][3]. In practice, error messages are typically generic descriptions of the failed validation or server-side issue [1][5][3].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file size =="
wc -l src/integrations/calcom.ts 2>/dev/null || true

echo "== relevant calcom.ts sections =="
sed -n '1,180p' src/integrations/calcom.ts

echo "== requests for calcom logging =="
rg -n "Cal.com|calcom|/v2/bookings|createBooking|getAvailableSlots|console\.log|console\.warn|HTTP_|http_|res\.text|res\.json|bookings" -S .

Repository: santmun/forja

Length of output: 37450


🌐 Web query:

Cal.com API v2 bookings validation error email name validation message

💡 Result:

When using the Cal.com API v2 to create bookings (POST /v2/bookings), validation errors—such as those involving attendee information (name, email) or other required fields—are typically returned as 400 Bad Request responses containing a structured error object [1][2]. Key details regarding validation and error messages: 1. Structured Error Format: API v2 provides structured error responses [1]. If a validation fails, the response will generally have a status of "error" and include an error code (e.g., BAD_REQUEST) along with specific details about which fields failed validation [1][2][3]. 2. Attendee Validation: - Name: Recent updates to the API have improved name validation to be more flexible, for instance by making the last name optional while still requiring a valid, non-empty first name [4]. - Contact Methods: The API requires at least one valid contact method—either an email or a phone number [5][6]. If an email is provided, it must follow a valid email format [5][6]. 3. Required Fields: If an event type has mandatory booking fields defined in its configuration, failing to provide values for these in the bookingFieldsResponses object will trigger a validation error [7]. Recent efforts have been made to improve these error messages to explicitly name the missing fields rather than returning generic or 500-level errors [7][8]. 4. Troubleshooting Common Errors: - 400 Bad Request: Usually indicates malformed JSON, missing required fields (like attendee information or timeZone), or invalid data types (e.g., sending an integer as a string) [2][9][3]. - 500 Internal Server Error: While the API aims to return 400s for validation issues, some complex edge cases (like lack of availability or deep configuration issues) have historically resulted in 500 errors; these are actively being tracked and refactored for better clarity [2][10][8]. For the most accurate debugging, ensure your request includes the correct cal-api-version header (e.g., 2024-08-13) and check the "details" or "errors" array in the JSON response body for the specific field-level constraints [1][2][9][3].

Citations:


Avoid logging raw Cal.com response bodies.

createBooking sends attendee name, email, and optional phoneNumber. Error logging uses (await res.text()), which can contain fields such as attendee data returned in Cal.com error messages. Log only the HTTP status and a truncated non-PII field such as error.code or error.message instead of the full raw body. The success log also outputs the entire /v2/slots payload; reduce it to a short summary such as the slot count.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/integrations/calcom.ts` around lines 91 - 96, Update the Cal.com response
logging in the slots request flow to avoid raw response bodies: replace the
!res.ok logging with HTTP status plus a truncated non-PII error field such as
error.code or error.message, and change the success log to report only a concise
slot-count summary instead of JSON.stringify(body.data). Preserve the existing
return behavior and locate these changes around the slots response handling and
createBooking-related error paths.

Comment thread src/system-prompt.ts
tone: overrides?.tone,
extraEscalationKeywords: overrides?.extraEscalationKeywords,
lessons: overrides?.lessons,
today: currentDateLine((env.CALCOM_TIMEZONE || "").trim() || "America/Mexico_City"),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Verify DEFAULT_TZ value and current imports in system-prompt.ts
set -euo pipefail

fd -e ts . src/integrations --exec cat -n {}
echo "---"
fd system-prompt.ts src --exec cat -n {}

Repository: santmun/forja

Length of output: 15480


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Top-level files:"
fd -e ts -e toml -e json -e yaml -e yml . src . --max-depth 3
echo "--- imports containing calcom:"
rg -n 'from ["'\''](\./|../)\.?integrations/.*calcom|calcomTimeZone|DEFAULT_TZ|systemPromptFromEnv' src --glob '*.ts'

Repository: santmun/forja

Length of output: 6750


Reuse calcomTimeZone(env) in the system prompt timezone fallback.

src/system-prompt.ts:191 duplicates calcomTimeZone(env) fallback logic, while scheduleAppointmentTool also uses calcomTimeZone(env). Import calcomTimeZone from ../integrations/calcom there so changes to the trim/default behavior apply in one place.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/system-prompt.ts` at line 191, Update the system prompt timezone
assignment near today to reuse calcomTimeZone(env) instead of duplicating the
CALCOM_TIMEZONE trim/default expression, and import calcomTimeZone from
../integrations/calcom. Keep the existing currentDateLine behavior unchanged by
passing the helper’s result to it.

Comment on lines +25 to +26
date: z.string().optional().describe("YYYY-MM-DD para consultar horarios libres de ese día"),
startTime: z.string().optional().describe("ISO datetime con offset para reservar, ej. 2026-08-03T15:00:00-06:00"),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Enforce YYYY-MM-DD format before the past-date comparison.

The past-date guard at Line 43 compares requestedDay < today as strings. This only works correctly if requestedDay is zero-padded (YYYY-MM-DD). today is always correctly padded because it comes from todayInTz, but date and startTime (Lines 25-26) have no format constraint in the schema. An unpadded date from the model (for example "2027-6-1") sorts lexicographically after a padded today even when it represents an earlier calendar date, silently defeating the guard this PR intentionally adds against "fecha fantasma."

Add a regex constraint to date (and validate the date portion of startTime) so malformed values are rejected before the comparison runs.

🐛 Proposed fix
     inputSchema: z.object({
-      date: z.string().optional().describe("YYYY-MM-DD para consultar horarios libres de ese día"),
-      startTime: z.string().optional().describe("ISO datetime con offset para reservar, ej. 2026-08-03T15:00:00-06:00"),
+      date: z
+        .string()
+        .regex(/^\d{4}-\d{2}-\d{2}$/)
+        .optional()
+        .describe("YYYY-MM-DD para consultar horarios libres de ese día"),
+      startTime: z
+        .string()
+        .regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)
+        .optional()
+        .describe("ISO datetime con offset para reservar, ej. 2026-08-03T15:00:00-06:00"),

Also applies to: 41-49

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/tools/scheduleAppointment.ts` around lines 25 - 26, Enforce strict
zero-padded YYYY-MM-DD validation in the scheduleAppointment schema’s date
field, and validate the date portion extracted from startTime before the
past-date guard compares it with today. Reject malformed or unpadded values
before reaching the requestedDay < today comparison, while preserving valid date
and ISO datetime handling.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant