200 lines
7.9 KiB
PHP
200 lines
7.9 KiB
PHP
<?php
|
|
/**
|
|
* ReporteController.php — Exportación de PDF e informes
|
|
*/
|
|
declare(strict_types=1);
|
|
require_once __DIR__ . '/../config/config.php';
|
|
require_once __DIR__ . '/../controllers/AuthController.php';
|
|
require_once __DIR__ . '/../models/Oficio.php';
|
|
|
|
class ReporteController {
|
|
|
|
/**
|
|
* Genera PDF de un oficio individual usando DomPDF si está disponible,
|
|
* o HTML simple de fallback
|
|
*/
|
|
public static function generarPdfOficio(array $oficio): void {
|
|
$dompdfPath = BASE_PATH . '/lib/dompdf/autoload.inc.php';
|
|
$html = self::htmlOficio($oficio);
|
|
|
|
if (file_exists($dompdfPath)) {
|
|
require_once $dompdfPath;
|
|
$options = new \Dompdf\Options();
|
|
$options->set('defaultFont', 'DejaVu Sans');
|
|
$options->setIsHtml5ParserEnabled(true);
|
|
$options->setIsFontSubsettingEnabled(true);
|
|
$dompdf = new \Dompdf\Dompdf($options);
|
|
$dompdf->loadHtml($html);
|
|
$dompdf->setPaper('A4', 'portrait');
|
|
$dompdf->render();
|
|
$filename = 'Oficio_' . preg_replace('/[^A-Za-z0-9\-_]/', '_', $oficio['numero_oficio']) . '_' . date('Ymd') . '.pdf';
|
|
$dompdf->stream($filename, ['Attachment' => true]);
|
|
} else {
|
|
// Fallback: HTML descargable
|
|
header('Content-Type: text/html; charset=utf-8');
|
|
header('Content-Disposition: attachment; filename="oficio_' . $oficio['id'] . '.html"');
|
|
echo $html;
|
|
}
|
|
exit();
|
|
}
|
|
|
|
/**
|
|
* Genera HTML del oficio para PDF
|
|
*/
|
|
private static function htmlOficio(array $o): string {
|
|
$logo = file_exists(LOGO_PATH) ? '<img src="' . LOGO_PATH . '" style="max-height:60px">' : '';
|
|
$fecha = date('d/m/Y H:i');
|
|
$vence = $o['fecha_vencimiento'] ? date('d/m/Y', strtotime($o['fecha_vencimiento'])) : 'Sin fecha';
|
|
$recep = date('d/m/Y', strtotime($o['fecha_recepcion']));
|
|
$etiquetas = $o['etiquetas'] ?? '—';
|
|
|
|
$colorPrioridad = ['alta' => '#d32f2f', 'media' => '#fbc02d', 'baja' => '#388e3c'];
|
|
$colorEstado = ['recibido' => '#2e7d32', 'en_proceso' => '#fbc02d', 'respondido' => '#0288d1', 'vencido' => '#d32f2f', 'archivado' => '#64748b'];
|
|
$pColor = $colorPrioridad[$o['prioridad']] ?? '#64748b';
|
|
$eColor = $colorEstado[$o['estado']] ?? '#64748b';
|
|
|
|
return <<<HTML
|
|
<!DOCTYPE html>
|
|
<html lang="es">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<style>
|
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
body { font-family: DejaVu Sans, Arial, sans-serif; font-size: 12px; color: #1e293b; }
|
|
.header { background: #1e1b4b; color: white; padding: 20px 30px; display: flex; justify-content: space-between; align-items: center; }
|
|
.header-title { font-size: 11px; opacity: .7; margin-top: 4px; }
|
|
.content { padding: 30px; }
|
|
.oficio-title { font-size: 20px; font-weight: bold; color: #1e1b4b; margin-bottom: 4px; }
|
|
.oficio-asunto { color: #475569; font-size: 13px; margin-bottom: 24px; }
|
|
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 20px; }
|
|
.field-group { border: 1px solid #e2e8f0; border-radius: 8px; padding: 12px; }
|
|
.field-label { font-size: 9px; font-weight: bold; color: #94a3b8; text-transform: uppercase; letter-spacing: .05em; margin-bottom: 4px; }
|
|
.field-value { font-size: 12px; font-weight: 600; }
|
|
.badge { display: inline-block; padding: 3px 10px; border-radius: 99px; font-size: 10px; font-weight: bold; }
|
|
.section-title { font-size: 11px; font-weight: bold; color: #64748b; text-transform: uppercase; letter-spacing: .05em; border-bottom: 2px solid #e2e8f0; padding-bottom: 6px; margin: 20px 0 12px; }
|
|
.description-box { background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 8px; padding: 14px; font-size: 12px; line-height: 1.7; min-height: 60px; }
|
|
.footer { margin-top: 40px; border-top: 1px solid #e2e8f0; padding-top: 20px; display: flex; justify-content: space-between; }
|
|
.firma-box { border-top: 1px solid #1e1b4b; width: 200px; text-align: center; padding-top: 6px; font-size: 11px; }
|
|
.watermark { color: #94a3b8; font-size: 10px; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="header">
|
|
<div>
|
|
<div style="font-size:15px;font-weight:bold">OFICIO DTIC</div>
|
|
<div class="header-title">{$o['numero_oficio']} · {$o['tipo']}</div>
|
|
</div>
|
|
<div style="text-align:right">
|
|
$logo
|
|
<div style="font-size:10px;opacity:.7;margin-top:4px">{INSTITUCION_NOMBRE}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="content">
|
|
<div class="oficio-title">{$o['numero_oficio']}</div>
|
|
<div class="oficio-asunto">{$o['asunto']}</div>
|
|
|
|
<div class="grid">
|
|
<div class="field-group">
|
|
<div class="field-label">Remitente</div>
|
|
<div class="field-value">{$o['remitente']}</div>
|
|
</div>
|
|
<div class="field-group">
|
|
<div class="field-label">Destinatario</div>
|
|
<div class="field-value">{$o['destinatario']}</div>
|
|
</div>
|
|
<div class="field-group">
|
|
<div class="field-label">Fecha de Recepción</div>
|
|
<div class="field-value">$recep</div>
|
|
</div>
|
|
<div class="field-group">
|
|
<div class="field-label">Fecha de Vencimiento</div>
|
|
<div class="field-value">$vence</div>
|
|
</div>
|
|
<div class="field-group">
|
|
<div class="field-label">Prioridad</div>
|
|
<div class="field-value">
|
|
<span class="badge" style="background:{$pColor}20;color:$pColor">{$o['prioridad']}</span>
|
|
</div>
|
|
</div>
|
|
<div class="field-group">
|
|
<div class="field-label">Estado</div>
|
|
<div class="field-value">
|
|
<span class="badge" style="background:{$eColor}20;color:$eColor">{$o['estado']}</span>
|
|
</div>
|
|
</div>
|
|
<div class="field-group">
|
|
<div class="field-label">Responsable</div>
|
|
<div class="field-value">{$o['responsable_nombre']}</div>
|
|
</div>
|
|
<div class="field-group">
|
|
<div class="field-label">Etiquetas</div>
|
|
<div class="field-value">$etiquetas</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="section-title">Descripción</div>
|
|
<div class="description-box">{$o['descripcion']}</div>
|
|
|
|
<div class="footer">
|
|
<div class="watermark">
|
|
Generado el $fecha · {APP_NAME}<br>
|
|
Documento generado automáticamente. No requiere firma.
|
|
</div>
|
|
<div>
|
|
<div class="firma-box">
|
|
<div>{INSTITUCION_CARGO_FIRMA}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
HTML;
|
|
}
|
|
|
|
/**
|
|
* Descarga backup de la BD como SQL
|
|
*/
|
|
public static function descargarBackupSQL(): void {
|
|
$dbName = env('DB_NAME', 'gestion_documentos');
|
|
$dbHost = env('DB_HOST', 'localhost');
|
|
$dbUser = env('DB_USER', 'root');
|
|
$dbPass = env('DB_PASS', '');
|
|
$fecha = date('Ymd_His');
|
|
$archivo = "$dbName-backup-$fecha.sql";
|
|
|
|
$backupDir = BASE_PATH . '/exports/backups/';
|
|
if (!is_dir($backupDir)) mkdir($backupDir, 0755, true);
|
|
|
|
$cmd = sprintf(
|
|
'mysqldump --host=%s --user=%s --password=%s %s > %s',
|
|
escapeshellarg($dbHost),
|
|
escapeshellarg($dbUser),
|
|
escapeshellarg($dbPass),
|
|
escapeshellarg($dbName),
|
|
escapeshellarg($backupDir . $archivo)
|
|
);
|
|
system($cmd, $ret);
|
|
|
|
if ($ret === 0 && file_exists($backupDir . $archivo)) {
|
|
header('Content-Type: application/octet-stream');
|
|
header('Content-Disposition: attachment; filename="' . $archivo . '"');
|
|
header('Content-Length: ' . filesize($backupDir . $archivo));
|
|
readfile($backupDir . $archivo);
|
|
} else {
|
|
http_response_code(500);
|
|
echo 'Error al generar el respaldo. Verifique mysqldump en el servidor.';
|
|
}
|
|
exit();
|
|
}
|
|
}
|
|
|
|
// Dispatcher si se invoca directamente
|
|
if (basename($_SERVER['PHP_SELF']) === 'ReporteController.php') {
|
|
AuthController::requerirAdmin();
|
|
$action = $_GET['action'] ?? '';
|
|
if ($action === 'backup_sql') ReporteController::descargarBackupSQL();
|
|
redirect(APP_URL . '/views/reportes/index.php');
|
|
}
|