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
@@ -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="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
<i class="fa-duotone fa-regular fa-clipboard-list"></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
@@ -25,8 +25,7 @@
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-bordered table-hover">
<table id="tcont" class="table table-striped table-bordered nowrap table-hover" style="width:100%;">
<thead class="thead-light">
<tr>
<th>Título</th>
@@ -52,11 +51,11 @@
<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>
<span class="badge badge-info"><i class="fa-duotone fa-solid fa-chalkboard-user"></i> Docentes</span>
@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')
<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
<span class="badge badge-light"></span>
@endif
@@ -86,34 +85,32 @@
<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>
<i class="fa-duotone fa-solid fa-eye"></i>
</a>
<a href="{{ route('evaluacion.edit', $evaluacion) }}"
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>
<form action="{{ route('evaluacion.destroy', $evaluacion) }}"
method="POST" class="d-inline"
onsubmit="return confirm('¿Eliminar esta evaluación y todas sus preguntas?')">
method="POST" class="d-inline delete-form">
@csrf @method('DELETE')
<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>
</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
@section('scripts')
@include('layouts._partials.tablaScript')
@include('layouts._partials.delete')
@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
+12 -5
View File
@@ -150,12 +150,19 @@
<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>
<div class="d-flex align-items-center mb-1 flex-wrap" style="gap:.3rem;">
<small class="text-muted font-weight-bold">
Pregunta {{ $i + 1 }}
@if($pregunta->requerida)
<span class="text-danger">*</span>
@endif
</small>
@if($pregunta->es_docente)
<span class="badge badge-info">
<i class="fas fa-chalkboard-teacher mr-1"></i>Por docente
</span>
@endif
</small>
</div>
<p class="mb-1 font-weight-bold">{{ $pregunta->texto }}</p>
<small class="text-muted">{{ $pregunta->tipo?->nombre }}</small>
</div>
@@ -202,6 +202,12 @@
</a>
</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
@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>
<span>Mis grupos</span></a>
</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
@@ -94,6 +94,10 @@
</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">
@@ -110,7 +114,7 @@
</div>
{{-- Tipo de respuesta --}}
<div class="col-md-4">
<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"
@@ -135,8 +139,32 @@
<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)
@@ -143,7 +143,7 @@
</tr>
@empty
<tr><td colspan="5">Sin materias en este plan.</td></tr>
<tr>Sin materias en este plan.</tr>
@endforelse
</tbody>
</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">
<i class="fas fa-arrow-left fa-sm"></i> Regresar
</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]) }}"
class="btn btn-sm btn-primary shadow-sm">
<i class="fas fa-plus fa-sm text-white-50"></i> Nueva Tarea
+305 -126
View File
@@ -1,147 +1,315 @@
@extends('layouts.landing')
@section('head')
@include('layouts._partials.tablaStyle')
@endsection
@section('title', 'Mis Tareas')
@section('content')
<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>
{{-- Alertas de sesión --}}
@if (session('info'))
<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
@if (session('error'))
<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
@if ($errors->has('entrega'))
<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
{{-- Sin ciclo activo --}}
@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
{{-- ══════════════════════════════════════════════════
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">
<table class="table table-sm table-hover mb-0">
<thead class="thead-light">
<tr>
<th>Tarea</th>
<th style="width:130px">Fecha límite</th>
<th>Entregar</th>
</tr>
</thead>
<tbody>
@foreach ($grupoTareas as $tarea)
<tr>
<td class="align-middle">
<strong>{{ $tarea->titulo }}</strong>
@if ($tarea->descripcion)
<p class="text-muted small mb-0">
{{ Str::limit($tarea->descripcion, 100) }}
</p>
@endif
</td>
<td class="align-middle">
@if ($tarea->fecha_entrega)
{{ \Carbon\Carbon::parse($tarea->fecha_entrega)->format('d/m/Y') }}
@if (\Carbon\Carbon::parse($tarea->fecha_entrega)->isPast())
<br><span class="badge badge-danger">Vencida</span>
@else
<br><span class="badge badge-success">Activa</span>
@endif
@else
<span class="text-muted">Sin fecha</span>
@endif
</td>
<td>
<form action="{{ route('tarea.entregar', $tarea) }}"
method="POST"
enctype="multipart/form-data">
@csrf
<div class="form-group mb-1">
<textarea name="texto"
class="form-control form-control-sm"
rows="3"
placeholder="Escribe tu respuesta aquí (opcional)..."></textarea>
</div>
<div class="form-group mb-1">
<div class="custom-file">
<input type="file"
class="custom-file-input"
id="archivo_{{ $tarea->id }}"
name="archivo">
<label class="custom-file-label small"
for="archivo_{{ $tarea->id }}">
Adjuntar archivo (opcional)
</label>
</div>
</div>
<button type="submit"
class="btn btn-primary btn-sm btn-block mt-1">
<i class="fas fa-upload fa-sm"></i> Entregar
</button>
</form>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
</div>
@endforeach
@endif
</div>
</div>
<div class="card-body">
{{-- ══════════════════════════════════════════════════
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
@if (session('info'))
<div class="alert alert-success"><strong>{{ session('info') }}</strong></div>
@endif
@if (session('error'))
<div class="alert alert-danger"><strong>{{ session('error') }}</strong></div>
@endif
@if ($errors->has('entrega'))
<div class="alert alert-warning"><strong>{{ $errors->first('entrega') }}</strong></div>
@endif
@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
@if (!isset($tareas) || $tareas->isEmpty())
<div class="alert alert-info">No tienes tareas asignadas en tus grupos.</div>
@else
<div class="table-responsive">
<table id="tcont" class="table table-striped table-bordered table-hover" style="width:100%">
<thead>
<tr>
<th>Tarea</th>
<th>Materia</th>
<th>Grupo</th>
<th>Fecha límite</th>
<th>Status</th>
<th>Calificación</th>
<th>Tu entrega / Acción</th>
</tr>
</thead>
<tbody>
@foreach ($tareas as $tarea)
@php $entrega = $entregasMap[$tarea->id] ?? null; $status = $entrega?->status ?? 'sin_subir'; @endphp
<tr>
<td>
<strong>{{ $tarea->titulo }}</strong>
@if ($tarea->descripcion)
<p class="text-muted small mb-0">{{ Str::limit($tarea->descripcion, 80) }}</p>
<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
</td>
<td>{{ $tarea->material->name }}</td>
<td>{{ $tarea->grupo->clave }}</td>
<td>
@if ($tarea->fecha_entrega)
{{ \Carbon\Carbon::parse($tarea->fecha_entrega)->format('d/m/Y') }}
@if (\Carbon\Carbon::parse($tarea->fecha_entrega)->isPast())
<span class="badge badge-danger">Vencida</span>
@else
<span class="badge badge-success">Activa</span>
@endif
@else
<span class="text-muted">Sin fecha</span>
@if ($calificadas > 0)
<span class="badge badge-success badge-pill mr-1">
{{ $calificadas }} calificada(s)
</span>
@endif
</td>
<td>
@if ($status === 'sin_subir')
<span class="badge badge-secondary">Sin subir</span>
@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">
@csrf
<div class="form-group mb-1">
<textarea name="texto" class="form-control form-control-sm"
rows="3" placeholder="Escribe tu respuesta aquí (opcional)..."></textarea>
</div>
<div class="form-group mb-1">
<div class="custom-file">
<input type="file" class="custom-file-input" id="archivo_{{ $tarea->id }}" name="archivo">
<label class="custom-file-label small" for="archivo_{{ $tarea->id }}">
Adjuntar archivo (opcional)
</label>
</div>
</div>
<button type="submit" class="btn btn-primary btn-sm btn-block mt-1">
<i class="fas fa-upload fa-sm"></i> Entregar
</button>
</form>
@elseif ($status === 'pendiente' || $status === 'calificada')
{{-- Mostrar lo que entregó --}}
@if ($entrega?->texto)
<div class="border rounded p-2 bg-light mb-2" style="max-height:120px;overflow-y:auto;font-size:.85rem;">
{{ $entrega->texto }}
</div>
@endif
@if ($entrega?->archivo)
<a href="{{ route('files.serve', $entrega->archivo) }}"
target="_blank" class="btn btn-sm btn-outline-info">
<i class="fas fa-eye fa-sm"></i> Ver archivo entregado
</a>
@endif
@if (!$entrega?->texto && !$entrega?->archivo)
<span class="text-muted small">Sin contenido registrado</span>
@endif
@if ($status === 'pendiente')
<br><small class="text-warning"><i class="fas fa-clock"></i> Esperando calificación</small>
@endif
@endif
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@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)
<div class="border rounded p-2 bg-light"
style="max-height:100px;overflow-y:auto;font-size:.85rem;">
{{ $entrega->texto }}
</div>
@endif
@if ($entrega?->archivo)
<a href="{{ route('files.serve', $entrega->archivo) }}"
target="_blank"
class="btn btn-sm btn-outline-info mt-1">
<i class="fas fa-eye fa-sm"></i> Ver archivo
</a>
@endif
@if (!$entrega?->texto && !$entrega?->archivo)
<span class="text-muted small">Sin contenido registrado</span>
@endif
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
</div>
@endforeach
@endif
</div>
</div>
</div>
@endif {{-- fin @if $cicloActual --}}
</div>
@endsection
@section('scripts')
@include('layouts._partials.tablaScript')
<script>
document.querySelectorAll('.custom-file-input').forEach(input => {
input.addEventListener('change', function () {
@@ -149,5 +317,16 @@ document.querySelectorAll('.custom-file-input').forEach(input => {
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>
@endsection