Files
sirpremiumv2/resources/views/livewire/chat/public-chat.blade.php
T
LizandroandClaude Sonnet 4.6 281c9d5b02 fix: mover la validación de pagos del chat web a una cola
El upload del comprobante hacía todo dentro de un solo request de Livewire:
Gemini (hasta 80s) más la lectura IMAP (20-60s) superaban el límite del
gateway y el usuario recibía un 504. Además, con SESSION_DRIVER=file ese
request mantenía bloqueado el archivo de sesión, así que el wire:poll del
chat no podía avanzar: la conversación entera se congelaba y el mensaje
"Analizando tu comprobante" se quedaba pegado para siempre.

Ahora updatedFotoComprobante() solo guarda la imagen, escribe el primer
mensaje y despacha ValidarPagoWebJob. Termina en ~1s.

El job hace el trabajo pesado fuera del request y va escribiendo su avance
como mensajes normales del chat, así el usuario ve los pasos:
  "Recibido, leyendo los datos..." -> "Datos leídos, buscando en el banco..."
  -> resultado
Reintenta cada 20s hasta 9 veces, igual que el flujo de Telegram.

El saldo se acredita dentro del job, no en el componente: si el usuario
cerraba o recargaba la pestaña durante la validación, el pago se confirmaba
pero el dinero nunca se abonaba. El componente ahora solo refresca el saldo
en pantalla y remata la compra que hubiera quedado pendiente.

Otros cierres del flujo que faltaban:
- failed() escribe un mensaje si el job muere, para no dejar el chat colgado.
- revisarPago() se rinde a los 6 min si el worker está caído.
- Los estados terminales dejan siempre un botón de menú.
- El botón Reintentar reencola en vez de validar dentro del request.

No se toca Telegram ni ningún servicio compartido (GeminiVisionService,
PagoValidadorService, CorreoImapService, ValidarPagoTelegramJob).

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

803 lines
63 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>
{{-- Mientras ValidarPagoWebJob trabaja, este poll solo pregunta si ya
hay desenlace (lectura de caché, sin llamadas externas).
Va en su propio elemento porque Livewire v2 solo respeta el
PRIMER wire:poll de un mismo nodo: compartirlo con el poll de
mensajes hacía que uno de los dos nunca se ejecutara. --}}
@if ($flujo === 'pago.procesando')
<div wire:poll.2000ms="revisarPago" 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 job valida el pago se queda fijo,
porque el trabajo ocurre fuera del request y wire:loading no
lo cubriría. --}}
@if ($flujo === 'pago.procesando')
<div class="flex 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>
@else
<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>
@endif
<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>
Enviando comprobante...
</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>