Modificación del userController para el cambnio de rol si es alumno o docente

This commit is contained in:
2026-05-05 18:33:36 -06:00
parent 59b7ff87f3
commit 91d0813a9c
21 changed files with 1267 additions and 165 deletions
+27 -5
View File
@@ -3,6 +3,7 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Models\Alumno; use App\Models\Alumno;
use App\Models\Campan;
use App\Models\Entrega; use App\Models\Entrega;
use App\Models\Tarea; use App\Models\Tarea;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@@ -16,22 +17,43 @@ class EntregaController extends Controller
$alumno = Alumno::whereHas('alumnos', fn($q) => $q->where('id', Auth::id()))->first(); $alumno = Alumno::whereHas('alumnos', fn($q) => $q->where('id', Auth::id()))->first();
if (!$alumno) { if (!$alumno) {
return view('tarea.mis_tareas', ['tareas' => collect(), 'entregasMap' => collect()]); return view('tarea.mis_tareas', [
'tareasPendientes' => collect(),
'tareasEntregadas' => collect(),
'entregasMap' => collect(),
'cicloActual' => null,
]);
} }
$gruposIds = $alumno->grupos()->pluck('grupos.id'); $cicloActual = Campan::where('status', 1)->first();
$tareas = Tarea::whereIn('grupo_id', $gruposIds) // Solo grupos del ciclo activo
$gruposActuales = $alumno->grupos()
->when($cicloActual, fn($q) => $q->where('ciclo', $cicloActual->id))
->pluck('grupos.id');
$todasTareas = Tarea::whereIn('grupo_id', $gruposActuales)
->with(['material', 'grupo']) ->with(['material', 'grupo'])
->orderByDesc('created_at') ->orderByDesc('created_at')
->get(); ->get();
$entregasMap = Entrega::where('alumno_id', $alumno->id) $entregasMap = Entrega::where('alumno_id', $alumno->id)
->whereIn('tarea_id', $tareas->pluck('id')) ->whereIn('tarea_id', $todasTareas->pluck('id'))
->get() ->get()
->keyBy('tarea_id'); ->keyBy('tarea_id');
return view('tarea.mis_tareas', compact('tareas', 'entregasMap', 'alumno')); // Separar: sin_subir = pendientes de entregar | pendiente/calificada = ya entregadas
$tareasPendientes = $todasTareas->filter(
fn($t) => ($entregasMap[$t->id]->status ?? 'sin_subir') === 'sin_subir'
);
$tareasEntregadas = $todasTareas->filter(
fn($t) => in_array($entregasMap[$t->id]->status ?? 'sin_subir', ['pendiente', 'calificada'])
);
return view('tarea.mis_tareas', compact(
'tareasPendientes', 'tareasEntregadas', 'entregasMap', 'alumno', 'cicloActual'
));
} }
public function store(Request $request, Tarea $tarea) public function store(Request $request, Tarea $tarea)
@@ -51,7 +51,13 @@ class EvaluacionController extends Controller
{ {
$evaluacion->delete(); $evaluacion->delete();
return redirect()->route('evaluacion.index')->with('info', 'Evaluación eliminada.'); session()->flash('swal',[
'icon'=>'success',
'title'=>'Eliminado!',
'text'=>'Se ha eliminado el registro correctamente'
]);
return redirect()->route('evaluacion.index');
} }
public function toggleStatus(Request $request, Evaluacion $evaluacion) public function toggleStatus(Request $request, Evaluacion $evaluacion)
@@ -2,9 +2,162 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Models\Alumno;
use App\Models\Docente;
use App\Models\Evaluacion;
use App\Models\EvaluacionSubmission;
use App\Models\Respuesta;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
class EvaluacionResponderController extends Controller class EvaluacionResponderController extends Controller
{ {
// public function misEvaluaciones()
{
$user = Auth::user();
$docente = Docente::whereHas('docentes', fn($q) => $q->where('id', $user->id))->first();
$alumno = Alumno::whereHas('alumnos', fn($q) => $q->where('id', $user->id))->first();
$evaluaciones = collect();
$respondidas = [];
if ($docente) {
$grupos = $docente->grupos()->get();
$plantelIds = $grupos->pluck('plantel')->unique()->filter()->values();
$nivelIds = $grupos->pluck('nivel')->unique()->filter()->values();
$evaluaciones = Evaluacion::whereHas('tipo', fn($q) => $q->where('slug', 'docente'))
->where('status', 'activa')
->where(fn($q) => $q
->whereHas('planteles', fn($p) => $p->whereIn('plantels.id', $plantelIds))
->orWhereDoesntHave('planteles')
)
->where(fn($q) => $q
->whereHas('niveles', fn($n) => $n->whereIn('nivels.id', $nivelIds))
->orWhereDoesntHave('niveles')
)
->where(fn($q) => $q->whereNull('fecha_inicio')->orWhereDate('fecha_inicio', '<=', now()))
->where(fn($q) => $q->whereNull('fecha_fin')->orWhereDate('fecha_fin', '>=', now()))
->withCount('preguntas')
->get();
} elseif ($alumno) {
$gruposIds = $alumno->grupos()->pluck('grupos.id');
$plantelIds = \App\Models\Grupo::whereIn('id', $gruposIds)->pluck('plantel')->unique()->filter()->values();
$nivelIds = \App\Models\Grupo::whereIn('id', $gruposIds)->pluck('nivel')->unique()->filter()->values();
$evaluaciones = Evaluacion::whereHas('tipo', fn($q) => $q->where('slug', 'alumno'))
->where('status', 'activa')
->where(fn($q) => $q
->whereHas('planteles', fn($p) => $p->whereIn('plantels.id', $plantelIds))
->orWhereDoesntHave('planteles')
)
->where(fn($q) => $q
->whereHas('niveles', fn($n) => $n->whereIn('nivels.id', $nivelIds))
->orWhereDoesntHave('niveles')
)
->where(fn($q) => $q->whereNull('fecha_inicio')->orWhereDate('fecha_inicio', '<=', now()))
->where(fn($q) => $q->whereNull('fecha_fin')->orWhereDate('fecha_fin', '>=', now()))
->withCount('preguntas')
->get();
}
if ($evaluaciones->isNotEmpty()) {
$respondidas = EvaluacionSubmission::where('user_id', $user->id)
->whereIn('evaluacion_id', $evaluaciones->pluck('id'))
->pluck('evaluacion_id')
->toArray();
}
return view('evaluacion.mis_evaluaciones', compact('evaluaciones', 'respondidas', 'docente', 'alumno'));
}
public function show(Evaluacion $evaluacion)
{
abort_if($evaluacion->status !== 'activa', 403, 'Esta evaluación no está activa.');
if ($evaluacion->fecha_inicio && \Carbon\Carbon::parse($evaluacion->fecha_inicio)->isFuture()) {
abort(403, 'Esta evaluación aún no ha comenzado.');
}
if ($evaluacion->fecha_fin && \Carbon\Carbon::parse($evaluacion->fecha_fin)->startOfDay()->isPast()) {
abort(403, 'Esta evaluación ya cerró.');
}
$yaRespondida = EvaluacionSubmission::where('evaluacion_id', $evaluacion->id)
->where('user_id', Auth::id())
->exists();
$preguntas = $evaluacion->preguntas()->with(['tipo', 'opciones'])->get();
$tipoSlug = $evaluacion->tipo->slug;
// Para tipo 'alumno': obtener docentes del alumno para preguntas es_docente
$docentesParaEvaluar = collect();
if ($tipoSlug === 'alumno' && $preguntas->where('es_docente', true)->count() > 0) {
$alumno = Alumno::whereHas('alumnos', fn($q) => $q->where('id', Auth::id()))->first();
if ($alumno) {
$gruposIds = $alumno->grupos()->pluck('grupos.id');
$docentesParaEvaluar = Docente::whereHas('grupos', fn($q) => $q->whereIn('grupos.id', $gruposIds))
->with('docentes')
->get()
->unique('id')
->values();
}
}
return view('evaluacion.responder', compact(
'evaluacion', 'preguntas', 'docentesParaEvaluar', 'tipoSlug', 'yaRespondida'
));
}
public function store(Request $request, Evaluacion $evaluacion)
{
abort_if($evaluacion->status !== 'activa', 403);
if (EvaluacionSubmission::where('evaluacion_id', $evaluacion->id)
->where('user_id', Auth::id())->exists()) {
return back()->with('error', 'Ya respondiste esta evaluación.');
}
DB::transaction(function () use ($request, $evaluacion) {
$submission = EvaluacionSubmission::create([
'evaluacion_id' => $evaluacion->id,
'user_id' => Auth::id(),
]);
foreach ($evaluacion->preguntas()->with('tipo')->get() as $pregunta) {
$tipoSlug = $pregunta->tipo->slug;
$esOpcionMultiple = $tipoSlug === 'opcion_multiple';
if ($pregunta->es_docente) {
$valores = $request->input("resp.{$pregunta->id}", []);
foreach ($valores as $docenteId => $valor) {
if (!filled($valor)) continue;
Respuesta::create([
'submission_id' => $submission->id,
'pregunta_id' => $pregunta->id,
'docente_id' => $docenteId,
'valor' => $esOpcionMultiple ? null : $valor,
'opcion_id' => $esOpcionMultiple ? $valor : null,
]);
}
} else {
$valor = $request->input("resp.{$pregunta->id}");
if (!filled($valor)) continue;
Respuesta::create([
'submission_id' => $submission->id,
'pregunta_id' => $pregunta->id,
'docente_id' => null,
'valor' => $esOpcionMultiple ? null : $valor,
'opcion_id' => $esOpcionMultiple ? $valor : null,
]);
}
}
});
return redirect()->route('evaluacion.mis_evaluaciones')
->with('info', '¡Evaluación enviada correctamente! Gracias por tu participación.');
}
} }
+2 -6
View File
@@ -188,12 +188,8 @@ class GrupoController extends Controller
public function update(Request $request,Grupo $grupo) public function update(Request $request,Grupo $grupo)
{ {
$nivel = Plan::where('id',$request->plan_id)->first(); $grupo->update([
$grupo = Grupo::update([ 'ciclo' => $request->ciclo,
'plan_id' => $request->plan_id,
'ciclo' => 1,
'nivel' => $nivel->nivel,
'plantel' => $request->plantel,
'turno' => $request->turno, 'turno' => $request->turno,
'status'=>1 'status'=>1
]); ]);
+18
View File
@@ -127,6 +127,24 @@ class TareaController extends Controller
->with('info', 'Tarea eliminada.'); ->with('info', 'Tarea eliminada.');
} }
public function acta(Grupo $grupo, Material $material)
{
$tareas = Tarea::where('grupo_id', $grupo->id)
->where('material_id', $material->id)
->orderBy('created_at')
->get();
$alumnos = $grupo->alumnos()->with('alumnos')->get();
// [alumno_id][tarea_id] => Entrega
$entregas = Entrega::whereIn('tarea_id', $tareas->pluck('id'))
->get()
->groupBy('alumno_id')
->map(fn($rows) => $rows->keyBy('tarea_id'));
return view('tarea.acta', compact('grupo', 'material', 'tareas', 'alumnos', 'entregas'));
}
public function calificar(Request $request, Tarea $tarea, Alumno $alumno) public function calificar(Request $request, Tarea $tarea, Alumno $alumno)
{ {
$request->validate([ $request->validate([
+68
View File
@@ -2,11 +2,14 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Models\Alumno;
use App\Models\AreaCode; use App\Models\AreaCode;
use App\Models\Docente;
use App\Models\Plantel; use App\Models\Plantel;
use App\Models\User; use App\Models\User;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Routing\Controller; use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Password; use Illuminate\Support\Facades\Password;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str; use Illuminate\Support\Str;
@@ -58,6 +61,8 @@ class UserController extends Controller
$user->plantelUsuarios()->sync($request->plantels); $user->plantelUsuarios()->sync($request->plantels);
$this->syncAlumnoDocente($user, $request->roles ?? []);
$token = Password::createToken($user); $token = Password::createToken($user);
$user->notify(new ResetPassword($token)); $user->notify(new ResetPassword($token));
@@ -82,12 +87,14 @@ class UserController extends Controller
$user->update($request->except(['plantels'])); $user->update($request->except(['plantels']));
$user->roles()->sync($request->roles); $user->roles()->sync($request->roles);
$user->plantelUsuarios()->sync($request->plantels); $user->plantelUsuarios()->sync($request->plantels);
$this->syncAlumnoDocente($user, $request->roles ?? []);
return redirect()->route('user.edit',compact('user'))->with('info','Se modifico el usuario correctamente'); return redirect()->route('user.edit',compact('user'))->with('info','Se modifico el usuario correctamente');
} }
public function destroy(User $user) public function destroy(User $user)
{ {
$this->detachAlumnoDocente($user);
$user->plantelUsuarios()->detach(); $user->plantelUsuarios()->detach();
$user->roles()->detach(); $user->roles()->detach();
$user->delete(); $user->delete();
@@ -102,4 +109,65 @@ class UserController extends Controller
public function profile(){ public function profile(){
return view('profile.show'); return view('profile.show');
} }
private function syncAlumnoDocente(User $user, array $roleIds): void
{
$roleNames = Role::whereIn('id', $roleIds)->pluck('name')->toArray();
// ── Alumno ──────────────────────────────────────────────────
if (in_array('Alumno', $roleNames)) {
$alumno = Alumno::whereHas('alumnos', fn($q) => $q->where('id', $user->id))->first();
if (!$alumno) {
$alumno = Alumno::create([
'nivel' => 0,
'plan_id' => 0,
'ciclo' => 0,
'turno' => 0,
'medio' => 0,
'usuario_registro' => Auth::id(),
]);
}
$alumno->alumnos()->syncWithoutDetaching([$user->id]);
} else {
$this->detachFromAlumno($user);
}
// ── Docente ─────────────────────────────────────────────────
if (in_array('Docente', $roleNames)) {
$docente = Docente::whereHas('docentes', fn($q) => $q->where('id', $user->id))->first();
if (!$docente) {
$docente = Docente::create([
'usuario_registro' => Auth::id(),
'status' => 1,
]);
}
$docente->docentes()->syncWithoutDetaching([$user->id]);
} else {
$this->detachFromDocente($user);
}
}
private function detachAlumnoDocente(User $user): void
{
$this->detachFromAlumno($user);
$this->detachFromDocente($user);
}
private function detachFromAlumno(User $user): void
{
Alumno::whereHas('alumnos', fn($q) => $q->where('id', $user->id))
->get()
->each(fn($a) => $a->alumnos()->detach($user->id));
}
private function detachFromDocente(User $user): void
{
Docente::whereHas('docentes', fn($q) => $q->where('id', $user->id))
->get()
->each(fn($d) => $d->docentes()->detach($user->id));
}
} }
+4
View File
@@ -58,6 +58,7 @@ class EvaluacionBuilder extends Component
'texto' => $p->texto, 'texto' => $p->texto,
'pregunta_tipo_id' => (string) $p->pregunta_tipo_id, 'pregunta_tipo_id' => (string) $p->pregunta_tipo_id,
'requerida' => (bool) $p->requerida, 'requerida' => (bool) $p->requerida,
'es_docente' => (bool) $p->es_docente,
'opciones' => $p->opciones->map(fn($o) => [ 'opciones' => $p->opciones->map(fn($o) => [
'id' => $o->id, 'id' => $o->id,
'texto' => $o->texto, 'texto' => $o->texto,
@@ -74,6 +75,7 @@ class EvaluacionBuilder extends Component
'texto' => '', 'texto' => '',
'pregunta_tipo_id' => (string) $defaultTipo, 'pregunta_tipo_id' => (string) $defaultTipo,
'requerida' => true, 'requerida' => true,
'es_docente' => false,
'opciones' => [], 'opciones' => [],
]; ];
} }
@@ -164,6 +166,7 @@ class EvaluacionBuilder extends Component
'texto' => $pData['texto'], 'texto' => $pData['texto'],
'pregunta_tipo_id' => $pData['pregunta_tipo_id'], 'pregunta_tipo_id' => $pData['pregunta_tipo_id'],
'requerida' => $pData['requerida'] ?? true, 'requerida' => $pData['requerida'] ?? true,
'es_docente' => $pData['es_docente'] ?? false,
'orden' => $orden, 'orden' => $orden,
]); ]);
} else { } else {
@@ -171,6 +174,7 @@ class EvaluacionBuilder extends Component
'texto' => $pData['texto'], 'texto' => $pData['texto'],
'pregunta_tipo_id' => $pData['pregunta_tipo_id'], 'pregunta_tipo_id' => $pData['pregunta_tipo_id'],
'requerida' => $pData['requerida'] ?? true, 'requerida' => $pData['requerida'] ?? true,
'es_docente' => $pData['es_docente'] ?? false,
'orden' => $orden, 'orden' => $orden,
]); ]);
} }
@@ -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::table('preguntas', function (Blueprint $table) {
$table->boolean('es_docente')->default(false)->after('requerida');
});
}
public function down(): void
{
Schema::table('preguntas', function (Blueprint $table) {
$table->dropColumn('es_docente');
});
}
};
@@ -0,0 +1,24 @@
<?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::table('respuestas', function (Blueprint $table) {
$table->unsignedBigInteger('docente_id')->nullable()->after('opcion_id');
$table->foreign('docente_id')->references('id')->on('docentes')->nullOnDelete();
});
}
public function down(): void
{
Schema::table('respuestas', function (Blueprint $table) {
$table->dropForeign(['docente_id']);
$table->dropColumn('docente_id');
});
}
};
@@ -0,0 +1,93 @@
{{--
Variables esperadas:
$pregunta - Pregunta model
$tipoSlug - string (slug del tipo)
$fieldName - string (name del input, ej: "resp[1]" o "resp[1][5]")
$fieldId - string (id único del input)
--}}
@if($tipoSlug === 'texto_libre')
<textarea name="{{ $fieldName }}"
id="{{ $fieldId }}"
class="form-control"
rows="4"
placeholder="Escribe tu respuesta aquí…"
{{ $pregunta->requerida ? 'required' : '' }}></textarea>
@elseif($tipoSlug === 'opcion_multiple')
<div>
@foreach($pregunta->opciones as $opcion)
<div class="form-check mb-2">
<input type="radio"
name="{{ $fieldName }}"
id="{{ $fieldId }}_{{ $opcion->id }}"
value="{{ $opcion->id }}"
class="form-check-input"
{{ $pregunta->requerida ? 'required' : '' }}>
<label class="form-check-label" for="{{ $fieldId }}_{{ $opcion->id }}">
{{ $opcion->texto }}
</label>
</div>
@endforeach
</div>
@elseif($tipoSlug === 'si_no')
<div class="d-flex">
<div class="form-check mr-4">
<input type="radio"
name="{{ $fieldName }}"
id="{{ $fieldId }}_si"
value="si"
class="form-check-input"
{{ $pregunta->requerida ? 'required' : '' }}>
<label class="form-check-label font-weight-bold text-success" for="{{ $fieldId }}_si">
</label>
</div>
<div class="form-check">
<input type="radio"
name="{{ $fieldName }}"
id="{{ $fieldId }}_no"
value="no"
class="form-check-input"
{{ $pregunta->requerida ? 'required' : '' }}>
<label class="form-check-label font-weight-bold text-danger" for="{{ $fieldId }}_no">
No
</label>
</div>
</div>
@elseif($tipoSlug === 'escala_5')
<div class="d-flex flex-wrap" style="gap:.5rem;">
@for($k = 1; $k <= 5; $k++)
<div class="form-check form-check-inline mr-0">
<input type="radio"
name="{{ $fieldName }}"
id="{{ $fieldId }}_{{ $k }}"
value="{{ $k }}"
class="form-check-input d-none escala-input"
{{ $pregunta->requerida ? 'required' : '' }}>
<label class="btn btn-outline-primary px-3 py-2"
for="{{ $fieldId }}_{{ $k }}">{{ $k }}</label>
</div>
@endfor
<small class="text-muted align-self-center ml-2">1 = Muy malo &nbsp; 5 = Excelente</small>
</div>
@elseif($tipoSlug === 'escala_10')
<div class="d-flex flex-wrap" style="gap:.5rem;">
@for($k = 1; $k <= 10; $k++)
<div class="form-check form-check-inline mr-0">
<input type="radio"
name="{{ $fieldName }}"
id="{{ $fieldId }}_{{ $k }}"
value="{{ $k }}"
class="form-check-input d-none escala-input"
{{ $pregunta->requerida ? 'required' : '' }}>
<label class="btn btn-outline-primary px-3 py-2"
for="{{ $fieldId }}_{{ $k }}">{{ $k }}</label>
</div>
@endfor
<small class="text-muted align-self-center ml-2">1 = Muy malo &nbsp; 10 = Excelente</small>
</div>
@endif
+15 -18
View File
@@ -16,7 +16,7 @@
<div class="card-header py-3"> <div class="card-header py-3">
<div class="d-sm-flex align-items-center justify-content-between"> <div class="d-sm-flex align-items-center justify-content-between">
<h6 class="m-0 font-weight-bold text-primary"> <h6 class="m-0 font-weight-bold text-primary">
<i class="fas fa-clipboard-list mr-1"></i> Evaluaciones <i class="fa-duotone fa-regular fa-clipboard-list"></i> Evaluaciones
</h6> </h6>
<a href="{{ route('evaluacion.create') }}" class="btn btn-sm btn-primary shadow-sm"> <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 <i class="fas fa-plus fa-sm text-white-50"></i> Nueva evaluación
@@ -25,8 +25,7 @@
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="table-responsive"> <table id="tcont" class="table table-striped table-bordered nowrap table-hover" style="width:100%;">
<table class="table table-bordered table-hover">
<thead class="thead-light"> <thead class="thead-light">
<tr> <tr>
<th>Título</th> <th>Título</th>
@@ -52,11 +51,11 @@
<td> <td>
@php $slug = $evaluacion->tipo?->slug; @endphp @php $slug = $evaluacion->tipo?->slug; @endphp
@if($slug === 'docente') @if($slug === 'docente')
<span class="badge badge-info"><i class="fas fa-chalkboard-teacher mr-1"></i>Docentes</span> <span class="badge badge-info"><i class="fa-duotone fa-solid fa-chalkboard-user"></i> Docentes</span>
@elseif($slug === 'alumno') @elseif($slug === 'alumno')
<span class="badge badge-success"><i class="fas fa-user-graduate mr-1"></i>Alumnos</span> <span class="badge badge-success"><i class="fa-duotone fa-solid fa-user-graduate"></i> Alumnos</span>
@elseif($slug === 'administrativo') @elseif($slug === 'administrativo')
<span class="badge badge-warning text-dark"><i class="fas fa-briefcase mr-1"></i>Administrativos</span> <span class="badge badge-warning text-dark"><i class="fa-duotone fa-regular fa-briefcase"></i> Administrativos</span>
@else @else
<span class="badge badge-light"></span> <span class="badge badge-light"></span>
@endif @endif
@@ -86,34 +85,32 @@
<td class="text-center text-nowrap"> <td class="text-center text-nowrap">
<a href="{{ route('evaluacion.show', $evaluacion) }}" <a href="{{ route('evaluacion.show', $evaluacion) }}"
class="btn btn-sm btn-info" title="Ver detalle"> class="btn btn-sm btn-info" title="Ver detalle">
<i class="fas fa-eye"></i> <i class="fa-duotone fa-solid fa-eye"></i>
</a> </a>
<a href="{{ route('evaluacion.edit', $evaluacion) }}" <a href="{{ route('evaluacion.edit', $evaluacion) }}"
class="btn btn-sm btn-primary" title="Editar"> class="btn btn-sm btn-primary" title="Editar">
<i class="fas fa-edit"></i> <i class="fa-duotone fa-solid fa-pen-to-square"></i>
</a> </a>
<form action="{{ route('evaluacion.destroy', $evaluacion) }}" <form action="{{ route('evaluacion.destroy', $evaluacion) }}"
method="POST" class="d-inline" method="POST" class="d-inline delete-form">
onsubmit="return confirm('¿Eliminar esta evaluación y todas sus preguntas?')">
@csrf @method('DELETE') @csrf @method('DELETE')
<button class="btn btn-sm btn-danger" title="Eliminar"> <button class="btn btn-sm btn-danger" title="Eliminar">
<i class="fas fa-trash"></i> <i class="fa-duotone fa-solid fa-trash-can"></i>
</button> </button>
</form> </form>
</td> </td>
</tr> </tr>
@empty @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 @endforelse
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>
</div> </div>
</div> @endsection
@section('scripts')
@include('layouts._partials.tablaScript')
@include('layouts._partials.delete')
@endsection @endsection
@@ -0,0 +1,98 @@
@extends('layouts.landing')
@section('title', 'Mis Evaluaciones')
@section('content')
<div class="container-fluid">
@if(session('info'))
<div class="alert alert-success alert-dismissible fade show">
<i class="fas fa-check-circle mr-1"></i> {{ session('info') }}
<button type="button" class="close" data-dismiss="alert"><span>&times;</span></button>
</div>
@endif
<div class="card shadow mb-4">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">
<i class="fas fa-clipboard-check mr-1"></i> Mis Evaluaciones
</h6>
</div>
<div class="card-body">
@if(!$docente && !$alumno)
<div class="alert alert-warning">
Tu cuenta no está vinculada a un docente o alumno. Contacta al administrador.
</div>
@elseif($evaluaciones->isEmpty())
<div class="alert alert-info">
<i class="fas fa-info-circle mr-1"></i>
No tienes evaluaciones activas en este momento.
</div>
@else
<div class="row">
@foreach($evaluaciones as $evaluacion)
@php $yaRespondida = in_array($evaluacion->id, $respondidas); @endphp
<div class="col-md-6 col-lg-4 mb-4">
<div class="card h-100 shadow-sm
{{ $yaRespondida ? 'border-success' : 'border-primary' }}">
<div class="card-header py-2 d-flex align-items-center justify-content-between
{{ $yaRespondida ? 'bg-success text-white' : 'bg-primary text-white' }}">
<small class="font-weight-bold">
@if($yaRespondida)
<i class="fas fa-check-circle mr-1"></i> Respondida
@else
<i class="fas fa-clipboard-list mr-1"></i> Pendiente
@endif
</small>
<span class="badge badge-light text-dark">
{{ $evaluacion->preguntas_count }} preguntas
</span>
</div>
<div class="card-body d-flex flex-column">
<h6 class="font-weight-bold mb-1">{{ $evaluacion->titulo }}</h6>
@if($evaluacion->descripcion)
<p class="text-muted small mb-2">
{{ Str::limit($evaluacion->descripcion, 120) }}
</p>
@endif
<div class="mt-auto">
@if($evaluacion->fecha_inicio || $evaluacion->fecha_fin)
<small class="text-muted d-block mb-2">
<i class="fas fa-calendar-alt mr-1"></i>
{{ $evaluacion->fecha_inicio
? \Carbon\Carbon::parse($evaluacion->fecha_inicio)->format('d/m/Y')
: 'Sin inicio' }}
{{ $evaluacion->fecha_fin
? \Carbon\Carbon::parse($evaluacion->fecha_fin)->format('d/m/Y')
: 'Sin límite' }}
</small>
@endif
@if($yaRespondida)
<button class="btn btn-success btn-sm btn-block" disabled>
<i class="fas fa-check mr-1"></i> Ya respondida
</button>
@else
<a href="{{ route('evaluacion.responder.show', $evaluacion) }}"
class="btn btn-primary btn-sm btn-block">
<i class="fas fa-pencil-alt mr-1"></i> Responder evaluación
</a>
@endif
</div>
</div>
</div>
</div>
@endforeach
</div>
@endif
</div>
</div>
</div>
@endsection
@@ -0,0 +1,151 @@
@extends('layouts.landing')
@section('title', $evaluacion->titulo)
@section('content')
<div class="container-fluid">
<div class="d-sm-flex align-items-center justify-content-between mb-3">
<div>
<h5 class="m-0 font-weight-bold text-primary">
<i class="fas fa-clipboard-list mr-1"></i> {{ $evaluacion->titulo }}
</h5>
@if($evaluacion->descripcion)
<small class="text-muted">{{ $evaluacion->descripcion }}</small>
@endif
</div>
<a href="{{ route('evaluacion.mis_evaluaciones') }}" class="btn btn-sm btn-secondary mt-2 mt-sm-0">
<i class="fas fa-arrow-left mr-1"></i> Regresar
</a>
</div>
@if(session('error'))
<div class="alert alert-danger">{{ session('error') }}</div>
@endif
@if($yaRespondida)
{{-- Ya respondida --}}
<div class="card shadow">
<div class="card-body text-center py-5">
<i class="fas fa-check-circle fa-4x text-success mb-3 d-block"></i>
<h5 class="font-weight-bold">Ya respondiste esta evaluación</h5>
<p class="text-muted">Tu participación ha sido registrada. ¡Gracias!</p>
<a href="{{ route('evaluacion.mis_evaluaciones') }}" class="btn btn-primary">
<i class="fas fa-arrow-left mr-1"></i> Volver a mis evaluaciones
</a>
</div>
</div>
@else
{{-- Formulario --}}
<form action="{{ route('evaluacion.responder.store', $evaluacion) }}" method="POST"
id="form-evaluacion">
@csrf
@php
$numPregunta = 1;
$totalPreguntas = $preguntas->count();
// For docente questions: multiply by number of teachers
if($docentesParaEvaluar->isNotEmpty()) {
$esDocenteCount = $preguntas->where('es_docente', true)->count();
$normalCount = $totalPreguntas - $esDocenteCount;
$totalPreguntas = $normalCount + ($esDocenteCount * $docentesParaEvaluar->count());
}
@endphp
{{-- Info de la evaluación --}}
<div class="alert alert-info d-flex align-items-center mb-4">
<i class="fas fa-info-circle fa-lg mr-3"></i>
<div>
<strong>{{ $totalPreguntas }} pregunta(s)</strong> en esta evaluación.
@if($evaluacion->anonima)
&nbsp;<span class="badge badge-secondary">
<i class="fas fa-user-secret mr-1"></i>Anónima
</span>
@endif
@if($evaluacion->fecha_fin)
&nbsp; Cierra el
<strong>{{ \Carbon\Carbon::parse($evaluacion->fecha_fin)->format('d/m/Y') }}</strong>.
@endif
</div>
</div>
@foreach($preguntas as $pregunta)
@php $tipoSlug = $pregunta->tipo?->slug; @endphp
@if($pregunta->es_docente && $docentesParaEvaluar->isNotEmpty())
{{-- ═══ PREGUNTA DE DOCENTE: una vez por cada docente ═══ --}}
@foreach($docentesParaEvaluar as $docente)
@php
$user = $docente->docentes->first();
$fotoUrl = $user?->profile_photo_url;
$nombreDocente = $user?->name ?? 'Docente';
@endphp
<div class="card shadow-sm mb-4 border-left-info">
<div class="card-header py-2 bg-light d-flex align-items-center">
{{-- Foto del docente --}}
<img src="{{ $fotoUrl }}"
alt="{{ $nombreDocente }}"
class="rounded-circle mr-3"
style="width:48px; height:48px; object-fit:cover; border:2px solid #36b9cc;">
<div>
<div class="font-weight-bold">{{ $nombreDocente }}</div>
<small class="text-muted">
<i class="fas fa-chalkboard-teacher mr-1 text-info"></i>
Pregunta {{ $numPregunta++ }}
@if($pregunta->requerida)
<span class="text-danger">*</span>
@endif
</small>
</div>
</div>
<div class="card-body">
<p class="font-weight-bold mb-3">{{ $pregunta->texto }}</p>
@include('evaluacion._input_pregunta', [
'pregunta' => $pregunta,
'tipoSlug' => $tipoSlug,
'fieldName' => "resp[{$pregunta->id}][{$docente->id}]",
'fieldId' => "resp_{$pregunta->id}_{$docente->id}",
])
</div>
</div>
@endforeach
@else
{{-- ═══ PREGUNTA NORMAL ═══ --}}
<div class="card shadow-sm mb-4 border-left-primary">
<div class="card-body">
<small class="text-muted font-weight-bold d-block mb-1">
Pregunta {{ $numPregunta++ }}
@if($pregunta->requerida)
<span class="text-danger">*</span>
@endif
</small>
<p class="font-weight-bold mb-3">{{ $pregunta->texto }}</p>
@include('evaluacion._input_pregunta', [
'pregunta' => $pregunta,
'tipoSlug' => $tipoSlug,
'fieldName' => "resp[{$pregunta->id}]",
'fieldId' => "resp_{$pregunta->id}",
])
</div>
</div>
@endif
@endforeach
<div class="card shadow-sm mb-4">
<div class="card-body text-center">
<button type="submit" class="btn btn-success btn-lg px-5"
onclick="return confirm('¿Estás seguro de enviar la evaluación? No podrás cambiar tus respuestas.')">
<i class="fas fa-paper-plane mr-2"></i> Enviar evaluación
</button>
</div>
</div>
</form>
@endif
</div>
@endsection
+8 -1
View File
@@ -150,12 +150,19 @@
<div class="card-body py-2 px-3"> <div class="card-body py-2 px-3">
<div class="d-flex justify-content-between align-items-start"> <div class="d-flex justify-content-between align-items-start">
<div class="flex-grow-1"> <div class="flex-grow-1">
<small class="text-muted font-weight-bold d-block mb-1"> <div class="d-flex align-items-center mb-1 flex-wrap" style="gap:.3rem;">
<small class="text-muted font-weight-bold">
Pregunta {{ $i + 1 }} Pregunta {{ $i + 1 }}
@if($pregunta->requerida) @if($pregunta->requerida)
<span class="text-danger">*</span> <span class="text-danger">*</span>
@endif @endif
</small> </small>
@if($pregunta->es_docente)
<span class="badge badge-info">
<i class="fas fa-chalkboard-teacher mr-1"></i>Por docente
</span>
@endif
</div>
<p class="mb-1 font-weight-bold">{{ $pregunta->texto }}</p> <p class="mb-1 font-weight-bold">{{ $pregunta->texto }}</p>
<small class="text-muted">{{ $pregunta->tipo?->nombre }}</small> <small class="text-muted">{{ $pregunta->tipo?->nombre }}</small>
</div> </div>
@@ -202,6 +202,12 @@
</a> </a>
</li> </li>
<li class="nav-item">
<a class="nav-link" href="{{ route('evaluacion.mis_evaluaciones') }}">
<i class="fa-duotone fa-regular fa-clipboard-list" style="--fa-primary-color: rgb(255, 255, 255); --fa-secondary-color: rgb(255, 255, 255);"></i>
<span>Encuestas</span></a>
</li>
@endrole @endrole
@hasrole(['Docente']) @hasrole(['Docente'])
@@ -211,6 +217,12 @@
<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> <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> <span>Mis grupos</span></a>
</li> </li>
<li class="nav-item">
<a class="nav-link" href="{{ route('evaluacion.mis_evaluaciones') }}">
<i class="fa-duotone fa-regular fa-clipboard-list " style="--fa-primary-color: rgb(255, 255, 255); --fa-secondary-color: rgb(255, 255, 255);"></i>
<span>Encuestas</span></a>
</li>
@endrole @endrole
@@ -94,6 +94,10 @@
</div> </div>
<div class="card-body py-3"> <div class="card-body py-3">
@php
$tipoAlumnoId = (string)(collect($tipos)->firstWhere('slug', 'alumno')['id'] ?? 0);
$esEvalAlumno = $evaluacionTipoId === $tipoAlumnoId;
@endphp
<div class="row"> <div class="row">
{{-- Texto de la pregunta --}} {{-- Texto de la pregunta --}}
<div class="col-md-7"> <div class="col-md-7">
@@ -110,7 +114,7 @@
</div> </div>
{{-- Tipo de respuesta --}} {{-- Tipo de respuesta --}}
<div class="col-md-4"> <div class="col-md-{{ $esEvalAlumno ? '3' : '4' }}">
<div class="form-group mb-2"> <div class="form-group mb-2">
<label class="small font-weight-bold">Tipo de respuesta</label> <label class="small font-weight-bold">Tipo de respuesta</label>
<select wire:model.live="preguntas.{{ $i }}.pregunta_tipo_id" <select wire:model.live="preguntas.{{ $i }}.pregunta_tipo_id"
@@ -135,7 +139,31 @@
<label class="form-check-label small" for="req-{{ $i }}">*</label> <label class="form-check-label small" for="req-{{ $i }}">*</label>
</div> </div>
</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>
</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 --}} {{-- Vista previa y opciones según tipo --}}
@php @php
@@ -143,7 +143,7 @@
</tr> </tr>
@empty @empty
<tr><td colspan="5">Sin materias en este plan.</td></tr> <tr>Sin materias en este plan.</tr>
@endforelse @endforelse
</tbody> </tbody>
</table> </table>
+213
View File
@@ -0,0 +1,213 @@
@extends('layouts.landing')
@section('title', 'Acta de Calificaciones')
@section('head')
<style>
@media print {
.no-print { display: none !important; }
body { font-size: 12px; }
.card { border: none !important; box-shadow: none !important; }
.card-header { background: none !important; border-bottom: 2px solid #333 !important; }
table { font-size: 11px; }
}
.calificacion-cell { min-width: 70px; text-align: center; }
.promedio-cell { min-width: 80px; text-align: center; font-weight: bold; }
th.rotate { height: 130px; white-space: nowrap; vertical-align: bottom; }
th.rotate > div { transform: rotate(-60deg); transform-origin: bottom left; width: 30px; padding-bottom: 5px; }
</style>
@endsection
@section('content')
<div class="container-fluid">
<div class="card shadow mb-4">
<div class="card-header py-3">
<div class="d-sm-flex align-items-center justify-content-between">
<div>
<h6 class="m-0 font-weight-bold text-primary">
Acta de Calificaciones
</h6>
<small class="text-muted">
{{ $material->name }} &middot; Grupo: {{ $grupo->clave }}
</small>
</div>
<div class="no-print">
<a href="{{ route('grupo.material.tareas', [$grupo, $material]) }}"
class="btn btn-sm btn-secondary shadow-sm mr-2">
<i class="fas fa-arrow-left fa-sm"></i> Regresar
</a>
<button onclick="window.print()" class="btn btn-sm btn-primary shadow-sm">
<i class="fas fa-print fa-sm"></i> Imprimir
</button>
</div>
</div>
</div>
<div class="card-body">
{{-- Resumen --}}
<div class="row mb-4">
<div class="col-md-3 col-sm-6 mb-3">
<div class="card border-left-primary shadow-sm h-100 py-2">
<div class="card-body">
<div class="text-xs font-weight-bold text-primary text-uppercase mb-1">
Total de Tareas
</div>
<div class="h5 mb-0 font-weight-bold text-gray-800">{{ $tareas->count() }}</div>
</div>
</div>
</div>
<div class="col-md-3 col-sm-6 mb-3">
<div class="card border-left-success shadow-sm h-100 py-2">
<div class="card-body">
<div class="text-xs font-weight-bold text-success text-uppercase mb-1">
Total de Alumnos
</div>
<div class="h5 mb-0 font-weight-bold text-gray-800">{{ $alumnos->count() }}</div>
</div>
</div>
</div>
<div class="col-md-3 col-sm-6 mb-3">
<div class="card border-left-info shadow-sm h-100 py-2">
<div class="card-body">
<div class="text-xs font-weight-bold text-info text-uppercase mb-1">
Materia
</div>
<div class="h5 mb-0 font-weight-bold text-gray-800">{{ $material->name }}</div>
</div>
</div>
</div>
<div class="col-md-3 col-sm-6 mb-3">
<div class="card border-left-warning shadow-sm h-100 py-2">
<div class="card-body">
<div class="text-xs font-weight-bold text-warning text-uppercase mb-1">
Generado
</div>
<div class="h5 mb-0 font-weight-bold text-gray-800">
{{ \Carbon\Carbon::now()->format('d/m/Y') }}
</div>
</div>
</div>
</div>
</div>
@if ($tareas->isEmpty())
<div class="alert alert-info">No hay tareas registradas para esta materia.</div>
@elseif ($alumnos->isEmpty())
<div class="alert alert-info">No hay alumnos inscritos en este grupo.</div>
@else
<div class="table-responsive">
<table class="table table-bordered table-sm" style="width:100%">
<thead class="thead-dark">
<tr>
<th class="align-middle" style="min-width:40px">#</th>
<th class="align-middle" style="min-width:180px">Alumno</th>
@foreach ($tareas as $tarea)
<th class="rotate calificacion-cell">
<div title="{{ $tarea->titulo }}">
{{ Str::limit($tarea->titulo, 25) }}
</div>
</th>
@endforeach
<th class="align-middle promedio-cell text-center"
style="background:#1a3a5c; color:#fff;">
Promedio Final
</th>
</tr>
</thead>
<tbody>
@php $fila = 1; @endphp
@foreach ($alumnos as $alumno)
@php
$user = $alumno->alumnos->first();
$nombre = $user?->name ?? 'Sin nombre';
$calificaciones = [];
$sumaCal = 0;
$countCal = 0;
foreach ($tareas as $tarea) {
$entrega = $entregas[$alumno->id][$tarea->id] ?? null;
$cal = ($entrega && $entrega->status === 'calificada')
? $entrega->calificacion
: null;
$calificaciones[$tarea->id] = [
'valor' => $cal,
'status' => $entrega?->status ?? 'sin_subir',
];
if ($cal !== null) {
$sumaCal += $cal;
$countCal++;
}
}
$promedio = $countCal > 0 ? round($sumaCal / $countCal, 1) : null;
@endphp
<tr>
<td class="text-center align-middle">{{ $fila++ }}</td>
<td class="align-middle">{{ $nombre }}</td>
@foreach ($tareas as $tarea)
@php $data = $calificaciones[$tarea->id]; @endphp
<td class="calificacion-cell align-middle">
@if ($data['valor'] !== null)
<span class="{{ $data['valor'] >= 6 ? 'text-success' : 'text-danger' }} font-weight-bold">
{{ number_format($data['valor'], 1) }}
</span>
@elseif ($data['status'] === 'pendiente')
<span class="text-warning" title="Pendiente de calificar">
<i class="fas fa-clock"></i>
</span>
@elseif ($data['status'] === 'sin_subir')
<span class="text-muted" title="Sin entregar">&mdash;</span>
@endif
</td>
@endforeach
<td class="promedio-cell align-middle" style="background:#f8f9fc;">
@if ($promedio !== null)
<span class="badge badge-pill {{ $promedio >= 6 ? 'badge-success' : 'badge-danger' }}"
style="font-size:.95rem; padding:.4em .7em;">
{{ number_format($promedio, 1) }}
</span>
@else
<span class="text-muted">S/C</span>
@endif
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
<div class="mt-3 no-print">
<small class="text-muted">
<i class="fas fa-info-circle"></i>
El promedio final se calcula únicamente sobre las tareas ya calificadas.
<strong>&mdash;</strong> = sin entregar &nbsp;|&nbsp;
<i class="fas fa-clock text-warning"></i> = entregada, pendiente de calificación &nbsp;|&nbsp;
<strong>S/C</strong> = sin calificaciones registradas.
</small>
</div>
{{-- Firma para impresión --}}
<div class="row mt-5 d-none d-print-flex">
<div class="col-4 text-center">
<div style="border-top:1px solid #333; padding-top:8px; margin-top:50px;">
Firma del Docente
</div>
</div>
<div class="col-4 text-center">
<div style="border-top:1px solid #333; padding-top:8px; margin-top:50px;">
Firma del Director
</div>
</div>
<div class="col-4 text-center">
<div style="border-top:1px solid #333; padding-top:8px; margin-top:50px;">
Sello de la Institución
</div>
</div>
</div>
@endif
</div>
</div>
</div>
@endsection
+4
View File
@@ -24,6 +24,10 @@
<a href="{{ route('grupo.administrar', $grupo) }}" class="btn btn-sm btn-secondary shadow-sm mr-2"> <a href="{{ route('grupo.administrar', $grupo) }}" class="btn btn-sm btn-secondary shadow-sm mr-2">
<i class="fas fa-arrow-left fa-sm"></i> Regresar <i class="fas fa-arrow-left fa-sm"></i> Regresar
</a> </a>
<a href="{{ route('grupo.material.tareas.acta', [$grupo, $material]) }}"
class="btn btn-sm btn-success shadow-sm mr-2">
<i class="fas fa-file-alt fa-sm text-white-50"></i> Generar Acta
</a>
<a href="{{ route('grupo.material.tareas.create', [$grupo, $material]) }}" <a href="{{ route('grupo.material.tareas.create', [$grupo, $material]) }}"
class="btn btn-sm btn-primary shadow-sm"> class="btn btn-sm btn-primary shadow-sm">
<i class="fas fa-plus fa-sm text-white-50"></i> Nueva Tarea <i class="fas fa-plus fa-sm text-white-50"></i> Nueva Tarea
+250 -71
View File
@@ -1,147 +1,315 @@
@extends('layouts.landing') @extends('layouts.landing')
@section('head')
@include('layouts._partials.tablaStyle')
@endsection
@section('title', 'Mis Tareas') @section('title', 'Mis Tareas')
@section('content') @section('content')
<div class="container-fluid"> <div class="container-fluid">
<div class="card shadow mb-4">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">Mis Tareas</h6>
</div>
<div class="card-body">
{{-- Alertas de sesión --}}
@if (session('info')) @if (session('info'))
<div class="alert alert-success"><strong>{{ session('info') }}</strong></div> <div class="alert alert-success alert-dismissible fade show">
<strong>{{ session('info') }}</strong>
<button type="button" class="close" data-dismiss="alert"><span>&times;</span></button>
</div>
@endif @endif
@if (session('error')) @if (session('error'))
<div class="alert alert-danger"><strong>{{ session('error') }}</strong></div> <div class="alert alert-danger alert-dismissible fade show">
<strong>{{ session('error') }}</strong>
<button type="button" class="close" data-dismiss="alert"><span>&times;</span></button>
</div>
@endif @endif
@if ($errors->has('entrega')) @if ($errors->has('entrega'))
<div class="alert alert-warning"><strong>{{ $errors->first('entrega') }}</strong></div> <div class="alert alert-warning alert-dismissible fade show">
<strong>{{ $errors->first('entrega') }}</strong>
<button type="button" class="close" data-dismiss="alert"><span>&times;</span></button>
</div>
@endif @endif
@if (!isset($tareas) || $tareas->isEmpty()) {{-- Sin ciclo activo --}}
<div class="alert alert-info">No tienes tareas asignadas en tus grupos.</div> @if (!$cicloActual)
<div class="alert alert-info">
<i class="fas fa-info-circle"></i>
No hay un ciclo escolar activo en este momento. Contacta a tu coordinador.
</div>
@else @else
{{-- ══════════════════════════════════════════════════
SECCIÓN 1: TAREAS POR ENTREGAR (ciclo actual)
══════════════════════════════════════════════════ --}}
<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-tasks mr-1"></i> Tareas por Entregar
</h6>
<span class="badge badge-primary badge-pill">{{ $cicloActual->name }}</span>
</div>
<div class="card-body">
@if ($tareasPendientes->isEmpty())
<div class="alert alert-success mb-0">
<i class="fas fa-check-circle"></i> ¡Estás al corriente! No tienes tareas pendientes de entrega.
</div>
@else
@php $pendientesAgrupadas = $tareasPendientes->groupBy('material_id'); @endphp
@foreach ($pendientesAgrupadas as $materialId => $grupoTareas)
@php
$material = $grupoTareas->first()->material;
$grupo = $grupoTareas->first()->grupo;
$vencidas = $grupoTareas->filter(fn($t) =>
$t->fecha_entrega && \Carbon\Carbon::parse($t->fecha_entrega)->isPast()
)->count();
@endphp
<div class="card border mb-3 shadow-sm">
<div class="card-header d-flex justify-content-between align-items-center"
data-toggle="collapse"
data-target="#pend-mat-{{ $materialId }}"
aria-expanded="true"
style="cursor:pointer; user-select:none;">
<div class="d-flex align-items-center">
<i class="fas fa-book-open mr-2 text-primary"></i>
<div>
<h6 class="mb-0 font-weight-bold">{{ $material->name }}</h6>
<small class="text-muted">Grupo: {{ $grupo->clave }}</small>
</div>
</div>
<div class="d-flex align-items-center">
@if ($vencidas > 0)
<span class="badge badge-danger badge-pill mr-1"
title="{{ $vencidas }} tarea(s) vencida(s)">
{{ $vencidas }} vencida(s)
</span>
@endif
<span class="badge badge-warning badge-pill mr-2">
{{ $grupoTareas->count() }} por entregar
</span>
<i class="fas fa-chevron-down text-muted accordion-icon"></i>
</div>
</div>
<div id="pend-mat-{{ $materialId }}" class="collapse show">
<div class="card-body p-0">
<div class="table-responsive"> <div class="table-responsive">
<table id="tcont" class="table table-striped table-bordered table-hover" style="width:100%"> <table class="table table-sm table-hover mb-0">
<thead> <thead class="thead-light">
<tr> <tr>
<th>Tarea</th> <th>Tarea</th>
<th>Materia</th> <th style="width:130px">Fecha límite</th>
<th>Grupo</th> <th>Entregar</th>
<th>Fecha límite</th>
<th>Status</th>
<th>Calificación</th>
<th>Tu entrega / Acción</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@foreach ($tareas as $tarea) @foreach ($grupoTareas as $tarea)
@php $entrega = $entregasMap[$tarea->id] ?? null; $status = $entrega?->status ?? 'sin_subir'; @endphp
<tr> <tr>
<td> <td class="align-middle">
<strong>{{ $tarea->titulo }}</strong> <strong>{{ $tarea->titulo }}</strong>
@if ($tarea->descripcion) @if ($tarea->descripcion)
<p class="text-muted small mb-0">{{ Str::limit($tarea->descripcion, 80) }}</p> <p class="text-muted small mb-0">
{{ Str::limit($tarea->descripcion, 100) }}
</p>
@endif @endif
</td> </td>
<td>{{ $tarea->material->name }}</td> <td class="align-middle">
<td>{{ $tarea->grupo->clave }}</td>
<td>
@if ($tarea->fecha_entrega) @if ($tarea->fecha_entrega)
{{ \Carbon\Carbon::parse($tarea->fecha_entrega)->format('d/m/Y') }} {{ \Carbon\Carbon::parse($tarea->fecha_entrega)->format('d/m/Y') }}
@if (\Carbon\Carbon::parse($tarea->fecha_entrega)->isPast()) @if (\Carbon\Carbon::parse($tarea->fecha_entrega)->isPast())
<span class="badge badge-danger">Vencida</span> <br><span class="badge badge-danger">Vencida</span>
@else @else
<span class="badge badge-success">Activa</span> <br><span class="badge badge-success">Activa</span>
@endif @endif
@else @else
<span class="text-muted">Sin fecha</span> <span class="text-muted">Sin fecha</span>
@endif @endif
</td> </td>
<td> <td>
@if ($status === 'sin_subir') <form action="{{ route('tarea.entregar', $tarea) }}"
<span class="badge badge-secondary">Sin subir</span> method="POST"
@elseif ($status === 'pendiente')
<span class="badge badge-warning">Pendiente</span>
@else
<span class="badge badge-success">Calificada</span>
@endif
</td>
<td>
@if ($status === 'calificada' && $entrega)
<strong class="text-success">{{ number_format($entrega->calificacion, 1) }}</strong>
@if ($entrega->comentario)
<br><small class="text-muted">{{ $entrega->comentario }}</small>
@endif
@else
<span class="text-muted"></span>
@endif
</td>
<td>
@if ($status === 'sin_subir')
{{-- Formulario de entrega: texto y/o archivo --}}
<form action="{{ route('tarea.entregar', $tarea) }}" method="POST"
enctype="multipart/form-data"> enctype="multipart/form-data">
@csrf @csrf
<div class="form-group mb-1"> <div class="form-group mb-1">
<textarea name="texto" class="form-control form-control-sm" <textarea name="texto"
rows="3" placeholder="Escribe tu respuesta aquí (opcional)..."></textarea> class="form-control form-control-sm"
rows="3"
placeholder="Escribe tu respuesta aquí (opcional)..."></textarea>
</div> </div>
<div class="form-group mb-1"> <div class="form-group mb-1">
<div class="custom-file"> <div class="custom-file">
<input type="file" class="custom-file-input" id="archivo_{{ $tarea->id }}" name="archivo"> <input type="file"
<label class="custom-file-label small" for="archivo_{{ $tarea->id }}"> class="custom-file-input"
id="archivo_{{ $tarea->id }}"
name="archivo">
<label class="custom-file-label small"
for="archivo_{{ $tarea->id }}">
Adjuntar archivo (opcional) Adjuntar archivo (opcional)
</label> </label>
</div> </div>
</div> </div>
<button type="submit" class="btn btn-primary btn-sm btn-block mt-1"> <button type="submit"
class="btn btn-primary btn-sm btn-block mt-1">
<i class="fas fa-upload fa-sm"></i> Entregar <i class="fas fa-upload fa-sm"></i> Entregar
</button> </button>
</form> </form>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
</div>
@endforeach
@endif
</div>
</div>
@elseif ($status === 'pendiente' || $status === 'calificada') {{-- ══════════════════════════════════════════════════
{{-- Mostrar lo que entregó --}} SECCIÓN 2: TAREAS YA ENTREGADAS
══════════════════════════════════════════════════ --}}
<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-success">
<i class="fas fa-check-double mr-1"></i> Tareas Entregadas
</h6>
<span class="badge badge-success badge-pill">{{ $tareasEntregadas->count() }}</span>
</div>
<div class="card-body">
@if ($tareasEntregadas->isEmpty())
<div class="alert alert-info mb-0">
Aún no has entregado ninguna tarea en este ciclo.
</div>
@else
@php $entregadasAgrupadas = $tareasEntregadas->groupBy('material_id'); @endphp
@foreach ($entregadasAgrupadas as $materialId => $grupoTareas)
@php
$material = $grupoTareas->first()->material;
$grupo = $grupoTareas->first()->grupo;
$calificadas = $grupoTareas->filter(
fn($t) => ($entregasMap[$t->id]->status ?? '') === 'calificada'
)->count();
$pendientes = $grupoTareas->count() - $calificadas;
@endphp
<div class="card border mb-3 shadow-sm">
<div class="card-header d-flex justify-content-between align-items-center"
data-toggle="collapse"
data-target="#entr-mat-{{ $materialId }}"
aria-expanded="false"
style="cursor:pointer; user-select:none;">
<div class="d-flex align-items-center">
<i class="fas fa-book-open mr-2 text-success"></i>
<div>
<h6 class="mb-0 font-weight-bold">{{ $material->name }}</h6>
<small class="text-muted">Grupo: {{ $grupo->clave }}</small>
</div>
</div>
<div class="d-flex align-items-center">
@if ($pendientes > 0)
<span class="badge badge-warning badge-pill mr-1"
title="{{ $pendientes }} esperando calificación">
{{ $pendientes }} por calificar
</span>
@endif
@if ($calificadas > 0)
<span class="badge badge-success badge-pill mr-1">
{{ $calificadas }} calificada(s)
</span>
@endif
<span class="badge badge-secondary badge-pill mr-2">
{{ $grupoTareas->count() }} total
</span>
<i class="fas fa-chevron-down text-muted accordion-icon"></i>
</div>
</div>
<div id="entr-mat-{{ $materialId }}" class="collapse">
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-sm table-hover mb-0">
<thead class="thead-light">
<tr>
<th>Tarea</th>
<th style="width:110px">Status</th>
<th style="width:90px">Calificación</th>
<th>Lo que entregaste</th>
</tr>
</thead>
<tbody>
@foreach ($grupoTareas as $tarea)
@php
$entrega = $entregasMap[$tarea->id] ?? null;
$status = $entrega?->status ?? 'sin_subir';
@endphp
<tr>
<td class="align-middle">
<strong>{{ $tarea->titulo }}</strong>
@if ($tarea->descripcion)
<p class="text-muted small mb-0">
{{ Str::limit($tarea->descripcion, 80) }}
</p>
@endif
</td>
<td class="align-middle">
@if ($status === 'pendiente')
<span class="badge badge-warning">
<i class="fas fa-clock"></i> Pendiente
</span>
@else
<span class="badge badge-success">
<i class="fas fa-check"></i> Calificada
</span>
@endif
</td>
<td class="align-middle text-center">
@if ($status === 'calificada' && $entrega)
<strong class="text-success h5 mb-0">
{{ number_format($entrega->calificacion, 1) }}
</strong>
@if ($entrega->comentario)
<br><small class="text-muted">{{ $entrega->comentario }}</small>
@endif
@else
<span class="text-muted small">Esperando...</span>
@endif
</td>
<td class="align-middle">
@if ($entrega?->texto) @if ($entrega?->texto)
<div class="border rounded p-2 bg-light mb-2" style="max-height:120px;overflow-y:auto;font-size:.85rem;"> <div class="border rounded p-2 bg-light"
style="max-height:100px;overflow-y:auto;font-size:.85rem;">
{{ $entrega->texto }} {{ $entrega->texto }}
</div> </div>
@endif @endif
@if ($entrega?->archivo) @if ($entrega?->archivo)
<a href="{{ route('files.serve', $entrega->archivo) }}" <a href="{{ route('files.serve', $entrega->archivo) }}"
target="_blank" class="btn btn-sm btn-outline-info"> target="_blank"
<i class="fas fa-eye fa-sm"></i> Ver archivo entregado class="btn btn-sm btn-outline-info mt-1">
<i class="fas fa-eye fa-sm"></i> Ver archivo
</a> </a>
@endif @endif
@if (!$entrega?->texto && !$entrega?->archivo) @if (!$entrega?->texto && !$entrega?->archivo)
<span class="text-muted small">Sin contenido registrado</span> <span class="text-muted small">Sin contenido registrado</span>
@endif @endif
@if ($status === 'pendiente')
<br><small class="text-warning"><i class="fas fa-clock"></i> Esperando calificación</small>
@endif
@endif
</td> </td>
</tr> </tr>
@endforeach @endforeach
</tbody> </tbody>
</table> </table>
</div> </div>
</div>
</div>
</div>
@endforeach
@endif @endif
</div>
</div>
@endif {{-- fin @if $cicloActual --}}
</div>
</div>
</div> </div>
@endsection @endsection
@section('scripts') @section('scripts')
@include('layouts._partials.tablaScript')
<script> <script>
document.querySelectorAll('.custom-file-input').forEach(input => { document.querySelectorAll('.custom-file-input').forEach(input => {
input.addEventListener('change', function () { input.addEventListener('change', function () {
@@ -149,5 +317,16 @@ document.querySelectorAll('.custom-file-input').forEach(input => {
this.nextElementSibling.textContent = fileName; this.nextElementSibling.textContent = fileName;
}); });
}); });
document.querySelectorAll('[data-toggle="collapse"]').forEach(header => {
const target = document.querySelector(header.dataset.target);
if (!target) return;
const icon = header.querySelector('.accordion-icon');
if (target.classList.contains('show') && icon) {
icon.classList.add('fa-rotate-180');
}
target.addEventListener('show.bs.collapse', () => icon?.classList.add('fa-rotate-180'));
target.addEventListener('hide.bs.collapse', () => icon?.classList.remove('fa-rotate-180'));
});
</script> </script>
@endsection @endsection
+8 -1
View File
@@ -18,6 +18,7 @@ use App\Http\Controllers\ConceptoController;
use App\Http\Controllers\DocenteController; use App\Http\Controllers\DocenteController;
use App\Http\Controllers\EntregaController; use App\Http\Controllers\EntregaController;
use App\Http\Controllers\EvaluacionController; use App\Http\Controllers\EvaluacionController;
use App\Http\Controllers\EvaluacionResponderController;
use App\Http\Controllers\TareaController; 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;
@@ -88,10 +89,15 @@ Route::middleware(['auth:sanctum',config('jetstream.auth_session'),'verified',
Route::get('/search', UserSearchController::class)->name('users.search'); Route::get('/search', UserSearchController::class)->name('users.search');
// Evaluaciones // Evaluaciones (administración)
Route::resource('/evaluacion', EvaluacionController::class); Route::resource('/evaluacion', EvaluacionController::class);
Route::patch('/evaluacion/{evaluacion}/status', [EvaluacionController::class, 'toggleStatus'])->name('evaluacion.toggleStatus'); Route::patch('/evaluacion/{evaluacion}/status', [EvaluacionController::class, 'toggleStatus'])->name('evaluacion.toggleStatus');
// Evaluaciones (responder — docentes y alumnos)
Route::get('/mis-evaluaciones', [EvaluacionResponderController::class, 'misEvaluaciones'])->name('evaluacion.mis_evaluaciones');
Route::get('/evaluacion/{evaluacion}/responder', [EvaluacionResponderController::class, 'show'])->name('evaluacion.responder.show');
Route::post('/evaluacion/{evaluacion}/responder', [EvaluacionResponderController::class, 'store'])->name('evaluacion.responder.store');
// Anuncios (gestión: solo Admin) // Anuncios (gestión: solo Admin)
Route::resource('/anuncio', AnuncioController::class); Route::resource('/anuncio', AnuncioController::class);
Route::patch('/anuncio/{anuncio}/toggle', [AnuncioController::class, 'toggleActive'])->name('anuncio.toggleActive'); Route::patch('/anuncio/{anuncio}/toggle', [AnuncioController::class, 'toggleActive'])->name('anuncio.toggleActive');
@@ -111,6 +117,7 @@ Route::middleware(['auth:sanctum',config('jetstream.auth_session'),'verified',
Route::get('/grupo/{grupo}/material/{material}/tareas', [TareaController::class, 'index'])->name('grupo.material.tareas'); 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::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::post('/grupo/{grupo}/material/{material}/tareas', [TareaController::class, 'store'])->name('grupo.material.tareas.store');
Route::get('/grupo/{grupo}/material/{material}/tareas/acta', [TareaController::class, 'acta'])->name('grupo.material.tareas.acta');
Route::get('/grupo/{grupo}/material/{material}/tareas/{tarea}', [TareaController::class, 'show'])->name('grupo.material.tareas.show'); 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::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::put('/grupo/{grupo}/material/{material}/tareas/{tarea}', [TareaController::class, 'update'])->name('grupo.material.tareas.update');