feat: preview de imagen y doble indicador de carga al subir comprobante

- Muestra thumbnail de la imagen seleccionada inmediatamente (FileReader)
  con spinner superpuesto mientras Livewire sube el archivo
- wire:loading wire:target="fotoComprobante" muestra "Analizando con IA..."
  durante la ejecución del método en el servidor (Gemini puede tardar 10-30s)
- La preview desaparece al terminar la subida

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lizandro
2026-07-29 20:52:49 +00:00
co-authored by Claude Sonnet 4.6
parent 7408a413ff
commit c7c52df743
@@ -161,10 +161,10 @@
{{-- ══ PASO 3: Interfaz de chat ══ --}}
@else
<div class="flex-1 flex flex-col min-h-0 bg-[#0b141a] relative"
x-data="{ uploading: false }"
x-data="{ uploading: false, previewUrl: null }"
@livewire-upload-start.window="uploading = true"
@livewire-upload-finish.window="uploading = false"
@livewire-upload-error.window="uploading = false">
@livewire-upload-finish.window="uploading = false; previewUrl = null"
@livewire-upload-error.window="uploading = false; previewUrl = null"
{{-- Header --}}
<div class="bg-[#202c33] shadow-md flex-shrink-0 safe-top safe-left safe-right">
@@ -617,8 +617,23 @@
{{-- Input + upload --}}
@if ($estadoConv !== 'cerrada')
<div class="bg-[#202c33] flex-shrink-0 safe-bottom safe-left safe-right" style="flex-shrink:0;">
{{-- Indicador subiendo / analizando imagen --}}
<div x-show="uploading" x-cloak class="px-4 pt-2 pb-1">
{{-- 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>
@@ -627,10 +642,22 @@
Subiendo imagen...
</div>
</div>
{{-- Indicador: analizando con IA (fase Livewire server) --}}
<div wire:loading 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">
<input type="file" wire:model="fotoComprobante" accept="image/*" class="hidden"
@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>