API Documentation: Get Paginated Students (Frontend)¶
Endpoint: GET /api/students/administrative
Versión: 2.0.0 (Actualizado: 2025-10-02)
Autenticación: Required
Descripción¶
Este endpoint devuelve una lista paginada de estudiantes con información completa sobre matrículas, centros, horarios y profesores asignados en el curso escolar actual.
Nuevas funcionalidades (Versión 2.0.0)¶
✨ Filtro centerId: Filtra estudiantes por centro educativo
✨ Filtro hasSchedule: Filtra estudiantes que tienen/no tienen horarios asignados
✨ Filtro employeeId: Filtra estudiantes por profesor asignado
✨ Búsqueda mejorada: Ahora busca por nombre, apellido, DNI y correo electrónico
✨ Campo centros: Array con nombres de centros donde el estudiante tiene matrículas
✨ Campo horarios: Array con horarios/eventos asignados al estudiante
Request¶
URL¶
Query Parameters¶
| Parámetro | Tipo | Requerido | Default | Descripción |
|---|---|---|---|---|
page |
number |
No | 1 |
Número de página (mínimo: 1) |
limit |
number |
No | 20 |
Elementos por página (máximo: 100) |
search |
string |
No | - | Buscar por nombre, apellido, DNI o email del estudiante |
status |
string |
No | - | Filtrar por estado(s). Valores separados por coma |
subjectId |
number |
No | - | Filtrar estudiantes por asignatura |
schoolCourseId |
number |
No | último curso | ID del curso escolar (usa último curso por defecto) |
renews |
boolean |
No | - | Filtrar por renovación (true=renuevan, false=no renuevan) |
centerId |
number |
No | - | ✨ NUEVO: Filtrar estudiantes por centro educativo |
hasSchedule |
boolean |
No | - | ✨ NUEVO: Filtrar por horarios (true=con horarios, false=sin horarios) |
employeeId |
number |
No | - | ✨ NUEVO: Filtrar estudiantes por profesor asignado |
Valores válidos para status¶
active- Estudiante activoinactive- Estudiante inactivopending- Estudiante pendienterestricted- Estudiante con restriccionesdelete/deleted- Estudiante eliminado
Nota: Puedes enviar múltiples estados separados por comas: ?status=active,pending
Response¶
Status Code: 200 OK¶
Response Body¶
{
data: Array<{
id: number;
name: string;
lastname: string;
email: string;
dni: string;
dateOfBirth: string; // ISO 8601 format
phone: string | number;
province: string;
city: string;
zipcode: string | null;
address: string;
urlContract: string | null;
needTutor: boolean;
userId: number | null;
monthlyFee: number | null;
feeStatus: string | null;
status: string;
guardian: {
id: number;
userId: number;
name: string;
lastName: string;
dateOfBirth: string;
dni: string;
phoneNumber: string;
email: string;
} | null;
renews: boolean; // true si tiene matrículas en curso actual
centros: string[]; // ✨ NUEVO: Array de nombres de centros donde está matriculado
horarios: Array<{ // ✨ NUEVO: Horarios/eventos del estudiante
id: number;
titulo: string;
inicio: string; // ISO 8601 format
duracion: string; // formato HH:MM
centro: string | null;
materia: string | null;
profesor: string | null;
}>;
}>;
meta: {
totalItems: number; // Total de estudiantes en la búsqueda
totalPages: number; // Total de páginas
currentPage: number; // Página actual
pageSize: number; // Elementos en esta página
statusCounts: { // ✨ NUEVO: Contadores por estado (respeta filtros aplicados)
all: number; // Total de estudiantes (todos los estados)
active: number; // Estudiantes con estado "active"
pending: number; // Estudiantes con estado "pending"
inactive: number; // Estudiantes con estado "inactive"
deleted: number; // Estudiantes con estado "delete" o "deleted"
};
};
}
Ejemplos de Uso¶
Ejemplo 1: Obtener primera página (básico)¶
Request:
Response:
{
"data": [
{
"id": 795,
"name": "Teresa",
"lastname": "Barazal Díaz",
"dni": "53280325C",
"email": "teresa.20160608@musicaymaestro.com",
"status": "restricted",
"phone": "34655756548",
"province": "Córdoba",
"city": "Madrid",
"address": "Calle navarro Sáez 32",
"zipcode": null,
"dateOfBirth": "2016-06-08",
"urlContract": null,
"needTutor": true,
"userId": 834,
"guardian": null,
"monthlyFee": null,
"feeStatus": null,
"renews": false
},
{
"id": 6,
"name": "Teresa",
"lastname": "Merino Bobillo",
"dni": "12190942E",
"email": "tmerinobobillo@hotmail.com",
"status": "active",
"phone": "34636082956",
"province": "Córdoba",
"city": "Peñarroya-Pueblonuevo",
"address": "C/ Eucaliptos, 10",
"zipcode": "14200",
"dateOfBirth": "1970-10-20",
"urlContract": "https://storage.googleapis.com/...",
"needTutor": false,
"userId": 41,
"guardian": null,
"monthlyFee": 18,
"feeStatus": "success_subscription",
"renews": true
}
],
"meta": {
"totalItems": 850,
"totalPages": 43,
"currentPage": 1,
"pageSize": 20,
"statusCounts": {
"all": 1502,
"active": 1123,
"pending": 30,
"inactive": 271,
"deleted": 60
}
}
}
Ejemplo 2: Buscar estudiantes por nombre, DNI o email ✨¶
Request:
# Buscar por nombre
GET /api/students/administrative?search=Teresa&page=1&limit=10
# Buscar por DNI
GET /api/students/administrative?search=53280325C&page=1&limit=10
# Buscar por email
GET /api/students/administrative?search=teresa@musicaymaestro.com&page=1&limit=10
Response:
{
"data": [
{
"id": 795,
"name": "Teresa",
"lastname": "Barazal Díaz",
"dni": "53280325C",
"email": "teresa.20160608@musicaymaestro.com",
"renews": false,
"centros": [],
"horarios": []
}
],
"meta": {
"totalItems": 1,
"totalPages": 1,
"currentPage": 1,
"pageSize": 10
}
}
Ejemplo 3: Filtrar por estado (solo activos)¶
Request:
Ejemplo 4: Filtrar solo estudiantes que NO renuevan ✨¶
Request:
Response:
{
"data": [
{
"id": 795,
"name": "Teresa",
"lastname": "Barazal Díaz",
"status": "restricted",
"renews": false,
// ... otros campos
},
{
"id": 13,
"name": "Teresa",
"lastname": "Escobar Moriega",
"status": "inactive",
"renews": false,
// ... otros campos
}
],
"meta": {
"totalItems": 45,
"totalPages": 3,
"currentPage": 1,
"pageSize": 20
}
}
Ejemplo 5: Filtrar solo estudiantes que SÍ renuevan ✨¶
Request:
Response:
{
"data": [
{
"id": 6,
"name": "Teresa",
"lastname": "Merino Bobillo",
"status": "active",
"renews": true,
// ... otros campos
}
],
"meta": {
"totalItems": 120,
"totalPages": 6,
"currentPage": 1,
"pageSize": 20
}
}
Ejemplo 6: Combinar filtro de renovación con estado ✨¶
Request:
Esto devolverá estudiantes que: - ✅ Tengan estado "active" - ✅ Y NO estén renovando matrícula
Ejemplo 7: Filtrar por centro ✨¶
Request:
Response:
{
"data": [
{
"id": 1680,
"name": "Geronimo",
"lastname": "Marin López",
"renews": true,
"centros": ["Escuela Municipal de Música Guadalcázar"],
"horarios": [
{
"id": 928,
"titulo": "Piano 1º",
"inicio": "2025-08-20T15:00:00.000Z",
"duracion": "01:00",
"centro": "Escuela Municipal de Música Guadalcázar",
"materia": "Piano 1º",
"profesor": "Carlos López Arregui"
}
]
}
],
"meta": {
"totalItems": 45,
"totalPages": 3,
"currentPage": 1,
"pageSize": 20
}
}
Esto devolverá solo estudiantes con matrículas en el centro ID 16.
Ejemplo 8: Filtrar estudiantes con horarios ✨¶
Request:
# Estudiantes CON horarios
GET /api/students/administrative?hasSchedule=true&page=1&limit=20
# Estudiantes SIN horarios
GET /api/students/administrative?hasSchedule=false&page=1&limit=20
Útil para: - Identificar estudiantes que necesitan asignación de horarios - Ver qué estudiantes ya tienen clases programadas - Generar reportes de cobertura de horarios
Ejemplo 9: Filtrar estudiantes por profesor ✨¶
Request:
Response:
{
"data": [
{
"id": 1680,
"name": "Geronimo",
"lastname": "Marin López",
"renews": true,
"centros": ["Escuela Municipal de Música Guadalcázar"],
"horarios": [
{
"id": 928,
"titulo": "Piano 1º",
"inicio": "2025-08-20T15:00:00.000Z",
"duracion": "01:00",
"centro": "Escuela Municipal de Música Guadalcázar",
"materia": "Piano 1º",
"profesor": "Carlos López Arregui"
}
]
}
]
}
Esto devolverá solo estudiantes con eventos del profesor ID 4.
Ejemplo 10: Combinar múltiples filtros ✨¶
Request:
GET /api/students/administrative?centerId=16&employeeId=4&hasSchedule=true&status=active&page=1&limit=20
Esto devolverá estudiantes que: - ✅ Tengan matrículas en el centro ID 16 - ✅ Y tengan horarios con el profesor ID 4 - ✅ Y tengan horarios asignados - ✅ Y su estado sea "active"
Ejemplo 11: Búsqueda avanzada combinada¶
Request:
Esto devolverá estudiantes que: - ✅ Tengan "37219841E" en nombre, apellido, DNI o email - ✅ Y estén matriculados en el centro ID 1 - ✅ Y SÍ estén renovando matrícula
Casos de Uso Frontend¶
1. Mostrar tabla de estudiantes¶
interface Student {
id: number;
name: string;
lastname: string;
email: string;
status: string;
renews: boolean;
// ... otros campos
}
interface PaginatedResponse {
data: Student[];
meta: {
totalItems: number;
totalPages: number;
currentPage: number;
pageSize: number;
};
}
async function fetchStudents(page: number = 1, limit: number = 20): Promise<PaginatedResponse> {
const response = await fetch(
`/api/students/administrative?page=${page}&limit=${limit}`,
{
headers: {
'Authorization': `Bearer ${token}`
}
}
);
if (!response.ok) {
throw new Error('Error al obtener estudiantes');
}
return response.json();
}
2. Implementar búsqueda con debounce¶
import { useState, useEffect } from 'react';
import { debounce } from 'lodash';
function StudentsList() {
const [students, setStudents] = useState<Student[]>([]);
const [search, setSearch] = useState('');
const [page, setPage] = useState(1);
const [meta, setMeta] = useState(null);
// Debounce para evitar múltiples requests
const debouncedSearch = debounce(async (searchTerm: string) => {
const response = await fetchStudents(1, 20, searchTerm);
setStudents(response.data);
setMeta(response.meta);
setPage(1);
}, 500);
useEffect(() => {
if (search) {
debouncedSearch(search);
}
}, [search]);
return (
<div>
<input
type="text"
placeholder="Buscar estudiante..."
value={search}
onChange={(e) => setSearch(e.target.value)}
/>
<table>
<thead>
<tr>
<th>Nombre</th>
<th>Apellido</th>
<th>Email</th>
<th>Estado</th>
<th>Renueva</th>
</tr>
</thead>
<tbody>
{students.map(student => (
<tr key={student.id}>
<td>{student.name}</td>
<td>{student.lastname}</td>
<td>{student.email}</td>
<td>
<span className={`badge badge-${student.status}`}>
{student.status}
</span>
</td>
<td>
{student.renews ? (
<span className="badge badge-success">✓ Renueva</span>
) : (
<span className="badge badge-warning">✗ No renueva</span>
)}
</td>
</tr>
))}
</tbody>
</table>
{/* Paginación */}
<div className="pagination">
<button
disabled={page === 1}
onClick={() => setPage(page - 1)}
>
Anterior
</button>
<span>Página {page} de {meta?.totalPages}</span>
<button
disabled={page === meta?.totalPages}
onClick={() => setPage(page + 1)}
>
Siguiente
</button>
</div>
</div>
);
}
3. Filtrar por estado con select¶
function StudentsFilter() {
const [status, setStatus] = useState<string[]>([]);
const statusOptions = [
{ value: 'active', label: 'Activos' },
{ value: 'inactive', label: 'Inactivos' },
{ value: 'pending', label: 'Pendientes' },
{ value: 'restricted', label: 'Restringidos' },
];
const handleStatusChange = (selectedStatus: string[]) => {
setStatus(selectedStatus);
// Construir query string
const statusQuery = selectedStatus.length > 0
? `&status=${selectedStatus.join(',')}`
: '';
fetchStudents(1, 20, '', statusQuery);
};
return (
<MultiSelect
options={statusOptions}
value={status}
onChange={handleStatusChange}
placeholder="Filtrar por estado..."
/>
);
}
4. Badge visual para "renews"¶
function RenewBadge({ renews }: { renews: boolean }) {
if (renews) {
return (
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
<svg className="mr-1.5 h-2 w-2" fill="currentColor" viewBox="0 0 8 8">
<circle cx="4" cy="4" r="3" />
</svg>
Renueva
</span>
);
}
return (
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800">
<svg className="mr-1.5 h-2 w-2" fill="currentColor" viewBox="0 0 8 8">
<circle cx="4" cy="4" r="3" />
</svg>
No renueva
</span>
);
}
// Uso:
<RenewBadge renews={student.renews} />
5. Tabs para filtrar por renovación ✨¶
import { useState } from 'react';
type RenewsFilter = 'all' | 'renews' | 'not_renews';
function StudentsPage() {
const [activeTab, setActiveTab] = useState<RenewsFilter>('all');
const [students, setStudents] = useState([]);
const [meta, setMeta] = useState(null);
const fetchStudents = async (renewsFilter: RenewsFilter) => {
let url = '/api/students/administrative?page=1&limit=20';
if (renewsFilter === 'renews') {
url += '&renews=true';
} else if (renewsFilter === 'not_renews') {
url += '&renews=false';
}
const response = await fetch(url);
const data = await response.json();
setStudents(data.data);
setMeta(data.meta);
};
return (
<div>
<div className="tabs">
<button
className={activeTab === 'all' ? 'active' : ''}
onClick={() => {
setActiveTab('all');
fetchStudents('all');
}}
>
Todos ({meta?.totalItems || 0})
</button>
<button
className={activeTab === 'renews' ? 'active' : ''}
onClick={() => {
setActiveTab('renews');
fetchStudents('renews');
}}
>
✓ Renuevan
</button>
<button
className={activeTab === 'not_renews' ? 'active' : ''}
onClick={() => {
setActiveTab('not_renews');
fetchStudents('not_renews');
}}
>
✗ No Renuevan
</button>
</div>
<table>
{/* Tabla de estudiantes */}
</table>
</div>
);
}
6. Tabs con contadores por estado (usando statusCounts) ✨¶
import { useState, useEffect } from 'react';
interface StatusTab {
key: string;
label: string;
status?: string[];
count: number;
}
function StudentsWithStatusTabs() {
const [activeStatus, setActiveStatus] = useState<string | null>(null);
const [students, setStudents] = useState([]);
const [meta, setMeta] = useState(null);
const [tabs, setTabs] = useState<StatusTab[]>([]);
const fetchStudents = async (status?: string[]) => {
let url = '/api/students/administrative?page=1&limit=20';
if (status) {
url += `&status=${status.join(',')}`;
}
const response = await fetch(url);
const data = await response.json();
setStudents(data.data);
setMeta(data.meta);
// Actualizar tabs con los contadores desde statusCounts
setTabs([
{ key: 'all', label: 'Todos', count: data.meta.statusCounts.all },
{ key: 'active', label: 'Activo', status: ['active'], count: data.meta.statusCounts.active },
{ key: 'pending', label: 'Pendiente', status: ['pending'], count: data.meta.statusCounts.pending },
{ key: 'inactive', label: 'Inactivo', status: ['inactive'], count: data.meta.statusCounts.inactive },
{ key: 'deleted', label: 'Eliminados', status: ['delete', 'deleted'], count: data.meta.statusCounts.deleted }
]);
};
useEffect(() => {
fetchStudents();
}, []);
const handleTabClick = (tab: StatusTab) => {
setActiveStatus(tab.key);
fetchStudents(tab.status);
};
return (
<div>
{/* Tabs con contadores */}
<div className="tabs-container">
{tabs.map(tab => (
<button
key={tab.key}
className={`tab ${activeStatus === tab.key ? 'active' : ''}`}
onClick={() => handleTabClick(tab)}
>
{tab.label}
<span className="badge">{tab.count}</span>
</button>
))}
</div>
{/* Tabla de estudiantes */}
<table>
<thead>
<tr>
<th>Nombre</th>
<th>Estado</th>
<th>Centros</th>
</tr>
</thead>
<tbody>
{students.map(student => (
<tr key={student.id}>
<td>{student.name} {student.lastname}</td>
<td><span className={`status-badge ${student.status}`}>{student.status}</span></td>
<td>{student.centros.join(', ')}</td>
</tr>
))}
</tbody>
</table>
{/* Paginación */}
<div className="pagination">
Página {meta?.currentPage} de {meta?.totalPages}
</div>
</div>
);
}
7. Dashboard de estadísticas ✨¶
interface DashboardStats {
total: number;
renewing: number;
notRenewing: number;
renewalRate: number;
}
async function getDashboardStats(): Promise<DashboardStats> {
// Obtener todos
const allResponse = await fetch('/api/students/administrative?page=1&limit=1');
const allData = await allResponse.json();
// Obtener los que renuevan
const renewingResponse = await fetch('/api/students/administrative?renews=true&page=1&limit=1');
const renewingData = await renewingResponse.json();
// Obtener los que NO renuevan
const notRenewingResponse = await fetch('/api/students/administrative?renews=false&page=1&limit=1');
const notRenewingData = await notRenewingResponse.json();
const total = allData.meta.totalItems;
const renewing = renewingData.meta.totalItems;
const notRenewing = notRenewingData.meta.totalItems;
return {
total,
renewing,
notRenewing,
renewalRate: (renewing / total) * 100
};
}
function Dashboard() {
const [stats, setStats] = useState<DashboardStats | null>(null);
useEffect(() => {
getDashboardStats().then(setStats);
}, []);
if (!stats) return <div>Cargando...</div>;
return (
<div className="dashboard">
<div className="stat-card">
<h3>Total Estudiantes</h3>
<p className="stat-number">{stats.total}</p>
</div>
<div className="stat-card success">
<h3>Renuevan</h3>
<p className="stat-number">{stats.renewing}</p>
</div>
<div className="stat-card warning">
<h3>No Renuevan</h3>
<p className="stat-number">{stats.notRenewing}</p>
</div>
<div className="stat-card info">
<h3>Tasa de Renovación</h3>
<p className="stat-number">{stats.renewalRate.toFixed(1)}%</p>
</div>
</div>
);
}
Lógica de Negocio: ¿Qué significa renews?¶
renews: true¶
El estudiante SÍ tiene matrículas activas en el curso escolar actual (2025-2026).
Significa:
- ✅ El estudiante está inscrito en al menos una asignatura este año
- ✅ Tiene registros (registers) asociados al curso actual
- ✅ Probablemente pagará cuota mensual este año
renews: false¶
El estudiante NO tiene matrículas activas en el curso escolar actual.
Puede significar: - ❌ No se ha inscrito este año (podría ser estudiante antiguo) - ❌ Aún no ha completado el proceso de renovación - ❌ Ya no asiste a la escuela
Notas Importantes¶
⚠️ Límite de resultados¶
El límite máximo por página es 100 estudiantes. Si solicitas más, se limitará automáticamente a 100.
📅 Curso escolar actual¶
El endpoint usa automáticamente el último curso escolar de la base de datos. Todos los filtros (centros, horarios, renews, hasSchedule) se aplican sobre el curso actual.
🔍 Búsqueda mejorada¶
La búsqueda es case-insensitive y busca en: - Nombre del estudiante - Apellido del estudiante - DNI (búsqueda parcial) - Email (búsqueda parcial)
🎯 Filtros combinables¶
Todos los filtros son combinables para búsquedas avanzadas:
- status + centerId + employeeId → Estudiantes activos de un centro y profesor
- renews + hasSchedule → Estudiantes que renuevan pero sin horarios
- search + múltiples filtros → Búsqueda específica con contexto
🏫 Filtro por centro (centerId)¶
- Filtra estudiantes que tienen matrículas activas en ese centro en el curso actual
- Solo muestra estudiantes con registros (
registers) en ese centro
⏰ Filtro por horarios (hasSchedule)¶
true: Estudiantes con eventos/horarios asignados en el curso actualfalse: Estudiantes sin eventos/horarios asignados (útil para asignación pendiente)
👨🏫 Filtro por profesor (employeeId)¶
- Filtra estudiantes que tienen eventos con ese profesor en el curso actual
- Útil para ver la lista de alumnos de un profesor específico
📊 Campos adicionales¶
centros: Array con nombres únicos de centros donde el estudiante tiene matrículashorarios: Array completo con todos los eventos/horarios del estudiante (incluye centro, materia y profesor)
🔢 Contadores por estado (statusCounts)¶
La respuesta siempre incluye contadores de estudiantes por estado en meta.statusCounts:
- Los contadores respetan todos los filtros aplicados (search, centerId, employeeId, hasSchedule, renews)
- Los contadores NO respetan el filtro de status (para poder mostrar todos los badges)
- Útil para mostrar tabs/badges con contadores como: "Todos (1502)", "Activos (1123)", "Pendientes (30)"
Ejemplo:
Si filtras por ?centerId=16, los contadores mostrarán:
- all: Total de estudiantes en el centro 16
- active: Estudiantes activos en el centro 16
- pending: Estudiantes pendientes en el centro 16
- Etc.
Manejo de Errores¶
Error 401 - No autorizado¶
Solución: Verifica que el token de autenticación sea válido.
Error 400 - Parámetros inválidos¶
Solución: Verifica que page y limit sean números positivos.
Changelog¶
v2.0.0 (2025-10-02) - MAJOR UPDATE¶
- ✨ NUEVO: Filtro
centerId- Filtra estudiantes por centro educativo - ✨ NUEVO: Filtro
hasSchedule- Filtra estudiantes con/sin horarios asignados - ✨ NUEVO: Filtro
employeeId- Filtra estudiantes por profesor asignado - ✨ NUEVO: Campo
centros- Array con nombres de centros del estudiante - ✨ NUEVO: Campo
horarios- Array completo con horarios/eventos del estudiante - Incluye: id, título, inicio, duración, centro, materia y profesor
- ✨ NUEVO:
meta.statusCounts- Contadores automáticos por estado - Incluye: all, active, pending, inactive, deleted
- Respeta filtros aplicados (excepto status)
- Ideal para mostrar tabs con badges de contadores
- 🔍 MEJORADO: Búsqueda ahora incluye DNI y email (además de nombre y apellido)
- 📚 11 ejemplos completos de uso con diferentes combinaciones de filtros
- 💡 Ejemplo de componente React con tabs usando statusCounts
- 🎯 Todos los filtros son combinables para búsquedas avanzadas
v1.2.0 (2025-10-02)¶
- ✨ NUEVO: Parámetro
renewspara filtrar estudiantes por renovación renews=true→ Solo estudiantes que renuevanrenews=false→ Solo estudiantes que NO renuevan- 📚 Ejemplos completos con tabs y dashboard de estadísticas
- 🔧 Paginación optimizada para el filtro de renovación
v1.1.0 (2025-10-02)¶
- ✨ Agregada propiedad
renewspara indicar si el estudiante renueva matrícula - 🔧 Filtro automático de registros por curso escolar actual
- 📝 Los estudiantes sin registros ahora se devuelven con
renews: false
v1.0.0¶
- 🎉 Versión inicial
- Paginación básica
- Filtros por estado y búsqueda
Última actualización: 2025-10-02 (Versión 2.0.0)