Cómo crear una aplicación de calendario - Una guía completa
Tabla de Contenidos
Integra varios calendarios utilizando una única API
Utiliza la OneCal Unified Calendar API para integrar rápidamente varios proveedores de calendario en tu aplicación y no tener que preocuparte por el mantenimiento ni por las pruebas.
Si eres un desarrollador encargado de integrar un calendario en tu aplicación, o un emprendedor que está pensando en crear una app de calendario, o una app que tenga integraciones de calendario, este artículo es para ti.
Aprendamos a crear una app de calendario, tomar cada decisión paso a paso, elegir las mejores tecnologías y entender los retos y las mejores prácticas.
Si deseas ver la implementación completa y el código, dirígete a nuestra aplicación de ejemplo Unified Calendar View en GitHub. Todas las instrucciones sobre cómo ejecutar la aplicación localmente se encuentran en el archivo README.
¿Cuál es el alcance de este artículo?
Como se mencionó en la introducción, el objetivo de este artículo es ayudarte a crear una aplicación de calendario completa o simplemente integrar proveedores de calendario en tu aplicación existente, donde los usuarios puedan conectar sus calendarios y gestionarlos a través de tu app.
Este artículo también es útil incluso si quieres integrarte con proveedores de calendario sin proporcionar una interfaz de calendario. Los ejemplos serían una aplicación de tareas, una app de citas o incluso una función que inserte un evento en los calendarios de un usuario.
Siéntete libre de tomar ciertas partes del artículo y usarlas en tu base de código, incluidas las elecciones tecnológicas, las API unificadas o partes del código.
Visita la OneCal Unified Calendar API si buscas integrar todos los proveedores de calendario usando una sola API.
Visión general de la arquitectura y el stack tecnológico
La plataforma que hemos elegido para ilustrar este ejemplo es la web, ya que es más fácil ponerla en marcha y existe mucho soporte de la comunidad para librerías de calendario.
El lenguaje de programación que usaremos 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: OneCal 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 y cómo se integra el stack tecnológico:
Definiciones:
Cliente (1): El dispositivo del usuario final. Esta ilustración usa un dispositivo móvil, pero podría ser un escritorio, portátil, etc. (un dispositivo que soporte navegadores web como Google Chrome). El cliente es responsable de renderizar el frontend de Next.js, manejar las interacciones del usuario y comunicarse 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 sirve como la capa backend, implementada usando rutas API y tRPC dentro de la misma aplicación de Next.js. Funciona como el centro que conecta el frontend, la base de datos y las integraciones externas como la OneCal Unified Calendar API. A diferencia de los endpoints REST tradicionales, tRPC permite comunicación de extremo a extremo con tipado entre frontend y backend sin necesidad de definir un esquema de API separado. Esto significa que el cliente puede llamar directamente a procedimientos del backend, con inferencia completa de tipos de TypeScript. Ayuda a mejorar la velocidad de desarrollo y a reducir 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.
OneCal Unified Calendar API (5): OneCal Unified Calendar API es la API que usamos para integrar todos los proveedores de calendario usando una API estandarizada. OneCal Unified nos permite integrar con todos los proveedores sin preocuparnos por codificar implementaciones separadas, tratar con diferentes interfaces de datos, mantener múltiples integraciones ni gestionar cambios en las APIs. Nuestra API se comunica con OneCal Unified Calendar API enviando la clave de API y qué calendarios queremos operar (CRUD de eventos, calendarios y más), independientemente del proveedor. En respuesta, OneCal se comunica con los proveedores de calendario y luego envía la respuesta en un formato estandarizado para todos los proveedores.
Ten en cuenta que el Servidor web (2) y la API de Next.js (3) pueden alojarse en el mismo servidor (usando Vercel, Docker, etc.), pero se muestran separados solo para entender visualmente que hay un servidor de UI y un servidor de API, aunque ambos están dentro de la misma base de código de Next.js y, por lo general, se alojan en el mismo servidor.
Diseñando el modelo de datos
Después de explicar la arquitectura y el stack, es momento de definir nuestro modelo de datos. Usaremos Prisma como nuestro ORM preferido.
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 se origina.
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 nuestro repositorio de GitHub para ver el esquema completo de la base de datos, incluidos tipos y relaciones.
Construyendo el Backend
Como se mencionó, usaremos rutas API de Next.js para construir la API, ya que es muy conveniente tener la API y la UI en la misma base de código y alojadas en el mismo servidor. Esto significa que puedes ejecutar la UI y la API simultáneamente.
Elegimos usar las rutas API de Next.js porque tiene sentido en términos de complejidad, ya que estamos construyendo una app de calendario simple, donde los usuarios inician sesión, conectan sus calendarios y realizan operaciones como crear eventos, actualizarlos, eliminarlos, etc. Si estás construyendo algo más complejo, eres libre de usar Node.js, Nest.js u otro framework. Las entidades de BD deberían ser las mismas y la OneCal Unified Calendar API que usamos para interactuar con todos los proveedores de calendario es basada en HTTP, por lo que puedes llamarla desde cualquier lenguaje o framework que uses.
Ten en cuenta que no estamos usando las rutas API de Next.js tal cual, estamos usando tRPC para que nuestras APIs sean seguras en tipos de extremo a extremo.
Construyendo la autenticación
Usaremos better-auth como nuestro framework de autenticación. Better-auth hace que la autenticación sea muy 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 a esa guía. Si quieres, explora el archivo de auth en nuestro repositorio para saber más.
Configurando la OneCal Unified Calendar API para comunicarte con todos los proveedores de calendario
El mayor punto de dolor al crear una app de calendario o integrar calendarios en una aplicación o funcionalidad existente es tratar con las APIs específicas de cada proveedor. Esto tiene un coste de tiempo, ya que tendríamos que aprender cada API por separado, tratar con diferentes estructuras de datos, peticiones, respuestas y más. Además, necesitaríamos crear una integración separada para cada proveedor y mantener dichas integraciones después de finalizar el desarrollo.
Una gran solución a este problema es usar una Calendar API unificada que nos ayude a integrarnos con todos los proveedores de calendario usando una API estandarizada. En este ejemplo, usaremos la OneCal Unified Calendar API.
Para empezar con OneCal Unified, sigue estos pasos:
El primer paso es registrarte en OneCal Unified y crear una cuenta gratuita.
Después de registrarte, habilita los proveedores de calendario con los que quieras integrarte. Recomendamos habilitar Google Calendar y Outlook, para entender el poder de usar un producto de API de calendario unificada. Ten en cuenta que no necesitas crear un cliente de Google o Microsoft, ya que para sandbox y desarrollo puedes usar el cliente de Google de OneCal Unified, lo que te permite conectar cuentas de Outlook o Google Calendar a tu aplicación.
Crea una clave de API y guárdala en la variable de entorno ONECAL_UNIFIED_API_KEY.
Construyendo el cliente de la OneCal Unified API
Después de configurar la OneCal Unified Calendar API y obtener la clave de API, es hora de construir el cliente que interactúa con la OneCal Unified Calendar API:
import { env } from "@/env";
import type {
EndUserAccount,
PaginatedResponse,
UnifiedCalendar,
UnifiedEvent as UniversalEvent,
} from "@/server/lib/onecal-unified/types";
import ky from "ky";
export const onecalUnifiedApi = ky.create({
prefixUrl: env.NEXT_PUBLIC_ONECAL_UNIFIED_URL,
headers: {
"x-api-key": env.ONECAL_UNIFIED_API_KEY,
},
});
export async function getEndUserAccountById(id: string) {
const response = await onecalUnifiedApi.get<EndUserAccount>(
`endUserAccounts/${id}`,
);
return response.json();
}
export async function getCalendarsForEndUserAccount(endUserAccountId: string) {
const response = await onecalUnifiedApi.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 onecalUnifiedApi.get<
PaginatedResponse<UniversalEvent>
>(`events/${endUserAccountId}/${calendarId}?${queryParams}`);
return response.json();
}
export async function getCalendarEvent(
endUserAccountId: string,
calendarId: string,
eventId: string,
) {
const response = await onecalUnifiedApi.get<UniversalEvent>(
`events/${endUserAccountId}/${calendarId}/${eventId}`,
);
return response.json();
}
export async function createCalendarEvent(
endUserAccountId: string,
calendarId: string,
event: Partial<UniversalEvent>,
) {
const response = await onecalUnifiedApi.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 onecalUnifiedApi.put<UniversalEvent>(
`events/${endUserAccountId}/${calendarId}/${eventId}`,
{json: event},
);
return response.json();
}
export async function deleteCalendarEvent(
endUserAccountId: string,
calendarId: string,
eventId: string,
) {
await onecalUnifiedApi.delete(
`events/${endUserAccountId}/${calendarId}/${eventId}`,
);
}
Puedes encontrar los tipos del cliente y otras clases relevantes en esta ubicación de GitHub.
El diagrama de secuencia explica cómo la app de calendario de ejemplo interactúa con la OneCal Unified Calendar API para ofrecer una integración fluida con todos los proveedores de calendario.
Creando las rutas de la API
Después de configurar el cliente de OneCal Unified Calendar API, ya estamos listos para crear las rutas de API para gestionar cuentas de calendario y eventos de calendario. Ten en cuenta que no necesitamos crear explícitamente APIs de sesión ya que aprovechamos BetterAuth para este propósito.
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 vería 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 OneCal Unified Calendar API que construimos arriba.
Abre la ruta de las APIs en nuestro repositorio de GitHub para obtener el contenido de cada ruta de API, ya que pegarlo en este artículo sería bastante repetitivo.
Construyendo el Frontend
El frontend se construirá usando Next.js + TypeScript. Al construir 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 en Next.js y React son:
Para este ejemplo, elegimos usar react-big-calendar por su facilidad de uso con Next.js, pero ten en cuenta que recomendaríamos usar fullcalendar en aplicaciones de producción, ya que es más personalizable y tiene un soporte comunitario más amplio.
Además, fullcalendar está disponible en otras librerías 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 puedes encontrar componentes para editar eventos (incluidos los recurrentes), eliminarlos y crearlos.
Así es como 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, obtiene la opción de eliminarlo o editarlo.
Cuando el evento es recurrente, el usuario obtiene la opción de editar la instancia seleccionada o toda la serie.
Así es como luce la UI de editar evento:
La UI necesita pulido, pero no queríamos construir una app de calendario perfecta, ya que el propósito es crear una app funcional e integración de calendario; tú puedes encargarte de los estilos y asegurarte de que coincidan con 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 que puedes enfrentar 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. Esto excluye los eventos de calendario, ya que no recomendamos almacenar eventos de calendario en tu base de datos. Cuando obtengas eventos a través de la API del proveedor, también deberías poder recibir la zona horaria del evento desde la API.
Convierte a la hora local del usuario solo cuando muestres 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. Esta práctica la siguen Google Calendar, Outlook y muchos otros clientes. Nosotros también la seguimos en nuestra app de calendario.
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 gracia y solicita a los usuarios reconectar sus cuentas cuando sea necesario.
4. Mantener los datos sincronizados
Reto:
Los datos del calendario pueden quedar desactualizados si solo los obtienes una vez.
Mejor práctica:
Usa webhooks de OneCal Unified Calendar API para mantenerte actualizado cuando los eventos cambian.
Recomendamos obtener eventos de los proveedores cuando el usuario interactúa con la app de calendario. Por favor, no almacenes eventos en tu base de datos, ya que mantenerlos sincronizados con todos los proveedores puede ser un desafío difícil. Además, almacenar eventos de calendario en la base de datos no es tan beneficioso, considerando que puedes obtener eventos de todos los proveedores usando OneCal Unified Calendar API.
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 OneCal Unified Calendar API tiene límites de tasa, así como los proveedores como Google Calendar / 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). La paginación es compatible con los principales proveedores. Si usas OneCal Unified Calendar API, notarás que todos los resultados están paginados, por lo que no tendrás este problema.
Obtén solo los eventos del rango visible (por ejemplo, esta semana o mes). En general, es una buena práctica obtener solo la cantidad de datos que necesitas. Una app de calendario tiene vista de día, semana, mes y año. Obtén eventos según el marco temporal.
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. Almacenar la clave de acceso y la clave de actualización debería ser suficiente.
Cifra tokens y campos sensibles en tu base de datos. 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, ya que si no permites desconectar o eliminar sus calendarios, revocarán el acceso directamente desde la gestión de su cuenta de Google.
Preguntas frecuentes (FAQ)
1. ¿Puedo usar otro backend en lugar de rutas API de Next.js?
Sí. Aunque este ejemplo usa rutas API de Next.js con tRPC, puedes usar cualquier framework backend como Nest.js, Express o Django. La clave es que tu backend debe comunicarse con OneCal Unified Calendar API usando solicitudes HTTPS. La estructura de base de datos y la lógica de API permanecerán mayormente iguales.
2. ¿Necesito crear mis propias apps de desarrollador de Google o Microsoft?
No, puedes usar los clientes de Google y Microsoft de OneCal Unified durante el desarrollo. Cuando tu app esté en producción, puedes crear tus propias credenciales OAuth2 si quieres control y branding completos para 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. También puedes elegir cualquier otra base de datos y ORM, dependiendo de tu stack.
4. ¿OneCal Unified Calendar API es gratis?
Puedes empezar gratis creando una cuenta en OneCal Unified. Hay un nivel gratuito ideal para pruebas y proyectos pequeños. Para uso en producción, puedes actualizar según tu uso y el número de cuentas conectadas.
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 sincronizar ni acceder más 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?
La OneCal Unified API incluye límites de tasa incorporados para garantizar la estabilidad. Si alcanzas el límite, haz backoff y reintenta tras una breve espera. Ten en cuenta que los proveedores de calendario también tendrán sus propios límites a nivel de aplicación. Estos límites pueden variar y tanto Google como Microsoft ofrecen la opción de solicitar un límite más alto si es necesario.
9. ¿Es posible sincronizar eventos en ambas direcciones?
Sí. La OneCal Unified API admite sincronización bidireccional, lo que significa que puedes leer y escribir eventos en calendarios conectados. Recibirás notificaciones de webhook cuando haya cambios del lado del proveedor.
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, ONECAL_UNIFIED_API_KEY, credenciales OAuth, etc.) en la configuración de tu proyecto. También puedes contenerizarla usando Docker si prefieres más control.