4Korners Member Portal
Una plataforma de membresías en producción para una organización sin fines de lucro de Montreal. Gestiona el registro y el inicio de sesión, las membresías con Stripe, las donaciones, un catálogo de actividades y un panel de administración. La construí y la opero yo solo, desde la base de datos hasta el pipeline de despliegue.
Arquitectura
Una aplicación Next.js respaldada por Postgres con Drizzle. Se sincroniza con el CRM Airtable de la organización mediante una caché de lectura que se refresca una vez al día, cobra pagos con Stripe y donaciones con Zeffy, y coloca cada pantalla de administración detrás de un control de acceso basado en roles.
Decisiones clave
Una caché SQL en vez de llamadas en vivo a Airtable
El límite de la API de Airtable no podía servir el portal directamente. Copio los datos del CRM, que cambian poco, en Postgres una vez al día y leo de ahí, así que las páginas que ven los miembros se mantienen rápidas y nunca tocan el límite.
Estado de membresía derivado, calculado y no almacenado
Si un miembro está activo, vencido o en periodo de gracia se calcula a partir de sus pagos cuando carga la página, no se guarda como bandera. Eso mantiene una sola fuente de verdad y no deja ningún estado obsoleto que reconciliar.
La parte difícil
Los escáneres de correo quemaban los enlaces de confirmación
Los escáneres de seguridad de correo precargan los enlaces para revisarlos. Abrían el enlace de confirmación antes que el usuario y gastaban el token, que solo funciona una vez, así que fallaban registros reales. La corrección fue dejar de tratar un GET simple como confirmación y exigir una acción explícita del usuario.
Seguridad y rendimiento
- Los administradores deben pasar un segundo factor (TOTP con códigos de respaldo) para llegar a cualquier pantalla que muestre datos personales.
- Los endpoints de inicio de sesión y correo tienen límite de tasa, y las server actions de producción están bloqueadas a su origen.
- Para cumplir con la Ley 25, los consentimientos se capturan al registrarse, y una solicitud de borrado se cumple tanto en el portal como en el CRM.