diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..20e2971 --- /dev/null +++ b/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "dompdf/dompdf": "^3.1" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..ea07e71 --- /dev/null +++ b/composer.lock @@ -0,0 +1,454 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "00da199c24f69d4e39903c3676bfe248", + "packages": [ + { + "name": "dompdf/dompdf", + "version": "v3.1.4", + "source": { + "type": "git", + "url": "https://github.com/dompdf/dompdf.git", + "reference": "db712c90c5b9868df3600e64e68da62e78a34623" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/dompdf/zipball/db712c90c5b9868df3600e64e68da62e78a34623", + "reference": "db712c90c5b9868df3600e64e68da62e78a34623", + "shasum": "" + }, + "require": { + "dompdf/php-font-lib": "^1.0.0", + "dompdf/php-svg-lib": "^1.0.0", + "ext-dom": "*", + "ext-mbstring": "*", + "masterminds/html5": "^2.0", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "ext-gd": "*", + "ext-json": "*", + "ext-zip": "*", + "mockery/mockery": "^1.3", + "phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11", + "squizlabs/php_codesniffer": "^3.5", + "symfony/process": "^4.4 || ^5.4 || ^6.2 || ^7.0" + }, + "suggest": { + "ext-gd": "Needed to process images", + "ext-gmagick": "Improves image processing performance", + "ext-imagick": "Improves image processing performance", + "ext-zlib": "Needed for pdf stream compression" + }, + "type": "library", + "autoload": { + "psr-4": { + "Dompdf\\": "src/" + }, + "classmap": [ + "lib/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1" + ], + "authors": [ + { + "name": "The Dompdf Community", + "homepage": "https://github.com/dompdf/dompdf/blob/master/AUTHORS.md" + } + ], + "description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter", + "homepage": "https://github.com/dompdf/dompdf", + "support": { + "issues": "https://github.com/dompdf/dompdf/issues", + "source": "https://github.com/dompdf/dompdf/tree/v3.1.4" + }, + "time": "2025-10-29T12:43:30+00:00" + }, + { + "name": "dompdf/php-font-lib", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/dompdf/php-font-lib.git", + "reference": "a6e9a688a2a80016ac080b97be73d3e10c444c9a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/php-font-lib/zipball/a6e9a688a2a80016ac080b97be73d3e10c444c9a", + "reference": "a6e9a688a2a80016ac080b97be73d3e10c444c9a", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11 || ^12" + }, + "type": "library", + "autoload": { + "psr-4": { + "FontLib\\": "src/FontLib" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-or-later" + ], + "authors": [ + { + "name": "The FontLib Community", + "homepage": "https://github.com/dompdf/php-font-lib/blob/master/AUTHORS.md" + } + ], + "description": "A library to read, parse, export and make subsets of different types of font files.", + "homepage": "https://github.com/dompdf/php-font-lib", + "support": { + "issues": "https://github.com/dompdf/php-font-lib/issues", + "source": "https://github.com/dompdf/php-font-lib/tree/1.0.2" + }, + "time": "2026-01-20T14:10:26+00:00" + }, + { + "name": "dompdf/php-svg-lib", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/dompdf/php-svg-lib.git", + "reference": "8259ffb930817e72b1ff1caef5d226501f3dfeb1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/8259ffb930817e72b1ff1caef5d226501f3dfeb1", + "reference": "8259ffb930817e72b1ff1caef5d226501f3dfeb1", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^7.1 || ^8.0", + "sabberworm/php-css-parser": "^8.4 || ^9.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11" + }, + "type": "library", + "autoload": { + "psr-4": { + "Svg\\": "src/Svg" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "The SvgLib Community", + "homepage": "https://github.com/dompdf/php-svg-lib/blob/master/AUTHORS.md" + } + ], + "description": "A library to read, parse and export to PDF SVG files.", + "homepage": "https://github.com/dompdf/php-svg-lib", + "support": { + "issues": "https://github.com/dompdf/php-svg-lib/issues", + "source": "https://github.com/dompdf/php-svg-lib/tree/1.0.2" + }, + "time": "2026-01-02T16:01:13+00:00" + }, + { + "name": "masterminds/html5", + "version": "2.10.0", + "source": { + "type": "git", + "url": "https://github.com/Masterminds/html5-php.git", + "reference": "fcf91eb64359852f00d921887b219479b4f21251" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/fcf91eb64359852f00d921887b219479b4f21251", + "reference": "fcf91eb64359852f00d921887b219479b4f21251", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Masterminds\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matt Butcher", + "email": "technosophos@gmail.com" + }, + { + "name": "Matt Farina", + "email": "matt@mattfarina.com" + }, + { + "name": "Asmir Mustafic", + "email": "goetas@gmail.com" + } + ], + "description": "An HTML5 parser and serializer.", + "homepage": "http://masterminds.github.io/html5-php", + "keywords": [ + "HTML5", + "dom", + "html", + "parser", + "querypath", + "serializer", + "xml" + ], + "support": { + "issues": "https://github.com/Masterminds/html5-php/issues", + "source": "https://github.com/Masterminds/html5-php/tree/2.10.0" + }, + "time": "2025-07-25T09:04:22+00:00" + }, + { + "name": "sabberworm/php-css-parser", + "version": "v9.1.0", + "source": { + "type": "git", + "url": "https://github.com/MyIntervals/PHP-CSS-Parser.git", + "reference": "1b363fdbdc6dd0ca0f4bf98d3a4d7f388133f1fb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/1b363fdbdc6dd0ca0f4bf98d3a4d7f388133f1fb", + "reference": "1b363fdbdc6dd0ca0f4bf98d3a4d7f388133f1fb", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": "^7.2.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "thecodingmachine/safe": "^1.3 || ^2.5 || ^3.3" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "1.4.0", + "phpstan/extension-installer": "1.4.3", + "phpstan/phpstan": "1.12.28 || 2.1.25", + "phpstan/phpstan-phpunit": "1.4.2 || 2.0.7", + "phpstan/phpstan-strict-rules": "1.6.2 || 2.0.6", + "phpunit/phpunit": "8.5.46", + "rawr/phpunit-data-provider": "3.3.1", + "rector/rector": "1.2.10 || 2.1.7", + "rector/type-perfect": "1.0.0 || 2.1.0" + }, + "suggest": { + "ext-mbstring": "for parsing UTF-8 CSS" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Sabberworm\\CSS\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Raphael Schweikert" + }, + { + "name": "Oliver Klee", + "email": "github@oliverklee.de" + }, + { + "name": "Jake Hotson", + "email": "jake.github@qzdesign.co.uk" + } + ], + "description": "Parser for CSS Files written in PHP", + "homepage": "https://www.sabberworm.com/blog/2010/6/10/php-css-parser", + "keywords": [ + "css", + "parser", + "stylesheet" + ], + "support": { + "issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues", + "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v9.1.0" + }, + "time": "2025-09-14T07:37:21+00:00" + }, + { + "name": "thecodingmachine/safe", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/safe.git", + "reference": "2cdd579eeaa2e78e51c7509b50cc9fb89a956236" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/safe/zipball/2cdd579eeaa2e78e51c7509b50cc9fb89a956236", + "reference": "2cdd579eeaa2e78e51c7509b50cc9fb89a956236", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpstan/phpstan": "^2", + "phpunit/phpunit": "^10", + "squizlabs/php_codesniffer": "^3.2" + }, + "type": "library", + "autoload": { + "files": [ + "lib/special_cases.php", + "generated/apache.php", + "generated/apcu.php", + "generated/array.php", + "generated/bzip2.php", + "generated/calendar.php", + "generated/classobj.php", + "generated/com.php", + "generated/cubrid.php", + "generated/curl.php", + "generated/datetime.php", + "generated/dir.php", + "generated/eio.php", + "generated/errorfunc.php", + "generated/exec.php", + "generated/fileinfo.php", + "generated/filesystem.php", + "generated/filter.php", + "generated/fpm.php", + "generated/ftp.php", + "generated/funchand.php", + "generated/gettext.php", + "generated/gmp.php", + "generated/gnupg.php", + "generated/hash.php", + "generated/ibase.php", + "generated/ibmDb2.php", + "generated/iconv.php", + "generated/image.php", + "generated/imap.php", + "generated/info.php", + "generated/inotify.php", + "generated/json.php", + "generated/ldap.php", + "generated/libxml.php", + "generated/lzf.php", + "generated/mailparse.php", + "generated/mbstring.php", + "generated/misc.php", + "generated/mysql.php", + "generated/mysqli.php", + "generated/network.php", + "generated/oci8.php", + "generated/opcache.php", + "generated/openssl.php", + "generated/outcontrol.php", + "generated/pcntl.php", + "generated/pcre.php", + "generated/pgsql.php", + "generated/posix.php", + "generated/ps.php", + "generated/pspell.php", + "generated/readline.php", + "generated/rnp.php", + "generated/rpminfo.php", + "generated/rrd.php", + "generated/sem.php", + "generated/session.php", + "generated/shmop.php", + "generated/sockets.php", + "generated/sodium.php", + "generated/solr.php", + "generated/spl.php", + "generated/sqlsrv.php", + "generated/ssdeep.php", + "generated/ssh2.php", + "generated/stream.php", + "generated/strings.php", + "generated/swoole.php", + "generated/uodbc.php", + "generated/uopz.php", + "generated/url.php", + "generated/var.php", + "generated/xdiff.php", + "generated/xml.php", + "generated/xmlrpc.php", + "generated/yaml.php", + "generated/yaz.php", + "generated/zip.php", + "generated/zlib.php" + ], + "classmap": [ + "lib/DateTime.php", + "lib/DateTimeImmutable.php", + "lib/Exceptions/", + "generated/Exceptions/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHP core functions that throw exceptions instead of returning FALSE on error", + "support": { + "issues": "https://github.com/thecodingmachine/safe/issues", + "source": "https://github.com/thecodingmachine/safe/tree/v3.3.0" + }, + "funding": [ + { + "url": "https://github.com/OskarStark", + "type": "github" + }, + { + "url": "https://github.com/shish", + "type": "github" + }, + { + "url": "https://github.com/staabm", + "type": "github" + } + ], + "time": "2025-05-14T06:15:44+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.6.0" +} diff --git a/public/admin/export-pdf.php b/public/admin/export-pdf.php new file mode 100644 index 0000000..d274b4a --- /dev/null +++ b/public/admin/export-pdf.php @@ -0,0 +1,95 @@ +requireAdmin(); + +$userModel = new User(); +$horariosModel = new DiasHorarios(); +$asignacionModel = new Asignacion(); +$db = \Database::getInstance()->getConnection(); + +$totalUsuarios = count($userModel->getAll()); +$totalAyudantes = count($userModel->getAyudantesActivos()); +$totalHorarios = count($horariosModel->getAll()); +$asignacionActual = $asignacionModel->getAsignacionActual(); + +$ayudantes = $userModel->getAyudantesActivos(); +$horarios = $horariosModel->getAll(); +$asignaciones = $asignacionModel->getTodasAsignaciones(); + +$html = PDFGenerator::getStyles(); +$html .= PDFGenerator::getHeader('Reporte de Administración'); + +$html .= '

Estadísticas

'; +$html .= ''; +$html .= ''; +$html .= ''; +$html .= ''; +$html .= ''; +$html .= ''; +$html .= ''; +$html .= ''; +$html .= ''; +$html .= '
Total UsuariosAyudantes ActivosDías ConfiguradosTurno Actual
' . $totalUsuarios . '' . $totalAyudantes . '' . $totalHorarios . '' . ($asignacionActual ? htmlspecialchars($asignacionActual['nombre']) : 'Sin asignar') . '
'; + +$html .= '

Ayudantes

'; +$html .= ''; +$html .= ''; +$html .= ''; + +foreach ($userModel->getAll() as $u) { + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; +} + +$html .= '
NombreEmailUsernameEstado
' . htmlspecialchars($u['nombre']) . '' . htmlspecialchars($u['email']) . '' . htmlspecialchars($u['username'] ?: '-') . '' . ($u['activo'] ? 'Activo' : 'Inactivo') . '
'; + +$html .= '

Horarios de Apertura

'; +$html .= ''; +$html .= ''; +$html .= ''; + +foreach ($horarios as $h) { + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; +} + +$html .= '
DíaHora AperturaHora CierreEstado
' . ucfirst($h['dia_semana']) . '' . date('H:i', strtotime($h['hora_apertura'])) . '' . date('H:i', strtotime($h['hora_cierre'])) . '' . ($h['activo'] ? 'Abierto' : 'Cerrado') . '
'; + +$html .= '

Historial de Asignaciones

'; +$html .= ''; +$html .= ''; +$html .= ''; + +$asignacionesLimit = array_slice($asignaciones, 0, 20); +foreach ($asignacionesLimit as $a) { + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; +} + +$html .= '
Semana InicioSemana FinAyudante
' . date('d/m/Y', strtotime($a['semana_inicio'])) . '' . date('d/m/Y', strtotime($a['semana_fin'])) . '' . htmlspecialchars($a['nombre']) . '
'; + +$html .= PDFGenerator::getFooter(); + +$pdf = new PDFGenerator(); +$pdf->download($html, 'reporte-admin-' . date('Y-m-d') . '.pdf'); diff --git a/public/admin/index.php b/public/admin/index.php index aacc60c..7f2aca9 100755 --- a/public/admin/index.php +++ b/public/admin/index.php @@ -35,7 +35,12 @@ $pageTitle = 'Dashboard';
-

Panel de Administración

+
+

Panel de Administración

+ + 📄 Exportar PDF + +
diff --git a/public/ayudante.php b/public/ayudante.php index c1eecf5..79dd2fd 100755 --- a/public/ayudante.php +++ b/public/ayudante.php @@ -84,7 +84,12 @@ $domingo->modify('-' . (int)$domingo->format('w') . ' days');
-

Mis Turnos

+
+

Mis Turnos

+ + 📄 Exportar PDF + +
requireAuth(); + +if ($auth->isAdmin()) { + header('Location: /admin/index.php'); + exit; +} + +$user = $auth->getCurrentUser(); +$horariosModel = new DiasHorarios(); +$asignacionModel = new Asignacion(); +$db = Database::getInstance()->getConnection(); + +$horarios = $horariosModel->getActivos(); +$userModel = new User(); +$ayudantes = $userModel->getAyudantesActivos(); + +$semanasFuturas = []; +$hoy = new DateTime(); +$diaSemana = (int)$hoy->format('w'); +$domingoEstaSemana = clone $hoy; +$domingoEstaSemana->modify('-' . $diaSemana . ' days'); + +for ($i = 0; $i <= 4; $i++) { + $semanaDomingo = clone $domingoEstaSemana; + $semanaDomingo->modify("+{$i} weeks"); + + $semanaInicio = $semanaDomingo->format('Y-m-d'); + $asignacionesSemana = $asignacionModel->getTodasAsignacionesPorSemana($semanaInicio); + + $semanasFuturas[] = [ + 'inicio' => $semanaInicio, + 'fin' => date('Y-m-d', strtotime('+5 days', strtotime($semanaInicio))), + 'asignaciones' => $asignacionesSemana, + ]; +} + +$diasNombres = [ + 'domingo' => 'Domingo', + 'lunes' => 'Lunes', + 'martes' => 'Martes', + 'miercoles' => 'Miércoles', + 'jueves' => 'Jueves', + 'viernes' => 'Viernes', + 'sabado' => 'Sábado' +]; +$diasOrden = ['domingo', 'lunes', 'martes', 'miercoles', 'jueves', 'viernes', 'sabado']; + +$html = PDFGenerator::getStyles(); +$html .= PDFGenerator::getHeader('Horarios y Turnos - Ayudante: ' . $user['nombre']); + +$html .= '

Mis Turnos

'; +$html .= ''; +$html .= ''; +$html .= ''; + +foreach ($semanasFuturas as $index => $semana) { + $esMiTurno = !empty($semana['asignaciones']) && in_array($user['id'], array_column($semana['asignaciones'], 'id')); + + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; +} + +$html .= '
SemanaPeríodoEstado
' . date('d/m/Y', strtotime($semana['inicio'])); + if ($index === 0) { + $html .= ' Actual'; + } + if ($esMiTurno) { + $html .= ' Tu turno'; + } + $html .= '' . date('d/m/Y', strtotime($semana['inicio'])) . ' - ' . date('d/m/Y', strtotime($semana['fin'])) . '' . ($esMiTurno ? 'Asignado' : 'Sin asignar') . '
'; + +$html .= '

Horarios por Semana

'; +$html .= ''; +$html .= ''; +foreach ($diasOrden as $dia) { + $html .= ''; +} +$html .= ''; +$html .= ''; + +foreach ($semanasFuturas as $index => $semana) { + $esMiTurno = !empty($semana['asignaciones']) && in_array($user['id'], array_column($semana['asignaciones'], 'id')); + + $html .= ''; + $html .= ''; + + foreach ($diasOrden as $dia) { + $horarioDia = null; + foreach ($horarios as $h) { + if ($h['dia_semana'] === $dia) { + $horarioDia = $h; + break; + } + } + $esActivo = $horarioDia && $horarioDia['activo']; + + if ($esMiTurno && $esActivo) { + $html .= ''; + } elseif (!$esActivo) { + $html .= ''; + } else { + $html .= ''; + } + } + $html .= ''; +} + +$html .= '
Semana' . $diasNombres[$dia] . '
' . date('d/m', strtotime($semana['inicio'])); + if ($index === 0) { + $html .= ' Actual'; + } + $html .= ''; + $html .= date('H:i', strtotime($horarioDia['hora_apertura'])) . '
'; + $html .= date('H:i', strtotime($horarioDia['hora_cierre'])); + $html .= '
Cerrado'; + $html .= date('H:i', strtotime($horarioDia['hora_apertura'])) . '
'; + $html .= date('H:i', strtotime($horarioDia['hora_cierre'])); + $html .= '
'; +$html .= PDFGenerator::getFooter(); + +$pdf = new PDFGenerator(); +$pdf->download($html, 'mis-turnos-' . date('Y-m-d') . '.pdf'); diff --git a/src/Asignacion.php b/src/Asignacion.php index 4242390..17d7e78 100755 --- a/src/Asignacion.php +++ b/src/Asignacion.php @@ -10,7 +10,12 @@ class Asignacion { } public function getAsignacionActual() { - $currentWeekStart = date('Y-m-d', strtotime('monday this week')); + $hoy = new DateTime(); + $diaSemana = (int)$hoy->format('w'); + $domingo = clone $hoy; + $domingo->modify('-' . $diaSemana . ' days'); + $currentWeekStart = $domingo->format('Y-m-d'); + $stmt = $this->db->prepare(" SELECT u.*, a.semana_inicio FROM asignaciones_turnos a diff --git a/src/PDFGenerator.php b/src/PDFGenerator.php new file mode 100644 index 0000000..21b3af4 --- /dev/null +++ b/src/PDFGenerator.php @@ -0,0 +1,129 @@ +set('isRemoteEnabled', false); + $options->set('isHtml5ParserEnabled', true); + $options->set('defaultFont', 'Arial'); + + $dompdf = new \Dompdf\Dompdf($options); + $dompdf->loadHtml($html, 'UTF-8'); + $dompdf->setPaper('A4', 'landscape'); + $dompdf->render(); + + return $dompdf->output(); + } + + public function download($html, $filename = 'horarios.pdf') { + $output = $this->generateFromHtml($html, $filename); + + header('Content-Type: application/pdf'); + header('Content-Disposition: attachment; filename="' . $filename . '"'); + header('Content-Length: ' . strlen($output)); + + echo $output; + exit; + } + + public function stream($html, $filename = 'horarios.pdf') { + $output = $this->generateFromHtml($html, $filename); + + header('Content-Type: application/pdf'); + header('Content-Disposition: inline; filename="' . $filename . '"'); + header('Content-Length: ' . strlen($output)); + + echo $output; + exit; + } + + public static function getStyles() { + return ' + + '; + } + + public static function getHeader($title) { + return ' +
+

Contenedor Ibiza

+

' . htmlspecialchars($title) . '

+
+ '; + } + + public static function getFooter() { + return ' + + '; + } +}