La página de los iconos es: https://heroicons.dev/
Si no logras ver las opciones del lado izquierdo para elegir el tipo de icono, has zoom out.
Diseñar un listado de preguntas en Laravel con Livewire requiere algo más que iterar registros: necesitas mostrar el avatar del autor, la categoría con su color, el conteo de respuestas y un enlace de acción. Aquí verás cómo cerrar ese diseño paso a paso conectando relaciones Eloquent, Gravatar y una consulta optimizada con withCount.
¿Cómo mostrar el avatar del usuario con Gravatar en Laravel?
El primer bloque del listado es la imagen del autor de la pregunta. La idea es traerla desde Gravatar, un servicio global que sirve avatares basados en el hash MD5 del email registrado.
Dentro de la vista Blade se imprime con la sintaxis {{ $pregunta->user->avatar() }} y se aplica una clase para esquinas redondeadas. Pero antes de que ese método funcione, hay dos piezas que conectar.
¿Por qué falla la relación user en el modelo Pregunta?
Al intentar acceder a $pregunta->user aparece un valor nulo porque la relación todavía no existe en el modelo. La tabla está vinculada físicamente, pero Eloquent no infiere la relación sola, hay que declararla [02:10].
En la entidad Pregunta (o Hilo) se agrega:
php public function user() { return $this->belongsTo(User::class); }
Una pregunta pertenece a un usuario, por eso usamos belongsTo. Ya con esto, el modelo sabe a dónde ir a buscar al autor.
¿Cómo se construye el método avatar() con Gravatar?
En User.php se define un método público que arma la URL con el email del usuario convertido a MD5:
php public function avatar() { return 'https://gravatar.com/avatar/' . md5($this->email) . '?s=50'; }
El parámetro s=50 define el tamaño en píxeles. Si el usuario tiene cuenta en Gravatar, aparece su foto real; si no, sale el avatar genérico, lo cual es esperable cuando trabajas con usuarios fake generados por seeders.
¿Qué es Gravatar y por qué se usa con MD5? Es un servicio que asocia avatares a emails. Para pedir la imagen, debes enviar el email convertido a hash MD5, así el servicio lo identifica sin exponer el correo en la URL.
¿Cómo conectar la categoría y su color a cada pregunta?
El siguiente bloque es la categoría. En la vista necesitas imprimir tanto el nombre como el color, y ambos viven en el modelo Categoria [04:30].
La relación se declara igual que con el usuario:
php public function category() { return $this->belongsTo(Category::class); }
Luego en Blade se usa {{ $pregunta->category->name }} para el texto y {{ $pregunta->category->color }} para aplicar el color dinámicamente. Con esto, cada tarjeta del listado refleja el color real de su categoría sin valores quemados.
¿Cómo contar respuestas con withCount en Livewire?
Mostrar cuántas respuestas tiene cada pregunta es donde más se nota la optimización. Lo ineficiente sería traer todas las respuestas y hacer count() en memoria. Lo correcto es delegarle el conteo a la base de datos.
Primero declaras la relación en Pregunta:
php public function respuestas() { return $this->hasMany(Respuesta::class); }
Después, en el componente Livewire ubicado en Http/Livewire, ajustas la consulta:
php Pregunta::withCount('respuestas')->get();
Este método genera automáticamente el campo respuestas_count en cada registro, listo para usar en la vista [07:15].
¿Qué hace withCount en Eloquent? Agrega una subconsulta que cuenta los registros relacionados y los devuelve en un campo nuevo, evitando cargar toda la colección. Es ideal para listados con contadores.
¿Cómo pluralizar respuesta o respuestas según el número?
Decir tres respuesta suena mal. La regla es simple: si el conteo es distinto de uno, agregas una s. Eso se resuelve con un operador ternario en Blade:
blade {{ $pregunta->respuestas_count }} respuesta{{ $pregunta->respuestas_count != 1 ? 's' : '' }}
Resultado: una respuesta, tres respuestas, dos respuestas. Pequeño detalle, gran diferencia en lectura.
¿Cómo agregar el icono de chat y el botón editar al listado?
Falta el remate visual. Junto al contador de respuestas se coloca un icono de chat sólido tomado de una librería como Heroicons. Pegas el SVG en la vista y le aplicas una clase de altura, por ejemplo h-4, para que escale bien con el texto.
Al lado se agrega un enlace de acción con la palabra editar y una clase que cambia el color a blanco al hacer hover. Para alinear icono, número y botón en una sola línea, el contenedor padre lleva las utilidades de Tailwind:
flex para activar el modelo de caja flexible.items-center para centrar verticalmente icono y texto.gap-1 para una brecha mínima entre elementos.Con eso, la fila inferior de cada tarjeta queda ordenada: avatar, nombre del autor, fecha, categoría con color, icono de chat, conteo pluralizado y botón editar.
¿Qué piezas quedaron conectadas en el componente?
Repasando lo que hiciste fluir entre modelos y vista:
avatar() en User que arma la URL de Gravatar con MD5.belongsTo hacia User y Category en el modelo Pregunta.hasMany hacia Respuesta para habilitar el conteo.withCount('respuestas') en el componente Livewire.Esta es la base del listado. El botón editar todavía no tiene lógica, eso viene en la siguiente práctica. Ahora aplica esta configuración en tu proyecto y comparte abajo cómo te quedó tu vista.
Diseñar un listado de preguntas en Laravel con Livewire requiere algo más que iterar registros: necesitas mostrar el avatar del autor, la categoría con su color, el conteo de respuestas y un enlace de acción. Aquí verás cómo cerrar ese diseño paso a paso conectando relaciones Eloquent, Gravatar y una consulta optimizada con withCount.
El primer bloque del listado es la imagen del autor de la pregunta. La idea es traerla desde Gravatar, un servicio global que sirve avatares basados en el hash MD5 del email registrado.
Dentro de la vista Blade se imprime con la sintaxis {{ $pregunta->user->avatar() }} y se aplica una clase para esquinas redondeadas. Pero antes de que ese método funcione, hay dos piezas que conectar.
Al intentar acceder a $pregunta->user aparece un valor nulo porque la relación todavía no existe en el modelo. La tabla está vinculada físicamente, pero Eloquent no infiere la relación sola, hay que declararla [02:10].
En la entidad Pregunta (o Hilo) se agrega:
php public function user() { return $this->belongsTo(User::class); }
Una pregunta pertenece a un usuario, por eso usamos belongsTo. Ya con esto, el modelo sabe a dónde ir a buscar al autor.
En User.php se define un método público que arma la URL con el email del usuario convertido a MD5:
php public function avatar() { return 'https://gravatar.com/avatar/' . md5($this->email) . '?s=50'; }
El parámetro s=50 define el tamaño en píxeles. Si el usuario tiene cuenta en Gravatar, aparece su foto real; si no, sale el avatar genérico, lo cual es esperable cuando trabajas con usuarios fake generados por seeders.
¿Qué es Gravatar y por qué se usa con MD5? Es un servicio que asocia avatares a emails. Para pedir la imagen, debes enviar el email convertido a hash MD5, así el servicio lo identifica sin exponer el correo en la URL.
El siguiente bloque es la categoría. En la vista necesitas imprimir tanto el nombre como el color, y ambos viven en el modelo Categoria [04:30].
La relación se declara igual que con el usuario:
php public function category() { return $this->belongsTo(Category::class); }
Luego en Blade se usa {{ $pregunta->category->name }} para el texto y {{ $pregunta->category->color }} para aplicar el color dinámicamente. Con esto, cada tarjeta del listado refleja el color real de su categoría sin valores quemados.
Mostrar cuántas respuestas tiene cada pregunta es donde más se nota la optimización. Lo ineficiente sería traer todas las respuestas y hacer count() en memoria. Lo correcto es delegarle el conteo a la base de datos.
Primero declaras la relación en Pregunta:
php public function respuestas() { return $this->hasMany(Respuesta::class); }
Después, en el componente Livewire ubicado en Http/Livewire, ajustas la consulta:
php Pregunta::withCount('respuestas')->get();
Este método genera automáticamente el campo respuestas_count en cada registro, listo para usar en la vista [07:15].
¿Qué hace withCount en Eloquent? Agrega una subconsulta que cuenta los registros relacionados y los devuelve en un campo nuevo, evitando cargar toda la colección. Es ideal para listados con contadores.
Decir tres respuesta suena mal. La regla es simple: si el conteo es distinto de uno, agregas una s. Eso se resuelve con un operador ternario en Blade:
blade {{ $pregunta->respuestas_count }} respuesta{{ $pregunta->respuestas_count != 1 ? 's' : '' }}
Resultado: una respuesta, tres respuestas, dos respuestas. Pequeño detalle, gran diferencia en lectura.
Falta el remate visual. Junto al contador de respuestas se coloca un icono de chat sólido tomado de una librería como Heroicons. Pegas el SVG en la vista y le aplicas una clase de altura, por ejemplo h-4, para que escale bien con el texto.
Al lado se agrega un enlace de acción con la palabra editar y una clase que cambia el color a blanco al hacer hover. Para alinear icono, número y botón en una sola línea, el contenedor padre lleva las utilidades de Tailwind:
flex para activar el modelo de caja flexible.items-center para centrar verticalmente icono y texto.gap-1 para una brecha mínima entre elementos.Con eso, la fila inferior de cada tarjeta queda ordenada: avatar, nombre del autor, fecha, categoría con color, icono de chat, conteo pluralizado y botón editar.
Repasando lo que hiciste fluir entre modelos y vista:
avatar() en User que arma la URL de Gravatar con MD5.belongsTo hacia User y Category en el modelo Pregunta.hasMany hacia Respuesta para habilitar el conteo.withCount('respuestas') en el componente Livewire.Esta es la base del listado. El botón editar todavía no tiene lógica, eso viene en la siguiente práctica. Ahora aplica esta configuración en tu proyecto y comparte abajo cómo te quedó tu vista.
La página de los iconos es: https://heroicons.dev/
Si no logras ver las opciones del lado izquierdo para elegir el tipo de icono, has zoom out.
La alternativa que utilicé para el avatar fue crear una migración para agregar un campo avatar a la tabla de User: $table->string('avatar_img')->nullable(); y en el User Factory cargo una url de imagen random utilizando la api de randomuser. De esta manera cada usuario tiene su imagen. 'avatar_img'=> 'https://randomuser.me/api/portraits/men/'.rand(1,10).'.jpg'
Buen día. Me aparece el error que no existe el modelo Reply. Lo cree pero tampoco funciona. Alguna idea?
Hola, Tengo una duda con las relaciones y los paréntesis,porque en algunas ocasiones se utiliza () y otras no? saludos
Para los avatar hice un getAvatarAttribute() en el Model User con este servicio
public function getAvatarAttribute()
{
return "https://ui-avatars.com/api/?name=".$this->name;
}
Y en la vista del componente no lo usé como método sino como campo así:
<img src="{{ $thread->user->avatar }}" alt="{{ $thread->user->name }}" class="rounded-full">
toda la clase estuvo bien, pero cuando puso lo de contar comentarios no entendi.
Entendí que se crea la relacion desde thread para acceder a los comentarios. pero la forma en la que le dice al controlador livewire que cuente los comentarios ya no.

Me mato! jajaja >:'v
Mis temores acerca de que no podia trabajar como programador por no saber lo suficiente no eran infundados, el full stack esta a otro level.
Excelente vamos bien, fascinado con Liveware.

arreglen el lag de su plataforma por favor, quitan la concentracion cuando uno esta tomando un curso