Alpine.data('quoteViewer', () => ({ showSignatureModal: false, signatureType: null, // 'creator' or 'client' isSavingSignature: false, isDrawing: false, ctx: null, hasSignature: {{ $quote->signature_path ? 'true' : 'false' }}, async sendByWhatsapp() { if (!this.hasSignature) { Swal.fire({ icon: 'warning', title: 'Firma Requerida', text: 'Por favor, firma la cotización primero.' }).then(() => { this.openSignaturePad('creator'); }); return; } const defaultPhone = '{{ preg_replace('/[^0-9]/', '', $quote->customer_phone ?? '') }}'; const { value: phone } = await Swal.fire({ title: 'Enviar por WhatsApp', input: 'text', inputLabel: 'Número de WhatsApp', inputValue: defaultPhone, showCancelButton: true, confirmButtonText: 'Abrir WhatsApp', cancelButtonText: 'Cancelar', confirmButtonColor: '#25D366' }); if (phone) { const msg = 'Hola {{ $quote->customer_name }}, adjunto enviamos la cotización solicitada: {{ route('quotes.public.show', $quote->token ?? 'error') }}'; try { await fetch('{{ route('quotes.status', $quote) }}', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'X-CSRF-TOKEN': '{{ csrf_token() }}' }, body: JSON.stringify({ status: 'Enviada' }) }); } catch(e) {} window.open('https://wa.me/' + phone.replace(/[^0-9]/g, '') + '?text=' + encodeURIComponent(msg), '_blank'); setTimeout(() => window.location.reload(), 1000); } }, async sendByEmail() { if (!this.hasSignature) { Swal.fire({ icon: 'warning', title: 'Firma Requerida', text: 'Por favor, firma la cotización antes de enviarla por correo.' }).then(() => { this.openSignaturePad('creator'); }); return; } const defaultEmail = '{{ $quote->customer_email ?? '' }}'; const { value: email } = await Swal.fire({ title: 'Enviar por Correo', input: 'email', inputLabel: 'Dirección de correo electrónico', inputValue: defaultEmail, showCancelButton: true, confirmButtonText: 'Enviar Correo', cancelButtonText: 'Cancelar', confirmButtonColor: 'var(--primary)' }); if (email) { Swal.fire({ title: 'Enviando...', text: 'Por favor espera', allowOutsideClick: false, didOpen: () => { Swal.showLoading(); } }); try { const response = await fetch('{{ route('quotes.email', $quote) }}', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'X-CSRF-TOKEN': '{{ csrf_token() }}' }, body: JSON.stringify({ email: email }) }); const data = await response.json(); if(data.success) { Swal.fire({ icon: 'success', title: '¡Enviado!', text: 'El correo ha sido enviado correctamente.' }).then(() => { window.location.reload(); }); } else { throw new Error(data.message); } } catch(error) { Swal.fire({ icon: 'error', title: 'Error', text: error.message || 'No se pudo enviar el correo.' }); } } }, init() { this.$watch('showSignatureModal', (val) => { if(val) { setTimeout(() => this.initCanvas(), 100); } }); }, openSignaturePad(type) { this.signatureType = type; this.showSignatureModal = true; }, closeSignaturePad() { this.showSignatureModal = false; this.signatureType = null; }, initCanvas() { const canvas = this.$refs.signatureCanvas; this.ctx = canvas.getContext('2d'); this.ctx.lineWidth = 2; this.ctx.lineCap = 'round'; this.ctx.strokeStyle = '#000000'; // Clear initial this.clearSignature(); const getPos = (e) => { const rect = canvas.getBoundingClientRect(); return { x: (e.clientX || e.touches[0].clientX) - rect.left, y: (e.clientY || e.touches[0].clientY) - rect.top }; }; canvas.addEventListener('mousedown', (e) => { this.isDrawing = true; const pos = getPos(e); this.ctx.beginPath(); this.ctx.moveTo(pos.x, pos.y); }); canvas.addEventListener('mousemove', (e) => { if (!this.isDrawing) return; const pos = getPos(e); this.ctx.lineTo(pos.x, pos.y); this.ctx.stroke(); }); canvas.addEventListener('mouseup', () => this.isDrawing = false); canvas.addEventListener('mouseleave', () => this.isDrawing = false); // Touch support canvas.addEventListener('touchstart', (e) => { e.preventDefault(); this.isDrawing = true; const pos = getPos(e); this.ctx.beginPath(); this.ctx.moveTo(pos.x, pos.y); }); canvas.addEventListener('touchmove', (e) => { e.preventDefault(); if (!this.isDrawing) return; const pos = getPos(e); this.ctx.lineTo(pos.x, pos.y); this.ctx.stroke(); }); canvas.addEventListener('touchend', (e) => { e.preventDefault(); this.isDrawing = false; }); }, clearSignature() { if(this.ctx) { this.ctx.clearRect(0, 0, this.$refs.signatureCanvas.width, this.$refs.signatureCanvas.height); } }, async saveSignature() { const dataURL = this.$refs.signatureCanvas.toDataURL('image/png'); this.isSavingSignature = true; try { const response = await fetch('{{ route('quotes.sign', $quote) }}', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'X-CSRF-TOKEN': '{{ csrf_token() }}' }, body: JSON.stringify({ signature_data: dataURL, type: this.signatureType }) }); const data = await response.json(); if(data.success) { this.hasSignature = true; this.closeSignaturePad(); Swal.fire({ title: 'Firma Guardada', text: '¿Cómo deseas enviar esta cotización al cliente?', icon: 'success', showDenyButton: true, showCancelButton: true, confirmButtonText: 'WhatsApp', confirmButtonColor: '#25D366', denyButtonText: 'Correo', denyButtonColor: 'var(--primary)', cancelButtonText: 'Solo Guardar' }).then((result) => { if (result.isConfirmed) { this.sendByWhatsapp(); } else if (result.isDenied) { this.sendByEmail(); } else { window.location.reload(); } }); } else { Swal.fire({ title: 'Error', text: 'Error al guardar la firma', icon: 'error' }); } } catch(e) { Swal.fire({ title: 'Error', text: 'Ocurrió un error de conexión', icon: 'error' }); } finally { this.isSavingSignature = false; } } })); });