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,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')
|
||||
<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>
|
||||
@@ -179,15 +187,30 @@
|
||||
|
||||
@hasrole(['Alumno'])
|
||||
|
||||
@can('pago.index')
|
||||
@can('pago.index')
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ route('pago.index') }}">
|
||||
<i class="fas fa-money-bill-alt" style="color: #e8e8e8;"></i>
|
||||
<span>Mis pagos</span></a>
|
||||
</li>
|
||||
@endcan
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ route('pago.index') }}">
|
||||
<i class="fas fa-money-bill-alt" style="color: #e8e8e8;"></i>
|
||||
<span>Mis pagos</span></a>
|
||||
</li>
|
||||
@endcan
|
||||
<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
|
||||
|
||||
|
||||
@@ -196,25 +219,32 @@
|
||||
<!-- 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">
|
||||
@@ -236,17 +266,17 @@
|
||||
|
||||
|
||||
|
||||
<!-- Nav Item - notes -->
|
||||
@can('note.index')
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{route('note.index')}}">
|
||||
<i class="fas fa-fw fa-sticky-note" style="color: #e8e8e8;"></i>
|
||||
<span>Notas</span></a>
|
||||
</li>
|
||||
@endcan
|
||||
<!-- Nav Item - notes -->
|
||||
@can('note.index')
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{route('note.index')}}">
|
||||
<i class="fas fa-fw fa-sticky-note" style="color: #e8e8e8;"></i>
|
||||
<span>Notas</span></a>
|
||||
</li>
|
||||
@endcan
|
||||
|
||||
<!-- Sidebar Toggler (Sidebar) -->
|
||||
<div class="text-center d-none d-md-inline">
|
||||
<button class="rounded-circle border-0" id="sidebarToggle"></button>
|
||||
</div>
|
||||
</ul>
|
||||
<!-- Sidebar Toggler (Sidebar) -->
|
||||
<div class="text-center d-none d-md-inline">
|
||||
<button class="rounded-circle border-0" id="sidebarToggle"></button>
|
||||
</div>
|
||||
</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>
|
||||
Reference in New Issue
Block a user