#The problem
The academy had over 240 active students — between external students and competition team members with daily classes —, 12 dance genres and more than a dozen teachers. Each with a different contract: some charge per number of students in class, others by percentage of revenue, others per fixed class. The process before the system:
Calculating a single teacher's payment took over an hour. With multiple teachers, month-end closing was a days-long process, full of uncertainty and room for error. The owner couldn't trust the numbers because the numbers depended on sheets of paper.
#The decision
The client — a friend — explained the disorder and I asked for two things: to understand how each teacher's contract worked, and to understand the complete operation flow. From there, the design was a natural consequence.
The challenge wasn't just to digitize — it was to standardize an operation full of exceptions: discounts to retain students, specially negotiated packages, teacher contracts mixing modalities. All of that had to fit in a system simple enough for everyone to use. It went into production in two weeks.
#The complete flow in one view
LDC App manages the complete academy cycle: from when a student buys a pack, to month-end with automatic teacher payroll.
01 / 08
Student buys a class pack (variable quantity per genre)
#Core Modules: The Ecosystem
The application is structured in modules accessible from the main dashboard. Each module solves a specific operational friction point in the academy:
Invisible input with permanent focus. Captures UUID from USB gun, validates active pack and records attendance in < 2 seconds.
Registration, editing, attendance history, active pack balance and downloadable PDF cards.
Flexible packs with shopping cart. One payment can include multiple packs of different genres in a single atomic transaction.
Integrated FullCalendar. Classes by genre, schedule and teacher with explicit America/Santiago timezone.
Configurable payroll engine per teacher and per class: fixed, commission, mixed or negotiated. Automatic closing with audited breakdown.
XLSX and PDF reports with attendance and payment breakdown by period. Student cards with QR included.
#The technical decisions that matter
1. Configurable payroll engine by modality
The central problem was that each teacher had different payment schemes — and the same teacher could have different schemes depending on the genre they taught. The system allows separate configuration: Fixed (fixed amount per class, regardless of attendance), Commission (percentage of proportional revenue), Mixed (fixed amount plus percentage) and Negotiated (manually agreed amount). At month-end, the system automatically calculates each teacher's payment, broken down by class, with the calculation detail stored in JSON for complete audit. What used to take over an hour per teacher now takes seconds, with full traceability.
2. Real-time QR access control
The access module maintains an invisible input with permanent focus that captures the UUID sent by a USB reader gun. The complete flow takes less than 2 seconds: QR scan → student lookup → active pack validation → balance deduction → visual confirmation → next student. If the pack is expired or has no classes, the system alerts immediately with an option to renew on the spot. Attendance registration is automatic — no manual intervention.
3. Standardization of operational exceptions
The biggest challenge wasn't technical in the classic sense — it was modeling the reality of a business full of special cases. Discounts to retain students, packs with non-standard class quantities, mixed modalities in a single transaction. The solution was a flexible pack system with a shopping cart: one payment can include multiple packs of different genres, each with its price and modality, processed in a single atomic transaction via PostgreSQL RPC.
Flexible Packs
Multi-genre cart
A student can buy packs of different genres in a single operation. Each pack has independent price, class quantity and expiration.
AtomicRow Level Security
Policy: auth.uid() = user_id
RLS on all tables. Academy data is not accessible without authentication. Admin and reception with differentiated roles.
SecureTimezone
America/Santiago explicit
With classes scheduled in Chilean local time and DB in UTC, the entire system uses America/Santiago to handle UTC-4 (summer) and UTC-3 (winter) without errors.
AccurateThe result: the owner can make exceptions — discounts, special packs, custom contracts — without breaking system consistency. Everything is recorded and auditable.
Payroll engine: calculation by modality
The calculation is broken down by class and contract modality. The JSON detail allows auditing exactly how each number was reached — no paper sheets or manual Excel formulas.
"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 };
} QR access control: complete flow
The invisible input maintains permanent focus to capture the USB reader gun. The Server Action validates the pack, deducts the balance and records attendance — all in a single atomic operation.
"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 && }
);
} #The impact in numbers
Before vs. after: the same team, the same operation volume, completely different results.
#Tech Stack
#What's next
Automatic notifications
Email or SMS when a pack is about to expire, to reduce student loss before they stop attending.
Student portal
Check balance, attendance history and class booking from the phone.
Online payment
Integration with Webpay or MercadoPago for students to pay for their packs without going to reception.
Predictive retention analysis
Identify students at risk of dropping out before they stop attending, using attendance patterns.
#Reflection
This project was built in two weeks because the problem was clear from day one. When you understand the operation before opening the editor, the design becomes obvious. The most valuable thing wasn't the code — it was the initial conversation where I understood that the owner didn't need a generic system.
He needed one that followed exactly how his academy worked, with all its exceptions included. Today the owner trusts the numbers. He has real-time information, and can focus on what really matters: improving the academy so more students come.
— Jaime Arias · Design, architecture and complete implementation · Build: 2 weeks · In production since March 2026 · Continuous evolution