Skip to main content

Overview

The Biométrico system provides powerful search and filtering capabilities to quickly find specific students or staff members in large datasets.

Search by CI or Name

Real-Time Search Input

The search input field supports searching by:
  • Cédula de Identidad (CI) - National ID number
  • Full Name - First name, last name, or maternal surname
<input 
  type="text" 
  placeholder="Ingresa la cédula o nombre a buscar..." 
  v-model="searchQuery"
  @input="debouncedFilter"
  class="h-11 w-full rounded-lg border" 
/>
To prevent excessive API calls while the user is typing, the search uses a 900ms debounce delay:
import debounce from 'lodash.debounce';

export default {
  data() {
    return {
      searchQuery: "",
      debouncedFilter: null,
    };
  },
  created() {
    // Initialize debounced function
    this.debouncedFilter = debounce(() => {
      this.filterAndFetch();
    }, 900);
  },
  methods: {
    filterAndFetch() {
      // Reset to page 1 when applying new filters
      this.currentPage = 1;
      this.getAdministrativosD(this.currentPage, this.searchQuery, this.selectedCarrera);
    }
  }
}
The 900ms delay means the API request only fires after the user stops typing for 900 milliseconds.

Filter by Career

Career Dropdown

Filter results by academic career or department:
<select v-model="selectedCarrera" @change="debouncedFilter"
  class="h-11 w-full rounded-lg border">
  <option value="Todos">Todas las Carreras</option>
  <option v-for="carrera in carrerasList" :key="carrera.id" :value="carrera.id">
    {{ carrera.nombre }}
  </option>
</select>

Load Career List

The list of careers is loaded from the backend when the component mounts:
async loadCarrerasList() {
  try {
    const response = await API.get(`${this.baseUrl}/carrerasList`);
    this.carrerasList = response.data?.data || [];
  } catch (error) {
    console.error("❌ Error al obtener carreras:", error);
    this.carrerasList = [];
  }
}
Call this method in the mounted() lifecycle hook:
async mounted() {
  this.getAdministrativosD(1, this.searchQuery, this.selectedCarrera);
  this.loadCarrerasList();
}

Combined Search and Filter

Data Flow

1

User Input

The user types in the search box or selects a career from the dropdown.
2

Debounced Trigger

After 900ms of inactivity, debouncedFilter() is called, which triggers filterAndFetch().
3

Reset to Page 1

When filters change, the system resets to the first page:
filterAndFetch() {
  this.currentPage = 1;
  this.getAdministrativosD(this.currentPage, this.searchQuery, this.selectedCarrera);
}
4

API Request with Parameters

The search query and career filter are sent to the backend:
async getAdministrativosD(page = 1, searchQuery = "", carreraName = "Todos") {
  this.cargando = true;
  
  const params = {
    page: page,
    search_query: searchQuery,
    carrera_name: carreraName === 'Todos' ? '' : carreraName,
  };

  const response = await API.get(`${this.baseUrl}/estudiantesfoto`, { params });
  
  this.filteredpostulaciones = response.data?.data || [];
  this.currentPage = response.data?.pagination.current_page || 1;
  this.lastPage = response.data?.pagination.last_page || 1;
  this.totalEstudiantes = response.data.pagination.total;
  
  this.cargando = false;
}
5

Display Filtered Results

The table is updated with the filtered data, and pagination controls are adjusted.

Search and Filter UI

Complete Example

<template>
  <div class="flex flex-col gap-4 md:flex-row md:items-center mb-6">
    <!-- Search Form -->
    <form class="flex-grow">
      <div class="relative">
        <button class="absolute left-4 top-1/2 -translate-y-1/2">
          <svg class="fill-gray-500" width="20" height="20"><!-- Search icon --></svg>
        </button>
        <input 
          type="text" 
          placeholder="Ingresa la cédula o nombre a buscar..." 
          v-model="searchQuery"
          @input="debouncedFilter"
          class="h-11 w-full rounded-lg border pl-12 pr-14"
        />
      </div>
    </form>
    
    <!-- Total Count -->
    <div>
      <label>Total de estudiantes matriculados y con foto: {{ totalEstudiantes }}</label>
    </div>

    <!-- Career Filter -->
    <div class="relative w-full md:w-auto md:min-w-[280px]">
      <select v-model="selectedCarrera" @change="debouncedFilter"
        class="h-11 w-full rounded-lg border">
        <option value="Todos">Todas las Carreras</option>
        <option v-for="carrera in carrerasList" :key="carrera.id" :value="carrera.id">
          {{ carrera.nombre }}
        </option>
      </select>
      
      <div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-4">
        <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
        </svg>
      </div>
    </div>
  </div>
</template>

Backend API Expectations

The backend endpoint should accept these query parameters:
GET /biometrico/estudiantesfoto?page=1&search_query=Juan&carrera_name=Ingeniería

Response Format

{
  "data": [
    {
      "CIInfPer": "1234567",
      "NombInfPer": "Juan",
      "ApellInfPer": "Pérez",
      "ApellMatInfPer": "García",
      "NombCarr": "Ingeniería de Sistemas",
      "mailInst": "[email protected]",
      "hasPhoto": true
    }
  ],
  "pagination": {
    "current_page": 1,
    "last_page": 5,
    "total": 123
  }
}

Filter for Staff (Docentes)

For staff members, you can filter by personnel type instead of career:
<select v-model="selectedtipodoc" @change="debouncedFilter">
  <option v-for="item in tipodocList" :key="item.value" :value="item.value">
    {{ item.label }}
  </option>
</select>
data() {
  return {
    selectedtipodoc: 'Todos',
    tipodocList: [
      { value: 'Todos', label: 'Todo el Personal' },
      { value: 'Docente', label: 'Docentes' },
      { value: 'Administrativo', label: 'Administrativos' },
      { value: 'Servicios', label: 'Servicios' }
    ],
  };
}

Clear Filters

To reset all filters:
clearFilters() {
  this.searchQuery = "";
  this.selectedCarrera = "Todos";
  this.filterAndFetch();
}
<button @click="clearFilters" class="btn btn-secondary">
  Limpiar Filtros
</button>

Performance Considerations

Do not send an API request on every keystroke. Use debouncing to reduce server load:
// ❌ Bad: Fires on every keystroke
@input="filterAndFetch()"

// ✅ Good: Waits 900ms after typing stops
@input="debouncedFilter"

Advanced: Multiple Filters

You can combine multiple filters:
filterAndFetch() {
  this.currentPage = 1;
  this.getAdministrativosD(
    this.currentPage, 
    this.searchQuery, 
    this.selectedCarrera,
    this.selectedStatus // Additional filter
  );
}

State Management

Data Properties

data() {
  return {
    searchQuery: "",
    selectedCarrera: 'Todos',
    carrerasList: [],
    filteredpostulaciones: [],
    totalEstudiantes: 0,
    currentPage: 1,
    lastPage: 1,
    cargando: false,
    debouncedFilter: null,
  };
}

Loading State

Display a loading indicator while fetching data:
<tr v-if="cargando">
  <td colspan="9" class="text-center">
    <h3>Cargando....</h3>
  </td>
</tr>
Store the user’s last search and filter selections in localStorage to persist them across page reloads.

Build docs developers (and LLMs) love