# Database Cheatsheet - GAMILIT Platform

**Versión:** 1.0
**Última actualización:** 2025-11-07
**Database:** PostgreSQL 15+
**Timezone:** America/Mexico_City

> **Nota:** Este es un cheatsheet rápido. Para esquema completo ver [docs/03-desarrollo/base-de-datos/ESQUEMA-COMPLETO.md](../03-desarrollo/base-de-datos/ESQUEMA-COMPLETO.md)

---

## 🔌 Conexión Rápida

### Desarrollo Local

```bash
# Conectar a database
psql -U gamilit_user -d gamilit_platform -h localhost

# Conectar con password inline (no recomendado en producción)
PGPASSWORD=gamilit_dev_2025 psql -U gamilit_user -d gamilit_platform -h localhost
```

### Variables de Entorno

```bash
export PGHOST=localhost
export PGPORT=5432
export PGDATABASE=gamilit_platform
export PGUSER=gamilit_user
export PGPASSWORD=gamilit_dev_2025

# Ahora solo:
psql
```

---

## 📊 Esquema General

### Resumen

- **Schemas:** 9
- **Tablas:** 44
- **Funciones:** 50+
- **Triggers:** 35+
- **Índices:** 279+
- **RLS Policies:** 159 (41 activas)

### Schemas Principales

| Schema | Tablas | Propósito |
|--------|--------|-----------|
| **auth_management** | 12 | Autenticación, usuarios, sesiones, roles |
| **educational_content** | 8 | Módulos, ejercicios, contenido educativo |
| **gamification_system** | 10 | ML Coins, rangos, achievements, power-ups |
| **progress_tracking** | 6 | Progreso, intentos, sesiones de aprendizaje |
| **social_features** | 7 | Escuelas, aulas, equipos, amistades |
| **content_management** | 4 | Media files, Marie Curie content |
| **audit_logging** | 2 | Logs de auditoría, eventos de seguridad |
| **system_configuration** | 1 | Configuración del sistema |
| **public** | 2 | Schema público |

---

## 🗂️ Comandos Útiles de psql

### Navegación

```sql
-- Listar schemas
\dn

-- Listar tablas de un schema
\dt auth_management.*

-- Listar todas las tablas
\dt *.*

-- Describir tabla
\d auth_management.profiles

-- Listar funciones
\df

-- Listar triggers de una tabla
\dy auth_management.profiles

-- Listar índices de una tabla
\di auth_management.profiles

-- Ver tamaño de tablas
\dt+ *.*

-- Ayuda
\?

-- Salir
\q
```

### Configuración

```sql
-- Mostrar configuración actual
\set

-- Timing de queries
\timing on

-- Formato expandido (mejor para filas anchas)
\x auto

-- Paginación
\pset pager off
```

---

## 🔍 Queries Comunes por Caso de Uso

### 1. Autenticación

```sql
-- Buscar usuario por email
SELECT id, email, full_name, role, status
FROM auth_management.profiles
WHERE email = 'student@example.com';

-- Verificar sesiones activas de un usuario
SELECT session_token, device_type, ip_address, expires_at, created_at
FROM auth_management.user_sessions
WHERE user_id = 'user-uuid'
  AND expires_at > NOW()
ORDER BY created_at DESC;

-- Contar intentos de login fallidos recientes
SELECT COUNT(*)
FROM auth_management.auth_attempts
WHERE user_id = 'user-uuid'
  AND success = false
  AND created_at > NOW() - INTERVAL '15 minutes';

-- Listar usuarios por rol
SELECT id, email, full_name, role, status, created_at
FROM auth_management.profiles
WHERE role = 'student'
  AND status = 'active'
ORDER BY created_at DESC
LIMIT 10;
```

### 2. Gamificación

```sql
-- Stats generales de un usuario
SELECT user_id, level, total_xp, ml_coins,
       current_streak, max_streak,
       exercises_completed, average_score,
       global_rank_position
FROM gamification_system.user_stats
WHERE user_id = 'user-uuid';

-- Balance de ML Coins actual
SELECT ml_coins
FROM gamification_system.user_stats
WHERE user_id = 'user-uuid';

-- Historial de transacciones ML Coins (últimas 20)
SELECT id, amount, transaction_type, reason,
       balance_after, created_at
FROM gamification_system.ml_coins_ledger
WHERE user_id = 'user-uuid'
ORDER BY created_at DESC
LIMIT 20;

-- Top 10 ranking global
SELECT u.user_id, p.display_name, u.total_xp,
       u.level, u.ml_coins, u.global_rank_position
FROM gamification_system.user_stats u
JOIN auth_management.profiles p ON u.user_id = p.id
WHERE u.global_rank_position IS NOT NULL
ORDER BY u.global_rank_position ASC
LIMIT 10;

-- Achievements desbloqueados por usuario
SELECT ua.achievement_id, a.name, a.description,
       a.category, a.rarity, a.ml_coins_reward,
       ua.unlocked_at
FROM gamification_system.user_achievements ua
JOIN gamification_system.achievements a ON ua.achievement_id = a.id
WHERE ua.user_id = 'user-uuid'
  AND ua.is_claimed = true
ORDER BY ua.unlocked_at DESC;

-- Rango actual del usuario (Maya ranks)
SELECT r.rank_name, r.min_xp_required, r.benefits
FROM gamification_system.user_ranks ur
JOIN gamification_system.ranks r ON ur.rank_id = r.id
WHERE ur.user_id = 'user-uuid'
  AND ur.is_current = true;
```

### 3. Progreso Educativo

```sql
-- Progreso general del estudiante
SELECT m.title AS module_name,
       mp.status,
       mp.progress_percentage,
       mp.completed_exercises,
       mp.total_exercises,
       mp.average_score,
       mp.time_spent,
       mp.last_accessed_at
FROM progress_tracking.module_progress mp
JOIN educational_content.modules m ON mp.module_id = m.id
WHERE mp.user_id = 'user-uuid'
ORDER BY mp.last_accessed_at DESC;

-- Ejercicios completados recientemente
SELECT e.title AS exercise_name,
       ea.score,
       ea.is_correct,
       ea.time_spent,
       ea.attempt_number,
       ea.created_at
FROM progress_tracking.exercise_attempts ea
JOIN educational_content.exercises e ON ea.exercise_id = e.id
WHERE ea.user_id = 'user-uuid'
  AND ea.created_at > NOW() - INTERVAL '7 days'
ORDER BY ea.created_at DESC
LIMIT 20;

-- Estadísticas de un módulo específico
SELECT status,
       completed_exercises || '/' || total_exercises AS progress,
       progress_percentage || '%' AS percentage,
       ROUND(average_score, 2) AS avg_score,
       time_spent,
       last_accessed_at
FROM progress_tracking.module_progress
WHERE user_id = 'user-uuid'
  AND module_id = 'module-uuid';

-- Mecánicas más completadas
SELECT e.mechanic_type, COUNT(*) AS times_completed,
       ROUND(AVG(ea.score), 2) AS avg_score
FROM progress_tracking.exercise_attempts ea
JOIN educational_content.exercises e ON ea.exercise_id = e.id
WHERE ea.user_id = 'user-uuid'
  AND ea.is_correct = true
GROUP BY e.mechanic_type
ORDER BY times_completed DESC;
```

### 4. Teacher Dashboard

```sql
-- Aulas del profesor
SELECT c.id, c.name, c.grade_level,
       c.current_students_count, c.capacity,
       s.name AS school_name
FROM social_features.classrooms c
LEFT JOIN social_features.schools s ON c.school_id = s.id
WHERE c.teacher_id = 'teacher-uuid'
  AND c.is_active = true
ORDER BY c.created_at DESC;

-- Estudiantes de un aula
SELECT p.id, p.display_name, p.email,
       us.level, us.total_xp, us.ml_coins,
       cm.enrolled_at
FROM social_features.classroom_members cm
JOIN auth_management.profiles p ON cm.student_id = p.id
JOIN gamification_system.user_stats us ON p.id = us.user_id
WHERE cm.classroom_id = 'classroom-uuid'
  AND cm.status = 'active'
ORDER BY us.total_xp DESC;

-- Progreso de estudiantes en un módulo
SELECT p.display_name,
       mp.status,
       mp.progress_percentage,
       mp.average_score,
       mp.time_spent
FROM progress_tracking.module_progress mp
JOIN auth_management.profiles p ON mp.user_id = p.id
JOIN social_features.classroom_members cm
  ON cm.student_id = p.id
WHERE cm.classroom_id = 'classroom-uuid'
  AND mp.module_id = 'module-uuid'
ORDER BY mp.progress_percentage DESC, mp.average_score DESC;

-- Estudiantes que necesitan atención (bajo rendimiento)
SELECT p.display_name, p.email,
       us.exercises_completed,
       us.average_score,
       us.current_streak,
       us.last_active_at
FROM gamification_system.user_stats us
JOIN auth_management.profiles p ON us.user_id = p.id
JOIN social_features.classroom_members cm ON p.id = cm.student_id
WHERE cm.classroom_id = 'classroom-uuid'
  AND (us.average_score < 70 OR us.current_streak = 0)
ORDER BY us.average_score ASC;
```

### 5. Analytics

```sql
-- Actividad diaria de la plataforma (últimos 7 días)
SELECT DATE(created_at) AS date,
       COUNT(DISTINCT user_id) AS active_users,
       COUNT(*) AS total_exercises
FROM progress_tracking.exercise_attempts
WHERE created_at > NOW() - INTERVAL '7 days'
GROUP BY DATE(created_at)
ORDER BY date DESC;

-- Sesiones de aprendizaje promedio por usuario
SELECT u.user_id, p.display_name,
       COUNT(ls.id) AS total_sessions,
       AVG(EXTRACT(EPOCH FROM ls.duration) / 60) AS avg_duration_minutes,
       SUM(ls.exercises_completed) AS total_exercises
FROM progress_tracking.learning_sessions ls
JOIN gamification_system.user_stats u ON ls.user_id = u.user_id
JOIN auth_management.profiles p ON u.user_id = p.id
WHERE ls.created_at > NOW() - INTERVAL '30 days'
GROUP BY u.user_id, p.display_name
HAVING COUNT(ls.id) > 5
ORDER BY total_sessions DESC
LIMIT 20;

-- Retención de usuarios (días activos)
SELECT days_active_total AS days_active,
       COUNT(*) AS user_count
FROM gamification_system.user_stats
GROUP BY days_active_total
ORDER BY days_active_total DESC;

-- Conversión de intentos a completaciones
SELECT e.title,
       COUNT(*) AS total_attempts,
       SUM(CASE WHEN ea.is_correct THEN 1 ELSE 0 END) AS successful,
       ROUND(100.0 * SUM(CASE WHEN ea.is_correct THEN 1 ELSE 0 END) / COUNT(*), 2) AS success_rate
FROM progress_tracking.exercise_attempts ea
JOIN educational_content.exercises e ON ea.exercise_id = e.id
WHERE ea.created_at > NOW() - INTERVAL '30 days'
GROUP BY e.id, e.title
HAVING COUNT(*) >= 10
ORDER BY success_rate ASC
LIMIT 10;
```

---

## 🔗 Relaciones Principales

### User → Stats → Progress

```sql
-- Diagrama de relaciones
auth_management.profiles (id)
    ↓
gamification_system.user_stats (user_id)
    ↓
progress_tracking.module_progress (user_id)
    ↓
progress_tracking.exercise_attempts (user_id)
```

### School → Classroom → Students

```sql
-- Diagrama de relaciones
social_features.schools (id)
    ↓
social_features.classrooms (school_id, teacher_id)
    ↓
social_features.classroom_members (classroom_id, student_id)
    ↓
auth_management.profiles (id)
```

### Module → Exercise → Attempts

```sql
-- Diagrama de relaciones
educational_content.modules (id)
    ↓
educational_content.exercises (module_id)
    ↓
progress_tracking.exercise_attempts (exercise_id, user_id)
```

---

## ⚡ Funciones Útiles

### Función: `gamilit.now_mexico()`

Retorna timestamp en zona horaria de México.

```sql
-- Uso
SELECT gamilit.now_mexico();

-- Comparar con UTC
SELECT NOW() AS utc_time, gamilit.now_mexico() AS mexico_time;
```

### Función: `gamilit.update_updated_at()`

Trigger function para actualizar automáticamente `updated_at`.

```sql
-- Ya está aplicado automáticamente a todas las tablas principales
-- No necesitas llamarlo manualmente
```

### Calcular Nivel desde XP

```sql
-- Fórmula: level = floor(sqrt(total_xp / 100)) + 1
SELECT FLOOR(SQRT(total_xp / 100)) + 1 AS calculated_level
FROM gamification_system.user_stats
WHERE user_id = 'user-uuid';
```

---

## 📈 Índices y Performance

### Ver índices de una tabla

```sql
-- Listar índices
SELECT indexname, indexdef
FROM pg_indexes
WHERE tablename = 'profiles'
  AND schemaname = 'auth_management';

-- Uso de índices (requiere pg_stat_statements)
SELECT schemaname, tablename, indexname,
       idx_scan, idx_tup_read, idx_tup_fetch
FROM pg_stat_user_indexes
WHERE schemaname = 'auth_management'
ORDER BY idx_scan DESC;
```

### Índices más importantes

```sql
-- Email lookup (muy frecuente)
CREATE INDEX idx_profiles_email ON auth_management.profiles(email);

-- User stats lookup
CREATE INDEX idx_user_stats_user_id ON gamification_system.user_stats(user_id);

-- Exercise attempts por usuario
CREATE INDEX idx_exercise_attempts_user_id
ON progress_tracking.exercise_attempts(user_id);

-- Sesiones activas
CREATE INDEX idx_sessions_expires
ON auth_management.user_sessions(expires_at)
WHERE expires_at > NOW();
```

---

## 🔐 RLS (Row Level Security)

### Verificar políticas RLS

```sql
-- Listar políticas de una tabla
SELECT schemaname, tablename, policyname, cmd, qual
FROM pg_policies
WHERE tablename = 'profiles';

-- Verificar si RLS está habilitado
SELECT schemaname, tablename, rowsecurity
FROM pg_tables
WHERE schemaname = 'auth_management';
```

### Políticas comunes

```sql
-- Students can only see their own data
CREATE POLICY students_view_own_data
ON progress_tracking.module_progress
FOR SELECT
USING (user_id = current_setting('app.current_user_id')::UUID);

-- Teachers can see their classroom students
CREATE POLICY teachers_view_classroom_data
ON progress_tracking.module_progress
FOR SELECT
USING (
  EXISTS (
    SELECT 1
    FROM social_features.classroom_members cm
    JOIN social_features.classrooms c ON cm.classroom_id = c.id
    WHERE cm.student_id = module_progress.user_id
      AND c.teacher_id = current_setting('app.current_user_id')::UUID
  )
);
```

---

## 🛠️ Mantenimiento

### Vacuum y Analyze

```sql
-- Vacuum de una tabla
VACUUM ANALYZE auth_management.profiles;

-- Vacuum de todo el schema
VACUUM ANALYZE auth_management.*;

-- Ver última vez que se ejecutó
SELECT schemaname, relname, last_vacuum, last_autovacuum, last_analyze
FROM pg_stat_user_tables
WHERE schemaname = 'auth_management'
ORDER BY last_vacuum DESC NULLS LAST;
```

### Ver tamaño de tablas

```sql
-- Tamaño de una tabla
SELECT pg_size_pretty(pg_total_relation_size('auth_management.profiles'));

-- Top 10 tablas más grandes
SELECT schemaname, tablename,
       pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) AS size
FROM pg_tables
WHERE schemaname NOT IN ('pg_catalog', 'information_schema')
ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC
LIMIT 10;
```

### Ver locks activos

```sql
-- Locks actuales
SELECT pid, usename, pg_blocking_pids(pid) AS blocked_by,
       query, state
FROM pg_stat_activity
WHERE pg_blocking_pids(pid)::text != '{}';

-- Terminar query lento
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE pid = 12345;
```

---

## 📊 Backups

### Backup completo

```bash
# Backup de toda la database
pg_dump -U gamilit_user -d gamilit_platform -h localhost \
  -F c -f gamilit_backup_$(date +%Y%m%d).dump

# Backup de un schema específico
pg_dump -U gamilit_user -d gamilit_platform -h localhost \
  -n auth_management -F c -f auth_schema_$(date +%Y%m%d).dump

# Backup en SQL text
pg_dump -U gamilit_user -d gamilit_platform -h localhost \
  -F p -f gamilit_backup_$(date +%Y%m%d).sql
```

### Restore

```bash
# Restore desde custom format
pg_restore -U gamilit_user -d gamilit_platform -h localhost \
  -c gamilit_backup_20251107.dump

# Restore desde SQL text
psql -U gamilit_user -d gamilit_platform -h localhost \
  -f gamilit_backup_20251107.sql
```

---

## 🧪 Testing Queries

### Crear usuario de prueba

```sql
-- Insertar perfil
INSERT INTO auth_management.profiles
  (id, email, full_name, display_name, role, status)
VALUES
  (gen_random_uuid(), 'test@example.com', 'Test User', 'Test', 'student', 'active')
RETURNING id;

-- Stats se crean automáticamente por trigger
```

### Limpiar datos de prueba

```sql
-- Eliminar usuario de prueba (cascade eliminará stats, progress, etc.)
DELETE FROM auth_management.profiles
WHERE email LIKE '%@example.com';

-- Resetear sequence (si aplica)
ALTER SEQUENCE some_sequence RESTART WITH 1;
```

### Ver queries lentas (últimas 24h)

```sql
-- Requiere pg_stat_statements extension
SELECT calls, total_exec_time, mean_exec_time,
       substring(query, 1, 100) AS query_preview
FROM pg_stat_statements
WHERE total_exec_time > 1000  -- más de 1 segundo total
ORDER BY mean_exec_time DESC
LIMIT 10;
```

---

## 💡 Tips y Best Practices

### 1. Siempre usar tenant_id en queries multi-tenant

```sql
-- ❌ MAL (no filtra por tenant)
SELECT * FROM auth_management.profiles WHERE email = 'test@example.com';

-- ✅ BIEN (filtra por tenant)
SELECT * FROM auth_management.profiles
WHERE email = 'test@example.com'
  AND tenant_id = 'tenant-uuid';
```

### 2. Usar prepared statements

```sql
-- Preparar statement
PREPARE get_user_stats (UUID) AS
  SELECT level, total_xp, ml_coins, current_streak
  FROM gamification_system.user_stats
  WHERE user_id = $1;

-- Ejecutar
EXECUTE get_user_stats('user-uuid');

-- Deallocate cuando termines
DEALLOCATE get_user_stats;
```

### 3. EXPLAIN para entender queries lentas

```sql
-- Ver plan de ejecución
EXPLAIN SELECT * FROM auth_management.profiles WHERE email = 'test@example.com';

-- Ver plan con costos reales
EXPLAIN ANALYZE SELECT * FROM auth_management.profiles WHERE email = 'test@example.com';
```

### 4. Transacciones para operaciones atómicas

```sql
BEGIN;
  -- Restar ML Coins
  UPDATE gamification_system.user_stats
  SET ml_coins = ml_coins - 50
  WHERE user_id = 'user-uuid'
    AND ml_coins >= 50;

  -- Registrar transacción
  INSERT INTO gamification_system.ml_coins_ledger
    (user_id, amount, transaction_type, reason, balance_after)
  VALUES
    ('user-uuid', -50, 'powerup_purchase', 'Bought hint', 450);
COMMIT;
```

---

## 📚 Recursos

**Documentación completa:**
- [ESQUEMA-COMPLETO.md](../03-desarrollo/base-de-datos/ESQUEMA-COMPLETO.md) - Esquema detallado de 44 tablas
- [RLS-POLICIES.md](../03-desarrollo/base-de-datos/RLS-POLICIES.md) - Políticas de Row Level Security
- [MIGRACIONES.md](../03-desarrollo/base-de-datos/MIGRACIONES.md) - Sistema de migraciones

**DDL Files:**
- [apps/database/ddl/](../../apps/database/ddl/) - Definiciones de esquema

**PostgreSQL Docs:**
- [PostgreSQL 15 Documentation](https://www.postgresql.org/docs/15/)
- [psql Commands](https://www.postgresql.org/docs/15/app-psql.html)

---

**Última actualización:** 2025-11-07
**Versión:** 1.0
**Método:** Extracted from ESQUEMA-COMPLETO.md
