|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace App\Services;
|
|
|
|
|
|
|
|
|
|
use App\Models\Examen;
|
|
|
|
|
use App\Models\Pregunta;
|
|
|
|
|
use App\Models\ReglaAreaProceso;
|
|
|
|
|
use App\Models\PreguntaAsignada;
|
|
|
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
|
|
|
|
|
|
class ExamenService
|
|
|
|
|
{
|
|
|
|
|
/**
|
|
|
|
|
* Generar preguntas según reglas
|
|
|
|
|
*/
|
|
|
|
|
public function generarPreguntasExamen(Examen $examen): array
|
|
|
|
|
{
|
|
|
|
|
if ($examen->preguntasAsignadas()->exists()) {
|
|
|
|
|
return [
|
|
|
|
|
'success' => false,
|
|
|
|
|
'message' => 'El examen ya tiene preguntas'
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$reglas = ReglaAreaProceso::where('area_proceso_id', $examen->area_proceso_id)
|
|
|
|
|
->orderBy('orden')
|
|
|
|
|
->get();
|
|
|
|
|
|
|
|
|
|
if ($reglas->isEmpty()) {
|
|
|
|
|
return [
|
|
|
|
|
'success' => false,
|
|
|
|
|
'message' => 'No hay reglas configuradas'
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DB::beginTransaction();
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
$orden = 1;
|
|
|
|
|
|
|
|
|
|
foreach ($reglas as $regla) {
|
|
|
|
|
$preguntas = Pregunta::where('curso_id', $regla->curso_id)
|
|
|
|
|
->where('activo', 1)
|
|
|
|
|
->when($regla->nivel_dificultad, fn ($q) =>
|
|
|
|
|
$q->where('nivel_dificultad', $regla->nivel_dificultad)
|
|
|
|
|
)
|
|
|
|
|
->inRandomOrder()
|
|
|
|
|
->limit($regla->cantidad_preguntas)
|
|
|
|
|
->get();
|
|
|
|
|
|
|
|
|
|
if ($preguntas->count() < $regla->cantidad_preguntas) {
|
|
|
|
|
throw new \Exception("Preguntas insuficientes para curso {$regla->curso_id}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach ($preguntas as $pregunta) {
|
|
|
|
|
PreguntaAsignada::create([
|
|
|
|
|
'examen_id' => $examen->id,
|
|
|
|
|
'pregunta_id' => $pregunta->id,
|
|
|
|
|
'orden' => $orden++,
|
|
|
|
|
'puntaje_base' => $regla->ponderacion,
|
|
|
|
|
'estado' => 'pendiente',
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DB::commit();
|
|
|
|
|
|
|
|
|
|
return ['success' => true];
|
|
|
|
|
|
|
|
|
|
} catch (\Throwable $e) {
|
|
|
|
|
DB::rollBack();
|
|
|
|
|
return ['success' => false, 'message' => $e->getMessage()];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public function obtenerPreguntasExamen(Examen $examen): array
|
|
|
|
|
{
|
|
|
|
|
// Traemos preguntas con curso
|
|
|
|
|
$preguntas = $examen->preguntasAsignadas()
|
|
|
|
|
->with('pregunta.curso')
|
|
|
|
|
->get()
|
|
|
|
|
->sortBy('orden');
|
|
|
|
|
|
|
|
|
|
// Traemos datos del área-proceso directamente desde la DB
|
|
|
|
|
$areaProceso = \DB::table('area_proceso')
|
|
|
|
|
->join('procesos', 'area_proceso.proceso_id', '=', 'procesos.id')
|
|
|
|
|
->join('areas', 'area_proceso.area_id', '=', 'areas.id')
|
|
|
|
|
->where('area_proceso.id', $examen->area_proceso_id)
|
|
|
|
|
->select(
|
|
|
|
|
'procesos.nombre as proceso_nombre',
|
|
|
|
|
'procesos.duracion as proceso_duracion',
|
|
|
|
|
'procesos.intentos_maximos as proceso_intentos_maximos',
|
|
|
|
|
'areas.nombre as area_nombre'
|
|
|
|
|
)
|
|
|
|
|
->first();
|
|
|
|
|
|
|
|
|
|
return $preguntas->map(fn($pa) => [
|
|
|
|
|
'id' => $pa->id,
|
|
|
|
|
'orden' => $pa->orden,
|
|
|
|
|
'enunciado' => $pa->pregunta->enunciado,
|
|
|
|
|
'extra' => $pa->pregunta->enunciado_adicional,
|
|
|
|
|
'opciones' => $this->mezclarOpciones($pa->pregunta->opciones),
|
|
|
|
|
'imagenes' => $pa->pregunta->imagenes,
|
|
|
|
|
'estado' => $pa->estado,
|
|
|
|
|
'respuesta' => $pa->pregunta->respuesta_correcta,
|
|
|
|
|
'curso' => $pa->pregunta->curso->nombre ?? null,
|
|
|
|
|
'proceso' => $areaProceso->proceso_nombre ?? null,
|
|
|
|
|
'duracion' => $areaProceso->proceso_duracion ?? null,
|
|
|
|
|
'intentos_maximos'=> $areaProceso->proceso_intentos_maximos ?? null,
|
|
|
|
|
'area' => $areaProceso->area_nombre ?? null
|
|
|
|
|
])->values()->toArray();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public function guardarRespuesta(PreguntaAsignada $pa, ?string $respuesta): array
|
|
|
|
|
{
|
|
|
|
|
if ($pa->estado === 'respondida') {
|
|
|
|
|
return ['success' => false, 'message' => 'Ya respondida'];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 🔹 Si está en blanco
|
|
|
|
|
if (empty($respuesta)) {
|
|
|
|
|
|
|
|
|
|
$pa->update([
|
|
|
|
|
'respuesta_usuario' => null,
|
|
|
|
|
'es_correcta' => 2, // 2 = blanco
|
|
|
|
|
'puntaje_obtenido' => 0,
|
|
|
|
|
'estado' => 'respondida',
|
|
|
|
|
'respondida_at' => now()
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
'success' => true,
|
|
|
|
|
'correcta' => 2,
|
|
|
|
|
'puntaje' => 0
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 🔹 Si respondió algo
|
|
|
|
|
$esCorrecta = $respuesta === $pa->pregunta->respuesta_correcta;
|
|
|
|
|
|
|
|
|
|
$pa->update([
|
|
|
|
|
'respuesta_usuario' => $respuesta,
|
|
|
|
|
'es_correcta' => $esCorrecta ? 1 : 0,
|
|
|
|
|
'puntaje_obtenido' => $esCorrecta ? $pa->puntaje_base : 0,
|
|
|
|
|
'estado' => 'respondida',
|
|
|
|
|
'respondida_at' => now()
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
'success' => true,
|
|
|
|
|
'correcta' => $esCorrecta ? 1 : 0,
|
|
|
|
|
'puntaje' => $pa->puntaje_obtenido
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private function mezclarOpciones(?array $opciones): array
|
|
|
|
|
{
|
|
|
|
|
if (!$opciones) return [];
|
|
|
|
|
|
|
|
|
|
$keys = array_keys($opciones);
|
|
|
|
|
shuffle($keys);
|
|
|
|
|
|
|
|
|
|
return array_map(fn ($k) => [
|
|
|
|
|
'key' => $k,
|
|
|
|
|
'texto' => $opciones[$k]
|
|
|
|
|
], $keys);
|
|
|
|
|
}
|
|
|
|
|
}
|