Cómo crear una aplicación de calendario - Una guía completa
Publicado el
Este artículo es para ti si eres un desarrollador que necesita integrar un calendario en una aplicación, o un fundador que está pensando en crear una app de calendario o un producto con integraciones de calendario.
Vamos a ver cómo crear una app de calendario, tomar cada decisión paso a paso, elegir las tecnologías adecuadas y repasar los principales retos y mejores prácticas.
¿Cuál es el alcance de este artículo?
El objetivo de este artículo es ayudarte a crear una aplicación de calendario completa, o a integrar proveedores de calendario en una aplicación existente, para que tus usuarios puedan conectar sus calendarios y gestionarlos desde tu producto.
El artículo también es útil si quieres conectarte con proveedores de calendario sin mostrar ninguna interfaz de calendario. Algunos ejemplos son una app de tareas, una app de citas o una función que simplemente inserta un evento en el calendario de un usuario.
Siéntete libre de tomar partes concretas de este artículo y reutilizarlas en tu propio código, incluidas las elecciones tecnológicas, el enfoque de API unificada o fragmentos del código.
Visión general de la arquitectura y el stack tecnológico
Elegimos la web como plataforma para este ejemplo, porque es la más rápida de poner en marcha y existe mucho soporte de la comunidad para librerías de calendario.
El lenguaje de programación es TypeScript, y el framework web es Next.js.
Aquí tienes una visión general del stack que utilizaremos:
- Frontend : Next.js (app router, TypeScript)
- Backend : Rutas API de Next.js + tRPC.
- Base de datos : PostgreSQL (usando Prisma como ORM)
- APIs de calendario : Apiroc Unified Calendar API
- Hosting : Agnóstico del proveedor; puedes alojarlo en Vercel o donde te sientas cómodo.
- Autenticación : OAuth2 (Google, Microsoft) a través de better-auth
A continuación, el diagrama de arquitectura que muestra cómo encajan las piezas del stack:
Definiciones:
- Cliente (1) : El dispositivo del usuario final. La ilustración muestra un dispositivo móvil, pero también podría ser un escritorio o un portátil (cualquier dispositivo con un navegador web como Google Chrome). El cliente renderiza el frontend de Next.js, gestiona las interacciones del usuario y se comunica con el backend mediante solicitudes HTTPS seguras.
- Servidor web (2) : El servidor web aloja y sirve la interfaz de usuario, una aplicación web de Next.js (App Router). Entrega HTML, CSS y JavaScript optimizados al cliente y proporciona renderizado del lado del servidor (SSR) y regeneración estática incremental (ISR) para un rendimiento rápido y beneficios de SEO.
- API de Next.js (3) : La API de Next.js es la capa backend, implementada con rutas API y tRPC dentro de la misma aplicación de Next.js. Es el centro que conecta el frontend, la base de datos y las integraciones externas como la Apiroc Unified Calendar API. A diferencia de los endpoints REST tradicionales, tRPC ofrece comunicación tipada de extremo a extremo entre frontend y backend sin un esquema de API separado. El cliente puede llamar directamente a procedimientos del backend, con inferencia completa de tipos de TypeScript. Esto acelera el desarrollo y reduce los errores en tiempo de ejecución.
- PostgreSQL (4) : La base de datos PostgreSQL almacena todos los datos persistentes de la aplicación, incluidos usuarios, sesiones, cuentas de calendario conectadas, etc. Es el sistema de registro para todos los datos relacionados con el usuario y los estados de sincronización. Con Prisma como capa ORM, el esquema se mapea limpiamente a la base de datos, lo que facilita migraciones y consultas.
- Apiroc Unified Calendar API (5) : Apiroc es la API que usamos para integrar todos los proveedores de calendario mediante una interfaz estandarizada. Así no tenemos que escribir una implementación separada por proveedor, tratar con distintos formatos de datos, mantener varias integraciones ni estar al día de los cambios en las APIs de cada proveedor. Nuestra API llama a Apiroc con la clave de API y los calendarios sobre los que queremos operar (CRUD de eventos, calendarios y más), independientemente del proveedor. Apiroc se comunica entonces con los proveedores de calendario y devuelve la respuesta en el mismo formato estandarizado para todos ellos.

Ten en cuenta que el Servidor web (2) y la API de Next.js (3) pueden ejecutarse en el mismo servidor (usando Vercel, Docker, etc.). En el diagrama aparecen separados solo para dejar claro que hay un servidor de UI y un servidor de API, aunque ambos viven en la misma base de código de Next.js y normalmente se alojan juntos.
Diseñando el modelo de datos
Ahora que la arquitectura y el stack están claros, es momento de definir el modelo de datos. Usamos Prisma como ORM.
Este esquema de Prisma define la estructura de datos para una aplicación de calendario básica que soporta autenticación de usuarios, conexiones de cuentas de calendario (Google, Microsoft) y sincronización de eventos a través de una API unificada.
Los modelos más importantes son:
1. User
Representa a un usuario final de la app. Cada usuario puede tener múltiples sesiones, cuentas conectadas (OAuth) y cuentas de calendario. Campos como email, name y onboardingCompletedAt ayudan a seguir el perfil y el estado de onboarding.
2. CalendarAccount
Representa una cuenta de calendario externa vinculada (por ejemplo, una cuenta de Google o Microsoft). Almacena el proveedor, el correo electrónico y el estado (activo o caducado). Cada CalendarAccount pertenece a un User y puede contener múltiples entradas de Calendar.
3. Calendar
Representa un calendario individual (como "Trabajo", "Personal" o "Familia") dentro de una cuenta vinculada. Incluye campos de visualización como name, color, timezone y banderas como isPrimary o isReadOnly. Cada calendario está vinculado tanto a un User como al CalendarAccount del que procede.
4. Account
Gestiona los datos del proveedor OAuth (Google o Microsoft). Almacena tokens de acceso y actualización, tiempos de expiración de tokens e información de scope, usados para autenticación y sincronización de calendarios.
5. Session
Rastrea las sesiones de inicio de sesión activas de los usuarios. Contiene campos como token, expiresAt, ipAddress y userAgent para gestionar y asegurar las sesiones activas.
6. Verification
Se usa para verificaciones de un solo uso, como enlaces mágicos de inicio de sesión por correo o códigos de autenticación sin contraseña. Almacena identificadores temporales y tiempos de expiración.
7. Enums
CalendarAccountProvider: Define proveedores soportados (GOOGLE,MICROSOFT).CalendarAccountStatus: Indica si una cuenta conectada estáACTIVEoEXPIRED.
El diagrama ER de la base de datos:
Abre el archivo schema.prisma en el repositorio de ejemplo para ver el esquema completo de la base de datos, incluidos tipos y relaciones.
Construyendo el Backend
Como mencionamos, usamos rutas API de Next.js para construir la API. Es muy conveniente tener la API y la UI en la misma base de código y en el mismo servidor, porque puedes ejecutar ambas a la vez.
Construyendo la autenticación
Usamos better-auth como framework de autenticación. Better Auth hace que la autenticación sea fácil y sin dolor. Sigue la guía de Better Auth sobre cómo integrar Better Auth con Next.js, ya que los pasos son casi idénticos. También puedes explorar el archivo de auth en el repositorio de ejemplo para saber más.
Configurando la Apiroc Unified Calendar API para comunicarte con todos los proveedores de calendario
El mayor punto de dolor al crear una app de calendario, o al añadir calendarios a un producto existente, es tratar con las APIs específicas de cada proveedor. Esto cuesta mucho tiempo: tienes que aprender cada API por separado y manejar distintas estructuras de datos, peticiones y respuestas. Además, necesitas crear una integración separada para cada proveedor y mantenerlas todas una vez terminado el desarrollo.
Una buena solución a este problema es una API de calendario unificada que nos permita integrarnos con todos los proveedores a través de una sola API estandarizada. En este ejemplo, usamos la Apiroc Unified Calendar API.
Para empezar con Apiroc, sigue estos pasos:
- Primero, regístrate en Apiroc y crea una cuenta gratuita.
- Después de registrarte, habilita los proveedores de calendario que quieras integrar. Recomendamos habilitar Google Calendar y Outlook, para que veas la ventaja de un producto de API de calendario unificada. No necesitas crear tu propio cliente de Google o Microsoft para sandbox y desarrollo. Puedes usar los clientes de Google y Microsoft de Apiroc para conectar cuentas de Google Calendar u Outlook a tu aplicación.
- Crea una clave de API y guárdala en la variable de entorno
APIROC_API_KEY
Construyendo el cliente de la API de Apiroc
Después de configurar Apiroc y obtener la clave de API, es hora de construir el cliente que se comunica con la Apiroc Unified Calendar API:
import { env } from "@/env";
import type {
EndUserAccount,
PaginatedResponse,
UnifiedCalendar,
UnifiedEvent as UniversalEvent,
} from "@/server/lib/apiroc/types";
import ky from "ky";
export const apirocApi = ky.create({
prefixUrl: env.NEXT_PUBLIC_APIROC_URL,
headers: {
"x-api-key": env.APIROC_API_KEY,
},
});
export async function getEndUserAccountById(id: string) {
const response = await apirocApi.get<EndUserAccount>(
`endUserAccounts/${id}`,
);
return response.json();
}
export async function getCalendarsForEndUserAccount(endUserAccountId: string) {
const response = await apirocApi.get<
PaginatedResponse<UnifiedCalendar>
>(`calendars/${endUserAccountId}`);
return response.json();
}
interface GetCalendarEventsParams {
pageToken?: string;
pageSize?: number;
syncToken?: string;
startDateTime?: string;
endDateTime?: string;
timeZone?: string;
expandRecurrences?: boolean;
}
export async function getCalendarEvents(
endUserAccountId: string,
calendarId: string,
params: GetCalendarEventsParams = {},
) {
const queryParams = new URLSearchParams(params as Record<string, string>);
const response = await apirocApi.get<
PaginatedResponse<UniversalEvent>
>(`events/${endUserAccountId}/${calendarId}?${queryParams}`);
return response.json();
}
export async function getCalendarEvent(
endUserAccountId: string,
calendarId: string,
eventId: string,
) {
const response = await apirocApi.get<UniversalEvent>(
`events/${endUserAccountId}/${calendarId}/${eventId}`,
);
return response.json();
}
export async function createCalendarEvent(
endUserAccountId: string,
calendarId: string,
event: Partial<UniversalEvent>,
) {
const response = await apirocApi.post<UniversalEvent>(
`events/${endUserAccountId}/${calendarId}`,
{json: event},
);
return response.json();
}
export async function editCalendarEvent(
endUserAccountId: string,
calendarId: string,
eventId: string,
event: Partial<UniversalEvent>,
) {
const response = await apirocApi.put<UniversalEvent>(
`events/${endUserAccountId}/${calendarId}/${eventId}`,
{json: event},
);
return response.json();
}
export async function deleteCalendarEvent(
endUserAccountId: string,
calendarId: string,
eventId: string,
) {
await apirocApi.delete(
`events/${endUserAccountId}/${calendarId}/${eventId}`,
);
}
No necesitas definir los tipos del cliente por tu cuenta. Instala el SDK oficial de Node.js (@apiroc/unified-calendar-api-node-sdk) e importa directamente desde él los tipos de cuentas de usuario final, calendarios y eventos. El SDK también incluye un cliente listo para usar, así que las funciones anteriores pueden ser aún más cortas.
El siguiente diagrama de secuencia muestra cómo la app de calendario de ejemplo interactúa con la Apiroc Unified Calendar API para integrarse con todos los proveedores de calendario.
Creando las rutas de la API
Con el cliente de Apiroc listo, podemos crear las rutas de API para gestionar cuentas de calendario y eventos de calendario. No necesitamos crear APIs de sesión por nuestra cuenta, porque Better Auth se encarga de ello.
La API contiene definiciones de rutas para:
- Cuentas de calendario : Ruta que expone métodos HTTP para listar todas las cuentas de calendario y eliminar una cuenta por ID.
- Eventos de calendario : Ruta que expone métodos HTTP para hacer CRUD de eventos de calendario.
- Calendarios : Ruta que expone métodos HTTP para actualizar calendarios.
Una definición de ruta usando tRPC se ve así:
export const calendarEventsRouter = createTRPCRouter({
getCalendarEvent: publicProcedure
.input(
z.object({
endUserAccountId: z.string(),
calendarId: z.string(),
eventId: z.string(),
}),
)
.query(async ({ ctx, input }) => {
return await getCalendarEvent(
input.endUserAccountId,
input.calendarId,
input.eventId,
);
}),
});
El método getCalendarEvent proviene del cliente de Apiroc que construimos arriba.
Abre la carpeta de rutas de la API en el repositorio de ejemplo para ver el contenido de cada ruta. Pegarlas todas aquí sería bastante repetitivo.
Construyendo el Frontend
El frontend se construye con Next.js + TypeScript. Al crear una app de calendario, el componente más importante es, lo adivinaste, el calendario.
Según nuestra experiencia, las mejores librerías de UI de calendario para Next.js y React son:
Para este ejemplo, elegimos react-big-calendar porque es fácil de usar con Next.js. Ten en cuenta que recomendaríamos fullcalendar para aplicaciones de producción, ya que es más personalizable y tiene un soporte comunitario más amplio.
Fullcalendar también está disponible para otros frameworks como Svelte, Vue.js, etc.
Uso de react-big-calendar:
<Calendar
culture="en-US"
localizer={localizer}
events={events}
defaultView="week"
eventPropGetter={eventPropGetter}
components={components}
onSelectSlot={(slotInfo) => {
setCreateEventStart(slotInfo.start);
setCreateEventEnd(slotInfo.end);
setCreateEventOpen(true);
}}
onSelectEvent={(event) => {
setSelectedEvent(event);
}}
selectable
onRangeChange={(range) => {
// Week view: range is array of dates
if (Array.isArray(range) && range.length >= 2) {
setDateRange([range[0]!, range[range.length - 1]!]);
return;
}
// Month view: range is object with start/end
if (
range &&
typeof range === "object" &&
"start" in range &&
"end" in range
) {
setDateRange([range.start, range.end]);
return;
}
// Day view: range is a single Date
if (range instanceof Date) {
setDateRange([range, range]);
return;
}
}}
/>
Para ver la implementación completa, abre la ruta src/app/(protected)/(calendar) en el repositorio de GitHub. El componente principal es la página events-calendar.tsx. También encontrarás componentes para editar eventos (incluidos los recurrentes), eliminarlos y crearlos.
Así se ve el calendario:
El usuario puede hacer clic en una celda y crear un evento:
Cuando el usuario hace clic en un evento existente, puede eliminarlo o editarlo.
Cuando el evento es recurrente, el usuario puede elegir entre editar solo la instancia seleccionada o toda la serie.
Así se ve la UI de edición de eventos:
La UI necesita algo de pulido, pero el objetivo no era una app de calendario perfecta. El objetivo era una app de calendario funcional con una integración de calendario que funcione. Tú puedes encargarte de los estilos y adaptarlos a tu marca.
Retos comunes y mejores prácticas
Crear una app de calendario o añadir funciones de calendario no siempre es fácil. Aunque la funcionalidad principal parezca simple, hay muchos pequeños detalles que pueden causar problemas más adelante. A continuación, algunos retos comunes con los que te puedes encontrar y algunas mejores prácticas para manejarlos.
1. Zonas horarias
Reto:
Los eventos pueden mostrarse a la hora incorrecta cuando los usuarios están en diferentes zonas horarias.
Mejor práctica:
- Guarda siempre las horas en UTC en tu base de datos. La excepción son los eventos de calendario, porque no recomendamos almacenarlos en tu base de datos. Cuando obtienes eventos a través de la API del proveedor, también recibes la zona horaria del evento.
- Convierte a la hora local del usuario solo al mostrar los datos en el frontend.
- Usa una librería como date-fns-tz o luxon para manejar conversiones. En este ejemplo, usamos
date-fnsydate-fns-tz.
2. Eventos recurrentes
Reto:
Gestionar eventos que se repiten (diarios, semanales, mensuales) puede ser complejo, especialmente cuando los usuarios quieren editar o eliminar una sola instancia.
Mejor práctica:
- Deja que el usuario elija si actualizar un evento o toda la serie. Google Calendar, Outlook y muchos otros clientes siguen esta práctica. Nosotros también la seguimos en nuestra app de ejemplo.
3. Expiración de tokens OAuth
Reto:
Los usuarios pueden perder la conexión con sus calendarios si los tokens expiran o se revocan.
Mejor práctica:
- Almacena los refresh tokens de forma segura para obtener nuevos access tokens automáticamente.
- Maneja los errores de token con cuidado y pide a los usuarios que reconecten sus cuentas cuando sea necesario.
4. Mantener los datos sincronizados
Reto:
Los datos del calendario quedan desactualizados si solo los obtienes una vez.
Mejor práctica:
- Usa los webhooks de la Apiroc Unified Calendar API para recibir notificaciones cuando cambian los eventos en calendarios de Google y Microsoft.

- Recomendamos obtener los eventos de los proveedores cuando el usuario interactúa con la app de calendario. No almacenes eventos en tu base de datos, porque mantenerlos sincronizados con todos los proveedores es un problema difícil. Además, guardarlos localmente aporta poco beneficio, ya que puedes obtener los eventos de todos los proveedores a través de Apiroc cuando los necesites.
5. Manejo de errores de API
Reto:
Las APIs externas (Google, Outlook, iCloud) pueden devolver errores, límites de tasa o fallos temporales.
Mejor práctica:
- Añade lógica de reintentos para errores temporales (usar timeouts es una opción viable).
- Respeta los límites de tasa y haz backoff cuando sea necesario. Ten en cuenta que Apiroc tiene límites de tasa, igual que los proveedores como Google Calendar y Outlook Calendar.
- Registra todas las solicitudes fallidas para facilitar la depuración.
6. Calendarios grandes
Reto:
Algunos usuarios tienen cientos o miles de eventos, lo que puede ralentizar la app.
Mejor práctica:
- Carga eventos por páginas (usa paginación). Todos los principales proveedores soportan paginación. Si usas Apiroc, todos los resultados vienen paginados, así que no tendrás este problema.
- Obtén solo los eventos del rango visible (por ejemplo, esta semana o este mes). Como regla general, obtén solo los datos que necesitas. Una app de calendario tiene vista de día, semana, mes y año, así que obtén los eventos según el marco temporal visible.
7. Privacidad y seguridad del usuario
Reto:
Los datos de calendario suelen incluir información privada.
Mejor práctica:
- No almacenes eventos de calendario en tu base de datos. Guardar el token de acceso y el token de actualización debería ser suficiente.
- Cifra tokens y campos sensibles en tu base de datos. También recomendamos cifrar tu base de datos en reposo. Servicios como AWS RDS ofrecen cifrado de datos de forma nativa.
- Permite a los usuarios desconectar sus cuentas de calendario en cualquier momento. Esto es muy importante. Si los usuarios no pueden desconectar o eliminar sus calendarios en tu app, revocarán el acceso directamente desde la configuración de su cuenta de Google.
Preguntas frecuentes (FAQ)
1. ¿Puedo usar otro backend en lugar de rutas API de Next.js?
Sí. Este ejemplo usa rutas API de Next.js con tRPC, pero puedes usar cualquier framework backend como Nest.js, Express o Django. La clave es que tu backend debe comunicarse con la Apiroc Unified Calendar API mediante HTTPS. La estructura de base de datos y la lógica de API se mantienen casi iguales.
2. ¿Necesito crear mis propias apps de desarrollador de Google o Microsoft?
No, puedes usar los clientes de Google y Microsoft de Apiroc durante el desarrollo. Cuando tu app pase a producción, usas tus propias credenciales OAuth2, lo que te da control y branding completos de cara a tus usuarios.
3. ¿Puedo usar una base de datos diferente a PostgreSQL?
Sí. Prisma soporta muchas bases de datos como MySQL, SQLite y MongoDB. Elegimos PostgreSQL porque es confiable, escalable y fácil de configurar para producción. Puedes elegir cualquier otra base de datos y ORM, según tu stack.
4. ¿La Apiroc Unified Calendar API es gratis?
Puedes empezar gratis creando una cuenta de Apiroc. No se necesita tarjeta de crédito. El plan gratuito incluye hasta 10 End User Accounts y es ideal para pruebas y proyectos pequeños. Para uso en producción, puedes pasar al plan Pro (25 $/mes, 50 End User Accounts incluidas, y después 0,50 $ por cada End User Account adicional al mes).
5. ¿Qué pasa si un usuario desconecta su calendario?
Cuando un usuario desconecta, la app debe eliminar el CalendarAccount y los Calendars correspondientes de tu base de datos. Puedes conservar datos locales para analítica si lo necesitas, pero asegúrate de no volver a sincronizar ni acceder a los calendarios desconectados.
7. ¿Puedo añadir notificaciones o recordatorios?
Sí. Puedes construir recordatorios en tu app o usar el sistema de notificaciones nativo del calendario conectado (Google, Outlook, etc.).
8. ¿Qué pasa si la API limita mis solicitudes?
Apiroc incluye límites de tasa incorporados para garantizar la estabilidad (20 solicitudes por segundo para apps en sandbox y 300 solicitudes por segundo para apps en producción). Si alcanzas el límite, haz backoff y reintenta tras una breve espera. Los proveedores de calendario también tienen sus propios límites a nivel de aplicación. Estos límites varían, y tanto Google como Microsoft permiten solicitar un límite más alto si es necesario.
9. ¿Es posible sincronizar eventos en ambas direcciones?
Sí. Apiroc permite leer y escribir eventos, así que puedes leer y escribir en los calendarios conectados. Recibirás notificaciones de webhook cuando haya cambios del lado del proveedor en calendarios de Google y Microsoft. Los webhooks para calendarios de Apple iCloud están planificados, pero todavía no están disponibles.
10. ¿Cómo puedo desplegar este proyecto?
Puedes desplegar la app fácilmente en Vercel. Asegúrate de configurar tus variables de entorno (DATABASE_URL, APIROC_API_KEY, credenciales OAuth, etc.) en la configuración de tu proyecto. También puedes contenerizarla con Docker si prefieres más control.
