Saltar al contenido principal

Notas de versión — 3 de abril de 2026

Esta versión completa el sistema de aplicación de límites tras degradación — el conjunto completo de cambios de backend, frontend, correo e IU que garantizan que la aplicación gestione correctamente las situaciones en las que los datos existentes de una unidad superan los límites de su nuevo plan (inferior) tras una degradación de suscripción.


Aplicación de degradación: Gestión completa de recursos con límite superado

Descripción general

Cuando el propietario de un espacio de trabajo degrada de PRO a FREE (o de ENTERPRISE a PRO), sus datos existentes pueden superar los límites del nuevo plan. Esta versión garantiza que cada capa del sistema — API, IU, notificaciones — aplique y comunique correctamente esos límites.

Límites afectados:

RecursoLímite FREELímite PRO
Miembros del equipo320
Proyectos (por espacio de trabajo)3Ilimitado
Tareas (por espacio de trabajo)1570
Entradas de tiempo (por espacio de trabajo)1001.000

Aplicación en el backend

Bloqueo de escritura para usuarios bloqueados

Todos los endpoints de escritura (POST, PUT, PATCH, DELETE) en proyectos, tareas, equipo y entradas de tiempo ahora están protegidos por el middleware requireNotOverLimitLocked. Los usuarios cuya cuenta ha sido bloqueada debido a una degradación reciben una respuesta 403 ACCOUNT_LOCKED en cualquier intento de escritura.

Bloqueo del inicio del temporizador

El endpoint startTimer() aplica el límite de entradas de tiempo para el espacio de trabajo. Si se intenta iniciar un nuevo temporizador cuando el espacio de trabajo está en el límite o lo ha superado, se devuelve 403 LIMIT_EXCEEDED.

Bloqueo de invitación al equipo

El endpoint inviteUser() comprueba maxTeamMembers con el nivel actual antes de enviar una invitación. Las invitaciones que superan el límite devuelven 403 LIMIT_EXCEEDED.

Bloqueo de miembros al degradar (Webhook)

Cuando se recibe un evento customer.subscription.updated o customer.subscription.deleted y el nivel disminuye, markExcessMembersAsLocked() se ejecuta automáticamente:

  • Consulta todos los miembros activos y pendientes (ambos cuentan para el uso)
  • Bloquea a los miembros más recientes que superan el nuevo límite
  • Envía correos de notificación a cada miembro bloqueado

Notificaciones por correo

Correo de resumen de degradación (Propietario)

El propietario del espacio de trabajo recibe un correo inmediatamente tras la degradación con la lista de todos los recursos con límite superado:

  • Número de miembros del equipo bloqueados
  • Número de proyectos en exceso
  • Enlaces para resolver cada problema (Equipo, Proyectos, Actualizar)

Correo de acceso limitado (Miembros bloqueados)

Cada miembro cuyo acceso ha sido bloqueado recibe un correo de notificación personal que explica:

  • La cuenta ha sido degradada
  • Su acceso ahora está limitado
  • A quién contactar para restaurar el acceso completo

Frontend: IU de límite superado

Los usuarios con isOverLimitLocked: true ven un banner de advertencia persistente en toda la aplicación (renderizado en AppShell):

"El acceso a tu cuenta está limitado debido a una degradación del plan. Contacta con el propietario del espacio de trabajo para restaurar el acceso."

Banners de límite superado en todas las páginas de recursos

PáginaCondición del banner
Proyectosactual > máx. proyectos
Tareasactual >= máx. tareas
Seguimiento de tiempoactual >= máx. entradas de tiempo
Miembros del equipoactual > máx. miembros (corregido: era >=)

Todos los banners incluyen un botón de acción Actualizar.

Banners de solo lectura para proyectos en exceso

  • Página de detalles del proyecto — aparece un banner de advertencia cuando el proyecto está marcado como isExcessProject: true, con una explicación y un enlace para resolver el problema
  • Pestaña Tareas del proyecto — la creación de tareas está desactivada con un banner que explica que el proyecto está en modo de solo lectura

Estados desactivados en los selectores

Los recursos bloqueados/en exceso se muestran visualmente desactivados (no ocultos) en todos los menús desplegables de selección:

ComponenteElementos desactivados
Crear tarea — ProyectoisExcessProject: true → insignia 🔒 "Límite superado"
Editar tarea — ProyectoisExcessProject: true → insignia 🔒 "Límite superado"
Crear tarea — AsignadosisOverLimitLocked: true → insignia 🔒 "Acceso limitado"
Editar tarea — AsignadosisOverLimitLocked: true → insignia 🔒 "Acceso limitado"
Temporizador / Entrada manual — Proyecto/TareaisExcessProject: true → insignia 🔒 "Límite superado"

Corrección del banner de límite superado — > estricto

Problema: El banner de advertencia de "Límite superado" en la página de Miembros del equipo aparecía exactamente en 3/3 (en el límite), no solo cuando se superaba estrictamente.

Corrección: El banner ahora usa actual > máx. (estrictamente superior). Estar en el límite es normal; estar por encima es un estado excepcional que requiere acción.


Correcciones de errores

Consulta de bloqueo de miembros: Miembros pendientes incluidos

markExcessMembersAsLocked() anteriormente solo consultaba miembros con status: 'active'. Dado que los miembros pending también cuentan para el límite de uso en UsageTrackingService, esto hacía que la lógica de bloqueo y el contador no concordaran.

Corrección: La consulta ahora usa { status: { $in: ['active', 'pending'] } }.

Límites de nivel del modelo Unit corregidos

El hook de pre-guardado del modelo Unit tenía valores hardcoded (FREE: 5 miembros, PRO: 25 miembros) que conflictuaban con los valores autoritativos de tiers.config.ts.

Corrección: El hook de pre-guardado ahora usa los valores correctos: FREE: 3, PRO: 20.


Herramientas para desarrolladores / Operaciones

Script manual-downgrade-to-free.mjs

Degrada manualmente una unidad a FREE en la base de datos — útil para flujos de trabajo de prueba y soporte sin activar un evento real de Stripe.

node scripts/manual-downgrade-to-free.mjs <unitBundle>

Acciones: Establece el nivel, límites, cancelAtPeriodEnd, currentPeriodEnd, y ejecuta markExcessMembersAsLocked() de inmediato.

Script test-downgrade-enforcement.mjs

Simula el ciclo completo de aplicación de degradación/actualización sin Stripe.

node scripts/test-downgrade-enforcement.mjs <unitBundle> [status|downgrade|upgrade]
EscenarioEfecto
statusMuestra el estado de bloqueo actual de todos los miembros
downgradeSimula PRO → FREE, bloquea los miembros en exceso
upgradeSimula la actualización de vuelta, desbloquea todos los miembros

Resumen

ÁreaCambio
BackendVerificación de límite en startTimer() — temporizador ya aplicado (confirmado)
BackendVerificación de límite de miembros en inviteUser() — ya aplicado (confirmado)
BackendMiddleware requireNotOverLimitLocked — aplicado a todas las rutas de escritura (proyectos, tareas, equipo, entradas de tiempo)
BackendmarkExcessMembersAsLocked ahora incluye miembros pendientes
BackendLímites del hook de pre-guardado de Unit.model.ts corregidos (FREE: 3, PRO: 20)
BackendEl webhook de degradación envía correo de resumen al propietario
BackendLos miembros bloqueados reciben correo de notificación de acceso limitado
FrontendBanner de la página de Miembros del equipo: >=> (límite superado estricto)
FrontendDiálogo de creación de tarea: miembros bloqueados mostrados como desactivados con insignia
FrontendDiálogo de edición de tarea: miembros bloqueados mostrados como desactivados con insignia
FrontendDiálogo de creación de tarea: proyectos en exceso mostrados como desactivados con insignia
FrontendDiálogo de edición de tarea: proyectos en exceso mostrados como desactivados con insignia
FrontendSelector Proyecto/Tarea: recursos bloqueados mostrados como desactivados con insignia
FrontendAppShell.tsx: banner persistente global para usuarios bloqueados
FrontendProjectDetailsPage: banner de solo lectura en proyectos en exceso
FrontendProjectTasksTab: banner de creación de tareas desactivada en proyectos en exceso
FrontendTimeTrackingPage: banner de límite superado + bloqueo del widget del temporizador
FrontendProjectsPage: banner de límite superado
FrontendTaskManagementPage: banner de límite superado
Scriptsmanual-downgrade-to-free.mjs añadido
Scriptstest-downgrade-enforcement.mjs añadido

Disponibilidad: Todos los planes (la aplicación se activa automáticamente al degradar)