Skip to main content
The TecNM Control Escolar app is planned to integrate with Supabase as its backend-as-a-service platform. This guide outlines the roadmap and planned features.
Current Status: The app uses FakeData.kt for mock data. Supabase integration is listed in “Próximos pasos” in the README.

Why Supabase?

Supabase was chosen for several reasons:
  • Open-source alternative to Firebase
  • PostgreSQL database with real-time subscriptions
  • Built-in authentication (email, OAuth, magic links)
  • Row-level security for multi-tenant data isolation
  • RESTful API and Kotlin client library
  • File storage for profile pictures and documents
Supabase provides a generous free tier perfect for student projects and MVPs.

Current State vs. Future State

Current Implementation

The app currently operates in offline mode with hardcoded data:
// Current approach
import com.example.appcontrolescolar.data.FakeData

val student = FakeData.student
val classes = FakeData.todayClasses
val buildings = FakeData.buildings
Limitations:
  • No user authentication
  • No personalized data per student
  • No real-time schedule updates
  • No attendance tracking
  • No grades or academic history

Future Implementation

With Supabase, the app will become a full-featured cloud application:
// Future approach
val supabase = createSupabaseClient {
    supabaseUrl = "https://your-project.supabase.co"
    supabaseKey = "your-anon-key"
}

val student = supabase.from("students")
    .select()
    .eq("control_number", currentUser.id)
    .single()

val classes = supabase.from("class_sessions")
    .select()
    .eq("student_id", student.id)
    .eq("day", getCurrentDay())
    .order("start_hour")

Planned Features

1. Authentication System

Login with TecNM credentials:
// Email/password authentication
val user = supabase.auth.signInWith(Email) {
    email = "226W0487@tecnm.mx"
    password = "student_password"
}

// Or magic link authentication (passwordless)
supabase.auth.signInWith(OTP) {
    email = "226W0487@tecnm.mx"
    createUser = false
}
Features:
  • Student login with control number
  • Password reset via email
  • Session management
  • Role-based access (student, teacher, admin)
-- Extends Supabase auth.users
CREATE TABLE profiles (
  id UUID REFERENCES auth.users PRIMARY KEY,
  control_number TEXT UNIQUE NOT NULL,
  full_name TEXT NOT NULL,
  career TEXT NOT NULL,
  semester INTEGER NOT NULL,
  campus TEXT NOT NULL,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

-- Row-level security
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;

CREATE POLICY "Students can view own profile"
  ON profiles FOR SELECT
  USING (auth.uid() = id);

2. Real Schedule Data

Fetch personalized schedule from database:
// Get today's classes for logged-in student
val classes = supabase.from("enrollments")
    .select("""
        class_sessions(
            id,
            subject,
            teacher,
            classroom,
            day,
            start_hour,
            end_hour
        )
    """)
    .eq("student_id", currentUser.id)
    .eq("class_sessions.day", currentDay)
Features:
  • Dynamic schedule per student
  • Semester-based enrollment
  • Schedule changes reflected in real-time
  • Support for multiple campuses
CREATE TABLE class_sessions (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  subject TEXT NOT NULL,
  teacher TEXT NOT NULL,
  classroom TEXT NOT NULL,
  day TEXT NOT NULL,
  start_hour TIME NOT NULL,
  end_hour TIME NOT NULL,
  semester TEXT NOT NULL,
  campus TEXT NOT NULL
);

CREATE TABLE enrollments (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  student_id UUID REFERENCES profiles(id),
  class_session_id UUID REFERENCES class_sessions(id),
  enrolled_at TIMESTAMPTZ DEFAULT NOW(),
  UNIQUE(student_id, class_session_id)
);

-- Students can only see their enrolled classes
CREATE POLICY "Students view enrolled classes"
  ON enrollments FOR SELECT
  USING (auth.uid() = student_id);

3. QR Code Attendance

Scan QR code to register attendance:
// After scanning QR code with class_session_id
val attendance = supabase.from("attendance")
    .insert(mapOf(
        "student_id" to currentUser.id,
        "class_session_id" to scannedClassId,
        "timestamp" to Clock.System.now(),
        "status" to "present"
    ))
Features:
  • QR code generation by teachers
  • Geofencing (must be on campus)
  • Time validation (only during class hours)
  • Attendance history
  • Attendance percentage per course
CREATE TABLE attendance (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  student_id UUID REFERENCES profiles(id),
  class_session_id UUID REFERENCES class_sessions(id),
  timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  status TEXT NOT NULL CHECK (status IN ('present', 'late', 'absent')),
  location GEOGRAPHY(POINT),
  UNIQUE(student_id, class_session_id, timestamp::DATE)
);

CREATE INDEX idx_attendance_student ON attendance(student_id);
CREATE INDEX idx_attendance_class ON attendance(class_session_id);

4. Campus Map Integration

Real-time building data with geolocation:
val buildings = supabase.from("buildings")
    .select()
    .eq("campus", "Zongolica")
    .order("name")

// Calculate distance from user's location
val userLocation = LocationServices.getFusedLocationProviderClient(context)
    .lastLocation.await()

buildings.forEach { building ->
    building.distance = calculateDistance(
        userLocation.latitude,
        userLocation.longitude,
        building.latitude,
        building.longitude
    )
}
Features:
  • GPS coordinates for each building
  • Distance calculation from user
  • Building photos and descriptions
  • Indoor navigation (future)
CREATE TABLE buildings (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  name TEXT NOT NULL,
  description TEXT,
  category TEXT NOT NULL,
  campus TEXT NOT NULL,
  latitude DECIMAL(10, 8),
  longitude DECIMAL(11, 8),
  photo_url TEXT
);

-- Public read access for buildings
CREATE POLICY "Buildings are viewable by everyone"
  ON buildings FOR SELECT
  USING (true);

5. Grades & Academic History

View grades and cumulative GPA:
val grades = supabase.from("grades")
    .select("""
        *,
        class_sessions(subject, teacher, semester)
    """)
    .eq("student_id", currentUser.id)
    .order("semester", ascending = false)

val gpa = grades.map { it.grade }.average()
Features:
  • Per-course grades
  • Semester GPA calculation
  • Cumulative GPA tracking
  • Grade history
CREATE TABLE grades (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  student_id UUID REFERENCES profiles(id),
  class_session_id UUID REFERENCES class_sessions(id),
  grade DECIMAL(5, 2) NOT NULL CHECK (grade >= 0 AND grade <= 100),
  semester TEXT NOT NULL,
  posted_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE POLICY "Students view own grades"
  ON grades FOR SELECT
  USING (auth.uid() = student_id);

Implementation Roadmap

1

Phase 1: Supabase Setup

  • Create Supabase project
  • Design database schema
  • Set up row-level security policies
  • Seed initial data (campuses, buildings, demo classes)
2

Phase 2: Authentication

  • Add Supabase Kotlin client dependency
  • Implement login screen
  • Create profile management
  • Add session persistence
3

Phase 3: Data Migration

  • Create repository layer
  • Replace FakeData with Supabase queries
  • Implement caching for offline support
  • Add loading states and error handling
4

Phase 4: New Features

  • Implement QR code scanner
  • Add attendance tracking
  • Build grades screen
  • Integrate real campus map
5

Phase 5: Real-time Updates

  • Add Supabase Realtime subscriptions
  • Push notifications for schedule changes
  • Live attendance updates
  • Real-time grade posting

Adding Supabase to the Project

When ready to begin integration:

1. Add Dependency

// app/build.gradle.kts
dependencies {
    // Existing dependencies...
    
    // Supabase
    implementation("io.github.jan-tennert.supabase:postgrest-kt:2.0.0")
    implementation("io.github.jan-tennert.supabase:realtime-kt:2.0.0")
    implementation("io.github.jan-tennert.supabase:storage-kt:2.0.0")
    implementation("io.github.jan-tennert.supabase:auth-kt:2.0.0")
    
    // Ktor client (required by Supabase)
    implementation("io.ktor:ktor-client-android:2.3.7")
}

2. Initialize Supabase Client

// app/src/main/java/com/example/appcontrolescolar/data/SupabaseClient.kt
package com.example.appcontrolescolar.data

import io.github.jan.supabase.createSupabaseClient
import io.github.jan.supabase.postgrest.Postgrest
import io.github.jan.supabase.auth.Auth
import io.github.jan.supabase.realtime.Realtime

object SupabaseClient {
    val client = createSupabaseClient(
        supabaseUrl = "https://your-project-ref.supabase.co",
        supabaseKey = "your-anon-key"
    ) {
        install(Postgrest)
        install(Auth)
        install(Realtime)
    }
}
Never commit your Supabase credentials to version control. Use local.properties or environment variables.

3. Create Repository Pattern

// app/src/main/java/com/example/appcontrolescolar/data/repository/StudentRepository.kt
package com.example.appcontrolescolar.data.repository

import com.example.appcontrolescolar.data.model.Student
import com.example.appcontrolescolar.data.model.ClassSession

interface StudentRepository {
    suspend fun getCurrentStudent(): Student
    suspend fun getTodayClasses(): List<ClassSession>
}

// Fake implementation (current)
class FakeStudentRepository : StudentRepository {
    override suspend fun getCurrentStudent() = FakeData.student
    override suspend fun getTodayClasses() = FakeData.todayClasses
}

// Real implementation (future)
class SupabaseStudentRepository : StudentRepository {
    override suspend fun getCurrentStudent(): Student {
        return SupabaseClient.client
            .from("profiles")
            .select()
            .eq("id", currentUserId)
            .single()
    }
    
    override suspend fun getTodayClasses(): List<ClassSession> {
        return SupabaseClient.client
            .from("enrollments")
            .select("class_sessions(*)")
            .eq("student_id", currentUserId)
    }
}

Testing Strategy

Option 1: Use Supabase StudioTest against your cloud Supabase project directly.Option 2: Self-hosted SupabaseRun Supabase locally with Docker:
# Clone Supabase
git clone --depth 1 https://github.com/supabase/supabase

# Start all services
cd supabase/docker
cp .env.example .env
docker-compose up
Then point the app to http://localhost:54321
class MockStudentRepository : StudentRepository {
    override suspend fun getCurrentStudent() = testStudent
    override suspend fun getTodayClasses() = testClasses
}

@Test
fun `ProfileScreen displays student info correctly`() {
    val mockRepo = MockStudentRepository()
    composeTestRule.setContent {
        ProfileScreen(repository = mockRepo)
    }
    
    composeTestRule.onNodeWithText("Arlyn Alfaro").assertExists()
}

Security Considerations

Critical security practices for production:
  • Enable Row-Level Security (RLS) on all tables
  • Use service role key only on backend (never in app)
  • Validate all user input server-side
  • Implement rate limiting on auth endpoints
  • Use HTTPS for all API requests (enforced by Supabase)

Row-Level Security Example

-- Students can only read their own data
CREATE POLICY "Students access own records"
  ON profiles
  FOR ALL
  USING (auth.uid() = id);

-- Students can only see their enrolled classes
CREATE POLICY "Students view enrolled classes"
  ON enrollments
  FOR SELECT
  USING (auth.uid() = student_id);

-- Students can only mark their own attendance
CREATE POLICY "Students mark own attendance"
  ON attendance
  FOR INSERT
  WITH CHECK (auth.uid() = student_id);

Resources

Supabase Docs

Official Supabase documentation

Kotlin Client

Supabase Kotlin library on GitHub

Authentication Guide

Implementing auth with Supabase

Row-Level Security

Securing your database with RLS

Next Steps

Supabase integration is a future enhancement. The current app works fully with FakeData for UI testing and development.

Fake Data Guide

Learn how test data currently works

Troubleshooting

Common issues and solutions

Build docs developers (and LLMs) love