Alejandro Rosales 1 年之前
父节点
当前提交
2e00fbec20
共有 13 个文件被更改,包括 619 次插入927 次删除
  1. 3 4
      action/action_auditoria.php
  2. 1 0
      action/action_facultad.php
  3. 34 4
      action/carrera.php
  4. 31 0
      action/nivel.php
  5. 93 778
      carreras.php
  6. 9 2
      css/sgi.css
  7. 0 133
      horario_profesor.php
  8. 44 0
      js/carreras.js
  9. 44 0
      js/periodos.js
  10. 220 0
      periodos.php
  11. 14 6
      puestos.php
  12. 63 0
      ts/carreras.ts
  13. 63 0
      ts/periodos.ts

+ 3 - 4
action/action_auditoria.php

@@ -36,8 +36,8 @@ try {
                     horario_fecha_fin,
                     horario_grupo,
                     horario_hora,
-                    periodo_fecha_inicio,
-                    periodo_fecha_fin,
+                    PERIODO.periodo_fecha_inicio,
+                    PERIODO.periodo_fecha_fin,
                     salon,
                     materia_nombre as materia,
                     carrera_nombre as carrera,
@@ -49,10 +49,9 @@ try {
                 JOIN carrera USING (carrera_id)
                 JOIN nivel USING (nivel_id)
                 JOIN facultad ON facultad.facultad_id = carrera.facultad_id
-                JOIN PERIODO_CARRERA USING (carrera_id)
                 JOIN PERIODO USING (periodo_id)
                 JOIN SALON USING (salon_id)
-                WHERE (periodo_id, facultad.facultad_id) = (:periodo_id, COALESCE(:facultad_id, facultad.facultad_id))
+                WHERE (PERIODO.periodo_id, facultad.facultad_id) = (:periodo_id, COALESCE(:facultad_id, facultad.facultad_id))
             ),
             fechas AS (
                 SELECT fechas_clase(h.horario_id, true) as registro_fecha_ideal, h.horario_id  

+ 1 - 0
action/action_facultad.php

@@ -30,6 +30,7 @@ try {
                 SELECT facultad_nombre, facultad_id, clave_dependencia
                 FROM facultad
                 WHERE facultad_id = :facultad_id OR :facultad_id IS NULL
+                ORDER BY facultad_nombre ASC
                 SQL
             ,
             [':facultad_id' => $user->facultad['facultad_id']]

+ 34 - 4
action/carrera.php

@@ -1,5 +1,4 @@
 <?php
-
 require_once "{$_SERVER['DOCUMENT_ROOT']}/class/c_login.php";
 header('Content-Type: application/json');
 
@@ -15,13 +14,44 @@ try {
         case 'GET':
             // Fetch all puestos
             $facultad_id = $user->facultad['facultad_id'];
+            $periodo_nivel_id = $db->where('periodo_id', $user->periodo_id)->getOne('periodo', 'nivel_id')['nivel_id'];
             $carreras = $db->query(<<<SQL
-                SELECT carrera_id, carrera_nombre, clave_carrera, facultad_id
+                SELECT carrera_id, carrera_nombre, clave_carrera,
+                facultad_id, facultad_nombre,
+                nivel_id, nivel_nombre
                 FROM carrera
-                WHERE facultad_id = :facultad_id OR :facultad_id IS NULL
-            SQL, ['facultad_id' => $facultad_id]);
+                join nivel using (nivel_id)
+                join facultad using (facultad_id)
+                WHERE facultad_id = :facultad_id OR :facultad_id IS NULL AND
+                nivel.nivel_id = :periodo_nivel_id
+                ORDER BY facultad_nombre, carrera_nombre    
+            SQL, [
+                'facultad_id' => $facultad_id,
+                'periodo_nivel_id' => $periodo_nivel_id
+            ]);
             echo json_encode($carreras);
             break;
+
+        case 'PUT':
+            // Update carrera {nivel_id}
+            $raw = file_get_contents('php://input');
+            $data = json_decode($raw, true);
+
+            if (!isset($data['carrera_id'], $data['nivel_id'])) {
+                header('HTTP/1.1 400 Bad Request');
+                echo json_encode(['error' => 'Falta el id de la carrera o el nivel']);
+                exit();
+            }
+
+            $carrera_id = $data['carrera_id'];
+            $nivel_id = $data['nivel_id'];
+            $db->where('carrera_id', $carrera_id)->update('carrera', ['nivel_id' => $nivel_id]);
+
+            $carrera_nombre = $db->where('carrera_id', $carrera_id)->getOne('carrera', 'carrera_nombre')['carrera_nombre'];
+            $nivel_nombre = $db->where('nivel_id', $nivel_id)->getOne('nivel', 'nivel_nombre')['nivel_nombre'];
+
+            echo json_encode(['success' => "$carrera_nombre actualizada a $nivel_nombre"]);
+            break;
         default:
             header('HTTP/1.1 405 Method Not Allowed');
             echo json_encode(['error' => 'Método no permitido']);

+ 31 - 0
action/nivel.php

@@ -0,0 +1,31 @@
+<?php
+
+require_once "{$_SERVER['DOCUMENT_ROOT']}/class/c_login.php";
+header('Content-Type: application/json');
+
+if (!Login::is_logged()) {
+    header('HTTP/1.1 401 Unauthorized');
+    echo json_encode(['error' => 'No se ha iniciado sesión']);
+    exit();
+}
+$user = Login::get_user();
+
+try {
+    switch ($_SERVER['REQUEST_METHOD']) {
+        case 'GET':
+            // Fetch all puestos
+            $nivel = $db->get('nivel');
+            echo json_encode($nivel);
+            break;
+        default:
+            header('HTTP/1.1 405 Method Not Allowed');
+            echo json_encode(['error' => 'Método no permitido']);
+            break;
+    }
+} catch (PDOException $e) {
+    echo json_encode([
+        'error' => $e->getMessage(),
+        'query' => $db->getLastQuery(),
+        'exception' => $e->getTraceAsString()
+    ]);
+}

+ 93 - 778
carreras.php

@@ -1,809 +1,124 @@
-<?php
-require_once 'class/c_login.php';
-require_once 'include/bd_pdo.php';
-
-$user = Login::get_user();
-
-$user->access('facultades');
-if($user->acceso == null){
-    header('Location: main.php?error=1');
-}else{
-    $user->print_to_log('Carreras');
-}
-if($user->facultad['facultad_id']!=$_GET['facultad']){
-    header('Location: carreras.php?facultad='.$user->facultad['facultad_id']);
-    $mal=true;
-}
-$mal=false;
-?>
 <!DOCTYPE html>
 <html lang="en">
 
 <head>
     <meta charset="UTF-8">
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <title>Carreras</title>
-    <link rel="stylesheet" href="css/jquery-ui.css">
-    <link rel="stylesheet" href="css/calendar.css">
-    <link rel="stylesheet" href="css/toggle.css" type="text/css">
+    <title>Auditoría asistencial</title>
     <?php
     include 'import/html_css_files.php';
     ?>
+    <link rel="stylesheet" type="text/css" href="https://unpkg.com/trix@2.0.0/dist/trix.css">
+    <style>
+        [v-cloak] {
+            display: none;
+        }
+    </style>
+    <script src="js/jquery.min.js"></script>
+    <script src="js/jquery-ui.js"></script>
+    <script src="js/bootstrap/bootstrap.min.js"></script>
 </head>
 
 <body>
-    <?php
-    if(isset($_GET['facultad'])){
-        $facultad=query("SELECT facultad_nombre FROM facultad WHERE facultad_id = :facultad", array(":facultad" => $_GET['facultad']), true);
-        $fs_carreras = query(
-            "SELECT * FROM fs_carreras(:idfacultad, null, null)",
-            array(':idfacultad' => $_GET['facultad']),
-            single:false
-        );
-    }
+    <?
+    $redirect = $_SERVER['PHP_SELF'];
     include "import/html_header.php";
-    html_header(
-        "CARRERAS | " . $facultad['facultad_nombre'],
-        "Gestión de Checador "
-    );
-    $user->access('facultades');
-    
-
-    $fs_niveles = query(
-        "SELECT * FROM nivel", null, false
-    );
+    global $user;
 
-    $fs_periodos = query(
-        "SELECT * FROM fs_periodos(:idfacultad) WHERE estado = 'Activo' ",
-        array(':idfacultad' => $_GET['facultad']),
-        false
-    );
-
-    $fs_tiempoLic = query(
-        "SELECT * FROM fs_tiempo_checado(:idfacultad, 1)",
-        array(':idfacultad' => $_GET['facultad']),
-        true
-    );
-    $fs_tiempoPos = query(
-        "SELECT * FROM fs_tiempo_checado(:idfacultad, 2)",
-        array(':idfacultad' => $_GET['facultad']),
-        true
+    html_header(
+        "Carreras",
+        "Sistema de gestión de checador",
     );
     ?>
-    <main class="content marco">
-        <?php #if($mal==true){ ?>
-            <div class="row">
-                <div class="col-12 text-left">
-                    <a href="facultades.php" title="Volver">
-                        <button type="button" class="btn btn-outline-secondary"><span class="ing-regresar ing-fw"></span>Volver</button>
-                    </a>
-                </div>
-            </div>
-        <?php #} ?>
-        <div id="message"></div>
-        <ul class="nav nav-tabs mt-3" id="myTab" role="tablist">
-            <li class="nav-item" role="presentation">
-                <button class="nav-link active" id="periodo-tab" data-toggle="tab" data-target="#periodo" type="button" role="tab" aria-controls="periodo" aria-selected="true">Periodo</button>
-            </li>
-            <li class="nav-item" role="presentation">
-                <button class="nav-link" id="carrera-tab" data-toggle="tab" data-target="#carrera" type="button" role="tab" aria-controls="carrera" aria-selected="false">Carrera</button>
-            </li>
-            <li class="nav-item" role="presentation">
-                <button class="nav-link" id="carrera-tab" data-toggle="tab" data-target="#tiempos" type="button" role="tab" aria-controls="tiempos" aria-selected="false">Tiempos</button>
-            </li>
-        </ul>
-        <div class="tab-content" id="myTabContent">
-            <!-- PERIODOS -->
-            
-            <div class="tab-pane fade show active" id="periodo" role="tabpanel" aria-labelledby="periodo-tab">
-                <div class="row mt-3">
-                    <?php if($user->acceso == 'w')  {?>
-                    <div class="col-12 text-right">
-                        <button type="button" class="btn btn-outline-secondary" data-toggle="modal" data-target="#modal_periodo" data-tipo="1"><span class="ing-mas ing-fw"></span>Agregar periodo</button>
-                    </div>
-                    <?php }?>
-                </div>
-                <!-- Tabla -->
-                <div class="row mt-3">
-                    <div class="col-12 table-responsive">
-                        <table class="table table-sm table-striped table-white">
-                            <thead class="thead-dark">
-                                <tr>
-                                    <th>Estado</th>
-                                    <th>Nivel</th>
-                                    <th>Periodo</th>
-                                    <th>Inicio</th>
-                                    <th>Fin</th>
-                                    <?php if($user->acceso == 'w')  {?>
-                                    <th>Acciones</th>
-                                    <?php }?>
-                                </tr>
-                            </thead>
-                            <tbody>
-                                <?php foreach($fs_periodos as $periodo){
-                                    $title=$periodo['estado'];
-                                    if($title=='Activo')
-                                        $color="success";
-                                    else
-                                        $color="danger";
-                                    ?>
-                                <tr data-id="<?= $periodo['id']?>" id="<?= $periodo['id']?>" >
-                                    <td class="text-<?= $color ?> text-center" title="<?= $title?>">
-                                        <span class="ing-bullet"></span>
-                                    </td>
-                                    <td class="text-primary">
-                                        <?= $periodo['nivel']?>
-                                    </td>
-                                    <td class="text-primary">
-                                        <?= $periodo['periodo']?>
-                                    </td>
-                                    <td class="text-primary">
-                                        <?= $periodo['inicio']?>
-                                    </td>
-                                    <td class="text-primary">
-                                        <?= $periodo['fin']?>
-                                    </td>
-                                    <?php if($user->acceso == 'w')  {?>
-                                    <td class="text-center icono-acciones">
-                                        <a href="#" data-toggle="modal" data-target="#modal_periodo" data-tipo="2" title="Editar"><span class="ing-editar ing-fw"></span></a>
-                                    </td>
-                                    <?php }?>
-                                </tr>
-                                <?php } ?>
-                            </tbody>
-                        </table>
-                    </div>
-                </div>
-            </div>
-            <!-- CARRERAS -->
-            <div class="tab-pane fade" id="carrera" role="tabpanel" aria-labelledby="carrera-tab">
-                <div class="row mt-3">
-                    <?php if($user->acceso == 'w')  {?>
-                    <div class="col-12 text-right">
-                        <button type="button" class="btn btn-outline-secondary" data-toggle="modal" data-target="#modal" data-tipo="1"><span class="ing-mas ing-fw"></span>Crear carrera</button>
-                    </div>
-                    <?php }?>
-                </div>
-                <!-- Tabla -->
-                <div class="row mt-3">
-                    <div class="col-12 table-responsive">
-                        <table class="table table-sm table-striped table-white">
-                            <thead class="thead-dark">
-                                <tr>
-                                    <th>Estado</th>
-                                    <th>Nivel</th>
-                                    <th>Carrera</th>
-                                    <?php if($user->acceso == 'w')  {?>
-                                    <th>Acciones</th>
-                                    <?php }?>
-                                </tr>
-                            </thead>
-                            <tbody>
-                                <?php
-                                foreach($fs_carreras as $carrera){
-                                    $color = "danger";
-                                    $title = "Inactiva";
-                                    if($carrera["carrera_activa"]==1){
-                                    $color ="success";
-                                    $title="Activa";
-                                    }
-                                    $nivel='Licenciatura';
-                                    if($carrera['nivel_id']==2)
-                                        $nivel='Posgrado';
-                                ?>
-                                <tr data-id="<?php echo $carrera['carrera_id'];?>" id="<?php echo $carrera['carrera_id'];?>">
-                                    <td class="text-<?php echo $color;?> text-center" title="<?php echo $title;?>">
-                                        <span class="ing-bullet"></span>
-                                    </td>
-                                    <td class="text-primary"><?php echo $nivel;?></td>
-                                    <td class="text-primary"><?php echo $carrera["carrera_nombre"];?></td>
-                                    <?php if($user->acceso == 'w')  {?>
-                                    <td class="text-center icono-acciones">
-                                        <a href="#" data-toggle="modal" data-target="#modal" data-tipo="2" title="Editar"><span class="ing-editar ing-fw"></span></a>
-                                    </td>
-                                    <?php }?>
-                                </tr>
-                                <?php }?>
-                            </tbody>
-                        </table>
-                    </div>
-                </div>
-            </div>
-            <!-- Tiempos -->
-            <div class="tab-pane fade" id="tiempos" role="tabpanel" aria-labelledby="tiempos-tab">
-                <p class="mt-4">Asigna los minutos de tolerancia antes y después del horario de clase</p>
-                
-                <form action="" method="post" id="formaModalTiempos" onsubmit="return valida_camposT()">
-                    <input type="hidden" name="facultadT" id="facultadT">
-                    <h3 class="text-center">Licenciatura</h3>
-                <div class="row mt-3" style="border:solid 2px; border-radius: 8px;">
-                    <div class="offset-1 col-2 text-center">
-                        Antes
-                        <input id="antesL" name="antesL" type="number" class="form-control text-center" maxlenth="10">
-                            <div class="invalid-feedback">
-                                Debe ser un numero mayor que 0
-                            </div>
-                            min 
-                        </div>
-                        <div class="col-2 text-center bg-light mt-4 mb-4">
-                            <h5 class="mt-2">Hora de clase</h5>
-                        </div>
-                        <div class="col-2 text-center">
-                            Despues
-                            <input id="despuesL" name="despuesL" type="number" class="form-control text-center" maxlenth="10">
-                            <div class="invalid-feedback">
-                                Debe ser un numero mayor que 0
-                            </div>
-                            min
-                        </div>
-                        <div class="col-2 text-center retardoLic">
-                            Retardos
-                            <input id="retardoL" name="retardoL" type="number" class="form-control text-center" maxlenth="10">
-                            <div class="invalid-feedback">
-                                Debe ser un numero mayor que 0
-                            </div>
-                            min
-                        </div>
-                        <div class="col-3 text-center">
-                            ¿Tiene retardos?<br>
-                            <div class="custom-control custom-switch mt-2">
-                                <input type="checkbox" class="custom-control-input tipo-switch"  name="retardoLic" id="retardoLic" value="1">
-                                <label class="custom-control-label" for="retardoLic">Si</label>
-                            </div>
-                        </div>
-                    </div>
-                    <br><br>
-                    <h3 class="text-center">Posgrado</h3>
-                    <div class="row mt-3" style="border:solid 2px; border-radius: 8px;">
-                        <div class="offset-1 col-2 text-center">
-                            Antes
-                            <input id="antesP" name="antesP" type="number" class="form-control text-center" maxlenth="10">
-                            <div class="invalid-feedback">
-                                Debe ser un numero mayor que 0
-                            </div>
-                            min
-                        </div>
-                        <div class="col-2 text-center bg-light mt-4 mb-4">
-                            <h5 class="mt-2">Hora de clase</h5>
-                        </div>
-                        <div class="col-2 text-center">
-                            Despues
-                            <input id="despuesP" name="despuesP" type="number" class="form-control text-center" maxlenth="10">
-                            <div class="invalid-feedback">
-                                Debe ser un numero mayor que 0
-                            </div>
-                            min
-                        </div>
-                        <div class="col-2 text-center retardoPos">
-                            Retardo
-                            <input id="retardoP" name="retardoP" type="number" class="form-control text-center" maxlenth="10">
-                            <div class="invalid-feedback">
-                                Debe ser un numero mayor que 0
-                            </div>
-                            min
-                        </div>
-                        <div class="col-3 text-center">
-                            ¿Tiene retardos?<br>
-                            <div class="custom-control custom-switch mt-2">
-                                <input type="checkbox" class="custom-control-input tipo-switch"  name="retardoPos" id="retardoPos" value="1">
-                                <label class="custom-control-label" for="retardoPos"></label>
-                            </div>
-                        </div>
-                    </div>
-                    <br>
-                </form>
-                <div class="form-group row mt-3">
-                    <div class="offset-4 col-8">
-                        <button class="btn btn-outline-primary" id="submitBtnT">
-                            <span class="ing-aceptar ing-fw"></span> Guardar
-                        </button>
-                        <button type="reset" id="reset" class="btn btn-outline-danger" data-dismiss="modal">
-                            <span class="ing-cancelar ing-fw"></span> Limpiar
+    <main class="container-fluid px-4 my-4" id="app" v-cloak @vue:mounted="mounted" style="min-height: 70vh;">
+        <section class="row mt-4">
+            <div class="col-12 position-relative">
+                <!-- Loop for messages -->
+                <div class="toast show shadow-sm mb-3 bg-white"
+                    style="position: fixed; top: 15%; right: 1%; max-width: 300px;" role="alert" aria-live="assertive"
+                    aria-atomic="true"
+                    @vue:mounted="$('.toast').toast({delay: 5000}).toast('show').on('hidden.bs.toast', () => { message.text = '' })"
+                    v-if="message.text">
+                    <div class="toast-header">
+                        <strong class="mr-auto text-primary text-uppercase text-center w-100 px-4">
+                            {{ message.title }}
+                        </strong>
+                        <small class="text-muted">{{ message.timestamp }}</small>
+                        <button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close"
+                            @click="message.text = ''">
+                            <span aria-hidden="true">
+                                <i class="fas fa-times"></i>
+                            </span>
                         </button>
                     </div>
-                </div>
-            </div>
-        </div>
-    </main>
-    <!-- Footer -->
-    <?php
-    include "import/html_footer.php";
-    ?>
-    <!-- Modal -->
-    <div class="modal fade" id="modal_periodo" tabindex="-1" role="dialog" arialabelledby="modal" aria-hidden="true">
-        <div class="modal-dialog modal-dialog-centered" role="document">
-            <div class="modal-content">
-                <div class="modal-header">
-                    <h4 class="col-12 modal-title text-center">
-                        <span id="modalLabelP">
-                            Editar periodo
-                        </span>
-                        <button type="button" class="close text-white" data-dismiss="modal" aria-label="close">
-                            <span aria-hidden="true">&times;</span>
-                        </button>
-                    </h4>
-                </div>
-                <div class="modal-body">
-                    <form action="" method="post" id="formaModalP" onsubmit="return valida_camposP()">
-                        <input type="hidden" name="idP" id="idP">
-                        <input type="hidden" name="facultadP" id="facultadP" value="<?php echo $_GET['facultad']; ?>">
-                        <div class="form-box">
-                            <div class="form-group row">
-                                <label for="nombreP" class="col-4 col-form-label">Nombre *</label>
-                                <div class="col-8">
-                                    <input id="nombreP" name="nombreP" type="text" class="form-control" maxlength="100">
-                                    <div class="invalid-feedback" id="nombreP-error">Campo obligatorio</div>
-                                </div>
-                            </div>
-                            <div class="form-group row">
-                                <label for="inicio" class="col-4 col-form-label">Fecha de inicio *</label>
-                                <div class="col-8">
-                                    <input id="fecha_inicial" name="fecha_inicial" type="text" class="form-control date-picker" placeholder="dd/mm/aaaa" maxlength="10" required="required" readonly="">
-                                    <div class="invalid-feedback">Debe seleccionar una fecha</div>
-                                </div>
-                            </div>
-                            <div class="form-group row">
-                                <label for="fin" class="col-4 col-form-label">Fecha de fin *</label>
-                                <div class="col-8">
-                                    <input id="fecha_final" name="fecha_final" type="text" class="form-control date-picker" placeholder="dd/mm/aaaa" maxlength="10" required="required" readonly="">
-                                    <div class="invalid-feedback">Debe seleccionar una fecha</div>
-                                </div>
-                            </div>
-                            <div class="form-group row">
-                                <label for="nivelP" class="col-4 col-form-label">Nivel *</label>
-                                <div class="col-8">
-                                    <div class="datalist datalist-select mb-1 w-100">
-                                        <div class="datalist-input">Mostrar todos</div>
-                                        <span class="ing-buscar icono"></span>
-                                        <ul style="display:none">
-                                            <?php foreach($fs_niveles as $pnivel){?>
-                                                <li data-id="<?php echo $pnivel['nivel_id']?>" class="pl-4"><?php echo $pnivel['nivel_nombre'] ?></li>
-                                            <?php }?>
-                                        </ul>
-                                        <input type="hidden" id="nivelP" name="nivelP" value="">
-                                    </div>
-                                    <div class="invalid-feedback">Debe seleccionar un nivel</div>
-                                </div>
-                            </div>
-                            <div class="form-group row">
-                                <label for="estadoP" class="col-4 col-form-label">Estado *</label>
-                                <div class="col-4">
-                                    <div class="form-check form-check-inline">
-                                        <input class="form-check-input radio-lg" type="radio" id="estado_activoP" name="estadoP" value="1" checked="checked">
-                                        <label for="estado_activoP" class="col-form-label">Activo</label>
-                                    </div>
-                                </div>
-                                <div class="col-4">
-                                    <div class="form-check form-check-inline">
-                                        <input class="form-check-input radio-lg" type="radio" id="estado_inactivoP" name="estadoP" value="2">
-                                        <label for="estado_inactivoP" class="col-form-label">Inactivo</label>
-                                    </div>
-                                </div>
-                            </div>
-                            <div class="from-group row">
-                                <div class="offset-4 col-8">
-                                    <button type="submit" class="btn btn-outline-primary" id="submitBtnP" data-tipo="1">
-                                        <span class="ing-aceptar ing-fw"></span> Guardar
-                                    </button>
-                                    <button type="reset" class="btn btn-outline-danger" data-dismiss="modal">
-                                        <span class="ing-cancelar ing-fw"></span> Cancelar
-                                    </button>
-                                </div>
-                            </div>
+                    <div class="toast-body">
+                        <div :class="message.type == 'success' ? 'text-success' : 'text-danger'"><i
+                                :class="message.type == 'success' ? 'fas fa-check-circle' : 'fas fa-times-circle'"></i>
+                            {{ message.text }}
                         </div>
-                    </form>
+                    </div>
                 </div>
             </div>
-        </div>
-    </div>
-    <div class="modal fade" id="modal" tabindex="-1" role="dialog" arialabelledby="modal" aria-hidden="true">
-        <div class="modal-dialog modal-dialog-centered" role="document">
-            <div class="modal-content">
-                <div class="modal-header">
-                    <h4 class="col-12 modal-title text-center">
-                        <span id="modalLabel">
-                            Editar nombre de Carrera
-                        </span>
-                        <button type="button" class="close text-white" data-dismiss="modal" aria-label="close">
-                            <span aria-hidden="true">&times;</span>
-                        </button>
-                    </h4>
-                </div>
-                <div class="modal-body">
-                    <form action="" method="post" id="formaModal" onsubmit="return valida_campos()">
-                        <input type="hidden" name="id" id="id">
-                        <input type="hidden" name="facultad" id="facultad" value="<?php echo $_GET['facultad']; ?>">
-                        <div class="form-box">
-                            <div class="form-group row">
-                                <label for="nivel" class="col-4 col-form-label">Nivel *</label>
-                                <div class="col-8">
-                                    <div class="datalist datalist-select mb-1 w-100">
-                                        <div class="datalist-input">Mostrar todos</div>
-                                        <span class="ing-buscar icono"></span>
-                                        <ul style="display:none">
-                                            
-                                            <?php foreach($fs_niveles as $pnivel){?>
-                                                <li data-id="<?php echo $pnivel['nivel_id']?>" class="pl-4"><?php echo $pnivel['nivel_nombre'] ?></li>
-                                            <?php }?>
-                                        </ul>
-                                        <input type="hidden" id="nivel" name="nivel" value="">
-                                    </div>
-                                </div>
-                            </div>
-                            <div class="form-group row">
-                                <label for="nombre" class="col-4 col-form-label">Nombre *</label>
-                                <div class="col-8">
-                                    <input id="nombre" name="nombre" type="text" class="form-control" maxlength="100">
-                                    <div class="invalid-feedback" id="nombre-error">Campo obligatorio</div>
-                                </div>
-                            </div>
-                            <div class="form-group row">
-                                <label for="estado" class="col-4 col-form-label">Estado *</label>
-                                <div class="col-4">
-                                    <div class="form-check form-check-inline">
-                                        <input class="form-check-input radio-lg" type="radio" id="estado_activo" name="estado" value="1" checked="checked">
-                                        <label for="estado_activo" class="col-form-label">Activo</label>
-                                    </div>
-                                </div>
-                                <div class="col-4">
-                                    <div class="form-check form-check-inline">
-                                        <input class="form-check-input radio-lg" type="radio" id="estado_inactivo" name="estado" value="0">
-                                        <label for="estado_inactivo" class="col-form-label">Inactivo</label>
+        </section>
+        <div class="container">
+            <div class="row" v-for="facultad in carreras">
+                <!-- Facultad Card -->
+                <div class="card col-12 mb-4 shadow-lg">
+                    <div class="card-header bg-primary text-white">
+                        <h3 class="mb-1">{{ facultad.facultad_nombre }}</h3>
+                    </div>
+                    <div class="card-body bg-white">
+                        <div class="row justify-content-center">
+                            <!-- Loop for Carreras -->
+                            <div class="col-md-6 mb-3" v-for="carrera in facultad.carreras">
+                                <div class="card border-secondary mb-3 shadow-sm">
+                                    <div class="card-body">
+                                        <h5 class="card-title text-primary text-uppercase text-center w-100 px-4 mb-3 text-truncate text-break border-bottom border-secondary pb-2"
+                                            :title="carrera.carrera_nombre">
+                                            {{ carrera.carrera_nombre }}
+                                        </h5>
+
+                                        <!-- Dropdown for Niveles -->
+                                        <div class="dropdown">
+                                            <button class="btn btn-outline-secondary dropdown-toggle" type="button"
+                                                data-toggle="dropdown" aria-expanded="false"
+                                                @vue:mounted="$('.dropdown-toggle').dropdown()">
+                                                {{ carrera.nivel_nombre }}
+                                            </button>
+                                            <div class="dropdown-menu shadow">
+                                                <a class="dropdown-item" v-for="nivel in niveles" key="nivel.nivel_id"
+                                                    style="cursor: pointer; user-select: none;"
+                                                    @click="setNivel(carrera, nivel)"
+                                                    :class="nivel.nivel_id == carrera.nivel_id ? 'active' : ''"
+                                                    :disabled="nivel.nivel_id == carrera.nivel_id">
+                                                    {{ nivel.nivel_nombre }}
+                                                </a>
+                                            </div>
+                                        </div>
                                     </div>
                                 </div>
-                            </div>
-                            <div class="from-group row">
-                                <div class="offset-4 col-8">
-                                    <button type="submit" class="btn btn-outline-primary" id="submitBtn" data-tipo="1">
-                                        <span class="ing-aceptar ing-fw"></span> Guardar
-                                    </button>
-                                    <button type="reset" class="btn btn-outline-danger" id="reset" data-dismiss="modal">
-                                        <span class="ing-cancelar ing-fw"></span> Cancelar
-                                    </button>
-                                </div>
-                            </div>
+                            </div> <!-- End of Carreras loop -->
                         </div>
-                    </form>
+                    </div>
                 </div>
+                <!-- End of Facultad Card -->
             </div>
         </div>
-    </div>
-    <script src="js/jquery.min.js"></script>
-    <script src="js/jquery-ui.js"></script>
-    <script src="js/bootstrap/bootstrap.min.js"></script>
+    </main>
+    <?
+    include "import/html_footer.php"; ?>
+    <script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js"
+        integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj"
+        crossorigin="anonymous"></script>
+    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"
+        integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN"
+        crossorigin="anonymous"></script>
+    <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.min.js"
+        integrity="sha384-+sLIOodYLS7CIrQpBjl+C7nPvqq+FbNUBDunl/OZv93DB7Ln/533i8e/mZXLi/P+"
+        crossorigin="anonymous"></script>
     <script src="js/datalist.js"></script>
-    <script src="js/datepicker-es.js"></script>
-    <script src="js/toggle.js"></script>
-    <?php
-        require_once 'js/messages.php';
-    ?>
-    <script>
-        $('#retardoLic').change(function(){
-            if($(this).is(':checked')){
-                $('.retardoLic').show();
-            }
-            else{
-                $('.retardoLic').hide();
-                $('#retardoL').val("0");
-            }
-        });
-        $('#retardoPos').change(function(){
-            if($(this).is(':checked')){
-                $('.retardoPos').show();
-            }
-            else{
-                $('.retardoPos').hide();
-                $('#retardoP').val("0");
-            }
-        });
-        $('#reset').on('click', function(){
-            $('#antesL').val("<?= -1*($fs_tiempoLic['desde_asistencia'] ?? 0) ?>");
-            $('#despuesL').val("<?= ($fs_tiempoLic['hasta_asistencia'] ?? 1)-1 ?>");
-            $('#retardoL').val("<?= ($fs_tiempoLic['hasta_retardo'] ?? 0) - ($fs_tiempoLic['hasta_asistencia'] ?? 0) ?>");
-            $('#antesP').val("<?= -1*($fs_tiempoPos['desde_asistencia'] ?? 0) ?>");
-            $('#despuesP').val("<?= ($fs_tiempoPos['hasta_asistencia'] ?? 1)-1 ?>");
-            $('#retardoP').val("<?= ($fs_tiempoPos['hasta_retardo'] ?? 0) - ($fs_tiempoPos['hasta_asistencia'] ?? 0) ?>");
-            <?php
-                if(($fs_tiempoLic['hasta_asistencia'] ?? 0) == ($fs_tiempoLic['hasta_retardo'] ?? 0)){ ?>
-                    $('.retardoLic').hide();
-                    $('#retardoL').val("0");
-                    $('#retardoLic').prop("checked", false).change();
-                    <?php }
-                else{ ?>
-                        $('#retardoLic').prop("checked", true).change();
-                <?php }
-                if(($fs_tiempoPos['hasta_asistencia'] ?? 0) == ($fs_tiempoPos['hasta_retardo'] ?? 0)){ ?>
-                    $('.retardoPos').hide();
-                    $('#retardoP').val("0");
-                    $('#retardoPos').prop("checked", false).change();
-                <?php }
-                else{ ?>
-                        $('#retardoPos').prop("checked", true).change();
-                <?php }
-                ?>
-            $('#antesL').removeClass("is-invalid");
-            $('#despuesL').removeClass("is-invalid");
-            $('#retardoL').removeClass("is-invalid");
-            $('#antesP').removeClass("is-invalid");
-            $('#despuesP').removeClass("is-invalid");
-            $('#retardoP').removeClass("is-invalid");
-        });
-        $(".date-picker").datepicker($.datepicker.regional["es"]);
-        $(".date-picker").datepicker({
-            dateFormat: "dd/mm/yyyy",
-            changeMonth: true,
-        });
-
-        $('#submitBtnT').on('click', function(){
-            //$('#antesL').addClass("is-invalid");
-            $('#formaModalTiempos').submit();
-        });
-
-        var today = new Date();
-
-        function valida_camposT(){
-            var error=false;
-            if($('#antesL').val()==""){
-                $('#antesL').addClass("is-invalid");
-                error=true;
-            }
-            if($('#antesL').val()<0){
-                $('#antesL').addClass("is-invalid");
-                error=true;
-            }
-            if(isNaN($('#antesL').val())){
-                $('#antesL').addClass("is-invalid");
-                error=true;
-            }
-            if($('#despuesL').val()==""){
-                $('#despuesL').addClass("is-invalid");
-                error=true;
-            }
-            if($('#despuesL').val()<0){
-                $('#despuesL').addClass("is-invalid");
-                error=true;
-            }
-            if(isNaN($('#despuesL').val())){
-                $('#despuesL').addClass("is-invalid");
-                error=true;
-            }
-            if($('#retardoL').val()==""){
-                $('#retardoL').addClass("is-invalid");
-                error=true;
-            }
-            if($('#retardoL').val()<0){
-                $('#retardoL').addClass("is-invalid");
-                error=true;
-            }
-            if(isNaN($('#retardoL').val())){
-                $('#retardoL').addClass("is-invalid");
-                error=true;
-            }
-            if($('#antesP').val()==""){
-                $('#antesP').addClass("is-invalid");
-                error=true;
-            }
-            if(isNaN($('#antesP').val())){
-                $('#antesP').addClass("is-invalid");
-                error=true;
-            }
-            if($('#despuesP').val()==""){
-                $('#despuesP').addClass("is-invalid");
-                error=true;
-            }
-            if(isNaN($('#despuesP').val())){
-                $('#despuesP').addClass("is-invalid");
-                error=true;
-            }
-            if($('#retardoP').val()==""){
-                $('#retardoP').addClass("is-invalid");
-                error=true;
-            }
-            if(isNaN($('#retardoP').val())){
-                $('#retardoP').addClass("is-invalid");
-                error=true;
-            }
-            if(!error){
-                $('#formaModalTiempos').prop("action", "./action/action_tiempos_update.php");
-            }else{
-                return false;
-            }
-        }
-
-        function valida_camposP(){
-            var error=false;
-            if($("#fecha_inicial").val()==""){
-                $("#fecha_inicial").addClass("is-invalid");
-                error=true;
-            }
-            if($("#fecha_final").val()==""){
-                $("#fecha_final").addClass("is-invalid");
-                error=true;
-            }
-            if($("#nombreP").val()==""){
-                $("#nombreP").addClass("is-invalid");
-                $("#nombreP-error").html("Campo obligatorio");
-                error=true;
-            }
-            if($("#nombreP").val()[0]==" "){
-                $("#nombreP").addClass("is-invalid");
-                $("#nombreP-error").html("No puede haber espacios al inicio");
-                error=true;
-            }
-            if($("#nivelP").val()==""){
-                error=true;
-                $("#nivelP").addClass("is-invalid");
-            }
-            if(error){
-                return false;
-            }else{
-                var btn = $("#submitBtnP");
-                if(btn.data("tipo")==2)//update
-                    $("#formaModalP").prop("action", "./action/action_periodos_update.php");
-                else{//insert
-                    $("#formaModalP").prop("action", "./action/action_periodos_insert.php");
-                }
-            }
-        }
-
-        <?php if(!$fs_carreras && !$fs_periodos){ ?>
-            triggerMessage("No se encontraron carreras ni periodos en esta facultad", "Error");
-        <?php } else if(!$fs_carreras){?>
-            triggerMessage("No se encontraron carreras en esta facultad", "Error");
-        <?php } else if(!$fs_periodos){?>
-            triggerMessage("No se encontraron periodos en esta facultad", "Error");
-        <?php }?>
-
-        function valida_campos(){
-            var error=false;
-            if($("#nombre").val()==""){
-                $("#nombre").addClass("is-invalid");
-                $("#nombre-error").html("Campo obligatorio");
-                error=true;
-            }
-            if($("#nombre").val()[0]==" "){
-                $("#nombre").addClass("is-invalid");
-                $("#nombre-error").html("No puede haber espacios al inicio");
-                error=true;
-            }
-            if($("#nivel").val()==""){
-                error=true;
-            }
-            if($('#estado_activo').prop('checked') == false && $('#estado_inactivo').prop('checked') == false){
-                error=true;
-            }
-            if(error){
-                return false;
-            }else{
-                var btn = $('#submitBtn');
-                if(btn.data("tipo")==2)//update
-                    $("#formaModal").prop("action", "./action/action_carreras_update.php");
-                else//insert
-                    $("#formaModal").prop("action", "./action/action_carreras_insert.php");
-            }
-        }
-
-        $('#modal_periodo').on('show.bs.modal', function(event){//datos periodo 
-            var button = $(event.relatedTarget);
-            var tipo = button.data('tipo');
-            $("#nombreP").removeClass("is-invalid");
-            if(tipo==1){//crear
-                $('#modalLabelP').html("Agregar periodo");
-                $("#submitBtnP").data("tipo", 1);
-                $("#fecha_inicial").datepicker("setDate", today);
-                $("#fecha_final").datepicker("setDate", today);
-                $("#nombreP").val("");
-                $("#estado_activoP").prop("checked", true);
-                setDatalist("#nivelP",1);
-                $("li").removeClass("selected");
-                var fi = $("#fecha_inicial").datepicker("getDate");
-                //$("#fecha_final").datepicker("option", "minDate", fi);
-            }else{//editar
-                $('#modalLabelP').html("Editar periodo");
-                $("#submitBtnP").data("tipo", 2);
-                var id = $(event.relatedTarget).parents("tr").data("id");
-                var fac = $("#facultadP").val();
-                $.ajax({
-                    url:"action/action_periodos_select.php",
-                    type:"post",
-                    dataType:"json",
-                    data:{idfacultad: fac, idperiodo: id},
-                    success:function(result){
-                        //console.log(result);
-                        $("#idP").val(result["id"]);
-                        $("#facultadP").val(result["facultad_id"]);
-                        $("#nombreP").val(result["periodo"]);
-                        var date = new Date(result["inicio"])
-                        date.setDate(date.getDate() + 1);
-                        $("#fecha_inicial").datepicker("setDate", date);
-                        date = new Date(result["fin"])
-                        date.setDate(date.getDate() + 1);
-                        $("#fecha_final").datepicker("setDate", date);
-                        //$(".datalist-input").html(result["nivel"]);
-                        setDatalist("#nivelP",result["nivel_id"]);
-                        var fi = $("#fecha_inicial").datepicker("getDate");
-                        //$("#fecha_final").datepicker("option", "minDate", fi);
-                        var ff = $("#fecha_final").datepicker("getDate");
-                        //$("#fecha_inicial").datepicker("option", "maxDate", ff);
-                        if(result['estado']=="Activo"){
-                            $('#estado_activoP').prop('checked', true);
-                        }else{
-                            $('#estado_inactivoP').prop('checked', true);
-                        }
-                    },
-                    error: function(){console.log("Error")}
-                });
-            }
-        })
-
-        $('#modal').on('show.bs.modal', function(event){
-            var button = $(event.relatedTarget);
-            var tipo = button.data('tipo');
-            var modal = $(this);
-            $("#nombre").removeClass("is-invalid");
-            if(tipo == 1){//crear
-                $("#submitBtn").data('tipo', 1);
-                $("#modalLabel").html("Crear Carrera");
-                $("#nombre").val("");
-                $('#estado_activo').prop('checked', true);
-                $('li').removeClass('selected');
-                $(".datalist-input").html("Mostrar todas");
-                $("#nivel").val("");
-            }else{//editar
-                $("#submitBtn").data('tipo', 2);
-                $("#modalLabel").html("Editar Carrera");
-                $("#nombre").val("");
-                $('#estado_activo').prop('checked', true);
-                var id = $(event.relatedTarget).parents("tr").data("id");
-                var fac = $("#facultad").val();
-                $.ajax({
-                    url:"action/action_carreras_select.php",
-                    type:"post",
-                    dataType:"json",
-                    data:{idfacultad: fac, idcarrera: id},
-                    success:function(result){
-                        //console.log(result);
-                        $("#id").val(result["carrera_id"]);
-                        $("#nombre").val(result["carrera_nombre"])
-                        if(result['carrera_activa']==1){
-                            $('#estado_activo').prop('checked', true);
-                        }else{
-                            $('#estado_inactivo').prop('checked', true);
-                        }
-                        setDatalist("#nivel", result["nivel_id"]);
-                    },
-                    error: function(){console.log("Error")}
-                });
-            }
-        });
-        $(document).ready(function(){
-            $('#antesL').val("<?= -1*($fs_tiempoLic['desde_asistencia'] ?? 0) ?>");
-            $('#despuesL').val("<?= ($fs_tiempoLic['hasta_asistencia'] ?? 1)-1 ?>");
-            $('#retardoL').val("<?= ($fs_tiempoLic['hasta_retardo'] ?? 1) - ($fs_tiempoLic['hasta_asistencia'] ?? 0) ?>");
-            $('#antesP').val("<?= -1*($fs_tiempoPos['desde_asistencia'] ?? 0) ?>");
-            $('#despuesP').val("<?= ($fs_tiempoPos['hasta_asistencia'] ?? 1) -1 ?>");
-            $('#retardoP').val("<?= ($fs_tiempoPos['hasta_retardo'] ?? 1) - ($fs_tiempoPos['hasta_asistencia'] ?? 0) ?>");
-            $('#facultadT').val("<?= $_GET['facultad'] ?>");
-            <?php
-                if(($fs_tiempoLic['hasta_asistencia'] ?? 0) == ($fs_tiempoLic['hasta_retardo'] ?? 0)){ ?>
-                    $('.retardoLic').hide();
-                    $('#retardoL').val("0");
-                    $('#retardoLic').prop("checked", false).change();
-                    <?php }
-                else{ ?>
-                        $('#retardoLic').prop("checked", true).change();
-                    <?php }
-                if(($fs_tiempoPos['hasta_asistencia'] ?? 0) == ($fs_tiempoPos['hasta_retardo'] ?? 0)){ ?>
-                    $('.retardoPos').hide();
-                    $('#retardoP').val("0");
-                    $('#retardoPos').prop("checked", false).change();
-                <?php }
-                else{ ?>
-                        $('#retardoPos').prop("checked", true).change();
-                <?php }
-            ?>
-        });
-    </script>
+    <script src="js/carreras.js?<?= rand(0, 2) ?>" type="module"></script>
+    <script src="js/scrollables.js"></script>
 </body>
 
 </html>

+ 9 - 2
css/sgi.css

@@ -863,7 +863,7 @@ footer ul {
 }
 
 footer .footerTop .menuFooter ul>li {
-    *zoom: 1;
+    zoom: 1;
     float: left;
     clear: none;
     text-align: inherit;
@@ -1058,11 +1058,18 @@ footer .tab-pane p {
 .movie {
     transition: all 0.1s;
     box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
+    height: 8rem;
+    /* align all inside content to the middle */
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+    background-color: #f7f7f7;
 }
 
 .movie:hover {
     transform: scale(1.05);
-    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
+    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.5);
     font-size: 1.1em;
 }
 

+ 0 - 133
horario_profesor.php

@@ -1,133 +0,0 @@
-<?php
-require_once 'class/c_login.php';
-$user = Login::get_user();
-
-$user->access();
-if (in_array($user->acceso, ['n']))
-    die(header('Location: main.php?error=1'));
-
-$user->print_to_log('Consultar horario');
-
-$write = $user->admin || in_array($user->acceso, ['w']);
-// var_dump($user);
-?>
-<!DOCTYPE html>
-<html lang="en">
-
-<head>
-    <title>Consultar horario | <?= $user->facultad['facultad'] ?? 'General' ?></title>
-    <meta charset="utf-8">
-    <meta http-equiv="content-type" content="text/plain; charset=UTF-8" />
-    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
-    <?php include_once "import/html_css_files.php"; ?>
-
-    <script src="js/scrollables.js" defer></script>
-    <script src="js/jquery.min.js" defer></script>
-    <script src="js/bootstrap/bootstrap.min.js" defer></script>
-
-    <script src="js/messages.js" defer></script>
-    <script>
-        const write = <?= $write ? 'true' : 'false' ?>;
-    </script>
-    <script src="js/moment.js" defer></script>
-    <script src="js/horario_profesor.js" defer></script>
-</head>
-<!--  -->
-
-<body style="display: block;">
-    <?php
-    include('include/constantes.php');
-    include("import/html_header.php");
-    html_header("Consultar horario", "Sistema de gestión de checador");
-    ?>
-    <?= "<!-- $user -->" ?>
-    <main class="container content marco content-margin" id="local-app">
-        <section id="message"></section>
-        <?php require('import/periodo.php') ?>
-
-        <form id="form" class="form-horizontal">
-            <div class="form-group">
-                <div class="form-box">
-                    <input type="hidden" name="periodo" value="<?= $user->periodo_id ?>" />
-                    <div class="form-group row">
-                        <label for="clave_profesor" class="col-4 col-form-label">Profesor</label>
-                        <div class="col-6">
-                            <input list="lista_profesores" name="clave_profesor" id="clave_profesor" class="form-control" placeholder="Profesor" required="required">
-                            <div class="valid-feedback">
-                                Profesor encontrado
-                            </div>
-                            <div class="invalid-feedback">
-                                Profesor no encontrado
-                            </div>
-                            <datalist id="lista_profesores">
-                                <?php
-                                $profesores = $db->where('facultad_id', $user->facultad['facultad_id'])->get("fs_profesor");
-                                foreach ($profesores as $profesor) {
-                                    extract($profesor);
-                                ?>
-                                    <option data-grado="<?= $grado ?>" data-clave="<?= $clave ?>" data-profesor="<?= $profesor ?>" data-id="<?= $id; ?>" value="<?= "$clave | $grado $profesor" ?>"></option>
-                                <?php
-                                }
-                                ?>
-                            </datalist>
-                            <ul class="list-group" id="profesores"></ul>
-                            <input type="hidden" id="periodo_id" name="periodo_id" value="<?= $user->periodo_id ?>">
-                            <input type="hidden" id="profesor_id" name="profesor_id" value="">
-                        </div>
-                    </div>
-
-                    <!-- ICO-BUSCAR FILTRAR & ICO-BORRAR LIMPIAR -->
-                    <div class="form-group row justify-content-center">
-                        <button class="btn btn-outline-primary mr-2">
-                            <span class="ing-buscar icono"></span>
-                            Buscar horario
-                        </button>
-                        <button type="button" class="btn btn-outline-danger" onclick="location.reload()">
-                            <span class="ing-borrar icono"></span>
-                            Limpiar
-                        </button>
-                    </div>
-                </div>
-            </div>
-        </form>
-        <div class="form-group mt-4 row justify-content-center">
-            <?php if ($write) { ?>
-                <button type="button" id="nuevo" class="btn btn-outline-primary ml-4 d-none" title="Nuevo horario" data-toggle="modal" data-target="#modal-editar">
-                    <span class="ing-mas ing-fw"></span> Nuevo
-                </button>
-            <?php } ?>
-        </div>
-        </div>
-        </div>
-
-        </form>
-        <!-- Horario is a (table with one a cell) within a table 
-            7:15 - 8:45, 8:45 - 10:15, 10:30 - 12:00, 12:00 - 13:30
-            de lunes a viernes, a excepción de que tenga sábado
-        -->
-        <div id="btn-excel-horario" class="mb-2 float-right hidden">
-            <button class="btn btn-outline-secondary " title="Exportar a Excel">
-                <span class="ing-descarga ing-fw"></span> Exportar a Excel
-            </button>
-        </div>
-        <!-- Table responsive -->
-        <div class="table-responsive">
-            <table class="table table-bordered table-sm table-responsive-md" id="table-horario">
-                <thead class="thead-dark">
-                    <tr id="headers">
-                        <th scope="col" class="text-center">Hora</th>
-                        <th scope="col" class="text-center">Lunes</th>
-                        <th scope="col" class="text-center">Martes</th>
-                        <th scope="col" class="text-center">Miércoles</th>
-                        <th scope="col" class="text-center">Jueves</th>
-                        <th scope="col" class="text-center">Viernes</th>
-                        <th scope="col" class="text-center">Sábado</th>
-                    </tr>
-                </thead>
-                <tbody id="horario"></tbody>
-            </table>
-        </div>
-    </main>
-</body>
-
-</html>

+ 44 - 0
js/carreras.js

@@ -0,0 +1,44 @@
+import { createApp } from 'https://unpkg.com/petite-vue?module';
+const app = createApp({
+    carreras: [],
+    niveles: [],
+    message: {},
+    async setNivel(carrera, nivel) {
+        if (carrera.nivel_id === nivel.nivel_id) {
+            return;
+        }
+        carrera.nivel_id = nivel.nivel_id;
+        carrera.nivel_nombre = nivel.nivel_nombre;
+        await fetch('action/carrera.php', {
+            method: 'PUT',
+            body: JSON.stringify({
+                carrera_id: carrera.carrera_id,
+                nivel_id: nivel.nivel_id
+            })
+        })
+            .then(res => res.json())
+            .then(res => {
+            this.message.title = "Actualización";
+            this.message.text = res.error ?? res.success;
+            this.message.type = res.error ? 'danger' : 'success';
+            this.message.timestamp = new Date().toLocaleTimeString();
+        });
+    },
+    async mounted() {
+        this.carreras = await fetch('action/carrera.php').then(res => res.json());
+        this.niveles = await fetch('action/nivel.php').then(res => res.json());
+        // group by facultad_id
+        const carreras = this.carreras.reduce((acc, cur) => {
+            const { facultad_nombre } = cur;
+            if (!acc[facultad_nombre]) {
+                acc[facultad_nombre] = [];
+            }
+            acc[facultad_nombre].push(cur);
+            return acc;
+        }, {});
+        this.carreras = Object.entries(carreras).map(([facultad_nombre, carreras]) => ({
+            facultad_nombre: facultad_nombre,
+            carreras
+        }));
+    }
+}).mount('#app');

+ 44 - 0
js/periodos.js

@@ -0,0 +1,44 @@
+import { createApp } from 'https://unpkg.com/petite-vue?module';
+const app = createApp({
+    carreras: [],
+    niveles: [],
+    message: {},
+    async setNivel(carrera, nivel) {
+        if (carrera.nivel_id === nivel.nivel_id) {
+            return;
+        }
+        carrera.nivel_id = nivel.nivel_id;
+        carrera.nivel_nombre = nivel.nivel_nombre;
+        await fetch('action/carrera.php', {
+            method: 'PUT',
+            body: JSON.stringify({
+                carrera_id: carrera.carrera_id,
+                nivel_id: nivel.nivel_id
+            })
+        })
+            .then(res => res.json())
+            .then(res => {
+            this.message.title = "Actualización";
+            this.message.text = res.error ?? res.success;
+            this.message.type = res.error ? 'danger' : 'success';
+            this.message.timestamp = new Date().toLocaleTimeString();
+        });
+    },
+    async mounted() {
+        this.carreras = await fetch('action/carrera.php').then(res => res.json());
+        this.niveles = await fetch('action/nivel.php').then(res => res.json());
+        // group by facultad_id
+        const carreras = this.carreras.reduce((acc, cur) => {
+            const { facultad_nombre } = cur;
+            if (!acc[facultad_nombre]) {
+                acc[facultad_nombre] = [];
+            }
+            acc[facultad_nombre].push(cur);
+            return acc;
+        }, {});
+        this.carreras = Object.entries(carreras).map(([facultad_nombre, carreras]) => ({
+            facultad_nombre: facultad_nombre,
+            carreras
+        }));
+    }
+}).mount('#app');

+ 220 - 0
periodos.php

@@ -0,0 +1,220 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Auditoría asistencial</title>
+    <?php
+    include 'import/html_css_files.php';
+    ?>
+    <link rel="stylesheet" type="text/css" href="https://unpkg.com/trix@2.0.0/dist/trix.css">
+    <style>
+        [v-cloak] {
+            display: none;
+        }
+    </style>
+    <script src="js/jquery.min.js"></script>
+    <script src="js/jquery-ui.js"></script>
+    <script src="js/bootstrap/bootstrap.min.js"></script>
+</head>
+
+<body>
+    <?
+    $redirect = $_SERVER['PHP_SELF'];
+    include "import/html_header.php";
+    global $user;
+
+    html_header("Periodos", "Sistema de gestión de checador");
+    ?>
+    <main class="container-fluid px-4 my-4" id="app" v-cloak @vue:mounted="mounted" style="min-height: 70vh;">
+        <section class="row mt-4">
+            <div class="col-12 position-relative">
+                <!-- Loop for messages -->
+                <div class="toast show shadow-sm mb-3 bg-white"
+                    style="position: fixed; top: 15%; right: 1%; max-width: 300px;" role="alert" aria-live="assertive"
+                    aria-atomic="true"
+                    @vue:mounted="$('.toast').toast({delay: 5000}).toast('show').on('hidden.bs.toast', () => { message.text = '' })"
+                    v-if="message.text">
+                    <div class="toast-header">
+                        <strong class="mr-auto text-primary text-uppercase text-center w-100 px-4">
+                            {{ message.title }}
+                        </strong>
+                        <small class="text-muted">{{ message.timestamp }}</small>
+                        <button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close"
+                            @click="message.text = ''">
+                            <span aria-hidden="true">
+                                <i class="fas fa-times"></i>
+                            </span>
+                        </button>
+                    </div>
+                    <div class="toast-body">
+                        <div :class="message.type == 'success' ? 'text-success' : 'text-danger'"><i
+                                :class="message.type == 'success' ? 'fas fa-check-circle' : 'fas fa-times-circle'"></i>
+                            {{ message.text }}
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </section>
+        <div class="container">
+                        <div class="d-flex flex-wrap justify-content-around">
+                <div class="accordion col-8 mb-4" id="puestos"
+                    v-scope="{selected_carrera_id: -1, current_materia: null, current_encargado: null}"
+                    v-if="puestos.length">
+                    <div class="card" v-for="(puesto, index) in puestos" :key="puesto.puesto_id">
+                        <div class="card-header bg-primary" :id="`puesto-${puesto.nombre}`">
+                            <h2 class="mb-0">
+                                <button class="btn btn-link btn-block text-left text-light" type="button"
+                                    data-toggle="collapse" :data-target="`#puesto-${puesto.puesto_id}`" aria-expanded="true"
+                                    :aria-controls="`puesto-${puesto.puesto_id}`">
+                                    {{puesto.nombre}}
+
+                                    <button type="button" class="btn btn-outline-danger float-right"
+                                        data-target="#eliminar-puesto" data-toggle="modal" @click="to_delete = puesto">
+                                        <span class="icono ing-basura"></span>
+                                    </button>
+                                </button>
+                            </h2>
+                        </div>
+
+                        <div :id="`puesto-${puesto.puesto_id}`" class="collapse" :class="{'show': index == 0}"
+                            :aria-labelledby="`puesto-${puesto.nombre}`" data-parent="#puestos">
+                            <div class="card-body">
+                                <!-- Encargado -->
+                                <div class="form-row justify-content-around align-items-center mb-2">
+                                    <label :for="`encargado-${puesto.puesto_id}`" class="col-3">
+                                        Encargado del área
+                                    </label>
+                                    <div id="encargados" class="datalist datalist-select mb-1 col-9">
+                                        <div class="datalist-input" v-if="puesto.encargado">
+                                            ({{puesto.encargado.usuario_clave}}) {{ puesto.encargado.usuario_nombre }}
+                                        </div>
+                                        <div class="datalist-input" v-else>
+                                            Selecciona un encargado
+                                        </div>
+                                        <span class="icono ing-buscar"></span>
+                                        <ul style="display:none">
+                                            <li class="datalist-option" v-for="usuario in usuarios"
+                                                :key="usuario.usuario_id" :data-id="usuario.usuario_id"
+                                                style=" white-space: nowrap;" @click="puesto.encargado = usuario"
+                                                :class="{'selected': puesto.encargado?.usuario_id == usuario.usuario_id}">
+                                                (<small> {{usuario.usuario_clave}} </small>) {{ usuario.usuario_nombre }}
+                                            </li>
+                                        </ul>
+                                        <input type="hidden" id="encargado_id" name="id">
+                                    </div>
+                                </div>
+                                <hr>
+
+                                <div class="form-row justify-content-around align-items-center mb-2"
+                                    v-show="carreras.length">
+                                    <label :for="`carrera-${puesto.puesto_id}`" class="col-3">
+                                        Carrera
+                                    </label>
+                                    <div id="dlCarreras" class="datalist datalist-select mb-1 col-9">
+                                        <div class="datalist-input">
+                                            Selecciona una carrera
+                                        </div>
+                                        <span class="icono ing-buscar"></span>
+                                        <ul style="display:none">
+                                            <li class="datalist-option" data-id="0" @click="selected_carrera_id = 0">
+                                                Todas las carreras
+                                            </li>
+                                            <li class="datalist-option" v-for="carrera in carreras"
+                                                :key="carrera.carrera_id" :data-id="carrera.carrera_id"
+                                                style=" white-space: nowrap;"
+                                                @click="selected_carrera_id = carrera.carrera_id">
+                                                (<small> {{carrera.clave_carrera}} </small>) {{ carrera.carrera_nombre }}
+                                            </li>
+                                        </ul>
+                                        <input type="hidden" id="carrera_id" name="id">
+                                    </div>
+                                </div>
+                                <div class="form-row justify-content-around align-items-center"
+                                    v-scope="{to_add_materia: null}">
+                                    <label :for="`materias-${puesto.puesto_id}`" class="col-3">
+                                        Materias
+                                    </label>
+                                    <input name="materia" placeholder="Seleccione una materia" list="datalist-materias"
+                                        class="form-control col-9 " v-model="current_materia" @input="to_add_materia = materias.find(m => current_materia == `${m.clave_materia} - ${m.materia_nombre}`);
+                                if (to_add_materia) {
+                                    if (puesto.materias.find(p => p.materia_id == to_add_materia.materia_id)) {
+                                        console.log('La materia ya está asignada');
+                                        current_materia = null;
+                                        return;
+                                    }
+                                    puesto.materias.push(to_add_materia);
+                                    materias.splice(materias.indexOf(to_add_materia), 1);
+                                    current_materia = null;
+                                }" :disabled="selected_carrera_id == -1" v-model="current_materia"
+                                        :id="`materias-${puesto.puesto_id}`" autocomplete="off">
+                                </div>
+                                <datalist id="datalist-materias">
+                                    <option
+                                        v-for="materia in materias.filter(m => selected_carrera_id == 0 || m.carrera_id == selected_carrera_id).filter(m => !puesto.materias.find(p => p.materia_id == m.materia_id))"
+                                        :value="`${materia.clave_materia} - ${materia.materia_nombre}`">
+                                </datalist>
+
+                                <hr>
+                                <fieldset class="container d-flex flex-column justify-content-center align-items-center">
+                                    <legend>Materias Asignadas <span
+                                            class="badge badge-secondary">{{puesto.materias.length}}</span></legend>
+                                    <ul class="list-group overflow-auto col-10" v-if="puesto.materias.length"
+                                        style="max-height: 200px; overflow-y: auto;">
+                                        <li class="list-group-item list-group-item-action"
+                                            v-for="materia in puesto.materias" :key="materia.materia_id"
+                                            @click="puesto.materias.splice(puesto.materias.indexOf(materia), 1); materias.push(materia)"
+                                            style="cursor: pointer;">
+                                            <div class="d-flex justify-content-center">
+                                                <div class="col-2 text-center">
+                                                    <span class="icono ing-borrar text-danger"></span>
+                                                </div>
+                                                <div class="col-10 text-left">
+                                                    {{materia.clave_materia}} - {{materia.materia_nombre}}
+                                                </div>
+                                            </div>
+                                        </li>
+                                    </ul>
+                                    <div class="alert alert-light" role="alert" v-else>
+                                        No hay materias asignadas
+                                    </div>
+                                </fieldset>
+
+                            </div>
+                            <div class="card-footer text-muted">
+                                <!-- scroll to top -->
+                                <button type="button" class="btn btn-outline-primary btn-lg btn-block"
+                                    @click="actualizarPuesto(puesto.puesto_id, puesto.materias, puesto.encargado?.usuario_id)"
+                                    onclick="window.scrollTo(0, 0);">
+                                    {{ puesto.encargado ? 'Guardar cambios' : 'Guardar sin encargado' }}
+                                </button>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+                <div v-else>
+                    <div class="alert alert-dark" role="alert">
+                        No hay puestos registrados
+                    </div>
+                </div>
+            </div>
+        </div>
+    </main>
+    <?
+    include "import/html_footer.php"; ?>
+    <script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js"
+        integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj"
+        crossorigin="anonymous"></script>
+    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"
+        integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN"
+        crossorigin="anonymous"></script>
+    <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.min.js"
+        integrity="sha384-+sLIOodYLS7CIrQpBjl+C7nPvqq+FbNUBDunl/OZv93DB7Ln/533i8e/mZXLi/P+"
+        crossorigin="anonymous"></script>
+    <script src="js/datalist.js"></script>
+    <script src="js/periodos.js?<?= rand(0, 2) ?>" type="module"></script>
+    <script src="js/scrollables.js"></script>
+</body>
+
+</html>

+ 14 - 6
puestos.php

@@ -38,12 +38,20 @@
             <div class="alert alert-success" role="alert" v-if="message">
                 {{message}}
             </div>
-            <div class="justify-content-between align-items-center d-flex mb-4">
-                <button type="button" class="btn btn-primary" data-toggle="modal" data-target="#nuevo-puesto">
-                    <span class="ing-mas"></span>
-                    Agregar puesto
-                </button>
-            </div>
+
+            <?
+            if ($user->acceso == 'w') {
+                ?>
+                <div class="justify-content-between align-items-center d-flex mb-4">
+                    <button type="button" class="btn btn-primary" data-toggle="modal" data-target="#nuevo-puesto">
+                        <span class="ing-mas"></span>
+                        Agregar puesto
+                    </button>
+                </div>
+
+                <?
+            }
+            ?>
 
             <div class="d-flex flex-wrap justify-content-around">
                 <div class="accordion col-8 mb-4" id="puestos"

+ 63 - 0
ts/carreras.ts

@@ -0,0 +1,63 @@
+import { createApp, reactive } from 'https://unpkg.com/petite-vue?module'
+
+type Carrera = {
+    carrera_id: number;
+    carrera_nombre: string;
+    clave_carrera: string;
+    facultad_id: number;
+    facultad_nombre: string;
+    nivel_id: number;
+    nivel_nombre: string;
+}
+
+type Nivel = {
+    nivel_id: number;
+    nivel_nombre: string;
+}
+
+const app = createApp({
+    carreras: [] as Carrera[],
+    niveles: [] as Nivel[],
+    message: {} as Record<string, string>,
+    async setNivel(carrera: Carrera, nivel: Nivel) {
+        if (carrera.nivel_id === nivel.nivel_id) {
+            return
+        }
+        carrera.nivel_id = nivel.nivel_id
+        carrera.nivel_nombre = nivel.nivel_nombre
+
+        await fetch('action/carrera.php', {
+            method: 'PUT',
+            body: JSON.stringify({
+                carrera_id: carrera.carrera_id,
+                nivel_id: nivel.nivel_id
+            })
+        })
+            .then(res => res.json())
+            .then(res => {
+                this.message.title = "Actualización"
+                this.message.text = res.error ?? res.success
+                this.message.type = res.error ? 'danger' : 'success'
+                this.message.timestamp = new Date().toLocaleTimeString()
+            })
+
+
+    },
+    async mounted() {
+        this.carreras = await fetch('action/carrera.php').then(res => res.json())
+        this.niveles = await fetch('action/nivel.php').then(res => res.json())
+        // group by facultad_id
+        const carreras = this.carreras.reduce((acc, cur) => {
+            const { facultad_nombre } = cur
+            if (!acc[facultad_nombre]) {
+                acc[facultad_nombre] = []
+            }
+            acc[facultad_nombre].push(cur)
+            return acc
+        }, {} as Record<number, Carrera[]>)
+        this.carreras = Object.entries(carreras).map(([facultad_nombre, carreras]) => ({
+            facultad_nombre: facultad_nombre,
+            carreras
+        }))
+    }
+}).mount('#app') 

+ 63 - 0
ts/periodos.ts

@@ -0,0 +1,63 @@
+import { createApp, reactive } from 'https://unpkg.com/petite-vue?module'
+
+type Carrera = {
+    carrera_id: number;
+    carrera_nombre: string;
+    clave_carrera: string;
+    facultad_id: number;
+    facultad_nombre: string;
+    nivel_id: number;
+    nivel_nombre: string;
+}
+
+type Nivel = {
+    nivel_id: number;
+    nivel_nombre: string;
+}
+
+const app = createApp({
+    carreras: [] as Carrera[],
+    niveles: [] as Nivel[],
+    message: {} as Record<string, string>,
+    async setNivel(carrera: Carrera, nivel: Nivel) {
+        if (carrera.nivel_id === nivel.nivel_id) {
+            return
+        }
+        carrera.nivel_id = nivel.nivel_id
+        carrera.nivel_nombre = nivel.nivel_nombre
+
+        await fetch('action/carrera.php', {
+            method: 'PUT',
+            body: JSON.stringify({
+                carrera_id: carrera.carrera_id,
+                nivel_id: nivel.nivel_id
+            })
+        })
+            .then(res => res.json())
+            .then(res => {
+                this.message.title = "Actualización"
+                this.message.text = res.error ?? res.success
+                this.message.type = res.error ? 'danger' : 'success'
+                this.message.timestamp = new Date().toLocaleTimeString()
+            })
+
+
+    },
+    async mounted() {
+        this.carreras = await fetch('action/carrera.php').then(res => res.json())
+        this.niveles = await fetch('action/nivel.php').then(res => res.json())
+        // group by facultad_id
+        const carreras = this.carreras.reduce((acc, cur) => {
+            const { facultad_nombre } = cur
+            if (!acc[facultad_nombre]) {
+                acc[facultad_nombre] = []
+            }
+            acc[facultad_nombre].push(cur)
+            return acc
+        }, {} as Record<number, Carrera[]>)
+        this.carreras = Object.entries(carreras).map(([facultad_nombre, carreras]) => ({
+            facultad_nombre: facultad_nombre,
+            carreras
+        }))
+    }
+}).mount('#app')