Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/ElthonJohan/comunitea/llms.txt

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

ComuniTEA uses AI in three distinct ways: OpenAI GPT transforms a child’s selected pictogram labels into natural spoken Spanish sentences; ElevenLabs generates neural voice audio clips with child-appropriate tone and rhythm; and a client-side analysis engine surfaces actionable recommendations to caregivers through the ai-insights Edge Function. All AI features are optional and degrade gracefully — the app remains fully functional when offline or when API keys are not configured, falling back to system TTS and disabling the Magic Expand button. Every AI call requires Supabase environment variables and a valid authenticated session.
All AI features require the following environment variables set in your Supabase project secrets. Without them the corresponding Edge Functions return 500 errors and the app shows an alert instead of AI output:
  • OPENAI_API_KEY — required by ai-expand and ai-insights
  • ELEVENLABS_API_KEY — required by elevenlabs-tts

ai-expand Edge Function

Purpose: Accepts an ordered list of pictogram labels selected by the child and returns a single natural-language sentence in Spanish, adapted to the child’s vocabulary level. Endpoint: POST /functions/v1/ai-expand

Request body

{
  pictogramLabels: string[];                              // Labels in selection order
  vocabularyLevel?: "BASICO" | "INTERMEDIO" | "AVANZADO"; // Defaults to "INTERMEDIO"
  timeOfDay?: "mañana" | "tarde" | "noche";               // Optional context
  lastRoutine?: string;                                    // Label of last completed routine
  phraseIntent?: "formador" | "tablero";                   // Optional style modifier
}
pictogramLabels
string[]
required
The pictogram labels in the order the child selected them (e.g. ["Yo", "Quiero", "Manzana"]). At least one label is required.
vocabularyLevel
"BASICO" | "INTERMEDIO" | "AVANZADO"
Controls sentence complexity. BASICO produces 3–6 word sentences; INTERMEDIO 5–10 words; AVANZADO up to 15 words.
timeOfDay
"mañana" | "tarde" | "noche"
Injected as context into the OpenAI prompt (e.g. “Hora del día: mañana”). Helps the model produce contextually appropriate sentences.
lastRoutine
string
The label of the most recently completed routine (e.g. "Desayuno"). Provides additional context for the expanded sentence.
phraseIntent
"formador" | "tablero"
Switches the prompt style. "formador" optimizes for “Yo quiero + object” patterns. "tablero" optimizes for multi-slot sentence strip output with natural connectors. Omit for default behavior.

Response body

{ expandedText: string }
expandedText
string
A single natural Spanish sentence. For example, given ["Yo", "Quiero", "Manzana"] at BASICO level: "Quiero una manzana.".

Error responses

StatusBodyCause
400{ error: "Se requiere al menos un pictograma" }Empty pictogramLabels array
405{ error: "Método no permitido" }Non-POST request
500{ error: "OPENAI_API_KEY no configurada" }Missing secret
502{ error: "OpenAI respondió 4xx/5xx", detail: "..." }OpenAI API error

OpenAI model and parameters

// Model configured via OPENAI_FRASE_MODEL secret (default: "gpt-5.4-nano")
model: OPENAI_FRASE_MODEL
temperature: 0.35
max_tokens: phraseIntent === "tablero" ? 90 : 70

How useMagicExpand invokes it

The useMagicExpand hook (client-side) constructs the request and handles session token refresh:
// From features/vocabulario/hooks/useMagicExpand.ts

const labels = sentence.map(item => item.speechText || item.label);
const timeOfDay = getTimeOfDay();       // "mañana" | "tarde" | "noche"
const lastRoutine = rutinas.filter(r => r.completed).slice(-1)[0]?.label;

// Refresh session token if expired
let { data: { session } } = await supabase.auth.getSession();
if (!session?.access_token) {
  const { data: refreshed } = await supabase.auth.refreshSession();
  session = refreshed.session;
}

const res = await fetch(`${supabaseUrl}/functions/v1/ai-expand`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${session.access_token}`,
  },
  body: JSON.stringify({
    pictogramLabels: labels,
    vocabularyLevel: profile?.level ?? 'INTERMEDIO',
    timeOfDay,
    lastRoutine,
  }),
});

const { expandedText } = await res.json();
if (expandedText) {
  await speakFreeText(expandedText);
  logEvent('ai_expand', { latencyMs: Date.now() - startedAt, sentenceLength: sentence.length });
  logSentence(sentence.map(s => s.id));
}
The latency of the ai-expand call is logged via logEvent('ai_expand', { latencyMs }) so you can track performance in the usage_stats table.

ai-insights Edge Function

Purpose: Analyzes the child’s usage patterns and returns 2–3 actionable, warmly-worded recommendations in Spanish for caregivers and therapists. Endpoint: POST /functions/v1/ai-insights

Request body

{
  vocabularyLevel: "BASICO" | "INTERMEDIO" | "AVANZADO";
  sublevel: number;                   // Current game sub-level (1–5)
  avgSentenceLength: number;          // Average pictogram count per sentence (last 30 days)
  totalSentences: number;             // Total sentences logged in last 30 days
  topPictogramLabels: string[];       // Top 5 most-used pictogram labels
  unusedCategoryLabels: string[];     // Category labels with no use in >14 days
}
vocabularyLevel
"BASICO" | "INTERMEDIO" | "AVANZADO"
required
The child’s vocabulary level from their profile.
sublevel
number
required
Current PECS game sub-level (1–5). Included in the prompt to give context about game progression.
avgSentenceLength
number
required
Mean pictogram count per sentence over the last 30 days. The AI uses this to suggest expanding vocabulary if the average is high.
totalSentences
number
required
Total sentences in sentence_log over the last 30 days.
topPictogramLabels
string[]
required
The 5 most-used pictogram labels (human-readable). Included in the prompt for context.
unusedCategoryLabels
string[]
required
Labels of vocabulary categories not used in more than 14 days. The AI may suggest practicing these.

Response body

{ suggestions: string[] }
suggestions
string[]
Array of 1–3 actionable suggestion strings in Spanish, second-person, positive tone. Example: ["Puedes practicar la categoría Baño esta semana.", "El niño está construyendo frases más largas — considera ampliar el vocabulario."]

OpenAI model and parameters

model: "gpt-4o-mini"
temperature: 0.7
max_tokens: 300

System prompt behavior

The function uses a fixed system prompt instructing GPT to respond exclusively with a valid JSON array of 3 strings. If the model returns malformed JSON, the function falls back to wrapping the raw text in a single-item array. Empty strings are filtered out and the result is capped at 3 items.
// System prompt excerpt:
// "Respond ONLY with a JSON array of 3 strings. No additional explanations. Example:
// ["Suggestion 1.", "Suggestion 2.", "Suggestion 3."]"

elevenlabs-tts Edge Function

Purpose: Proxies text-to-speech requests to the ElevenLabs API, returning an MP3 audio buffer. Acts as a secure server-side proxy so the ElevenLabs API key is never exposed to the client. Endpoint: POST /functions/v1/elevenlabs-tts

Request body

{
  text: string;      // Text to synthesize (non-empty)
  voiceId?: string;  // ElevenLabs voice ID — defaults to the "Samanta" feminine voice
  speed?: number;    // Playback speed multiplier — clamped to [0.5, 2.0], defaults to 1.0
}
text
string
required
The text to synthesize. Must be a non-empty string.
voiceId
string
ElevenLabs voice ID. Defaults to qBvury71WUJfVeT1STkG (Samanta, feminine voice). The app passes the masculine or feminine voice ID based on the user’s voice preference.
speed
number
Synthesis speed multiplier. Clamped to [0.5, 2.0]. Sourced from parental tts_speed setting (default 1.0).

Response

On success, returns the raw MP3 audio as a binary blob:
Content-Type: audio/mpeg
Body: <MP3 audio buffer>
On error, returns JSON:
{ error: string }

ElevenLabs model settings

{
  model_id: "eleven_multilingual_v2",
  voice_settings: {
    stability: 0.5,
    similarity_boost: 0.75,
    style: 0.0,
    use_speaker_boost: true,
    speed: ttsSpeed,    // From request body
  }
}

How useSpeech invokes it

// From features/vocabulario/hooks/useSpeech.ts

const { data: { session } } = await supabase.auth.getSession();
const token = session?.access_token || process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY;
const edgeUrl = `${process.env.EXPO_PUBLIC_SUPABASE_URL}/functions/v1/elevenlabs-tts`;

const response = await fetch(edgeUrl, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${token}`,
  },
  body: JSON.stringify({ text, voiceId, speed: ttsSpeed }),
});

if (!response.ok) {
  // Falls through to expo-speech system TTS
  return new Promise((resolve) => fallbackSpeech(text, resolve));
}

// Write MP3 to a temp file, play with expo-audio
const arrayBuffer = await response.arrayBuffer();
const base64 = btoa(String.fromCharCode(...new Uint8Array(arrayBuffer)));
const fileUri = FileSystem.documentDirectory + `temp_speech_${Date.now()}.mp3`;
await FileSystem.writeAsStringAsync(fileUri, base64, { encoding: FileSystem.EncodingType.Base64 });

const newSound = createAudioPlayer(normalizeFileUriForAudio(fileUri));
await playAudioPlayerUntilDone(newSound, 120_000);
FileSystem.deleteAsync(fileUri, { idempotent: true }).catch(() => {});

TTS fallback chain

1. Bundled MP3 clip (getAudioAssets — fastest, offline-safe)
      ↓ no clip found
2. elevenlabs-tts Edge Function (neural voice)
      ↓ no network OR HTTP error OR ELEVENLABS_API_KEY missing
3. expo-speech (system TTS, es-MX, pitch adjusted per voice profile)
When isConnected === false, step 2 is skipped entirely — the app jumps directly to expo-speech without attempting any network call. This keeps pictogram taps responsive even on offline field deployments.

delete-user-data Edge Function

Purpose: Implements the right to erasure (GDPR Article 17 / COPPA). Permanently and irreversibly deletes all data associated with the authenticated user. Endpoint: POST /functions/v1/delete-user-data Authentication: Requires a valid user JWT in the Authorization: Bearer <token> header. Uses a service_role admin client internally to perform privileged deletions.

Deletion sequence

The function executes the following steps in order:
1

Storage files

Lists and removes all files in the avatars/{userId}/ and pictograms/{userId}/ Storage buckets.
2

Database rows

Deletes user_id-keyed rows from every data table: sentence_log, usage_stats, config_audit_log, ai_insights, game_sessions, game_progress, activity_sessions, guided_activities, parental_settings, child_team, share_tokens, child_profiles, tasks, custom_pictograms, categories, profiles
3

Auth account

Calls supabase.auth.admin.deleteUser(userId) to permanently remove the authentication record. This is irreversible.

Response

// Success
{ success: true }

// Error
{ error: "Failed to delete user data" }  // HTTP 500
{ error: "Unauthorized" }                 // HTTP 401

Required secrets

SecretPurpose
SUPABASE_URLProject URL
SUPABASE_ANON_KEYFor verifying the user JWT
SUPABASE_SERVICE_ROLE_KEYFor admin-level deletion operations
Calling delete-user-data is irreversible. Once the auth account is deleted the user cannot be recovered. Present the user with a confirmation dialog before invoking this endpoint. Consider logging the deletion request in your own audit system before making the call.

Offline Queue

When the device is offline, telemetry events and sentence log entries that would normally be written directly to Supabase are instead queued in AsyncStorage by lib/offlineQueue.ts. The queue is flushed automatically when NetworkContext detects reconnection.
// lib/offlineQueue.ts

export type QueuedEvent =
  | {
      type: 'stat';
      user_id: string;
      event_type: string;    // e.g. "ai_expand", "pictogram_tap"
      created_at: string;    // ISO timestamp captured at event time
      category_id?: string;
      sentence_length?: number;
      response_latency_ms?: number;
    }
  | {
      type: 'sentence';
      user_id: string;
      pictogram_ids: string[];
      created_at: string;
    };

/** Appends an event to the AsyncStorage queue. Fire-and-forget safe. */
export async function enqueueEvent(event: QueuedEvent): Promise<void>;

/** Flushes all pending events to Supabase. Clears the queue only on full success. */
export async function flushQueue(): Promise<void>;

Flush behavior

flushQueue() partitions the queue by type, runs two parallel supabase.insert() calls (usage_stats and sentence_log) wrapped in Promise.allSettled, and only clears AsyncStorage if both succeed. Failed batches remain queued and are retried on the next flush trigger.
const results = await Promise.allSettled([
  stats.length > 0
    ? supabase.from('usage_stats').insert(stats)
    : Promise.resolve(),
  sentences.length > 0
    ? supabase.from('sentence_log').insert(sentences)
    : Promise.resolve(),
]);

if (results.every(r => r.status === 'fulfilled')) {
  await AsyncStorage.removeItem(QUEUE_KEY);
}

Audit Log

lib/auditLog.ts provides a best-effort audit trail for configuration changes. It records who changed what to the config_audit_log table. Errors are silently suppressed so that audit failures never interrupt the user-facing flow.
// lib/auditLog.ts

export async function auditLog(
  userId: string,
  field: string,    // The setting or action name (e.g. "vocabulary_pictogram_toggle")
  oldValue: string, // Previous value as a string
  newValue: string, // New value as a string
): Promise<void>

What gets logged

Actionfield valueTriggered by
Pictogram hidden/shownvocabulary_pictogram_toggleuseDisabledPictograms.toggle()
Team member invitedchild_team_inviteuseChildTeam.inviteMember()
Team member revokedchild_team_revokeuseChildTeam.revokeMember()
Team permission changechild_team_perm_can_view_reports / child_team_perm_can_edit_vocabularyuseChildTeam.updatePermissions()

Schema

config_audit_log (
  id          uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id     uuid REFERENCES auth.users,
  field_name  text,
  old_value   text,
  new_value   text,
  created_at  timestamptz DEFAULT now()
)

Usage example

import { auditLog } from '@/lib/auditLog';

// Inside a mutation function:
await auditLog(
  user.id,
  'vocabulary_pictogram_toggle',
  'enabled',   // old value
  'disabled',  // new value
);
The auditLog function is async but its errors are fully suppressed. You do not need to await it in UI-critical code paths — fire and forget is safe and idiomatic here.

AI feature summary

ai-expand

OpenAI GPT converts selected pictogram labels into a single natural Spanish sentence. Adapts complexity to the child’s vocabulary level. Invoked by useMagicExpand. Requires OPENAI_API_KEY.

ai-insights

OpenAI GPT analyzes 30-day usage data and returns 3 actionable recommendations for caregivers. Invoked from the caregiver report screen. Requires OPENAI_API_KEY.

elevenlabs-tts

ElevenLabs neural TTS proxied through a Supabase Edge Function. Returns MP3 audio for any text. Falls back to expo-speech on error or offline. Requires ELEVENLABS_API_KEY.

delete-user-data

GDPR right to erasure Edge Function. Permanently deletes all user storage files, database rows, and the auth account. Irreversible. Requires SUPABASE_SERVICE_ROLE_KEY.

Build docs developers (and LLMs) love