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.
664 lines
17 KiB
Vue
664 lines
17 KiB
Vue
|
2 months ago
|
<template>
|
||
|
|
<div class="examen-panel">
|
||
|
|
<!-- Header con información del examen -->
|
||
|
|
<a-card>
|
||
|
|
<div class="examen-header">
|
||
|
|
<div class="header-info">
|
||
|
|
<h2>{{ examenInfo.proceso || 'Proceso no disponible' }}</h2>
|
||
|
|
<p><strong>Área:</strong> {{ examenInfo.area || 'Área no disponible' }}</p>
|
||
|
|
<p><strong>Duración:</strong> {{ examenInfo.duracion }} minutos</p>
|
||
|
|
<p><strong>Intentos:</strong> {{ examenInfo.intentos }} / {{ examenInfo.intentos_maximos }}</p>
|
||
|
|
<p><strong>Tiempo restante:</strong></p>
|
||
|
|
</div>
|
||
|
|
<div class="timer">
|
||
|
|
<a-statistic-countdown
|
||
|
|
:value="timerValue"
|
||
|
|
@finish="finalizarExamenAutomaticamente"
|
||
|
|
format="HH:mm:ss"
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</a-card>
|
||
|
|
|
||
|
|
<!-- Preguntas -->
|
||
|
|
<a-card
|
||
|
|
v-for="(pregunta, index) in preguntasTransformadas"
|
||
|
|
:key="pregunta.id"
|
||
|
|
class="pregunta-card"
|
||
|
|
style="margin-top: 16px;"
|
||
|
|
>
|
||
|
|
<template #title>
|
||
|
|
<div class="pregunta-header">
|
||
|
|
<span class="pregunta-numero">Pregunta {{ index + 1 }}</span>
|
||
|
|
<span class="curso-tag">
|
||
|
|
<a-tag color="blue">{{ pregunta.curso }}</a-tag>
|
||
|
|
</span>
|
||
|
|
<a-tag :color="pregunta.respondida ? 'green' : 'orange'">
|
||
|
|
{{ pregunta.estado === 'respondida' ? 'Respondida' : 'Pendiente' }}
|
||
|
|
</a-tag>
|
||
|
|
</div>
|
||
|
|
</template>
|
||
|
|
|
||
|
|
<!-- Enunciado -->
|
||
|
|
<div class="enunciado" v-html="pregunta.enunciado"></div>
|
||
|
|
|
||
|
|
<!-- Contenido adicional -->
|
||
|
|
<div v-if="pregunta.extra && pregunta.extra !== pregunta.enunciado"
|
||
|
|
class="extra" v-html="pregunta.extra"></div>
|
||
|
|
|
||
|
|
<!-- Opciones múltiples -->
|
||
|
|
<div class="opciones" v-if="pregunta.opciones && pregunta.opciones.length">
|
||
|
|
<a-radio-group
|
||
|
|
v-model:value="pregunta.respuestaSeleccionada"
|
||
|
|
@change="responderPregunta(pregunta)"
|
||
|
|
:disabled="pregunta.estado === 'respondida'"
|
||
|
|
>
|
||
|
|
<a-space direction="vertical" style="width: 100%;">
|
||
|
|
<a-radio
|
||
|
|
v-for="opcion in pregunta.opcionesOrdenadas"
|
||
|
|
:key="opcion.key"
|
||
|
|
:value="opcion.key.toString()" <!-- CONVERTIR A STRING -->
|
||
|
|
class="opcion-radio"
|
||
|
|
>
|
||
|
|
<span class="opcion-key">{{ getLetraOpcion(opcion.key) }}.</span>
|
||
|
|
<span v-html="opcion.texto" class="opcion-texto"></span>
|
||
|
|
</a-radio>
|
||
|
|
</a-space>
|
||
|
|
</a-radio-group>
|
||
|
|
|
||
|
|
<!-- Mostrar respuesta seleccionada -->
|
||
|
|
<div v-if="pregunta.respuestaSeleccionada" class="seleccion-actual">
|
||
|
|
<a-alert
|
||
|
|
:message="`Ha seleccionado: ${getTextoOpcionSeleccionada(pregunta)}`"
|
||
|
|
type="info"
|
||
|
|
show-icon
|
||
|
|
style="margin-top: 12px;"
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Pregunta abierta (si no hay opciones) -->
|
||
|
|
<div v-else class="pregunta-abierta">
|
||
|
|
<a-textarea
|
||
|
|
v-model:value="pregunta.respuestaTexto"
|
||
|
|
placeholder="Escriba su respuesta aquí..."
|
||
|
|
:rows="4"
|
||
|
|
:disabled="pregunta.estado === 'respondida'"
|
||
|
|
@blur="responderPreguntaTexto(pregunta)"
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Información de respuesta correcta (solo para debug) -->
|
||
|
|
<div v-if="debugMode" class="debug-info">
|
||
|
|
<a-alert
|
||
|
|
:message="`Respuesta correcta: ${pregunta.respuesta} (key: ${pregunta.respuestaKey})`"
|
||
|
|
type="warning"
|
||
|
|
show-icon
|
||
|
|
style="margin-top: 12px;"
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
</a-card>
|
||
|
|
|
||
|
|
<!-- Resumen y botones -->
|
||
|
|
<a-card style="margin-top: 24px;">
|
||
|
|
<div class="resumen-examen">
|
||
|
|
<h3>Resumen del Examen</h3>
|
||
|
|
<p><strong>Total preguntas:</strong> {{ preguntasTransformadas.length }}</p>
|
||
|
|
<p><strong>Respondidas:</strong> {{ preguntasRespondidas }} de {{ preguntasTransformadas.length }}</p>
|
||
|
|
<p><strong>Progreso:</strong></p>
|
||
|
|
<a-progress :percent="porcentajeCompletado" :stroke-color="{
|
||
|
|
'0%': '#108ee9',
|
||
|
|
'100%': '#87d068',
|
||
|
|
}" />
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="action-buttons" style="margin-top: 24px; text-align: center;">
|
||
|
|
<a-button
|
||
|
|
type="primary"
|
||
|
|
size="large"
|
||
|
|
:loading="finalizando"
|
||
|
|
@click="finalizarExamen"
|
||
|
|
:disabled="!todasRespondidas"
|
||
|
|
>
|
||
|
|
{{ todasRespondidas ? 'Finalizar Examen' : `Responda todas las preguntas (${preguntasTransformadas.length - preguntasRespondidas} pendientes)` }}
|
||
|
|
</a-button>
|
||
|
|
|
||
|
|
<a-button
|
||
|
|
type="default"
|
||
|
|
size="large"
|
||
|
|
style="margin-left: 12px;"
|
||
|
|
@click="guardarYSalir"
|
||
|
|
>
|
||
|
|
Guardar y salir
|
||
|
|
</a-button>
|
||
|
|
</div>
|
||
|
|
</a-card>
|
||
|
|
</div>
|
||
|
|
</template>
|
||
|
|
|
||
|
|
<script setup>
|
||
|
|
import { ref, computed, onMounted } from 'vue'
|
||
|
|
import { useRoute, useRouter } from 'vue-router'
|
||
|
|
import { useExamenStore } from '../../store/examen.store'
|
||
|
|
import { message, Modal } from 'ant-design-vue'
|
||
|
|
|
||
|
|
const route = useRoute()
|
||
|
|
const router = useRouter()
|
||
|
|
const examenStore = useExamenStore()
|
||
|
|
|
||
|
|
const finalizando = ref(false)
|
||
|
|
const debugMode = ref(false) // Cambiar a true para depuración
|
||
|
|
const timerValue = ref(null)
|
||
|
|
|
||
|
|
// Computed properties
|
||
|
|
const examenInfo = computed(() => {
|
||
|
|
if (!examenStore.examenActual) {
|
||
|
|
return {
|
||
|
|
proceso: null,
|
||
|
|
area: null,
|
||
|
|
duracion: 60,
|
||
|
|
intentos: 0,
|
||
|
|
intentos_maximos: 3
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
proceso: examenStore.examenActual?.proceso || 'Proceso no disponible',
|
||
|
|
area: examenStore.examenActual?.area || 'Área no disponible',
|
||
|
|
duracion: examenStore.examenActual?.duracion || 60,
|
||
|
|
intentos: examenStore.examenActual?.intentos || 0,
|
||
|
|
intentos_maximos: examenStore.examenActual?.intentos_maximos || 3
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
// Transformar preguntas - CORREGIDO para manejar keys numéricas
|
||
|
|
const preguntasTransformadas = computed(() => {
|
||
|
|
if (!examenStore.preguntas || !Array.isArray(examenStore.preguntas)) {
|
||
|
|
return []
|
||
|
|
}
|
||
|
|
|
||
|
|
return examenStore.preguntas.map(pregunta => {
|
||
|
|
// Encontrar la key correcta para la respuesta
|
||
|
|
let respuestaKey = null
|
||
|
|
if (pregunta.opciones && pregunta.respuesta) {
|
||
|
|
const opcionCorrecta = pregunta.opciones.find(op =>
|
||
|
|
op.texto === pregunta.respuesta ||
|
||
|
|
op.key.toString() === pregunta.respuesta.toString()
|
||
|
|
)
|
||
|
|
respuestaKey = opcionCorrecta ? opcionCorrecta.key : null
|
||
|
|
}
|
||
|
|
|
||
|
|
// Ordenar opciones por key
|
||
|
|
const opcionesOrdenadas = pregunta.opciones ?
|
||
|
|
[...pregunta.opciones].sort((a, b) => a.key - b.key) :
|
||
|
|
[]
|
||
|
|
|
||
|
|
return {
|
||
|
|
...pregunta,
|
||
|
|
respuestaKey, // Guardar la key de la respuesta correcta
|
||
|
|
opcionesOrdenadas,
|
||
|
|
// Agregar propiedades reactivas
|
||
|
|
respuestaSeleccionada: null,
|
||
|
|
respuestaTexto: '',
|
||
|
|
// El estado real viene del backend: 'pendiente' o 'respondida'
|
||
|
|
}
|
||
|
|
})
|
||
|
|
})
|
||
|
|
|
||
|
|
// Helper para convertir key numérico a letra
|
||
|
|
const getLetraOpcion = (key) => {
|
||
|
|
const letras = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']
|
||
|
|
return letras[key] || `Opción ${key}`
|
||
|
|
}
|
||
|
|
|
||
|
|
// Obtener texto de opción seleccionada
|
||
|
|
const getTextoOpcionSeleccionada = (pregunta) => {
|
||
|
|
if (!pregunta.respuestaSeleccionada) return ''
|
||
|
|
const opcion = pregunta.opcionesOrdenadas.find(
|
||
|
|
op => op.key.toString() === pregunta.respuestaSeleccionada.toString()
|
||
|
|
)
|
||
|
|
return opcion ? opcion.texto : 'Opción no encontrada'
|
||
|
|
}
|
||
|
|
|
||
|
|
const preguntasRespondidas = computed(() => {
|
||
|
|
return preguntasTransformadas.value.filter(p =>
|
||
|
|
p.estado === 'respondida' || p.respuestaSeleccionada || p.respuestaTexto
|
||
|
|
).length
|
||
|
|
})
|
||
|
|
|
||
|
|
const porcentajeCompletado = computed(() => {
|
||
|
|
if (preguntasTransformadas.length === 0) return 0
|
||
|
|
return Math.round((preguntasRespondidas.value / preguntasTransformadas.value.length) * 100)
|
||
|
|
})
|
||
|
|
|
||
|
|
const todasRespondidas = computed(() => {
|
||
|
|
return preguntasTransformadas.value.every(p =>
|
||
|
|
p.estado === 'respondida' || p.respuestaSeleccionada || p.respuestaTexto
|
||
|
|
)
|
||
|
|
})
|
||
|
|
|
||
|
|
// Métodos
|
||
|
|
const responderPregunta = async (pregunta) => {
|
||
|
|
if (!pregunta.respuestaSeleccionada) return
|
||
|
|
|
||
|
|
try {
|
||
|
|
// Asegurarse de que enviamos string (no número)
|
||
|
|
const respuestaString = pregunta.respuestaSeleccionada.toString()
|
||
|
|
|
||
|
|
// Encontrar el texto completo de la opción seleccionada
|
||
|
|
const opcionSeleccionada = pregunta.opcionesOrdenadas.find(
|
||
|
|
op => op.key.toString() === respuestaString
|
||
|
|
)
|
||
|
|
|
||
|
|
// Enviar el TEXTO de la opción, no la key
|
||
|
|
const textoRespuesta = opcionSeleccionada ? opcionSeleccionada.texto : respuestaString
|
||
|
|
|
||
|
|
const result = await examenStore.responderPregunta(
|
||
|
|
pregunta.id, // ID de PreguntaAsignada
|
||
|
|
textoRespuesta // Enviar el texto, no la key
|
||
|
|
)
|
||
|
|
|
||
|
|
if (result.success) {
|
||
|
|
// Actualizar estado local
|
||
|
|
pregunta.estado = 'respondida'
|
||
|
|
message.success('Respuesta guardada correctamente')
|
||
|
|
|
||
|
|
// Verificar si es correcta
|
||
|
|
if (result.correcta) {
|
||
|
|
message.info('¡Respuesta correcta!')
|
||
|
|
} else {
|
||
|
|
message.warning('Respuesta incorrecta')
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
message.error(result.message || 'Error al guardar respuesta')
|
||
|
|
// Revertir selección si falla
|
||
|
|
pregunta.respuestaSeleccionada = null
|
||
|
|
}
|
||
|
|
} catch (error) {
|
||
|
|
message.error('Error al guardar respuesta')
|
||
|
|
console.error('Error:', error)
|
||
|
|
pregunta.respuestaSeleccionada = null
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const responderPreguntaTexto = async (pregunta) => {
|
||
|
|
if (!pregunta.respuestaTexto.trim()) return
|
||
|
|
|
||
|
|
try {
|
||
|
|
const result = await examenStore.responderPregunta(
|
||
|
|
pregunta.id,
|
||
|
|
pregunta.respuestaTexto
|
||
|
|
)
|
||
|
|
|
||
|
|
if (result.success) {
|
||
|
|
pregunta.estado = 'respondida'
|
||
|
|
message.success('Respuesta guardada')
|
||
|
|
} else {
|
||
|
|
message.error(result.message || 'Error al guardar respuesta')
|
||
|
|
}
|
||
|
|
} catch (error) {
|
||
|
|
message.error('Error al guardar respuesta')
|
||
|
|
console.error(error)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const finalizarExamen = async () => {
|
||
|
|
if (!todasRespondidas.value) {
|
||
|
|
message.warning(`Por favor responda todas las preguntas. ${preguntasTransformadas.value.length - preguntasRespondidas.value} pendientes.`)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
Modal.confirm({
|
||
|
|
title: '¿Está seguro de finalizar el examen?',
|
||
|
|
content: 'Una vez finalizado no podrá modificar sus respuestas.',
|
||
|
|
okText: 'Sí, finalizar',
|
||
|
|
cancelText: 'Cancelar',
|
||
|
|
onOk: async () => {
|
||
|
|
try {
|
||
|
|
finalizando.value = true
|
||
|
|
const result = await examenStore.finalizarExamen(route.params.examenId)
|
||
|
|
|
||
|
|
if (result.success) {
|
||
|
|
message.success('Examen finalizado correctamente')
|
||
|
|
router.push({
|
||
|
|
name: 'panel-resultados',
|
||
|
|
params: { examenId: route.params.examenId }
|
||
|
|
})
|
||
|
|
} else {
|
||
|
|
message.error(result.message || 'Error al finalizar examen')
|
||
|
|
}
|
||
|
|
} catch (error) {
|
||
|
|
message.error('Error al finalizar examen')
|
||
|
|
console.error(error)
|
||
|
|
} finally {
|
||
|
|
finalizando.value = false
|
||
|
|
}
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
const guardarYSalir = async () => {
|
||
|
|
// Primero guardar todas las respuestas pendientes
|
||
|
|
const preguntasPendientes = preguntasTransformadas.value.filter(p =>
|
||
|
|
(p.respuestaSeleccionada || p.respuestaTexto) && p.estado !== 'respondida'
|
||
|
|
)
|
||
|
|
|
||
|
|
if (preguntasPendientes.length > 0) {
|
||
|
|
Modal.confirm({
|
||
|
|
title: 'Guardar respuestas pendientes',
|
||
|
|
content: `Tiene ${preguntasPendientes.length} respuesta(s) pendientes de guardar. ¿Desea guardarlas antes de salir?`,
|
||
|
|
okText: 'Guardar y salir',
|
||
|
|
cancelText: 'Salir sin guardar',
|
||
|
|
onOk: async () => {
|
||
|
|
try {
|
||
|
|
// Guardar cada respuesta pendiente
|
||
|
|
for (const pregunta of preguntasPendientes) {
|
||
|
|
if (pregunta.respuestaSeleccionada) {
|
||
|
|
await responderPregunta(pregunta)
|
||
|
|
} else if (pregunta.respuestaTexto) {
|
||
|
|
await responderPreguntaTexto(pregunta)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
message.success('Respuestas guardadas')
|
||
|
|
router.push({ name: 'DashboardPostulante' })
|
||
|
|
} catch (error) {
|
||
|
|
message.error('Error al guardar respuestas')
|
||
|
|
}
|
||
|
|
},
|
||
|
|
onCancel: () => {
|
||
|
|
router.push({ name: 'DashboardPostulante' })
|
||
|
|
}
|
||
|
|
})
|
||
|
|
} else {
|
||
|
|
router.push({ name: 'DashboardPostulante' })
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const finalizarExamenAutomaticamente = () => {
|
||
|
|
message.warning('Tiempo agotado. El examen se finalizará automáticamente.')
|
||
|
|
finalizarExamen()
|
||
|
|
}
|
||
|
|
|
||
|
|
const calcularTiempoRestante = () => {
|
||
|
|
if (examenStore.examenActual?.hora_inicio && examenInfo.value.duracion) {
|
||
|
|
const horaInicio = new Date(examenStore.examenActual.hora_inicio)
|
||
|
|
const duracionMs = examenInfo.value.duracion * 60 * 1000
|
||
|
|
const tiempoFinal = horaInicio.getTime() + duracionMs
|
||
|
|
timerValue.value = tiempoFinal
|
||
|
|
|
||
|
|
// Verificar si ya se agotó el tiempo
|
||
|
|
if (Date.now() > tiempoFinal) {
|
||
|
|
finalizarExamenAutomaticamente()
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
// Si no hay hora_inicio, usar duración por defecto desde ahora
|
||
|
|
timerValue.value = Date.now() + (examenInfo.value.duracion * 60 * 1000)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Lifecycle
|
||
|
|
onMounted(async () => {
|
||
|
|
console.log('=== DATOS DEL EXAMEN ===')
|
||
|
|
|
||
|
|
try {
|
||
|
|
await examenStore.iniciarExamen(route.params.examenId)
|
||
|
|
// ahora las preguntas deberían estar disponibles
|
||
|
|
if (!examenStore.preguntas || examenStore.preguntas.length === 0) {
|
||
|
|
message.error('No se encontraron preguntas para este examen')
|
||
|
|
router.push({ name: 'DashboardPostulante' })
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
calcularTiempoRestante()
|
||
|
|
setInterval(calcularTiempoRestante, 30000)
|
||
|
|
} catch (err) {
|
||
|
|
console.error(err)
|
||
|
|
message.error('Error al cargar el examen')
|
||
|
|
router.push({ name: 'DashboardPostulante' })
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<style scoped>
|
||
|
|
/* Estilos mejorados */
|
||
|
|
.examen-panel {
|
||
|
|
max-width: 1200px;
|
||
|
|
margin: 0 auto;
|
||
|
|
padding: 20px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.examen-header {
|
||
|
|
display: flex;
|
||
|
|
justify-content: space-between;
|
||
|
|
align-items: flex-start;
|
||
|
|
flex-wrap: wrap;
|
||
|
|
gap: 20px;
|
||
|
|
margin-bottom: 20px;
|
||
|
|
padding: 16px;
|
||
|
|
background: #f0f9ff;
|
||
|
|
border-radius: 8px;
|
||
|
|
border: 1px solid #d6e4ff;
|
||
|
|
}
|
||
|
|
|
||
|
|
.header-info h2 {
|
||
|
|
margin: 0 0 8px 0;
|
||
|
|
color: #1890ff;
|
||
|
|
font-size: 20px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.header-info p {
|
||
|
|
margin: 4px 0;
|
||
|
|
color: #595959;
|
||
|
|
}
|
||
|
|
|
||
|
|
.timer {
|
||
|
|
text-align: right;
|
||
|
|
min-width: 180px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.timer :deep(.ant-statistic-content) {
|
||
|
|
font-size: 28px;
|
||
|
|
font-weight: bold;
|
||
|
|
color: #fa541c;
|
||
|
|
}
|
||
|
|
|
||
|
|
.pregunta-card {
|
||
|
|
margin-bottom: 24px;
|
||
|
|
border: 1px solid #e8e8e8;
|
||
|
|
border-radius: 8px;
|
||
|
|
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.08);
|
||
|
|
transition: box-shadow 0.3s;
|
||
|
|
}
|
||
|
|
|
||
|
|
.pregunta-card:hover {
|
||
|
|
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.12);
|
||
|
|
}
|
||
|
|
|
||
|
|
.pregunta-header {
|
||
|
|
display: flex;
|
||
|
|
justify-content: space-between;
|
||
|
|
align-items: center;
|
||
|
|
flex-wrap: wrap;
|
||
|
|
gap: 10px;
|
||
|
|
padding-bottom: 12px;
|
||
|
|
border-bottom: 1px solid #f0f0f0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.pregunta-numero {
|
||
|
|
font-weight: bold;
|
||
|
|
font-size: 18px;
|
||
|
|
color: #1890ff;
|
||
|
|
}
|
||
|
|
|
||
|
|
.curso-tag {
|
||
|
|
flex-grow: 1;
|
||
|
|
margin-left: 12px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.enunciado {
|
||
|
|
font-size: 16px;
|
||
|
|
line-height: 1.7;
|
||
|
|
margin: 20px 0;
|
||
|
|
padding: 20px;
|
||
|
|
background: #fafafa;
|
||
|
|
border-radius: 8px;
|
||
|
|
border-left: 4px solid #1890ff;
|
||
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||
|
|
}
|
||
|
|
|
||
|
|
.enunciado :deep(math) {
|
||
|
|
font-size: 1.1em;
|
||
|
|
}
|
||
|
|
|
||
|
|
.extra {
|
||
|
|
font-size: 14px;
|
||
|
|
line-height: 1.6;
|
||
|
|
margin: 16px 0;
|
||
|
|
padding: 16px;
|
||
|
|
background: #fff7e6;
|
||
|
|
border-radius: 6px;
|
||
|
|
border: 1px solid #ffd591;
|
||
|
|
color: #d46b08;
|
||
|
|
}
|
||
|
|
|
||
|
|
.opciones {
|
||
|
|
margin-top: 24px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.opcion-radio {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
padding: 16px 20px;
|
||
|
|
margin: 10px 0;
|
||
|
|
border: 2px solid #d9d9d9;
|
||
|
|
border-radius: 8px;
|
||
|
|
transition: all 0.3s;
|
||
|
|
min-height: 60px;
|
||
|
|
cursor: pointer;
|
||
|
|
}
|
||
|
|
|
||
|
|
.opcion-radio:hover {
|
||
|
|
border-color: #40a9ff;
|
||
|
|
background: #f0f9ff;
|
||
|
|
transform: translateY(-2px);
|
||
|
|
}
|
||
|
|
|
||
|
|
.opcion-radio:deep(.ant-radio) {
|
||
|
|
margin-right: 12px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.opcion-key {
|
||
|
|
font-weight: bold;
|
||
|
|
color: #1890ff;
|
||
|
|
margin-right: 12px;
|
||
|
|
min-width: 24px;
|
||
|
|
font-size: 16px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.opcion-texto {
|
||
|
|
flex: 1;
|
||
|
|
line-height: 1.6;
|
||
|
|
font-size: 15px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.seleccion-actual {
|
||
|
|
margin-top: 16px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.debug-info {
|
||
|
|
margin-top: 16px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.pregunta-abierta textarea {
|
||
|
|
margin-top: 16px;
|
||
|
|
font-size: 15px;
|
||
|
|
line-height: 1.6;
|
||
|
|
}
|
||
|
|
|
||
|
|
.resumen-examen {
|
||
|
|
padding: 16px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.resumen-examen h3 {
|
||
|
|
margin-bottom: 16px;
|
||
|
|
color: #1890ff;
|
||
|
|
}
|
||
|
|
|
||
|
|
.resumen-examen p {
|
||
|
|
margin: 8px 0;
|
||
|
|
color: #595959;
|
||
|
|
}
|
||
|
|
|
||
|
|
.action-buttons {
|
||
|
|
padding: 24px 0;
|
||
|
|
border-top: 1px solid #f0f0f0;
|
||
|
|
margin-top: 32px;
|
||
|
|
display: flex;
|
||
|
|
justify-content: center;
|
||
|
|
gap: 12px;
|
||
|
|
}
|
||
|
|
|
||
|
|
:deep(.ant-card-head) {
|
||
|
|
border-bottom: 1px solid #f0f0f0;
|
||
|
|
background: #fafafa;
|
||
|
|
}
|
||
|
|
|
||
|
|
:deep(.ant-card-body) {
|
||
|
|
padding: 24px;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Estilos para matemáticas */
|
||
|
|
.enunciado :deep(.katex) {
|
||
|
|
font-size: 1.1em;
|
||
|
|
}
|
||
|
|
|
||
|
|
.extra :deep(.katex) {
|
||
|
|
font-size: 1em;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Responsive */
|
||
|
|
@media (max-width: 768px) {
|
||
|
|
.examen-panel {
|
||
|
|
padding: 10px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.examen-header {
|
||
|
|
flex-direction: column;
|
||
|
|
align-items: stretch;
|
||
|
|
}
|
||
|
|
|
||
|
|
.timer {
|
||
|
|
text-align: left;
|
||
|
|
margin-top: 16px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.pregunta-header {
|
||
|
|
flex-direction: column;
|
||
|
|
align-items: flex-start;
|
||
|
|
gap: 8px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.curso-tag {
|
||
|
|
margin-left: 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.enunciado {
|
||
|
|
padding: 16px;
|
||
|
|
font-size: 15px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.opcion-radio {
|
||
|
|
padding: 12px 16px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.action-buttons {
|
||
|
|
flex-direction: column;
|
||
|
|
gap: 16px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.action-buttons button {
|
||
|
|
width: 100%;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
</style>
|