diff --git a/app/Http/Controllers/EvaluacionController.php b/app/Http/Controllers/EvaluacionController.php new file mode 100644 index 00000000..44817faf --- /dev/null +++ b/app/Http/Controllers/EvaluacionController.php @@ -0,0 +1,64 @@ +withCount(['preguntas', 'submissions']) + ->latest() + ->get(); + + return view('evaluacion.index', compact('evaluaciones')); + } + + public function create() + { + return view('evaluacion.create'); + } + + public function store(Request $request) + { + // Manejado por Livewire EvaluacionBuilder + } + + public function show(Evaluacion $evaluacion) + { + $evaluacion->load(['tipo', 'preguntas.tipo', 'preguntas.opciones', 'planteles', 'niveles']); + + return view('evaluacion.show', compact('evaluacion')); + } + + public function edit(Evaluacion $evaluacion) + { + $evaluacion->load(['preguntas.opciones', 'planteles', 'niveles']); + + return view('evaluacion.create', compact('evaluacion')); + } + + public function update(Request $request, Evaluacion $evaluacion) + { + // Manejado por Livewire EvaluacionBuilder + } + + public function destroy(Evaluacion $evaluacion) + { + $evaluacion->delete(); + + return redirect()->route('evaluacion.index')->with('info', 'Evaluación eliminada.'); + } + + public function toggleStatus(Request $request, Evaluacion $evaluacion) + { + $request->validate(['status' => 'required|in:borrador,activa,cerrada']); + $evaluacion->update(['status' => $request->status]); + + return back()->with('info', 'Estado actualizado.'); + } +} diff --git a/app/Http/Controllers/EvaluacionResponderController.php b/app/Http/Controllers/EvaluacionResponderController.php new file mode 100644 index 00000000..06e09330 --- /dev/null +++ b/app/Http/Controllers/EvaluacionResponderController.php @@ -0,0 +1,10 @@ +tipos = EvaluacionTipo::all(['id', 'nombre', 'slug'])->toArray(); + $this->preguntaTipos = PreguntaTipo::all(['id', 'nombre', 'slug'])->toArray(); + $this->planteles = Plantel::orderBy('name')->get(['id', 'name'])->toArray(); + $this->niveles = Nivel::orderBy('name')->get(['id', 'name'])->toArray(); + + if ($evaluacion instanceof Evaluacion && $evaluacion->exists) { + $this->evaluacionId = $evaluacion->id; + $this->titulo = $evaluacion->titulo; + $this->descripcion = $evaluacion->descripcion ?? ''; + $this->evaluacionTipoId = (string) $evaluacion->evaluacion_tipo_id; + $this->status = $evaluacion->status; + $this->fechaInicio = $evaluacion->fecha_inicio?->format('Y-m-d') ?? ''; + $this->fechaFin = $evaluacion->fecha_fin?->format('Y-m-d') ?? ''; + $this->anonima = (bool) $evaluacion->anonima; + $this->selectedPlanteles = $evaluacion->planteles->pluck('id')->map(fn($v) => (string) $v)->toArray(); + $this->selectedNiveles = $evaluacion->niveles->pluck('id')->map(fn($v) => (string) $v)->toArray(); + + $this->preguntas = $evaluacion->preguntas->map(fn($p) => [ + 'id' => $p->id, + 'texto' => $p->texto, + 'pregunta_tipo_id' => (string) $p->pregunta_tipo_id, + 'requerida' => (bool) $p->requerida, + 'opciones' => $p->opciones->map(fn($o) => [ + 'id' => $o->id, + 'texto' => $o->texto, + ])->toArray(), + ])->toArray(); + } + } + + public function addPregunta(): void + { + $defaultTipo = $this->preguntaTipos[0]['id'] ?? ''; + $this->preguntas[] = [ + 'id' => null, + 'texto' => '', + 'pregunta_tipo_id' => (string) $defaultTipo, + 'requerida' => true, + 'opciones' => [], + ]; + } + + public function removePregunta(int $index): void + { + array_splice($this->preguntas, $index, 1); + $this->preguntas = array_values($this->preguntas); + } + + public function movePregunta(int $index, string $direction): void + { + $swap = $direction === 'up' ? $index - 1 : $index + 1; + + if ($swap < 0 || $swap >= count($this->preguntas)) { + return; + } + + [$this->preguntas[$index], $this->preguntas[$swap]] = [$this->preguntas[$swap], $this->preguntas[$index]]; + } + + public function addOpcion(int $preguntaIndex): void + { + $this->preguntas[$preguntaIndex]['opciones'][] = ['id' => null, 'texto' => '']; + } + + public function removeOpcion(int $preguntaIndex, int $opcionIndex): void + { + array_splice($this->preguntas[$preguntaIndex]['opciones'], $opcionIndex, 1); + $this->preguntas[$preguntaIndex]['opciones'] = array_values($this->preguntas[$preguntaIndex]['opciones']); + } + + public function guardar(): void + { + $this->validate([ + 'titulo' => 'required|string|max:255', + 'descripcion' => 'nullable|string', + 'evaluacionTipoId' => 'required|exists:evaluacion_tipos,id', + 'status' => 'required|in:borrador,activa,cerrada', + 'fechaInicio' => 'nullable|date', + 'fechaFin' => 'nullable|date', + 'selectedPlanteles' => 'array', + 'selectedNiveles' => 'array', + 'preguntas' => 'required|array|min:1', + 'preguntas.*.texto' => 'required|string|max:1000', + 'preguntas.*.pregunta_tipo_id' => 'required|exists:pregunta_tipos,id', + 'preguntas.*.opciones.*.texto' => 'required|string|max:500', + ], [ + 'titulo.required' => 'El título es obligatorio.', + 'evaluacionTipoId.required' => 'Selecciona el tipo de evaluación.', + 'preguntas.required' => 'Agrega al menos una pregunta.', + 'preguntas.min' => 'Agrega al menos una pregunta.', + 'preguntas.*.texto.required' => 'El texto de la pregunta es obligatorio.', + 'preguntas.*.pregunta_tipo_id.required' => 'Selecciona el tipo de respuesta.', + 'preguntas.*.opciones.*.texto.required' => 'El texto de la opción es obligatorio.', + ]); + + DB::transaction(function () { + $data = [ + 'titulo' => $this->titulo, + 'descripcion' => $this->descripcion ?: null, + 'evaluacion_tipo_id' => $this->evaluacionTipoId, + 'status' => $this->status, + 'fecha_inicio' => $this->fechaInicio ?: null, + 'fecha_fin' => $this->fechaFin ?: null, + 'anonima' => $this->anonima, + ]; + + if ($this->evaluacionId) { + $evaluacion = Evaluacion::findOrFail($this->evaluacionId); + $evaluacion->update($data); + } else { + $evaluacion = Evaluacion::create($data); + $this->evaluacionId = $evaluacion->id; + } + + $evaluacion->planteles()->sync($this->selectedPlanteles); + $evaluacion->niveles()->sync($this->selectedNiveles); + + // Eliminar preguntas removidas + $keptIds = collect($this->preguntas)->pluck('id')->filter()->values()->toArray(); + $evaluacion->preguntas()->whereNotIn('id', $keptIds)->delete(); + + foreach ($this->preguntas as $orden => $pData) { + if (!empty($pData['id'])) { + $pregunta = Pregunta::findOrFail($pData['id']); + $pregunta->update([ + 'texto' => $pData['texto'], + 'pregunta_tipo_id' => $pData['pregunta_tipo_id'], + 'requerida' => $pData['requerida'] ?? true, + 'orden' => $orden, + ]); + } else { + $pregunta = $evaluacion->preguntas()->create([ + 'texto' => $pData['texto'], + 'pregunta_tipo_id' => $pData['pregunta_tipo_id'], + 'requerida' => $pData['requerida'] ?? true, + 'orden' => $orden, + ]); + } + + $tipoSlug = collect($this->preguntaTipos) + ->firstWhere('id', (int) $pData['pregunta_tipo_id'])['slug'] ?? ''; + + if ($tipoSlug === 'opcion_multiple') { + $keptOpcionIds = collect($pData['opciones'] ?? []) + ->pluck('id')->filter()->values()->toArray(); + $pregunta->opciones()->whereNotIn('id', $keptOpcionIds)->delete(); + + foreach ($pData['opciones'] ?? [] as $opOrden => $opData) { + if (!empty($opData['id'])) { + PreguntaOpcion::findOrFail($opData['id'])->update([ + 'texto' => $opData['texto'], + 'orden' => $opOrden, + ]); + } else { + $pregunta->opciones()->create([ + 'texto' => $opData['texto'], + 'orden' => $opOrden, + ]); + } + } + } else { + $pregunta->opciones()->delete(); + } + } + }); + + session()->flash('info', 'Evaluación guardada correctamente.'); + $this->redirect(route('evaluacion.index')); + } + + public function render() + { + return view('livewire.evaluacion-builder'); + } +} diff --git a/app/Models/Evaluacion.php b/app/Models/Evaluacion.php new file mode 100644 index 00000000..1bb4da14 --- /dev/null +++ b/app/Models/Evaluacion.php @@ -0,0 +1,45 @@ + 'boolean', + 'fecha_inicio' => 'date', + 'fecha_fin' => 'date', + ]; + + public function tipo(): BelongsTo + { + return $this->belongsTo(EvaluacionTipo::class, 'evaluacion_tipo_id'); + } + + public function preguntas(): HasMany + { + return $this->hasMany(Pregunta::class)->orderBy('orden'); + } + + public function planteles(): BelongsToMany + { + return $this->belongsToMany(Plantel::class, 'evaluacion_plantel'); + } + + public function niveles(): BelongsToMany + { + return $this->belongsToMany(Nivel::class, 'evaluacion_nivel'); + } + + public function submissions(): HasMany + { + return $this->hasMany(EvaluacionSubmission::class); + } +} diff --git a/app/Models/EvaluacionSubmission.php b/app/Models/EvaluacionSubmission.php new file mode 100644 index 00000000..1d6d1741 --- /dev/null +++ b/app/Models/EvaluacionSubmission.php @@ -0,0 +1,27 @@ +belongsTo(Evaluacion::class); + } + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } + + public function respuestas(): HasMany + { + return $this->hasMany(Respuesta::class, 'submission_id'); + } +} diff --git a/app/Models/EvaluacionTipo.php b/app/Models/EvaluacionTipo.php new file mode 100644 index 00000000..9e014336 --- /dev/null +++ b/app/Models/EvaluacionTipo.php @@ -0,0 +1,17 @@ +hasMany(Evaluacion::class); + } +} diff --git a/app/Models/Nivel.php b/app/Models/Nivel.php index a62e496a..9613e1a1 100644 --- a/app/Models/Nivel.php +++ b/app/Models/Nivel.php @@ -42,4 +42,9 @@ class Nivel extends Model 'documento_tipo_id' ); } + + public function evaluaciones() : BelongsToMany + { + return $this->belongsToMany(Evaluacion::class, 'evaluacion_nivel'); + } } diff --git a/app/Models/Plantel.php b/app/Models/Plantel.php index 0dc645b2..2242090e 100644 --- a/app/Models/Plantel.php +++ b/app/Models/Plantel.php @@ -35,4 +35,9 @@ class Plantel extends Model { return $this->belongsToMany(Turno::class); } + + public function evaluaciones() : BelongsToMany + { + return $this->belongsToMany(Evaluacion::class, 'evaluacion_plantel'); + } } diff --git a/app/Models/Pregunta.php b/app/Models/Pregunta.php new file mode 100644 index 00000000..07533e47 --- /dev/null +++ b/app/Models/Pregunta.php @@ -0,0 +1,32 @@ +belongsTo(Evaluacion::class); + } + + public function tipo(): BelongsTo + { + return $this->belongsTo(PreguntaTipo::class, 'pregunta_tipo_id'); + } + + public function opciones(): HasMany + { + return $this->hasMany(PreguntaOpcion::class)->orderBy('orden'); + } + + public function respuestas(): HasMany + { + return $this->hasMany(Respuesta::class); + } +} diff --git a/app/Models/PreguntaOpcion.php b/app/Models/PreguntaOpcion.php new file mode 100644 index 00000000..b28e011a --- /dev/null +++ b/app/Models/PreguntaOpcion.php @@ -0,0 +1,18 @@ +belongsTo(Pregunta::class); + } +} diff --git a/app/Models/PreguntaTipo.php b/app/Models/PreguntaTipo.php new file mode 100644 index 00000000..627d2f05 --- /dev/null +++ b/app/Models/PreguntaTipo.php @@ -0,0 +1,17 @@ +hasMany(Pregunta::class); + } +} diff --git a/app/Models/Respuesta.php b/app/Models/Respuesta.php new file mode 100644 index 00000000..f281c34e --- /dev/null +++ b/app/Models/Respuesta.php @@ -0,0 +1,26 @@ +belongsTo(EvaluacionSubmission::class); + } + + public function pregunta(): BelongsTo + { + return $this->belongsTo(Pregunta::class); + } + + public function opcion(): BelongsTo + { + return $this->belongsTo(PreguntaOpcion::class, 'opcion_id'); + } +} diff --git a/database/migrations/2026_04_30_121640_create_evaluacion_tipos_table.php b/database/migrations/2026_04_30_121640_create_evaluacion_tipos_table.php new file mode 100644 index 00000000..fe2ec96a --- /dev/null +++ b/database/migrations/2026_04_30_121640_create_evaluacion_tipos_table.php @@ -0,0 +1,21 @@ +id(); + $table->string('nombre'); + $table->string('slug')->unique(); + }); + DB::table('evaluacion_tipos')->insert([ + ['nombre' => 'Docente', 'slug' => 'docente'], + ['nombre' => 'Alumno', 'slug' => 'alumno'], + ['nombre' => 'Personal administrativo', 'slug' => 'administrativo'], + ]); + } + public function down(): void { Schema::dropIfExists('evaluacion_tipos'); } +}; diff --git a/database/migrations/2026_04_30_121641_create_pregunta_tipos_table.php b/database/migrations/2026_04_30_121641_create_pregunta_tipos_table.php new file mode 100644 index 00000000..8afe90aa --- /dev/null +++ b/database/migrations/2026_04_30_121641_create_pregunta_tipos_table.php @@ -0,0 +1,23 @@ +id(); + $table->string('nombre'); + $table->string('slug')->unique(); + }); + DB::table('pregunta_tipos')->insert([ + ['nombre' => 'Texto libre', 'slug' => 'texto_libre'], + ['nombre' => 'Opcion multiple', 'slug' => 'opcion_multiple'], + ['nombre' => 'Escala del 1 al 5', 'slug' => 'escala_5'], + ['nombre' => 'Escala del 1 al 10', 'slug' => 'escala_10'], + ['nombre' => 'Si o No', 'slug' => 'si_no'], + ]); + } + public function down(): void { Schema::dropIfExists('pregunta_tipos'); } +}; diff --git a/database/migrations/2026_04_30_121642_create_evaluaciones_table.php b/database/migrations/2026_04_30_121642_create_evaluaciones_table.php new file mode 100644 index 00000000..2bd3d1b1 --- /dev/null +++ b/database/migrations/2026_04_30_121642_create_evaluaciones_table.php @@ -0,0 +1,22 @@ +id(); + $table->string('titulo'); + $table->text('descripcion')->nullable(); + $table->unsignedBigInteger('evaluacion_tipo_id'); + $table->enum('status', ['borrador', 'activa', 'cerrada'])->default('borrador'); + $table->date('fecha_inicio')->nullable(); + $table->date('fecha_fin')->nullable(); + $table->boolean('anonima')->default(false); + $table->timestamps(); + $table->foreign('evaluacion_tipo_id')->references('id')->on('evaluacion_tipos'); + }); + } + public function down(): void { Schema::dropIfExists('evaluaciones'); } +}; diff --git a/database/migrations/2026_04_30_121643_create_preguntas_table.php b/database/migrations/2026_04_30_121643_create_preguntas_table.php new file mode 100644 index 00000000..71ee7957 --- /dev/null +++ b/database/migrations/2026_04_30_121643_create_preguntas_table.php @@ -0,0 +1,21 @@ +id(); + $table->unsignedBigInteger('evaluacion_id'); + $table->unsignedBigInteger('pregunta_tipo_id'); + $table->text('texto'); + $table->unsignedSmallInteger('orden')->default(0); + $table->boolean('requerida')->default(true); + $table->timestamps(); + $table->foreign('evaluacion_id')->references('id')->on('evaluaciones')->onDelete('cascade'); + $table->foreign('pregunta_tipo_id')->references('id')->on('pregunta_tipos'); + }); + } + public function down(): void { Schema::dropIfExists('preguntas'); } +}; diff --git a/database/migrations/2026_04_30_121644_create_pregunta_opciones_table.php b/database/migrations/2026_04_30_121644_create_pregunta_opciones_table.php new file mode 100644 index 00000000..4c19556e --- /dev/null +++ b/database/migrations/2026_04_30_121644_create_pregunta_opciones_table.php @@ -0,0 +1,17 @@ +id(); + $table->unsignedBigInteger('pregunta_id'); + $table->string('texto'); + $table->unsignedSmallInteger('orden')->default(0); + $table->foreign('pregunta_id')->references('id')->on('preguntas')->onDelete('cascade'); + }); + } + public function down(): void { Schema::dropIfExists('pregunta_opciones'); } +}; diff --git a/database/migrations/2026_04_30_121645_create_evaluacion_submissions_table.php b/database/migrations/2026_04_30_121645_create_evaluacion_submissions_table.php new file mode 100644 index 00000000..14bd5e1e --- /dev/null +++ b/database/migrations/2026_04_30_121645_create_evaluacion_submissions_table.php @@ -0,0 +1,19 @@ +id(); + $table->unsignedBigInteger('evaluacion_id'); + $table->unsignedBigInteger('user_id'); + $table->timestamps(); + $table->unique(['evaluacion_id', 'user_id']); + $table->foreign('evaluacion_id')->references('id')->on('evaluaciones')->onDelete('cascade'); + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); + }); + } + public function down(): void { Schema::dropIfExists('evaluacion_submissions'); } +}; diff --git a/database/migrations/2026_04_30_121645_create_respuestas_table.php b/database/migrations/2026_04_30_121645_create_respuestas_table.php new file mode 100644 index 00000000..b17174c7 --- /dev/null +++ b/database/migrations/2026_04_30_121645_create_respuestas_table.php @@ -0,0 +1,21 @@ +id(); + $table->unsignedBigInteger('submission_id'); + $table->unsignedBigInteger('pregunta_id'); + $table->text('valor')->nullable(); + $table->unsignedBigInteger('opcion_id')->nullable(); + $table->timestamps(); + $table->foreign('submission_id')->references('id')->on('evaluacion_submissions')->onDelete('cascade'); + $table->foreign('pregunta_id')->references('id')->on('preguntas')->onDelete('cascade'); + $table->foreign('opcion_id')->references('id')->on('pregunta_opciones')->nullOnDelete(); + }); + } + public function down(): void { Schema::dropIfExists('respuestas'); } +}; diff --git a/database/migrations/2026_04_30_121648_create_evaluacion_plantel_table.php b/database/migrations/2026_04_30_121648_create_evaluacion_plantel_table.php new file mode 100644 index 00000000..963781fc --- /dev/null +++ b/database/migrations/2026_04_30_121648_create_evaluacion_plantel_table.php @@ -0,0 +1,18 @@ +unsignedBigInteger('evaluacion_id'); + $table->unsignedBigInteger('plantel_id'); + $table->primary(['evaluacion_id', 'plantel_id']); + $table->foreign('evaluacion_id')->references('id')->on('evaluaciones')->onDelete('cascade'); + $table->foreign('plantel_id')->references('id')->on('plantels'); + }); + } + public function down(): void { Schema::dropIfExists('evaluacion_plantel'); } +}; diff --git a/database/migrations/2026_04_30_121649_create_evaluacion_nivel_table.php b/database/migrations/2026_04_30_121649_create_evaluacion_nivel_table.php new file mode 100644 index 00000000..a358d80b --- /dev/null +++ b/database/migrations/2026_04_30_121649_create_evaluacion_nivel_table.php @@ -0,0 +1,18 @@ +unsignedBigInteger('evaluacion_id'); + $table->unsignedBigInteger('nivel_id'); + $table->primary(['evaluacion_id', 'nivel_id']); + $table->foreign('evaluacion_id')->references('id')->on('evaluaciones')->onDelete('cascade'); + $table->foreign('nivel_id')->references('id')->on('nivels'); + }); + } + public function down(): void { Schema::dropIfExists('evaluacion_nivel'); } +}; diff --git a/resources/views/evaluacion/create.blade.php b/resources/views/evaluacion/create.blade.php new file mode 100644 index 00000000..e37f5369 --- /dev/null +++ b/resources/views/evaluacion/create.blade.php @@ -0,0 +1,21 @@ +@extends('layouts.landing') + +@section('title', isset($evaluacion) ? 'Editar Evaluación' : 'Nueva Evaluación') + +@section('content') +
+ +
+
+ + {{ isset($evaluacion) ? 'Editar Evaluación' : 'Nueva Evaluación' }} +
+ + Regresar + +
+ + @livewire('evaluacion-builder', ['evaluacion' => $evaluacion ?? null]) + +
+@endsection diff --git a/resources/views/evaluacion/index.blade.php b/resources/views/evaluacion/index.blade.php new file mode 100644 index 00000000..0a45c87f --- /dev/null +++ b/resources/views/evaluacion/index.blade.php @@ -0,0 +1,119 @@ +@extends('layouts.landing') + +@section('title', 'Evaluaciones') + +@section('content') +
+ + @if(session('info')) + + @endif + +
+
+
+
+ Evaluaciones +
+ + Nueva evaluación + +
+
+ +
+
+ + + + + + + + + + + + + + @forelse($evaluaciones as $evaluacion) + + + + + + + + + + @empty + + + + @endforelse + +
TítuloDirigida aEstadoPreguntasRespuestasVigenciaAcciones
+ {{ $evaluacion->titulo }} + @if($evaluacion->anonima) + + + + @endif + + @php $slug = $evaluacion->tipo?->slug; @endphp + @if($slug === 'docente') + Docentes + @elseif($slug === 'alumno') + Alumnos + @elseif($slug === 'administrativo') + Administrativos + @else + + @endif + + @if($evaluacion->status === 'activa') + Activa + @elseif($evaluacion->status === 'borrador') + Borrador + @else + Cerrada + @endif + {{ $evaluacion->preguntas_count }}{{ $evaluacion->submissions_count }} + @if($evaluacion->fecha_inicio || $evaluacion->fecha_fin) + + {{ $evaluacion->fecha_inicio?->format('d/m/Y') ?? '—' }} + → + {{ $evaluacion->fecha_fin?->format('d/m/Y') ?? '—' }} + + @else + Sin fecha límite + @endif + + + + + + + +
+ @csrf @method('DELETE') + +
+
+ + No hay evaluaciones registradas. +
+
+
+
+
+@endsection diff --git a/resources/views/evaluacion/show.blade.php b/resources/views/evaluacion/show.blade.php new file mode 100644 index 00000000..a4c74483 --- /dev/null +++ b/resources/views/evaluacion/show.blade.php @@ -0,0 +1,204 @@ +@extends('layouts.landing') + +@section('title', $evaluacion->titulo) + +@section('content') +
+ + @if(session('info')) +
+ {{ session('info') }} + +
+ @endif + + +
+
+ {{ $evaluacion->titulo }} +
+
+ + Editar + + + Regresar + +
+
+ +
+ +
+ + +
+
+
Información general
+
+
+ @if($evaluacion->descripcion) +

{{ $evaluacion->descripcion }}

+
+ @endif + +
+
Tipo
+
+ @php $slug = $evaluacion->tipo?->slug; @endphp + @if($slug === 'docente') + Docentes + @elseif($slug === 'alumno') + Alumnos + @elseif($slug === 'administrativo') + Administrativos + @endif +
+ +
Estado
+
+ @if($evaluacion->status === 'activa') + Activa + @elseif($evaluacion->status === 'borrador') + Borrador + @else + Cerrada + @endif +
+ +
Anónima
+
{{ $evaluacion->anonima ? 'Sí' : 'No' }}
+ +
Preguntas
+
{{ $evaluacion->preguntas->count() }}
+ +
Respuestas
+
{{ $evaluacion->submissions->count() }}
+ + @if($evaluacion->fecha_inicio || $evaluacion->fecha_fin) +
Inicio
+
{{ $evaluacion->fecha_inicio?->format('d/m/Y') ?? '—' }}
+
Fin
+
{{ $evaluacion->fecha_fin?->format('d/m/Y') ?? '—' }}
+ @endif +
+
+
+ + + @if($evaluacion->planteles->isNotEmpty()) +
+
+
Planteles
+
+
+ @foreach($evaluacion->planteles as $plantel) + {{ $plantel->name }} + @endforeach +
+
+ @endif + + + @if($evaluacion->niveles->isNotEmpty()) +
+
+
Niveles educativos
+
+
+ @foreach($evaluacion->niveles as $nivel) + {{ $nivel->name }} + @endforeach +
+
+ @endif + + +
+
+
Cambiar estado
+
+
+
+ @csrf @method('PATCH') +
+ +
+ +
+
+
+
+ + +
+
+
+
+ Preguntas ({{ $evaluacion->preguntas->count() }}) +
+
+
+ @forelse($evaluacion->preguntas as $i => $pregunta) +
+
+
+
+ + Pregunta {{ $i + 1 }} + @if($pregunta->requerida) + * + @endif + +

{{ $pregunta->texto }}

+ {{ $pregunta->tipo?->nombre }} +
+
+ + @if($pregunta->tipo?->slug === 'opcion_multiple' && $pregunta->opciones->isNotEmpty()) +
    + @foreach($pregunta->opciones as $opcion) +
  • {{ $opcion->texto }}
  • + @endforeach +
+ @elseif($pregunta->tipo?->slug === 'si_no') +
+ + No +
+ @elseif($pregunta->tipo?->slug === 'escala_5') +
+ @for($k = 1; $k <= 5; $k++) + {{ $k }} + @endfor + / 5 +
+ @elseif($pregunta->tipo?->slug === 'escala_10') +
+ @for($k = 1; $k <= 10; $k++) + {{ $k }} + @endfor + / 10 +
+ @elseif($pregunta->tipo?->slug === 'texto_libre') +
+ — Respuesta abierta — +
+ @endif +
+
+ @empty +

Esta evaluación no tiene preguntas.

+ @endforelse +
+
+
+
+
+@endsection diff --git a/resources/views/layouts/_partials/menu.blade.php b/resources/views/layouts/_partials/menu.blade.php index ad629c9e..8d6be78e 100644 --- a/resources/views/layouts/_partials/menu.blade.php +++ b/resources/views/layouts/_partials/menu.blade.php @@ -104,6 +104,14 @@ @can('horario.index') Horarios @endcan + + + Anuncios + + + + Evaluaciones + @@ -179,15 +187,30 @@ @hasrole(['Alumno']) - @can('pago.index') + @can('pago.index') + + @endcan + - @endcan + + + Mis Tareas + + + @endrole + @hasrole(['Docente']) + + @endrole @@ -196,25 +219,32 @@ + @can('comunidad') + @endcan + + @can('amigos') + @endcan + + @can('mensajes') - + @endcan @can('asistencia') - @endcan + + @can('note.index') + + @endcan - -
- -
- + +
+ +
+ diff --git a/resources/views/livewire/evaluacion-builder.blade.php b/resources/views/livewire/evaluacion-builder.blade.php new file mode 100644 index 00000000..05ee8c93 --- /dev/null +++ b/resources/views/livewire/evaluacion-builder.blade.php @@ -0,0 +1,384 @@ +
+ {{-- Mensajes de error globales --}} + @if($errors->any()) +
+ Por favor corrige los siguientes errores: + + +
+ @endif + +
+ + {{-- ===================== COLUMNA PRINCIPAL ===================== --}} +
+ + {{-- Info general --}} +
+
+
+ Información general +
+
+
+
+ + + @error('titulo')
{{ $message }}
@enderror +
+ +
+ + +
+
+
+ + {{-- ================== CONSTRUCTOR DE PREGUNTAS ================== --}} +
+
+
+ + Preguntas {{ count($preguntas) }} +
+ +
+ +
+ @error('preguntas') +
{{ $message }}
+ @enderror + + @forelse($preguntas as $i => $pregunta) +
+ + {{-- Header de la pregunta --}} +
+ Pregunta {{ $i + 1 }} +
+ + + +
+
+ +
+
+ {{-- Texto de la pregunta --}} +
+
+ + + @error("preguntas.{$i}.texto") +
{{ $message }}
+ @enderror +
+
+ + {{-- Tipo de respuesta --}} +
+
+ + + @error("preguntas.{$i}.pregunta_tipo_id") +
{{ $message }}
+ @enderror +
+
+ + {{-- Obligatoria --}} +
+
+ + +
+
+
+ + {{-- Vista previa y opciones según tipo --}} + @php + $tipoSlug = collect($preguntaTipos) + ->firstWhere('id', (int)($pregunta['pregunta_tipo_id'] ?? 0))['slug'] ?? ''; + @endphp + + {{-- Opciones múltiples --}} + @if($tipoSlug === 'opcion_multiple') +
+ + + @foreach($pregunta['opciones'] as $j => $opcion) +
+
+ {{ $j + 1 }} +
+ +
+ +
+ @error("preguntas.{$i}.opciones.{$j}.texto") +
{{ $message }}
+ @enderror +
+ @endforeach + + + + @if(empty($pregunta['opciones'])) +

+ + Agrega al menos una opción de respuesta. +

+ @endif +
+ + {{-- Vista previa: Sí / No --}} + @elseif($tipoSlug === 'si_no') +
+ + No + Vista previa +
+ + {{-- Vista previa: Escala 1-5 --}} + @elseif($tipoSlug === 'escala_5') +
+ @for($k = 1; $k <= 5; $k++) + {{ $k }} + @endfor + Escala del 1 al 5 — Vista previa +
+ + {{-- Vista previa: Escala 1-10 --}} + @elseif($tipoSlug === 'escala_10') +
+ @for($k = 1; $k <= 10; $k++) + {{ $k }} + @endfor + Escala del 1 al 10 — Vista previa +
+ + {{-- Vista previa: Texto libre --}} + @elseif($tipoSlug === 'texto_libre') +
+ +
+ @endif +
+
+ @empty +
+ +

No hay preguntas todavía.

+

Haz clic en "Agregar pregunta" para comenzar a construir la evaluación.

+
+ @endforelse + + @if(count($preguntas) > 0) +
+ +
+ @endif +
+
+
+ + {{-- ===================== COLUMNA LATERAL ===================== --}} +
+ + {{-- Guardar --}} +
+
+ + + Cancelar + +
+
+ + {{-- Configuración --}} +
+
+
+ Configuración +
+
+
+ +
+ + + @error('evaluacionTipoId') +
{{ $message }}
+ @enderror +
+ +
+ + +
+ +
+
+
+ + +
+
+
+
+ + +
+
+
+ +
+ + +
+
+
+ + {{-- Planteles --}} +
+
+
+ Planteles + (opcional) +
+
+
+ @forelse($planteles as $plantel) +
+ + +
+ @empty +

No hay planteles registrados.

+ @endforelse +
+
+ + {{-- Niveles --}} +
+
+
+ Niveles educativos + (opcional) +
+
+
+ @forelse($niveles as $nivel) +
+ + +
+ @empty +

No hay niveles registrados.

+ @endforelse +
+
+ +
{{-- fin columna lateral --}} +
{{-- fin row --}} +
diff --git a/routes/web.php b/routes/web.php index 38cf9685..94483226 100644 --- a/routes/web.php +++ b/routes/web.php @@ -2,6 +2,7 @@ use App\Events\MessageSent; use App\Http\Controllers\AlumnoController; +use App\Http\Controllers\AnuncioController; use App\Http\Controllers\AreaCodeController; use App\Http\Controllers\AreaController; use App\Http\Controllers\CampanController; @@ -15,6 +16,9 @@ use App\Http\Controllers\UserSearchController; use App\Http\Controllers\CodeController; use App\Http\Controllers\ConceptoController; use App\Http\Controllers\DocenteController; +use App\Http\Controllers\EntregaController; +use App\Http\Controllers\EvaluacionController; +use App\Http\Controllers\TareaController; use App\Http\Controllers\DocumentoController; use App\Http\Controllers\DocumentoTipoController; use App\Http\Controllers\DurationController; @@ -52,7 +56,12 @@ Route::middleware(['auth:sanctum',config('jetstream.auth_session'),'verified', Route::resource('/alumno',AlumnoController::class); Route::resource('/area',AreaController::class); Route::resource('/areacode',AreaCodeController::class); - Route::get('/dashboard', function () {return view('dashboard');})->name('dashboard'); + Route::get('/dashboard', function () { + $user = auth()->user(); + $roles = $user->getRoleNames()->toArray(); + $anuncios = \App\Models\Anuncio::vigentes()->paraRoles($roles)->latest()->get(); + return view('dashboard', compact('anuncios')); + })->name('dashboard'); Route::resource('/campan',CampanController::class); Route::resource('/carrera',CarreraController::class); Route::resource('/code',CodeController::class); @@ -78,6 +87,14 @@ Route::middleware(['auth:sanctum',config('jetstream.auth_session'),'verified', Route::post('/friends/{user}/toggle', [FriendshipController::class, 'toggle'])->name('friendships.toggle'); Route::get('/search', UserSearchController::class)->name('users.search'); + + // Evaluaciones + Route::resource('/evaluacion', EvaluacionController::class); + Route::patch('/evaluacion/{evaluacion}/status', [EvaluacionController::class, 'toggleStatus'])->name('evaluacion.toggleStatus'); + + // Anuncios (gestión: solo Admin) + Route::resource('/anuncio', AnuncioController::class); + Route::patch('/anuncio/{anuncio}/toggle', [AnuncioController::class, 'toggleActive'])->name('anuncio.toggleActive'); Route::get('/perfil/{user}', [SocialProfileController::class, 'show'])->name('social.profile'); Route::resource('/docente',DocenteController::class); @@ -87,6 +104,22 @@ Route::middleware(['auth:sanctum',config('jetstream.auth_session'),'verified', Route::resource('/documentoTipo',DocumentoTipoController::class); Route::resource('/duration',DurationController::class); Route::resource('/grupo', GrupoController::class); + Route::get('/grupo/{grupo}/administrar', [GrupoController::class, 'administrar'])->name('grupo.administrar'); + Route::get('/grupo/{grupo}/ver-materias', [GrupoController::class, 'verMaterias'])->name('grupo.ver.materias'); + + // Tareas (docente) + Route::get('/grupo/{grupo}/material/{material}/tareas', [TareaController::class, 'index'])->name('grupo.material.tareas'); + Route::get('/grupo/{grupo}/material/{material}/tareas/create', [TareaController::class, 'create'])->name('grupo.material.tareas.create'); + Route::post('/grupo/{grupo}/material/{material}/tareas', [TareaController::class, 'store'])->name('grupo.material.tareas.store'); + Route::get('/grupo/{grupo}/material/{material}/tareas/{tarea}', [TareaController::class, 'show'])->name('grupo.material.tareas.show'); + Route::get('/grupo/{grupo}/material/{material}/tareas/{tarea}/edit', [TareaController::class, 'edit'])->name('grupo.material.tareas.edit'); + Route::put('/grupo/{grupo}/material/{material}/tareas/{tarea}', [TareaController::class, 'update'])->name('grupo.material.tareas.update'); + Route::delete('/grupo/{grupo}/material/{material}/tareas/{tarea}', [TareaController::class, 'destroy'])->name('grupo.material.tareas.destroy'); + Route::post('/tareas/{tarea}/calificar/{alumno}', [TareaController::class, 'calificar'])->name('tarea.calificar'); + + // Entregas (alumno) + Route::get('/mis-tareas', [EntregaController::class, 'index'])->name('mis.tareas'); + Route::post('/tareas/{tarea}/entregar', [EntregaController::class, 'store'])->name('tarea.entregar'); Route::get('/horario', [HorarioController::class, 'index'])->name('horario.index'); Route::get('/horario/create', [HorarioController::class, 'create'])->name('horario.create'); Route::post('/horario', [HorarioController::class, 'store'])->name('horario.store');