24 🛠️ Detalles de Usuario: Layout de 2 Columnas, Breadcrumbs y Gestión de Imágenes | Laravel 12 🚀
Duración: 11 minDescripción
🛠️ Lección 24: Detalles de Usuario: Layout de 2 Columnas y Gestión de Imágenes
En esta lección de Benji V2, nos enfocamos en la experiencia de usuario (UX) al implementar la vista de detalles (Show) para el módulo de usuarios. Aprendemos a organizar información compleja de forma estética, responsiva y funcional.
🎨 Diseño de la Interfaz (Vista Show)
Transformamos la visualización de datos planos en un perfil de usuario profesional:
- 📐 Layout de 2 Columnas: Organizamos la información en dos secciones para aprovechar el espacio, separando los datos personales de la información de contacto y seguridad [03:33].
- 🧭 Breadcrumbs Dinámicos: Implementamos una barra de navegación (Inicio > Listado de Usuarios > Datos del Usuario) para que el administrador siempre sepa dónde se encuentra [04:43].
- 🖼️ Gestión de Fotos de Perfil: Configuramos la visualización de la imagen del usuario. Si el usuario no tiene una foto cargada, el sistema muestra un avatar por defecto de forma elegante [03:03].
🚦 Estados e Indicadores Visuales
Mejoramos la lectura rápida de la información mediante señales de color:
- 🟢 Indicador de Activo/Inactivo: Refinamos el diseño del estado del usuario. Utilizamos tonos verdes (green-500) para usuarios activos y rojos (red-500) para inactivos, asegurando que el diseño sea consistente tanto en Modo Claro como en Modo Oscuro [06:44].
- 🕒 Auditoría Visual: Mostramos campos de tiempo como la fecha de registro y la última actualización para un control administrativo más preciso [03:51].
🛡️ Seguridad en la Jerarquía de Roles
Reforzamos la protección del sistema restringiendo el acceso al rol más sensible:
- 🚫 Protección del Super Administrador: Ajustamos el controlador de roles para que el rol "Super Administrador" sea invisible en los listados generales. Esto evita que pueda ser editado o eliminado accidentalmente por otros administradores, garantizando la estabilidad del sistema [08:49].
- 👥 Inclusión de Clientes: Preparamos el terreno para futuros módulos añadiendo el rol de "Cliente" directamente desde el Seeder [10:01].
✅ Resultado de la Lección
Al finalizar, el sistema cuenta con una pantalla de perfil de usuario completa y responsive [04:08]. La información está categorizada lógicamente, los estados son fáciles de identificar y la estructura de roles del sistema ha quedado protegida contra manipulaciones indebidas.
Contenido
Código fuente de la lección
<x-layouts.app title="Detalles del Usuario">
<flux:breadcrumbs>
<flux:breadcrumbs.item href="{{ url('/admin') }}">Inicio</flux:breadcrumbs.item>
<flux:breadcrumbs.item href="{{ url('/admin/usuarios') }}">Listado de Usuarios</flux:breadcrumbs.item>
<flux:breadcrumbs.item>Datos del usuario: {{ $usuario->name }}</flux:breadcrumbs.item>
</flux:breadcrumbs>
<br>
<flux:separator variant="subtle" />
{{-- Card Principal --}}
<div class="bg-white dark:bg-neutral-800 border-t border-gray-200 dark:border-gray-700 rounded-lg shadow-lg">
<div class="p-6">
<div
class="flex flex-col md:flex-row items-center gap-6 mb-8 bg-slate-50 dark:bg-neutral-700/30 p-6 rounded-2xl">
<div class="relative">
<div
class="h-32 w-32 rounded-full border-4 border-white dark:border-neutral-800 shadow-md overflow-hidden bg-slate-200 flex items-center justify-center">
@if ($usuario->foto_perfil)
<img src="{{ asset('storage/' . $usuario->foto_perfil) }}" alt="Foto"
class="h-full w-full object-cover">
@else
<flux:icon name="user" class="text-slate-400 h-16 w-16" />
@endif
</div>
</div>
<div class="text-center md:text-left">
<flux:heading level="2" size="xl">{{ $usuario->nombres }} {{ $usuario->apellidos }}
</flux:heading>
<p class="text-blue-600 font-medium">{{ $usuario->getRoleNames()->first() ?? 'Sin Rol' }}</p>
<div class="mt-2 flex flex-wrap justify-center md:justify-start gap-3">
<span
class="px-3 py-1 bg-white dark:bg-neutral-800 rounded-full text-xs shadow-sm border border-slate-200 dark:border-neutral-600">
ID: #{{ $usuario->id }}
</span>
<span
class="px-3 py-1 bg-white dark:bg-neutral-800 rounded-full text-xs shadow-sm border border-slate-200 dark:border-neutral-600">
Desde: {{ $usuario->created_at->format('d/m/Y') }}
</span>
@if ($usuario->estado == 'Activo')
<span
class="inline-flex items-center gap-1.5 py-1 px-3 rounded-full text-xs font-medium bg-green-100 text-green-700 dark:bg-green-500/10 dark:text-green-400 border border-green-200 dark:border-green-500/20">
<span class="relative flex h-2 w-2">
<span
class="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"></span>
<span class="relative inline-flex rounded-full h-2 w-2 bg-green-500"></span>
</span>
{{ $usuario->estado }}
</span>
@else
<span
class="inline-flex items-center gap-1.5 py-1 px-3 rounded-full text-xs font-medium bg-red-100 text-red-700 dark:bg-red-500/10 dark:text-red-400 border border-red-200 dark:border-red-500/20">
<span class="h-2 w-2 rounded-full bg-red-500"></span>
{{ $usuario->estado }}
</span>
@endif
</div>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
{{-- Columna Izquierda: Cuenta y Personal --}}
<div class="space-y-6">
<section>
<flux:heading level="3" size="lg" class="mb-4 flex items-center gap-2">
<flux:icon name="identification" variant="micro" class="text-blue-500" />
Datos Personales
</flux:heading>
<div class="grid grid-cols-2 gap-4 bg-slate-50/50 dark:bg-neutral-900/20 p-4 rounded-xl">
<div>
<label class="text-xs text-slate-400 uppercase font-bold">Tipo Doc.</label>
<p class="text-sm text-slate-700 dark:text-neutral-200">{{ $usuario->tipo_documento }}
</p>
</div>
<div>
<label class="text-xs text-slate-400 uppercase font-bold">Nro. Documento</label>
<p class="text-sm text-slate-700 dark:text-neutral-200">
{{ $usuario->numero_documento }}
</p>
</div>
<div>
<label class="text-xs text-slate-400 uppercase font-bold">Género</label>
<p class="text-sm text-slate-700 dark:text-neutral-200">{{ $usuario->genero }}</p>
</div>
<div>
<label class="text-xs text-slate-400 uppercase font-bold">Fecha Nacimiento</label>
<p class="text-sm text-slate-700 dark:text-neutral-200">
{{ $usuario->fecha_nacimiento }}
</p>
</div>
</div>
</section>
<section>
<flux:heading level="3" size="lg" class="mb-4 flex items-center gap-2">
<flux:icon name="map-pin" variant="micro" class="text-blue-500" />
Ubicación y Contacto
</flux:heading>
<div class="space-y-3 bg-slate-50/50 dark:bg-neutral-900/20 p-4 rounded-xl">
<div>
<label class="text-xs text-slate-400 uppercase font-bold">Dirección</label>
<p class="text-sm text-slate-700 dark:text-neutral-200">{{ $usuario->direccion }}</p>
</div>
<div class="flex gap-8">
<div>
<label class="text-xs text-slate-400 uppercase font-bold">Celular</label>
<p class="text-sm text-slate-700 dark:text-neutral-200">{{ $usuario->celular }}</p>
</div>
<div>
<label class="text-xs text-slate-400 uppercase font-bold">Email</label>
<p class="text-sm text-slate-700 dark:text-neutral-200">{{ $usuario->email }}</p>
</div>
</div>
</div>
</section>
</div>
{{-- Columna Derecha: Emergencia y Cuenta --}}
<div class="space-y-6">
<section>
<flux:heading level="3" size="lg" class="mb-4 flex items-center gap-2 text-red-600">
<flux:icon name="phone-arrow-up-right" variant="micro" />
Contacto de Emergencia
</flux:heading>
<div class="space-y-3 border-l-4 border-red-100 dark:border-red-900/30 pl-4 py-2">
<div>
<label class="text-xs text-slate-400 uppercase font-bold">Nombre</label>
<p class="text-sm text-slate-700 dark:text-neutral-200 font-medium">
{{ $usuario->contacto_nombre }}</p>
</div>
<div class="flex gap-8">
<div>
<label class="text-xs text-slate-400 uppercase font-bold">Parentesco</label>
<p class="text-sm text-slate-700 dark:text-neutral-200">
{{ $usuario->contacto_relacion }}</p>
</div>
<div>
<label class="text-xs text-slate-400 uppercase font-bold">Teléfono</label>
<p class="text-sm text-slate-700 dark:text-neutral-200">
{{ $usuario->contacto_telefono }}</p>
</div>
</div>
</div>
</section>
<section>
<flux:heading level="3" size="lg" class="mb-4 flex items-center gap-2">
<flux:icon name="shield-check" variant="micro" class="text-blue-500" />
Seguridad del Sistema
</flux:heading>
<div
class="bg-blue-50/30 dark:bg-blue-900/10 p-4 rounded-xl border border-blue-100 dark:border-blue-900/30">
<label class="text-xs text-slate-400 uppercase font-bold">Nombre de acceso (User)</label>
<p class="text-sm text-blue-700 dark:text-blue-300 font-mono">{{ $usuario->name }}</p>
<div class="mt-4 p-3 bg-white dark:bg-neutral-800 rounded-lg text-xs text-slate-500">
<i class="fas fa-info-circle mr-1 text-blue-500"></i>
Última actualización: {{ $usuario->updated_at->diffForHumans() }}
</div>
</div>
</section>
</div>
</div>
</div>
{{-- Footer con Botones --}}
<div class="bg-gray-50 dark:bg-neutral-700 border-t border-gray-200 dark:border-gray-700 rounded-b-lg p-6">
<div class="flex flex-wrap gap-3 justify-between">
<a href="{{ url('/admin/usuarios') }}"
class="px-5 py-2 text-sm font-medium text-gray-600 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 transition-all inline-flex items-center">
<i class="fas fa-arrow-left mr-2"></i> Volver al listado
</a>
</div>
</div>
</div>
</x-layouts.app>