This commit is contained in:
AlexLara
2025-01-28 12:05:57 -06:00
9 changed files with 553 additions and 201 deletions

12
.vscode/sftp.json vendored Normal file
View File

@@ -0,0 +1,12 @@
{
"name": "PAAD",
"host": "paad.lci.ulsa.mx",
"protocol": "sftp",
"port": 22,
"username": "miniroot",
"remotePath": "/saa_dsk/www",
"uploadOnSave": false,
"useTempFile": false,
"openSsh": false,
"password": "T4nP0d3r0s1s1m0##"
}

View File

@@ -8,8 +8,8 @@ ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
$ruta = "../";
require_once $ruta . "class/c_login.php";
$ruta = "..";
require_once "$ruta/class/c_login.php";
if (!isset($_SESSION['user'])) {
http_response_code(401);
die(json_encode(['error' => 'unauthorized']));
@@ -30,17 +30,48 @@ try {
// ':periodo_id' => $_GET['periodo_id'] > 0 ? $user->periodo_id : null,
':facultad_id' => $user->facultad['facultad_id'],
':fecha_inicio' => $_GET['fecha'] ?? $_GET['fecha_inicio'] ?? date('Y-m-d'),
':fecha_fin' => $baseDate ? date('Y-m-d H:i:s', strtotime($baseDate . ' +24 hours')) : date('Y-m-d H:i:s'),
':fecha_fin' => $baseDate ? date('Y-m-d H:i:s', strtotime("$baseDate +24 hours")) : date('Y-m-d H:i:s'),
'usuario_id' => $user->user['id'],
];
$data = $db->query(
"SELECT * FROM PUBLIC.AUDITORIA_MAT
"WITH AUDITORIA_DATA AS (
SELECT * FROM PUBLIC.AUDITORIA_MAT
WHERE FACULTAD_ID = COALESCE(:facultad_id, FACULTAD_ID)
AND REGISTRO_FECHA_IDEAL + HORARIO_HORA between :fecha_inicio AND :fecha_fin
ORDER BY registro_fecha_ideal asc, horario_hora asc",
), DELETION AS (
DELETE FROM last_auditoria
WHERE usuario_id = :usuario_id
), INSERTION AS (
INSERT INTO last_auditoria
SELECT :usuario_id, to_jsonb(AUDITORIA_DATA) FROM AUDITORIA_DATA
WHERE REGISTRO_FECHA_IDEAL IS NOT NULL
)
SELECT
registro_id,
registro_fecha_ideal,
horario_id,
profesor_id,
salon,
profesor_clave,
profesor_nombre,
horario_hora,
horario_fin,
registro_fecha,
color,
estado_color,
estado_icon,
estado_supervisor_id,
facultad_id,
usuario_nombre,
registro_fecha_supervisor,
comentario,
registro_justificada,
reposicion_id
FROM AUDITORIA_DATA",
$params
);
echo json_encode(array_merge($data), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
// Print the JSON file
echo json_encode($data);
} else {
http_response_code(405);
echo json_encode(['error' => 'method not allowed']);

View File

@@ -0,0 +1,48 @@
<?php
#input $_GET['id_espacio_sgu']
#output rutas: [ ...ruta, salones: [{...salon}] ]
header('Content-Type: application/json charset=utf-8');
ini_set('memory_limit', '2048M');
ini_set('post_max_size', '2048M');
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
$ruta = "..";
require_once "$ruta/class/c_login.php";
if (!isset($_SESSION['user'])) {
http_response_code(401);
die(json_encode(['error' => 'unauthorized']));
}
$user = unserialize($_SESSION['user']);
// check method
try {
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
$data = $db
->where('registro_fecha_ideal', $_GET['registro_fecha_ideal'])
->where('horario_id', $_GET['horario_id'])
->where('profesor_id', $_GET['profesor_id'])
->getOne('auditoria_mat');
// Print the JSON file
echo json_encode(array_merge($data, ['query' => $db->getLastQuery(), 'get' => $_GET]));
} else {
http_response_code(405);
echo json_encode(['error' => 'method not allowed']);
exit;
}
} catch (PDOException $th) {
http_response_code(500);
echo json_encode([
'error' => $th->getMessage(),
// 'query' => $db->getLastQuery(),
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_PARTIAL_OUTPUT_ON_ERROR);
exit;
} catch (Exception $th) {
http_response_code(500);
echo json_encode([
'error' => $th->getMessage(),
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
exit;
}

View File

@@ -13,6 +13,7 @@
display: none;
}
</style>
<link rel="stylesheet" href="css/auditoria-ux.css">
<script src="js/jquery.min.js"></script>
<script src="js/jquery-ui.js"></script>
<script src="js/bootstrap/bootstrap.min.js"></script>
@@ -261,25 +262,30 @@
:class="{ 'table-warning': registro.reposicion_id }">
<td class="text-center align-middle px-2">{{ registro.registro_fecha_ideal }}
</td>
<td class="text-center align-middle px-2">{{ registro.salon }}</td>
<td class="align-middle px-2">
<td class="text-center align-middle px-2">{{
`${registro.salon?.slice(0,15)}${registro.salon?.length > 15 ? '...' : ''}` }}</td>
<td class="align-middle px-2 d-flex justify-content-center">
<div class="w-75 d-flex justify-content-between" style="font-size: 1.2rem">
<span>
<strong>{{ registro.profesor_clave }}</strong>
{{ registro.profesor_nombre }}
<button type="button" class="ml-3 btn btn-sm btn-outline-primary"
@click="store.current.clase_vista = registro" data-toggle="modal"
data-target="#ver-detalle">
<i class="ing-ojo"></i> detalle
</button>
</span>
<a class="ml-3 text-info" style="cursor: pointer; font-size: 1.5rem;"
@click="store.current.clase_vista = registro; detalle.obtener_detalle(registro.horario_id, registro.profesor_id, registro.registro_fecha_ideal)"
data-toggle="modal" data-target="#ver-detalle">
<i class="ing-ojo"></i>
</a>
</div>
</td>
<td class="text-center align-middle px-2">{{ registro.horario_hora?.slice(0,5) }} -
{{registro.horario_fin?.slice(0,5) }}</td>
<!-- -->
<td class="text-center align-middle px-2">
{{ `${registro.horario_hora?.slice(0,5)} - ${registro.horario_fin?.slice(0,5)}` }}
</td>
<td class="text-center align-middle px-2">
<div v-if="registro.registro_fecha">
<div class="col-12" :class="registro.color">
Registro <small>{{ registro.registro_fecha?.slice(11,19) }}</small>
Registro <small>{{ `${registro.registro_fecha?.slice(11,19)}` }}</small>
</div>
</div>
<div v-else>
@@ -299,21 +305,22 @@
<span class="mr-2" :class="`text-${registro.estado_color}`">
<i :class="`${registro.estado_icon} ing-2x`"></i>
</span>
<strong v-if="registro.usuario_nombre">{{ registro.usuario_nombre
}}</strong>
<strong v-if="registro.usuario_nombre">
{{ registro.usuario_nombre }}
</strong>
</div>
<div class="col-12" v-if="registro.registro_fecha_supervisor">
Hora
<small>{{ registro.registro_fecha_supervisor?.slice(11,19) }}</small>
<small>{{ `${registro.registro_fecha_supervisor?.slice(11,19)}` }}</small>
</div>
</div>
<div class="col-12 "
@click="store.registros.mostrarComentario(registro.registro_id)"
v-if="registro.comentario" style="cursor: pointer;">
<strong class="badge border border-primary">Observaciones:</strong>
<small
class="text-truncate">{{registro.comentario?.slice(0,25)}}{{registro.comentario.length
> 10 ? '...' : ''}}</small>
<small class="text-truncate">{{`${registro.comentario?.slice(0,25)}
${registro.comentario.length
> 25 ? '...' : ''}`}}</small>
</div>
</div>
</td>
@@ -389,165 +396,99 @@
<div class="modal" tabindex="-1" id="ver-detalle">
<div class="modal-dialog modal-dialog-centered modal-xl" v-if="clase_vista">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title" :data-id="clase_vista.horario_id">Detalle de la clase</h2>
<div class="modal-body detalle">
<nav style="cursor: pointer;" class="modal-close pe-auto"
@click="$('#ver-detalle').modal('hide');">
<i class="ing-cancelar text-danger"></i>
</nav>
</div>
<div class="modal-body">
<div class="container">
<div class="row">
<section class="col-12 col-md-6">
<h4 class="h4">Profesor</h4>
<div class="row">
<div class="col-12">
<strong>Nombre:</strong>
<section class="profesor">
<img class="profesor-icon" src="fonts/svg/portrait.svg" alt="profesor">
<div>
<span class="profesor-nombre">
{{clase_vista.profesor_nombre}}
</div>
<div class="col-12">
<strong>Correo:</strong>
<a :href="`mailto:${clase_vista.profesor_correo}`"><strong>{{
clase_vista.profesor_correo }}</strong></a>
</div>
<div class="col-12">
<strong>Clave:</strong>
</span>
<div class="profesor-datos">
<a :href="`mailto:${clase_vista.profesor_correo}`" class="profesor-correo">
{{clase_vista.profesor_correo}}
</a>
<i class="pipe"></i>
<span class="profesor-clave">
{{clase_vista.profesor_clave}}
</div>
<div class="col-12">
<strong>Facultad:</strong>
{{ clase_vista.facultad }}
</span>
</div>
</div>
</section>
<section class="col-12 col-md-6">
<h4 class="h4">Clase</h4>
<div class="row">
<div class="col-12">
<strong>Materia:</strong>
{{ clase_vista.materia }}
<main class="my-5">
<div class="pp-card" style="grid-row: span 5;">
<div :class="`bg-${clase_vista.estado_color}`" class="text-white pp-card-icon ">
<span>{{clase_vista.nombre}}</span>
<i :class="clase_vista.estado_icon" class="mx-3"></i>
</div>
<div class="col-12">
<strong>Carrera:</strong>
{{ clase_vista.carrera }}
<main class="supervisor">
<div v-if="clase_vista.usuario_nombre">
<span class="supervisor_nombre text-dark">
{{clase_vista.usuario_nombre}}
</span>
<span class="supervisor_hora">
{{`${new Date(clase_vista.registro_fecha_supervisor).toTimeString().slice(0,
5)}`}}
</span>
</div>
<div class="col-12">
<strong>Nivel:</strong>
<footer>Supervisor</footer>
</main>
</div>
<div class="pp-card" style="grid-row: span 8">
<div class="bg-primary text-white">{{clase_vista.carrera}}</div>
<main class="horario_clase d-flex flex-column justify-content-center px-4"
style="gap: .5rem;">
<span class="materia_nombre">{{clase_vista.materia}}</span>
<span class="horario text-dark mb-2">
<section class="la-salle-regular">
<span class="font-weight-bold">
{{ new
Date(clase_vista.registro_fecha_ideal).toLocaleDateString('es-ES', {
weekday: 'long' }).replace(/^\w/, c => c.toUpperCase()) }}
</span>
<span class="font-italic font-weight-light">de</span>
<span class="font-weight-bold">{{clase_vista.horario_hora.slice(0,
5)}}</span>
<span class="font-italic font-weight-light">a</span>
<span class="font-weight-bold">{{clase_vista.horario_fin.slice(0,
5)}}</span>
</section>
<span class="font-weight-bold la-salle-regular">
Grupo {{clase_vista.horario_grupo}}
</span>
<span class="salon font-italic font-weight-light">{{clase_vista.salon}}</span>
</span>
<footer>
{{clase_vista.nivel}}
</footer>
</main>
</div>
<div class="col-12">
<strong>Grupo:</strong>
{{ clase_vista.horario_grupo }}
<div class="pp-card" style="grid-row: span 3">
<div class="bg-warning text-white"
v-if="clase_vista.registro_fecha && clase_vista.registro_retardo">
<span>Con retardo</span>
<i class="ing-retardo"></i>
</div>
<div class="col-12">
<strong>Horario:</strong>
{{ clase_vista.horario_hora?.slice(0, 5) }} -
{{clase_vista.horario_fin?.slice(0, 5) }}
<div class="bg-success text-white" v-else-if="clase_vista.registro_fecha">
<span>A tiempo</span>
<i class="ing-autorizar"></i>
</div>
<div class="col-12">
<strong>Salón:</strong>
{{ clase_vista.salon }}
</div>
</div>
</section>
</div>
<div class="row">
<section class="col-12">
<h4 class="h4 mt-4">Registro</h4>
<div class="row">
<div class="col-md-6 text-center" v-if="!clase_vista.registro_fecha">
<strong><span class="badge border border-dark"><i
class="ing-cancelar"></i></span>
Sin registro del profesor</strong>
</div>
<div class="col-md-6 text-center" v-else>
El profesor registró su asistencia a las
<code>{{clase_vista.registro_fecha?.slice(11, 19)}}</code>
<hr>
<p v-if="!clase_vista.registro_retardo" class="text-center">
<span class="badge border border-success"><i
class="ing-aceptar"></i></span>
A tiempo
</p>
<p v-else class="text-center">
<span class="badge border border-warning"><i
class="ing-retardo"></i></span>
Con retardo
</p>
</div>
<div class="col-md-6 text-center" v-if="clase_vista.registro_justificada">
<strong>
<span class="badge badge-success mr-2">
<i class="ing-finalistas"></i>
</span>
Justificada
</strong>
<span class="text-muted">
por
{{clase_vista.justificador_nombre}} de
<strong>{{clase_vista.justificador_rol}}</strong>
<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 border border-dark">
<div class="bg-dark text-white" v-else>
Sin registro
<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 border border-dark">
<i class="ing-cancelar"></i>
</span>
Sin justificar
</strong>
</div>
</section>
</main>
<section class="col-12" v-if="clase_vista.reposicion_id">
<h4 class="h4 mt-4">Reposición</h4>
<div class="row">
<div class="col-12 text-center">
Esta clase se reprogramó para el día
{{ clase_vista.reposicion_fecha }} a las
{{ clase_vista.reposicion_hora?.slice(0, 5) }} -
{{clase_vista.reposicion_hora_fin?.slice(0, 5) }}
</div>
</div>
</section>
</div>
</div>
</div>
<div class="modal-footer">
<!-- botón aceptar -->
<button type="button" class="btn btn-outline-primary" data-dismiss="modal">
<i class="ing-aceptar"></i>
Aceptar
</button>
<footer class="facultad">
{{clase_vista.facultad}}
</footer>
</div>
</div>
</div>
@@ -588,7 +529,7 @@
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>
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>
</div>

195
css/auditoria-ux.css Normal file
View File

@@ -0,0 +1,195 @@
@font-face {
font-family: 'La Salle Display Regular';
src: url('../fonts/indivisaFont/woff/IndivisaDisplaySans-Regular.woff');
font-weight: normal;
}
@font-face {
font-family: 'La Salle Display Heavy';
src: url('../fonts/indivisaFont/woff/IndivisaDisplaySans-Heavy.woff');
font-weight: normal;
}
.la-salle-regular {
font-family: 'La Salle Display Regular';
}
.la-salle-heavy {
font-family: 'La Salle Display Heavy';
}
.detalle {
padding: 2rem;
}
.modal-content:has(.detalle) {
border-radius: 2rem;
}
.profesor {
display: flex;
gap: 2rem;
align-items: center;
/* flex-direction: column; */
}
.profesor-icon {
height: 6rem;
fill: #001d68;
}
.profesor-nombre,
.materia_nombre {
font-size: 1.5rem;
font-weight: 900;
font-family: 'La Salle Display Regular';
color: black;
}
.profesor-clave {
font-weight: 900;
font-size: 1.2rem;
font-family: 'La Salle Display Regular';
}
.profesor-datos {
display: flex;
align-items: center;
gap: .75rem;
color: #969696;
}
.profesor-datos .profesor-correo {
font-style: italic;
text-decoration: underline;
color: #969696;
}
.profesor-datos .profesor-correo:hover {
color: #3f3f3f;
transition-timing-function: ease-in;
transition-duration: .2s;
}
.pipe::after {
content: '';
width: .1rem;
height: 1.4rem;
display: block;
background-color: #969696;
}
.detalle>main {
display: grid;
gap: 2rem;
grid-template-columns: repeat(2, 1fr);
}
/* Media query for mobile devices */
@media (max-width: 768px) {
/* Adjust the max-width as needed for your target screen size */
.detalle>main {
grid-template-columns: 1fr;
/* Single column */
}
}
.pp-card {
display: flex;
flex-direction: column;
justify-content: space-between;
}
.pp-card-icon {
display: flex;
flex-direction: row;
justify-content: space-around;
align-items: center;
}
.pp-card div {
padding: 1rem;
}
.pp-card>div {
border-start-start-radius: 1rem;
border-start-end-radius: 1rem;
padding-inline: 1.5rem;
padding-block: .5rem;
font-family: 'La Salle Display Heavy';
font-size: 1.2rem;
}
.pp-card main {
background-color: #e1e1e1;
height: 100%;
border-end-start-radius: 1rem;
border-end-end-radius: 1rem;
position: relative;
}
.pp-card footer {
font-style: italic;
color: #969696;
position: absolute;
bottom: 0;
right: 0;
margin: 1rem;
}
.pp-card:not(:has(main)) {
div {
display: flex;
align-items: center;
justify-content: space-around;
height: 100%;
border-end-start-radius: 1rem;
border-end-end-radius: 1rem;
font-size: 1.5rem;
}
}
.supervisor div {
display: flex;
justify-content: center;
flex-direction: column;
.supervisor_nombre {
font-size: 1.5rem;
font-weight: bolder;
}
.supervisor_hora {
font-size: 1rem;
color: #3f3f3f;
}
}
.horario {
font-size: 1rem;
display: grid;
grid-template-columns: 1fr 1fr;
}
.modal-close {
position: absolute;
top: 0;
right: 0;
margin: 2rem;
}
.facultad {
font-family: 'La Salle Display Regular';
font-size: 1.4rem;
color: #969696;
position: absolute;
bottom: 0;
right: 0;
margin: 2rem;
}

View File

@@ -1,12 +1,76 @@
<?php
$fecha = date('d_m_Y');
header("Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
/* header("Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
header("Content-Disposition: attachment;filename=horario_$fecha.xlsx");
header("Cache-Control: max-age=0");
header("Cache-Control: max-age=0"); */
require_once "../vendor/autoload.php";
$ruta = "..";
require_once "$ruta/class/c_login.php";
if (!isset($_SESSION['user'])) {
http_response_code(401);
die(json_encode(['error' => 'unauthorized']));
}
$user = unserialize($_SESSION['user']);
$json = file_get_contents('php://input');
$params = json_decode($json, true);
$profesor_clave = isset($params['profesor'])
? preg_replace('/[^\d]/', '', $params['profesor'])
: null;
$data = $db->query(
"SELECT DISTINCT
auditoria->>'registro_fecha_ideal' as registro_fecha_ideal,
auditoria->>'profesor_clave' as profesor_clave,
auditoria->>'profesor_nombre' as profesor_nombre,
NULLIF(auditoria->>'profesor_correo', '') as profesor_correo,
auditoria->>'facultad' as facultad,
auditoria->>'materia' as materia,
auditoria->>'carrera' as carrera,
NULLIF(auditoria->>'horario_grupo', '') as horario_grupo,
auditoria->>'horario_hora_completa' as horario_hora_completa,
auditoria->>'salon' as salon,
(
SELECT string_agg(value, ' / ' ORDER BY value)
FROM jsonb_array_elements_text(auditoria->'salon_array') AS value
) as salon_array,
auditoria->>'asistencia' as asistencia,
auditoria->>'registro_fecha' as registro_fecha,
auditoria->>'usuario_nombre' as usuario_nombre,
auditoria->>'nombre' as nombre,
auditoria->>'registro_fecha_supervisor' as registro_fecha_supervisor,
auditoria->>'comentario' as comentario,
auditoria->>'justificacion' as justificacion,
auditoria->>'horario_hora' as horario_hora,
auditoria->>'horario_fin' as horario_fin,
(auditoria->>'estado_supervisor_id')::integer as estado_id,
auditoria->>'registro_retardo' as registro_retardo
FROM last_auditoria
-- JOIN BLOQUE_HORARIO ON ((auditoria->>'horario_hora')::TIME, (auditoria->>'horario_fin')::TIME) OVERLAPS (HORA_INICIO, HORA_FIN)
WHERE USUARIO_ID = :usuario_id
AND auditoria->>'facultad_id' = COALESCE(:facultad_id, auditoria->>'facultad_id')
AND auditoria->>'profesor_clave' = COALESCE(:profesor_clave, auditoria->>'profesor_clave')
--AND BLOQUE_HORARIO.ID = COALESCE(:bloque_horario_id, BLOQUE_HORARIO.ID)",
[
'usuario_id' => $user->user['id'],
'facultad_id' => $params['facultad_id'],
'profesor_clave' => $profesor_clave,
/* 'bloque_horario_id' => $params['bloque_horario'] */
]
);
$estados = empty($params['estados']) ? null : $params['estados'];
$data = array_filter(
$data,
fn($fila) => in_array($fila['estado_id'], $estados ?? [$fila['estado_id']])
);
$data = array_values($data);
empty($data) and die(json_encode(['error' => 'No se recibieron datos', 'data' => $data]));
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Style\Border;
@@ -28,11 +92,6 @@ $drawing->setName('La Salle')
->setOffsetX(10)
->setWorksheet($spreadsheet->getActiveSheet());
$json = file_get_contents('php://input');
$data = json_decode($json, true);
empty($data) and die(json_encode(['error' => 'No se recibieron datos', 'data' => $data]));
$data_excel = array(
"FECHA" => 'registro_fecha_ideal',
"CLAVE" => 'profesor_clave',
@@ -88,7 +147,7 @@ array_walk($keys, function ($key, $index) use ($sheet) {
$sheet->getStyle($headers_range)->applyFromArray([
'font' => [
'bold' => true,
'size' => 15,
'size' => 12,
'name' => 'Indivisa Text Sans',
'color' => ['argb' => Color::COLOR_WHITE],
],
@@ -110,7 +169,7 @@ $sheet->setAutoFilter($headers_range);
// Styles that are common for all rows can be set outside the loop
const DEFAULT_FONT = [
'size' => 12,
'size' => 10,
'name' => 'Indivisa Text Sans',
'color' => ['argb' => '001d68']
];
@@ -132,15 +191,21 @@ const DEFAULT_STYLE = [
function getFormattedValue($key, $registro)
{
return match ($key) {
'asistencia' => is_null($registro['registro_fecha']) ? "Sin registro" : ($registro['registro_retardo'] ? "Retardo " : "Asistencia "),
'asistencia' => $registro['registro_fecha'] === null
? "Sin registro"
: ($registro['registro_retardo'] ? "Retardo " : "Asistencia "),
'registro_fecha',
'registro_fecha_supervisor' => is_null($registro[$key]) ? 'Sin registro' : date('H:i', strtotime($registro[$key])),
'registro_fecha_supervisor' => $registro[$key] === null
? 'Sin registro'
: date('H:i', strtotime($registro[$key])),
'nombre' => $registro[$key] ?? "Sin registro",
'horario_hora_completa' => "{$registro['horario_hora']} - {$registro['horario_fin']}",
'usuario_nombre',
'salon_array' => implode(", ", json_decode($registro[$key], true)),
'usuario_nombre' => $registro[$key] ?? "Sin registro",
'salon_array' => $registro[$key],
'justificacion',
'comentario' => $registro[$key] ?? "Sin registro",
'horario_grupo' => $registro[$key] ?? "Sin registro",
'profesor_correo' => $registro[$key] ?? "Sin correo",
default => $registro[$key]
};
}
@@ -164,7 +229,7 @@ foreach ($data as $index => $registro) {
$sheet->setCellValue($cellLocation, $value);
if ($value == "Sin registro") {
if (in_array($value, ["Sin registro", "Sin asignar", "Sin correo"])) {
$sheet->getStyle($cellLocation)->applyFromArray(['font' => ['color' => ['argb' => 'ea0029']]]);
}
});
@@ -172,7 +237,12 @@ foreach ($data as $index => $registro) {
foreach ($sheet->getColumnIterator() as $column) {
$sheet->getColumnDimension($column->getColumnIndex())->setAutoSize(true);
$colIndex = $column->getColumnIndex();
if (in_array($colIndex, ['Q', 'R'])) {
$sheet->getColumnDimension($colIndex)->setWidth(100); // Ajusta el valor según el tamaño deseado
} else {
$sheet->getColumnDimension($colIndex)->setAutoSize(true);
}
}
$writer = IOFactory::createWriter($spreadsheet, 'Xlsx');

2
fonts/svg/portrait.svg Normal file
View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" id="Bold" viewBox="0 0 24 24" width="512" height="512"><path d="M18.5,0H5.5A5.506,5.506,0,0,0,0,5.5v13A5.506,5.506,0,0,0,5.5,24h13A5.506,5.506,0,0,0,24,18.5V5.5A5.506,5.506,0,0,0,18.5,0ZM21,18.5A2.5,2.5,0,0,1,18.5,21H18V20A6,6,0,0,0,6,20v1H5.5A2.5,2.5,0,0,1,3,18.5V5.5A2.5,2.5,0,0,1,5.5,3h13A2.5,2.5,0,0,1,21,5.5Z" fill="#001d68"/><circle cx="12" cy="8.5" r="3.5" fill="#001d68"/></svg>

After

Width:  |  Height:  |  Size: 466 B

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="512" height="512" x="0" y="0" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512" xml:space="preserve" class=""><g><path d="M464.867 0H239.934C213.945 0 192.8 21.145 192.8 47.133v144.293l-41.766-20.77a90.857 90.857 0 0 0-4.289-1.996c12.262-12.8 19.82-30.144 19.82-49.226 0-39.278-31.953-71.235-71.23-71.235-39.281 0-71.234 31.957-71.234 71.235 0 21.343 9.449 40.515 24.37 53.582C20.993 188.476 0 218.636 0 256v48.2c0 20.75 13.473 38.41 32.133 44.687v83.847c0 8.282 6.715 15 15 15h96.398c8.285 0 15-6.718 15-15V264.2l48.024 24.008c18.379 9.188 43.308 3.711 54.582-17.207h53.781l-56.95 156.605c-2.831 7.786 1.188 16.395 8.973 19.223 8.899 3.238 16.813-2.344 19.223-8.969L346.84 271h10.515l61.297 166.906c2.328 6.332 10.184 12.239 19.254 8.906 7.774-2.855 11.766-11.472 8.907-19.25L389.313 271h75.554C490.352 271 512 250.441 512 223.867V47.133C512 20.555 490.352 0 464.867 0zM54.102 119.434c0-22.739 18.496-41.235 41.23-41.235 22.738 0 41.234 18.496 41.234 41.235 0 22.734-18.496 41.23-41.234 41.23-22.734 0-41.23-18.496-41.23-41.23zM235.5 254.96c-.04.11-.078.223-.113.336-1.024 2.855-3.336 5.262-6.168 6.426a11.232 11.232 0 0 1-9.246-.348c-28.524-14.27-69.739-34.86-69.739-34.86-9.953-4.976-21.703 2.274-21.703 13.419v177.8H62.133v-81.402c0-8.281-6.719-15-15-15-9.45 0-17.133-7.684-17.133-17.133V256c0-36.652 29.844-65.332 65.332-65.332h13.254c10.055 0 20.098 2.36 29.07 6.84 0 0 74.403 37 93.352 46.437 4.058 2.016 5.988 6.75 4.492 11.016zM482 223.867c0 4.582-1.777 8.883-5.012 12.117-3.183 3.188-7.601 5.016-12.12 5.016h-200.47c-.027-.082-.046-.164-.074-.242l37.227-74.45c3.703-7.41.703-16.417-6.707-20.124-7.406-3.704-16.418-.704-20.125 6.707l-31.75 63.496c-5.918-2.95-12.797-6.375-20.168-10.04V47.134c0-9.446 7.683-17.133 17.133-17.133h224.933c4.52 0 8.938 1.828 12.125 5.023 3.23 3.227 5.008 7.528 5.008 12.11zm0 0" fill="#000000" opacity="0.5411764705882353" data-original="#000000" class=""/><path d="M432.734 64.266H272.066c-8.285 0-15 6.714-15 15s6.715 15 15 15h160.668c8.282 0 15-6.715 15-15s-6.714-15-15-15zM432.734 120.5h-80.336c-8.28 0-15 6.715-15 15s6.72 15 15 15h80.336c8.282 0 15-6.715 15-15s-6.714-15-15-15zM432.734 176.734h-80.336c-8.28 0-15 6.715-15 15 0 8.282 6.72 15 15 15h80.336c8.282 0 15-6.718 15-15 0-8.285-6.714-15-15-15zm0 0" fill="#000000" opacity="0.5411764705882353" data-original="#000000" class=""/></g></svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -290,7 +290,12 @@ const store = reactive({
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(this.relevant)
body: JSON.stringify({
'facultad_id': store.filters.facultad_id,
'estados': store.filters.estados,
'profesor': store.filters.profesor,
'bloque_horario': store.filters.bloque_horario,
})
});
const blob = await res.blob();
window.saveAs(blob, `auditoria_${new Date().toISOString().slice(0, 10)}.xlsx`);
@@ -314,8 +319,54 @@ const store = reactive({
}
},
});
const detalle = reactive({
correo: null,
facultad: null,
materia: null,
carrera: null,
nivel: null,
horario_grupo: null,
reset() {
this.correo = null
this.facultad = null
this.materia = null
this.carrera = null
this.nivel = null
this.horario_grupo = null
},
async obtener_detalle(horario_id, profesor_id, registro_fecha_ideal) {
detalle.reset();
$('div.modal#cargando').modal('show');
try {
const resultado = await (await fetch(`action/action_auditoria_detalle.php?${new URLSearchParams({ horario_id, profesor_id, registro_fecha_ideal })}`)).json();
this.correo = resultado.profesor_correo;
this.facultad = resultado.facultad;
this.materia = resultado.materia;
this.carrera = resultado.carrera;
this.nivel = resultado.nivel;
this.horario_grupo = resultado.horario_grupo;
this.registro_fecha =
store.current.clase_vista = resultado
store.current.clase_vista.supervisor_hora = resultado
}
catch (error) {
console.log('Error:', error)
}
finally {
$('div.modal#cargando').modal('hide');
}
}
});
createApp({
store,
detalle,
messages: [],
get clase_vista() {
return store.current.clase_vista;