Soluciones

Empresa

Cal.ai

Desarrollador

Recursos

Precios

Por

Keith Williams

14 oct 2025

Ingeniería en 2026 y más allá

Ingeniería en 2026 y más allá

Ingeniería en 2026 y más allá

Estamos construyendo infraestructura que casi nunca debe fallar. Para lograrlo, nos movemos rápidamente mientras lanzamos software de calidad increíble sin atajos ni compromisos. Este documento describe los estándares de ingeniería que nos guiarán hasta 2026 y más allá.

Estructura del Equipo

Nuestra organización de ingeniería consta de cinco equipos principales, cada uno con responsabilidades distintas:

  • Equipo de Fundación: Se enfoca en establecer y mantener estándares de codificación y patrones arquitectónicos. Este equipo trabaja de manera colaborativa con otros equipos para establecer las mejores prácticas a lo largo de la organización.

  • Equipos de Consumidor, Empresa y Plataforma: Equipos enfocados en el producto que lanzan características rápidamente mientras mantienen los estándares de calidad establecidos en este documento. Estos equipos demuestran que la velocidad y la calidad no son mutuamente excluyentes.

  • Equipo de Comunidad: Responsable de revisar rápidamente los PRs de la comunidad de código libre, proporcionar retroalimentación y guiar ese trabajo hasta la fusión. Este equipo se asegura de que nuestros colaboradores de código libre tengan una gran experiencia y de que sus contribuciones cumplan con nuestros estándares de calidad.

Nuestros Resultados Hasta Ahora

Los datos hablan por sí mismos. En el último año y medio, hemos transformado fundamentalmente cómo construimos software:

Screenshot 2025-10-01 at 9.40.36 PM.png

Hemos duplicado aproximadamente nuestro rendimiento de ingeniería mientras mejoramos simultáneamente la calidad. Aún más impresionante es el cambio en lo que estamos construyendo:

Screenshot 2025-10-01 at 9.41.51 PM.png

Hemos reasignado con éxito aproximadamente el 20% del esfuerzo de ingeniería de solucionarse problemas hacia características, mejoras de rendimiento, refactorizaciones y tareas. Este cambio demuestra que invertir en calidad y arquitectura no te ralentiza. Te acelera.

La Fundación de Cal.com Permite la Excelencia para coss.com

  • Cal.com es un negocio estable y rentable que continuaremos haciendo crecer.

    • Este éxito nos da una ventaja única mientras construimos coss.com. A diferencia de los primeros días de Cal.com, donde necesitábamos movernos rápidamente para establecer un ajuste producto-mercado y construir un negocio sostenible, coss.com comienza desde una posición de fuerza.

  • No necesitamos apresurar coss.com.

    • La estabilidad de Cal.com significa que podemos permitirnos construir coss.com de la manera correcta desde el primer día. Tenemos el lujo de implementar estos estándares de ingeniería sin la presión de las demandas inmediatas del mercado o restricciones de financiación. Esta es una posición de inicio fundamentalmente diferente.

  • La "lentitud" es una inversión, no un costo.

    • Sí, seguir estos estándares puede parecer más lento inicialmente e incluso puede ser frustrante para algunos ingenieros. Escribir DTOs lleva más tiempo que pasar tipos de base de datos directamente al frontend. Crear abstracciones adecuadas e inyección de dependencias requiere más diseño inicial. Mantener una cobertura de pruebas de más del 80% para el nuevo código requiere disciplina. Pero esta aparente lentitud es temporal y el beneficio es exponencial.

Considera los rendimientos compuestos...

  • El código que está arquitectado correctamente desde el principio no necesita grandes refactorizaciones más adelante

  • El alta cobertura de pruebas previene errores que de otro modo consumirían semanas de depuración y correcciones rápidas (ver 2023 a mediados de 2024)

  • Las abstracciones adecuadas hacen que agregar nuevas características sea mucho más rápido con el tiempo

  • Los límites claros y los DTOs prevenen la erosión arquitectónica que eventualmente exige reescrituras completas

La trayectoria de Cal.com muestra lo que ocurre cuando optimizas para la velocidad inmediata. Alta velocidad inicial que gradualmente se degrada a medida que la deuda técnica se acumula, los atajos arquitectónicos crean cuellos de botella y más tiempo se dedica a solucionar problemas que a construir características (ver gráfico anterior donde estábamos dedicando el 55-60% del esfuerzo de ingeniería a arreglos).

La trayectoria de coss.com abrazará el poder de construir correctamente desde el primer día. Velocidad inicial ligeramente más lenta mientras se establecen los patrones adecuados, seguida de una aceleración exponencial a medida que estos patrones dan dividendos y permiten un desarrollo más rápido con mayor confianza.

Principios Fundamentales

1. No diferir la calidad

  • Minimizaremos "Lo haré en un PR de seguimiento" para pequeñas refactorizaciones.

    • Los PRs de seguimiento para mejoras menores rara vez se materializan. En cambio, se acumulan como deuda técnica que nos agobia meses o años después. Si se puede hacer una pequeña refactorización ahora, hazlo ahora. Los seguimientos deben reservarse para cambios importantes que genuinamente justifiquen PRs separados o para casos excepcionales y urgentes.

2. Altos estándares en la revisión de código

  • No dejes pasar PRs con muchos detalles solo para evitar ser "la persona mala".

    • Esto es precisamente como se vuelven descuidados los bases de código con el tiempo. La revisión de código no se trata de ser amable. Se trata de mantener los estándares de calidad que demanda nuestra infraestructura. Cada detalle importa. Cada violación de patrón importa. Abórdalos antes de hacer la fusión, no después.

3. Empujarnos mutuamente a hacer lo correcto

  • Nos responsabilizamos mutuamente por la calidad

    • Tomar atajos podría parecer más rápido en el momento, pero crea problemas que ralentizan a todos más adelante. Cuando veas a un compañero de equipo a punto de fusionar un PR con problemas evidentes, habla. Cuando alguien sugiera un hack rápido en lugar de la solución adecuada, opónte. Cuando tengas la tentación de omitir pruebas o ignorar patrones arquitectónicos, espera que tus compañeros te desafíen.

  • Esto no se trata de ser difícil o ralentizar a las personas

    • Se trata de propiedad colectiva de nuestro base de código y nuestra reputación. Cada atajo que toma una persona se convierte en problema de todos. Cada esquina cortada hoy significa más sesiones de depuración, más correcciones rápidas y más clientes frustrados mañana.

  • Hacer que sea normal desafiar decisiones pobres, respetuosamente

    • Si alguien dice "simplemente codifiquemos esto por ahora", la respuesta esperada debe ser "¿qué se necesitaría para hacerlo bien la primera vez?". Si alguien quiere comprometer código que no ha sido probado, el equipo debe oponerse. Si alguien sugiere copiar y pegar en lugar de crear una abstracción adecuada, resáltalo respetuosamente.

  • Estamos construyendo algo que casi nunca debe fallar

    • Esa nivel de fiabilidad no ocurre por accidente. Ocurre cuando cada ingeniero se siente responsable de la calidad, no solo del propio código sino del sistema completo. Triunfamos como equipo o fallamos como equipo.

4. Apuntar a la simplicidad

  • Priorizar la claridad sobre la astucia

    • El objetivo es código fácil de leer y entender rápidamente, no complejidad elegante. Los sistemas simples reducen la carga cognitiva para cada ingeniero.

  • Hacerte las preguntas correctas

    • ¿Estoy realmente resolviendo el problema en cuestión?

    • ¿Estoy pensando demasiado en posibles casos de uso futuros?

    • ¿He considerado al menos 1 otra alternativa para resolver esto? ¿Cómo se compara?

  • Simplicidad no significa carencia de características

    • Solo porque nuestro objetivo es crear sistemas simples, esto no significa que deban sentirse anémicos y carentes de funcionalidades obvias.

5. Automatizar todo

  • Aprovechar la IA

    • Generar el 80% de código de plantilla y no crítico utilizando IA, permitiéndonos centrarnos únicamente en lógica empresarial compleja y arquitecturas críticas.

    • Construir manejo de errores inteligentes y alertas sin ruido.

    • Las pruebas manuales son cada vez más obsoletas. La IA puede construir rápidamente y de manera inteligente mega suites de pruebas para nosotros.

  • Nuestro CI es el jefe final

    • Todo en este documento de estándares se verifica antes de que el código sea fusionado en PRs

    • No hay sorpresas que lleguen a la rama principal

    • Las comprobaciones son rápidas y útiles

Estándares Arquitectónicos

Estamos en transición hacia un modelo arquitectónico estricto basado en Arquitectura de Corte Vertical y Diseño Orientado al Dominio (DDD). Los siguientes patrones y principios se aplicarán rigurosamente en las revisiones de PR y mediante linters.

Arquitectura de Corte Vertical: paquetes/características

Nuestra base de código está organizada por dominio, no por capa técnica. El directorio packages/features es el corazón de este enfoque arquitectónico. Cada carpeta dentro representa un corte vertical completo de la aplicación, impulsado por el dominio que toca.

Estructura:


Cada carpeta de características es un corte vertical autónomo que incluye todo lo necesario para ese dominio:

  • Lógica de dominio: Las reglas de negocio y entidades principales específicas de esa característica

  • Servicios de aplicación: Orquestación de casos de uso para ese dominio

  • Repositorios: Acceso a datos específicos para las necesidades de esa característica

  • DTOs: Objetos de transferencia de datos para cruzar límites

  • Componentes UI: Componentes frontend relacionados con esta característica (donde sea aplicable)

  • Pruebas: Pruebas de unidad, integración y E2E para esta característica

Por Qué Importan los Cortes Verticales

La arquitectura en capas tradicional se organiza por preocupaciones técnicas:


Esto crea varios problemas:

  • Los cambios en una característica requieren tocar archivos dispersos en múltiples directorios

  • Es difícil entender lo que hace una característica porque su código está fragmentado

  • Los equipos se pisan los pies cuando trabajan en diferentes características

  • No puedes extraer o desaprobar fácilmente una característica

La arquitectura de corte vertical se organiza por dominio:


Esto soluciona estos problemas:

  • Todo lo relacionado con la disponibilidad vive en packages/features/availability

  • Puedes entender toda la característica de disponibilidad explorando un solo directorio

  • Los equipos pueden trabajar en diferentes características sin conflictos (si el equipo de ingeniería de Cal.com crece, pero ciertamente en coss.com tendremos equipos que asuman paquetes importantes)

  • Las características están poco acopladas y pueden evolucionar de forma independiente

Guías para la Organización de Características

En teoría, cada característica es independientemente desplegable. Aunque quizás no las despleguemos por separado, organizarlas de esta manera nos obliga a mantener claros los dependencias y el acoplamiento mínimo. Esta es la premisa y el éxito de los micro-servicios, aunque aún no desplegaremos micro-servicios.

Las características se comunican a través de interfaces bien definidas. Si bookings necesita datos de availability, los importa desde @calcom/features/availability a través de las interfaces exportadas, no accediendo a detalles de implementación internos.

El código compartido vive en los lugares apropiados:

  • Utilidades agnósticas de dominio e inquietudes transversales (auth, logging): packages/lib

  • Primitivos UI compartidos: packages/ui (y próximamente coss.com ui)

Los límites de dominio se aplican automáticamente. Construiremos linting que prevenga acceder a los internos de las características donde no deberías estar permitido. Si packages/features/bookings intenta importar desde packages/features/availability/services/internal, el linter lo bloqueará. Todas las dependencias inter-características deben pasar a través de la API pública de la característica.

good_architecture_diagram.pngbad_architecture_diagram.png

Las nuevas características comienzan como cortes verticales. Al construir algo nuevo, crea una nueva carpeta en packages/features con el corte vertical completo. Esto deja en claro qué estás construyendo y mantiene todo organizado desde el primer día.

Beneficios

  • Capacidad de descubrimiento

    • ¿Buscas lógica de reserva? Está todo en packages/features/bookings. No hay necesidad de buscar a través de controladores, servicios, repositorios y utilidades dispersas por toda la base de código.

  • Pruebas más fáciles

    • Pruébala característica completa como unidad. Tienes todas las piezas en un solo lugar, haciendo que las pruebas de integración sean naturales y sencillas.

  • Dependencias más claras

    • Cuando ves import { getAvailability } from '@calcom/features/availability', sabes exactamente de qué característica estás dependiendo. Cuando las dependencias se vuelven demasiado complejas, es obvio y se puede abordar.

Patrón de Repositorio y Inyección de Dependencias

Las elecciones tecnológicas no deben filtrarse a través de la aplicación. El problema de Prisma lo ilustra perfectamente. Actualmente tenemos referencias a Prisma dispersas en cientos de archivos. Esto crea un gran acoplamiento y hace que los cambios tecnológicos sean prohibitivamente caros. Estamos sintiendo el dolor de esto ahora al actualizar Prisma a v6.16. Algo que debería haber sido solo un refactorización localizada detrás de repositorios protegidos se ha convertido en una persecución intrincada y casi interminable de problemas a través de múltiples aplicaciones.

El estándar en adelante:

  • Todo acceso a la base de datos debe pasar por clases de Repositorio. Ya tenemos una buena ventaja en esto.

  • Los repositorios son el único código que sabe sobre Prisma (o cualquier otro ORM). No debería haber lógica en ellos.

  • Los repositorios son inyectados a través de contenedores de Inyección de Dependencias

  • Si alguna vez cambiáramos de Prisma a Drizzle u otro ORM, los únicos cambios requeridos serían:

    • Implementaciones de repositorios

    • Conexiones del contenedor DI para nuevos repositorios

    • Nada más en la base de código debería preocuparse o cambiar

Esto no es teórico. Así es como construimos sistemas mantenibles.

Objetos de Transferencia de Datos (DTOs)

Los tipos de base de datos no deben filtrarse al frontend. Esto se ha convertido en un atajo popular en nuestro stack tecnológico, pero es un olor a código que crea múltiples problemas.

  • Acoplamiento tecnológico (tipos de Prisma terminando en componentes de React)

  • Riesgos de seguridad (fuga accidental de campos sensibles)

  • Contratos frágiles entre servidor y cliente (esto es particularmente problemático a medida que construimos muchas más APIs)

  • Incapacidad para evolucionar el esquema de la base de datos de forma independiente

  • Todas las conversiones de DTOs a través de Zod, incluso para una respuesta API para asegurarnos de que se está validando toda la data antes de enviarla al usuario. Es mejor fallar que devolver algo incorrecto.

El estándar de ahora en adelante:
Crea DTOs explícitos en cada límite arquitectónico.

  1. Capa de Datos → Capa de Aplicación → API: Transforma modelos de bases de datos en DTOs de la capa de aplicación, luego transforma DTOs de la aplicación en DTOs específicos de API

  2. API → Capa de Aplicación → Capa de Datos: Transforma DTOs de API a través de la capa de aplicación y en DTOs específicos de datos

Sí, esto requiere más código. Sí, vale la pena. Los límites explícitos prevenen la erosión arquitectónica que crea pesadillas de mantenimiento a largo plazo.

Patrones de Diseño Orientado al Dominio

Los siguientes patrones deben ser usados correctamente y de manera consistente:

  • Servicios de Aplicación

    • Orquestar casos de uso, coordinar entre servicios de dominio y repositorios

  • Servicios de Dominio

    • Contienen lógica de negocios que no pertenece naturalmente a una sola entidad

  • Repositorios

    • Abstraer el acceso a datos, aislar elecciones tecnológicas

  • Inyección de Dependencias

    • Permitir un acoplamiento débil, facilitar las pruebas, aislar los intereses

  • Proxy de Caché

    • Envolver repositorios o servicios para agregar comportamiento de caché de manera transparente

    • No es la única forma de hacer caching, por supuesto, pero un buen punto de partida

  • Decoradores

    • Agregar preocupaciones transversales (registro, métricas, etc.) sin contaminar la lógica de dominio

Consistencia del Código

Nuestras bases de código deberían parecer como si una persona las escribiera. Este nivel de consistencia requiere una estricta adherencia a los patrones establecidos, reglas integrales de linting que impongan estándares arquitectónicos revisiones de código que rechacen violaciones de patrones + la ayuda de revisores de código AI.

Mover Condicionales al Punto de Entrada de la Aplicación

Las sentencias If pertenecen al punto de entrada, no dispersas por tus servicios. Este es uno de los principios arquitectónicos más importantes para mantener un código limpio y enfocado que no se torne en complejidad inmantenible.

Así es como un código se degrada con el tiempo: Un servicio se escribe para un propósito claro y específico. La lógica es limpia y enfocada. Luego llega un nuevo requisito de producto, y alguien agrega una sentencia if. Unos años y varios requisitos después, ese servicio está plagado de comprobaciones condicionales para diferentes escenarios. El servicio se ha convertido en:

  • Complicado y difícil de leer

  • Dificultoso de entender y razonar

  • Más susceptible a errores

  • Violando la responsabilidad única (manejando demasiados casos diferentes)

  • Casi imposible de probar a fondo

El servicio ha excedido sus límites en términos de responsabilidades y lógica.

Una Solución: Patrón de Fábrica con Servicios Especializados
Utiliza el patrón de fábrica para tomar decisiones en el punto de entrada, luego delega a servicios especializados que manejan su lógica específica sin condicionales.

Ejemplo de nuestro base de código:
El BillingPortalServiceFactory determina si la facturación es para una organización, equipo o usuario individual, luego devuelve el servicio apropiado:

export class BillingPortalServiceFactory {  
  static async createService(teamId: number): Promise<BillingPortalService>

Cada servicio maneja su lógica específica sin necesidad de comprobar "¿soy una organización o un equipo?":

// OrganizationBillingPortalService handles ONLY organization logic
class OrganizationBillingPortalService extends BillingPortalService {  
  async checkPermissions(userId: number, teamId: number): Promise<boolean> {    
    return await this.permissionService.checkPermission({      
      userId,      
      teamId,      
      permission: "organization.manageBilling",  // Organization-specific      
      fallbackRoles: [MembershipRole.ADMIN, MembershipRole.OWNER],    
    });  
  }  
  // ... more organization-specific logic
}

// TeamBillingPortalService handles ONLY team logic
class TeamBillingPortalService extends BillingPortalService {  
  async checkPermissions(userId: number, teamId: number): Promise<boolean> {    
    return await this.permissionService.checkPermission({      
      userId,      
      teamId,      
      permission: "team.manageBilling",  // Team-specific      
      fallbackRoles: [MembershipRole.ADMIN, MembershipRole.OWNER]

Por Qué Esto Importa

  • Los servicios mantienen su enfoque

    • Cada servicio tiene una responsabilidad y no necesita saber sobre otros contextos. El OrganizationBillingPortalService no contiene sentencias if verificando if (isTeam) o if (isUser). Solo sabe cómo manejar organizaciones.

  • Los cambios están aislados

    • Cuando necesitas modificar la lógica de facturación de la organización, solo tocas OrganizationBillingPortalService. No corres el riesgo de romper la facturación de equipos o usuarios. No necesitas seguir condicionales anidados para averiguar qué camino toma tu código.

  • Las pruebas son directas

    • Prueba cada servicio independientemente con sus escenarios específicos. No necesitas probar cada combinación de condicionales en diferentes contextos.

  • Los nuevos requisitos no contaminan el código existente

    • por ejemplo, cuando necesitas agregar facturación empresarial con reglas diferentes, creas EnterpriseBillingPortalService. La fábrica gana una condicional más, pero los servicios existentes permanecen intocados y enfocados.

Cómo Lograrlo

  • Empuja las condicionales hacia los controladores, fábricas o lógica de enrutamiento. Deja que estos puntos de entrada tomen decisiones sobre qué servicio usar.

  • Mantén los servicios puros y enfocados en una única responsabilidad. Si un servicio necesita comprobar "¿qué tipo soy?", probablemente necesites múltiples servicios.

  • Prefiere polimorfismo sobre condicionales

    • Las interfaces definen el contrato. Las implementaciones concretas proporcionan los detalles.

  • Vigila la acumulación de declaraciones if

    • Durante la revisión de código, si ves que un servicio gana condicionales para diferentes escenarios, esa es una señal de que debes refactorizar en servicios especializados.

Diseño de API: Controladores Delgados y Abstracción HTTP

  • Los controladores son capas delgadas que manejan solo preocupaciones HTTP.

    • Reciben solicitudes, las procesan, y mapean datos a DTOs que se pasan a la lógica central de la aplicación. De ahora en adelante, no se debe ver lógica de aplicación o central en rutas API o manejadores tRPC.

  • Debemos despegar la tecnología HTTP de nuestra aplicación.

    • La forma en que transferimos datos entre cliente y servidor (ya sea REST, tRPC, etc.) no debe influir en cómo funciona nuestra aplicación central. HTTP es un mecanismo de entrega, no un impulsor arquitectónico.

Responsabilidades del controlador (y SOLO estas):

  • Recibir y validar solicitudes entrantes

  • Extraer datos de parámetros de solicitud, cuerpo, encabezados

  • Transformar datos de solicitud en DTOs

  • Llamar a los servicios de aplicación apropiados con esos DTOs

  • Transformar las respuestas de los servicios de la aplicación en DTOs de respuesta

  • Devolver respuestas HTTP con códigos de estado apropiados

Los controladores no deben:

  • Contener lógica de negocio o reglas de dominio

  • Acceder directamente a bases de datos o servicios externos

  • Realizar transformaciones de datos complejas o cálculos

  • Tomar decisiones sobre lo que la aplicación debe hacer

  • Saber detalles de implementación del dominio

Ejemplo del patrón controlador delgado:


Versionado de API y Cambios críticos

No hay cambios críticos. Esto es crítico. Una vez que un endpoint de API es público, debe permanecer estable. Los cambios críticos destruyen la confianza del desarrollador y crean pesadillas de integración para nuestros usuarios.

Estrategias para evitar cambios críticos:

  • Sempre añadí novos campos como opcionales

  • Utiliza versionado de API cuando debes cambiar el comportamiento existente

  • Deprecia endpoints antiguos de manera elegante con caminos claros de migración

  • Mantén la compatibilidad hacia atrás durante al menos dos versiones mayores

Cuando debes hacer cambios críticos:

  • Crea una nueva versión de API utilizando el versionado específico por fecha en la API v2 (quizás también miremos el versionado por nombre que Stripe recientemente introdujo)

  • Ejecuta ambas versiones simultáneamente durante la transición (ya hacemos esto en la API v2)

  • Proporciona herramientas de migración automatizadas cuando sea posible

  • Da a los usuarios tiempo suficiente para migrarse (mínimo seis meses para API públicas)

  • Documenta exactamente qué cambió y por qué

Rendimiento y Complejidad Algorítmica

Construimos para grandes organizaciones y equipos. Lo que funciona bien con 10 usuarios o 50 registros puede colapsar bajo el peso de la escala empresarial. El rendimiento no es algo que optimizamos más tarde. Es algo que construimos correctamente desde el principio.

Pensar en la Escala Desde el Primer Día

Al construir características, pregúntate siempre: "¿Cómo se comporta esto con 1,000 usuarios? 10,000 registros? 100,000 operaciones?" La diferencia entre algoritmos O(n) y O(n²) puede ser imperceptible en desarrollo, pero catastrófica en producción.

Patrones comunes O(n²) a evitar:

  • Iteraciones anidadas de matriz (.map dentro de .map, .forEach dentro de .forEach)

  • Métodos de matriz como .some, .find, o .filter dentro de bucles o callbacks

  • Verificar cada elemento contra cada otro elemento sin optimización

  • Filtros encadenados o mapeado anidados sobre listas grandes

Ejemplo del mundo real: Para 100 espacios disponibles y 50 períodos ocupados, un algoritmo O(n²) realiza 5,000 verificaciones. Aumenta eso a 500 espacios y 200 períodos ocupados, y estás realizando 100,000 operaciones. Eso es un aumento de carga computacional de 20 veces para solo un aumento de datos de 5 veces.

Elige las Estructuras de Datos y Algoritmos Correctos

La mayoría de los problemas de rendimiento se resuelven eligiendo mejores estructuras de datos y algoritmos:

  • Ordenado + salida temprana: Ordena tus datos una vez, luego rompe los bucles cuando sepas que los elementos restantes no coincidirán

  • Búsqueda binaria: Utiliza búsqueda binaria para búsquedas en matrices ordenadas en lugar de escaneos lineales

  • Técnicas de dos pointers: Para fusionar o intersectar secuencias ordenadas, camina a través de ambas con pointers en lugar de bucles anidados

  • Mapas/sets hash: Usa objetos o conjuntos para búsquedas O(1) en lugar de .find o .includes en matrices

  • Árboles de intervalos: Para programación de horarios, disponibilidad y consultas de rango, usa estructuras de árbol adecuadas en lugar de comparación exhaustiva

Transformación de ejemplo:

// Bad: O(n²) - checks every slot against every busy time
availableSlots.filter(slot => {  
  return !busyTimes.some(busy => checkOverlap(slot, busy));
});

// Good: O(n log n) - sort once, break early
const sortedBusy = [...busyTimes]

Comprobaciones de Rendimiento Automatizadas

Implementaremos múltiples capas de defensa contra regresiones de rendimiento:

Reglas de linting que marcan:

  • Funciones con bucles anidados o métodos de matriz anidados

  • Llamadas múltiples a .some anidadas, .find, o .filter

  • Recursión sin memoización

  • Patrones conocidos anti-patrón para nuestro dominio (programación, comprobaciones de disponibilidad, etc.)

Pruebas de rendimiento en CI que:

  • Ejecutan algoritmos críticos sobre datos realistas y a gran escala

  • Comparan tiempos de ejecución contra una línea base en cada PR

  • Bloquean fusiones que introducen regresiones de rendimiento

  • Prueba con datos a nivel empresarial (miles de usuarios, decenas de miles de registros)

Monitoreo de producción que:

  • Rastrea el tiempo de ejecución para rutas críticas

  • Alerta cuando los algoritmos se desaceleran a medida que crecen los datos

  • Detecta regresiones antes de que los usuarios lo noten

  • Proporciona datos de rendimiento del mundo real para informar las optimizaciones

El Rendimiento es una Característica

El rendimiento no es opcional. No es algo que "abordamos más tarde". Para clientes empresariales reservando a través de grandes equipos, respuestas lentas significan productividad perdida y usuarios frustrados (nuestra experiencia con algunos clientes empresariales más grandes puede dar testimonio de esto).

Cada ingeniero debería:

  • Perfila tu código antes de optimizar, pero piensa en la complejidad desde el principio

  • Prueba con datos realistas a gran escala (no solo 5 registros de prueba). Ya hemos construido scripts de seed. Probablemente necesitemos extenderlos.

  • Elige algoritmos y estructuras de datos eficientes desde el principio

  • Observa las iteraciones anidadas en la revisión de código

  • Cuestiona cualquier algoritmo que escale con el producto de dos variables

La Dura Realidad NP de la Programación

Los problemas de programación son fundamentalmente NP-duros. Esto significa que a medida que crece el número de restricciones, participantes, o espacios de tiempo, la complejidad computacional puede explotar exponencialmente. La mayoría de los algoritmos de programación óptima tienen un peor de los casos de complejidad de tiempo exponencial, lo que hace que la elección del algoritmo sea absolutamente crítica.

Implicaciones del mundo real:

  • Encontrar el tiempo de reunión óptimo para 10 personas a través de 3 zonas horarias con restricciones de disponibilidad individual es computacionalmente costoso

  • Agregar detección de conflictos, buffers, y una multitud de otras opciones amplifica el problema

  • Elecciones de algoritmos pobres que funcionan bien para equipos pequeños se vuelven completamente inusuales para grandes organizaciones

  • Lo que toma milisegundos para 5 usuarios puede tardar muchos segundos para organizaciones

Estrategias para manejar la complejidad NP-dura:

  • Usa algoritmos de aproximación que encuentran soluciones "suficientemente buenas" rápidamente en lugar de soluciones perfectas lentamente

  • Implementa un almacenamiento en caché agresivo de programas de computación y disponibilidad

  • Precomputar escenarios comunes durante horas no pico

  • Dividir problemas de programación grandes en trozos más pequeños y manejables

  • Establecer límites de tiempo razonables y recurrir a algoritmos más simples cuando sea necesario

Esta es la razón por la que el rendimiento no es solo algo agradable de tener en el software de programación de horarios. Es la base que determina si tu sistema puede escalar a las necesidades empresariales o colapsa bajo patrones de uso del mundo real.

Requisitos de Cobertura de Código

  • Rastreo de cobertura global

    • Rastrearemos la cobertura total de la base de código como una métrica clave que mejora con el tiempo. Esto nos da visibilidad en nuestra madurez de pruebas y ayuda a identificar áreas que necesitan atención. El porcentaje de cobertura global se muestra destacadamente en nuestros tableros.

  • 80%+ de cobertura para nuevo código

    • Cada PR debe tener un 80%+ de cobertura de pruebas para el código que introduce o modifica. Esto se aplica automáticamente en nuestra canalización de CI. Si agregas 50 líneas de nuevo código, esas 50 líneas deben estar cubiertas por pruebas. Si modificas una función existente, tus cambios deben ser probados. Esta es la cobertura general de pruebas. La cobertura de pruebas unitarias necesita estar cerca del 100%, especialmente con la capacidad de aprovechar la IA para ayudar a generar estas.

Abordando el argumento de que "la cobertura no es la historia completa": Sí, sabemos que la cobertura no garantiza pruebas perfectas. Sabemos que puedes escribir pruebas sin sentido que golpean cada línea pero no prueban nada significativo. Sabemos que la cobertura es solo una métrica entre muchas. Pero, seguramente es mejor apuntar a un porcentaje alto que no tener idea de dónde estás en absoluto.

Midiendo el Éxito

  • "Velocidad" (robando esto de Scrum aunque no usaremos Scrum)

    • Crecimiento continuo en estadísticas mensuales (características, mejoras, refactorizaciones)

  • Calidad

    • Reduce el esfuerzo de PR dedicado a arreglos del 35% actual al 20% o menos para finales de 2026 (calculado en función de cambios de archivo y adiciones/eliminaciones)

  • Salud arquitectónica

    • Métricas sobre adherencia a patrones, acoplamiento tecnológico, violaciones de límites

  • Eficiencia de revisión

    • PRs más pequeños, revisiones más rápidas, menos rondas de retroalimentación

  • Tiempo de actividad de la aplicación y API

    • ¿Qué tan cerca estamos de 99.99%?

¡Comienza con Cal.com gratis hoy!

Experimenta una programación y productividad sin problemas, sin tarifas ocultas. ¡Regístrate en segundos y comienza a simplificar tu programación hoy, sin necesidad de tarjeta de crédito!