320 lines
12 KiB
Vue
320 lines
12 KiB
Vue
<script setup>
|
|
import { ref, onMounted, computed } from 'vue';
|
|
import { useRoute } from 'vue-router';
|
|
import { apiURL } from '@Services/Api';
|
|
|
|
import GoogleIcon from '@Shared/GoogleIcon.vue';
|
|
import Loader from '@Shared/Loader.vue';
|
|
import Input from '@Holos/Form/Input.vue';
|
|
import PrimaryButton from '@Holos/Button/Primary.vue';
|
|
|
|
/** Definidores */
|
|
const route = useRoute();
|
|
|
|
/** Estado */
|
|
const loading = ref(true);
|
|
const submitting = ref(false);
|
|
const submitted = ref(false);
|
|
const error = ref(null);
|
|
const saleData = ref(null);
|
|
const formErrors = ref({});
|
|
|
|
const form = ref({
|
|
name: '',
|
|
email: '',
|
|
phone: '',
|
|
address: '',
|
|
rfc: '',
|
|
razon_social: '',
|
|
regimen_fiscal: '',
|
|
cp_fiscal: '',
|
|
uso_cfdi: ''
|
|
});
|
|
|
|
/** Computed */
|
|
const invoiceNumber = computed(() => route.params.invoiceNumber);
|
|
|
|
/** Métodos */
|
|
const fetchSaleData = async () => {
|
|
loading.value = true;
|
|
error.value = null;
|
|
|
|
try {
|
|
const response = await fetch(apiURL(`facturacion/${invoiceNumber.value}`), {
|
|
method: 'GET',
|
|
headers: {
|
|
'Accept': 'application/json'
|
|
}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
if (response.status === 404) {
|
|
error.value = 'No se encontró la venta con el folio proporcionado.';
|
|
} else if (response.status === 400) {
|
|
const data = await response.json();
|
|
error.value = data.message || 'Esta venta ya fue facturada.';
|
|
} else {
|
|
error.value = 'Error al obtener los datos de la venta.';
|
|
}
|
|
return;
|
|
}
|
|
|
|
const data = await response.json();
|
|
saleData.value = data.sale;
|
|
|
|
} catch (err) {
|
|
console.error('Error:', err);
|
|
error.value = 'Error de conexión. Por favor intente más tarde.';
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
};
|
|
|
|
const submitForm = async () => {
|
|
submitting.value = true;
|
|
formErrors.value = {};
|
|
|
|
try {
|
|
const response = await fetch(apiURL(`facturacion/${invoiceNumber.value}`), {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Accept': 'application/json'
|
|
},
|
|
body: JSON.stringify(form.value)
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (!response.ok) {
|
|
if (data.errors) {
|
|
formErrors.value = data.errors;
|
|
} else {
|
|
error.value = data.message || 'Error al enviar los datos.';
|
|
}
|
|
return;
|
|
}
|
|
|
|
submitted.value = true;
|
|
|
|
} catch (err) {
|
|
console.error('Error:', err);
|
|
error.value = 'Error de conexión. Por favor intente más tarde.';
|
|
} finally {
|
|
submitting.value = false;
|
|
}
|
|
};
|
|
|
|
const formatCurrency = (value) => {
|
|
return new Intl.NumberFormat('es-MX', {
|
|
style: 'currency',
|
|
currency: 'MXN'
|
|
}).format(value || 0);
|
|
};
|
|
|
|
/** Ciclos */
|
|
onMounted(() => {
|
|
fetchSaleData();
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div class="min-h-screen bg-gray-100 dark:bg-gray-900 py-8 px-4">
|
|
<div class="max-w-2xl mx-auto">
|
|
<!-- Header -->
|
|
<div class="text-center mb-8">
|
|
<div class="inline-flex items-center justify-center w-16 h-16 rounded-full bg-primary dark:bg-primary-d mb-4">
|
|
<GoogleIcon name="receipt_long" class="text-3xl text-white" />
|
|
</div>
|
|
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">
|
|
Solicitud de Factura
|
|
</h1>
|
|
<p class="text-gray-600 dark:text-gray-400 mt-2">
|
|
Complete sus datos fiscales para generar su factura
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Loading -->
|
|
<div v-if="loading" class="flex justify-center py-12">
|
|
<Loader />
|
|
</div>
|
|
|
|
<!-- Error -->
|
|
<div v-else-if="error" class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-8 text-center">
|
|
<div class="inline-flex items-center justify-center w-16 h-16 rounded-full bg-red-100 dark:bg-red-900/30 mb-4">
|
|
<GoogleIcon name="error" class="text-3xl text-red-500" />
|
|
</div>
|
|
<h2 class="text-xl font-semibold text-gray-900 dark:text-white mb-2">
|
|
No se puede procesar
|
|
</h2>
|
|
<p class="text-gray-600 dark:text-gray-400">
|
|
{{ error }}
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Submitted Success -->
|
|
<div v-else-if="submitted" class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-8 text-center">
|
|
<div class="inline-flex items-center justify-center w-16 h-16 rounded-full bg-green-100 dark:bg-green-900/30 mb-4">
|
|
<GoogleIcon name="check_circle" class="text-3xl text-green-500" />
|
|
</div>
|
|
<h2 class="text-xl font-semibold text-gray-900 dark:text-white mb-2">
|
|
Solicitud Enviada
|
|
</h2>
|
|
<p class="text-gray-600 dark:text-gray-400 mb-4">
|
|
Sus datos han sido recibidos correctamente. Recibirá su factura en el correo electrónico proporcionado.
|
|
</p>
|
|
<p class="text-sm text-gray-500 dark:text-gray-500">
|
|
Folio: <span class="font-mono font-semibold">{{ invoiceNumber }}</span>
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Form -->
|
|
<div v-else class="space-y-6">
|
|
<!-- Sale Info Card -->
|
|
<div v-if="saleData" class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6">
|
|
<h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-4 flex items-center gap-2">
|
|
<GoogleIcon name="shopping_cart" class="text-xl" />
|
|
Datos de la Venta
|
|
</h2>
|
|
<div class="grid grid-cols-2 gap-4 text-sm">
|
|
<div>
|
|
<span class="text-gray-500 dark:text-gray-400">Folio:</span>
|
|
<span class="ml-2 font-semibold text-gray-900 dark:text-white">{{ saleData.invoice_number }}</span>
|
|
</div>
|
|
<div>
|
|
<span class="text-gray-500 dark:text-gray-400">Fecha:</span>
|
|
<span class="ml-2 font-semibold text-gray-900 dark:text-white">
|
|
{{ new Date(saleData.created_at).toLocaleDateString('es-MX') }}
|
|
</span>
|
|
</div>
|
|
<div class="col-span-2">
|
|
<span class="text-gray-500 dark:text-gray-400">Total:</span>
|
|
<span class="ml-2 font-bold text-lg text-green-600 dark:text-green-400">
|
|
{{ formatCurrency(saleData.total) }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Billing Form -->
|
|
<form @submit.prevent="submitForm" class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6">
|
|
<h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-6 flex items-center gap-2">
|
|
<GoogleIcon name="description" class="text-xl" />
|
|
Datos de Facturación
|
|
</h2>
|
|
|
|
<div class="grid gap-4 grid-cols-1 md:grid-cols-2">
|
|
<Input
|
|
v-model="form.name"
|
|
id="name"
|
|
title="name Completo"
|
|
placeholder="Juan Pérez García"
|
|
:onError="formErrors.name"
|
|
/>
|
|
|
|
<Input
|
|
v-model="form.email"
|
|
id="email"
|
|
title="Correo Electrónico"
|
|
type="email"
|
|
placeholder="correo@ejemplo.com"
|
|
:onError="formErrors.email"
|
|
/>
|
|
|
|
<Input
|
|
v-model="form.phone"
|
|
id="phone"
|
|
title="Teléfono"
|
|
type="tel"
|
|
placeholder="55 1234 5678"
|
|
:onError="formErrors.phone"
|
|
/>
|
|
|
|
<Input
|
|
v-model="form.rfc"
|
|
id="rfc"
|
|
title="RFC"
|
|
placeholder="XAXX010101000"
|
|
maxlength="13"
|
|
:onError="formErrors.rfc"
|
|
/>
|
|
|
|
<Input
|
|
v-model="form.razon_social"
|
|
id="razon_social"
|
|
title="Razón Social"
|
|
placeholder="Como aparece en la Constancia Fiscal"
|
|
:onError="formErrors.razon_social"
|
|
/>
|
|
|
|
<Input
|
|
v-model="form.regimen_fiscal"
|
|
id="regimen_fiscal"
|
|
title="Régimen Fiscal"
|
|
placeholder="Ej: 601 - General de Ley"
|
|
:onError="formErrors.regimen_fiscal"
|
|
/>
|
|
|
|
<Input
|
|
v-model="form.cp_fiscal"
|
|
id="cp_fiscal"
|
|
title="Código Postal Fiscal"
|
|
placeholder="06600"
|
|
maxlength="5"
|
|
:onError="formErrors.cp_fiscal"
|
|
/>
|
|
|
|
<Input
|
|
v-model="form.uso_cfdi"
|
|
id="uso_cfdi"
|
|
title="Uso de CFDI"
|
|
placeholder="Ej: G03 - Gastos en general"
|
|
:onError="formErrors.uso_cfdi"
|
|
/>
|
|
|
|
<div class="md:col-span-2">
|
|
<Input
|
|
v-model="form.address"
|
|
id="address"
|
|
title="Dirección"
|
|
placeholder="Calle, Número, Colonia, Ciudad, Estado"
|
|
:onError="formErrors.address"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Submit Button -->
|
|
<div class="mt-6 flex justify-center">
|
|
<PrimaryButton
|
|
:class="{ 'opacity-25': submitting }"
|
|
:disabled="submitting"
|
|
>
|
|
<template v-if="submitting">
|
|
<svg class="animate-spin h-5 w-5 mr-2" xmlns="http://www.w3.org/2000/svg" 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-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
</svg>
|
|
Enviando...
|
|
</template>
|
|
<template v-else>
|
|
<GoogleIcon name="send" class="mr-2" />
|
|
Solicitar Factura
|
|
</template>
|
|
</PrimaryButton>
|
|
</div>
|
|
|
|
<!-- Info Note -->
|
|
<p class="mt-4 text-xs text-gray-500 dark:text-gray-400 text-center">
|
|
Al enviar este formulario, acepta que sus datos serán utilizados únicamente para la emisión de su factura fiscal.
|
|
</p>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Footer -->
|
|
<div class="mt-8 text-center text-sm text-gray-500 dark:text-gray-400">
|
|
<p>¿Tiene dudas? Contáctenos</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|