archivo de configuracion de nginx agregado para el puerto 9000

This commit is contained in:
2026-05-02 16:57:29 -06:00
parent 2af36ea272
commit 881e67fb12
33 changed files with 2035 additions and 245 deletions
+162
View File
@@ -0,0 +1,162 @@
@extends('layouts.landing')
@section('title', 'Nuevo Anuncio')
@section('content')
<div class="container-fluid">
<div class="card shadow mb-4" style="max-width:760px; margin:auto">
<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-plus-circle mr-1"></i> Nuevo Anuncio
</h6>
<a href="{{ route('anuncio.index') }}" class="btn btn-sm btn-secondary">
<i class="fas fa-arrow-left mr-1"></i> Volver
</a>
</div>
<div class="card-body">
@if($errors->any())
<div class="alert alert-danger">
<ul class="mb-0">
@foreach($errors->all() as $e)
<li>{{ $e }}</li>
@endforeach
</ul>
</div>
@endif
<form action="{{ route('anuncio.store') }}" method="POST" enctype="multipart/form-data">
@csrf
{{-- Título --}}
<div class="form-group">
<label class="font-weight-bold">Título <span class="text-danger">*</span></label>
<input type="text" name="title" class="form-control @error('title') is-invalid @enderror"
value="{{ old('title') }}" maxlength="200" placeholder="Título del anuncio" required>
@error('title')<div class="invalid-feedback">{{ $message }}</div>@enderror
</div>
{{-- Cuerpo --}}
<div class="form-group">
<label class="font-weight-bold">Descripción</label>
<textarea name="body" class="form-control @error('body') is-invalid @enderror"
rows="4" maxlength="3000" placeholder="Texto del anuncio (opcional)">{{ old('body') }}</textarea>
@error('body')<div class="invalid-feedback">{{ $message }}</div>@enderror
</div>
{{-- Tipo --}}
<div class="form-group">
<label class="font-weight-bold">Tipo <span class="text-danger">*</span></label>
<div class="d-flex flex-wrap gap-2">
@foreach($types as $key => $cfg)
<div class="form-check form-check-inline mr-3">
<input class="form-check-input" type="radio" name="type" id="type_{{ $key }}"
value="{{ $key }}" {{ old('type', 'info') === $key ? 'checked' : '' }} required>
<label class="form-check-label" for="type_{{ $key }}">
<span class="badge badge-{{ $cfg['color'] }}">
<i class="fas {{ $cfg['icon'] }} mr-1"></i>{{ $cfg['label'] }}
</span>
</label>
</div>
@endforeach
</div>
@error('type')<div class="text-danger small mt-1">{{ $message }}</div>@enderror
</div>
{{-- Audiencia --}}
<div class="form-group">
<label class="font-weight-bold">Audiencia <span class="text-danger">*</span></label>
<div class="border rounded p-3 bg-light">
<div class="row">
@foreach($roles as $key => $label)
<div class="col-6 col-md-4">
<div class="form-check">
<input class="form-check-input audience-check" type="checkbox"
name="audience[]" id="aud_{{ $key }}" value="{{ $key }}"
{{ in_array($key, old('audience', [])) ? 'checked' : '' }}>
<label class="form-check-label" for="aud_{{ $key }}">{{ $label }}</label>
</div>
</div>
@endforeach
</div>
</div>
@error('audience')<div class="text-danger small mt-1">{{ $message }}</div>@enderror
</div>
{{-- Imagen --}}
<div class="form-group">
<label class="font-weight-bold">Imagen</label>
<div class="custom-file">
<input type="file" class="custom-file-input @error('image') is-invalid @enderror"
id="imageInput" name="image" accept="image/*">
<label class="custom-file-label" for="imageInput">Seleccionar imagen (máx. 5 MB)</label>
</div>
@error('image')<div class="text-danger small mt-1">{{ $message }}</div>@enderror
<div id="imagePreview" class="mt-2 d-none">
<img id="previewImg" src="" alt="Vista previa" class="img-fluid rounded" style="max-height:200px">
</div>
</div>
{{-- Vencimiento --}}
<div class="form-group">
<label class="font-weight-bold">Fecha de vencimiento</label>
<input type="datetime-local" name="expires_at"
class="form-control @error('expires_at') is-invalid @enderror"
value="{{ old('expires_at') }}">
<small class="text-muted">Dejar vacío para que no tenga vencimiento.</small>
@error('expires_at')<div class="invalid-feedback">{{ $message }}</div>@enderror
</div>
{{-- Activo --}}
<div class="form-group">
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" id="activeSwitch"
name="active" value="1" {{ old('active', '1') ? 'checked' : '' }}>
<label class="custom-control-label font-weight-bold" for="activeSwitch">Publicar inmediatamente</label>
</div>
</div>
<hr>
<div class="d-flex justify-content-end">
<a href="{{ route('anuncio.index') }}" class="btn btn-secondary mr-2">Cancelar</a>
<button type="submit" class="btn btn-primary">
<i class="fas fa-save mr-1"></i> Guardar anuncio
</button>
</div>
</form>
</div>
</div>
</div>
@endsection
@section('scripts')
<script>
// Custom file label
document.getElementById('imageInput').addEventListener('change', function () {
const file = this.files[0];
this.nextElementSibling.textContent = file ? file.name : 'Seleccionar imagen (máx. 5 MB)';
if (file) {
const reader = new FileReader();
reader.onload = e => {
document.getElementById('previewImg').src = e.target.result;
document.getElementById('imagePreview').classList.remove('d-none');
};
reader.readAsDataURL(file);
} else {
document.getElementById('imagePreview').classList.add('d-none');
}
});
// "Todos" exclusivo con los demás
document.querySelectorAll('.audience-check').forEach(function (cb) {
cb.addEventListener('change', function () {
if (this.value === 'all' && this.checked) {
document.querySelectorAll('.audience-check').forEach(c => { if (c !== this) c.checked = false; });
} else if (this.value !== 'all' && this.checked) {
const allCb = document.querySelector('.audience-check[value="all"]');
if (allCb) allCb.checked = false;
}
});
});
</script>
@endsection
+173
View File
@@ -0,0 +1,173 @@
@extends('layouts.landing')
@section('title', 'Editar Anuncio')
@section('content')
<div class="container-fluid">
<div class="card shadow mb-4" style="max-width:760px; margin:auto">
<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-edit mr-1"></i> Editar Anuncio
</h6>
<a href="{{ route('anuncio.index') }}" class="btn btn-sm btn-secondary">
<i class="fas fa-arrow-left mr-1"></i> Volver
</a>
</div>
<div class="card-body">
@if($errors->any())
<div class="alert alert-danger">
<ul class="mb-0">
@foreach($errors->all() as $e)
<li>{{ $e }}</li>
@endforeach
</ul>
</div>
@endif
<form action="{{ route('anuncio.update', $anuncio) }}" method="POST" enctype="multipart/form-data">
@csrf
@method('PUT')
{{-- Título --}}
<div class="form-group">
<label class="font-weight-bold">Título <span class="text-danger">*</span></label>
<input type="text" name="title" class="form-control @error('title') is-invalid @enderror"
value="{{ old('title', $anuncio->title) }}" maxlength="200" required>
@error('title')<div class="invalid-feedback">{{ $message }}</div>@enderror
</div>
{{-- Cuerpo --}}
<div class="form-group">
<label class="font-weight-bold">Descripción</label>
<textarea name="body" class="form-control @error('body') is-invalid @enderror"
rows="4" maxlength="3000">{{ old('body', $anuncio->body) }}</textarea>
@error('body')<div class="invalid-feedback">{{ $message }}</div>@enderror
</div>
{{-- Tipo --}}
<div class="form-group">
<label class="font-weight-bold">Tipo <span class="text-danger">*</span></label>
<div class="d-flex flex-wrap">
@foreach($types as $key => $cfg)
<div class="form-check form-check-inline mr-3">
<input class="form-check-input" type="radio" name="type" id="type_{{ $key }}"
value="{{ $key }}" {{ old('type', $anuncio->type) === $key ? 'checked' : '' }} required>
<label class="form-check-label" for="type_{{ $key }}">
<span class="badge badge-{{ $cfg['color'] }}">
<i class="fas {{ $cfg['icon'] }} mr-1"></i>{{ $cfg['label'] }}
</span>
</label>
</div>
@endforeach
</div>
@error('type')<div class="text-danger small mt-1">{{ $message }}</div>@enderror
</div>
{{-- Audiencia --}}
<div class="form-group">
<label class="font-weight-bold">Audiencia <span class="text-danger">*</span></label>
<div class="border rounded p-3 bg-light">
<div class="row">
@foreach($roles as $key => $label)
<div class="col-6 col-md-4">
<div class="form-check">
<input class="form-check-input audience-check" type="checkbox"
name="audience[]" id="aud_{{ $key }}" value="{{ $key }}"
{{ in_array($key, old('audience', $anuncio->audience ?? [])) ? 'checked' : '' }}>
<label class="form-check-label" for="aud_{{ $key }}">{{ $label }}</label>
</div>
</div>
@endforeach
</div>
</div>
@error('audience')<div class="text-danger small mt-1">{{ $message }}</div>@enderror
</div>
{{-- Imagen actual + nueva --}}
<div class="form-group">
<label class="font-weight-bold">Imagen</label>
@if($anuncio->image_path)
<div class="mb-2">
<img src="{{ Storage::url($anuncio->image_path) }}" alt="Imagen actual"
class="img-fluid rounded" style="max-height:150px">
<div class="form-check mt-1">
<input class="form-check-input" type="checkbox" name="remove_image" id="removeImage" value="1">
<label class="form-check-label text-danger" for="removeImage">Eliminar imagen actual</label>
</div>
</div>
@endif
<div class="custom-file">
<input type="file" class="custom-file-input @error('image') is-invalid @enderror"
id="imageInput" name="image" accept="image/*">
<label class="custom-file-label" for="imageInput">
{{ $anuncio->image_path ? 'Reemplazar imagen (máx. 5 MB)' : 'Seleccionar imagen (máx. 5 MB)' }}
</label>
</div>
@error('image')<div class="text-danger small mt-1">{{ $message }}</div>@enderror
<div id="imagePreview" class="mt-2 d-none">
<img id="previewImg" src="" alt="Vista previa" class="img-fluid rounded" style="max-height:200px">
</div>
</div>
{{-- Vencimiento --}}
<div class="form-group">
<label class="font-weight-bold">Fecha de vencimiento</label>
<input type="datetime-local" name="expires_at"
class="form-control @error('expires_at') is-invalid @enderror"
value="{{ old('expires_at', $anuncio->expires_at ? $anuncio->expires_at->format('Y-m-d\TH:i') : '') }}">
<small class="text-muted">Dejar vacío para que no tenga vencimiento.</small>
@error('expires_at')<div class="invalid-feedback">{{ $message }}</div>@enderror
</div>
{{-- Activo --}}
<div class="form-group">
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" id="activeSwitch"
name="active" value="1" {{ old('active', $anuncio->active) ? 'checked' : '' }}>
<label class="custom-control-label font-weight-bold" for="activeSwitch">Anuncio activo</label>
</div>
</div>
<hr>
<div class="d-flex justify-content-end">
<a href="{{ route('anuncio.index') }}" class="btn btn-secondary mr-2">Cancelar</a>
<button type="submit" class="btn btn-primary">
<i class="fas fa-save mr-1"></i> Actualizar anuncio
</button>
</div>
</form>
</div>
</div>
</div>
@endsection
@section('scripts')
<script>
document.getElementById('imageInput').addEventListener('change', function () {
const file = this.files[0];
this.nextElementSibling.textContent = file ? file.name : 'Seleccionar imagen (máx. 5 MB)';
if (file) {
const reader = new FileReader();
reader.onload = e => {
document.getElementById('previewImg').src = e.target.result;
document.getElementById('imagePreview').classList.remove('d-none');
};
reader.readAsDataURL(file);
} else {
document.getElementById('imagePreview').classList.add('d-none');
}
});
document.querySelectorAll('.audience-check').forEach(function (cb) {
cb.addEventListener('change', function () {
if (this.value === 'all' && this.checked) {
document.querySelectorAll('.audience-check').forEach(c => { if (c !== this) c.checked = false; });
} else if (this.value !== 'all' && this.checked) {
const allCb = document.querySelector('.audience-check[value="all"]');
if (allCb) allCb.checked = false;
}
});
});
</script>
@endsection
+115
View File
@@ -0,0 +1,115 @@
@extends('layouts.landing')
@section('title', 'Anuncios')
@section('content')
<div class="container-fluid">
<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-bullhorn mr-1"></i> Anuncios
</h6>
<a href="{{ route('anuncio.create') }}" class="btn btn-sm btn-primary shadow-sm">
<i class="fas fa-plus fa-sm text-white-50 mr-1"></i> Nuevo anuncio
</a>
</div>
<div class="card-body">
@if(session('success'))
<div class="alert alert-success alert-dismissible fade show" role="alert">
{{ session('success') }}
<button type="button" class="close" data-dismiss="alert"><span>&times;</span></button>
</div>
@endif
<div class="table-responsive">
<table class="table table-bordered table-hover" style="width:100%">
<thead class="thead-light">
<tr>
<th style="width:40px">#</th>
<th>Título</th>
<th style="width:130px">Tipo</th>
<th style="width:160px">Audiencia</th>
<th style="width:130px">Vence</th>
<th style="width:80px" class="text-center">Activo</th>
<th style="width:130px" class="text-center">Opciones</th>
</tr>
</thead>
<tbody>
@forelse($anuncios as $anuncio)
@php $cfg = $anuncio->typeConfig(); @endphp
<tr class="{{ $anuncio->isExpired() ? 'table-secondary text-muted' : '' }}">
<td>{{ $anuncio->id }}</td>
<td>
<span class="badge badge-{{ $cfg['color'] }} mr-1">
<i class="fas {{ $cfg['icon'] }}"></i>
</span>
{{ $anuncio->title }}
@if($anuncio->image_path)
<i class="fas fa-image text-secondary ml-1" title="Tiene imagen"></i>
@endif
</td>
<td>
<span class="badge badge-{{ $cfg['color'] }}">{{ $cfg['label'] }}</span>
</td>
<td>
@foreach($anuncio->audience as $aud)
<span class="badge badge-light border mr-1">{{ \App\Models\Anuncio::ROLES[$aud] ?? $aud }}</span>
@endforeach
</td>
<td>
@if($anuncio->expires_at)
<span class="{{ $anuncio->isExpired() ? 'text-danger' : 'text-secondary' }}">
<i class="fas fa-clock mr-1"></i>
{{ $anuncio->expires_at->format('d/m/Y H:i') }}
</span>
@else
<span class="text-muted">Sin vencimiento</span>
@endif
</td>
<td class="text-center">
<form action="{{ route('anuncio.toggleActive', $anuncio) }}" method="POST">
@csrf
@method('PATCH')
<button type="submit" class="btn btn-sm {{ $anuncio->active ? 'btn-success' : 'btn-secondary' }}"
title="{{ $anuncio->active ? 'Desactivar' : 'Activar' }}">
<i class="fas {{ $anuncio->active ? 'fa-toggle-on' : 'fa-toggle-off' }}"></i>
</button>
</form>
</td>
<td class="text-center">
<a href="{{ route('anuncio.edit', $anuncio) }}" class="btn btn-sm btn-primary">
<i class="fas fa-edit"></i>
</a>
<form action="{{ route('anuncio.destroy', $anuncio) }}" method="POST" class="d-inline delete-form">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-sm btn-danger">
<i class="fas fa-trash"></i>
</button>
</form>
</td>
</tr>
@empty
<tr>
<td colspan="7" class="text-center text-muted py-4">
<i class="fas fa-bullhorn fa-2x mb-2 d-block"></i>
No hay anuncios creados.
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<div class="d-flex justify-content-center mt-3">
{{ $anuncios->links() }}
</div>
</div>
</div>
</div>
@endsection
@section('scripts')
@include('layouts._partials.delete')
@endsection
+23 -10
View File
@@ -9,23 +9,36 @@
<h5 class="mb-0"><i class="fas fa-comments"></i> Chats</h5>
</div>
<div class="card-body p-0" style="overflow-y: auto;">
@forelse($friends as $friend)
<a href="{{ route('chat.show', $friend) }}" class="text-decoration-none">
@forelse($users as $user)
<a href="{{ route('chat.show', $user) }}" class="text-decoration-none">
<div class="d-flex align-items-center p-3 border-bottom chat-user-item">
<img src="{{ $friend->profile_photo_url }}"
class="rounded-circle mr-3"
width="45" height="45"
style="object-fit:cover">
<div>
<div class="font-weight-bold text-dark">{{ $friend->name }}</div>
<small class="text-muted">{{ $friend->getRoleNames()->first() }}</small>
<div class="position-relative mr-3 flex-shrink-0">
<img src="{{ $user->profile_photo_url }}"
class="rounded-circle"
width="45" height="45"
style="object-fit:cover;
{{ $user->is_friend ? '' : 'filter:grayscale(60%); opacity:.8' }}">
@unless($user->is_friend)
<span class="position-absolute"
style="bottom:0;right:0;background:#dc3545;border-radius:50%;
width:16px;height:16px;display:flex;align-items:center;
justify-content:center;border:2px solid #fff">
<i class="fas fa-lock" style="font-size:.45rem;color:#fff"></i>
</span>
@endunless
</div>
<div class="flex-grow-1 overflow-hidden">
<div class="font-weight-bold text-dark text-truncate">{{ $user->name }}</div>
<small class="{{ $user->is_friend ? 'text-muted' : 'text-danger' }}">
{{ $user->is_friend ? $user->getRoleNames()->first() : 'Ya no son amigos' }}
</small>
</div>
</div>
</a>
@empty
<div class="text-center text-muted py-5">
<i class="fas fa-user-friends fa-3x mb-3 d-block"></i>
<p class="mb-2">Aún no tienes amigos para chatear.</p>
<p class="mb-2">Aún no tienes conversaciones.</p>
<a href="{{ route('feed') }}" class="btn btn-primary btn-sm">
<i class="fas fa-search mr-1"></i> Buscar personas
</a>
+55 -11
View File
@@ -1,15 +1,59 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{ __('Dashboard') }}
</h2>
</x-slot>
@extends('layouts.landing')
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-xl sm:rounded-lg">
<x-welcome />
@section('title', 'Inicio')
@section('content')
<div class="container-fluid">
{{-- Anuncios vigentes para este usuario --}}
@if(isset($anuncios) && $anuncios->count())
<div class="mb-4">
@foreach($anuncios as $anuncio)
@php $cfg = $anuncio->typeConfig(); @endphp
<div class="alert alert-{{ $cfg['color'] }} alert-dismissible fade show shadow-sm mb-3" role="alert">
<div class="d-flex align-items-start">
<i class="fas {{ $cfg['icon'] }} fa-lg mr-3 mt-1"></i>
<div class="flex-grow-1">
<h6 class="font-weight-bold mb-1">{{ $anuncio->title }}</h6>
@if($anuncio->body)
<p class="mb-1" style="white-space:pre-line">{{ $anuncio->body }}</p>
@endif
@if($anuncio->image_path)
<img src="{{ Storage::url($anuncio->image_path) }}"
alt="{{ $anuncio->title }}"
class="img-fluid rounded mt-2"
style="max-height:300px; max-width:100%; object-fit:contain">
@endif
@if($anuncio->expires_at)
<div class="mt-1">
<small class="opacity-75">
<i class="fas fa-clock mr-1"></i>
Válido hasta {{ $anuncio->expires_at->format('d/m/Y H:i') }}
</small>
</div>
@endif
</div>
</div>
<button type="button" class="close" data-dismiss="alert" aria-label="Cerrar">
<span aria-hidden="true">&times;</span>
</button>
</div>
@endforeach
</div>
@endif
{{-- Contenido de bienvenida --}}
<div class="row">
<div class="col-12">
<div class="card shadow">
<div class="card-body text-center py-5">
<i class="fas fa-graduation-cap fa-3x text-primary mb-3"></i>
<h4 class="font-weight-bold">Bienvenido al Sistema Educativo SECUIEP</h4>
<p class="text-muted">Utiliza el menú lateral para acceder a los módulos disponibles.</p>
</div>
</div>
</div>
</div>
</x-app-layout>
</div>
@endsection
@@ -0,0 +1,60 @@
@extends('layouts.landing')
@section('head')
@include('layouts._partials.tablaStyle')
@endsection
@section('title', 'Administrar grupo')
@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">
<h6 class="m-0 font-weight-bold text-primary">
Materias asignadas &mdash; Grupo: {{ $grupo->clave }}
</h6>
<a href="{{ route('grupo.index') }}" class="btn btn-sm btn-secondary shadow-sm">
<i class="fas fa-arrow-left fa-sm"></i> Regresar
</a>
</div>
</div>
<div class="card-body">
@if (session('info'))
<div class="alert alert-success"><strong>{{ session('info') }}</strong></div>
@endif
@if ($materiales->isEmpty())
<div class="alert alert-warning">No tienes materias asignadas en este grupo.</div>
@else
<div class="row">
@foreach ($materiales as $material)
<div class="col-md-4 mb-4">
<div class="card border-left-primary shadow h-100">
<div class="card-body">
<div class="text-xs font-weight-bold text-primary text-uppercase mb-1">
{{ $material->clave }}
</div>
<div class="h5 mb-2 font-weight-bold text-gray-800">
{{ $material->name }}
</div>
<p class="text-muted small mb-3">
Ciclo: {{ $material->ciclo }} &middot; {{ $material->horas }} hrs
</p>
<a href="{{ route('grupo.material.tareas', [$grupo, $material]) }}"
class="btn btn-primary btn-sm btn-block">
<i class="fas fa-tasks fa-sm"></i> Gestionar Tareas
</a>
</div>
</div>
</div>
@endforeach
</div>
@endif
</div>
</div>
</div>
@endsection
+4
View File
@@ -88,8 +88,12 @@
<option value="0" {{ 0 == $grupo->status ? 'selected' : '' }}>Inactivo</option>
</select>
</div>
@hasrole('Control escolar')
<input class="btn btn-primary" type="submit" value="Guardar">
@endhasrole
</form>
</div>
</div>
+16
View File
@@ -54,6 +54,22 @@
</div>
@endcan
@hasrole('Docente')
<div class="col-sm-6 col-md-6">
<a class="btn btn-primary" href="{{ route('grupo.administrar', $grupo->id) }}">
<i class="fas fa-tasks fa-sm"></i> Administrar
</a>
</div>
@endhasrole
@hasrole('Coordinación académica')
<div class="col-sm-6 col-md-6">
<a class="btn btn-info" href="{{ route('grupo.ver.materias', $grupo->id) }}">
<i class="fas fa-eye fa-sm"></i> Ver Tareas
</a>
</div>
@endhasrole
</td>
</tr>
@empty
@@ -0,0 +1,58 @@
@extends('layouts.landing')
@section('title', 'Materias del grupo')
@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">
<h6 class="m-0 font-weight-bold text-primary">
Materias &mdash; Grupo: {{ $grupo->clave }}
</h6>
<a href="{{ route('grupo.index') }}" class="btn btn-sm btn-secondary shadow-sm">
<i class="fas fa-arrow-left fa-sm"></i> Regresar
</a>
</div>
</div>
<div class="card-body">
@if ($asignaciones->isEmpty())
<div class="alert alert-warning">Este grupo no tiene materias asignadas.</div>
@else
<div class="row">
@foreach ($asignaciones as $asig)
<div class="col-md-4 mb-4">
<div class="card border-left-info shadow h-100">
<div class="card-body">
<div class="text-xs font-weight-bold text-info text-uppercase mb-1">
{{ $asig->material_clave }}
</div>
<div class="h5 mb-1 font-weight-bold text-gray-800">
{{ $asig->material_name }}
</div>
<p class="text-muted small mb-1">
Ciclo: {{ $asig->ciclo }} &middot; {{ $asig->horas }} hrs
</p>
<p class="small mb-3">
<i class="fas fa-chalkboard-teacher fa-sm text-gray-500"></i>
<span class="text-gray-700">
{{ $asig->apellidoPaterno }} {{ $asig->apellidoMaterno }} {{ $asig->docente_nombre }}
</span>
</p>
<a href="{{ route('grupo.material.tareas', [$grupo->id, $asig->material_id]) }}"
class="btn btn-info btn-sm btn-block">
<i class="fas fa-tasks fa-sm"></i> Ver Tareas
</a>
</div>
</div>
</div>
@endforeach
</div>
@endif
</div>
</div>
</div>
@endsection
@@ -1,7 +1,7 @@
<footer class="sticky-footer bg-white">
<div class="container my-auto">
<div class="copyright text-center my-auto">
<span>Copyright &copy; Your Website 2021</span>
<span>Copyright &copy; Your Website {{date('Y')}}</span>
</div>
</div>
</footer>
@@ -9,4 +9,4 @@
<a class="scroll-to-top rounded" href="#page-top">
<i class="fas fa-angle-up"></i>
</a>
</a>
@@ -8,6 +8,7 @@
<i class="fa fa-bars"></i>
</button>
@can(['comunidad','amigos'])
<!-- Topbar Search -->
<div class="d-none d-sm-inline-block form-inline mr-auto ml-md-3 my-2 my-md-0 mw-100 navbar-search position-relative">
<div class="input-group">
@@ -25,8 +26,10 @@
min-width:300px"></div>
</div>
@endcan
<ul class="navbar-nav ml-auto">
<!-- Nav Item - Search (XS) -->
<li class="nav-item no-arrow d-sm-none position-relative">
<a class="nav-link" href="#" id="mobile-search-toggle" role="button">
@@ -50,6 +53,7 @@
</div>
</li>
@can(['comunidad','amigos'])
<!-- Nav Item - Notificaciones -->
<li class="nav-item dropdown no-arrow mx-1">
<a class="nav-link dropdown-toggle" href="#" id="alertsDropdown" role="button"
@@ -98,6 +102,9 @@
</div>
</li>
@endcan
@can('mensajes')
<!-- Nav Item - Mensajes -->
<li class="nav-item dropdown no-arrow mx-1">
<a class="nav-link dropdown-toggle" href="{{ route('chat.index') }}" id="messagesDropdown"
@@ -143,6 +150,7 @@
</a>
</div>
</li>
@endcan
<div class="topbar-divider d-none d-sm-block"></div>
+148 -130
View File
@@ -1,16 +1,13 @@
<div>
{{-- Modal --}}
{{-- Modal: Agregar docente --}}
<div wire:ignore.self class="modal fade" id="modalDocente" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Agregar Docente al Grupo</h5>
<button type="button" class="close" data-dismiss="modal">
<span>&times;</span>
</button>
<h5 class="modal-title">Asignar Docente al Grupo</h5>
<button type="button" class="close" data-dismiss="modal"><span>&times;</span></button>
</div>
<div class="modal-body">
{{-- Docente --}}
<div class="mb-3">
<label>Docente</label>
<select class="form-control" wire:model="docente_id">
@@ -18,30 +15,23 @@
@foreach ($docentes as $docente)
@php $usr = $docente->docentes->first() @endphp
<option value="{{ $docente->id }}">
{{ $usr?->name }}
{{ $usr?->apellidoPaterno }}
{{ $usr?->apellidoPaterno }} {{ $usr?->apellidoMaterno }} {{ $usr?->name }}
</option>
@endforeach
</select>
@error('docente_id')
<span class="text-danger small">{{ $message }}</span>
@enderror
@error('docente_id')<span class="text-danger small">{{ $message }}</span>@enderror
</div>
{{-- Materia --}}
<div class="mb-3">
<label>Materia</label>
<select class="form-control" wire:model="materia_id">
<option value="">Seleccionar materia...</option>
@foreach ($materiasSelect as $materia)
<option value="{{ $materia->id }}">
ciclo {{ $materia->ciclo }} - {{ $materia->name }}
Ciclo {{ $materia->ciclo }} {{ $materia->name }}
</option>
@endforeach
</select>
@error('materia_id')
<span class="text-danger small">{{ $message }}</span>
@enderror
@error('materia_id')<span class="text-danger small">{{ $message }}</span>@enderror
</div>
</div>
<div class="modal-footer">
@@ -52,148 +42,176 @@
</div>
</div>
{{-- Tabla docentes asignados --}}
<table id="tcont2" class="table table-striped table-bordered table-hover" style="width:100%;">
<thead>
{{-- Modal: Reemplazar docente --}}
<div wire:ignore.self class="modal fade" id="modalReemplazo" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Reemplazar Docente</h5>
<button type="button" class="close" data-dismiss="modal"><span>&times;</span></button>
</div>
<div class="modal-body">
<div class="alert alert-info mb-3">
<i class="fas fa-info-circle"></i>
Materia: <strong>{{ $reemplazoMateriaNombre }}</strong><br>
<small>Las tareas ya asignadas por el docente actual se conservarán y quedarán bajo el nuevo docente.</small>
</div>
<div class="mb-3">
<label>Nuevo docente</label>
<select class="form-control" wire:model="reemplazoDocenteId">
<option value="">Seleccionar docente...</option>
@foreach ($docentes as $docente)
@php $usr = $docente->docentes->first() @endphp
@if ($docente->id != $reemplazoDocenteActualId)
<option value="{{ $docente->id }}">
{{ $usr?->apellidoPaterno }} {{ $usr?->apellidoMaterno }} {{ $usr?->name }}
</option>
@endif
@endforeach
</select>
@error('reemplazoDocenteId')<span class="text-danger small">{{ $message }}</span>@enderror
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancelar</button>
<button wire:click="reemplazar" class="btn btn-warning">
<i class="fas fa-exchange-alt fa-sm"></i> Confirmar reemplazo
</button>
</div>
</div>
</div>
</div>
{{-- Tabla materias / docentes --}}
<table id="tcont2" class="table table-striped table-bordered table-hover" style="width:100%;">
<thead>
<tr>
<th>Materia</th>
<th>Ciclo</th>
<th>Docente asignado</th>
@hasrole('Coordinación académica')
<th>Ver tareas</th>
<th>Acciones</th>
@endhasrole
</tr>
</thead>
<tbody>
@forelse ($materias as $materia)
@php
$docenteAsignado = $grupo->docentes->firstWhere('pivot.materia_id', $materia->id);
$usr = $docenteAsignado?->docentes->first();
@endphp
<tr>
<th>Id</th>
<th>Materia</th>
<th>Ciclo</th>
<th>Docente asignado</th>
<td>{{ $materia->name }}</td>
<td>{{ $materia->ciclo }}</td>
<td>
@if ($usr)
{{ $usr->apellidoPaterno }} {{ $usr->apellidoMaterno }} {{ $usr->name }}
@else
<span class="badge badge-secondary">Sin docente</span>
@endif
</td>
@hasrole('Coordinación académica')
<th>Opciones</th>
@endhasrole
{{-- Columna Ver tareas --}}
<td>
@if ($docenteAsignado)
<a href="{{ route('grupo.material.tareas', [$grupo->id, $materia->id]) }}"
class="btn btn-sm btn-info">
<i class="fas fa-tasks fa-sm"></i> Ver tareas
</a>
@else
<span class="text-muted small">Sin asignar</span>
@endif
</td>
{{-- Columna Acciones --}}
<td>
@if ($docenteAsignado)
<button
wire:click="abrirReemplazo({{ $docenteAsignado->id }}, {{ $materia->id }}, '{{ addslashes($materia->name) }}')"
class="btn btn-sm btn-warning" title="Reemplazar docente">
<i class="fas fa-exchange-alt fa-sm"></i> Reemplazar
</button>
<button
onclick="confirmarEliminar({{ $docenteAsignado->id }}, {{ $materia->id }}, '{{ $usr?->apellidoPaterno }} {{ $usr?->name }}', '{{ addslashes($materia->name) }}')"
class="btn btn-sm btn-danger" title="Quitar docente">
<i class="fas fa-user-minus fa-sm"></i>
</button>
@endif
</td>
@endhasrole
</tr>
</thead>
<tbody>
@forelse ($materias as $materia)
@php
$docenteAsignado = $grupo->docentes->firstWhere('pivot.materia_id', $materia->id);
$usr = $docenteAsignado?->docentes->first();
@endphp
<tr>
<td>{{ $materia->id }}</td>
<td>{{ $materia->name }}</td>
<td>{{ $materia->ciclo }}</td>
<td>
@if($usr)
{{ $usr->name }} {{ $usr->apellidoPaterno }}
@else
<span class="badge badge-secondary">Sin docente</span>
@endif
</td>
@hasrole('Coordinación académica')
<td>
@if($docenteAsignado)
<button
onclick="confirmarEliminar({{ $docenteAsignado->id }}, {{ $materia->id }}, '{{ $usr?->name }} {{ $usr?->apellidoPaterno }}', '{{ $materia->name }}')"
style="border:none; background:none;">
<i class="fa-regular fa-circle-user-circle-minus fa-2xl" style="color: rgb(255, 0, 0);"></i>
</button>
@endif
</td>
@endhasrole
</tr>
@empty
<tr>Sin materias en este plan.</tr>
@endforelse
</tbody>
@empty
<tr><td colspan="5">Sin materias en este plan.</td></tr>
@endforelse
</tbody>
</table>
</div>
@push('scripts')
<script>
document.addEventListener('DOMContentLoaded', initTable);
document.addEventListener('livewire:init', () => {
// ✅ Este hook es el correcto para Livewire v3
Livewire.hook('commit', ({ component, succeed }) => {
succeed(() => {
setTimeout(() => initTable(), 50);
});
succeed(() => setTimeout(() => initTable(), 50));
});
Livewire.on('open-modal-docente', () => $('#modalDocente').modal('show'));
Livewire.on('close-modal-docente', () => $('#modalDocente').modal('hide'));
Livewire.on('open-modal-reemplazo', () => $('#modalReemplazo').modal('show'));
Livewire.on('close-modal-reemplazo',() => $('#modalReemplazo').modal('hide'));
});
function confirmarEliminar(docenteId, materiaId, nombreDocente, nombreMateria) {
Swal.fire({
title: "¿Eliminar docente?",
html: `¿Deseas quitar a <b>${nombreDocente}</b> de <b>${nombreMateria}</b>?`,
icon: "warning",
showCancelButton: true,
confirmButtonText: "Sí, eliminar",
cancelButtonText: "Cancelar",
confirmButtonColor: '#e74a3b',
}).then((result) => {
if (result.isConfirmed) {
@this.eliminar(docenteId, materiaId);
}
});
}
function confirmarEliminar(docenteId, materiaId, nombreDocente, nombreMateria) {
Swal.fire({
title: "¿Quitar docente?",
html: `¿Deseas quitar a <b>${nombreDocente}</b> de <b>${nombreMateria}</b>?`,
icon: "warning",
showCancelButton: true,
confirmButtonText: "Sí, quitar",
cancelButtonText: "Cancelar",
confirmButtonColor: '#e74a3b',
}).then(result => {
if (result.isConfirmed) @this.eliminar(docenteId, materiaId);
});
}
function initTable() {
// ✅ Destroy correcto
function initTable() {
if ($.fn.DataTable.isDataTable('#tcont2')) {
$('#tcont2').DataTable().destroy();
$('#tcont2').empty(); // limpia el DOM completamente
$('#tcont2').empty();
}
new DataTable('#tcont2', {
retrieve: true, // ✅ evita el error de reinicialización
retrieve: true,
responsive: true,
order: [],
language: {
"decimal": "",
"emptyTable": "No hay información",
"info": "Mostrando _START_ a _END_ de _TOTAL_ Entradas",
"infoEmpty": "Mostrando 0 to 0 of 0 Entradas",
"infoFiltered": "(Filtrado de _MAX_ total entradas)",
"infoPostFix": "",
"thousands": ",",
"lengthMenu": "Mostrar _MENU_ Entradas",
"loadingRecords": "Cargando...",
"processing": "Procesando...",
"search": "Buscar:",
"zeroRecords": "Sin resultados encontrados"
emptyTable: "No hay información",
info: "Mostrando _START_ a _END_ de _TOTAL_ entradas",
infoEmpty: "Mostrando 0 de 0 entradas",
infoFiltered: "(filtrado de _MAX_ entradas)",
lengthMenu: "Mostrar _MENU_ entradas",
loadingRecords: "Cargando...",
processing: "Procesando...",
search: "Buscar:",
zeroRecords: "Sin resultados",
thousands: ",",
},
lengthMenu: [
[10, 25, 50, -1],
['10 filas', '25 filas', '50 filas', 'Todos']
],
lengthMenu: [[10, 25, 50, -1], ['10', '25', '50', 'Todos']],
layout: {
topStart: {
buttons: ['colvis', 'pageLength']
},
topStart: { buttons: ['colvis', 'pageLength'] },
topEnd: ['search'],
bottomStart: [{
buttons: [
{ extend: 'excel', title: 'Excel' },
{ extend: 'pdfHtml5', title: 'PDF' },
{ extend: 'print' }
]
}, 'info'],
}
bottomStart: [{ buttons: [
{ extend: 'excel', title: 'Excel' },
{ extend: 'pdfHtml5', title: 'PDF' },
{ extend: 'print' },
]}, 'info'],
},
});
$("#tcont2_wrapper").removeClass("form-inline");
$("#tcont2_wrapper").addClass("w-100");
$("#tcont2_wrapper").removeClass("form-inline").addClass("w-100");
}
document.addEventListener('livewire:init', () => {
Livewire.on('open-modal-docente', () => {
$('#modalDocente').modal('show');
});
Livewire.on('close-modal-docente', () => {
$('#modalDocente').modal('hide');
});
});
</script>
@endpush
+67
View File
@@ -0,0 +1,67 @@
@extends('layouts.landing')
@section('title', isset($tarea) ? 'Editar Tarea' : 'Nueva Tarea')
@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">
<h6 class="m-0 font-weight-bold text-primary">
{{ isset($tarea) ? 'Editar Tarea' : 'Nueva Tarea' }}
&mdash; {{ $material->name }} &middot; Grupo: {{ $grupo->clave }}
</h6>
<a href="{{ route('grupo.material.tareas', [$grupo, $material]) }}"
class="btn btn-sm btn-secondary shadow-sm">
<i class="fas fa-arrow-left fa-sm"></i> Regresar
</a>
</div>
</div>
<div class="card-body">
@if ($errors->any())
<div class="alert alert-danger">
<ul class="mb-0">
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
@if (isset($tarea))
<form action="{{ route('grupo.material.tareas.update', [$grupo, $material, $tarea]) }}" method="POST">
@method('PUT')
@else
<form action="{{ route('grupo.material.tareas.store', [$grupo, $material]) }}" method="POST">
@endif
@csrf
<div class="form-group">
<label for="titulo">Título <span class="text-danger">*</span></label>
<input type="text" id="titulo" name="titulo" class="form-control"
value="{{ old('titulo', $tarea->titulo ?? '') }}" required>
</div>
<div class="form-group">
<label for="descripcion">Descripción / Instrucciones</label>
<textarea id="descripcion" name="descripcion" class="form-control" rows="5">{{ old('descripcion', $tarea->descripcion ?? '') }}</textarea>
</div>
<div class="form-group">
<label for="fecha_entrega">Fecha límite de entrega</label>
<input type="date" id="fecha_entrega" name="fecha_entrega" class="form-control"
value="{{ old('fecha_entrega', isset($tarea) && $tarea->fecha_entrega ? $tarea->fecha_entrega : '') }}">
</div>
<button type="submit" class="btn btn-primary">
<i class="fas fa-save fa-sm"></i>
{{ isset($tarea) ? 'Guardar cambios' : 'Crear tarea' }}
</button>
</form>
</div>
</div>
</div>
@endsection
+109
View File
@@ -0,0 +1,109 @@
@extends('layouts.landing')
@section('head')
@include('layouts._partials.tablaStyle')
@endsection
@section('title', 'Tareas')
@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">
<h6 class="m-0 font-weight-bold text-primary">
Tareas &mdash; {{ $material->name }} &middot; Grupo: {{ $grupo->clave }}
</h6>
<div>
{{-- Botón regresar según rol --}}
@hasrole('Coordinación académica')
<a href="{{ route('grupo.ver.materias', $grupo) }}" class="btn btn-sm btn-secondary shadow-sm mr-2">
<i class="fas fa-arrow-left fa-sm"></i> Regresar
</a>
@else
<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.create', [$grupo, $material]) }}"
class="btn btn-sm btn-primary shadow-sm">
<i class="fas fa-plus fa-sm text-white-50"></i> Nueva Tarea
</a>
@endhasrole
</div>
</div>
</div>
<div class="card-body">
@if (session('info'))
<div class="alert alert-success"><strong>{{ session('info') }}</strong></div>
@endif
@if ($tareas->isEmpty())
<div class="alert alert-info">Aún no hay tareas para esta materia.</div>
@else
<table id="tcont" class="table table-striped table-bordered table-hover" style="width:100%">
<thead>
<tr>
<th>Título</th>
<th>Fecha límite</th>
<th>Pendientes</th>
<th>Calificadas</th>
<th>Total alumnos</th>
<th>Opciones</th>
</tr>
</thead>
<tbody>
@foreach ($tareas as $tarea)
<tr>
<td>{{ $tarea->titulo }}</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 ml-1">Vencida</span>
@else
<span class="badge badge-success ml-1">Activa</span>
@endif
@else
<span class="text-muted">Sin fecha</span>
@endif
</td>
<td><span class="badge badge-warning">{{ $tarea->pendientes_count }}</span></td>
<td><span class="badge badge-success">{{ $tarea->calificadas_count }}</span></td>
<td>{{ $tarea->entregas_count }}</td>
<td>
<a href="{{ route('grupo.material.tareas.show', [$grupo, $material, $tarea]) }}"
class="btn btn-sm btn-info">
<i class="fas fa-eye"></i> Ver
</a>
@hasrole('Docente')
<a href="{{ route('grupo.material.tareas.edit', [$grupo, $material, $tarea]) }}"
class="btn btn-sm btn-warning">
<i class="fas fa-edit"></i> Editar
</a>
<form class="delete-form d-inline"
action="{{ route('grupo.material.tareas.destroy', [$grupo, $material, $tarea]) }}"
method="POST">
@csrf @method('DELETE')
<button type="submit" class="btn btn-sm btn-danger">
<i class="fas fa-trash"></i>
</button>
</form>
@endhasrole
</td>
</tr>
@endforeach
</tbody>
</table>
@endif
</div>
</div>
</div>
@endsection
@section('scripts')
@include('layouts._partials.tablaScript')
@include('layouts._partials.delete')
@endsection
+153
View File
@@ -0,0 +1,153 @@
@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>
</div>
<div class="card-body">
@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
@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>
@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>
@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
</div>
</div>
</div>
@endsection
@section('scripts')
@include('layouts._partials.tablaScript')
<script>
document.querySelectorAll('.custom-file-input').forEach(input => {
input.addEventListener('change', function () {
const fileName = this.files[0]?.name || 'Adjuntar archivo (opcional)';
this.nextElementSibling.textContent = fileName;
});
});
</script>
@endsection
+156
View File
@@ -0,0 +1,156 @@
@extends('layouts.landing')
@section('head')
@include('layouts._partials.tablaStyle')
@endsection
@section('title', 'Detalle de Tarea')
@section('content')
<div class="container-fluid">
{{-- Info de la tarea --}}
<div class="card shadow mb-4">
<div class="card-header py-3">
<div class="d-sm-flex align-items-center justify-content-between">
<h6 class="m-0 font-weight-bold text-primary">
{{ $tarea->titulo }}
&mdash; {{ $material->name }} &middot; Grupo: {{ $grupo->clave }}
</h6>
<a href="{{ route('grupo.material.tareas', [$grupo, $material]) }}"
class="btn btn-sm btn-secondary shadow-sm">
<i class="fas fa-arrow-left fa-sm"></i> Regresar
</a>
</div>
</div>
<div class="card-body">
@if (session('info'))
<div class="alert alert-success"><strong>{{ session('info') }}</strong></div>
@endif
@if ($tarea->descripcion)
<p class="mb-2"><strong>Instrucciones:</strong> {{ $tarea->descripcion }}</p>
@endif
@if ($tarea->fecha_entrega)
<p class="mb-0">
<strong>Fecha límite:</strong>
{{ \Carbon\Carbon::parse($tarea->fecha_entrega)->format('d/m/Y') }}
@if (\Carbon\Carbon::parse($tarea->fecha_entrega)->isPast())
<span class="badge badge-danger ml-1">Vencida</span>
@else
<span class="badge badge-success ml-1">Activa</span>
@endif
</p>
@endif
</div>
</div>
{{-- Entregas de alumnos --}}
<div class="card shadow mb-4">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">Entregas de alumnos</h6>
</div>
<div class="card-body table-responsive">
<table id="tcont" class="table table-striped table-bordered table-hover" style="width:100%">
<thead>
<tr>
<th>Alumno</th>
<th>Status</th>
<th>Entrega</th>
@hasrole('Docente')
<th>Calificar / Calificación</th>
@endhasrole
@hasrole('Coordinación académica')
<th>Calificación</th>
@endhasrole
</tr>
</thead>
<tbody>
@foreach ($alumnos as $alumno)
@php $entrega = $entregas[$alumno->id] ?? null; @endphp
<tr>
<td class="align-middle">
{{ $alumno->alumnos->first()?->apellidoPaterno }}
{{ $alumno->alumnos->first()?->apellidoMaterno }}
{{ $alumno->alumnos->first()?->name }}
</td>
<td class="align-middle">
@if (!$entrega || $entrega->status === 'sin_subir')
<span class="badge badge-secondary">Sin subir</span>
@elseif ($entrega->status === 'pendiente')
<span class="badge badge-warning">Pendiente</span>
@else
<span class="badge badge-success">Calificada</span>
@endif
</td>
<td class="align-middle" style="max-width:280px">
@if ($entrega && ($entrega->texto || $entrega->archivo))
@if ($entrega->texto)
<div class="border rounded p-2 bg-light mb-1"
style="max-height:100px;overflow-y:auto;font-size:.85rem;white-space:pre-wrap;">{{ $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-file fa-sm"></i> Ver archivo
</a>
@endif
@else
<span class="text-muted small">Sin entrega</span>
@endif
</td>
{{-- Docente: puede calificar --}}
@hasrole('Docente')
<td class="align-middle" style="min-width:300px">
@if ($entrega && in_array($entrega->status, ['pendiente', 'calificada']))
<form action="{{ route('tarea.calificar', [$tarea, $alumno]) }}" method="POST"
class="d-flex align-items-center flex-wrap" style="gap:4px">
@csrf
<input type="number" name="calificacion" min="0" max="10" step="0.1"
class="form-control form-control-sm" style="width:80px"
placeholder="010"
value="{{ $entrega->status === 'calificada' ? $entrega->calificacion : '' }}"
required>
<input type="text" name="comentario" class="form-control form-control-sm"
style="width:140px" placeholder="Comentario"
value="{{ $entrega->comentario ?? '' }}">
<button type="submit"
class="btn btn-sm {{ $entrega->status === 'calificada' ? 'btn-warning' : 'btn-success' }}">
<i class="fas fa-{{ $entrega->status === 'calificada' ? 'redo' : 'check' }} fa-sm"></i>
{{ $entrega->status === 'calificada' ? 'Recalificar' : 'Calificar' }}
</button>
</form>
@else
<span class="text-muted small">Sin entrega aún</span>
@endif
</td>
@endhasrole
{{-- Coordinación: solo lectura de calificación --}}
@hasrole('Coordinación académica')
<td class="align-middle">
@if ($entrega?->status === 'calificada')
<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>
@endhasrole
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
@endsection
@section('scripts')
@include('layouts._partials.tablaScript')
@endsection