#El problema
La academia tenía más de 240 alumnos activos — entre alumnos externos y miembros del equipo de competición con clases todos los días —, 12 géneros de baile y más de una docena de profesores. Cada uno con un contrato distinto: algunos cobran por cantidad de alumnos en clase, otros por porcentaje del ingreso, otros por clase fija. El proceso antes del sistema:
El cálculo de pago de un solo profesor tomaba más de una hora. Con múltiples profesores, el cierre de mes era un proceso de días, lleno de incertidumbre y margen de error. El dueño no podía confiar en los números porque los números dependían de hojas de papel.
#La decisión
El cliente — un amigo — me explicó el desorden y pedí dos cosas: entender cómo funcionaba cada contrato de profesor, y entender el flujo completo de la operación. A partir de ahí, el diseño fue consecuencia natural.
El reto no era solo digitalizar — era estandarizar una operación llena de excepciones: descuentos a alumnos para no perderlos, paquetes especiales negociados caso a caso, contratos de profesores que mezclaban modalidades. Todo eso tenía que caber en un sistema que fuera simple de operar. Entró en producción en dos semanas.
#El flujo completo en una vista
LDC App gestiona el ciclo completo de la academia: desde que el alumno compra un pack, hasta el cierre de mes con liquidación automática por profesor.
01 / 08
Alumno compra pack de clases (cantidad variable por género)
#Módulos Core: El Ecosistema
La aplicación está estructurada en módulos accesibles desde el dashboard principal. Cada módulo resuelve una fricción operativa específica de la academia:
Input invisible con foco permanente. Captura UUID de pistola USB, valida pack activo y registra asistencia en < 2 segundos.
Alta, edición, historial de asistencia, saldo de packs activos y carnets PDF descargables.
Packs flexibles con carrito de compra. Un pago puede incluir múltiples packs de géneros distintos en una sola transacción atómica.
FullCalendar integrado. Clases por género, horario y profesor con zona horaria America/Santiago explícita.
Motor de liquidación configurable por profesor y por clase: fijo, comisión, mixto o convenio. Cierre automático con desglose auditado.
Reportes XLSX y PDF con desglose de asistencia y pagos por período. Carnets de alumno con QR incluido.
#Las decisiones técnicas que importan
1. Motor de liquidación configurable por modalidad
El problema central era que cada profesor tenía esquemas de pago distintos — y un mismo profesor podía tener esquemas diferentes según el género que enseñara. El sistema permite configurar por separado: Fijo (monto fijo por clase, independiente de asistencia), Comisión (porcentaje del ingreso proporcional), Mixto (monto fijo más porcentaje) y Convenio (monto negociado manualmente). Al cerrar el mes, el sistema calcula automáticamente el pago de cada profesor, desagregado por clase, con el detalle del cálculo guardado en JSON para auditoría completa. Lo que antes tomaba más de una hora por profesor ahora toma segundos, con trazabilidad total.
2. Control de acceso QR en tiempo real
El módulo de acceso mantiene un input invisible con foco permanente que captura el UUID enviado por una pistola lectora USB. El flujo completo dura menos de 2 segundos: escaneo QR → búsqueda alumno → validación pack activo → descuento de saldo → confirmación visual → siguiente alumno. Si el pack está vencido o sin clases, el sistema alerta inmediatamente con opción de renovar en el momento. El registro de asistencia es automático — sin intervención manual.
3. Estandarización de excepciones operativas
El mayor reto no fue técnico en el sentido clásico — fue modelar la realidad de un negocio lleno de casos especiales. Descuentos para retener alumnos, packs con cantidad de clases no estándar, modalidades mezcladas en una sola transacción. La solución fue un sistema de packs flexible con carrito de compra: un pago puede incluir múltiples packs de géneros distintos, cada uno con su precio y modalidad, procesados en una sola transacción atómica vía RPC de PostgreSQL.
Packs Flexibles
Carrito multi-género
Un alumno puede comprar packs de géneros distintos en una sola operación. Cada pack tiene precio, cantidad de clases y vigencia independiente.
AtómicoRow Level Security
Policy: auth.uid() = user_id
RLS en todas las tablas. Los datos de la academia no son accesibles sin autenticación. Admin y recepción con roles diferenciados.
SeguroZona Horaria
America/Santiago explícito
Con clases programadas en hora local chilena y BD en UTC, todo el sistema usa America/Santiago para manejar UTC-4 (verano) y UTC-3 (invierno) sin errores.
PrecisoEl resultado: el dueño puede hacer excepciones — descuentos, packs especiales, contratos a medida — sin romper la consistencia del sistema. Todo queda registrado y es auditable.
Motor de liquidación: cálculo por modalidad
El cálculo se desagrega por clase y modalidad de contrato. El detalle en JSON permite auditar exactamente cómo se llegó a cada número — sin hojas de papel ni fórmulas manuales en Excel.
"text-slate-500">// lib/liquidacion/calcularPagoProfesor.ts
"text-purple-400">export "text-purple-400">async "text-purple-400">function calcularPagoProfesor(
profesorId: string,
mes: number,
anio: number
): Promise {
"text-purple-400">const { data: clases } = "text-purple-400">await supabase
."text-purple-400">from('clases_mes')
.select(`
id, nombre, asistencia_count,
ingreso_proporcional,
contrato_profesor!inner(modalidad, monto_fijo, porcentaje)
`)
.eq('profesor_id', profesorId)
.eq('mes', mes)
.eq('anio', anio);
"text-purple-400">const desglose = clases."text-purple-400">map((clase) => {
"text-purple-400">const { modalidad, monto_fijo, porcentaje } = clase.contrato_profesor;
"text-purple-400">const pago =
modalidad === 'FIJO' ? monto_fijo :
modalidad === 'COMISION' ? clase.ingreso_proporcional * porcentaje :
modalidad === 'MIXTO' ? monto_fijo + clase.ingreso_proporcional * porcentaje :
/* CONVENIO */ monto_fijo;
"text-purple-400">return { claseId: clase.id, nombre: clase.nombre, pago, detalleJSON: { modalidad, monto_fijo, porcentaje } };
});
"text-purple-400">const total = desglose."text-purple-400">reduce((sum, d) => sum + d.pago, 0);
"text-purple-400">return { profesorId, mes, anio, total, desglose };
} Control de acceso QR: flujo completo
El input invisible mantiene foco permanente para capturar la pistola lectora USB. El Server Action valida el pack, descuenta el saldo y registra la asistencia — todo en una sola operación atómica.
"text-slate-500">// app/acceso/page.tsx — Input invisible con foco permanente
'use client';
"text-purple-400">export "text-purple-400">default "text-purple-400">function AccesoPage() {
"text-purple-400">const inputRef = "text-purple-400">useRef(null);
"text-purple-400">const [resultado, setResultado] = "text-purple-400">useState(null);
"text-slate-500">// Mantener foco permanente para capturar pistola USB
"text-purple-400">useEffect(() => {
"text-purple-400">const mantenerFoco = () => inputRef.current?.focus();
document.addEventListener('click', mantenerFoco);
mantenerFoco();
"text-purple-400">return () => document.removeEventListener('click', mantenerFoco);
}, []);
"text-purple-400">async "text-purple-400">function handleScan(uuid: string) {
"text-purple-400">const res = "text-purple-400">await validarAcceso(uuid); "text-slate-500">// Server Action
setResultado(res);
"text-slate-500">// Auto-reset para siguiente alumno
setTimeout(() => { setResultado(null); inputRef.current?.focus(); }, 2000);
}
"text-purple-400">return (
"sr-only" onKeyDown={/* captura UUID */} />
{resultado && }
);
} #El impacto en números
Antes vs. después: el mismo equipo, el mismo volumen de operación, resultados completamente distintos.
#Stack Tecnológico
#Lo que sigue
Notificaciones automáticas
Email o SMS cuando un pack está por vencer, para reducir la pérdida de alumnos antes de que dejen de asistir.
Portal del alumno
Consulta de saldo, historial de asistencia y reserva de clase desde el teléfono.
Pago online
Integración con Webpay o MercadoPago para que los alumnos paguen sus packs sin ir a recepción.
Análisis de retención predictivo
Identificar alumnos en riesgo de abandono antes de que dejen de asistir, usando patrones de asistencia.
#Reflexión
Este proyecto se construyó en dos semanas porque el problema estaba claro desde el primer día. Cuando entiendes la operación antes de abrir el editor, el diseño se vuelve obvio. Lo más valioso no fue el código — fue la conversación inicial donde entendí que el dueño no necesitaba un sistema genérico.
Necesitaba uno que siguiera exactamente cómo funcionaba su academia, con todas sus excepciones incluidas. Hoy el dueño confía en los números. Tiene información en tiempo real, y puede concentrarse en lo que realmente importa: mejorar la academia para que vengan más alumnos.
— Jaime Arias · Diseño, arquitectura e implementación completa · Construcción: 2 semanas · Producción desde marzo 2026 · Evolución continua