Files
sirpremiumv2/resources/views/livewire/chat/public-chat.blade.php
T
LizandroandClaude Sonnet 4.6 807472f07d fix: indicador de subida colgado y apertura directa de servicio por nombre
1) "Subiendo comprobante..." no se quitaba nunca.

   La miniatura solo se limpiaba con livewire-upload-finish, que cubre la
   subida del archivo pero no el procesado posterior. Y updatedFotoComprobante
   tenía tres rutas de salida tempranas (sin convId, análisis deshabilitado,
   validación fallida) que ni siquiera llegaban al finally.

   Ahora todo el cuerpo va dentro de un try/finally que emite siempre
   'comprobante-listo', y el cliente limpia con ese evento del servidor en vez
   de depender de los eventos internos de Livewire. Se añade además un timeout
   de 45s como red de seguridad.

2) "cuenta de hbo" abría el listado completo en vez de HBO.

   El regex de servicios.listar tenía las marcas quemadas (netflix|hbo|disney…),
   así que capturaba la frase y devolvía data vacía antes de que la IA pudiera
   extraer el nombre. Telegram no falla porque sus keywords son solo intents
   genéricos y deja los específicos al agente.

   - Se sacan las marcas del regex genérico.
   - Nuevo detectarServicioPorNombre(): resuelve contra los nombres reales de
     la tabla servicios (nada quemado en código, sin llamada a la IA) y abre
     el servicio pedido.
   - clickBoton('servicios.listar') ahora honra data.servicio, como hace
     dispatchAction en Telegram.
   - El prompt de GeminiIntentService pide data.servicio. Este servicio lo usa
     solo el chat web; Telegram usa GeminiAgentService y no se toca.

   El patrón de credenciales se refuerza y pasa a evaluarse primero, para que
   "no puedo entrar a netflix" siga siendo soporte y no una intención de compra.

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

816 lines
64 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"
x-on:livewire-upload-error.window="uploading = false; previewUrl = null"
{{-- Quien manda es el servidor: emite 'comprobante-listo' en todas
sus rutas de salida. Los eventos de upload de Livewire solo
cubren la subida del archivo, no el procesado posterior. --}}
x-on:comprobante-listo.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]);
// Red de seguridad: si la respuesta nunca llega,
// no dejamos la miniatura girando para siempre.
setTimeout(() => { previewUrl = null; uploading = false; }, 45000);
}
"
>
<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>