Creación de un DataTable Profesional con Laravel Livewire (CRUD Educativo – Parte 1)
Creación de un DataTable Profesional
En este tutorial aprenderás paso a paso cómo construir un DataTable profesional completamente personalizado usando Laravel Livewire. Sin depender de librerías externas como jQuery DataTables, y con funciones avanzadas como:
- 🔍 Búsqueda en tiempo real
- 📌 Ordenamiento dinámico
- 📄 Paginación integrada
- 🎚 Filtrado avanzado (por estado)
- ⚡ Carga diferida (lazy loading)
Este DataTable forma parte de un CRUD educativo donde hoy cubriremos la sección de listar. En un próximo artículo complementaremos con crear, editar y eliminar.
📌 1. ¿Qué es un DataTable y por qué hacerlo con Livewire?
Un DataTable es un listado dinámico de registros que permite ordenar, filtrar y paginar información. Aunque existen librerías como jQuery DataTables o Vue.js, construirlo con Livewire tiene muchas ventajas:
- ✔ Integración perfecta con Laravel
- ✔ No requiere JavaScript
- ✔ Se actualiza en tiempo real
- ✔ Código ordenado y fácil de mantener
- ✔ Total control del diseño con TailwindCSS
Es ideal para aplicaciones administrativas o dashboards profesionales.
📌 2. Estructura del componente Livewire
El DataTable lo construimos dentro del componente:
readyToLoad = true;
}
public function order($sort)
{
if ($this->sort == $sort) {
$this->direction = $this->direction == 'desc' ? 'asc' : 'desc';
} else {
$this->sort = $sort;
$this->direction = 'asc';
}
}
public function render()
{
$companyId = auth()->user()->employee->company->id;
$transportistas = $this->readyToLoad
? Transportista::where('company_id', $companyId)
->where(function($query) {
$query->where('nomrazonsocial', 'like', '%' . $this->search . '%')
->orWhere('numdoc', 'like', '%' . $this->search . '%');
})
->when($this->state, fn($q) => $q->where('state', 1))
->orderBy($this->sort, $this->direction)
->paginate($this->cant)
: [];
return view('livewire.admin.transportista-list', compact('transportistas'));
}
}
?>
Este código nos permite filtrar, buscar, paginar y ordenar sin recargar la página.
📌 3. Vista del DataTable (HTML + Tailwind + Livewire)
La vista del componente es la encargada de mostrar nuestro DataTable con un diseño limpio y profesional usando TailwindCSS y la potencia reactiva de Livewire. Aquí combinamos:
- Selector de cantidad de registros por página
- Buscador en tiempo real
- Filtro de estado (Activos)
- Botón para crear un nuevo registro
- Tabla con ordenamiento dinámico en las columnas
- Paginación nativa de Laravel
A continuación, el código completo de la vista livewire.admin.transportista-list:
<div wire:init="loadTransportistas">
<!-- Encabezado -->
<div class="flex items-center mb-4">
<h2 class="text-xl font-semibold leading-tight text-gray-600">
Lista de Transportistas
</h2>
</div>
<div class="container py-6 mx-auto max-w-7xl sm:px-6 lg:px-8">
<!-- Barra de controles (Mostrar, Buscar, Nuevo, Activos) -->
<div class="flex flex-wrap items-center px-6 py-4 bg-gray-100 rounded-lg gap-4">
<!-- Cantidad por página -->
<div class="flex items-center">
<span>Mostrar</span>
<select wire:model="cant"
class="ml-3 mr-3 block py-1.5 px-3 text-sm border border-gray-300 rounded-lg focus:ring-indigo-500 focus:border-indigo-500">
<option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
<span>registros</span>
</div>
<!-- Buscador -->
<div class="flex-1 min-w-[200px]">
<input type="text" wire:model="search"
class="w-full px-3 py-2 text-sm border rounded-lg focus:ring-indigo-500 focus:border-indigo-500"
placeholder="Buscar por RUC / Razón Social">
</div>
<!-- Botón Nuevo -->
<div>
<a href="#"
class="inline-flex items-center px-4 py-2 text-sm font-semibold text-white bg-orange-600 rounded-lg shadow hover:bg-orange-700">
<i class="fa-regular fa-file mr-2"></i> Nuevo
</a>
</div>
<!-- Checkbox Activos -->
<div class="flex items-center">
<input type="checkbox" wire:model="state" class="mr-2 rounded border-gray-300">
<span>Activos</span>
</div>
</div>
<!-- Tabla -->
<x-table>
@if (count($transportistas))
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th scope="col"
class="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase cursor-pointer"
wire:click="order('id')">
Id
@if ($sort == 'id')
@if ($direction == 'asc')
<i class="float-right mt-1 fas fa-sort-alpha-up-alt"></i>
@else
<i class="float-right mt-1 fas fa-sort-alpha-down-alt"></i>
@endif
@else
<i class="float-right mt-1 fas fa-sort"></i>
@endif
</th>
<th scope="col"
class="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase">
Tipo Doc.
</th>
<th scope="col"
class="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase cursor-pointer"
wire:click="order('numdoc')">
Num Doc
@if ($sort == 'numdoc')
@if ($direction == 'asc')
<i class="float-right mt-1 fas fa-sort-alpha-up-alt"></i>
@else
<i class="float-right mt-1 fas fa-sort-alpha-down-alt"></i>
@endif
@else
<i class="float-right mt-1 fas fa-sort"></i>
@endif
</th>
<th scope="col"
class="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase cursor-pointer"
wire:click="order('nomrazonsocial')">
Razón Social
@if ($sort == 'nomrazonsocial')
@if ($direction == 'asc')
<i class="float-right mt-1 fas fa-sort-alpha-up-alt"></i>
@else
<i class="float-right mt-1 fas fa-sort-alpha-down-alt"></i>
@endif
@else
<i class="float-right mt-1 fas fa-sort"></i>
@endif
</th>
<th scope="col"
class="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase cursor-pointer"
wire:click="order('address')">
Dirección
@if ($sort == 'address')
@if ($direction == 'asc')
<i class="float-right mt-1 fas fa-sort-alpha-up-alt"></i>
@else
<i class="float-right mt-1 fas fa-sort-alpha-down-alt"></i>
@endif
@else
<i class="float-right mt-1 fas fa-sort"></i>
@endif
</th>
<th scope="col"
class="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase cursor-pointer"
wire:click="order('nromtc')">
Nro. MTC
@if ($sort == 'nromtc')
@if ($direction == 'asc')
<i class="float-right mt-1 fas fa-sort-alpha-up-alt"></i>
@else
<i class="float-right mt-1 fas fa-sort-alpha-down-alt"></i>
@endif
@else
<i class="float-right mt-1 fas fa-sort"></i>
@endif
</th>
<th scope="col"
class="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase">
Estado
</th>
<th scope="col"
class="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase">
Acciones
</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
@foreach ($transportistas as $transportista)
<tr>
<td class="px-6 py-4 text-sm text-gray-500 whitespace-nowrap">
{{ $transportista->id }}
</td>
<td class="px-6 py-4 text-sm text-gray-500">
{{ $transportista->tipodocumento->name }}
</td>
<td class="px-6 py-4 text-sm text-gray-500 whitespace-nowrap">
{{ $transportista->numdoc }}
</td>
<td class="px-6 py-4 text-sm text-gray-500 whitespace-nowrap">
{{ $transportista->nomrazonsocial }}
</td>
<td class="px-6 py-4 text-sm text-gray-500">
{{ $transportista->address }}
</td>
<td class="px-6 py-4 text-sm text-gray-500 whitespace-nowrap">
{{ $transportista->nromtc }}
</td>
<td class="px-6 py-4 whitespace-nowrap">
@switch($transportista->state)
@case(0)
<span wire:click="activar({{ $transportista->id }})"
class="inline-flex px-2 text-xs font-semibold leading-5 text-red-800 bg-red-100 rounded-full cursor-pointer">
inactivo
</span>
@break
@case(1)
<span wire:click="desactivar({{ $transportista->id }})"
class="inline-flex px-2 text-xs font-semibold leading-5 text-green-800 bg-green-100 rounded-full cursor-pointer">
activo
</span>
@break
@endswitch
</td>
<td class="px-6 py-4 text-sm font-medium text-right whitespace-nowrap">
<a href="#"
class="btn btn-green">
<i class="fa-solid fa-pen-to-square"></i>
</a>
<a class="btn btn-red ml-2"
wire:click="$emit('deletetransportista', {{ $transportista->id }})">
<i class="fa-solid fa-trash-can"></i>
</a>
</td>
</tr>
@endforeach
</tbody>
</table>
@if ($transportistas->hasPages())
<div class="px-6 py-4">
{{ $transportistas->links() }}
</div>
@endif
@endif
</x-table>
</div>
</div>
📌 4. Funciones clave explicadas
| Función Livewire | ¿Qué hace? |
|---|---|
wire:model="search" |
Actualiza la búsqueda en tiempo real sin recargar la página. |
wire:click="order('campo')" |
Ordena asc/desc como un DataTable profesional. |
wire:init="loadTransportistas" |
Evita cargar datos hasta que el componente esté listo (mejora el rendimiento). |
$this->paginate() |
Añade paginación automática sin JavaScript. |
📌 5. Ventajas de crear tu propio DataTable sin plugins
- ✔ No dependes de librerías externas como DataTables.js
- ✔ Menos peso, más rendimiento
- ✔ Mejor integración con el backend
- ✔ Total libertad de diseño con Tailwind
- ✔ Lógica limpia, escalable y mantenible
- ✔ Ideal para CRUD profesionales
🚀 Conclusión
Crear un DataTable profesional con Livewire es una de las mejores formas de construir paneles administrativos modernos. En este artículo hemos desarrollado un listado completo con:
- ✔ Búsqueda
- ✔ Filtros
- ✔ Paginación
- ✔ Ordenamiento
- ✔ Activar / desactivar
En el siguiente artículo avanzaremos con la creación de:
- 🟢 Crear nuevo transportista
- 🟡 Editar
- 🔴 Eliminar con confirmación
De esta forma construiremos el CRUD completo usando Livewire.
📞 Contáctanos
En Ticom Software somos especialistas en Laravel, PHP y Odoo ERP. Ofrecemos desarrollo de aplicaciones web, capacitaciones y soporte técnico.
- 🔗 Web: https://www.ticomsoftware.com
- 📧 Email: info@ticomsoftware.com
- 📱 WhatsApp: +51 996 929 478
- 🔵 Facebook: https://www.facebook.com/ticomperuscrl/
- 📸 Instagram: https://www.instagram.com/ticom.peru/
- 🎵 TikTok: https://www.tiktok.com/@ticomscrl