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.

The ejercicios (exercises) module delivers ComuniTEA’s structured PECS learning path. Children are grouped into one of three tiers based on their vocabulary level, and each group follows its own multi-level exercise road map. Progress is stored locally in AsyncStorage via two Zustand persist stores — useG2ProgressStore and useG3ProgressStore — which are surfaced to components through the useEjerciciosG2 and useEjerciciosG3 selector hooks. The useUserGroup hook maps the authenticated user’s VocabLevel to a group tier and provides board layout constants used throughout the tablero and exercise screens.

useUserGroup

useUserGroup reads profile.level from the auth context and returns a normalized group designation along with UI layout constants. It is used by both the tablero (to limit visible subcategories for basic users) and exercise screens (to decide which exercise road map to show).
export type UserGroupLevel = 'basic' | 'intermediate' | 'advanced';

export function useUserGroup(): {
  nivel: UserGroupLevel;
  pictoWidth: number;
  pictoHeight: number;
  minTouchSize: number;
  maxSubcategoriasVisibles: number;
}

VocabLevelUserGroupLevel mapping

profile.level (DB value)UserGroupLevelExercise group
"BASICO"'basic'G3 (severe)
"INTERMEDIO"'intermediate'G2 (moderate)
"AVANZADO"'advanced'G2 (moderate/advanced)

Return shape

nivel
UserGroupLevel
Normalized group string. Drive exercise routing with this value.
pictoWidth
number
Recommended pictogram tile width in dp. 62 for basic, 54 for intermediate/advanced.
pictoHeight
number
Recommended pictogram tile height in dp. 58 for basic, 50 for intermediate/advanced.
minTouchSize
number
Minimum touch target size (always 48 dp) — fulfills accessibility guidelines.
maxSubcategoriasVisibles
number
4 for basic users (fewer choices = less cognitive load), 999 (unlimited) for intermediate/advanced.

Usage example

import { useUserGroup } from '@/features/ejercicios/hooks/useUserGroup';

export function ExercisesRouter() {
  const { nivel } = useUserGroup();

  if (nivel === 'basic') return <EjerciciosG3Screen />;
  return <EjerciciosG2Screen />;
}

useEjerciciosG2

useEjerciciosG2 is a Zustand selector hook that exposes the full G2 progress state and all mutation actions from useG2ProgressStore. It uses useShallow for stability — only re-renders when a selected field actually changes.
import { useShallow } from 'zustand/react/shallow';
import { useG2ProgressStore } from '@/stores/g2ProgressStore';

export function useEjerciciosG2(): EjerciciosG2State & G2Actions

State shape

completados
string[]
Array of completed exercise keys in the form "nivel-ejercicioIndex" (e.g. "2-1", "3-E2"). An exercise key is generated by g2ExerciseKey(nivel, ejercicioIndex).
fallosPorEjercicio
Record<string, number>
Maps exercise key → number of wrong answers. Used to compute the star rating when the exercise is completed.
estrellasPorNivel
Record<number, 1 | 2 | 3>
Stars earned per level (1–5). Set when the last exercise in a level is completed.
emocionesRegistradas
string[]
Rolling log of emotion IDs recorded during Level 2 exercises (max 30 entries).
fallosPorSubEjercicio
Record<string, number>
Fine-grained fail counter for sub-exercise slots (Levels 2–3 only). Key format: "nivel-E1", "nivel-E2", "nivel-E3".
estrellasPorEjercicio
Record<string, 1 | 2 | 3>
Stars per sub-exercise slot. Key matches g2SubExerciseKey(nivel, sub).
ayudasRegistradas
AyudaRegistroG2[]
Rolling log of “ask for help” registrations from Level 3 exercises (max 40 entries). Each entry is { situacion: string, persona: string }.
hydrated
boolean
true after the Zustand persist middleware has finished reading from AsyncStorage. Gate all progress rendering behind this flag.

Query methods

isExerciseDone
(nivel: number, ejIndex: number) => boolean
Returns true if the exercise at the given level and index is in completados.
isLevelComplete
(nivel: number) => boolean
Returns true when all G2_EJERCICIOS_POR_NIVEL (6) exercise slots for the level are completed.
isLevelUnlocked
(nivel: number) => boolean
Level 1 is always unlocked. Level N is unlocked when level N−1 is complete.
nivelBadgeState
(nivel: number) => 'bloqueado' | 'disponible' | 'completado'
Convenience method for rendering the level badge/thumbnail state on the road-map screen.
firstIncompleteIndex
(nivel: number) => number
Returns the 0-based index of the first incomplete exercise in the level. Used to resume from where the child left off.
estrellasNivel
(nivel: number) => 0 | 1 | 2 | 3
Returns 0 if the level is not yet complete, otherwise the stored star count (1–3).
estrellasSubEjercicio
(nivel: 2 | 3, sub: G2SubEjercicio) => 1 | 2 | 3 | null
Returns the star count for a specific sub-exercise, or null if it hasn’t been completed yet.

Mutation actions

registerFail
(nivel: number, ejIndex: number) => Promise<void>
Increments the fail counter for the exercise and fires the gamification bridge (wrong_attempt event).
registerSubFail
(nivel: 2 | 3, sub: G2SubEjercicio) => Promise<void>
Increments the sub-exercise fail counter for levels 2 and 3.
completeSubExercise
(nivel: 2 | 3, sub: G2SubEjercicio) => Promise<void>
Marks a sub-exercise as complete, computes its stars from accumulated fails, and — for the third sub-exercise (E3) — computes the level stars as the minimum of E1/E2/E3 stars.
completeExercise
(nivel: number, ejIndex: number) => Promise<void>
Marks a regular exercise complete. When the last exercise in the level is completed, computes and stores the level star count.
registerEmocion
(emotionId: string) => Promise<void>
Appends an emotion ID to emocionesRegistradas (Level 2 activities).
registerAyudaRegistro
(situacion: string, persona: string) => Promise<void>
Appends a { situacion, persona } record to ayudasRegistradas (Level 3 activities).
resetAllProgress
() => void
Resets all G2 state to defaults. Useful for testing or when starting over.

Level structure

LevelNameEmojiExercises
1Reconocer y Pedir👋6 slots (E1–E6)
2Mis emociones💜6 slots (E1–E3 as sub-exercises)
3Pedir ayuda🙋6 slots (E1–E3 as sub-exercises)
4Las Categorías🗂️6 slots
5Turno y Respuesta💬6 slots

Usage example

import { useEjerciciosG2 } from '@/features/ejercicios/hooks/useEjerciciosG2';

export function G2LevelMap() {
  const {
    hydrated,
    isLevelUnlocked,
    isLevelComplete,
    nivelBadgeState,
    estrellasNivel,
    firstIncompleteIndex,
  } = useEjerciciosG2();

  if (!hydrated) return <LoadingSpinner />;

  return [1, 2, 3, 4, 5].map(nivel => (
    <LevelNode
      key={nivel}
      badge={nivelBadgeState(nivel)}
      stars={estrellasNivel(nivel)}
      resumeIndex={isLevelUnlocked(nivel) ? firstIncompleteIndex(nivel) : -1}
    />
  ));
}

useEjerciciosG3

useEjerciciosG3 mirrors useEjerciciosG2 but targets Group 3 (severe TEA / basic level). The G3 path has 5 levels with exactly 3 exercises each (no sub-exercise tabs). It also exposes a caminoCompleto flag and a segmentBetweenCompleted helper for rendering the connecting path segments on the road-map UI.
import { useShallow } from 'zustand/react/shallow';
import { useG3ProgressStore } from '@/stores/g3ProgressStore';

export function useEjerciciosG3(): EjercicioG3State & G3Actions

State shape

completados
string[]
Completed exercise keys in the form "nivel-ejercicioIndex" (e.g. "1-1", "3-2"). Generated by g3ExerciseKey(nivel, ejercicioIndex).
fallosPorEjercicio
Record<string, number>
Maps exercise key → number of wrong answers.
estrellasPorNivel
Record<number, 1 | 2 | 3>
Stars earned per level. Computed when the 3rd exercise (index 2) in a level is completed.
caminoCompleto
boolean
true when all 5 levels are fully completed. Persisted separately in AsyncStorage under STORAGE_KEYS.EJERCICIOS_G3_COMPLETADOS.
hydrated
boolean
true after AsyncStorage rehydration is complete.

Query methods

isExerciseDone
(nivel: number, ejIndex: number) => boolean
Returns true if the exercise is in completados.
isLevelComplete
(nivel: number) => boolean
Returns true when all 3 exercises (indices 0, 1, 2) for the level are completed.
isLevelUnlocked
(nivel: number) => boolean
Level 1 is always unlocked. Level N requires level N−1 to be complete.
nivelBadgeState
(nivel: number) => 'bloqueado' | 'disponible' | 'completado'
Road-map badge state.
firstIncompleteIndex
(nivel: number) => number
Returns the 0-based index of the first incomplete exercise in the level (0, 1, or 2). Returns 0 when all exercises are done. Used to resume from where the child left off.
segmentBetweenCompleted
(fromNivel: number, toNivel: number) => boolean
Returns true when fromNivel is complete AND fromNivel < toNivel. Used to render the connecting path segment between two level nodes as “completed” (colored vs. grey).
estrellasNivel
(nivel: number) => 0 | 1 | 2 | 3
Returns 0 for incomplete levels, otherwise 1–3.

Mutation actions

registerFail
(nivel: number, ejIndex: number) => Promise<void>
Increments fallosPorEjercicio[key] and fires the wrong_attempt gamification event.
completeExercise
(nivel: number, ejIndex: number) => Promise<void>
Marks the exercise done. If it is the final exercise in the level (index 2), computes and stores the level star count from total fails across all 3 exercises.
setCaminoCompleto
() => Promise<void>
Marks the entire G3 path as complete and persists the flag to AsyncStorage.
resetAllProgress
() => Promise<void>
Clears EJERCICIOS_G3_COMPLETADOS from AsyncStorage and resets all store state to defaults.

Level structure

LevelNameEmojiExercises
1El Despertar☀️3
2La Elección🔀3
3Mis Necesidades💭3
4Pedir Ayuda🤝3
5Mi Primera Frase3

Usage example

import { useEjerciciosG3 } from '@/features/ejercicios/hooks/useEjerciciosG3';

export function G3ExerciseScreen({ nivel, ejIndex }) {
  const { registerFail, completeExercise, estrellasNivel } = useEjerciciosG3();

  const handleCorrect = async () => {
    await completeExercise(nivel, ejIndex);
    const stars = estrellasNivel(nivel);
    // Show star result if this was the last exercise in the level
  };

  const handleWrong = () => registerFail(nivel, ejIndex);

  return <ExerciseContent onCorrect={handleCorrect} onWrong={handleWrong} />;
}

Progress stores (Zustand + AsyncStorage)

Both G2 and G3 exercise stores use Zustand’s persist middleware with a custom AsyncStorage adapter. Progress is local only — it is not synced to Supabase in the current version.

G2 store key

// AsyncStorage key for G2 progress
STORAGE_KEYS.EJERCICIOS_G2   // → '@ejercicios_g2'

G3 store key

// AsyncStorage key for G3 progress
STORAGE_KEYS.EJERCICIOS_G3          // → '@ejercicios_g3'
STORAGE_KEYS.EJERCICIOS_G3_COMPLETADOS  // → '@ejercicios_g3_completados'

Reading progress outside React (async helpers)

For non-component contexts (e.g. report generation, background jobs), two async helpers read G2 data directly from AsyncStorage without mounting the store:
import {
  loadG2EmocionesRegistradas,
  loadG2AyudasRegistradas,
} from '@/features/ejercicios/hooks/useEjerciciosG2';

// Returns string[] of emotion IDs
const emociones = await loadG2EmocionesRegistradas();

// Returns AyudaRegistroG2[] of help requests
const ayudas = await loadG2AyudasRegistradas();

Resetting all progress

// G2
const { resetAllProgress } = useEjerciciosG2();
resetAllProgress(); // synchronous — clears completados, fallos, estrellas, emociones, ayudas

// G3
const { resetAllProgress } = useEjerciciosG3();
await resetAllProgress(); // async — also removes EJERCICIOS_G3_COMPLETADOS from AsyncStorage

Star system

Both G2 and G3 use the same star-awarding formula based on the total number of wrong answers accumulated during the exercise (or level, for G2).
// From features/ejercicios/data/grupo2.ts
export function starsFromFailsG2(totalFails: number): 1 | 2 | 3 {
  if (totalFails === 0) return 3;   // ★★★ — no errors
  if (totalFails <= 2) return 2;    // ★★☆ — 1–2 errors
  return 1;                          // ★☆☆ — 3 or more errors
}

// From features/ejercicios/data/grupo3.ts (identical logic)
export function starsFromFails(totalFails: number): 1 | 2 | 3 {
  if (totalFails === 0) return 3;
  if (totalFails <= 2) return 2;
  return 1;
}
ErrorsStarsDescription
0★★★Perfect — no mistakes
1–2★★☆Good — a few mistakes
3+★☆☆Completed — many mistakes
For G2 levels that use sub-exercises (Levels 2 and 3), the level star count is the minimum of the three sub-exercise star counts: min(stars_E1, stars_E2, stars_E3). This ensures the level rating reflects the weakest sub-exercise, not the average.
Exercise progress is stored locally in AsyncStorage and is not synchronized to Supabase in the current version of ComuniTEA. Progress is per-device. Clearing app data or uninstalling the app will erase all exercise progress. A cloud sync feature is planned for a future release.

Exercise data reference

G2 exercise data (features/ejercicios/data/grupo2.ts)

The G2 exercise data module exports typed constants for each level and exercise:
// Level 1 — Recognize and Request
export const G2_N1_E1: { opciones: [G2Opt, G2Opt]; instruccion: string }
export const G2_N1_E2: { opciones: G2Opt[]; correctoId: string; instruccion: string; pista: string }
export const G2_N1_E3: { opciones: G2Opt[]; correctoId: string; instruccion: string; pista: string }

// Level 4 — Categories
export const G2_N4_E1  // Two-phase: category selection → pictogram selection
export const G2_N4_E2
export const G2_N4_E3  // Free-form tablero practice

// Level 5 — Turn and Response
export const G2_N5_E1  // "What do you want for a snack?"
export const G2_N5_E2  // "How are you today?"
export const G2_N5_E3  // "What do you want to do now?"

export type G2Opt = { id: string; emoji: string; label: string }

G3 exercise data (features/ejercicios/data/grupo3.ts)

// Level 1 — El Despertar (tap what you see)
export const G3_NIVEL_1: G3L1[]

// Level 2 — La Elección (two-choice matching)
export const G3_NIVEL_2: G3L2[]

// Level 3 — Mis Necesidades (left/right two-choice)
export const G3_NIVEL_3: G3L3[]

// Level 4 — Pedir Ayuda (situational need cards)
export const G3_NIVEL_4: G3L4[]   // needs: 'duele' | 'agua' | 'ayuda'

// Level 5 — Mi Primera Frase (build "Yo quiero X")
export const G3_NIVEL_5: G3L5[]

export type G3Side = 'left' | 'right'
export type G3NeedKind = 'duele' | 'agua' | 'ayuda'

Group 2 (Moderate TEA)

5 levels × 6 exercises = 30 total exercise slots. Features emotion tracking (Level 2) and help-request logging (Level 3). Stars calculated per sub-exercise and aggregated by level minimum.

Group 3 (Severe TEA)

5 levels × 3 exercises = 15 total exercise slots. Simpler one-or-two-choice interactions. Stars calculated from total level fails. Includes a caminoCompleto milestone when all levels are finished.

Build docs developers (and LLMs) love