Tema
Consultar la base de datos
QA usa SQL para una cosa muy concreta: verificar que lo que muestra la aplicación coincide con lo que hay guardado. Cuando no coinciden, has encontrado algo.
Cómo entrar
Por el navegador (recomendado)
Abre db.theproject.ec:
| Campo | Valor |
|---|---|
| Motor | PostgreSQL |
| Servidor | postgres-lab |
| Usuario | student_ro |
| Contraseña | lectura-qa-2026 |
| Base de datos | reservalab |
No hay que instalar nada. Escribe la consulta en SQL command y ejecuta.
Con DBeaver o psql
Si prefieres una herramienta de escritorio, pídele al instructor el acceso: la base de datos no está abierta a internet directamente, por seguridad.
El usuario es de solo lectura
student_ro puede consultar todo pero no modificar nada. Si intentas:
sql
DELETE FROM appointments WHERE id = 1;Obtendrás:
ERROR: permission denied for table appointmentsEso es correcto, no un problema del entorno. Los permisos son parte de cómo se protege una base de datos.
Las tablas
users
id, email, password_hash, nombre, rol, created_at
rol ∈ ('admin', 'cliente')
services
id, nombre, descripcion, duracion_min, precio, activo, created_at
appointments
id, user_id, service_id, fecha, estado, notas, created_at, cancelled_at
estado ∈ ('confirmada', 'cancelada', 'completada')Las relaciones: appointments.user_id → users.id y appointments.service_id → services.id.
Antes de nada: usa siempre LIMIT
No hagas SELECT * FROM appointments
La tabla de citas tiene más de doscientas mil filas. Si pides todas, el navegador tiene que dibujar doscientas mil filas y se queda colgado varios minutos. No es que la consola esté mal: es que le pediste demasiado.
sql
-- ❌ Mal: intenta traer la tabla entera
SELECT * FROM appointments;
-- ✅ Bien: mira lo que necesitas
SELECT * FROM appointments LIMIT 100;
-- ✅ Mejor todavía: filtra por lo que te interesa
SELECT * FROM appointments WHERE user_id = 9;Esto no es una limitación del laboratorio, es el hábito correcto. En cualquier sistema real las tablas tienen millones de filas y un SELECT * sin límite bloquea la herramienta, satura la red y en el peor caso afecta al servicio en producción. Acostúmbrate desde ahora: primero LIMIT, después amplías.
Si solo quieres saber cuántas hay, no las traigas: cuéntalas.
sql
SELECT COUNT(*) FROM appointments;Lo que necesitas saber de SQL
Seleccionar y filtrar
sql
SELECT id, nombre, precio
FROM services
WHERE activo = true
ORDER BY precio DESC;Contar
sql
SELECT COUNT(*) FROM appointments;Agrupar
sql
SELECT estado, COUNT(*) AS total
FROM appointments
GROUP BY estado
ORDER BY total DESC;Unir tablas
Un JOIN combina filas de dos tablas por una columna en común:
sql
SELECT a.id, a.fecha, s.nombre AS servicio, u.email AS cliente
FROM appointments a
JOIN services s ON s.id = a.service_id
JOIN users u ON u.id = a.user_id
LIMIT 10;JOIN vs LEFT JOIN — esto importa mucho
Un JOIN normal (inner) descarta las filas que no tienen pareja. Un LEFT JOIN las conserva.
Si añades una condición al JOIN:
sql
JOIN services s ON s.id = a.service_id AND s.activo = true...estás eliminando del resultado todas las citas cuyo servicio esté inactivo, aunque esas citas existan. Es una de las formas más comunes de que datos reales "desaparezcan" de una pantalla. Tenlo en la cabeza.
Filtrar por fecha
sql
-- Las citas de un día concreto
SELECT * FROM appointments
WHERE fecha >= '2026-08-15'::date
AND fecha < '2026-08-16'::date;
-- Las futuras
SELECT * FROM appointments WHERE fecha > now();Consultas útiles para el bootcamp
Todas tus citas
sql
SELECT a.id, a.fecha::date AS dia, a.estado, s.nombre AS servicio, s.activo
FROM appointments a
JOIN services s ON s.id = a.service_id
JOIN users u ON u.id = a.user_id
WHERE u.email = 'studentNN@theproject.ec'
ORDER BY a.id;Comparar la API con la base de datos
Ejecuta GET /appointments y apunta el total. Después:
sql
SELECT COUNT(*)
FROM appointments a
JOIN users u ON u.id = a.user_id
WHERE u.email = 'studentNN@theproject.ec';Si los números no coinciden, la aplicación te está ocultando datos. Averigua cuáles y por qué.
Servicios dados de baja
sql
SELECT id, nombre, activo FROM services ORDER BY id;Citas huérfanas de un servicio inactivo
sql
SELECT a.id, a.estado, a.fecha::date, s.nombre, s.activo
FROM appointments a
JOIN services s ON s.id = a.service_id
WHERE s.activo = false;Ver los índices de una tabla
Relevante para el módulo 4: un índice que falta es la causa más común de que una consulta vaya lenta.
sql
SELECT indexname, indexdef
FROM pg_indexes
WHERE tablename = 'appointments';Ver el plan de ejecución
EXPLAIN ANALYZE te dice cómo ejecuta Postgres la consulta y cuánto tarda:
sql
EXPLAIN ANALYZE
SELECT COUNT(*) FROM appointments
WHERE fecha >= '2026-03-15'::date AND fecha < '2026-03-16'::date;Lo que buscas en la salida:
| Aparece | Significa |
|---|---|
Index Scan / Index Only Scan | Usa un índice. Bien. |
Seq Scan / Parallel Seq Scan | Lee la tabla entera. Con muchas filas, mal. |
Rows Removed by Filter: 199813 | Leyó 199.813 filas para descartarlas. Muy mal. |
Errores típicos
| Error | Causa |
|---|---|
relation "appointment" does not exist | La tabla es appointments, en plural |
permission denied for table | Intentaste escribir con el usuario de solo lectura |
column "user" does not exist | Es user_id; y user es palabra reservada |
operator does not exist: timestamp = text | Convierte con ::date o ::timestamptz |