Files
sirpremiumv2/resources/views/livewire/chat/public-chat.blade.php
T
LizandroandClaude Sonnet 4.6 f4f689891c feat: chat público con sensación de WhatsApp/Telegram real
Bugs corregidos:
- La imagen del comprobante nunca se veía: se guardaba en el disco 'public'
  y se servía como asset('storage/...'), pero este proyecto no tiene el
  symlink public/storage. Ahora usa store('photos') + asset($path), la
  convención del resto del código (config/filesystems.php links).
- Dos wire:poll en el mismo nodo: Livewire v2 solo respeta el primero, así
  que autoReintentarPago nunca se ejecutaba. Ahora va en su propio elemento
  y solo se renderiza mientras hay un pago pendiente.
- Sin wire:key en los mensajes, morphdom recreaba toda la lista cada 3s:
  parpadeo, scroll saltando y estado Alpine perdido (el "¡Copiado!" de las
  credenciales desaparecía solo).
- pollMensajes cargaba la conversación entera cada 3s. Ahora consulta solo
  MAX(id) y recarga únicamente si llegó algo nuevo; el historial se limita
  a los 60 mensajes más recientes.
- El upload no se validaba: se añade regla image/mimes/max 8MB.
- Se elimina $procesandoImagen, propiedad muerta que viajaba en cada payload.

Interfaz:
- Envío optimista: la burbuja del usuario aparece al instante, sin esperar
  el round-trip, con reloj de "pendiente" y checks al confirmarse.
- Indicador "escribiendo…" con puntos animados mientras el bot responde.
- Visor de imagen a pantalla completa al tocar el comprobante.
- Textarea que crece con el texto (Shift+Enter para salto de línea),
  con wire:ignore para que el poll no le reinicie la altura al escribir.
- Colas de burbuja, fondo con textura y animación de entrada, esta última
  activada solo tras pintar el historial para no animarlo entero al abrir.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-01 13:26:01 +00:00

789 lines
62 KiB
PHP
Executable File

<div id="chat-root" class="flex flex-col bg-[#111b21] w-full overflow-hidden" style="height:100vh;height:100dvh;height:-webkit-fill-available;">
{{-- ══ PASO 1: Ingreso de correo ══ --}}
@if ($paso === 'email')
<div class="flex flex-col items-center justify-center flex-1 px-5 safe-top safe-bottom safe-left safe-right">
<div class="w-full bg-[#202c33] rounded-2xl shadow-2xl p-7">
<div class="flex flex-col items-center mb-7">
<div class="w-20 h-20 bg-[#00a884] rounded-full flex items-center justify-center mb-4 shadow-lg">
<svg class="w-10 h-10 text-white" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M8.625 9.75a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H8.25m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H12m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0h-.375M21 12c0 4.556-4.03 8.25-9 8.25a9.764 9.764 0 0 1-2.555-.337A5.972 5.972 0 0 1 5.41 20.97a5.969 5.969 0 0 1-.474-.065 4.48 4.48 0 0 0 .978-2.025c.09-.457-.133-.901-.467-1.226C3.93 16.178 3 14.189 3 12c0-4.556 4.03-8.25 9-8.25s9 3.694 9 8.25Z" />
</svg>
</div>
<h1 class="text-white text-2xl font-semibold">Chat en vivo</h1>
<p class="text-[#8696a0] text-sm mt-1 text-center">Ingresa tu correo para comenzar</p>
</div>
<form wire:submit.prevent="iniciar" class="space-y-4">
<div>
<label class="block text-[#8696a0] text-xs font-medium mb-1.5 uppercase tracking-wider">Correo electrónico *</label>
<input wire:model.defer="email" type="email" inputmode="email" autocomplete="email"
placeholder="tucorreo@ejemplo.com"
class="w-full bg-[#2a3942] text-white rounded-xl px-4 py-3.5 outline-none border border-transparent focus:border-[#00a884] transition placeholder-[#8696a0]"
style="color-scheme: dark;">
@error('email') <span class="text-red-400 text-xs mt-1 block">{{ $message }}</span> @enderror
</div>
<button type="submit"
class="w-full bg-[#00a884] active:bg-[#06cf9c] text-white font-semibold rounded-xl transition shadow-lg"
style="min-height: 48px;">
<span wire:loading.remove wire:target="iniciar">Continuar &rarr;</span>
<span wire:loading wire:target="iniciar">Verificando...</span>
</button>
</form>
</div>
</div>
{{-- ══ PASO 2: No existe ofrecer registro ══ --}}
@elseif ($paso === 'registro')
<div class="flex flex-col items-center justify-center flex-1 px-5 safe-top safe-bottom safe-left safe-right">
<div class="w-full bg-[#202c33] rounded-2xl shadow-2xl p-7">
<div class="flex flex-col items-center mb-6">
<div class="w-16 h-16 bg-yellow-500 rounded-full flex items-center justify-center mb-4 shadow-lg">
<svg class="w-8 h-8 text-white" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 6a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0ZM4.501 20.118a7.5 7.5 0 0 1 14.998 0A17.933 17.933 0 0 1 12 21.75c-2.676 0-5.216-.584-7.499-1.632Z" />
</svg>
</div>
<h1 class="text-white text-lg font-semibold text-center">No encontramos tu cuenta</h1>
<p class="text-[#8696a0] text-sm mt-2 text-center leading-relaxed">
No existe una cuenta con el correo<br>
<span class="text-white font-medium">{{ $email }}</span>
</p>
<p class="text-[#8696a0] text-sm mt-3 text-center">¿Deseas crear una cuenta?</p>
</div>
<form wire:submit.prevent="crearCuenta" class="space-y-4">
<div>
<label class="block text-[#8696a0] text-xs font-medium mb-1.5 uppercase tracking-wider">Nombre completo *</label>
<input wire:model.defer="nombreRegistro" type="text" autocomplete="name"
placeholder="¿Cómo te llamas?"
class="w-full bg-[#2a3942] text-white rounded-xl px-4 py-3.5 outline-none border border-transparent focus:border-[#00a884] transition placeholder-[#8696a0]"
style="color-scheme: dark;">
@error('nombreRegistro') <span class="text-red-400 text-xs mt-1 block">{{ $message }}</span> @enderror
</div>
<div>
<label class="block text-[#8696a0] text-xs font-medium mb-1.5 uppercase tracking-wider">Cédula</label>
<input wire:model.defer="cedulaRegistro" type="text" inputmode="numeric"
placeholder="Número de cédula"
class="w-full bg-[#2a3942] text-white rounded-xl px-4 py-3.5 outline-none border border-transparent focus:border-[#00a884] transition placeholder-[#8696a0]"
style="color-scheme: dark;">
@error('cedulaRegistro') <span class="text-red-400 text-xs mt-1 block">{{ $message }}</span> @enderror
</div>
<div>
<label class="block text-[#8696a0] text-xs font-medium mb-1.5 uppercase tracking-wider">Celular</label>
<input wire:model.defer="celularRegistro" type="tel" inputmode="tel" autocomplete="tel"
placeholder="Ej: 3001234567"
class="w-full bg-[#2a3942] text-white rounded-xl px-4 py-3.5 outline-none border border-transparent focus:border-[#00a884] transition placeholder-[#8696a0]"
style="color-scheme: dark;">
@error('celularRegistro') <span class="text-red-400 text-xs mt-1 block">{{ $message }}</span> @enderror
</div>
<button type="submit"
class="w-full bg-[#00a884] active:bg-[#06cf9c] text-white font-semibold rounded-xl transition shadow-lg"
style="min-height: 48px;">
<span wire:loading.remove wire:target="crearCuenta">, crear cuenta &rarr;</span>
<span wire:loading wire:target="crearCuenta">Creando cuenta...</span>
</button>
</form>
<button wire:click="rechazarRegistro" type="button"
class="w-full mt-3 text-[#8696a0] text-sm hover:text-white transition py-2">
No, volver
</button>
</div>
</div>
{{-- ══ PASO 4: Verificación OTP ══ --}}
@elseif ($paso === 'verificacion')
<div class="flex flex-col items-center justify-center flex-1 px-5 safe-top safe-bottom safe-left safe-right">
<div class="w-full bg-[#202c33] rounded-2xl shadow-2xl p-7">
<div class="flex flex-col items-center mb-7">
<div class="w-20 h-20 bg-[#00a884] rounded-full flex items-center justify-center mb-4 shadow-lg">
<svg class="w-10 h-10 text-white" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round"
d="M21.75 6.75v10.5a2.25 2.25 0 0 1-2.25 2.25h-15a2.25 2.25 0 0 1-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25m19.5 0v.243a2.25 2.25 0 0 1-1.07 1.916l-7.5 4.615a2.25 2.25 0 0 1-2.36 0L3.32 8.91a2.25 2.25 0 0 1-1.07-1.916V6.75" />
</svg>
</div>
<h1 class="text-white text-xl font-semibold">Verifica tu identidad</h1>
<p class="text-[#8696a0] text-sm mt-2 text-center leading-relaxed">
Enviamos un código de 6 dígitos a<br>
<span class="text-[#00a884] font-semibold">{{ $emailMascarado }}</span>
</p>
</div>
@if (session('otp_reenviado'))
<p class="text-[#00a884] text-sm text-center mb-3">{{ session('otp_reenviado') }}</p>
@endif
@if ($errorEnvioOtp)
<div class="bg-red-900/40 border border-red-500/50 rounded-xl px-4 py-3 mb-3 text-red-300 text-sm text-center">
{{ $errorEnvioOtp }}
</div>
@endif
<form wire:submit.prevent="verificarCodigo" class="space-y-4">
<div>
<label class="block text-[#8696a0] text-xs font-medium mb-1.5 uppercase tracking-wider">Código de verificación</label>
<input wire:model.defer="codigoIngresado" type="text" inputmode="numeric"
pattern="[0-9]*" maxlength="6" autocomplete="off"
placeholder="000000"
class="w-full bg-[#2a3942] text-white rounded-xl px-4 py-3.5 outline-none border border-transparent focus:border-[#00a884] transition placeholder-[#8696a0] text-center text-2xl font-bold tracking-[0.5em]"
style="color-scheme: dark;">
@error('codigoIngresado') <span class="text-red-400 text-xs mt-1 block">{{ $message }}</span> @enderror
</div>
<button type="submit"
class="w-full bg-[#00a884] active:bg-[#06cf9c] text-white font-semibold rounded-xl transition shadow-lg"
style="min-height: 48px;">
<span wire:loading.remove wire:target="verificarCodigo">Verificar &rarr;</span>
<span wire:loading wire:target="verificarCodigo">Verificando...</span>
</button>
</form>
<div class="mt-5 flex flex-col items-center gap-2">
<button wire:click="reenviarCodigo" type="button"
class="text-[#00a884] text-sm hover:underline">
<span wire:loading.remove wire:target="reenviarCodigo">¿No llegó? Reenviar código</span>
<span wire:loading wire:target="reenviarCodigo">Enviando...</span>
</button>
<button wire:click="$set('paso', 'email')" type="button"
class="text-[#8696a0] text-xs hover:text-white">
Volver
</button>
</div>
</div>
</div>
{{-- ══ PASO 3: Interfaz de chat ══ --}}
@else
<div class="flex-1 flex flex-col min-h-0 chat-bg relative"
x-data="{
uploading: false,
previewUrl: null,
pendiente: null,
lightbox: null,
enviarTexto() {
const ta = this.$refs.entrada;
const t = (ta?.value ?? '').trim();
if (! t) return;
this.pendiente = t; // burbuja optimista: se ve al instante
ta.value = '';
this.autoGrow();
this.$nextTick(() => window.dispatchEvent(new CustomEvent('scroll-chat')));
try {
Promise.resolve(this.$wire.enviar(t)).finally(() => this.pendiente = null);
} catch (e) {
// Si el envío no sale, devolvemos el texto al cuadro para no perderlo
this.pendiente = null;
ta.value = t;
this.autoGrow();
}
},
autoGrow() {
const ta = this.$refs.entrada;
if (! ta) return;
ta.style.height = 'auto';
ta.style.height = Math.min(ta.scrollHeight, 112) + 'px';
}
}"
x-on:livewire-upload-start.window="uploading = true"
x-on:livewire-upload-finish.window="uploading = false; previewUrl = null"
x-on:livewire-upload-error.window="uploading = false; previewUrl = null">
{{-- Header --}}
<div class="bg-[#202c33] shadow-md flex-shrink-0 safe-top safe-left safe-right">
<div class="flex items-center gap-3 px-4 py-3">
<div class="w-10 h-10 bg-[#00a884] rounded-full flex items-center justify-center flex-shrink-0">
<svg class="w-5 h-5 text-white" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M8.625 9.75a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H8.25m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H12m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0h-.375M21 12c0 4.556-4.03 8.25-9 8.25a9.764 9.764 0 0 1-2.555-.337A5.972 5.972 0 0 1 5.41 20.97a5.969 5.969 0 0 1-.474-.065 4.48 4.48 0 0 0 .978-2.025c.09-.457-.133-.901-.467-1.226C3.93 16.178 3 14.189 3 12c0-4.556 4.03-8.25 9-8.25s9 3.694 9 8.25Z" />
</svg>
</div>
<div class="flex-1 min-w-0">
<p class="text-white font-semibold text-sm truncate">
{{ $nombreUsuario ?: 'Soporte en línea' }}
</p>
<p class="text-[#8696a0] text-xs">
@if ($estadoConv === 'agente') Atendido por un agente
@elseif ($estadoConv === 'cerrada') Conversación cerrada
@else Bot activo @endif
</p>
</div>
{{-- Saldo si hay usuario identificado --}}
@if ($saldoUsuario !== null)
<div class="text-right flex-shrink-0">
<p class="text-[#00a884] text-xs font-semibold">Saldo</p>
<p class="text-white text-sm font-bold">${{ number_format($saldoUsuario) }}</p>
</div>
@endif
{{-- Menú de opciones --}}
<div class="flex-shrink-0 ml-1 relative" x-data="{ open: false }" @click.outside="open = false">
<button @click="open = !open"
class="p-2 rounded-full hover:bg-white/10 transition text-[#8696a0]">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
<circle cx="12" cy="5" r="1.5"/>
<circle cx="12" cy="12" r="1.5"/>
<circle cx="12" cy="19" r="1.5"/>
</svg>
</button>
<div x-show="open" x-transition
class="absolute right-0 top-10 w-44 bg-[#233138] rounded-lg shadow-xl z-50 overflow-hidden">
<button wire:click="limpiarChat" @click="open = false"
class="w-full flex items-center gap-3 px-4 py-3 text-sm text-white hover:bg-white/10 transition text-left">
<svg class="w-4 h-4 text-[#8696a0]" fill="none" viewBox="0 0 24 24" stroke-width="1.8" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
</svg>
Limpiar chat
</button>
<div class="border-t border-white/10"></div>
<button wire:click="cerrarSesion" @click="open = false"
class="w-full flex items-center gap-3 px-4 py-3 text-sm text-red-400 hover:bg-white/10 transition text-left">
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke-width="1.8" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 9V5.25A2.25 2.25 0 0 0 13.5 3h-6a2.25 2.25 0 0 0-2.25 2.25v13.5A2.25 2.25 0 0 0 7.5 21h6a2.25 2.25 0 0 0 2.25-2.25V15M12 9l-3 3m0 0 3 3m-3-3h12.75" />
</svg>
Cerrar sesión
</button>
</div>
</div>
</div>
</div>
{{-- Badge: nuevos mensajes mientras se lee el historial --}}
<div id="new-msg-badge" class="hidden absolute left-0 right-0 flex justify-center z-20 pointer-events-none"
style="bottom:80px;">
<button id="new-msg-badge-btn"
class="pointer-events-auto flex items-center gap-2 bg-[#00a884] text-white text-xs font-semibold px-4 py-2 rounded-full shadow-xl active:bg-[#06cf9c] transition">
<svg class="w-3.5 h-3.5 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 13.5 12 21m0 0-7.5-7.5M12 21V3" />
</svg>
<span id="new-msg-count">1</span>&nbsp;mensaje nuevo
</button>
</div>
{{-- Reintento automático de validación de pago.
Va en su propio elemento: Livewire v2 solo respeta el PRIMER
wire:poll de un mismo nodo, así que compartirlo con el poll de
mensajes hacía que este nunca se ejecutara. Solo se renderiza
mientras hay un pago pendiente, así no gasta requests. --}}
@if ($flujo === 'pago.pendiente')
<div wire:poll.20000ms="autoReintentarPago" class="w-0 h-0 overflow-hidden" aria-hidden="true"></div>
@endif
{{-- Mensajes --}}
<div class="flex-1 min-h-0 overflow-y-auto px-3 py-4 space-y-2 ios-scroll"
id="pub-messages"
wire:poll.3000ms="pollMensajes"
style="overscroll-behavior-y:contain;-webkit-overflow-scrolling:touch;">
@forelse($mensajes as $msg)
{{-- ── Mensaje del USUARIO ── --}}
@if ($msg['tipo'] === 'usuario')
<div class="flex justify-end msg-in" wire:key="msg-{{ $msg['id'] }}">
@if (($msg['tipo_ui'] ?? 'text') === 'imagen')
<div class="max-w-[75%] bg-[#005c4b] rounded-xl rounded-tr-sm overflow-hidden shadow bubble-out">
<img src="{{ $msg['payload']['url'] ?? '' }}"
loading="lazy" decoding="async" alt="Comprobante enviado"
class="w-full object-cover cursor-zoom-in bg-[#0b141a]"
style="max-height:260px; min-height:120px;"
x-on:click="lightbox = '{{ $msg['payload']['url'] ?? '' }}'"
x-on:error="$el.classList.add('img-rota')">
<div class="px-3 py-1 flex items-center justify-end gap-1">
<span class="text-white/60 text-[11px]">{{ $msg['created_at'] }}</span>
<svg class="w-4 h-4 text-[#53bdeb]" viewBox="0 0 16 15" fill="currentColor"><path d="M15.01 3.316l-.478-.372a.365.365 0 0 0-.51.063L8.666 9.879a.32.32 0 0 1-.484.033l-.358-.325a.319.319 0 0 0-.484.032l-.378.483a.418.418 0 0 0 .036.541l1.32 1.266c.143.14.361.125.484-.033l6.272-8.048a.366.366 0 0 0-.064-.512zm-4.1 0l-.478-.372a.365.365 0 0 0-.51.063L4.566 9.879a.32.32 0 0 1-.484.033L1.891 7.769a.366.366 0 0 0-.515.006l-.423.433a.364.364 0 0 0 .006.514l3.258 3.185c.143.14.361.125.484-.033l6.272-8.048a.365.365 0 0 0-.063-.51z"/></svg>
</div>
</div>
@else
<div class="max-w-[80%] bg-[#005c4b] text-white rounded-xl rounded-tr-sm px-3 py-2 shadow bubble-out">
<p class="whitespace-pre-line break-words leading-relaxed" style="font-size:15px;">{{ $msg['contenido'] }}</p>
<span class="inline-flex items-center gap-1 float-right mt-1 ml-2 translate-y-1">
<span class="text-white/60 text-[11px]">{{ $msg['created_at'] }}</span>
<svg class="w-4 h-4 text-[#53bdeb]" viewBox="0 0 16 15" fill="currentColor"><path d="M15.01 3.316l-.478-.372a.365.365 0 0 0-.51.063L8.666 9.879a.32.32 0 0 1-.484.033l-.358-.325a.319.319 0 0 0-.484.032l-.378.483a.418.418 0 0 0 .036.541l1.32 1.266c.143.14.361.125.484-.033l6.272-8.048a.366.366 0 0 0-.064-.512zm-4.1 0l-.478-.372a.365.365 0 0 0-.51.063L4.566 9.879a.32.32 0 0 1-.484.033L1.891 7.769a.366.366 0 0 0-.515.006l-.423.433a.364.364 0 0 0 .006.514l3.258 3.185c.143.14.361.125.484-.033l6.272-8.048a.365.365 0 0 0-.063-.51z"/></svg>
</span>
</div>
@endif
</div>
{{-- ── Mensajes del BOT / AGENTE ── --}}
@else
<div class="flex justify-start msg-in" wire:key="msg-{{ $msg['id'] }}">
@php $tipoUi = $msg['tipo_ui'] ?? 'text'; $payload = $msg['payload'] ?? []; @endphp
{{-- TEXTO normal --}}
@if ($tipoUi === 'text')
<div class="max-w-[80%] bg-[#202c33] text-white rounded-xl rounded-tl-sm px-3 py-2 shadow bubble-in">
@if ($msg['tipo'] === 'agente')
<span class="text-amber-400 text-[11px] font-semibold block mb-0.5">Agente</span>
@endif
<p class="whitespace-pre-line break-words leading-relaxed" style="font-size:15px;">{{ $msg['contenido'] }}</p>
<span class="text-[#8696a0] text-[11px] float-right mt-1 ml-2">{{ $msg['created_at'] }}</span>
</div>
{{-- BUTTONS: texto + fila de botones --}}
@elseif ($tipoUi === 'buttons')
<div class="max-w-[88%] space-y-2">
@if ($msg['contenido'])
<div class="bg-[#202c33] text-white rounded-xl rounded-tl-sm px-4 py-2 shadow">
<p class="whitespace-pre-line break-words leading-relaxed" style="font-size:15px;">{{ $msg['contenido'] }}</p>
<span class="text-[#8696a0] text-[11px] float-right mt-1 ml-2">{{ $msg['created_at'] }}</span>
</div>
@endif
<div class="flex flex-wrap gap-2">
@foreach ($payload['botones'] ?? [] as $btn)
@if ($btn['disabled'] ?? false)
<button disabled
class="px-3 py-2 rounded-xl text-sm font-medium border border-[#3d4a51] text-[#3d4a51] bg-transparent cursor-not-allowed opacity-50">
{{ $btn['label'] }}
</button>
@else
<button wire:click="clickBoton('{{ $btn['action'] }}', {{ json_encode($btn['data'] ?? []) }})"
class="px-3 py-2 rounded-xl text-sm font-medium border border-[#00a884] text-[#00a884] bg-transparent active:bg-[#00a884]/20 transition">
{{ $btn['label'] }}
</button>
@endif
@endforeach
</div>
</div>
{{-- CARD: tarjeta de servicio o promo --}}
@elseif ($tipoUi === 'card')
<div class="max-w-[85%] bg-[#202c33] rounded-xl rounded-tl-sm shadow overflow-hidden">
@if (!empty($payload['imagen']))
<img src="{{ Str::startsWith($payload['imagen'], ['http','//']) ? $payload['imagen'] : asset($payload['imagen']) }}" class="w-full object-cover" style="max-height:130px;">
@endif
<div class="px-4 py-3">
<p class="text-white font-semibold text-sm">{{ $payload['titulo'] ?? '' }}</p>
@if (!empty($payload['descripcion']))
<p class="text-[#8696a0] text-xs mt-1">{{ $payload['descripcion'] }}</p>
@endif
@if (!empty($payload['precio']))
<p class="text-[#00a884] font-bold text-base mt-2">${{ number_format($payload['precio']) }}</p>
@endif
@if (!empty($payload['detalle']))
<p class="text-[#8696a0] text-xs">{{ $payload['detalle'] }}</p>
@endif
@if (!empty($payload['accion']))
<button wire:click="clickBoton('{{ $payload['accion']['action'] }}', {{ json_encode($payload['accion']['data'] ?? []) }})"
class="mt-3 w-full bg-[#00a884] text-white text-sm font-semibold rounded-xl py-2 active:bg-[#06cf9c] transition">
{{ $payload['accion']['label'] }}
</button>
@endif
</div>
<div class="px-4 pb-2 text-right">
<span class="text-[#8696a0] text-[11px]">{{ $msg['created_at'] }}</span>
</div>
</div>
{{-- CARDS SLIDER: servicios / promos en carrusel horizontal --}}
@elseif ($tipoUi === 'cards_slider')
<div class="w-full max-w-full space-y-2">
@if ($msg['contenido'])
<div class="bg-[#202c33] text-white rounded-xl rounded-tl-sm px-4 py-2 shadow inline-block max-w-[80%]">
<p class="text-sm">{{ $msg['contenido'] }}</p>
<span class="text-[#8696a0] text-[11px] float-right mt-1 ml-2">{{ $msg['created_at'] }}</span>
</div>
@endif
{{-- Scroll horizontal de cards --}}
<div class="flex gap-3 overflow-x-auto pb-2"
style="-webkit-overflow-scrolling:touch; scroll-snap-type:x mandatory; scrollbar-width:none;">
@foreach ($payload['cards'] ?? [] as $card)
<div class="flex-shrink-0 bg-[#202c33] rounded-xl shadow overflow-hidden"
style="width:180px; scroll-snap-align:start;">
@if (!empty($card['imagen']))
<img src="{{ Str::startsWith($card['imagen'], ['http','//']) ? $card['imagen'] : asset($card['imagen']) }}" class="w-full object-cover" style="height:100px;">
@else
<div class="w-full bg-[#2a3942] flex items-center justify-center" style="height:80px;">
<svg class="w-8 h-8 text-[#8696a0]" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="m15.75 10.5 4.72-4.72a.75.75 0 0 1 1.28.53v11.38a.75.75 0 0 1-1.28.53l-4.72-4.72M4.5 18.75h9a2.25 2.25 0 0 0 2.25-2.25v-9a2.25 2.25 0 0 0-2.25-2.25h-9A2.25 2.25 0 0 0 2.25 7.5v9a2.25 2.25 0 0 0 2.25 2.25Z" />
</svg>
</div>
@endif
<div class="px-3 py-2.5">
<p class="text-white text-sm font-semibold truncate">{{ $card['titulo'] ?? '' }}</p>
@if (!empty($card['precio']))
<p class="text-[#00a884] font-bold text-sm mt-0.5">${{ number_format($card['precio']) }}</p>
@endif
@if (!empty($card['detalle']))
<p class="text-[#8696a0] text-xs mt-0.5 truncate">{{ $card['detalle'] }}</p>
@endif
@if (!empty($card['descripcion']))
<p class="text-[#8696a0] text-xs mt-0.5 line-clamp-2">{{ $card['descripcion'] }}</p>
@endif
@if (!empty($card['accion']))
<button wire:click="clickBoton('{{ $card['accion']['action'] }}', {{ json_encode($card['accion']['data'] ?? []) }})"
class="mt-2.5 w-full bg-[#00a884] text-white text-xs font-semibold rounded-lg py-1.5 active:bg-[#06cf9c] transition">
{{ $card['accion']['label'] }}
</button>
@endif
</div>
</div>
@endforeach
</div>
</div>
{{-- LINK: botón de pago externo --}}
@elseif ($tipoUi === 'link')
<div class="max-w-[85%] bg-[#202c33] rounded-xl rounded-tl-sm px-4 py-3 shadow">
<p class="text-white text-sm mb-3 whitespace-pre-line">{{ $msg['contenido'] }}</p>
<a href="{{ $payload['url'] ?? '#' }}" target="_blank"
class="flex items-center justify-center gap-2 w-full bg-[#00a884] text-white text-sm font-semibold rounded-xl py-2.5 active:bg-[#06cf9c] transition">
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M13.5 6H5.25A2.25 2.25 0 0 0 3 8.25v10.5A2.25 2.25 0 0 0 5.25 21h10.5A2.25 2.25 0 0 0 18 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" />
</svg>
{{ $payload['label'] ?? 'Ir a pagar' }}
</a>
@if (!empty($payload['nota']))
<p class="text-[#8696a0] text-xs mt-2 text-center">{{ $payload['nota'] }}</p>
@endif
<div class="mt-2 text-right">
<span class="text-[#8696a0] text-[11px]">{{ $msg['created_at'] }}</span>
</div>
</div>
{{-- VALIDACION: resultado de comprobante de pago --}}
@elseif ($tipoUi === 'validacion')
<div class="max-w-[88%] bg-[#202c33] rounded-xl rounded-tl-sm shadow overflow-hidden">
{{-- Header estado --}}
@php
$estado = $payload['estado'] ?? 'no_encontrado';
$esConfirmado = $estado === 'confirmado';
$esIncorrecto = $estado === 'monto_incorrecto';
@endphp
<div class="px-4 py-2 {{ $esConfirmado ? 'bg-green-900/40' : ($esIncorrecto ? 'bg-amber-900/40' : 'bg-red-900/30') }}">
<p class="text-sm font-semibold {{ $esConfirmado ? 'text-green-400' : ($esIncorrecto ? 'text-amber-400' : 'text-red-400') }}">
@if ($esConfirmado) Pago confirmado
@elseif ($esIncorrecto) ⚠️ Monto no coincide
@else Verificando pago...
@endif
</p>
</div>
{{-- Datos extraídos --}}
<div class="px-4 py-3 space-y-1">
<p class="text-[#8696a0] text-xs font-semibold uppercase tracking-wider mb-2">Datos del comprobante</p>
@foreach (['banco' => 'Banco', 'valor' => 'Valor', 'remitente' => 'De', 'referencia' => 'Referencia', 'fecha' => 'Fecha'] as $key => $label)
@if (!empty($payload['ia_datos'][$key]))
<div class="flex justify-between text-xs">
<span class="text-[#8696a0]">{{ $label }}</span>
<span class="text-white font-medium">{{ $payload['ia_datos'][$key] }}</span>
</div>
@endif
@endforeach
</div>
{{-- Botones de acción --}}
<div class="px-4 pb-3 flex flex-wrap gap-2">
@foreach ($payload['botones'] ?? [] as $btn)
<button wire:click="clickBoton('{{ $btn['action'] }}', {{ json_encode($btn['data'] ?? []) }})"
class="flex-1 px-3 py-2 rounded-xl text-xs font-medium border {{ $esConfirmado ? 'border-green-500 text-green-400' : 'border-[#8696a0] text-[#8696a0]' }} bg-transparent active:bg-white/5 transition">
{{ $btn['label'] }}
</button>
@endforeach
</div>
<div class="px-4 pb-2 text-right">
<span class="text-[#8696a0] text-[11px]">{{ $msg['created_at'] }}</span>
</div>
</div>
{{-- CREDENCIALES: tarjeta con usuario/contraseña --}}
@elseif ($tipoUi === 'credenciales')
<div class="max-w-[85%] bg-[#202c33] rounded-xl rounded-tl-sm shadow overflow-hidden"
x-data="{
copiado: null,
copiarCampo(val, id) {
navigator.clipboard.writeText(val).then(() => { this.copiado = id; setTimeout(() => this.copiado = null, 2000); });
},
copiarTodo() {
let parts = [
'Servicio: ' + ($refs.cred_servicio?.innerText ?? ''),
$refs.cred_email ? 'Email: ' + $refs.cred_email.innerText : '',
$refs.cred_pass ? 'Password: ' + $refs.cred_pass.innerText : '',
$refs.cred_perfil ? 'Perfil: ' + $refs.cred_perfil.innerText : '',
$refs.cred_vence ? 'Vence: ' + $refs.cred_vence.innerText : '',
].filter(Boolean);
navigator.clipboard.writeText(parts.join('\n')).then(() => { this.copiado = 'todo'; setTimeout(() => this.copiado = null, 2000); });
}
}">
{{-- Encabezado servicio --}}
<div class="px-4 py-2 bg-[#00a884]/20 flex items-center justify-between">
<p class="text-[#00a884] text-xs font-semibold uppercase tracking-wider" x-ref="cred_servicio">{{ $payload['servicio'] ?? 'Credenciales' }}</p>
<span class="text-[#00a884] text-[10px]"> Compra exitosa</span>
</div>
{{-- Mensaje de gracias --}}
@if (!empty($msg['contenido']))
<div class="px-4 pt-3 pb-1">
<p class="text-[#e9edef] text-sm leading-snug">{{ $msg['contenido'] }}</p>
</div>
@endif
{{-- Campos con copiar individual --}}
<div class="px-4 py-3 space-y-2">
@if (!empty($payload['email']))
<div class="bg-[#0b141a] rounded-lg px-3 py-2 cursor-pointer active:opacity-70"
@click="copiarCampo($refs.cred_email.innerText, 'email')">
<div class="flex justify-between items-center mb-0.5">
<p class="text-[#8696a0] text-[11px]">Usuario / Email</p>
<svg class="w-3.5 h-3.5 transition" :class="copiado==='email' ? 'text-green-400' : 'text-[#8696a0]'" fill="none" viewBox="0 0 24 24" stroke-width="1.8" stroke="currentColor">
<template x-if="copiado !== 'email'"><path stroke-linecap="round" stroke-linejoin="round" d="M15.666 3.888A2.25 2.25 0 0 0 13.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 0 1-.75.75H9a.75.75 0 0 1-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 0 1-2.25 2.25H6.75A2.25 2.25 0 0 1 4.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 0 1 1.927-.184" /></template>
<template x-if="copiado === 'email'"><path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" /></template>
</svg>
</div>
<p class="text-white text-sm font-mono break-all" x-ref="cred_email">{{ $payload['email'] }}</p>
</div>
@endif
@if (!empty($payload['password']))
<div class="bg-[#0b141a] rounded-lg px-3 py-2 cursor-pointer active:opacity-70"
@click="copiarCampo($refs.cred_pass.innerText, 'pass')">
<div class="flex justify-between items-center mb-0.5">
<p class="text-[#8696a0] text-[11px]">Contraseña</p>
<svg class="w-3.5 h-3.5 transition" :class="copiado==='pass' ? 'text-green-400' : 'text-[#8696a0]'" fill="none" viewBox="0 0 24 24" stroke-width="1.8" stroke="currentColor">
<template x-if="copiado !== 'pass'"><path stroke-linecap="round" stroke-linejoin="round" d="M15.666 3.888A2.25 2.25 0 0 0 13.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 0 1-.75.75H9a.75.75 0 0 1-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 0 1-2.25 2.25H6.75A2.25 2.25 0 0 1 4.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 0 1 1.927-.184" /></template>
<template x-if="copiado === 'pass'"><path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" /></template>
</svg>
</div>
<p class="text-white text-sm font-mono" x-ref="cred_pass">{{ $payload['password'] }}</p>
</div>
@endif
@if (!empty($payload['perfil']))
<div class="bg-[#0b141a] rounded-lg px-3 py-2">
<p class="text-[#8696a0] text-[11px] mb-0.5">Perfil</p>
<p class="text-white text-sm" x-ref="cred_perfil">{{ $payload['perfil'] }}</p>
</div>
@endif
@if (!empty($payload['vence']))
<p class="text-[#8696a0] text-xs text-center">📅 Vence: <span x-ref="cred_vence">{{ $payload['vence'] }}</span></p>
@endif
</div>
{{-- Botón Copiar todo --}}
<div class="px-4 pb-3">
<button @click="copiarTodo()"
class="w-full py-2 rounded-xl text-sm font-semibold transition flex items-center justify-center gap-2"
:class="copiado === 'todo' ? 'bg-green-500/20 text-green-400' : 'bg-[#00a884]/10 text-[#00a884] active:bg-[#00a884]/20'">
<template x-if="copiado !== 'todo'">
<span class="flex items-center gap-1.5">
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke-width="1.8" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M15.666 3.888A2.25 2.25 0 0 0 13.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 0 1-.75.75H9a.75.75 0 0 1-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 0 1-2.25 2.25H6.75A2.25 2.25 0 0 1 4.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 0 1 1.927-.184" /></svg>
Copiar todo
</span>
</template>
<template x-if="copiado === 'todo'">
<span class="flex items-center gap-1.5">
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" /></svg>
¡Copiado!
</span>
</template>
</button>
</div>
<div class="px-4 pb-2 text-right">
<span class="text-[#8696a0] text-[11px]">{{ $msg['created_at'] }}</span>
</div>
</div>
{{-- BANCO: datos de transferencia bancaria --}}
@elseif ($tipoUi === 'banco')
<div class="max-w-[85%] bg-[#202c33] rounded-xl rounded-tl-sm shadow overflow-hidden">
<div class="px-4 py-2.5 bg-blue-900/40 flex items-center gap-2">
<svg class="w-4 h-4 text-blue-400 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 21v-8.25M15.75 21v-8.25M8.25 21v-8.25M3 9l9-6 9 6m-1.5 12V10.332A48.36 48.36 0 0 0 12 9.75c-2.551 0-5.056.2-7.5.582V21M3 21h18M12 6.75h.008v.008H12V6.75Z" />
</svg>
<p class="text-blue-300 text-xs font-semibold uppercase tracking-wider">Pago por Bre-B</p>
</div>
<div class="px-4 py-3 space-y-3">
@if (!empty($payload['banco']))
<div class="flex justify-between items-center">
<span class="text-[#8696a0] text-xs">Banco / Billetera</span>
<span class="text-white text-sm font-semibold">{{ $payload['banco'] }}</span>
</div>
@endif
<div class="bg-[#0b141a] rounded-xl px-4 py-3 text-center"
x-data="{ copiado: false }">
<p class="text-[#8696a0] text-[11px] mb-2">Llave Bre-B</p>
<div class="flex items-center justify-center gap-3">
<p class="text-[#00a884] text-2xl font-bold font-mono tracking-widest select-all">{{ $payload['clave'] ?? '' }}</p>
<button
@click="navigator.clipboard.writeText('{{ $payload['clave'] ?? '' }}').then(() => { copiado = true; setTimeout(() => copiado = false, 2000) })"
class="flex-shrink-0 p-2 rounded-xl transition"
:class="copiado ? 'bg-green-500/20 text-green-400' : 'bg-[#00a884]/20 text-[#00a884] active:bg-[#00a884]/40'">
<template x-if="!copiado">
<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke-width="1.8" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.666 3.888A2.25 2.25 0 0 0 13.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 0 1-.75.75H9a.75.75 0 0 1-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 0 1-2.25 2.25H6.75A2.25 2.25 0 0 1 4.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 0 1 1.927-.184" />
</svg>
</template>
<template x-if="copiado">
<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" />
</svg>
</template>
</button>
</div>
<p x-show="copiado" x-transition class="text-green-400 text-xs mt-1.5">¡Copiado!</p>
</div>
@if (!empty($payload['titular']))
<div class="flex justify-between items-center">
<span class="text-[#8696a0] text-xs">Titular</span>
<span class="text-white text-sm">{{ $payload['titular'] }}</span>
</div>
@endif
<div class="flex justify-between items-center border-t border-white/10 pt-2">
<span class="text-[#8696a0] text-xs">Monto exacto</span>
<span class="text-white text-base font-bold">${{ number_format($payload['monto'] ?? 0) }}</span>
</div>
</div>
<div class="px-4 pb-3">
<div class="bg-amber-900/20 border border-amber-700/30 rounded-lg px-3 py-2 flex items-start gap-2">
<svg class="w-4 h-4 text-amber-400 flex-shrink-0 mt-0.5" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M6.827 6.175A2.31 2.31 0 0 1 5.186 7.23c-.38.054-.757.112-1.134.175C2.999 7.58 2.25 8.507 2.25 9.574V18a2.25 2.25 0 0 0 2.25 2.25h15A2.25 2.25 0 0 0 21.75 18V9.574c0-1.067-.75-1.994-1.802-2.169a47.865 47.865 0 0 0-1.134-.175 2.31 2.31 0 0 1-1.64-1.055l-.822-1.316a2.192 2.192 0 0 0-1.736-1.039 48.774 48.774 0 0 0-5.232 0 2.192 2.192 0 0 0-1.736 1.039l-.821 1.316Z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M16.5 12.75a4.5 4.5 0 1 1-9 0 4.5 4.5 0 0 1 9 0ZM18.75 10.5h.008v.008h-.008V10.5Z" />
</svg>
<p class="text-amber-300 text-xs">Envía el comprobante con el ícono 📷 de abajo y el sistema lo validará automáticamente</p>
</div>
</div>
<div class="px-4 pb-2 text-right">
<span class="text-[#8696a0] text-[11px]">{{ $msg['created_at'] }}</span>
</div>
</div>
{{-- fallback --}}
@else
<div class="max-w-[80%] bg-[#202c33] text-white rounded-xl rounded-tl-sm px-4 py-2 shadow">
<p class="whitespace-pre-line break-words leading-relaxed" style="font-size:15px;">{{ $msg['contenido'] }}</p>
<span class="text-[#8696a0] text-[11px] float-right mt-1 ml-2">{{ $msg['created_at'] }}</span>
</div>
@endif
</div>
@endif
@empty
<div class="text-center text-[#8696a0] text-sm py-10">Iniciando chat...</div>
@endforelse
{{-- Burbuja optimista: el mensaje del usuario aparece antes de
que el servidor responda, igual que en WhatsApp. Se borra
sola cuando Livewire devuelve la lista real. --}}
<template x-if="pendiente">
<div class="flex justify-end msg-in">
<div class="max-w-[80%] bg-[#005c4b] text-white rounded-xl rounded-tr-sm px-3 py-2 shadow opacity-80 bubble-out">
<p class="whitespace-pre-line break-words leading-relaxed" style="font-size:15px;" x-text="pendiente"></p>
<span class="inline-flex items-center gap-1 float-right mt-1 ml-2 translate-y-1">
<span class="text-white/60 text-[11px]">ahora</span>
<svg class="w-3.5 h-3.5 text-white/50" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
<circle cx="12" cy="12" r="9" stroke-dasharray="2.5 2.5"/>
</svg>
</span>
</div>
</div>
</template>
{{-- "Escribiendo…" mientras el bot procesa cualquier acción --}}
<div wire:loading.flex wire:target="enviar, clickBoton, fotoComprobante" class="justify-start msg-in">
<div class="bg-[#202c33] rounded-xl rounded-tl-sm px-4 py-3 shadow bubble-in">
<div class="flex items-center gap-1">
<span class="typing-dot"></span>
<span class="typing-dot"></span>
<span class="typing-dot"></span>
</div>
</div>
</div>
<div id="pub-messages-end"></div>
</div>
{{-- Input + upload --}}
@if ($estadoConv !== 'cerrada')
<div class="bg-[#202c33] flex-shrink-0 safe-bottom safe-left safe-right" style="flex-shrink:0;">
{{-- Preview local de la imagen seleccionada --}}
<template x-if="previewUrl">
<div class="px-3 pt-2 pb-1 flex items-center gap-3">
<div class="relative">
<img :src="previewUrl" class="h-16 w-16 object-cover rounded-xl border-2 border-[#00a884]">
<div class="absolute inset-0 bg-black/50 rounded-xl flex items-center justify-center">
<svg class="w-5 h-5 animate-spin text-white" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8z"></path>
</svg>
</div>
</div>
<span class="text-[#8696a0] text-xs">Subiendo comprobante...</span>
</div>
</template>
{{-- Indicador: subiendo archivo (fase Alpine) --}}
<div x-show="uploading && !previewUrl" x-cloak class="px-4 pt-2 pb-1">
<div class="flex items-center gap-2 text-[#8696a0] text-xs">
<svg class="w-4 h-4 animate-spin text-[#00a884]" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8z"></path>
</svg>
Subiendo imagen...
</div>
</div>
{{-- Indicador: analizando con IA (fase Livewire server) --}}
<div wire:loading.block wire:target="fotoComprobante" class="px-4 pt-2 pb-1">
<div class="flex items-center gap-2 text-[#8696a0] text-xs">
<svg class="w-4 h-4 animate-spin text-[#00a884]" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8z"></path>
</svg>
Analizando comprobante con IA...
</div>
</div>
<div class="flex items-end gap-2 px-3 py-2.5">
{{-- Botón adjuntar foto --}}
<label class="flex-shrink-0 cursor-pointer text-[#8696a0] active:text-[#00a884] transition" style="min-width:44px; min-height:44px; display:flex; align-items:center; justify-content:center;">
<input type="file" wire:model="fotoComprobante" accept="image/*" class="hidden"
x-on:change="if($event.target.files[0]){ let r=new FileReader(); r.onload=e=>previewUrl=e.target.result; r.readAsDataURL($event.target.files[0]); }"
>
<svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="m2.25 15.75 5.159-5.159a2.25 2.25 0 0 1 3.182 0l5.159 5.159m-1.5-1.5 1.409-1.409a2.25 2.25 0 0 1 3.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 0 0 1.5-1.5V6a1.5 1.5 0 0 0-1.5-1.5H3.75A1.5 1.5 0 0 0 2.25 6v12a1.5 1.5 0 0 0 1.5 1.5Zm10.5-11.25h.008v.008h-.008V8.25Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z" />
</svg>
</label>
{{-- wire:ignore: su contenido y su altura los maneja Alpine.
Sin esto, el poll de 3s le devolvería la altura original
mientras el usuario está escribiendo. --}}
<textarea
wire:ignore
x-ref="entrada"
x-on:input="autoGrow()"
x-on:keydown.enter.prevent="if (! $event.shiftKey) enviarTexto()"
rows="1"
placeholder="Escribe o usa los botones..."
class="flex-1 bg-[#2a3942] text-white rounded-xl px-4 py-3 resize-none outline-none placeholder-[#8696a0]"
style="font-size:16px; overflow-y:auto; max-height:7rem; line-height:1.4; color-scheme:dark;"
></textarea>
<button type="button" x-on:click="enviarTexto()"
class="bg-[#00a884] active:bg-[#06cf9c] active:scale-95 rounded-full flex items-center justify-center transition flex-shrink-0"
style="width:44px; height:44px; min-width:44px;">
<svg class="w-5 h-5 text-white" viewBox="0 0 24 24" fill="currentColor">
<path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/>
</svg>
</button>
</div>
</div>
@else
<div class="bg-[#202c33] px-4 py-4 text-center text-[#8696a0] text-sm flex-shrink-0 safe-bottom">
Esta conversación ha sido cerrada.
</div>
@endif
{{-- Visor de imagen a pantalla completa --}}
<template x-if="lightbox">
<div class="fixed inset-0 z-50 bg-black/95 flex flex-col"
x-on:click="lightbox = null"
x-on:keydown.escape.window="lightbox = null">
<div class="flex justify-end safe-top px-4 py-3">
<button type="button" class="p-2 rounded-full bg-white/10 text-white active:bg-white/20">
<svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
</svg>
</button>
</div>
<div class="flex-1 flex items-center justify-center px-3 pb-6 safe-bottom">
<img :src="lightbox" alt="Comprobante" class="max-w-full max-h-full object-contain rounded-lg">
</div>
</div>
</template>
</div>
@endif
</div>