You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

355 lines
9.7 KiB
Vue

<template>
<a-card title="Mi Examen" :loading="examenStore.cargando">
<!-- Estado de carga -->
<template v-if="examenStore.cargando && !examenStore.examenActual">
<a-skeleton active />
</template>
<!-- Si no hay examen asignado -->
<div v-else-if="!examenStore.examenActual" class="no-examen">
<a-empty description="No tienes un examen asignado actualmente">
<template #extra>
<a-button type="primary" @click="showModal = true">
Asignar Examen
</a-button>
</template>
</a-empty>
</div>
<!-- Si hay examen -->
<div v-else class="examen-info">
<a-descriptions title="Información del Examen" bordered>
<a-descriptions-item label="Proceso">
{{ examenStore.examenActual.proceso?.nombre || 'No asignado' }}
</a-descriptions-item>
<a-descriptions-item label="Área">
{{ examenStore.examenActual.area?.nombre || 'No asignado' }}
</a-descriptions-item>
<a-descriptions-item label="Intentos realizados">
{{ examenStore.examenActual.intentos || 0 }}
</a-descriptions-item>
<a-descriptions-item label="Estado del examen">
<a-tag :color="getEstadoColor">
{{ getEstadoTexto }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item label="Pago" v-if="examenStore.examenActual.pagado">
<a-tag color="green">Pagado ({{ examenStore.examenActual.tipo_pago }})</a-tag>
</a-descriptions-item>
</a-descriptions>
<div class="actions-container" style="margin-top: 24px;">
<!-- Botón Seleccionar Área (solo si no tiene área) -->
<a-button
v-if="!examenStore.examenActual.area"
type="primary"
@click="showModal = true"
style="margin-right: 8px;"
>
Seleccionar Área
</a-button>
<!-- Botón Iniciar Examen (solo si hay examen y no hay intentos) -->
<a-button
v-if="examenStore.examenActual && (!examenStore.examenActual.intentos || examenStore.examenActual.intentos === 0)"
type="primary"
:loading="iniciandoExamen"
@click="irAlExamen"
style="margin-right: 8px;"
>
Iniciar Examen
</a-button>
<!-- Botón Ver Resultados (solo si hay intentos) -->
<a-button
v-if="examenStore.examenActual?.intentos > 0"
type="default"
@click="verResultado"
>
Ver Resultados
</a-button>
</div>
</div>
<!-- Modal para seleccionar área -->
<a-modal
v-model:visible="showModal"
title="Seleccionar Área de Examen"
:confirm-loading="creandoExamen"
@ok="crearExamen"
@cancel="resetModal"
:mask-closable="false"
width="600px"
>
<a-form :model="formState" :rules="rules" layout="vertical">
<!-- Selección de Proceso -->
<a-form-item label="Proceso" name="proceso_id" required>
<a-select
v-model:value="formState.proceso_id"
placeholder="Seleccione un proceso"
:options="procesoOptions"
@change="handleProcesoChange"
:loading="examenStore.cargando"
/>
</a-form-item>
<!-- Selección de Área -->
<a-form-item label="Área" name="area_proceso_id" required>
<a-select
v-model:value="formState.area_proceso_id"
placeholder="Seleccione un área"
:options="areaOptions"
:disabled="!formState.proceso_id"
:loading="examenStore.cargando"
/>
</a-form-item>
<!-- Información de pago (si el proceso lo requiere) -->
<div v-if="procesoRequierePago">
<a-alert
message="Este proceso requiere pago"
type="info"
show-icon
style="margin-bottom: 16px;"
/>
<a-form-item label="Tipo de Pago" name="tipo_pago" required>
<a-select
v-model:value="formState.tipo_pago"
placeholder="Seleccione tipo de pago"
:options="tipoPagoOptions"
/>
</a-form-item>
<a-form-item label="Código de Pago" name="codigo_pago" required>
<a-input
v-model:value="formState.codigo_pago"
placeholder="Ingrese el código de pago"
/>
</a-form-item>
</div>
</a-form>
</a-modal>
</a-card>
</template>
<script setup>
import { ref, computed, reactive, onMounted, watch } from 'vue'
import { useRouter } from 'vue-router'
import { useExamenStore } from '../../store/examen.store'
import { message, Modal } from 'ant-design-vue'
const router = useRouter()
const examenStore = useExamenStore()
// Estados reactivos
const showModal = ref(false)
const iniciandoExamen = ref(false)
const creandoExamen = ref(false)
// Formulario para crear examen
const formState = reactive({
proceso_id: undefined,
area_proceso_id: undefined,
tipo_pago: undefined,
codigo_pago: ''
})
// Reglas de validación
const rules = {
proceso_id: [{ required: true, message: 'Por favor seleccione un proceso' }],
area_proceso_id: [{ required: true, message: 'Por favor seleccione un área' }],
tipo_pago: [{
required: computed(() => procesoRequierePago.value),
message: 'Por favor seleccione tipo de pago'
}],
codigo_pago: [{
required: computed(() => procesoRequierePago.value),
message: 'Por favor ingrese código de pago'
}]
}
// Opciones para selects
const procesoOptions = computed(() => {
return examenStore.procesos.map(p => ({
value: p.id,
label: p.nombre,
requiere_pago: p.requiere_pago
}))
})
const areaOptions = computed(() => {
return examenStore.areas.map(a => ({
value: a.area_proceso_id,
label: a.nombre
}))
})
const tipoPagoOptions = [
{ value: 'pyto_peru', label: 'Pago por Pytoperú' },
{ value: 'banco_nacion', label: 'Banco de la Nación' },
{ value: 'caja', label: 'Caja' }
]
// Computed properties
const procesoRequierePago = computed(() => {
const proceso = examenStore.procesos.find(p => p.id === formState.proceso_id)
return proceso?.requiere_pago === 1
})
const getEstadoColor = computed(() => {
if (!examenStore.examenActual?.intentos || examenStore.examenActual.intentos === 0) {
return 'blue'
}
return 'green'
})
const getEstadoTexto = computed(() => {
if (!examenStore.examenActual?.intentos || examenStore.examenActual.intentos === 0) {
return 'Disponible'
}
return 'Completado'
})
// Métodos
const handleProcesoChange = async (procesoId) => {
formState.area_proceso_id = undefined
if (procesoId) {
await examenStore.fetchAreas(procesoId)
}
}
const crearExamen = async () => {
try {
creandoExamen.value = true
const pagoData = procesoRequierePago.value ? {
tipo_pago: formState.tipo_pago,
codigo_pago: formState.codigo_pago
} : null
const result = await examenStore.crearExamen(
formState.area_proceso_id,
pagoData
)
if (result.success) {
message.success('Examen creado correctamente')
showModal.value = false
resetModal()
// Recargar el examen actual
await examenStore.fetchExamenActual()
} else {
message.error(result.message || 'Error al crear examen')
}
} catch (error) {
message.error('Error al crear examen')
} finally {
creandoExamen.value = false
}
}
const irAlExamen = async () => {
if (!examenStore.examenActual?.id) {
message.error('No hay examen para iniciar')
return
}
try {
iniciandoExamen.value = true
// Primero intentar generar preguntas
const generarResult = await examenStore.generarPreguntas(examenStore.examenActual.id)
if (!generarResult.success) {
// Si hay error real, mostrar mensaje
if (!generarResult.ya_tiene_preguntas) {
message.error(generarResult.message || 'Error al generar preguntas')
return
}
// Si ya tiene preguntas, es un éxito (continuar)
}
// Luego iniciar el examen (esto carga las preguntas)
const iniciarResult = await examenStore.iniciarExamen(examenStore.examenActual.id)
if (iniciarResult.success) {
// Redirigir al panel de examen
router.push({ name: 'PanelExamen', params: { examenId: examenStore.examenActual.id } })
} else {
message.error(iniciarResult.message || 'Error al iniciar examen')
}
} catch (error) {
message.error('Error al procesar la solicitud')
console.error(error)
} finally {
iniciandoExamen.value = false
}
}
const verResultado = async () => {
// Actualizar intentos del examen
await examenStore.fetchExamenActual()
// Redirigir a resultados
router.push({
name: 'PanelResultados',
params: { examenId: examenStore.examenActual?.id }
})
}
const resetModal = () => {
Object.keys(formState).forEach(key => {
if (key === 'proceso_id' || key === 'area_proceso_id') {
formState[key] = undefined
} else if (key === 'codigo_pago') {
formState[key] = ''
} else {
formState[key] = undefined
}
})
}
// Lifecycle
onMounted(async () => {
// Cargar procesos disponibles
await examenStore.fetchProcesos()
// Cargar examen actual
await examenStore.fetchExamenActual()
})
// Watch para limpiar áreas cuando se cierra el modal
watch(showModal, (newVal) => {
if (!newVal) {
examenStore.areas = []
}
})
</script>
<style scoped>
.no-examen {
text-align: center;
padding: 40px 0;
}
.examen-info {
padding: 16px 0;
}
.actions-container {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
:deep(.ant-descriptions) {
margin-bottom: 16px;
}
:deep(.ant-descriptions-item-label) {
font-weight: 600;
width: 180px;
}
</style>