feat: módulo de evaluaciones para admin con constructor dinámico

- Modelos completos: Evaluacion, EvaluacionTipo, Pregunta, PreguntaTipo,
  PreguntaOpcion, EvaluacionSubmission, Respuesta (con $table explícito
  para nombres en español que Laravel pluraliza incorrectamente)
- Relaciones evaluaciones() añadidas a Plantel y Nivel
- EvaluacionBuilder (Livewire): constructor interactivo con preguntas
  de tipo texto libre, opción múltiple, escala 1-5/1-10 y sí/no;
  reorden con flechas, vista previa por tipo, selección de planteles
  y niveles, configuración de estado, fechas y flag de anónima
- EvaluacionController: CRUD completo + toggleStatus
- Vistas: index, create/edit (con Livewire builder), show con detalle
  de preguntas y cambio de estado
- Rutas resource /evaluacion + PATCH /evaluacion/{id}/status
- Migraciones: evaluaciones, preguntas, pregunta_opciones,
  evaluacion_submissions, respuestas, evaluacion_plantel, evaluacion_nivel
- Menú: enlace Evaluaciones en panel de Administrador

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-30 18:09:48 -06:00
parent f81237b23c
commit 2af36ea272
27 changed files with 1471 additions and 21 deletions
@@ -0,0 +1,64 @@
<?php
namespace App\Http\Controllers;
use App\Models\Evaluacion;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
class EvaluacionController extends Controller
{
public function index()
{
$evaluaciones = Evaluacion::with('tipo')
->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.');
}
}
@@ -0,0 +1,10 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class EvaluacionResponderController extends Controller
{
//
}
+213
View File
@@ -0,0 +1,213 @@
<?php
namespace App\Livewire;
use App\Models\Evaluacion;
use App\Models\EvaluacionTipo;
use App\Models\Nivel;
use App\Models\Plantel;
use App\Models\Pregunta;
use App\Models\PreguntaOpcion;
use App\Models\PreguntaTipo;
use Illuminate\Support\Facades\DB;
use Livewire\Component;
class EvaluacionBuilder extends Component
{
public ?int $evaluacionId = null;
public string $titulo = '';
public string $descripcion = '';
public string $evaluacionTipoId = '';
public string $status = 'borrador';
public string $fechaInicio = '';
public string $fechaFin = '';
public bool $anonima = false;
public array $selectedPlanteles = [];
public array $selectedNiveles = [];
public array $preguntas = [];
// Catálogos cargados en mount (solo lectura en la vista)
public array $tipos = [];
public array $preguntaTipos = [];
public array $planteles = [];
public array $niveles = [];
public function mount($evaluacion = null): void
{
$this->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');
}
}
+45
View File
@@ -0,0 +1,45 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Evaluacion extends Model
{
protected $table = 'evaluaciones';
protected $guarded = [];
protected $casts = [
'anonima' => '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);
}
}
+27
View File
@@ -0,0 +1,27 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
class EvaluacionSubmission extends Model
{
protected $guarded = [];
public function evaluacion(): BelongsTo
{
return $this->belongsTo(Evaluacion::class);
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function respuestas(): HasMany
{
return $this->hasMany(Respuesta::class, 'submission_id');
}
}
+17
View File
@@ -0,0 +1,17 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class EvaluacionTipo extends Model
{
public $timestamps = false;
protected $guarded = [];
public function evaluaciones(): HasMany
{
return $this->hasMany(Evaluacion::class);
}
}
+5
View File
@@ -42,4 +42,9 @@ class Nivel extends Model
'documento_tipo_id'
);
}
public function evaluaciones() : BelongsToMany
{
return $this->belongsToMany(Evaluacion::class, 'evaluacion_nivel');
}
}
+5
View File
@@ -35,4 +35,9 @@ class Plantel extends Model
{
return $this->belongsToMany(Turno::class);
}
public function evaluaciones() : BelongsToMany
{
return $this->belongsToMany(Evaluacion::class, 'evaluacion_plantel');
}
}
+32
View File
@@ -0,0 +1,32 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Pregunta extends Model
{
protected $guarded = [];
public function evaluacion(): BelongsTo
{
return $this->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);
}
}
+18
View File
@@ -0,0 +1,18 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class PreguntaOpcion extends Model
{
protected $table = 'pregunta_opciones';
public $timestamps = false;
protected $guarded = [];
public function pregunta(): BelongsTo
{
return $this->belongsTo(Pregunta::class);
}
}
+17
View File
@@ -0,0 +1,17 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class PreguntaTipo extends Model
{
public $timestamps = false;
protected $guarded = [];
public function preguntas(): HasMany
{
return $this->hasMany(Pregunta::class);
}
}
+26
View File
@@ -0,0 +1,26 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Respuesta extends Model
{
protected $guarded = [];
public function submission(): BelongsTo
{
return $this->belongsTo(EvaluacionSubmission::class);
}
public function pregunta(): BelongsTo
{
return $this->belongsTo(Pregunta::class);
}
public function opcion(): BelongsTo
{
return $this->belongsTo(PreguntaOpcion::class, 'opcion_id');
}
}
@@ -0,0 +1,21 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void {
Schema::create('evaluacion_tipos', function (Blueprint $table) {
$table->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'); }
};
@@ -0,0 +1,23 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void {
Schema::create('pregunta_tipos', function (Blueprint $table) {
$table->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'); }
};
@@ -0,0 +1,22 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void {
Schema::create('evaluaciones', function (Blueprint $table) {
$table->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'); }
};
@@ -0,0 +1,21 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void {
Schema::create('preguntas', function (Blueprint $table) {
$table->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'); }
};
@@ -0,0 +1,17 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void {
Schema::create('pregunta_opciones', function (Blueprint $table) {
$table->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'); }
};
@@ -0,0 +1,19 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void {
Schema::create('evaluacion_submissions', function (Blueprint $table) {
$table->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'); }
};
@@ -0,0 +1,21 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void {
Schema::create('respuestas', function (Blueprint $table) {
$table->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'); }
};
@@ -0,0 +1,18 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void {
Schema::dropIfExists('evaluacion_plantel');
Schema::create('evaluacion_plantel', function (Blueprint $table) {
$table->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'); }
};
@@ -0,0 +1,18 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void {
Schema::dropIfExists('evaluacion_nivel');
Schema::create('evaluacion_nivel', function (Blueprint $table) {
$table->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'); }
};
@@ -0,0 +1,21 @@
@extends('layouts.landing')
@section('title', isset($evaluacion) ? 'Editar Evaluación' : 'Nueva Evaluación')
@section('content')
<div class="container-fluid">
<div class="d-sm-flex align-items-center justify-content-between mb-3">
<h5 class="m-0 font-weight-bold text-primary">
<i class="fas fa-clipboard-list mr-1"></i>
{{ isset($evaluacion) ? 'Editar Evaluación' : 'Nueva Evaluación' }}
</h5>
<a href="{{ route('evaluacion.index') }}" class="btn btn-sm btn-secondary shadow-sm">
<i class="fas fa-arrow-left fa-sm"></i> Regresar
</a>
</div>
@livewire('evaluacion-builder', ['evaluacion' => $evaluacion ?? null])
</div>
@endsection
+119
View File
@@ -0,0 +1,119 @@
@extends('layouts.landing')
@section('title', 'Evaluaciones')
@section('content')
<div class="container-fluid">
@if(session('info'))
<div class="alert alert-success alert-dismissible fade show" role="alert">
{{ session('info') }}
<button type="button" class="close" data-dismiss="alert"><span>&times;</span></button>
</div>
@endif
<div class="card shadow mb-4">
<div class="card-header py-3">
<div class="d-sm-flex align-items-center justify-content-between">
<h6 class="m-0 font-weight-bold text-primary">
<i class="fas fa-clipboard-list mr-1"></i> Evaluaciones
</h6>
<a href="{{ route('evaluacion.create') }}" class="btn btn-sm btn-primary shadow-sm">
<i class="fas fa-plus fa-sm text-white-50"></i> Nueva evaluación
</a>
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-bordered table-hover">
<thead class="thead-light">
<tr>
<th>Título</th>
<th>Dirigida a</th>
<th>Estado</th>
<th class="text-center">Preguntas</th>
<th class="text-center">Respuestas</th>
<th>Vigencia</th>
<th class="text-center">Acciones</th>
</tr>
</thead>
<tbody>
@forelse($evaluaciones as $evaluacion)
<tr>
<td>
<strong>{{ $evaluacion->titulo }}</strong>
@if($evaluacion->anonima)
<span class="badge badge-secondary ml-1" title="Anónima">
<i class="fas fa-user-secret"></i>
</span>
@endif
</td>
<td>
@php $slug = $evaluacion->tipo?->slug; @endphp
@if($slug === 'docente')
<span class="badge badge-info"><i class="fas fa-chalkboard-teacher mr-1"></i>Docentes</span>
@elseif($slug === 'alumno')
<span class="badge badge-success"><i class="fas fa-user-graduate mr-1"></i>Alumnos</span>
@elseif($slug === 'administrativo')
<span class="badge badge-warning text-dark"><i class="fas fa-briefcase mr-1"></i>Administrativos</span>
@else
<span class="badge badge-light"></span>
@endif
</td>
<td>
@if($evaluacion->status === 'activa')
<span class="badge badge-success">Activa</span>
@elseif($evaluacion->status === 'borrador')
<span class="badge badge-secondary">Borrador</span>
@else
<span class="badge badge-dark">Cerrada</span>
@endif
</td>
<td class="text-center">{{ $evaluacion->preguntas_count }}</td>
<td class="text-center">{{ $evaluacion->submissions_count }}</td>
<td>
@if($evaluacion->fecha_inicio || $evaluacion->fecha_fin)
<small>
{{ $evaluacion->fecha_inicio?->format('d/m/Y') ?? '—' }}
&rarr;
{{ $evaluacion->fecha_fin?->format('d/m/Y') ?? '—' }}
</small>
@else
<small class="text-muted">Sin fecha límite</small>
@endif
</td>
<td class="text-center text-nowrap">
<a href="{{ route('evaluacion.show', $evaluacion) }}"
class="btn btn-sm btn-info" title="Ver detalle">
<i class="fas fa-eye"></i>
</a>
<a href="{{ route('evaluacion.edit', $evaluacion) }}"
class="btn btn-sm btn-primary" title="Editar">
<i class="fas fa-edit"></i>
</a>
<form action="{{ route('evaluacion.destroy', $evaluacion) }}"
method="POST" class="d-inline"
onsubmit="return confirm('¿Eliminar esta evaluación y todas sus preguntas?')">
@csrf @method('DELETE')
<button class="btn btn-sm btn-danger" title="Eliminar">
<i class="fas fa-trash"></i>
</button>
</form>
</td>
</tr>
@empty
<tr>
<td colspan="7" class="text-center text-muted py-4">
<i class="fas fa-clipboard fa-2x mb-2 d-block"></i>
No hay evaluaciones registradas.
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</div>
@endsection
+204
View File
@@ -0,0 +1,204 @@
@extends('layouts.landing')
@section('title', $evaluacion->titulo)
@section('content')
<div class="container-fluid">
@if(session('info'))
<div class="alert alert-success alert-dismissible fade show">
{{ session('info') }}
<button type="button" class="close" data-dismiss="alert"><span>&times;</span></button>
</div>
@endif
<!-- Header -->
<div class="d-sm-flex align-items-center justify-content-between mb-3">
<h5 class="m-0 font-weight-bold text-primary">
<i class="fas fa-clipboard-list mr-1"></i> {{ $evaluacion->titulo }}
</h5>
<div>
<a href="{{ route('evaluacion.edit', $evaluacion) }}" class="btn btn-sm btn-primary mr-1">
<i class="fas fa-edit"></i> Editar
</a>
<a href="{{ route('evaluacion.index') }}" class="btn btn-sm btn-secondary">
<i class="fas fa-arrow-left"></i> Regresar
</a>
</div>
</div>
<div class="row">
<!-- Columna info general -->
<div class="col-lg-4 mb-4">
<!-- Tarjeta de info -->
<div class="card shadow mb-4">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">Información general</h6>
</div>
<div class="card-body">
@if($evaluacion->descripcion)
<p class="text-muted">{{ $evaluacion->descripcion }}</p>
<hr>
@endif
<dl class="row mb-0">
<dt class="col-sm-5">Tipo</dt>
<dd class="col-sm-7">
@php $slug = $evaluacion->tipo?->slug; @endphp
@if($slug === 'docente')
<span class="badge badge-info">Docentes</span>
@elseif($slug === 'alumno')
<span class="badge badge-success">Alumnos</span>
@elseif($slug === 'administrativo')
<span class="badge badge-warning text-dark">Administrativos</span>
@endif
</dd>
<dt class="col-sm-5">Estado</dt>
<dd class="col-sm-7">
@if($evaluacion->status === 'activa')
<span class="badge badge-success">Activa</span>
@elseif($evaluacion->status === 'borrador')
<span class="badge badge-secondary">Borrador</span>
@else
<span class="badge badge-dark">Cerrada</span>
@endif
</dd>
<dt class="col-sm-5">Anónima</dt>
<dd class="col-sm-7">{{ $evaluacion->anonima ? 'Sí' : 'No' }}</dd>
<dt class="col-sm-5">Preguntas</dt>
<dd class="col-sm-7">{{ $evaluacion->preguntas->count() }}</dd>
<dt class="col-sm-5">Respuestas</dt>
<dd class="col-sm-7">{{ $evaluacion->submissions->count() }}</dd>
@if($evaluacion->fecha_inicio || $evaluacion->fecha_fin)
<dt class="col-sm-5">Inicio</dt>
<dd class="col-sm-7">{{ $evaluacion->fecha_inicio?->format('d/m/Y') ?? '—' }}</dd>
<dt class="col-sm-5">Fin</dt>
<dd class="col-sm-7">{{ $evaluacion->fecha_fin?->format('d/m/Y') ?? '—' }}</dd>
@endif
</dl>
</div>
</div>
<!-- Planteles asignados -->
@if($evaluacion->planteles->isNotEmpty())
<div class="card shadow mb-4">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">Planteles</h6>
</div>
<div class="card-body py-2">
@foreach($evaluacion->planteles as $plantel)
<span class="badge badge-light border mr-1 mb-1">{{ $plantel->name }}</span>
@endforeach
</div>
</div>
@endif
<!-- Niveles asignados -->
@if($evaluacion->niveles->isNotEmpty())
<div class="card shadow mb-4">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">Niveles educativos</h6>
</div>
<div class="card-body py-2">
@foreach($evaluacion->niveles as $nivel)
<span class="badge badge-light border mr-1 mb-1">{{ $nivel->name }}</span>
@endforeach
</div>
</div>
@endif
<!-- Cambiar estado -->
<div class="card shadow mb-4">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">Cambiar estado</h6>
</div>
<div class="card-body">
<form action="{{ route('evaluacion.toggleStatus', $evaluacion) }}" method="POST">
@csrf @method('PATCH')
<div class="form-group">
<select name="status" class="form-control form-control-sm">
<option value="borrador" {{ $evaluacion->status === 'borrador' ? 'selected' : '' }}>Borrador</option>
<option value="activa" {{ $evaluacion->status === 'activa' ? 'selected' : '' }}>Activa</option>
<option value="cerrada" {{ $evaluacion->status === 'cerrada' ? 'selected' : '' }}>Cerrada</option>
</select>
</div>
<button type="submit" class="btn btn-sm btn-outline-primary btn-block">
<i class="fas fa-sync-alt mr-1"></i> Actualizar estado
</button>
</form>
</div>
</div>
</div>
<!-- Columna preguntas -->
<div class="col-lg-8 mb-4">
<div class="card shadow">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">
Preguntas ({{ $evaluacion->preguntas->count() }})
</h6>
</div>
<div class="card-body">
@forelse($evaluacion->preguntas as $i => $pregunta)
<div class="card border-left-primary mb-3">
<div class="card-body py-2 px-3">
<div class="d-flex justify-content-between align-items-start">
<div class="flex-grow-1">
<small class="text-muted font-weight-bold d-block mb-1">
Pregunta {{ $i + 1 }}
@if($pregunta->requerida)
<span class="text-danger">*</span>
@endif
</small>
<p class="mb-1 font-weight-bold">{{ $pregunta->texto }}</p>
<small class="text-muted">{{ $pregunta->tipo?->nombre }}</small>
</div>
</div>
@if($pregunta->tipo?->slug === 'opcion_multiple' && $pregunta->opciones->isNotEmpty())
<ul class="mt-2 mb-0 pl-3">
@foreach($pregunta->opciones as $opcion)
<li><small>{{ $opcion->texto }}</small></li>
@endforeach
</ul>
@elseif($pregunta->tipo?->slug === 'si_no')
<div class="mt-2">
<span class="badge badge-outline-secondary border mr-1"></span>
<span class="badge badge-outline-secondary border">No</span>
</div>
@elseif($pregunta->tipo?->slug === 'escala_5')
<div class="mt-2">
@for($k = 1; $k <= 5; $k++)
<span class="badge badge-light border mr-1">{{ $k }}</span>
@endfor
<small class="text-muted">/ 5</small>
</div>
@elseif($pregunta->tipo?->slug === 'escala_10')
<div class="mt-2">
@for($k = 1; $k <= 10; $k++)
<span class="badge badge-light border mr-1">{{ $k }}</span>
@endfor
<small class="text-muted">/ 10</small>
</div>
@elseif($pregunta->tipo?->slug === 'texto_libre')
<div class="mt-2">
<small class="text-muted font-italic"> Respuesta abierta </small>
</div>
@endif
</div>
</div>
@empty
<p class="text-muted text-center py-3">Esta evaluación no tiene preguntas.</p>
@endforelse
</div>
</div>
</div>
</div>
</div>
@endsection
@@ -104,6 +104,14 @@
@can('horario.index')
<a class="collapse-item" href="{{ route('horario.index') }}" style="color:#85888e">Horarios</a>
@endcan
<a class="collapse-item" href="{{ route('anuncio.index') }}" style="color:#85888e">
<i class="fas fa-bullhorn mr-1"></i> Anuncios
</a>
<a class="collapse-item" href="{{ route('evaluacion.index') }}" style="color:#85888e">
<i class="fas fa-clipboard-list mr-1"></i> Evaluaciones
</a>
</div>
</div>
</li>
@@ -187,34 +195,56 @@
</li>
@endcan
<li class="nav-item">
<a class="nav-link" href="{{ route('mis.tareas') }}">
<i class="fas fa-fw fa-tasks" style="color: #e8e8e8;"></i>
<span>Mis Tareas</span>
</a>
</li>
@endrole
@hasrole(['Docente'])
<li class="nav-item">
<a class="nav-link" href="{{ route('grupo.index') }}">
<i class="fa-duotone fa-regular fa-user-group" style="--fa-primary-color: rgb(255, 255, 255); --fa-secondary-color: rgb(255, 255, 255);"></i>
<span>Mis grupos</span></a>
</li>
@endrole
<!-- Divider -->
<hr class="sidebar-divider">
<!-- Heading -->
<div class="sidebar-heading" style="color:white">Adicionales</div>
@can('comunidad')
<li class="nav-item">
<a class="nav-link" href="{{ route('feed') }}">
<i class="fas fa-fw fa-stream" style="color:#e8e8e8"></i>
<span>Comunidad</span>
</a>
</li>
@endcan
@can('amigos')
<li class="nav-item">
<a class="nav-link" href="{{ route('friendships.index') }}">
<i class="fas fa-fw fa-user-friends" style="color:#e8e8e8"></i>
<span>Amigos</span>
</a>
</li>
@endcan
@can('mensajes')
<li class="nav-item">
<a class="nav-link" href="{{ route('chat.index') }}">
<i class="fas fa-fw fa-comment-dots" style="color:#e8e8e8"></i>
<span>Mensajes</span>
</a>
</li>
@endcan
@can('asistencia')
<li class="nav-item">
@@ -0,0 +1,384 @@
<div>
{{-- Mensajes de error globales --}}
@if($errors->any())
<div class="alert alert-danger alert-dismissible fade show mb-3">
<strong>Por favor corrige los siguientes errores:</strong>
<ul class="mb-0 mt-1">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
<button type="button" class="close" data-dismiss="alert"><span>&times;</span></button>
</div>
@endif
<div class="row">
{{-- ===================== COLUMNA PRINCIPAL ===================== --}}
<div class="col-lg-8">
{{-- Info general --}}
<div class="card shadow mb-4">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">
<i class="fas fa-info-circle mr-1"></i> Información general
</h6>
</div>
<div class="card-body">
<div class="form-group">
<label>Título <span class="text-danger">*</span></label>
<input type="text"
wire:model="titulo"
class="form-control @error('titulo') is-invalid @enderror"
placeholder="Ej: Evaluación docente 1er semestre 2026">
@error('titulo') <div class="invalid-feedback">{{ $message }}</div> @enderror
</div>
<div class="form-group">
<label>Descripción / Instrucciones</label>
<textarea wire:model="descripcion"
class="form-control"
rows="3"
placeholder="Instrucciones opcionales para los evaluadores…"></textarea>
</div>
</div>
</div>
{{-- ================== CONSTRUCTOR DE PREGUNTAS ================== --}}
<div class="card shadow mb-4">
<div class="card-header py-3 d-flex align-items-center justify-content-between">
<h6 class="m-0 font-weight-bold text-primary">
<i class="fas fa-list-ol mr-1"></i>
Preguntas <span class="badge badge-primary ml-1">{{ count($preguntas) }}</span>
</h6>
<button type="button" wire:click="addPregunta"
class="btn btn-sm btn-outline-primary">
<i class="fas fa-plus mr-1"></i> Agregar pregunta
</button>
</div>
<div class="card-body">
@error('preguntas')
<div class="alert alert-warning py-2">{{ $message }}</div>
@enderror
@forelse($preguntas as $i => $pregunta)
<div wire:key="pregunta-{{ $i }}"
class="card mb-3"
style="border-left: 4px solid #4e73df;">
{{-- Header de la pregunta --}}
<div class="card-header py-2 d-flex align-items-center justify-content-between bg-light">
<span class="font-weight-bold text-primary small">Pregunta {{ $i + 1 }}</span>
<div class="btn-group btn-group-sm">
<button type="button"
wire:click="movePregunta({{ $i }}, 'up')"
class="btn btn-outline-secondary"
title="Subir" {{ $i === 0 ? 'disabled' : '' }}>
<i class="fas fa-chevron-up"></i>
</button>
<button type="button"
wire:click="movePregunta({{ $i }}, 'down')"
class="btn btn-outline-secondary"
title="Bajar" {{ $i === count($preguntas) - 1 ? 'disabled' : '' }}>
<i class="fas fa-chevron-down"></i>
</button>
<button type="button"
wire:click="removePregunta({{ $i }})"
class="btn btn-outline-danger"
title="Eliminar"
onclick="return confirm('¿Eliminar esta pregunta?')">
<i class="fas fa-times"></i>
</button>
</div>
</div>
<div class="card-body py-3">
<div class="row">
{{-- Texto de la pregunta --}}
<div class="col-md-7">
<div class="form-group mb-2">
<label class="small font-weight-bold">Pregunta <span class="text-danger">*</span></label>
<input type="text"
wire:model="preguntas.{{ $i }}.texto"
class="form-control form-control-sm @error("preguntas.{$i}.texto") is-invalid @enderror"
placeholder="Escribe la pregunta…">
@error("preguntas.{$i}.texto")
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
</div>
{{-- Tipo de respuesta --}}
<div class="col-md-4">
<div class="form-group mb-2">
<label class="small font-weight-bold">Tipo de respuesta</label>
<select wire:model.live="preguntas.{{ $i }}.pregunta_tipo_id"
class="form-control form-control-sm @error("preguntas.{$i}.pregunta_tipo_id") is-invalid @enderror">
@foreach($preguntaTipos as $tipo)
<option value="{{ $tipo['id'] }}">{{ $tipo['nombre'] }}</option>
@endforeach
</select>
@error("preguntas.{$i}.pregunta_tipo_id")
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
</div>
{{-- Obligatoria --}}
<div class="col-md-1 d-flex align-items-end pb-3">
<div class="form-check" title="Obligatoria">
<input type="checkbox"
wire:model="preguntas.{{ $i }}.requerida"
class="form-check-input"
id="req-{{ $i }}">
<label class="form-check-label small" for="req-{{ $i }}">*</label>
</div>
</div>
</div>
{{-- 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')
<div class="mt-2 p-3 bg-light rounded">
<label class="small font-weight-bold mb-2 d-block">
Opciones de respuesta
</label>
@foreach($pregunta['opciones'] as $j => $opcion)
<div wire:key="opcion-{{ $i }}-{{ $j }}"
class="input-group input-group-sm mb-2">
<div class="input-group-prepend">
<span class="input-group-text">{{ $j + 1 }}</span>
</div>
<input type="text"
wire:model="preguntas.{{ $i }}.opciones.{{ $j }}.texto"
class="form-control @error("preguntas.{$i}.opciones.{$j}.texto") is-invalid @enderror"
placeholder="Opción {{ $j + 1 }}">
<div class="input-group-append">
<button type="button"
wire:click="removeOpcion({{ $i }}, {{ $j }})"
class="btn btn-outline-danger">
<i class="fas fa-times"></i>
</button>
</div>
@error("preguntas.{$i}.opciones.{$j}.texto")
<div class="invalid-feedback d-block">{{ $message }}</div>
@enderror
</div>
@endforeach
<button type="button"
wire:click="addOpcion({{ $i }})"
class="btn btn-outline-primary btn-sm mt-1">
<i class="fas fa-plus mr-1"></i> Agregar opción
</button>
@if(empty($pregunta['opciones']))
<p class="text-muted small mt-2 mb-0">
<i class="fas fa-info-circle"></i>
Agrega al menos una opción de respuesta.
</p>
@endif
</div>
{{-- Vista previa: / No --}}
@elseif($tipoSlug === 'si_no')
<div class="mt-2 p-2 bg-light rounded d-flex align-items-center">
<span class="badge badge-success mr-2 px-3 py-2"></span>
<span class="badge badge-danger px-3 py-2">No</span>
<small class="text-muted ml-3">Vista previa</small>
</div>
{{-- Vista previa: Escala 1-5 --}}
@elseif($tipoSlug === 'escala_5')
<div class="mt-2 p-2 bg-light rounded">
@for($k = 1; $k <= 5; $k++)
<span class="badge badge-light border mr-1 px-3 py-2">{{ $k }}</span>
@endfor
<small class="text-muted ml-2">Escala del 1 al 5 Vista previa</small>
</div>
{{-- Vista previa: Escala 1-10 --}}
@elseif($tipoSlug === 'escala_10')
<div class="mt-2 p-2 bg-light rounded">
@for($k = 1; $k <= 10; $k++)
<span class="badge badge-light border mr-1 px-2 py-2">{{ $k }}</span>
@endfor
<small class="text-muted ml-2">Escala del 1 al 10 Vista previa</small>
</div>
{{-- Vista previa: Texto libre --}}
@elseif($tipoSlug === 'texto_libre')
<div class="mt-2 p-2 bg-light rounded">
<input type="text" class="form-control form-control-sm" disabled
placeholder="El evaluador escribirá su respuesta aquí…">
</div>
@endif
</div>
</div>
@empty
<div class="text-center py-5 text-muted">
<i class="fas fa-question-circle fa-3x mb-3 d-block text-primary" style="opacity:.4"></i>
<p class="mb-0">No hay preguntas todavía.</p>
<p class="small">Haz clic en <strong>"Agregar pregunta"</strong> para comenzar a construir la evaluación.</p>
</div>
@endforelse
@if(count($preguntas) > 0)
<div class="text-center mt-3">
<button type="button" wire:click="addPregunta"
class="btn btn-outline-primary btn-sm">
<i class="fas fa-plus mr-1"></i> Agregar otra pregunta
</button>
</div>
@endif
</div>
</div>
</div>
{{-- ===================== COLUMNA LATERAL ===================== --}}
<div class="col-lg-4">
{{-- Guardar --}}
<div class="card shadow mb-4">
<div class="card-body">
<button type="button" wire:click="guardar"
wire:loading.attr="disabled"
class="btn btn-success btn-block btn-lg">
<span wire:loading.remove>
<i class="fas fa-save mr-1"></i>
{{ $evaluacionId ? 'Guardar cambios' : 'Crear evaluación' }}
</span>
<span wire:loading>
<i class="fas fa-spinner fa-spin mr-1"></i> Guardando…
</span>
</button>
<a href="{{ route('evaluacion.index') }}"
class="btn btn-outline-secondary btn-block btn-sm mt-2">
Cancelar
</a>
</div>
</div>
{{-- Configuración --}}
<div class="card shadow mb-4">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">
<i class="fas fa-cog mr-1"></i> Configuración
</h6>
</div>
<div class="card-body">
<div class="form-group">
<label class="small font-weight-bold">
Dirigida a <span class="text-danger">*</span>
</label>
<select wire:model="evaluacionTipoId"
class="form-control form-control-sm @error('evaluacionTipoId') is-invalid @enderror">
<option value=""> Selecciona </option>
@foreach($tipos as $tipo)
<option value="{{ $tipo['id'] }}">{{ $tipo['nombre'] }}</option>
@endforeach
</select>
@error('evaluacionTipoId')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="form-group">
<label class="small font-weight-bold">Estado</label>
<select wire:model="status" class="form-control form-control-sm">
<option value="borrador">Borrador</option>
<option value="activa">Activa</option>
<option value="cerrada">Cerrada</option>
</select>
</div>
<div class="row">
<div class="col-6">
<div class="form-group">
<label class="small font-weight-bold">Fecha inicio</label>
<input type="date" wire:model="fechaInicio"
class="form-control form-control-sm">
</div>
</div>
<div class="col-6">
<div class="form-group">
<label class="small font-weight-bold">Fecha fin</label>
<input type="date" wire:model="fechaFin"
class="form-control form-control-sm">
</div>
</div>
</div>
<div class="form-check">
<input type="checkbox" wire:model="anonima"
class="form-check-input" id="anonima">
<label class="form-check-label small" for="anonima">
<i class="fas fa-user-secret mr-1"></i> Evaluación anónima
</label>
</div>
</div>
</div>
{{-- Planteles --}}
<div class="card shadow mb-4">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">
<i class="fas fa-school mr-1"></i> Planteles
<small class="text-muted font-weight-normal">(opcional)</small>
</h6>
</div>
<div class="card-body py-2" style="max-height:180px; overflow-y:auto;">
@forelse($planteles as $plantel)
<div class="form-check py-1">
<input type="checkbox"
wire:model="selectedPlanteles"
value="{{ $plantel['id'] }}"
class="form-check-input"
id="plantel-{{ $plantel['id'] }}">
<label class="form-check-label small" for="plantel-{{ $plantel['id'] }}">
{{ $plantel['name'] }}
</label>
</div>
@empty
<p class="text-muted small mb-0">No hay planteles registrados.</p>
@endforelse
</div>
</div>
{{-- Niveles --}}
<div class="card shadow mb-4">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">
<i class="fas fa-layer-group mr-1"></i> Niveles educativos
<small class="text-muted font-weight-normal">(opcional)</small>
</h6>
</div>
<div class="card-body py-2" style="max-height:180px; overflow-y:auto;">
@forelse($niveles as $nivel)
<div class="form-check py-1">
<input type="checkbox"
wire:model="selectedNiveles"
value="{{ $nivel['id'] }}"
class="form-check-input"
id="nivel-{{ $nivel['id'] }}">
<label class="form-check-label small" for="nivel-{{ $nivel['id'] }}">
{{ $nivel['name'] }}
</label>
</div>
@empty
<p class="text-muted small mb-0">No hay niveles registrados.</p>
@endforelse
</div>
</div>
</div>{{-- fin columna lateral --}}
</div>{{-- fin row --}}
</div>
+34 -1
View File
@@ -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');