237 lines
13 KiB
PHP

<?php
/**
* OficioController.php — Controlador de oficios (acciones POST/GET)
*/
declare(strict_types=1);
require_once __DIR__ . '/../config/config.php';
require_once __DIR__ . '/../controllers/AuthController.php';
require_once __DIR__ . '/../models/Oficio.php';
require_once __DIR__ . '/../models/Usuario.php';
AuthController::requerirAuth();
$action = $_GET['action'] ?? $_POST['action'] ?? '';
$model = new OficioModel();
$esAdmin = AuthController::esAdmin();
$userId = (int)$_SESSION['usuario_id'];
switch ($action) {
// ── CREAR ──────────────────────────────────────────────────────────────
case 'crear':
if ($_SERVER['REQUEST_METHOD'] !== 'POST') redirect(APP_URL.'/views/oficios/crear.php');
verificarCsrf();
$datos = [
'numero_oficio' => clean($_POST['numero_oficio'] ?? ''),
'tipo' => clean($_POST['tipo'] ?? 'recibido'),
'remitente' => clean($_POST['remitente'] ?? ''),
'destinatario' => clean($_POST['destinatario'] ?? ''),
'asunto' => clean($_POST['asunto'] ?? ''),
'descripcion' => clean($_POST['descripcion'] ?? ''),
'fecha_recepcion' => clean($_POST['fecha_recepcion'] ?? ''),
'fecha_vencimiento' => clean($_POST['fecha_vencimiento'] ?? ''),
'prioridad' => clean($_POST['prioridad'] ?? 'media'),
'estado' => clean($_POST['estado'] ?? 'recibido'),
'responsable_id' => (int)($_POST['responsable_id'] ?? 0),
'es_confidencial' => isset($_POST['es_confidencial']) ? 1 : 0,
'etiquetas' => $_POST['etiquetas'] ?? [],
];
$errores = validarOficio($datos, $model);
if (!empty($errores)) {
$_SESSION['errores_form'] = $errores;
$_SESSION['datos_form'] = $datos;
redirect(APP_URL.'/views/oficios/crear.php');
}
$id = $model->crear($datos, $userId);
// Subir adjuntos
subirAdjuntos($id, $userId);
// Crear notificación al responsable
if ($datos['responsable_id']) {
crearNotificacion($datos['responsable_id'], $id, 'sistema',
'Nuevo oficio asignado',
"Se te ha asignado el oficio {$datos['numero_oficio']}: {$datos['asunto']}");
}
logActividad($userId, 'crear_oficio', 'oficios', "Oficio #{$datos['numero_oficio']} creado.");
redirect(APP_URL.'/views/oficios/detalle.php?id='.$id.'&success='.urlencode('Oficio creado exitosamente.'));
// ── ACTUALIZAR ─────────────────────────────────────────────────────────
case 'actualizar':
if ($_SERVER['REQUEST_METHOD'] !== 'POST') redirect(APP_URL.'/views/oficios/lista.php');
verificarCsrf();
$id = (int)($_POST['id'] ?? 0);
if (!$id) redirect(APP_URL.'/views/oficios/lista.php');
$datos = [
'numero_oficio' => clean($_POST['numero_oficio'] ?? ''),
'tipo' => clean($_POST['tipo'] ?? 'recibido'),
'remitente' => clean($_POST['remitente'] ?? ''),
'destinatario' => clean($_POST['destinatario'] ?? ''),
'asunto' => clean($_POST['asunto'] ?? ''),
'descripcion' => clean($_POST['descripcion'] ?? ''),
'fecha_recepcion' => clean($_POST['fecha_recepcion'] ?? ''),
'fecha_vencimiento' => clean($_POST['fecha_vencimiento'] ?? ''),
'prioridad' => clean($_POST['prioridad'] ?? 'media'),
'estado' => clean($_POST['estado'] ?? 'recibido'),
'responsable_id' => (int)($_POST['responsable_id'] ?? 0),
'es_confidencial' => isset($_POST['es_confidencial']) ? 1 : 0,
'etiquetas' => $_POST['etiquetas'] ?? [],
];
$errores = validarOficio($datos, $model, $id);
if (!empty($errores)) {
$_SESSION['errores_form'] = $errores;
$_SESSION['datos_form'] = $datos;
redirect(APP_URL.'/views/oficios/editar.php?id='.$id);
}
$model->actualizar($id, $datos, $userId);
subirAdjuntos($id, $userId);
logActividad($userId, 'editar_oficio', 'oficios', "Oficio #$id actualizado.");
redirect(APP_URL.'/views/oficios/detalle.php?id='.$id.'&success='.urlencode('Oficio actualizado exitosamente.'));
// ── ELIMINAR (lógico) ──────────────────────────────────────────────────
case 'eliminar':
verificarCsrf();
$id = (int)($_GET['id'] ?? $_POST['id'] ?? 0);
if (!$id) redirect(APP_URL.'/views/oficios/lista.php');
$model->eliminarLogico($id, $userId);
logActividad($userId, 'eliminar_oficio', 'oficios', "Oficio #$id movido a papelera.");
redirect(APP_URL.'/views/oficios/lista.php?success='.urlencode('Oficio movido a la papelera.'));
// ── ELIMINAR FÍSICO (solo admin) ───────────────────────────────────────
case 'eliminar_fisico':
AuthController::requerirAdmin();
verificarCsrf();
$id = (int)($_GET['id'] ?? $_POST['id'] ?? 0);
if (!$id) redirect(APP_URL.'/views/oficios/papelera.php');
$model->eliminarFisico($id);
logActividad($userId, 'eliminar_fisico_oficio', 'oficios', "Oficio #$id eliminado permanentemente.");
redirect(APP_URL.'/views/oficios/papelera.php?success='.urlencode('Oficio eliminado permanentemente.'));
// ── RESTAURAR ─────────────────────────────────────────────────────────
case 'restaurar':
AuthController::requerirAdmin();
verificarCsrf();
$id = (int)($_GET['id'] ?? $_POST['id'] ?? 0);
if (!$id) redirect(APP_URL.'/views/oficios/papelera.php');
$model->restaurar($id, $userId);
logActividad($userId, 'restaurar_oficio', 'oficios', "Oficio #$id restaurado.");
redirect(APP_URL.'/views/oficios/papelera.php?success='.urlencode('Oficio restaurado exitosamente.'));
// ── DERIVAR ───────────────────────────────────────────────────────────
case 'derivar':
if ($_SERVER['REQUEST_METHOD'] !== 'POST') redirect(APP_URL.'/views/oficios/lista.php');
verificarCsrf();
$id = (int)($_POST['id'] ?? 0);
$nuevoResponsable= (int)($_POST['nuevo_responsable'] ?? 0);
$comentario = clean($_POST['comentario_derivacion'] ?? '');
if (!$id || !$nuevoResponsable) redirect(APP_URL.'/views/oficios/detalle.php?id='.$id.'&error='.urlencode('Datos incompletos para la derivación.'));
$model->derivar($id, $nuevoResponsable, $userId, $comentario);
crearNotificacion($nuevoResponsable, $id, 'derivacion',
'Oficio derivado hacia ti',
"Se te ha derivado el oficio: $comentario");
logActividad($userId, 'derivar_oficio', 'oficios', "Oficio #$id derivado a usuario #$nuevoResponsable.");
redirect(APP_URL.'/views/oficios/detalle.php?id='.$id.'&success='.urlencode('Oficio derivado exitosamente.'));
// ── COMENTARIO ────────────────────────────────────────────────────────
case 'comentar':
if ($_SERVER['REQUEST_METHOD'] !== 'POST') redirect(APP_URL.'/views/oficios/lista.php');
verificarCsrf();
$id = (int)($_POST['oficio_id'] ?? 0);
$texto = clean($_POST['comentario'] ?? '');
$privado = isset($_POST['es_privado']);
if (!$id || strlen($texto) < 2) redirect(APP_URL.'/views/oficios/detalle.php?id='.$id.'&error='.urlencode('El comentario no puede estar vacío.'));
$model->agregarComentario($id, $userId, $texto, $privado);
redirect(APP_URL.'/views/oficios/detalle.php?id='.$id.'&success='.urlencode('Comentario agregado.'));
// ── GENERAR NÚMERO ────────────────────────────────────────────────────
case 'gen_numero':
$tipo = clean($_GET['tipo'] ?? 'recibido');
jsonResponse(['numero' => $model->generarNumero($tipo)]);
// ── MARCAR NOTIFICACIÓN LEÍDA ─────────────────────────────────────────
case 'marcar_notif':
$nId = (int)($_GET['id'] ?? 0);
if ($nId) {
$db = getDB();
$db->prepare("UPDATE notificaciones SET leida=1,leida_at=NOW() WHERE id=? AND usuario_id=?")
->execute([$nId, $userId]);
}
redirect(APP_URL.'/views/oficios/detalle.php?id='.($_GET['oficio'] ?? 0));
// ── PDF ───────────────────────────────────────────────────────────────
case 'pdf':
$id = (int)($_GET['id'] ?? 0);
$datos = $model->buscarPorId($id);
if (!$dados = $datos) redirect(APP_URL.'/views/oficios/lista.php');
require_once __DIR__ . '/../controllers/ReporteController.php';
ReporteController::generarPdfOficio($datos);
break;
default:
redirect(APP_URL.'/views/oficios/lista.php');
}
// ── Helpers ──────────────────────────────────────────────────────────────────
function validarOficio(array $datos, OficioModel $model, ?int $excepto = null): array {
$errores = [];
if (empty($datos['numero_oficio'])) $errores[] = 'El número de oficio es requerido.';
elseif ($model->existeNumero($datos['numero_oficio'], $excepto)) $errores[] = 'El número de oficio ya existe en el sistema.';
if (empty($datos['remitente'])) $errores[] = 'El remitente es requerido.';
if (empty($datos['destinatario'])) $errores[] = 'El destinatario es requerido.';
if (empty($datos['asunto'])) $errores[] = 'El asunto es requerido.';
if (empty($datos['fecha_recepcion'])) $errores[] = 'La fecha de recepción es requerida.';
if (!in_array($datos['tipo'], ['recibido','enviado'])) $errores[] = 'Tipo inválido.';
if (!in_array($datos['prioridad'], ['alta','media','baja'])) $errores[] = 'Prioridad inválida.';
if (!in_array($datos['estado'], ['recibido','en_proceso','respondido','vencido','archivado'])) $errores[] = 'Estado inválido.';
return $errores;
}
function subirAdjuntos(int $oficioId, int $userId): void {
if (empty($_FILES['adjuntos']['name'][0])) return;
$db = getDB();
$count = count($_FILES['adjuntos']['name']);
for ($i = 0; $i < $count; $i++) {
if ($_FILES['adjuntos']['error'][$i] !== UPLOAD_ERR_OK) continue;
$originalName = $_FILES['adjuntos']['name'][$i];
$ext = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
if (!in_array($ext, UPLOAD_ALLOWED_TYPES)) continue;
if ($_FILES['adjuntos']['size'][$i] > UPLOAD_MAX_SIZE) continue;
$safeName = date('Ymd_His') . '_' . bin2hex(random_bytes(6)) . '.' . $ext;
$destDir = UPLOAD_PATH . 'oficios/' . $oficioId . '/';
if (!is_dir($destDir)) mkdir($destDir, 0755, true);
if (move_uploaded_file($_FILES['adjuntos']['tmp_name'][$i], $destDir . $safeName)) {
$db->prepare(
"INSERT INTO documentos_adjuntos (oficio_id,nombre_original,nombre_archivo,ruta,tipo_mime,tamanio,subido_por)
VALUES (?,?,?,?,?,?,?)"
)->execute([$oficioId, $originalName, $safeName, 'oficios/'.$oficioId.'/'.$safeName, $_FILES['adjuntos']['type'][$i], $_FILES['adjuntos']['size'][$i], $userId]);
}
}
}
function crearNotificacion(int $usuarioId, ?int $oficioId, string $tipo, string $titulo, string $mensaje): void {
$db = getDB();
$db->prepare(
"INSERT INTO notificaciones (usuario_id,oficio_id,tipo,titulo,mensaje) VALUES (?,?,?,?,?)"
)->execute([$usuarioId, $oficioId, $tipo, $titulo, $mensaje]);
}
function logActividad(int $userId, string $accion, string $modulo, string $desc = ''): void {
$db = getDB();
$db->prepare(
"INSERT INTO log_actividad (usuario_id,accion,modulo,descripcion,ip_address,user_agent) VALUES (?,?,?,?,?,?)"
)->execute([$userId, $accion, $modulo, $desc, $_SERVER['REMOTE_ADDR']??null, $_SERVER['HTTP_USER_AGENT']??null]);
}