Jelajahi Sumber

Añadir justificaciones

Alejandro Rosales 2 tahun lalu
induk
melakukan
9ce1dee313
5 mengubah file dengan 420 tambahan dan 135 penghapusan
  1. 13 2
      action/action_auditoria.php
  2. 72 25
      action/action_justificar.php
  3. 173 52
      auditoría.php
  4. 78 22
      js/auditoría.js
  5. 84 34
      ts/auditoría.ts

+ 13 - 2
action/action_auditoria.php

@@ -25,18 +25,29 @@ try {
                 SELECT fechas_clase(h.horario_id) as registro_fecha_ideal, h.horario_id  
                 FROM horarios h
             )
-            SELECT estado_supervisor.*, usuario.*, registro.*, profesor.*, horarios.*, fechas.*
+            SELECT estado_supervisor.*, usuario.*, registro.*, profesor.*, horarios.*, fechas.*,
+                justificador.usuario_nombre as justificador_nombre,
+                justificador.usuario_clave as justificador_clave,
+                facultad.facultad_nombre as justificador_facultad,
+                rol.rol_titulo as justificador_rol
             FROM horarios
             JOIN fechas using (horario_id)
             JOIN horario_profesor using (horario_id)
             JOIN profesor using (profesor_id)
             LEFT JOIN registro USING (horario_id, registro_fecha_ideal, profesor_id)
-            left join estado_supervisor using (estado_supervisor_id)
+            LEFT join estado_supervisor using (estado_supervisor_id)
             LEFT JOIN USUARIO ON USUARIO.usuario_id = REGISTRO.supervisor_id
+            LEFT JOIN USUARIO JUSTIFICADOR ON JUSTIFICADOR.usuario_id = REGISTRO.justificador_id
+            LEFT JOIN ROL on ROL.rol_id = justificador.rol_id
+            left join facultad on facultad.facultad_id = justificador.facultad_id
+            WHERE fechas.registro_fecha_ideal BETWEEN COALESCE(:fecha_inicio, LEAST(CURRENT_DATE, PERIODO_FECHA_FIN)) AND  COALESCE(:fecha_fin, LEAST(CURRENT_DATE, PERIODO_FECHA_FIN))
             ORDER BY fechas.registro_fecha_ideal DESC, horarios.horario_id, profesor_nombre",
             [
                 ':periodo_id' => $user->periodo_id,
                 ':facultad_id' => $user->facultad['facultad_id'],
+                ':fecha_inicio' => $_GET['fecha'] ?? $_GET['fecha_inicio'] ?? null,
+                ':fecha_fin' => $_GET['fecha'] ?? $_GET['fecha_fin'] ?? null,
+
             ]
         );
 

+ 72 - 25
action/action_justificar.php

@@ -1,33 +1,80 @@
-<?php
+<?
+header('Content-Type: application/json charset=utf-8');
+#return html
 $ruta = "../";
 require_once "../class/c_login.php";
+// check method
 
-extract($_POST);
+if (!isset($_SESSION['user'])) {
+    http_response_code(401);
+    echo json_encode(['error' => 'unauthorized']);
+    exit;
+}
 
-/* print_r($claves);
-exit; */
+$user = unserialize($_SESSION['user']);
 
-$fecha = DateTime::createFromFormat('d/m/Y', $fecha);
+try {
+    if ($_SERVER['REQUEST_METHOD'] === 'PUT') {
+        // check parameters
+        $raw = file_get_contents('php://input');
+        $post_data = json_decode($raw, true);
+        // if it's a list
+        // step 1: get subrutas
+        if (empty($post_data)) {
+            http_response_code(400);
+            echo json_encode(['error' => 'No hay clases pendientes']);
+            exit;
+        }
+
+        $data = $db->querySingle(
+            'INSERT INTO registro (profesor_id, horario_id, registro_fecha_ideal, registro_justificada, justificador_id, registro_fecha_justificacion, justificacion)
+            VALUES (:profesor_id, :horario_id, :registro_fecha_ideal, :registro_justificada, :justificador_id, NOW(), :justificacion)
+            ON CONFLICT (profesor_id, horario_id, registro_fecha_ideal)
+            DO UPDATE SET registro_justificada = :registro_justificada, justificador_id = :justificador_id, registro_fecha_justificacion = NOW(), justificacion = :justificacion
+            RETURNING *',
+            array(
+                'profesor_id' => $post_data['profesor_id'],
+                'horario_id' => $post_data['horario_id'],
+                'registro_fecha_ideal' => $post_data['registro_fecha_ideal'],
+                'registro_justificada' => $post_data['registro_justificada'],
+                'justificador_id' => $user->user['id'],
+                'justificacion' => empty($post_data['justificacion']) ? null : $post_data['justificacion'],
+            )
+        );
+
+        $data_justificador = $db->querySingle(
+            "SELECT justificador.usuario_nombre as justificador_nombre,
+            justificador.usuario_clave as justificador_clave,
+            facultad.facultad_nombre as justificador_facultad, rol.rol_titulo as justificador_rol
+
+            FROM USUARIO JUSTIFICADOR
+            JOIN ROL on ROL.rol_id = justificador.rol_id
+            LEFT JOIN facultad on facultad.facultad_id = justificador.facultad_id
+            where justificador.usuario_id = :justificador_id",
+            array(
+                'justificador_id' => $user->user['id'],
+            )
+        );
+        echo json_encode(array_merge($data, $data_justificador), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
+    } else {
+        http_response_code(405);
+        echo json_encode(['error' => 'method not allowed']);
+        exit;
 
-if (isset($hora)) {
-    $claves = [[
-        'clave' => $clave,
-        'hora' => $hora,
-        'id' => $id
-    ]];
-}
-foreach ($claves as $horario)
-    try {
-        $profesor_id = $horario["clave"];
-        query("SELECT fi_registrar_asistencia(:id::INT, FALSE, ARRAY[ NOW(), :fecha::DATE + :hora::TIME ]::TIMESTAMP[], :profesor_id::INT, TRUE)", [
-            ":fecha" => $fecha->format('Y-m-d'),
-            ":hora" => $horario["hora"],
-            ":id" => $horario["id"],
-            ":profesor_id" => $profesor_id
-        ]);
-    }
-    catch (Exception $e) {
-        die( json_encode(["error" => $e->getMessage()]) );
     }
 
-die(json_encode(["success" => true]));
+} catch (PDOException $th) {
+    http_response_code(500);
+    echo json_encode([
+        'error' => $th->getMessage(),
+        'query' => $db->getLastQuery(),
+        'post_data' => $post_data,
+    ], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
+    exit;
+} catch (Exception $th) {
+    http_response_code(500);
+    echo json_encode([
+        'error' => $th->getMessage(),
+    ], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
+    exit;
+}

+ 173 - 52
auditoría.php

@@ -4,7 +4,7 @@
 <head>
     <meta charset="UTF-8">
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <title>Supervisor</title>
+    <title>Auditoría asistencial</title>
     <?php
     include 'import/html_css_files.php';
     ?>
@@ -20,16 +20,21 @@
 
 <body>
     <?
+    $justificadores = array(
+        'Root',
+        'Secretario Académico',
+        'Vicerrectoría',
+    );
+
     $redirect = $_SERVER['PHP_SELF'];
     include "import/html_header.php";
     global $user;
+    $user->access();
     html_header(
         "Registro de asistencia - Vicerrectoría Académica",
         "Sistema de gestión de checador",
     );
-    ?>
-
-    <? if (!$user->periodo_id) { ?>
+    if (!$user->periodo_id) { ?>
         <script defer src="js/jquery.min.js"></script>
         <script src="js/bootstrap/bootstrap.min.js"></script>
 
@@ -92,16 +97,18 @@
                         <label class="custom-control-label" for="switchFecha"></label>
                     </div>
                 </label>
-                <div class="col-3" v-if="store.filters.switchFecha">
-                    <div class="form-row">
-                        <input id="fecha_inicio" name="fecha_inicio" class="form-control date-picker"
+
+                <div class="col-6" v-if="store.filters.switchFecha">
+                    <div class="form-row justify-content-around align-items-center">
+                        <input id="fecha_inicio" name="fecha_inicio" class="form-control date-picker col-5 mr-4"
                             placeholder="Seleccione una fecha de inicio" readonly v-model="store.filters.fecha_inicio">
-                    </div>
-                </div>
-                <div class="col-3" v-if="store.filters.switchFecha">
-                    <div class="form-row">
-                        <input id="fecha_fin" name="fecha_fin" class="form-control date-picker"
+                        <input id="fecha_fin" name="fecha_fin" class="form-control date-picker col-5"
                             placeholder="Seleccione una fecha final" readonly v-model="store.filters.fecha_fin">
+                        <button type="button" class="btn btn-info btn-sm form-control col-1 ml-auto"
+                            @click="store.filters.fetchByDate"
+                            :disabled="store.filters.fecha_inicio == null || store.filters.fecha_fin == null">
+                            <i class="ing-aceptar"></i>
+                        </button>
                     </div>
                 </div>
 
@@ -206,11 +213,10 @@
 
                         <div id="dlAsistencia" :class="{'d-none': !store.filters.sin_registro}"
                             class="datalist datalist-select mb-1 w-100">
-                            <div class="datalist-input" id="estados">
-                                <span class="badge badge-dark">
+                            <div id="estados" class="datalist-input text-center">
+                                <span class="badge badge-dark px-5">
                                     <i class="ing-cancelar mr-3"></i>
                                     <span class="text-uppercase">
-
                                         Sin registro
                                     </span>
                                 </span>
@@ -224,13 +230,13 @@
         <div class="mt-3 d-flex justify-content-center flex-wrap">
             <!-- botón descargar -->
 
-            <div class="btn-group my-3" v-if="registros.relevant.length > 0">
-                <button type="button" class="btn btn-info mr-3" @click="registros.descargar">
+            <div class="btn-group my-3" v-if="store.registros.relevant.length > 0">
+                <button type="button" class="btn btn-info mr-3" @click="store.registros.descargar">
                     <i class="ing-descarga"></i>
                     Descargar reporte
                 </button>
             </div>
-            <div v-else-if="registros.loading && registros.relevant.length > 0">
+            <div v-else-if="store.registros.loading && store.registros.relevant.length > 0">
                 <div class="spinner-border" role="status">
                     <span class="sr-only">Loading...</span>
                 </div>
@@ -243,8 +249,8 @@
                     <thead class="thead-dark">
                         <tr>
                             <th scope="col" class="text-center align-middle px-2">
-                                <button @click="registros.invertir" class="btn btn-info mr-3"
-                                    v-if="registros.relevant.length > 1">
+                                <button @click="store.registros.invertir" class="btn btn-info mr-3"
+                                    v-if="store.registros.relevant.length > 1">
                                     <i class="ing-cambiar ing-rotate-90"></i>
                                 </button>
                                 Fecha
@@ -258,10 +264,10 @@
                         </tr>
                     </thead>
                     <tbody>
-                        <tr v-if="registros.relevant.length == 0">
+                        <tr v-if="store.registros.relevant.length == 0">
                             <td colspan="7" class="text-center">No hay clases en este horario</td>
                         </tr>
-                        <tr v-for="registro in registros.relevant?.slice((store.current.page - 1) * store.current.perPage, store.current.page * store.current.perPage)"
+                        <tr v-for="registro in store.registros.relevant?.slice((store.current.page - 1) * store.current.perPage, store.current.page * store.current.perPage)"
                             :key="`${registro.registro_id}-${registro.registro_fecha_ideal}-${registro.horario_id}-${registro.profesor_id}-${registro.salon_id}`">
                             <td class="text-center align-middle px-2">{{ registro.registro_fecha_ideal }}
                             </td>
@@ -293,7 +299,7 @@
                                 <div v-else>
                                     <strong>
                                         <div class="col-12">
-                                            <span class="badge badge-danger"><i class="ing-cancelar"></i></span>
+                                            <span class="badge badge-dark"><i class="ing-cancelar"></i></span>
                                         </div>
                                         <div class="col-12 mt-2">
                                             Sin registro
@@ -303,7 +309,7 @@
                             </td>
 
                             <!-- Sí checó supervisor -->
-                            <td class="text-center align-middle px-2">
+                            <td class="text-center align-middle px-2 d-flex justify-content-center">
                                 <div v-if="registro.registro_fecha_supervisor">
                                     <div class="row">
                                         <div class="col-12">
@@ -322,23 +328,36 @@
                                     </div>
                                     <!-- comentario -->
                                     <hr v-if="registro.comentario">
-                                    <div class="col-12 " @click="registros.mostrarComentario(registro.registro_id)"
+                                    <div class="col-12 "
+                                        @click="store.registros.mostrarComentario(registro.registro_id)"
                                         v-if="registro.comentario" style="cursor: pointer;">
                                         <strong class="badge badge-primary">Observaciones:</strong>
                                         <small class="text-truncate">{{registro.comentario?.slice(0,
                                             25)}}{{registro.comentario.length > 10 ? '...' : ''}}</small>
                                     </div>
                                 </div>
-
                                 <!-- No checó -->
                                 <div v-else>
                                     <div class="col-12">
-                                        <span class="badge badge-danger"><i class="ing-cancelar"></i></span>
+                                        <span class="badge badge-dark"><i class="ing-cancelar"></i></span>
                                     </div>
                                     <div class="col-12 mt-2">
                                         <strong>Sin registro</strong>
                                     </div>
                                 </div>
+                                <? if ($user->acceso == 'w') { ?>
+                                    <button class="btn text-center mx-2" data-toggle="modal"
+                                        :class="`btn-${registro.registro_justificada ? 'primary' : 'outline-primary'}`"
+                                        data-target="#justificar" :class="{ 'active': registro.comentario }"
+                                        @click="set_justificar(registro.horario_id, registro.profesor_id, registro.registro_fecha_ideal)">
+                                        <i :class="`ing-${registro.registro_justificada ? 'aceptar' : 'editar'}`"></i> {{
+                                        registro.registro_justificada ? 'Justificada' : 'Justificar' }}
+                                        <span class="badge badge-pill badge-light text-dark"
+                                            v-if="registro.registro_justificada && registro.justificacion">...</span>
+                                        <span class="sr-only">{{ registro.registro_justificada ? 'Justificada' :
+                                            'Justificar' }}</span>
+                                    </button>
+                                <? } ?>
                             </td>
                         </tr>
                     </tbody>
@@ -346,23 +365,23 @@
             </div>
 
             <!-- page -->
-            <nav class="mt-3" v-if="registros.relevant.length > 0">
+            <nav class="mt-3" v-if="store.registros.relevant.length > 0">
                 <ul class="pagination justify-content-center">
                     <li class="page-item" :class="{'disabled': store.current.page == 1}"
                         @click="store.current.page == 1 ? '' : store.current.page--" :disabled="store.current.page == 1"
-                        :title="`Página ${store.current.page} de ${registros.pages}`">
+                        :title="`Página ${store.current.page} de ${store.registros.pages}`">
                         <a class="page-link" style="cursor: pointer;">Anterior</a>
                     </li>
                     <li class="page-item"
-                        v-for="page in [...Array(registros.pages).keys()].map(x => ++x).slice(store.current.page - 3 > 0 ? store.current.page - 3 : 0, store.current.page + 2 < registros.pages ? store.current.page + 2 : registros.pages)"
+                        v-for="page in [...Array(store.registros.pages).keys()].map(x => ++x).slice(store.current.page - 3 > 0 ? store.current.page - 3 : 0, store.current.page + 2 < store.registros.pages ? store.current.page + 2 : store.registros.pages)"
                         :class="{'active': store.current.page == page}" @click="store.current.page = page"
-                        :title="`Página ${store.current.page} de ${registros.pages}`">
+                        :title="`Página ${store.current.page} de ${store.registros.pages}`">
                         <a class="page-link" style="cursor: pointer;">{{ page }}</a>
                     </li>
-                    <li class="page-item" :class="{'disabled': store.current.page == registros.pages}"
-                        :disabled="store.current.page == registros.pages"
-                        @click="store.current.page += store.current.page == registros.pages ? 0 : 1"
-                        :title="`Página ${store.current.page} de ${registros.pages}`">
+                    <li class="page-item" :class="{'disabled': store.current.page == store.registros.pages}"
+                        :disabled="store.current.page == store.registros.pages"
+                        @click="store.current.page += store.current.page == store.registros.pages ? 0 : 1"
+                        :title="`Página ${store.current.page} de ${store.registros.pages}`">
                         <a class="page-link" style="cursor: pointer;">Siguiente</a>
                     </li>
                 </ul>
@@ -375,9 +394,7 @@
                 <div class="modal-content">
                     <div class="modal-header">
                         <h5 class="modal-title">Comentario</h5>
-                        <button type="button" class="close text-white" data-dismiss="modal" aria-label="Close">
-                            <span aria-hidden="true">&times;</span>
-                        </button>
+
                     </div>
                     <div class="modal-body">
                         <div class="container">
@@ -401,9 +418,7 @@
                 <div class="modal-content">
                     <div class="modal-header">
                         <h2 class="modal-title" :data-id="clase_vista.horario_id">Detalle de la clase</h2>
-                        <button type="button" class="close text-white" data-dismiss="modal" aria-label="Close">
-                            <span aria-hidden="true">&times;</span>
-                        </button>
+
                     </div>
                     <div class="modal-body">
                         <div class="container">
@@ -466,11 +481,11 @@
                                 <section class="col-12">
                                     <h4 class="h4 mt-4">Registro</h4>
                                     <div class="row">
-                                        <div class="col-12 text-center" v-if="!clase_vista.registro_fecha">
-                                            <strong><span class="badge badge-danger"><i class="ing-cancelar"></i></span>
-                                                El profesor aún no ha registrado su asistencia</strong>
+                                        <div class="col-md-6 text-center" v-if="!clase_vista.registro_fecha">
+                                            <strong><span class="badge badge-dark"><i class="ing-cancelar"></i></span>
+                                                Sin registro del profesor</strong>
                                         </div>
-                                        <div class="col-6 text-center" v-else>
+                                        <div class="col-md-6 text-center" v-else>
                                             El profesor registró su asistencia a las
                                             <code>{{clase_vista.registro_fecha?.slice(11, 16)}}</code>
                                             <hr>
@@ -483,7 +498,56 @@
                                                 Con retardo
                                             </p>
                                         </div>
-                                    </div>
+                                        <div class="col-md-6 text-center" v-if="clase_vista.registro_justificada">
+                                            <strong>
+                                                <span class="badge badge-primary mr-2">
+                                                    <i class="ing-aceptar"></i>
+                                                </span>
+                                                Justificada
+                                            </strong>
+                                            <span class="text-muted">
+                                                por
+                                                <strong>{{clase_vista.justificador_rol}}</strong>
+                                                {{clase_vista.justificador_nombre}}
+                                                <span v-if="clase_vista.justificador_facultad"> de
+                                                    <strong>{{clase_vista.justificador_facultad}}</strong>
+                                                </span>
+
+                                                el día {{clase_vista.registro_fecha_justificacion?.slice(0, 10)}} a las
+                                                {{clase_vista.registro_fecha_justificacion?.slice(11, 16)}}
+                                            </span>
+                                            <div v-if="clase_vista.justificacion">
+                                                <hr>
+                                                <p class="text-center">
+                                                    <strong>Observación:</strong>
+                                                    {{clase_vista.justificacion}}
+                                                </p>
+                                            </div>
+
+                                        </div>
+                                        <div class="col-md-6 text-center"
+                                            v-else-if="clase_vista.registro_fecha_justificacion">
+                                            <strong>
+                                                <span class="badge badge-dark">
+                                                    <i class="ing-cancelar"></i>
+                                                </span>
+                                                Sin justificar, <span class="text-muted">
+                                                    {{clase_vista.justificador_nombre}}
+                                                    ({{clase_vista.justificador_rol}}{{clase_vista.justificador_facultad
+                                                    ? ' de ' + clase_vista.justificador_facultad : ''}})
+                                                </span> borró la justificación, el día
+                                                {{clase_vista.registro_fecha_justificacion?.slice(0, 10)}} a las
+                                                {{clase_vista.registro_fecha_justificacion?.slice(11, 16)}}
+                                            </strong>
+                                        </div>
+                                        <div class="col-md-6 text-center" v-else>
+                                            <strong>
+                                                <span class="badge badge-dark">
+                                                    <i class="ing-cancelar"></i>
+                                                </span>
+                                                Sin justificar
+                                            </strong>
+                                        </div>
                                 </section>
                             </div>
                         </div>
@@ -499,13 +563,11 @@
             </div>
         </div>
         <div class="modal" tabindex="-1" id="cargando">
-            <div class="modal-dialog modal-dialog-centered modal-xl">
+            <div class="modal-dialog modal-dialog-centered modal-sm">
                 <div class="modal-content">
                     <div class="modal-header">
-                        <h2 class="modal-title">{{store.current.modal_state}}</h2>
-                        <button type="button" class="close text-white" data-dismiss="modal" aria-label="Close">
-                            <span aria-hidden="true">&times;</span>
-                        </button>
+                        <h4 class="modal-title">{{store.current.modal_state}}</h4>
+
                     </div>
                     <div class="modal-body container">
                         <div class="row">
@@ -517,12 +579,71 @@
                 </div>
             </div>
         </div>
+        <? if ($user->acceso == 'w') { ?>
+            <div class="modal fade" tabindex="-1" id="justificar" data-backdrop="static" data-keyboard="false">
+                <div class="modal-dialog modal-dialog-centered" v-if="store.current.justificada">
+                    <div class="modal-content">
+                        <div class="modal-header">
+                            <h5 class="modal-title">Justificar Asistencia</h5>
+                        </div>
+                        <div class="modal-body">
+                            <div class="container">
+                                <h2 class="text-center">¿Desea justificar la asistencia?</h2>
+                                <br>
+                                <div class="custom-control custom-switch">
+                                    <input type="checkbox" class="custom-control-input form-control-lg my-auto"
+                                        id="está-justificada" v-model="store.current.justificada.registro_justificada"
+                                        @click="store.current.justificada.justificacion = ''">
+                                    <label class="custom-control-label" for="está-justificada">Justificar
+                                        <strong v-if="store.current.justificada.estado_supervisor_id"
+                                            :class="`text-${store.current.justificada.estado_color}`"
+                                            class="text-uppercase">
+                                            {{store.current.justificada.nombre.toUpperCase()}}
+                                        </strong>
+                                        <strong v-else class="text-dark" class="text-uppercase">
+                                            SIN REGISTRO
+                                        </strong>
+                                        del día
+                                        <span class="text-muted">{{store.current.justificada.registro_fecha_ideal}}</span>
+                                        a las
+                                        <span
+                                            class="text-muted">{{store.current.justificada.horario_hora?.slice(0,5)}}</span>
+                                        para el profesor
+                                        <span class="text-muted">{{store.current.justificada.profesor_nombre}}</span>
+                                    </label>
+                                </div>
+                                <hr>
+                                <div class="input-group" v-if="store.current.justificada.registro_justificada">
+                                    <div class="input-group-prepend">
+                                        <span class="input-group-text text-white bg-primary">Observación</span>
+                                    </div>
+                                    <textarea class="form-control" aria-label="Observación"
+                                        placeholder="Puedes justificar sin observación" rows="2"
+                                        v-model="store.current.justificada.justificacion"></textarea>
+                                </div>
+                            </div>
+
+                        </div>
+                        <div class="modal-footer">
+                            <button type="button" class="btn btn-outline-danger" data-dismiss="modal"
+                                @click="cancelar_justificacion">
+                                <i class="ing-cancelar"></i>
+                                Cancelar
+                            </button>
+                            <button type="button" class="btn btn-primary" data-dismiss="modal" @click="store.justificar">
+                                Aceptar
+                            </button>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        <? } ?>
     </main>
 
     <!-- <script src="js/datalist.js"></script> -->
     <script src="js/datepicker-es.js"></script>
     <script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script>
-    <script src="js/auditoría.js" type="module"></script>
+    <script src="js/auditoría.js?<?= rand(0, 2) ?>" type="module"></script>
     <script src="js/scrollables.js"></script>
 </body>
 

+ 78 - 22
js/auditoría.js

@@ -1,4 +1,9 @@
 import { createApp, reactive } from 'https://unpkg.com/petite-vue?module';
+$('div.modal#cargando').modal({
+    backdrop: 'static',
+    keyboard: false,
+    show: false,
+});
 const store = reactive({
     loading: false,
     current: {
@@ -9,6 +14,7 @@ const store = reactive({
         maxPages: 10,
         perPage: 10,
         modal_state: "Cargando datos...",
+        justificada: null,
     },
     facultades: {
         data: [],
@@ -32,7 +38,11 @@ const store = reactive({
         async switchFechas() {
             const periodo = await fetch('action/periodo_datos.php');
             const periodo_data = await periodo.json();
-            // console.log(`Fecha inicio: ${periodo_data.periodo_fecha_inicio} Fecha fin: ${periodo_data.fecha_final}`);
+            if (!store.filters.switchFecha) {
+                $('div.modal#cargando').modal('show');
+                await store.registros.fetch();
+                $('div.modal#cargando').modal('hide');
+            }
             $(function () {
                 store.filters.fecha_inicio = store.filters.fecha_fin = store.filters.fecha = null;
                 $("#fecha, #fecha_inicio, #fecha_fin").datepicker({
@@ -42,6 +52,7 @@ const store = reactive({
                     showAnim: "slide",
                 });
                 const fecha = $("#fecha"), inicio = $("#fecha_inicio"), fin = $("#fecha_fin");
+                fecha.datepicker("setDate", new Date(`${periodo_data.fecha_final}:00:00:00`));
                 inicio.on("change", function () {
                     store.filters.fecha_inicio = inicio.val();
                     fin.datepicker("option", "minDate", inicio.val());
@@ -50,10 +61,19 @@ const store = reactive({
                     store.filters.fecha_fin = fin.val();
                     inicio.datepicker("option", "maxDate", fin.val());
                 });
-                fecha.on("change", function () {
+                fecha.on("change", async function () {
                     store.filters.fecha = fecha.val();
+                    $('div.modal#cargando').modal('show');
+                    await store.registros.fetch(store.filters.fecha);
+                    $('div.modal#cargando').modal('hide');
                 });
             });
+        },
+        async fetchByDate() {
+            $('div.modal#cargando').modal('show');
+            await store.registros.fetch(undefined, store.filters.fecha_inicio, store.filters.fecha_fin);
+            store.current.page = 1;
+            $('div.modal#cargando').modal('hide');
         }
     },
     estados: {
@@ -96,20 +116,36 @@ const store = reactive({
         }
         return newArray;
     },
-});
-$('div.modal#cargando').modal({
-    backdrop: 'static',
-    keyboard: false,
-    show: false,
-});
-createApp({
-    store,
-    get clase_vista() {
-        return store.current.clase_vista;
+    async justificar() {
+        if (!store.current.justificada)
+            return;
+        let data;
+        try {
+            const res = await fetch('action/action_justificar.php', {
+                method: 'PUT',
+                headers: {
+                    'Content-Type': 'application/json'
+                },
+                body: JSON.stringify(store.current.justificada)
+            });
+            data = await res.json();
+        }
+        catch (error) {
+            alert('Error al justificar');
+            store.current.justificada = store.current.clone_justificada;
+        }
+        finally {
+            delete store.current.clone_justificada;
+        }
+        store.current.justificada.justificador_nombre = data.justificador_nombre;
+        store.current.justificada.justificador_clave = data.justificador_clave;
+        store.current.justificada.justificador_facultad = data.justificador_facultad;
+        store.current.justificada.justificador_rol = data.justificador_rol;
+        store.current.justificada.registro_fecha_justificacion = data.registro_fecha_justificacion;
     },
     registros: {
         data: [],
-        async fetch() {
+        async fetch(fecha, fecha_inicio, fecha_fin) {
             // if (!store.filters.facultad_id || !store.filters.periodo_id) return
             this.loading = true;
             this.data = [];
@@ -117,6 +153,12 @@ createApp({
                 facultad_id: 19,
                 periodo_id: 2,
             };
+            if (fecha)
+                params['fecha'] = fecha;
+            if (fecha_inicio)
+                params['fecha_inicio'] = fecha_inicio;
+            if (fecha_fin)
+                params['fecha_fin'] = fecha_fin;
             const paramsUrl = new URLSearchParams(params).toString();
             const res = await fetch(`action/action_auditoria.php?${paramsUrl}`, {
                 method: 'GET',
@@ -152,10 +194,6 @@ createApp({
                     perPage: 10,
             */
             return this.data.filter((registro) => {
-                if (store.filters.sin_registro && !registro.registro_fecha_supervisor)
-                    return true;
-                else if (store.filters.sin_registro)
-                    return false;
                 return filters.every((filtro) => {
                     switch (filtro) {
                         case 'fecha':
@@ -184,9 +222,13 @@ createApp({
                             return store.filters[filtro].includes(registro.estado_supervisor_id);
                         case 'bloque_horario':
                             const bloque = store.bloques_horario.data.find((bloque) => bloque.id === store.filters[filtro]);
-                            return registro.horario_hora <= bloque.hora_fin && registro.horario_fin >= bloque.hora_inicio;
-                        default:
-                            return true;
+                            return registro.horario_hora < bloque.hora_fin && registro.horario_fin > bloque.hora_inicio;
+                        default: {
+                            if (store.filters.sin_registro && !registro.registro_fecha_supervisor)
+                                return true;
+                            else if (store.filters.sin_registro)
+                                return false;
+                        }
                     }
                 });
             });
@@ -226,8 +268,22 @@ createApp({
             return Math.ceil(this.relevant.length / store.current.perPage);
         }
     },
+});
+createApp({
+    store,
+    get clase_vista() {
+        return store.current.clase_vista;
+    },
+    set_justificar(horario_id, profesor_id, registro_fecha_ideal) {
+        store.current.justificada = store.registros.relevant.find((registro) => registro.horario_id === horario_id && registro.profesor_id === profesor_id && registro.registro_fecha_ideal === registro_fecha_ideal);
+        store.current.clone_justificada = JSON.parse(JSON.stringify(store.current.justificada));
+    },
+    cancelar_justificacion() {
+        Object.assign(store.current.justificada, store.current.clone_justificada);
+        delete store.current.clone_justificada;
+    },
     get profesores() {
-        return this.registros.data
+        return store.registros.data
             .map((registro) => ({
             profesor_id: registro.profesor_id,
             profesor_nombre: registro.profesor_nombre,
@@ -245,7 +301,7 @@ createApp({
     },
     async mounted() {
         $('div.modal#cargando').modal('show');
-        await this.registros.fetch();
+        await store.registros.fetch();
         await store.facultades.fetch();
         await store.estados.fetch();
         await store.bloques_horario.fetch();

+ 84 - 34
ts/auditoría.ts

@@ -69,6 +69,12 @@ type Periodo = {
     periodo_nombre: string;
 }
 
+declare var $: any
+$('div.modal#cargando').modal({
+    backdrop: 'static',
+    keyboard: false,
+    show: false,
+})
 
 const store = reactive({
     loading: false,
@@ -80,6 +86,7 @@ const store = reactive({
         maxPages: 10,
         perPage: 10,
         modal_state: "Cargando datos...",
+        justificada: null,
     },
     facultades: {
         data: [] as Facultad[],
@@ -104,8 +111,12 @@ const store = reactive({
         async switchFechas() {
             const periodo = await fetch('action/periodo_datos.php');
             const periodo_data = await periodo.json() as Periodo;
-            // console.log(`Fecha inicio: ${periodo_data.periodo_fecha_inicio} Fecha fin: ${periodo_data.fecha_final}`);
 
+            if (!store.filters.switchFecha) {
+                $('div.modal#cargando').modal('show');
+                await store.registros.fetch()
+                $('div.modal#cargando').modal('hide');
+            }
             $(function () {
                 store.filters.fecha_inicio = store.filters.fecha_fin = store.filters.fecha = null
                 $("#fecha, #fecha_inicio, #fecha_fin").datepicker({
@@ -116,6 +127,7 @@ const store = reactive({
                 });
 
                 const fecha = $("#fecha"), inicio = $("#fecha_inicio"), fin = $("#fecha_fin")
+                fecha.datepicker("setDate", new Date(`${periodo_data.fecha_final}:00:00:00`));
                 inicio.on("change", function () {
                     store.filters.fecha_inicio = inicio.val()
                     fin.datepicker("option", "minDate", inicio.val());
@@ -124,10 +136,21 @@ const store = reactive({
                     store.filters.fecha_fin = fin.val()
                     inicio.datepicker("option", "maxDate", fin.val());
                 });
-                fecha.on("change", function () {
+                fecha.on("change", async function () {
                     store.filters.fecha = fecha.val()
+
+                    $('div.modal#cargando').modal('show');
+                    await store.registros.fetch(store.filters.fecha)
+                    $('div.modal#cargando').modal('hide');
+
                 });
             });
+        },
+        async fetchByDate() {
+            $('div.modal#cargando').modal('show');
+            await store.registros.fetch(undefined, store.filters.fecha_inicio, store.filters.fecha_fin);
+            store.current.page = 1;
+            $('div.modal#cargando').modal('hide');
         }
     },
     estados: {
@@ -173,39 +196,46 @@ const store = reactive({
         }
         return newArray
     },
-})
-
-declare var $: any
-
-
+    async justificar() {
+        if (!store.current.justificada) return;
+        let data;
+        try {
+            const res = await fetch('action/action_justificar.php', {
+                method: 'PUT',
+                headers: {
+                    'Content-Type': 'application/json'
+                },
+                body: JSON.stringify(store.current.justificada)
+            })
+            data = await res.json()
+        } catch (error) {
+            alert('Error al justificar')
+            store.current.justificada = store.current.clone_justificada
+        } finally {
+            delete store.current.clone_justificada
+        }
 
-type Profesor = {
-    profesor_id: number;
-    profesor_nombre: string;
-    profesor_correo: string;
-    profesor_clave: string;
-    profesor_grado: string;
-}
-$('div.modal#cargando').modal({
-    backdrop: 'static',
-    keyboard: false,
-    show: false,
-})
-createApp({
-    store,
-    get clase_vista() {
-        return store.current.clase_vista
+        store.current.justificada.justificador_nombre = data.justificador_nombre
+        store.current.justificada.justificador_clave = data.justificador_clave
+        store.current.justificada.justificador_facultad = data.justificador_facultad
+        store.current.justificada.justificador_rol = data.justificador_rol
+        store.current.justificada.registro_fecha_justificacion = data.registro_fecha_justificacion
     },
     registros: {
         data: [] as Registro[],
-        async fetch() {
+        async fetch(fecha?: Date, fecha_inicio?: Date, fecha_fin?: Date) {
             // if (!store.filters.facultad_id || !store.filters.periodo_id) return
+            
             this.loading = true
             this.data = [] as Registro[]
             const params = {
                 facultad_id: 19,
                 periodo_id: 2,
             }
+            if (fecha) params['fecha'] = fecha
+            if (fecha_inicio) params['fecha_inicio'] = fecha_inicio
+            if (fecha_fin) params['fecha_fin'] = fecha_fin
+
             const paramsUrl = new URLSearchParams(params as any).toString()
             const res = await fetch(`action/action_auditoria.php?${paramsUrl}`, {
                 method: 'GET',
@@ -221,7 +251,6 @@ createApp({
             store.current.comentario = registro.comentario
             $('#ver-comentario').modal('show')
         },
-
         get relevant() {
             /* 
                 facultad_id: null,
@@ -242,10 +271,6 @@ createApp({
                     perPage: 10,
             */
             return this.data.filter((registro: Registro) => {
-
-                if (store.filters.sin_registro && !registro.registro_fecha_supervisor) return true
-                else if (store.filters.sin_registro) return false
-
                 return filters.every((filtro) => {
                     switch (filtro) {
                         case 'fecha':
@@ -272,9 +297,11 @@ createApp({
                             return store.filters[filtro].includes(registro.estado_supervisor_id);
                         case 'bloque_horario':
                             const bloque = store.bloques_horario.data.find((bloque: Bloque_Horario) => bloque.id === store.filters[filtro]) as Bloque_Horario;
-                            return registro.horario_hora <= bloque.hora_fin && registro.horario_fin >= bloque.hora_inicio;
-                        default:
-                            return true;
+                            return registro.horario_hora < bloque.hora_fin && registro.horario_fin > bloque.hora_inicio;
+                        default: {
+                            if (store.filters.sin_registro && !registro.registro_fecha_supervisor) return true
+                            else if (store.filters.sin_registro) return false
+                        }
                     }
                 })
             })
@@ -313,8 +340,31 @@ createApp({
             return Math.ceil(this.relevant.length / store.current.perPage)
         }
     },
+})
+
+type Profesor = {
+    profesor_id: number;
+    profesor_nombre: string;
+    profesor_correo: string;
+    profesor_clave: string;
+    profesor_grado: string;
+}
+createApp({
+    store,
+    get clase_vista() {
+        return store.current.clase_vista
+    },
+
+    set_justificar(horario_id: Number, profesor_id: Number, registro_fecha_ideal: Date) {
+        store.current.justificada = store.registros.relevant.find((registro: Registro) => registro.horario_id === horario_id && registro.profesor_id === profesor_id && registro.registro_fecha_ideal === registro_fecha_ideal)
+        store.current.clone_justificada = JSON.parse(JSON.stringify(store.current.justificada))
+    },
+    cancelar_justificacion() {
+        Object.assign(store.current.justificada, store.current.clone_justificada)
+        delete store.current.clone_justificada
+    },
     get profesores() {
-        return this.registros.data
+        return store.registros.data
             .map((registro: Registro) => ({
                 profesor_id: registro.profesor_id,
                 profesor_nombre: registro.profesor_nombre,
@@ -336,7 +386,7 @@ createApp({
     async mounted() {
         $('div.modal#cargando').modal('show');
 
-        await this.registros.fetch()
+        await store.registros.fetch()
         await store.facultades.fetch()
         await store.estados.fetch()
         await store.bloques_horario.fetch()