Dask

Gestión de academias de baile con un sitio público por academia

La plataforma donde una academia de baile gestiona matrícula, cobros y comunicación con sus estudiantes, y obtiene su propio sitio público en su propio subdominio. Multi-tenant sobre Supabase, con los cobros automatizados con pg_cron y las reglas de acceso verificadas con más de 200 chequeos en SQL.

Rol: Fundador & Full Stack Developer
Duración: 2025 - Presente
Dask hero image

Pantallas

Sitio público de una academia de baile en su propio subdominio de Dask

Stack tecnológico

React 18 TypeScript Vite Zustand TanStack Query Supabase PostgreSQL Cloudflare R2 Resend Astro Tailwind CSS

Visión general

Las academias de baile son negocios hechos de personas: grupos que cambian, estudiantes que van y vienen, mensualidades que hay que perseguir. Casi todas operan entre hojas de cálculo y chats, con comprobantes de pago perdidos en WhatsApp, y el costo es silencioso: horas administrativas, pagos que se escapan y conversaciones incómodas de cobro que desgastan la relación con el estudiante. Bailo salsa y bachata, así que conocía este mundo desde adentro. Dask empezó por el dinero, que es donde más duele, y después creció para darle a cada academia su propio sitio público, con sus colores, en su propio subdominio.

⚡ El desafío

El dinero en una academia pequeña es desordenado: efectivo, transferencias y comprobantes de SINPE enviados como fotos, pagos parciales, promos de dos por uno. Cada academia es un tenant que nunca debe ver los datos de otra, y una misma persona puede ser profesora en una academia y administradora en otra.

✓ La solución

El cobro separa tres cosas que normalmente se mezclan: un cargo es una deuda, un reporte de pago es un reclamo con una foto y una transacción es dinero real. Los cargos mensuales los genera un job de pg_cron en SQL puro, idempotente por estudiante y fecha de vencimiento, con el día de cobro ajustado en los meses cortos. El estudiante sube un comprobante, comprimido en el navegador y guardado en Cloudflare R2 mediante una Edge Function; un administrador lo aprueba, y un trigger de base de datos salda el cargo cuando las transacciones pagadas alcanzan el monto. El orden de los jobs diarios es deliberado: generar a medianoche, marcar vencidos a las 06:00 y enviar recordatorios a las 08:00, hora de Costa Rica.

Impacto y resultados

5
Academias incorporadas

Cada una en su propio subdominio con su marca y su sitio público

~10 h/semana
Trabajo administrativo ahorrado

Al automatizar cargos, validación de comprobantes y recordatorios

204
Chequeos de seguridad en SQL

177 chequeos de acceso y 27 ataques de escalada de privilegios en la suite de gates

Decisiones clave

1

Cargos, reportes de pago y transacciones son tres tablas

Una deuda, un reclamo y el dinero tienen ciclos de vida distintos. Mantenerlos separados es lo que permite pagos parciales, comprobantes rechazados y pagos por adelantado sin editar la historia.

2

Id del tenant desnormalizado para un RLS rápido

academy_id se copia en las seis tablas transaccionales y un trigger lo mantiene correcto, así las policies filtran por la propia fila en lugar de hacer join con profiles.

3

Los helpers de RLS devuelven conjuntos, no escalares

Cuando una persona pudo tener varios perfiles, un helper que hacía LIMIT 1 sobre una columna que ya no era única empezó a devolver una fila arbitraria, en silencio, en 47 de las 76 policies de producción. Primero audité producción en modo solo lectura y después reescribí las 76; borrar los helpers viejos al final hacía fallar la migración si alguna policy todavía los usaba.

4

Gates de seguridad escritos como ataques

No existía una suite de tests, así que escribí gates en SQL que corren dentro de una transacción y hacen rollback: chequeos de quién puede ver o tocar qué, invariantes contables que deben contar cero violaciones y 27 ataques de escalada de privilegios. Existen porque, antes de agregar permisos finos para profesores, me pregunté si un estudiante podía simplemente volverse profesor, y encontré dos formas.

5

Un cron que no está en una migración no existe

Staging y producción habían terminado con conjuntos de jobs programados completamente distintos. Los siete jobs ahora viven en migraciones y llaman a las Edge Functions mediante una única función puente que lee su configuración de la base de datos.

6

Un solo resolvedor para el subdominio

La app, su manifest de PWA y el sitio público en Astro resuelven la academia desde el hostname con la misma lista de nombres reservados, que la base de datos también hace cumplir, así nadie puede registrar admin.getdask.com. El sitio público resuelve el tenant una sola vez en el middleware, desde una única vista.

Destacados técnicos

Aprendizajes clave

Una auditoría encontró que el registro público aceptaba un rol enviado por el cliente. Nunca hay que confiar en un identificador o un rol que venga del cliente; se resuelve en el servidor.

Un RPC que recibía el id de la academia como parámetro era una fuga entre tenants esperando a ocurrir. Ahora resuelve la academia desde la sesión.

El precache del service worker de la app secuestraba la ruta raíz en los subdominios de los tenants y servía el shell de la app en lugar del sitio público de la academia. Dos productos en un mismo dominio necesitan un contrato explícito sobre quién es dueño de cada URL.

Aplastar el esquema para igualarlo a producción y desplegar las migraciones desde CI fueron la misma decisión: dejar de editar producción a mano.

La documentación que admite dónde el producto está mal (rutas huérfanas, estados inalcanzables, bugs de flujo conocidos) es más útil que la que solo describe el camino feliz.

Ver todos los casos de estudio