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:
@@ -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
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
@@ -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');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -42,4 +42,9 @@ class Nivel extends Model
|
|||||||
'documento_tipo_id'
|
'documento_tipo_id'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function evaluaciones() : BelongsToMany
|
||||||
|
{
|
||||||
|
return $this->belongsToMany(Evaluacion::class, 'evaluacion_nivel');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,4 +35,9 @@ class Plantel extends Model
|
|||||||
{
|
{
|
||||||
return $this->belongsToMany(Turno::class);
|
return $this->belongsToMany(Turno::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function evaluaciones() : BelongsToMany
|
||||||
|
{
|
||||||
|
return $this->belongsToMany(Evaluacion::class, 'evaluacion_plantel');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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>×</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') ?? '—' }}
|
||||||
|
→
|
||||||
|
{{ $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
|
||||||
@@ -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>×</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">Sí</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')
|
@can('horario.index')
|
||||||
<a class="collapse-item" href="{{ route('horario.index') }}" style="color:#85888e">Horarios</a>
|
<a class="collapse-item" href="{{ route('horario.index') }}" style="color:#85888e">Horarios</a>
|
||||||
@endcan
|
@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>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
@@ -187,34 +195,56 @@
|
|||||||
</li>
|
</li>
|
||||||
@endcan
|
@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
|
@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 -->
|
<!-- Divider -->
|
||||||
<hr class="sidebar-divider">
|
<hr class="sidebar-divider">
|
||||||
<!-- Heading -->
|
<!-- Heading -->
|
||||||
<div class="sidebar-heading" style="color:white">Adicionales</div>
|
<div class="sidebar-heading" style="color:white">Adicionales</div>
|
||||||
|
|
||||||
|
@can('comunidad')
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{{ route('feed') }}">
|
<a class="nav-link" href="{{ route('feed') }}">
|
||||||
<i class="fas fa-fw fa-stream" style="color:#e8e8e8"></i>
|
<i class="fas fa-fw fa-stream" style="color:#e8e8e8"></i>
|
||||||
<span>Comunidad</span>
|
<span>Comunidad</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
@endcan
|
||||||
|
|
||||||
|
@can('amigos')
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{{ route('friendships.index') }}">
|
<a class="nav-link" href="{{ route('friendships.index') }}">
|
||||||
<i class="fas fa-fw fa-user-friends" style="color:#e8e8e8"></i>
|
<i class="fas fa-fw fa-user-friends" style="color:#e8e8e8"></i>
|
||||||
<span>Amigos</span>
|
<span>Amigos</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
@endcan
|
||||||
|
|
||||||
|
@can('mensajes')
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{{ route('chat.index') }}">
|
<a class="nav-link" href="{{ route('chat.index') }}">
|
||||||
<i class="fas fa-fw fa-comment-dots" style="color:#e8e8e8"></i>
|
<i class="fas fa-fw fa-comment-dots" style="color:#e8e8e8"></i>
|
||||||
<span>Mensajes</span>
|
<span>Mensajes</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
@endcan
|
||||||
|
|
||||||
@can('asistencia')
|
@can('asistencia')
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
@@ -249,4 +279,4 @@
|
|||||||
<div class="text-center d-none d-md-inline">
|
<div class="text-center d-none d-md-inline">
|
||||||
<button class="rounded-circle border-0" id="sidebarToggle"></button>
|
<button class="rounded-circle border-0" id="sidebarToggle"></button>
|
||||||
</div>
|
</div>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -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>×</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: Sí / 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">Sí</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
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
use App\Events\MessageSent;
|
use App\Events\MessageSent;
|
||||||
use App\Http\Controllers\AlumnoController;
|
use App\Http\Controllers\AlumnoController;
|
||||||
|
use App\Http\Controllers\AnuncioController;
|
||||||
use App\Http\Controllers\AreaCodeController;
|
use App\Http\Controllers\AreaCodeController;
|
||||||
use App\Http\Controllers\AreaController;
|
use App\Http\Controllers\AreaController;
|
||||||
use App\Http\Controllers\CampanController;
|
use App\Http\Controllers\CampanController;
|
||||||
@@ -15,6 +16,9 @@ use App\Http\Controllers\UserSearchController;
|
|||||||
use App\Http\Controllers\CodeController;
|
use App\Http\Controllers\CodeController;
|
||||||
use App\Http\Controllers\ConceptoController;
|
use App\Http\Controllers\ConceptoController;
|
||||||
use App\Http\Controllers\DocenteController;
|
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\DocumentoController;
|
||||||
use App\Http\Controllers\DocumentoTipoController;
|
use App\Http\Controllers\DocumentoTipoController;
|
||||||
use App\Http\Controllers\DurationController;
|
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('/alumno',AlumnoController::class);
|
||||||
Route::resource('/area',AreaController::class);
|
Route::resource('/area',AreaController::class);
|
||||||
Route::resource('/areacode',AreaCodeController::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('/campan',CampanController::class);
|
||||||
Route::resource('/carrera',CarreraController::class);
|
Route::resource('/carrera',CarreraController::class);
|
||||||
Route::resource('/code',CodeController::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::post('/friends/{user}/toggle', [FriendshipController::class, 'toggle'])->name('friendships.toggle');
|
||||||
|
|
||||||
Route::get('/search', UserSearchController::class)->name('users.search');
|
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::get('/perfil/{user}', [SocialProfileController::class, 'show'])->name('social.profile');
|
||||||
|
|
||||||
Route::resource('/docente',DocenteController::class);
|
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('/documentoTipo',DocumentoTipoController::class);
|
||||||
Route::resource('/duration',DurationController::class);
|
Route::resource('/duration',DurationController::class);
|
||||||
Route::resource('/grupo', GrupoController::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', [HorarioController::class, 'index'])->name('horario.index');
|
||||||
Route::get('/horario/create', [HorarioController::class, 'create'])->name('horario.create');
|
Route::get('/horario/create', [HorarioController::class, 'create'])->name('horario.create');
|
||||||
Route::post('/horario', [HorarioController::class, 'store'])->name('horario.store');
|
Route::post('/horario', [HorarioController::class, 'store'])->name('horario.store');
|
||||||
|
|||||||
Reference in New Issue
Block a user