434 lines
23 KiB
PHP
434 lines
23 KiB
PHP
<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"
|
||
class="btn btn-outline-danger"
|
||
title="Eliminar"
|
||
onclick="confirmarEliminarPregunta({{ $i }}, this)">
|
||
<i class="fas fa-times"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card-body py-3">
|
||
@php
|
||
$tipoAlumnoId = (string)(collect($tipos)->firstWhere('slug', 'alumno')['id'] ?? 0);
|
||
$esEvalAlumno = $evaluacionTipoId === $tipoAlumnoId;
|
||
@endphp
|
||
<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-{{ $esEvalAlumno ? '3' : '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>
|
||
|
||
{{-- Pregunta de docente (solo en evaluaciones de tipo Alumno) --}}
|
||
@if($esEvalAlumno)
|
||
<div class="col-md-1 d-flex align-items-end pb-3">
|
||
<div class="form-check"
|
||
title="Marcar si esta pregunta evalúa a un docente específico">
|
||
<input type="checkbox"
|
||
wire:model="preguntas.{{ $i }}.es_docente"
|
||
class="form-check-input"
|
||
id="esd-{{ $i }}">
|
||
<label class="form-check-label small text-primary" for="esd-{{ $i }}">
|
||
<i class="fas fa-chalkboard-teacher"></i>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
@endif
|
||
</div>
|
||
|
||
{{-- Indicador visual si es pregunta de docente --}}
|
||
@if($esEvalAlumno && !empty($pregunta['es_docente']))
|
||
<div class="alert alert-info py-1 px-2 mb-2 d-flex align-items-center" style="font-size:.82rem;">
|
||
<i class="fas fa-chalkboard-teacher mr-2"></i>
|
||
Esta pregunta se repetirá por cada docente que imparte clase al alumno.
|
||
</div>
|
||
@endif
|
||
|
||
{{-- 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>
|
||
|
||
@push('scripts')
|
||
<script>
|
||
function confirmarEliminarPregunta(index, el) {
|
||
Swal.fire({
|
||
title: '¿Eliminar pregunta?',
|
||
text: 'Esta acción no se puede deshacer.',
|
||
icon: 'warning',
|
||
showCancelButton: true,
|
||
confirmButtonText: 'Sí, eliminar',
|
||
cancelButtonText: 'Cancelar',
|
||
confirmButtonColor: '#e74a3b',
|
||
cancelButtonColor: '#6c757d',
|
||
}).then(result => {
|
||
if (result.isConfirmed) {
|
||
const wireEl = el.closest('[wire\\:id]');
|
||
Livewire.find(wireEl.getAttribute('wire:id')).call('removePregunta', index);
|
||
}
|
||
});
|
||
}
|
||
</script>
|
||
@endpush
|