Saltar a contenido

Nuevo Filtro: renews - Estudiantes que Renuevan

Fecha: 2025-10-02
Endpoint: GET /api/students/administrative
Versión: 1.2.0


Nueva Funcionalidad

Agregamos un nuevo parámetro renews que permite filtrar estudiantes según si están renovando matrícula en el curso escolar actual.


Parámetro renews

Parámetro Tipo Valores Descripción
renews boolean true, false, (omitido) Filtrar por estado de renovación

Valores posibles:

  • renews=true → Solo estudiantes que SÍ renuevan (tienen matrículas activas)
  • renews=false → Solo estudiantes que NO renuevan (sin matrículas)
  • Sin parámetro → Todos los estudiantes (comportamiento por defecto)

Ejemplos de Uso

1. Obtener solo estudiantes que SÍ renuevan

Request:

GET /api/students/administrative?renews=true&page=1&limit=20

Response:

{
  "data": [
    {
      "id": 6,
      "name": "Teresa",
      "lastname": "Merino Bobillo",
      "status": "active",
      "renews": true,
      "email": "tmerinobobillo@hotmail.com"
    },
    {
      "id": 45,
      "name": "Juan",
      "lastname": "García López",
      "status": "active",
      "renews": true,
      "email": "juan.garcia@example.com"
    }
    // ... más estudiantes que SÍ renuevan
  ],
  "meta": {
    "totalItems": 120,
    "totalPages": 6,
    "currentPage": 1,
    "pageSize": 20
  }
}


2. Obtener solo estudiantes que NO renuevan

Request:

GET /api/students/administrative?renews=false&page=1&limit=20

Response:

{
  "data": [
    {
      "id": 795,
      "name": "Teresa",
      "lastname": "Barazal Díaz",
      "status": "restricted",
      "renews": false,
      "email": "teresa.20160608@musicaymaestro.com"
    },
    {
      "id": 13,
      "name": "Teresa",
      "lastname": "Escobar Moriega",
      "status": "inactive",
      "renews": false,
      "email": "teresaes2009@hotmail.com"
    }
    // ... más estudiantes que NO renuevan
  ],
  "meta": {
    "totalItems": 45,
    "totalPages": 3,
    "currentPage": 1,
    "pageSize": 20
  }
}


3. Combinar con otros filtros

Request:

GET /api/students/administrative?renews=false&status=active&page=1&limit=20

Esto devuelve: - ✅ Estudiantes con estado "active" - ✅ Que NO tienen matrículas activas este año


4. Búsqueda + Filtro de renovación

Request:

GET /api/students/administrative?search=Maria&renews=true&page=1&limit=10

Esto devuelve: - ✅ Estudiantes con "Maria" en nombre o apellido - ✅ Que SÍ están renovando matrícula


Casos de Uso Frontend

Ejemplo 1: 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 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);
  };

  return (
    <div>
      <div className="tabs">
        <button 
          className={activeTab === 'all' ? 'active' : ''}
          onClick={() => {
            setActiveTab('all');
            fetchStudents('all');
          }}
        >
          Todos ({totalStudents})
        </button>

        <button 
          className={activeTab === 'renews' ? 'active' : ''}
          onClick={() => {
            setActiveTab('renews');
            fetchStudents('renews');
          }}
        >
          Renuevan ({studentsRenewing})
        </button>

        <button 
          className={activeTab === 'not_renews' ? 'active' : ''}
          onClick={() => {
            setActiveTab('not_renews');
            fetchStudents('not_renews');
          }}
        >
          No Renuevan ({studentsNotRenewing})
        </button>
      </div>

      <table>
        {/* Tabla de estudiantes */}
      </table>
    </div>
  );
}

Ejemplo 2: Select dropdown para filtrar

function RenewsFilterDropdown({ onChange }: { onChange: (value: string) => void }) {
  return (
    <select 
      className="form-select" 
      onChange={(e) => onChange(e.target.value)}
      defaultValue=""
    >
      <option value="">Todos los estudiantes</option>
      <option value="true">Solo los que renuevan</option>
      <option value="false">Solo los que NO renuevan</option>
    </select>
  );
}

// Uso:
function StudentsList() {
  const handleRenewsChange = (value: string) => {
    const url = value 
      ? `/api/students/administrative?renews=${value}`
      : '/api/students/administrative';

    fetchStudents(url);
  };

  return (
    <div>
      <RenewsFilterDropdown onChange={handleRenewsChange} />
      {/* ... resto del componente */}
    </div>
  );
}

Ejemplo 3: Dashboard con 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>
  );
}

Cómo Funciona Internamente

renews=true (Solo estudiantes que renuevan)

-- Equivalente SQL (simplificado)
SELECT DISTINCT students.*
FROM students
INNER JOIN student_registrations 
  ON students.id = student_registrations.studentId
WHERE student_registrations.schoolCoursesId = [curso_actual]

renews=false (Solo estudiantes que NO renuevan)

-- Equivalente SQL (simplificado)
SELECT students.*
FROM students
WHERE students.id NOT IN (
  SELECT DISTINCT studentId 
  FROM student_registrations 
  WHERE schoolCoursesId = [curso_actual]
)

Sin parámetro (Todos los estudiantes)

-- Equivalente SQL (simplificado)
SELECT students.*, student_registrations.*
FROM students
LEFT JOIN student_registrations 
  ON students.id = student_registrations.studentId
  AND student_registrations.schoolCoursesId = [curso_actual]

Notas Importantes

⚠️ Diferencia entre renews (query param) y renews (propiedad)

  • renews como query parameter → Filtro para la búsqueda
  • renews como propiedad en response → Valor calculado para cada estudiante

Ejemplo:

# Request con filtro
GET /api/students/administrative?renews=true

# Response - TODOS tienen renews: true porque el filtro los seleccionó
{
  "data": [
    { "id": 1, "renews": true },
    { "id": 2, "renews": true },
    { "id": 3, "renews": true }
  ]
}

📊 Paginación funciona correctamente

La paginación se aplica después del filtro, así que:

GET /api/students/administrative?renews=false&page=2&limit=20
Te devolverá la página 2 de los estudiantes que NO renuevan (elementos 21-40).

🔍 Combinación con otros filtros

Puedes combinar renews con cualquier otro filtro:

# Activos que NO renuevan
?status=active&renews=false

# Inactivos buscando "Maria"
?search=Maria&status=inactive&renews=false

# Con asignatura específica que renuevan
?subjectId=5&renews=true

TypeScript Types Actualizados

interface StudentQueryParams {
  page?: number;
  limit?: number;
  search?: string;
  status?: string;
  subjectId?: number;
  schoolCourseId?: number;
  renews?: boolean;  // ✨ NUEVO
}

// Ejemplo de uso
const params: StudentQueryParams = {
  page: 1,
  limit: 20,
  renews: true,
  status: 'active'
};

const queryString = new URLSearchParams({
  page: params.page.toString(),
  limit: params.limit.toString(),
  renews: params.renews.toString(),
  status: params.status
}).toString();

const url = `/api/students/administrative?${queryString}`;

Changelog

v1.2.0 (2025-10-02)

  • NUEVO: Parámetro renews para filtrar estudiantes por renovación
  • renews=true → Solo estudiantes que renuevan
  • renews=false → Solo estudiantes que NO renuevan
  • 🔧 Paginación optimizada para el filtro de renovación
  • 📝 Documentación completa con ejemplos

v1.1.0 (2025-10-02)

  • ✨ Agregada propiedad renews en la respuesta

v1.0.0

  • 🎉 Versión inicial

Soporte

¿Preguntas sobre este filtro? - 📧 Email: backend@musicaymaestro.com - 💬 Slack: #backend-support


Última actualización: 2025-10-02