Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/DavidCevallos15/inforario-IA-null/llms.txt

Use this file to discover all available pages before exploring further.

Inforario supports two distinct user modes: a fully anonymous guest mode that requires no account and stores everything locally, and an authenticated mode powered by Supabase Auth that enables cloud sync, profile management, and Google Calendar integration. Both modes share the same UI — the app detects which mode is active by validating whether the current deviceId is a proper UUID, and gates all database calls accordingly.

User Modes

Guest Mode

No account required. A deviceId is generated as dev-{random string} and persisted in localStorage under the key inforario_device_id. Schedules are parsed and displayed locally but never written to Supabase — all DB calls are gated behind a UUID validation check that rejects the dev- prefix.

Authenticated Mode

After sign-in, deviceId becomes the user’s UUID from the Supabase session. Schedules, the user profile, and Google Calendar tokens are all persisted in the database and available across devices.

UUID Gate

Every database operation in supabaseClient.ts runs the user ID through a UUID regex before touching Supabase:
const isUUID = (id: string): boolean => {
  const uuidRegex =
    /^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
  return uuidRegex.test(id);
};

// Example — saveScheduleToDB
if (!isSupabaseConfigured() || !isUUID(userId)) return null;
A guest user’s dev-{random string} ID will never match, so no accidental writes or 400 errors reach the database.

isSupabaseConfigured

Before any Supabase operation, the app checks whether valid credentials are present:
import { isSupabaseConfigured } from '@/services/supabase/supabaseClient';

if (!isSupabaseConfigured()) {
  // Fall back to local/demo mode
}
The function returns false if either of the following is true:
  • VITE_SUPABASE_URL or VITE_SUPABASE_ANON_KEY is missing from the environment
  • The URL contains the placeholder string "placeholder" (i.e., the default unconfigured value)
const isConfigured = !!(
  activeUrl &&
  activeKey &&
  !activeUrl.includes("placeholder")
);

export const isSupabaseConfigured = (): boolean => isConfigured && !!client;

Dummy Client

When Supabase is not configured, the module exports a dummy client object instead of crashing. Every method on the dummy client returns a resolved promise with a descriptive error payload, allowing the entire app to operate in offline-first mode without TypeError exceptions:
export const supabase = client || {
  from: () => createDummyBuilder(),   // full query-builder chain, returns { data: null, error }
  functions: {
    invoke: () => Promise.resolve({ data: null, error: { message: "Database not configured" } })
  },
  auth: {
    getSession:          () => Promise.resolve({ data: { session: null }, error: null }),
    getUser:             () => Promise.resolve({ data: { user: null }, error: null }),
    onAuthStateChange:   () => ({ data: { subscription: { unsubscribe: () => {} } }, error: null }),
    signInWithPassword:  () => Promise.resolve({ error: { message: "Database not configured" } }),
    signUp:              () => Promise.resolve({ error: { message: "Database not configured" } }),
    resetPasswordForEmail: () => Promise.resolve({ error: { message: "Database not configured" } }),
    signInWithOAuth:     () => Promise.resolve({ error: { message: "Database not configured" } }),
    signOut:             () => Promise.resolve({ error: null })
  }
};
The createDummyBuilder function additionally returns a chainable builder that supports every Supabase query modifier (.select, .eq, .order, .single, etc.) so that multi-step query chains do not throw.

Authentication Methods

Sign In

import { signInWithEmail } from '@/services/supabase/supabaseClient';

await signInWithEmail(email, password);
// Internally: supabase.auth.signInWithPassword({ email, password })

Sign Up

import { signUpWithEmail } from '@/services/supabase/supabaseClient';

await signUpWithEmail(email, password, fullName);
// Internally: supabase.auth.signUp({
//   email, password,
//   options: { emailRedirectTo: window.location.origin, data: { full_name: fullName } }
// })
After sign-up, Supabase sends a confirmation email. The user must click the link before the session becomes active.

Reset Password

import { resetPasswordForEmail } from '@/services/supabase/supabaseClient';

await resetPasswordForEmail(email);
// Internally: supabase.auth.resetPasswordForEmail(email, {
//   redirectTo: window.location.origin
// })

Sign-Up Flow

1

Open the Auth Modal

The user clicks Iniciar Sesión / Registrarse in the Inforario header. The AuthModal component opens with the Login tab active by default.
2

Switch to Register tab

The user clicks the Registrarse tab. A full name field appears in addition to email and password.
3

Enter credentials and validate password

As the user types their password, a live strength indicator validates four requirements in real time: 8+ characters, at least one uppercase letter, at least one lowercase letter, and at least one number. The Crear cuenta button is disabled until all four pass.
4

Submit the form

On submit, signUpWithEmail(email, password, fullName) is called. Supabase creates the account and sends a confirmation email with a link to window.location.origin.
5

Confirm email

The user clicks the confirmation link in their inbox. The Supabase session becomes active and the app’s onAuthStateChange listener updates the UI to authenticated mode.

Session Management

The app uses two Supabase Auth hooks at startup to hydrate and maintain the user session:
// On mount — restore an existing session
const { data: { session } } = await supabase.auth.getSession();

// Subscription — react to sign-in, sign-out, token refresh
const { data: { subscription } } = supabase.auth.onAuthStateChange(
  (event, session) => {
    // Update app-level auth state
  }
);

// Cleanup
return () => subscription.unsubscribe();
onAuthStateChange fires for every auth event: SIGNED_IN, SIGNED_OUT, TOKEN_REFRESHED, PASSWORD_RECOVERY, and USER_UPDATED. Inforario uses this callback to update the deviceId and trigger a re-fetch of the user’s cloud schedules.

getUserProfile

Reads the authenticated user’s profile row from the profiles table.
import { getUserProfile } from '@/services/supabase/supabaseClient';

const profile = await getUserProfile(userId);
// Returns UserProfile | null
userId
string
required
The user’s UUID from the Supabase session. The function returns null immediately if the ID is not a valid UUID (i.e., for guest users).
Returns a UserProfile object:
export interface UserProfile {
  id: string;
  email: string;
  full_name?: string;
  avatar_url?: string;
  career?: string;
}

Database Tables

profiles

One row per authenticated user. Contains id (UUID matching the Supabase Auth user), email, full_name, avatar_url, and career. Read by getUserProfile.

schedules

Stores parsed timetables. Each row has a user_id foreign key (UUID) and a schedule_data JSONB column holding the ClassSession[] array. Guest users never write here.

user_calendar_tokens

Stores Google OAuth2 tokens per user. Requires a valid authenticated session — the Google Calendar sync Edge Function rejects requests without a row in this table.

Environment Variables

# .env.local
VITE_SUPABASE_URL=https://your-project-ref.supabase.co
VITE_SUPABASE_ANON_KEY=your-anon-key
Both variables are read at module initialization time. If either is absent or the URL contains "placeholder", the app switches to dummy-client mode and all users are treated as guests with local-only access.
You can run Inforario entirely without Supabase credentials for development or demonstration purposes. The dummy client prevents any crashes — schedule parsing, display, and ICS export all work without a database connection. Only cloud save, profile, and Google Calendar sync are unavailable.

Auth Modal Views

The AuthModal component (components/modals/AuthModal.tsx) exposes three views selectable at runtime:
ViewTriggerAction
LOGINDefault on opensignInWithEmail
REGISTERClick Registrarse tabsignUpWithEmail + validation
FORGOT_PASSWORDClick ¿Olvidaste tu contraseña?resetPasswordForEmail
All three views share the same form onSubmit handler, which checks isSupabaseConfigured() first. If Supabase is not configured, the modal simulates a successful login after a 1-second delay — useful for previewing the app without credentials.

Build docs developers (and LLMs) love