Agregar archivos de proyecto.

This commit is contained in:
2025-12-25 09:54:32 -06:00
parent 42d745e5e1
commit be7d5ef10d
99 changed files with 85993 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
@*
For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
*@
@{
}

View File

@@ -0,0 +1,745 @@
@model ExpedienteViewModel
@{
Layout = null;
ViewData["Title"] = "Expediente - " + Model.NombreCompleto;
}
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>@ViewData["Title"]</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css" rel="stylesheet">
<style>
/* Estilos generales - Tono ejecutivo */
:root {
--primary-color: #2c3e50;
--secondary-color: #3498db;
--accent-color: #e74c3c;
--success-color: #27ae60;
--light-bg: #f8f9fa;
--border-color: #dee2e6;
}
/* Estilos optimizados para impresión */
@@page {
size: A4;
margin: 15mm 20mm;
}
@@media print {
body {
font-family: 'Segoe UI', 'Roboto', Arial, sans-serif;
color: #000;
background: white;
font-size: 11pt;
line-height: 1.4;
margin: 0;
padding: 0;
}
.no-print, .print-controls {
display: none !important;
}
.container {
box-shadow: none !important;
margin: 0 !important;
padding: 0 !important;
max-width: 100% !important;
}
.document-header {
margin-bottom: 15px !important;
padding: 15px 0 !important;
}
.document-header h1 {
font-size: 22px !important;
}
.section {
margin-bottom: 15px !important;
page-break-inside: avoid;
break-inside: avoid;
}
.section-title {
font-size: 14px !important;
padding: 8px 15px !important;
margin: -15px -15px 10px -15px !important;
}
.photo {
width: 100px !important;
height: 100px !important;
}
.info-grid {
gap: 10px !important;
}
.info-item {
margin-bottom: 8px !important;
padding-bottom: 8px !important;
}
.info-label {
font-size: 10px !important;
}
.info-value {
font-size: 11px !important;
padding-left: 18px !important;
}
.row {
display: block !important;
margin: 0 !important;
}
.col {
padding: 0 !important;
min-width: 100% !important;
margin-bottom: 10px !important;
}
.photo-container {
margin: 10px 0 !important;
padding: 10px !important;
}
.document-footer {
margin-top: 20px !important;
font-size: 9px !important;
}
/* Eliminar todos los saltos de página automáticos */
h1, h2, h3, h4, h5, .section, .section-title, .info-grid, .info-item {
page-break-inside: avoid !important;
page-break-after: avoid !important;
page-break-before: avoid !important;
}
/* Permitir saltos de página solo donde sea necesario */
.page-break {
page-break-before: always;
}
}
/* Estilos para pantalla */
body {
font-family: 'Segoe UI', 'Roboto', Arial, sans-serif;
max-width: 210mm;
margin: 0 auto;
background-color: #f5f7fa;
color: #333;
min-height: 100vh;
}
.container {
background: white;
border-radius: 10px;
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.1);
margin: 20px auto;
padding: 30px;
position: relative;
overflow: hidden;
}
/* Botones de control - NO se imprimen */
.print-controls {
background: linear-gradient(135deg, #2c3e50 0%, #4a6491 100%);
padding: 15px;
border-radius: 10px 10px 0 0;
margin-bottom: 20px;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 6px;
font-weight: 600;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 8px;
transition: all 0.3s ease;
font-size: 14px;
}
.btn-primary {
background: linear-gradient(135deg, #3498db 0%, #2980b9 100%);
color: white;
}
.btn-primary:hover {
background: linear-gradient(135deg, #2980b9 0%, #1f639e 100%);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(52, 152, 219, 0.3);
}
.btn-secondary {
background: linear-gradient(135deg, #95a5a6 0%, #7f8c8d 100%);
color: white;
}
.btn-secondary:hover {
background: linear-gradient(135deg, #7f8c8d 0%, #6c7b7d 100%);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(149, 165, 166, 0.3);
}
.btn-info {
background: linear-gradient(135deg, #27ae60 0%, #219653 100%);
color: white;
}
.btn-info:hover {
background: linear-gradient(135deg, #219653 0%, #1e8449 100%);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(39, 174, 96, 0.3);
}
/* Cabecera del documento - REVISADO para impresión */
.document-header {
text-align: center;
padding: 20px 0;
margin-bottom: 20px;
border-bottom: 2px solid var(--primary-color);
position: relative;
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
border-radius: 8px;
overflow: hidden;
page-break-inside: avoid;
}
.document-header::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 3px;
background: linear-gradient(90deg, var(--secondary-color), var(--success-color));
}
.document-header h1 {
margin: 0 0 8px 0;
font-size: 24px;
color: var(--primary-color);
font-weight: 700;
letter-spacing: 0.5px;
page-break-after: avoid;
}
.document-subtitle {
color: var(--secondary-color);
margin: 0 0 5px 0;
font-size: 13px;
font-weight: 500;
}
.document-meta {
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 20px;
margin-top: 10px;
color: #666;
font-size: 11px;
page-break-inside: avoid;
}
.meta-item {
display: flex;
align-items: center;
gap: 5px;
white-space: nowrap;
}
/* Secciones del documento - REVISADO para impresión */
.section {
margin-bottom: 20px;
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 15px;
background: white;
position: relative;
overflow: hidden;
page-break-inside: avoid;
break-inside: avoid;
}
.section::before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 4px;
background: linear-gradient(to bottom, var(--secondary-color), var(--success-color));
}
.section-title {
background: linear-gradient(135deg, var(--light-bg), #e9ecef);
padding: 10px 15px;
margin: -15px -15px 15px -15px;
border-bottom: 1px solid var(--border-color);
font-weight: 600;
color: var(--primary-color);
font-size: 15px;
display: flex;
align-items: center;
gap: 8px;
page-break-after: avoid;
}
.section-title i {
color: var(--secondary-color);
}
/* Layout de información */
.info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 15px;
}
.info-item {
margin-bottom: 12px;
padding-bottom: 12px;
border-bottom: 1px dashed #eee;
page-break-inside: avoid;
}
.info-item:last-child {
border-bottom: none;
}
.info-label {
font-weight: 600;
color: var(--primary-color);
margin-bottom: 4px;
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.3px;
display: flex;
align-items: center;
gap: 6px;
}
.info-label i {
color: var(--secondary-color);
font-size: 12px;
}
.info-value {
color: #444;
font-size: 13px;
line-height: 1.5;
padding-left: 20px;
}
/* Foto del estudiante - REVISADO para impresión */
.photo-container {
text-align: center;
margin: 15px 0;
padding: 12px;
background: linear-gradient(135deg, #f8f9fa, #e9ecef);
border-radius: 8px;
border: 1px solid var(--border-color);
}
.photo-frame {
display: inline-block;
padding: 6px;
background: white;
border-radius: 10px;
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1);
border: 2px solid var(--secondary-color);
}
.photo {
width: 120px;
height: 120px;
border-radius: 50%;
object-fit: cover;
border: 3px solid white;
}
/* Layout de columnas - REVISADO para impresión */
.row {
display: flex;
flex-wrap: wrap;
margin: 0 -10px;
page-break-inside: avoid;
}
.col {
flex: 1;
padding: 0 10px;
min-width: 280px;
page-break-inside: avoid;
}
/* Badges */
.badge {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 5px 10px;
border-radius: 15px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.3px;
}
.badge-male {
background: linear-gradient(135deg, #3498db, #2980b9);
color: white;
}
.badge-female {
background: linear-gradient(135deg, #e74c3c, #c0392b);
color: white;
}
.status-active {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 5px 12px;
background: linear-gradient(135deg, #27ae60, #219653);
color: white;
border-radius: 15px;
font-size: 11px;
font-weight: 600;
}
.status-active::before {
content: '';
width: 6px;
height: 6px;
background: white;
border-radius: 50%;
display: inline-block;
}
/* Pie de documento */
.document-footer {
margin-top: 30px;
padding-top: 15px;
border-top: 1px solid var(--border-color);
text-align: center;
color: #666;
font-size: 10px;
line-height: 1.5;
page-break-before: avoid;
}
.footer-note {
font-style: italic;
color: #999;
margin-top: 8px;
padding: 8px;
background: var(--light-bg);
border-radius: 4px;
border-left: 3px solid var(--secondary-color);
font-size: 9px;
}
/* Watermark solo para impresión */
@@media print {
.watermark {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) rotate(-45deg);
font-size: 60px;
color: rgba(0, 0, 0, 0.03);
z-index: -1;
white-space: nowrap;
font-weight: bold;
opacity: 0.5;
}
}
.watermark {
display: none;
}
/* Responsive para pantalla */
@@media (max-width: 768px) {
.container {
padding: 15px;
margin: 10px;
}
.print-controls {
flex-direction: column;
gap: 10px;
}
.col {
min-width: 100%;
}
.document-header h1 {
font-size: 20px;
}
.photo {
width: 100px;
height: 100px;
}
.info-grid {
grid-template-columns: 1fr;
}
}
/* Clase especial para evitar problemas de impresión */
.keep-together {
page-break-inside: avoid !important;
break-inside: avoid !important;
}
/* Ajuste específico para la primera sección */
.first-section {
page-break-after: avoid !important;
}
</style>
</head>
<body>
<!-- Watermark solo visible al imprimir -->
<div class="watermark">CONFIDENCIAL - MIE SYSTEM</div>
<!-- Controles de impresión - NO se imprimen -->
<div class="print-controls no-print">
<div>
<button onclick="window.history.back()" class="btn btn-secondary">
<i class="bi bi-arrow-left"></i> Regresar
</button>
</div>
<div style="display: flex; gap: 10px;">
<button onclick="window.print()" class="btn btn-primary">
<i class="bi bi-printer"></i> Imprimir Documento
</button>
<button onclick="downloadPDF()" class="btn btn-info">
<i class="bi bi-download"></i> Guardar como PDF
</button>
</div>
</div>
<!-- Contenedor principal del documento -->
<div class="container keep-together">
<!-- Cabecera del documento -->
<div class="document-header keep-together">
<h1>EXPEDIENTE MIE SYSTEM</h1>
<p class="document-subtitle">Sistema Integral de Gestión Educativa</p>
<div class="document-meta">
<div class="meta-item">
<i class="bi bi-calendar"></i>
<span>Generado: @DateTime.Now.ToString("dd/MM/yyyy HH:mm")</span>
</div>
<div class="meta-item">
<i class="bi bi-hash"></i>
<span>ID Expediente: @Model.Id</span>
</div>
<div class="meta-item">
<i class="bi bi-shield-check"></i>
<span>Documento Oficial</span>
</div>
</div>
</div>
<!-- Información Personal -->
<div class="section first-section keep-together">
<div class="section-title">
<i class="bi bi-person-badge"></i> Información Personal
</div>
<div class="row keep-together">
<div class="col">
<div class="info-grid">
<div class="info-item">
<div class="info-label">
<i class="bi bi-person"></i> Nombre Completo
</div>
<div class="info-value">@Model.NombreCompleto</div>
</div>
<div class="info-item">
<div class="info-label">
<i class="bi bi-calendar-event"></i> Fecha de Nacimiento
</div>
<div class="info-value">@Model.FechaNacimiento.ToString("dd/MM/yyyy")</div>
</div>
<div class="info-item">
<div class="info-label">
<i class="bi bi-calculator"></i> Edad
</div>
<div class="info-value">@Model.Edad años</div>
</div>
<div class="info-item">
<div class="info-label">
<i class="bi bi-gender-ambiguous"></i> Sexo
</div>
<div class="info-value">
@if (Model.Sexo == "M")
{
<span class="badge badge-male">
<i class="bi bi-gender-male"></i> Masculino
</span>
}
else
{
<span class="badge badge-female">
<i class="bi bi-gender-female"></i> Femenino
</span>
}
</div>
</div>
</div>
</div>
<div class="col">
<div class="photo-container">
<div class="photo-frame">
<img src="@(string.IsNullOrEmpty(Model.FotoUrl) ? "/images/default-avatar.png" : Model.FotoUrl)"
alt="Fotografía del estudiante"
class="photo">
</div>
<p style="margin-top: 8px; font-size: 11px; color: #666;">
<i class="bi bi-camera"></i> Fotografía actual
</p>
</div>
</div>
</div>
<div class="info-grid keep-together">
<div class="info-item">
<div class="info-label">
<i class="bi bi-geo-alt"></i> Dirección
</div>
<div class="info-value">@Model.Direccion</div>
</div>
<div class="info-item">
<div class="info-label">
<i class="bi bi-telephone"></i> Teléfono de Contacto
</div>
<div class="info-value">@(string.IsNullOrEmpty(Model.Telefono) ? "No especificado" : Model.Telefono)</div>
</div>
</div>
</div>
<!-- Información de Padres y Responsable -->
<div class="row keep-together">
<div class="col">
<div class="section keep-together">
<div class="section-title">
<i class="bi bi-people"></i> Información de los Padres
</div>
<div class="info-grid">
<div class="info-item">
<div class="info-label">
<i class="bi bi-person-standing"></i> Padre
</div>
<div class="info-value">@(string.IsNullOrEmpty(Model.NombrePadre) ? "No especificado" : Model.NombrePadre)</div>
</div>
<div class="info-item">
<div class="info-label">
<i class="bi bi-person-standing-dress"></i> Madre
</div>
<div class="info-value">@(string.IsNullOrEmpty(Model.NombreMadre) ? "No especificado" : Model.NombreMadre)</div>
</div>
</div>
</div>
</div>
<div class="col">
<div class="section keep-together">
<div class="section-title">
<i class="bi bi-person-heart"></i> Responsable a Cargo
</div>
<div class="info-grid">
<div class="info-item">
<div class="info-label">
<i class="bi bi-person-check"></i> Nombre del Responsable
</div>
<div class="info-value">@Model.NombreResponsable</div>
</div>
<div class="info-item">
<div class="info-label">
<i class="bi bi-diagram-3"></i> Parentesco
</div>
<div class="info-value">@(string.IsNullOrEmpty(Model.ParentescoResponsable) ? "Responsable" : Model.ParentescoResponsable)</div>
</div>
</div>
</div>
</div>
</div>
<!-- Observaciones -->
@if (!string.IsNullOrEmpty(Model.Observaciones))
{
<div class="section keep-together">
<div class="section-title">
<i class="bi bi-chat-left-text"></i> Observaciones y Notas
</div>
<div class="info-value" style="white-space: pre-line; background: #f8f9fa; padding: 12px; border-radius: 5px; border-left: 3px solid var(--secondary-color); font-size: 12px;">
@Model.Observaciones
</div>
</div>
}
<!-- Información del Expediente -->
<div class="section keep-together">
<div class="section-title">
<i class="bi bi-folder-check"></i> Información del Expediente
</div>
<div class="info-grid">
<div class="info-item">
<div class="info-label">
<i class="bi bi-hash"></i> ID del Expediente
</div>
<div class="info-value">@Model.Id</div>
</div>
<div class="info-item">
<div class="info-label">
<i class="bi bi-check-circle"></i> Estado
</div>
<div class="info-value">
<span class="status-active">ACTIVO</span>
</div>
</div>
<div class="info-item">
<div class="info-label">
<i class="bi bi-clock-history"></i> Última Actualización
</div>
<div class="info-value">@DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss")</div>
</div>
</div>
</div>
<!-- Pie de documento -->
<div class="document-footer keep-together">
<p><strong>MIE SYSTEM</strong> - Sistema Integral de Gestión Educativa</p>
<p>Este documento ha sido generado automáticamente y es de carácter oficial.</p>
<div class="footer-note">
<i class="bi bi-shield-exclamation"></i> Documento confidencial - Uso exclusivo institucional
</div>
</div>
</div>
<script>
// Función para simular descarga de PDF
function downloadPDF() {
alert('Funcionalidad de descarga de PDF en desarrollo.\nPor ahora, use la opción "Imprimir" y seleccione "Guardar como PDF" en el diálogo de impresión.');
}
// Prevenir que la página se cierre automáticamente
window.onbeforeunload = null;
</script>
</body>
</html>

View File

@@ -0,0 +1,647 @@
@{
ViewData["Title"] = "Expedientes";
ViewData["ActionButtons"] = @"<button class='btn btn-primary' data-bs-toggle='modal' data-bs-target='#createModal'>
<i class='bi bi-plus-circle me-1'></i> Nuevo Expediente
</button>";
}
<div class="row mb-4">
<!-- Tarjeta de total de niños registrados -->
<div class="col-md-4">
<div class="card border-primary">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<h5 class="card-title text-primary">Total de Niños</h5>
<h2 class="text-primary" id="totalNinos">0</h2>
<p class="card-text text-muted">Registrados en el sistema</p>
</div>
<div class="bg-primary text-white rounded-circle p-3">
<i class="bi bi-people-fill" style="font-size: 2rem;"></i>
</div>
</div>
</div>
</div>
</div>
<!-- Tarjeta de cumpleaños del mes -->
<div class="col-md-4">
<div class="card border-success">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<h5 class="card-title text-success">Cumpleaños del Mes</h5>
<h2 class="text-success" id="cumpleanerosMes">0</h2>
<p class="card-text text-muted">Cumplen años este mes</p>
</div>
<div class="bg-success text-white rounded-circle p-3">
<i class="bi bi-balloon-fill" style="font-size: 2rem;"></i>
</div>
</div>
</div>
</div>
</div>
<!-- Tarjeta de mayores de 14 años -->
<div class="col-md-4">
<div class="card border-warning">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<h5 class="card-title text-warning">Mayores de 14 años</h5>
<h2 class="text-warning" id="mayores14">0</h2>
<p class="card-text text-muted">Niños con 14+ años</p>
</div>
<div class="bg-warning text-white rounded-circle p-3">
<i class="bi bi-person-badge" style="font-size: 2rem;"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Tabla de expedientes -->
<div class="card">
<div class="card-header">
<div class="d-flex justify-content-between align-items-center">
<h5 class="mb-0">Lista de Expedientes</h5>
<div class="input-group" style="max-width: 300px;">
<input type="text" class="form-control" placeholder="Buscar niño..." id="searchInput">
<button class="btn btn-outline-secondary" type="button" id="searchButton">
<i class="bi bi-search"></i>
</button>
</div>
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover" id="expedientesTable">
<thead>
<tr>
<th>Foto</th>
<th>Nombre Completo</th>
<th>Edad</th>
<th>Sexo</th>
<th>Fecha Nacimiento</th>
<th>Responsable</th>
<th>Acciones</th>
</tr>
</thead>
<tbody id="expedientesTableBody">
<!-- Los datos se cargarán dinámicamente -->
</tbody>
</table>
</div>
<!-- Paginación -->
<nav aria-label="Page navigation" class="mt-3">
<ul class="pagination justify-content-center" id="pagination">
<!-- La paginación se generará dinámicamente -->
</ul>
</nav>
</div>
</div>
<!-- Modal para crear nuevo expediente -->
<div class="modal fade" id="createModal" tabindex="-1" aria-labelledby="createModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="createModalLabel">Nuevo Expediente</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
@await Html.PartialAsync("_CreateOrEdit", new ExpedienteViewModel())
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
<button type="button" class="btn btn-primary" id="saveExpediente">Guardar</button>
</div>
</div>
</div>
</div>
<!-- Modal para editar expediente -->
<div class="modal fade" id="editModal" tabindex="-1" aria-labelledby="editModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="editModalLabel">Editar Expediente</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body" id="editModalBody">
<!-- El contenido se cargará dinámicamente -->
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
<button type="button" class="btn btn-primary" id="updateExpediente">Actualizar</button>
</div>
</div>
</div>
</div>
@section Scripts {
<script>
// Variables globales
let currentPage = 1;
const itemsPerPage = 10;
let allExpedientes = [];
$(document).ready(function() {
// Cargar datos iniciales
loadDashboardStats();
loadExpedientes();
// Configurar búsqueda
$('#searchButton').click(searchExpedientes);
$('#searchInput').on('keyup', function(e) {
if (e.key === 'Enter') {
searchExpedientes();
}
});
// Configurar guardar expediente
$('#saveExpediente').click(saveExpediente);
$('#updateExpediente').click(updateExpediente);
// Configurar subida de imagen independiente
$(document).on('change', '#Foto, #FotoEdit', function() {
const input = this;
const isEdit = $(input).attr('id') === 'FotoEdit';
const previewId = isEdit ? 'fotoPreviewEdit' : 'fotoPreview';
if (input.files && input.files[0]) {
// Mostrar vista previa inmediata
previewImage(input, previewId);
// Subir imagen al servidor
uploadImage(input.files[0], isEdit);
}
});
// Configurar botones de eliminar imagen
$(document).on('click', '#deleteFoto, #deleteFotoEdit', function() {
const isEdit = $(this).attr('id') === 'deleteFotoEdit';
deleteImage(isEdit);
});
});
// Cargar estadísticas del dashboard
function loadDashboardStats() {
$.ajax({
url: '/Expedientes/GetDashboardStats',
type: 'GET',
success: function(data) {
$('#totalNinos').text(data.totalNinos);
$('#cumpleanerosMes').text(data.cumpleanerosMes);
$('#mayores14').text(data.mayores14);
}
});
}
// Cargar expedientes
function loadExpedientes(page = 1) {
currentPage = page;
$.ajax({
url: `/Expedientes/GetExpedientes?page=${page}&pageSize=${itemsPerPage}`,
type: 'GET',
success: function(data) {
allExpedientes = data.items;
renderExpedientesTable(data.items);
renderPagination(data.totalItems, data.totalPages, page);
}
});
}
// Renderizar tabla de expedientes
function renderExpedientesTable(expedientes) {
const tbody = $('#expedientesTableBody');
tbody.empty();
if (expedientes.length === 0) {
tbody.append(`
<tr>
<td colspan="7" class="text-center py-4">
<i class="bi bi-folder-x" style="font-size: 3rem; color: #6c757d;"></i>
<p class="mt-2">No se encontraron expedientes</p>
</td>
</tr>
`);
return;
}
expedientes.forEach(expediente => {
const edad = calculateAge(expediente.fechaNacimiento);
const row = `
<tr>
<td>
<div class="avatar avatar-sm">
<img src="${expediente.fotoUrl || '/images/default-avatar.png'}"
alt="${expediente.nombre}"
class="rounded-circle"
style="width: 40px; height: 40px; object-fit: cover;">
</div>
</td>
<td>
<strong>${expediente.nombre} ${expediente.apellidos}</strong>
</td>
<td>${edad} años</td>
<td>
<span class="badge ${expediente.sexo === 'M' ? 'bg-info' : 'bg-pink'}">
${expediente.sexo === 'M' ? 'Masculino' : 'Femenino'}
</span>
</td>
<td>${formatDate(expediente.fechaNacimiento)}</td>
<td>${expediente.nombreResponsable}</td>
<td>
<div class="btn-group" role="group">
<button class="btn btn-sm btn-outline-primary" onclick="viewExpediente(${expediente.id})" title="Ver">
<i class="bi bi-eye"></i>
</button>
<button class="btn btn-sm btn-outline-warning" onclick="editExpediente(${expediente.id})" title="Editar">
<i class="bi bi-pencil"></i>
</button>
<button class="btn btn-sm btn-outline-danger" onclick="deleteExpediente(${expediente.id})" title="Eliminar">
<i class="bi bi-trash"></i>
</button>
</div>
</td>
</tr>
`;
tbody.append(row);
});
}
// Renderizar paginación
function renderPagination(totalItems, totalPages, currentPage) {
const pagination = $('#pagination');
pagination.empty();
// Botón anterior
pagination.append(`
<li class="page-item ${currentPage === 1 ? 'disabled' : ''}">
<a class="page-link" href="#" onclick="loadExpedientes(${currentPage - 1})" aria-label="Previous">
<span aria-hidden="true">&laquo;</span>
</a>
</li>
`);
// Números de página
for (let i = 1; i <= totalPages; i++) {
pagination.append(`
<li class="page-item ${i === currentPage ? 'active' : ''}">
<a class="page-link" href="#" onclick="loadExpedientes(${i})">${i}</a>
</li>
`);
}
// Botón siguiente
pagination.append(`
<li class="page-item ${currentPage === totalPages ? 'disabled' : ''}">
<a class="page-link" href="#" onclick="loadExpedientes(${currentPage + 1})" aria-label="Next">
<span aria-hidden="true">&raquo;</span>
</a>
</li>
`);
}
// Buscar expedientes
function searchExpedientes() {
const searchTerm = $('#searchInput').val().toLowerCase();
if (searchTerm.trim() === '') {
loadExpedientes(1);
return;
}
const filtered = allExpedientes.filter(exp =>
exp.nombre.toLowerCase().includes(searchTerm) ||
exp.apellidos.toLowerCase().includes(searchTerm) ||
exp.nombreResponsable.toLowerCase().includes(searchTerm)
);
renderExpedientesTable(filtered);
$('#pagination').empty(); // Limpiar paginación en búsqueda
}
// Guardar nuevo expediente
function saveExpediente() {
const formData = new FormData($('#createForm')[0]);
$.ajax({
url: '/Expedientes/Create',
type: 'POST',
data: formData,
processData: false,
contentType: false,
success: function(response) {
// Mostrar siempre el mensaje que viene en response.message
const message = response.message ||
(response.success ? 'Operación completada' : 'Error en la operación');
if (response.success) {
$('#createModal').modal('hide');
loadDashboardStats();
loadExpedientes(currentPage);
showAlert('success', message);
} else {
showAlert('error', message);
}
},
error: function(xhr) {
// Extraer el message del JSON si existe
let message = 'Error en la solicitud';
try {
const jsonResponse = JSON.parse(xhr.responseText);
if (jsonResponse && jsonResponse.message) {
message = jsonResponse.message;
}
} catch (e) {
// Si no es JSON válido, usar el texto de respuesta
if (xhr.responseText) {
message = xhr.responseText;
}
}
showAlert('error', message);
}
});
}
// Editar expediente
function editExpediente(id) {
$.ajax({
url: `/Expedientes/Edit/${id}`,
type: 'GET',
success: function(data) {
$('#editModalBody').html(data);
$('#editModal').modal('show');
// Mostrar/ocultar botón de eliminar imagen según si hay imagen personalizada
const fotoUrl = $('#editForm input[name="FotoUrl"]').val();
const deleteBtn = $('#deleteFotoEdit');
if (fotoUrl && fotoUrl !== '/images/default-avatar.png') {
deleteBtn.show();
} else {
deleteBtn.hide();
}
}
});
}
// Actualizar expediente
function updateExpediente() {
const formData = new FormData($('#editForm')[0]);
const id = $('#editForm').data('id');
$.ajax({
url: `/Expedientes/Edit/${id}`,
type: 'POST',
data: formData,
processData: false,
contentType: false,
success: function(response) {
if (response.success) {
$('#editModal').modal('hide');
loadDashboardStats();
loadExpedientes(currentPage);
showAlert('success', 'Expediente actualizado exitosamente');
} else {
showAlert('error', response.message || 'Error al actualizar expediente');
}
},
error: function() {
showAlert('error', 'Error al procesar la solicitud');
}
});
}
// Ver expediente (detalle)
function viewExpediente(id) {
window.location.href = `/Expedientes/Details/${id}`;
}
// Eliminar expediente
function deleteExpediente(id) {
if (confirm('¿Está seguro de eliminar este expediente?')) {
$.ajax({
url: `/Expedientes/Delete/${id}`,
type: 'DELETE',
success: function(response) {
if (response.success) {
loadDashboardStats();
loadExpedientes(currentPage);
showAlert('success', 'Expediente eliminado exitosamente');
} else {
showAlert('error', response.message || 'Error al eliminar expediente');
}
}
});
}
}
// ========== FUNCIONES PARA MANEJO DE IMÁGENES ==========
// Mostrar vista previa de imagen
function previewImage(input, previewId) {
const preview = document.getElementById(previewId);
const file = input.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function(e) {
preview.src = e.target.result;
preview.style.display = 'block';
}
reader.readAsDataURL(file);
}
}
// Subir imagen al servidor
function uploadImage(file, isEdit = false) {
const formData = new FormData();
formData.append('file', file);
// Mostrar indicador de carga
const previewId = isEdit ? 'fotoPreviewEdit' : 'fotoPreview';
const preview = document.getElementById(previewId);
const originalOpacity = preview.style.opacity;
preview.style.opacity = '0.5';
$.ajax({
url: '/Expedientes/UploadImage',
type: 'POST',
data: formData,
processData: false,
contentType: false,
success: function(response) {
preview.style.opacity = originalOpacity;
if (response.success) {
// Actualizar el campo oculto con la URL de la imagen
const formId = isEdit ? 'editForm' : 'createForm';
const hiddenInput = $(`#${formId} input[name="FotoUrl"]`);
if (hiddenInput.length === 0) {
// Crear campo oculto si no existe
$(`#${formId}`).append(`<input type="hidden" name="FotoUrl" value="${response.imageUrl}" />`);
} else {
// Actualizar valor existente
hiddenInput.val(response.imageUrl);
}
// Mostrar botón de eliminar
const deleteBtnId = isEdit ? 'deleteFotoEdit' : 'deleteFoto';
$(`#${deleteBtnId}`).show();
showAlert('success', 'Imagen subida exitosamente');
} else {
showAlert('error', response.message || 'Error al subir imagen');
// Restaurar imagen predeterminada
preview.src = '/images/default-avatar.png';
// Ocultar botón de eliminar
const deleteBtnId = isEdit ? 'deleteFotoEdit' : 'deleteFoto';
$(`#${deleteBtnId}`).hide();
}
},
error: function(xhr) {
preview.style.opacity = originalOpacity;
let message = 'Error en la solicitud';
try {
const jsonResponse = JSON.parse(xhr.responseText);
if (jsonResponse && jsonResponse.message) {
message = jsonResponse.message;
}
} catch (e) {
if (xhr.responseText) {
message = xhr.responseText;
}
}
showAlert('error', message);
// Restaurar imagen predeterminada
preview.src = '/images/default-avatar.png';
// Ocultar botón de eliminar
const deleteBtnId = isEdit ? 'deleteFotoEdit' : 'deleteFoto';
$(`#${deleteBtnId}`).hide();
}
});
}
// Eliminar imagen
function deleteImage(isEdit = false) {
const formId = isEdit ? 'editForm' : 'createForm';
const hiddenInput = $(`#${formId} input[name="FotoUrl"]`);
const imageUrl = hiddenInput.val();
const previewId = isEdit ? 'fotoPreviewEdit' : 'fotoPreview';
const deleteBtnId = isEdit ? 'deleteFotoEdit' : 'deleteFoto';
if (imageUrl && imageUrl !== '/images/default-avatar.png') {
if (confirm('¿Está seguro de eliminar esta imagen?')) {
$.ajax({
url: '/Expedientes/DeleteImage',
type: 'DELETE',
data: { imageUrl: imageUrl },
success: function(response) {
if (response.success) {
// Restaurar imagen predeterminada
$(`#${previewId}`).attr('src', '/images/default-avatar.png');
hiddenInput.val('/images/default-avatar.png');
$(`#${deleteBtnId}`).hide();
showAlert('success', 'Imagen eliminada exitosamente');
} else {
showAlert('error', response.message || 'Error al eliminar imagen');
}
},
error: function() {
showAlert('error', 'Error al procesar la solicitud');
}
});
}
}
}
// ========== FUNCIONES UTILITARIAS ==========
// Calcular edad
function calculateAge(birthDate) {
const today = new Date();
const birth = new Date(birthDate);
let age = today.getFullYear() - birth.getFullYear();
const monthDiff = today.getMonth() - birth.getMonth();
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birth.getDate())) {
age--;
}
return age;
}
// Formatear fecha
function formatDate(dateString) {
const date = new Date(dateString);
return date.toLocaleDateString('es-ES', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
}
// Mostrar alerta
function showAlert(type, message) {
// Crear elemento de alerta
const alertDiv = document.createElement('div');
alertDiv.className = `alert alert-${type} alert-dismissible fade show position-fixed`;
alertDiv.style.cssText = 'top: 20px; right: 20px; z-index: 1050; min-width: 300px;';
alertDiv.innerHTML = `
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
`;
// Agregar al body
document.body.appendChild(alertDiv);
// Auto-eliminar después de 5 segundos
setTimeout(() => {
if (alertDiv.parentNode) {
alertDiv.remove();
}
}, 5000);
}
</script>
<style>
.bg-pink {
background-color: #e83e8c !important;
}
.avatar {
display: inline-flex;
align-items: center;
justify-content: center;
}
.table th {
font-weight: 600;
color: #495057;
border-bottom: 2px solid #dee2e6;
}
.table td {
vertical-align: middle;
}
.badge {
font-size: 0.75rem;
padding: 0.35em 0.65em;
}
.btn-group .btn {
padding: 0.25rem 0.5rem;
font-size: 0.875rem;
}
.modal-lg {
max-width: 800px;
}
</style>
}

View File

@@ -0,0 +1,176 @@
@using MieSystem.Models;
@model ExpedienteViewModel
<form id="@(ViewData["IsEdit"] != null && (bool)ViewData["IsEdit"] ? "editForm" : "createForm")"
method="post"
enctype="multipart/form-data"
data-id="@(Model?.Id ?? 0)">
@if (ViewData["IsEdit"] != null && (bool)ViewData["IsEdit"])
{
<input type="hidden" asp-for="Id" />
}
<!-- Campo oculto para almacenar la URL de la imagen subida -->
<input type="hidden" asp-for="FotoUrl" />
<div class="row">
<!-- Columna izquierda - Datos personales -->
<div class="col-md-6">
<div class="card mb-3">
<div class="card-header bg-light">
<h6 class="mb-0">Datos Personales</h6>
</div>
<div class="card-body">
<!-- Foto -->
<div class="mb-3 text-center">
<div class="mb-2">
<img id="@(ViewData["IsEdit"] != null && (bool)ViewData["IsEdit"] ? "fotoPreviewEdit" : "fotoPreview")"
src="@(Model?.FotoUrl ?? "/images/default-avatar.png")"
alt="Foto del niño"
class="rounded-circle border"
style="width: 120px; height: 120px; object-fit: cover;">
</div>
<div class="d-flex justify-content-center align-items-center gap-2">
<label class="btn btn-sm btn-outline-primary">
<i class="bi bi-camera me-1"></i> Seleccionar Foto
<input type="file"
asp-for="Foto"
id="@(ViewData["IsEdit"] != null && (bool)ViewData["IsEdit"] ? "FotoEdit" : "Foto")"
class="d-none"
accept="image/*">
</label>
<!-- Botón para eliminar imagen -->
<button type="button"
class="btn btn-sm btn-outline-danger"
id="@(ViewData["IsEdit"] != null && (bool)ViewData["IsEdit"] ? "deleteFotoEdit" : "deleteFoto")"
style="display: @((Model?.FotoUrl != null && Model.FotoUrl != "/images/default-avatar.png") ? "block" : "none");">
<i class="bi bi-trash me-1"></i> Eliminar
</button>
</div>
<div class="mt-2">
<small class="text-muted">Formatos permitidos: JPG, PNG, GIF, BMP. Tamaño máximo: 5MB</small>
</div>
<span asp-validation-for="Foto" class="text-danger"></span>
</div>
<!-- Nombre -->
<div class="mb-3">
<label asp-for="Nombre" class="form-label">Nombre *</label>
<input asp-for="Nombre" class="form-control" placeholder="Ingrese el nombre">
<span asp-validation-for="Nombre" class="text-danger"></span>
</div>
<!-- Apellidos -->
<div class="mb-3">
<label asp-for="Apellidos" class="form-label">Apellidos *</label>
<input asp-for="Apellidos" class="form-control" placeholder="Ingrese los apellidos">
<span asp-validation-for="Apellidos" class="text-danger"></span>
</div>
<!-- Fecha de Nacimiento -->
<div class="mb-3">
<label asp-for="FechaNacimiento" class="form-label">Fecha de Nacimiento *</label>
<input asp-for="FechaNacimiento" type="date" class="form-control">
<span asp-validation-for="FechaNacimiento" class="text-danger"></span>
</div>
<!-- Sexo -->
<div class="mb-3">
<label asp-for="Sexo" class="form-label">Sexo *</label>
<div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" asp-for="Sexo" value="M" id="sexoM">
<label class="form-check-label" for="sexoM">Masculino</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" asp-for="Sexo" value="F" id="sexoF">
<label class="form-check-label" for="sexoF">Femenino</label>
</div>
</div>
<span asp-validation-for="Sexo" class="text-danger"></span>
</div>
</div>
</div>
</div>
<!-- Columna derecha - Datos familiares y dirección -->
<div class="col-md-6">
<div class="card mb-3">
<div class="card-header bg-light">
<h6 class="mb-0">Datos Familiares</h6>
</div>
<div class="card-body">
<!-- Nombre del Padre -->
<div class="mb-3">
<label asp-for="NombrePadre" class="form-label">Nombre del Padre</label>
<input asp-for="NombrePadre" class="form-control" placeholder="Ingrese nombre del padre">
<span asp-validation-for="NombrePadre" class="text-danger"></span>
</div>
<!-- Nombre de la Madre -->
<div class="mb-3">
<label asp-for="NombreMadre" class="form-label">Nombre de la Madre</label>
<input asp-for="NombreMadre" class="form-control" placeholder="Ingrese nombre de la madre">
<span asp-validation-for="NombreMadre" class="text-danger"></span>
</div>
<!-- Nombre del Responsable -->
<div class="mb-3">
<label asp-for="NombreResponsable" class="form-label">Nombre del Responsable *</label>
<input asp-for="NombreResponsable" class="form-control" placeholder="Ingrese nombre del responsable">
<span asp-validation-for="NombreResponsable" class="text-danger"></span>
</div>
<!-- Parentesco del Responsable -->
<div class="mb-3">
<label asp-for="ParentescoResponsable" class="form-label">Parentesco del Responsable</label>
<select asp-for="ParentescoResponsable" class="form-select">
<option value="">Seleccione parentesco</option>
<option value="Padre">Padre</option>
<option value="Madre">Madre</option>
<option value="Abuelo">Abuelo</option>
<option value="Abuela">Abuela</option>
<option value="Tío">Tío</option>
<option value="Tía">Tía</option>
<option value="Hermano">Hermano</option>
<option value="Hermana">Hermana</option>
<option value="Otro">Otro</option>
</select>
<span asp-validation-for="ParentescoResponsable" class="text-danger"></span>
</div>
</div>
</div>
<div class="card">
<div class="card-header bg-light">
<h6 class="mb-0">Dirección y Contacto</h6>
</div>
<div class="card-body">
<!-- Dirección -->
<div class="mb-3">
<label asp-for="Direccion" class="form-label">Dirección *</label>
<textarea asp-for="Direccion" class="form-control" rows="3" placeholder="Ingrese dirección completa"></textarea>
<span asp-validation-for="Direccion" class="text-danger"></span>
</div>
<!-- Teléfono -->
<div class="mb-3">
<label asp-for="Telefono" class="form-label">Teléfono de Contacto</label>
<input asp-for="Telefono" class="form-control" placeholder="Ingrese número telefónico">
<span asp-validation-for="Telefono" class="text-danger"></span>
</div>
<!-- Observaciones -->
<div class="mb-3">
<label asp-for="Observaciones" class="form-label">Observaciones</label>
<textarea asp-for="Observaciones" class="form-control" rows="2" placeholder="Observaciones adicionales"></textarea>
<span asp-validation-for="Observaciones" class="text-danger"></span>
</div>
</div>
</div>
</div>
</div>
</form>

View File

@@ -0,0 +1,8 @@
@{
ViewData["Title"] = "Home Page";
}
<div class="text-center">
<h1 class="display-4">Welcome</h1>
<p>Learn about <a href="https://learn.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>

View File

@@ -0,0 +1,6 @@
@{
ViewData["Title"] = "Privacy Policy";
}
<h1>@ViewData["Title"]</h1>
<p>Use this page to detail your site's privacy policy.</p>

View File

@@ -0,0 +1,25 @@
@model ErrorViewModel
@{
ViewData["Title"] = "Error";
}
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>

View File

@@ -0,0 +1,406 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - MieSystem</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
<link rel="stylesheet" href="~/MieSystem.styles.css" asp-append-version="true" />
<style>
/* Estilos personalizados para el menú lateral */
body {
display: flex;
flex-direction: column;
min-height: 100vh;
}
.main-wrapper {
display: flex;
flex: 1;
}
.sidebar {
width: 250px;
background: linear-gradient(180deg, #2c3e50 0%, #1a2530 100%);
color: white;
transition: all 0.3s;
min-height: calc(100vh - 73px); /* Altura total menos el header */
}
.sidebar.collapsed {
margin-left: -250px;
}
.sidebar-header {
padding: 20px;
background-color: rgba(0, 0, 0, 0.2);
text-align: center;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.sidebar-header h3 {
font-size: 1.2rem;
margin-bottom: 0;
color: #ecf0f1;
}
.sidebar-menu {
list-style: none;
padding: 20px 0;
margin: 0;
}
.sidebar-menu li {
position: relative;
}
.sidebar-menu a {
display: flex;
align-items: center;
padding: 12px 20px;
color: #bdc3c7;
text-decoration: none;
transition: all 0.3s;
border-left: 3px solid transparent;
}
.sidebar-menu a:hover {
color: white;
background-color: rgba(255, 255, 255, 0.05);
border-left: 3px solid #3498db;
}
.sidebar-menu a.active {
color: white;
background-color: rgba(52, 152, 219, 0.2);
border-left: 3px solid #3498db;
}
.sidebar-menu i {
margin-right: 10px;
font-size: 1.2rem;
width: 20px;
text-align: center;
}
.content-wrapper {
flex: 1;
padding: 20px;
background-color: #f8f9fa;
overflow-y: auto;
}
.navbar-brand {
font-weight: 600;
}
.toggle-sidebar-btn {
background: none;
border: none;
color: #2c3e50;
font-size: 1.5rem;
cursor: pointer;
margin-right: 15px;
}
.user-info {
display: flex;
align-items: center;
color: #2c3e50;
font-weight: 500;
}
.user-info i {
margin-right: 8px;
font-size: 1.2rem;
}
/* Responsive
media (max-width: 768px) {
.sidebar
{
position: fixed;
z-index: 1000;
height: calc(100vh - 73px);
}*/
.sidebar.collapsed {
margin-left: -250px;
}
.content-wrapper {
width: 100%;
}
.overlay {
display: none;
position: fixed;
top: 73px;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 999;
}
.overlay.active {
display: block;
}
}
/* Estilos para la página de contenido */
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
padding-bottom: 15px;
border-bottom: 1px solid #dee2e6;
}
.page-title {
font-size: 1.8rem;
font-weight: 600;
color: #2c3e50;
margin-bottom: 0;
}
.card {
border: none;
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
margin-bottom: 20px;
}
.card-header {
background-color: white;
border-bottom: 1px solid #eaeaea;
font-weight: 600;
color: #2c3e50;
padding: 15px 20px;
}
.footer {
margin-top: auto;
background-color: #2c3e50;
color: white;
}
.footer a {
color: #bdc3c7;
}
.footer a:hover {
color: white;
}
</style>
</head>
<body>
<header>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow">
<div class="container-fluid">
<button class="toggle-sidebar-btn" id="toggleSidebar">
<i class="bi bi-list"></i>
</button>
<a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">
<i class="bi bi-house-heart-fill text-primary me-2"></i>MieSystem
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<div class="user-info">
<i class="bi bi-person-circle"></i>
<span id="username-placeholder">Usuario</span>
</div>
</li>
</ul>
</div>
</div>
</nav>
</header>
<div class="main-wrapper">
<!-- Menú lateral -->
<div class="sidebar" id="sidebar">
<div class="sidebar-header">
<h3><i class="bi bi-gear-wide-connected me-2"></i>Panel de Control</h3>
</div>
<ul class="sidebar-menu">
<li>
<a asp-area="" asp-controller="Home" asp-action="Index" class="nav-link @(ViewContext.RouteData.Values["Controller"]?.ToString() == "Home" && ViewContext.RouteData.Values["Action"]?.ToString() == "Index" ? "active" : "")">
<i class="bi bi-house-door"></i> Inicio
</a>
</li>
<li>
<a asp-area="" asp-controller="Expedientes" asp-action="Index" class="nav-link @(ViewContext.RouteData.Values["Controller"]?.ToString() == "Expedientes" ? "active" : "")">
<i class="bi bi-folder"></i> Expedientes
</a>
</li>
<li>
<a asp-area="" asp-controller="Asistencia" asp-action="Index" class="nav-link @(ViewContext.RouteData.Values["Controller"]?.ToString() == "Asistencia" ? "active" : "")">
<i class="bi bi-calendar-check"></i> Asistencia
</a>
</li>
<!-- Separador y sección de administración -->
<li class="mt-4">
<div class="sidebar-header">
<h6 class="text-uppercase text-muted small">Administración</h6>
</div>
</li>
<li>
<a href="#" class="nav-link">
<i class="bi bi-people"></i> Niños
</a>
</li>
<li>
<a href="#" class="nav-link">
<i class="bi bi-person-badge"></i> Maestros
</a>
</li>
<li>
<a href="#" class="nav-link">
<i class="bi bi-person-workspace"></i> Personal
</a>
</li>
<li>
<a href="#" class="nav-link">
<i class="bi bi-bar-chart"></i> Reportes
</a>
</li>
<li>
<a asp-area="" asp-controller="Home" asp-action="Privacy" class="nav-link @(ViewContext.RouteData.Values["Controller"]?.ToString() == "Home" && ViewContext.RouteData.Values["Action"]?.ToString() == "Privacy" ? "active" : "")">
<i class="bi bi-shield-check"></i> Privacidad
</a>
</li>
</ul>
</div>
<!-- Overlay para móviles -->
<div class="overlay" id="overlay"></div>
<!-- Contenido principal -->
<div class="content-wrapper" id="contentWrapper">
<div class="page-header">
<h1 class="page-title">@ViewData["Title"]</h1>
@if (ViewData["ActionButtons"] != null)
{
<div>
@Html.Raw(ViewData["ActionButtons"])
</div>
}
</div>
<main role="main">
@RenderBody()
</main>
</div>
</div>
<footer class="footer text-muted">
<div class="container py-3">
<div class="row">
<div class="col-md-6">
&copy; 2025 - MieSystem - Sistema de Gestión Educativa
</div>
<div class="col-md-6 text-md-end">
<a asp-area="" asp-controller="Home" asp-action="Privacy" class="text-decoration-none me-3">Privacidad</a>
<a href="#" class="text-decoration-none">Ayuda</a>
</div>
</div>
</div>
</footer>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
<script>
// Control del menú lateral
document.addEventListener('DOMContentLoaded', function() {
const sidebar = document.getElementById('sidebar');
const toggleBtn = document.getElementById('toggleSidebar');
const overlay = document.getElementById('overlay');
const contentWrapper = document.getElementById('contentWrapper');
// Función para alternar el menú lateral
function toggleSidebar() {
sidebar.classList.toggle('collapsed');
overlay.classList.toggle('active');
// Guardar estado en localStorage
const isCollapsed = sidebar.classList.contains('collapsed');
localStorage.setItem('sidebarCollapsed', isCollapsed);
}
// Evento para el botón de alternar
toggleBtn.addEventListener('click', toggleSidebar);
// Evento para el overlay (en móviles)
overlay.addEventListener('click', toggleSidebar);
// Cargar estado guardado
const savedState = localStorage.getItem('sidebarCollapsed');
if (savedState === 'true') {
sidebar.classList.add('collapsed');
}
// Cerrar menú lateral en móviles al hacer clic en un enlace
const sidebarLinks = document.querySelectorAll('.sidebar-menu a');
sidebarLinks.forEach(link => {
link.addEventListener('click', function() {
if (window.innerWidth < 768) {
toggleSidebar();
}
});
});
// Actualizar nombre de usuario (ejemplo)
const usernameElement = document.getElementById('username-placeholder');
// Aquí puedes obtener el nombre de usuario real desde tu backend
// Por ahora, usaremos un nombre de ejemplo
const userName = localStorage.getItem('userName') || 'Administrador';
usernameElement.textContent = userName;
// Detectar controlador activo para resaltar menú
const currentPath = window.location.pathname;
const menuItems = document.querySelectorAll('.sidebar-menu a');
menuItems.forEach(item => {
const href = item.getAttribute('href');
if (href && currentPath.includes(href.replace('~', ''))) {
item.classList.add('active');
}
});
});
// Manejo responsive
window.addEventListener('resize', function() {
const sidebar = document.getElementById('sidebar');
const overlay = document.getElementById('overlay');
if (window.innerWidth >= 768) {
// En pantallas grandes, asegurarse que el menú esté visible
sidebar.classList.remove('collapsed');
overlay.classList.remove('active');
} else {
// En pantallas pequeñas, si el menú no está colapsado, mostrar overlay
if (!sidebar.classList.contains('collapsed')) {
overlay.classList.add('active');
}
}
});
</script>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>

View File

@@ -0,0 +1,48 @@
/* Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification
for details on configuring this project to bundle and minify static web assets. */
a.navbar-brand {
white-space: normal;
text-align: center;
word-break: break-all;
}
a {
color: #0077cc;
}
.btn-primary {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
.nav-pills .nav-link.active, .nav-pills .show > .nav-link {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
.border-top {
border-top: 1px solid #e5e5e5;
}
.border-bottom {
border-bottom: 1px solid #e5e5e5;
}
.box-shadow {
box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
}
button.accept-policy {
font-size: 1rem;
line-height: inherit;
}
.footer {
position: absolute;
bottom: 0;
width: 100%;
white-space: nowrap;
line-height: 60px;
}

View File

@@ -0,0 +1,2 @@
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.min.js"></script>

View File

@@ -0,0 +1,3 @@
@using MieSystem
@using MieSystem.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

View File

@@ -0,0 +1,3 @@
@{
Layout = "_Layout";
}