Avanzado

DataTables Server-Side en Perfex CRM

J. Negro 10 Dic 2025 18 min lectura
Volver al Blog

¿Por qué Server-Side Processing?

Cuando tu tabla tiene más de 1,000 registros, cargar todos los datos en el navegador se vuelve lento e ineficiente. DataTables Server-Side solo carga los datos necesarios para la página actual, haciendo tu aplicación mucho más rápida.

💡 Ventajas del Server-Side

  • Carga inicial instantánea
  • Bajo consumo de memoria en cliente
  • Búsqueda y ordenación en servidor
  • Escalable a millones de registros

Arquitectura de 3 Capas

La implementación requiere tres componentes que trabajan juntos:

📄 1. Vista (HTML + JS)

Define la estructura de la tabla y configura DataTables para usar AJAX.

🎮 2. Controlador (PHP)

Endpoint que recibe parámetros de DataTables y devuelve JSON.

🗄️ 3. Modelo (PHP)

Consulta la base de datos con filtros, paginación y ordenación.

Paso 1: La Vista

Crea una tabla HTML con los headers y un script que inicialice DataTables:

<!-- views/productos/list.php -->

<table class="table table-striped" id="tabla-productos">
    <thead>
        <tr>
            <th><?php echo _l('producto_id'); ?></th>
            <th><?php echo _l('producto_nombre'); ?></th>
            <th><?php echo _l('producto_precio'); ?></th>
            <th><?php echo _l('producto_stock'); ?></th>
            <th><?php echo _l('acciones'); ?></th>
        </tr>
    </thead>
    <tbody></tbody>
</table>

<script>
$(function() {
    initDataTable('#tabla-productos', admin_url + 'mi_modulo/productos_table', [
        // Columna 0: ID
        {
            data: 'id',
            name: 'id',
            width: '5%'
        },
        // Columna 1: Nombre (con enlace)
        {
            data: 'nombre',
            name: 'nombre',
            render: function(data, type, row) {
                return '<a href="' + admin_url + 'mi_modulo/producto/' + row.id + '">' + data + '</a>';
            }
        },
        // Columna 2: Precio (formateado)
        {
            data: 'precio',
            name: 'precio',
            render: function(data) {
                return format_money(data);
            }
        },
        // Columna 3: Stock (con badge)
        {
            data: 'stock',
            name: 'stock',
            render: function(data) {
                var badge = data > 10 ? 'success' : data > 0 ? 'warning' : 'danger';
                return '<span class="badge bg-' + badge + '">' + data + '</span>';
            }
        },
        // Columna 4: Acciones
        {
            data: 'acciones',
            name: 'acciones',
            orderable: false,
            searchable: false
        }
    ]);
});
</script>

Paso 2: El Controlador

Crea un método que procese la petición AJAX y devuelva los datos en el formato que DataTables espera:

<?php
// controllers/Mi_modulo.php

public function productos_table()
{
    if (!$this->input->is_ajax_request()) {
        show_404();
    }

    $this->load->model('productos_model');

    // Obtener datos con el helper de Perfex
    $aColumns = [
        'id',
        'nombre',
        'precio',
        'stock'
    ];

    $sIndexColumn = 'id';
    $sTable       = db_prefix() . 'mi_modulo_productos';

    $result = data_tables_init(
        $aColumns,
        $sIndexColumn,
        $sTable,
        [], // joins
        [], // where
        [   // Condiciones adicionales
            'activo' => 1
        ]
    );

    $output  = $result['output'];
    $rResult = $result['rResult'];

    foreach ($rResult as $aRow) {
        $row = [];

        $row[] = $aRow['id'];
        $row[] = $aRow['nombre'];
        $row[] = app_format_money($aRow['precio'], '');
        $row[] = $aRow['stock'];

        // Columna de acciones
        $acciones = '';

        if (has_permission('mi_modulo', '', 'edit')) {
            $acciones .= '<a href="' . admin_url('mi_modulo/producto/' . $aRow['id']) . '" class="btn btn-sm btn-default">';
            $acciones .= '<i class="fa fa-pencil"></i></a> ';
        }

        if (has_permission('mi_modulo', '', 'delete')) {
            $acciones .= '<a href="' . admin_url('mi_modulo/eliminar_producto/' . $aRow['id']) . '" class="btn btn-sm btn-danger _delete">';
            $acciones .= '<i class="fa fa-trash"></i></a>';
        }

        $row[] = $acciones;

        $output['aaData'][] = $row;
    }

    echo json_encode($output);
}

Paso 3: Filtros Avanzados

Añade filtros personalizados que se envían junto con la petición de DataTables:

// En la vista - añadir filtros
<div class="row mbot15">
    <div class="col-md-3">
        <select id="filtro-categoria" class="selectpicker">
            <option value="">Todas las categorías</option>
            <?php foreach($categorias as $cat): ?>
            <option value="<?php echo $cat['id']; ?>"><?php echo $cat['nombre']; ?></option>
            <?php endforeach; ?>
        </select>
    </div>
    <div class="col-md-3">
        <select id="filtro-stock" class="selectpicker">
            <option value="">Todo el stock</option>
            <option value="disponible">Con stock</option>
            <option value="bajo">Stock bajo</option>
            <option value="agotado">Agotado</option>
        </select>
    </div>
</div>

<script>
// Configuración con filtros
var tablaProductos = initDataTable('#tabla-productos', admin_url + 'mi_modulo/productos_table', columnas, columnas, 'undefined', [0, 'desc'], {
    // Enviar filtros con cada petición
    ajax: {
        url: admin_url + 'mi_modulo/productos_table',
        type: 'POST',
        data: function(d) {
            d.categoria = $('#filtro-categoria').val();
            d.stock = $('#filtro-stock').val();
        }
    }
});

// Recargar tabla cuando cambian filtros
$('#filtro-categoria, #filtro-stock').on('change', function() {
    tablaProductos.ajax.reload();
});
</script>

Procesar Filtros en el Controlador

// En el controlador, antes de data_tables_init()

$where = [];

// Filtro de categoría
if ($this->input->post('categoria')) {
    $where[] = 'AND categoria_id = ' . $this->db->escape($this->input->post('categoria'));
}

// Filtro de stock
$stock_filter = $this->input->post('stock');
if ($stock_filter) {
    switch ($stock_filter) {
        case 'disponible':
            $where[] = 'AND stock > 0';
            break;
        case 'bajo':
            $where[] = 'AND stock BETWEEN 1 AND 10';
            break;
        case 'agotado':
            $where[] = 'AND stock = 0';
            break;
    }
}

$result = data_tables_init(
    $aColumns,
    $sIndexColumn,
    $sTable,
    [], // joins
    $where, // condiciones WHERE adicionales
    ['activo' => 1]
);

✅ Resultado Final

Ahora tienes una tabla que carga datos de forma eficiente, con búsqueda, ordenación y filtros personalizados, todo procesado en el servidor. Esta implementación puede manejar cientos de miles de registros sin problemas de rendimiento.

JN

J. Negro

Desarrollador especializado en Perfex CRM con más de 5 años de experiencia creando módulos empresariales. Fundador de SalesCloud.