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
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> |