+ +
+ + + + +
+ +
+ + +
+ + + + + + + + + +
+ + + +
+
+ Notificaciones + + nuevas + +
+ + +
+ + Sin notificaciones pendientes +
+ + + +
+ +
+
+
+ +
+
+ +
+
+ +
+
+
+ + + Ver todas las notificaciones + + +
+
+ + + +
+ strtoupper($p[0]), array_slice(explode(' ', $_SESSION['usuario_nombre'] ?? 'U'), 0, 2))) ?> +
+
+
+
diff --git a/views/oficios/crear.php b/views/oficios/crear.php new file mode 100644 index 0000000..1c6ba60 --- /dev/null +++ b/views/oficios/crear.php @@ -0,0 +1,324 @@ +etiquetas(); +$usuarios = $usuario->usuariosParaSelector(); +$errores = $_SESSION['errores_form'] ?? []; +$datos = $_SESSION['datos_form'] ?? []; +unset($_SESSION['errores_form'], $_SESSION['datos_form']); + +// Número sugerido +$tipoDefault = $_GET['tipo'] ?? 'recibido'; +$numSugerido = $oficio->generarNumero($tipoDefault); + +$pageTitle = 'Nuevo Oficio'; +$activeNav = 'nuevo'; + +// Plantillas de respuesta +$db = getDB(); +$plantillas = $db->query("SELECT * FROM plantillas_respuesta WHERE activo=1 ORDER BY titulo")->fetchAll(); + +include __DIR__ . '/../../views/layout/header.php'; +include __DIR__ . '/../../views/layout/sidebar.php'; +include __DIR__ . '/../../views/layout/topbar.php'; +?> +
+ + + + + + +
+ +
+ Por favor corrija los siguientes errores: +
    + +
  • + +
+
+
+ + +
+ + +
+ + +
+
+
+ + Datos del Oficio +
+
+ +
+
+ +
+ + +
+
El número debe ser único en el sistema
+
+ +
+ + +
+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + + +
+
+
+ +
+
+ + +
+
+ + Documentos Adjuntos +
+
+
+ +

+ Arrastra archivos aquí o haz clic para seleccionar +

+

PDF, Word, Excel, imágenes · Máximo 10 MB por archivo

+ +
+
+
+
+ +
+ + +
+
+
+ + Control y Seguimiento +
+
+ +
+
+ + +
+ +
+ + +
+
+ +
+
+ + +
+ +
+ + +
+
+ +
+ + +
+ +
+ +
+ + + +
+
+ +
+ +
Los oficios confidenciales solo son visibles para el responsable y administradores
+
+ +
+
+ + +
+ + + + Cancelar + +
+ +
+
+
+ +
+ + + + diff --git a/views/oficios/detalle.php b/views/oficios/detalle.php new file mode 100644 index 0000000..1668adc --- /dev/null +++ b/views/oficios/detalle.php @@ -0,0 +1,382 @@ +buscarPorId($id); + +if (!$oficio) { + http_response_code(404); + redirect(APP_URL . '/views/oficios/lista.php?error='.urlencode('Oficio no encontrado.')); +} + +// Marcar notificación si viene de ella +if (isset($_GET['notif'])) { + $db = getDB(); + $db->prepare("UPDATE notificaciones SET leida=1,leida_at=NOW() WHERE id=? AND usuario_id=?") + ->execute([(int)$_GET['notif'], $_SESSION['usuario_id']]); +} + +$historial = $model->historial($id); +$comentarios = $model->comentarios($id, !AuthController::esAdmin()); +$etiquetas = $model->etiquetasDeOficio($id); +$adjuntos = getDB()->prepare("SELECT * FROM documentos_adjuntos WHERE oficio_id=? ORDER BY created_at DESC"); +$adjuntos->execute([$id]); +$adjuntos = $adjuntos->fetchAll(); + +$usuarios = (new UsuarioModel())->usuariosParaSelector(); +$esAdmin = AuthController::esAdmin(); +$userId = $_SESSION['usuario_id']; + +// Calcular semáforo +$semaforo = $oficio['semaforo'] ?? 'vigente'; +$semaforoColors = ['vigente'=>'success','proximo'=>'warning','vencido'=>'danger','completado'=>'info']; +$semaColor = $semaforoColors[$semaforo] ?? 'secondary'; + +$pageTitle = 'Oficio: '.$oficio['numero_oficio']; +$activeNav = $oficio['tipo'] === 'recibido' ? 'entrada' : 'salida'; + +include __DIR__ . '/../../views/layout/header.php'; +include __DIR__ . '/../../views/layout/sidebar.php'; +include __DIR__ . '/../../views/layout/topbar.php'; +?> +
+ + + + + + +
+ + +
+ + + + + +
+ + +
+ + +
+
+ + Datos del Oficio +
+
+
+
+
TIPO
+
+ Recibido' + : ' Enviado' ?> +
+
+
+
PRIORIDAD
+
+ + + +
+
+
+
ESTADO
+
+ + + +
+
+
+
RESPONSABLE
+
+
+
+
FECHA RECEPCIÓN
+
+
+
+
FECHA VENCIMIENTO
+
+ + + + + +
+ +
+ + +
+
+
+ +
+
REMITENTE
+
+
+
+
DESTINATARIO
+
+
+
+
ASUNTO
+
+
+ +
+
DESCRIPCIÓN
+
+
+ + + +
+
ETIQUETAS
+
+ + + + + + +
+
+ +
+
+ + + +
+
+ + Documentos Adjuntos () +
+
+ 'fa-file-pdf','doc'=>'fa-file-word','docx'=>'fa-file-word','xls'=>'fa-file-excel','xlsx'=>'fa-file-excel','jpg'=>'fa-file-image','jpeg'=>'fa-file-image','png'=>'fa-file-image']; + $iconColor = ['pdf'=>'#ef4444','doc'=>'#3b82f6','docx'=>'#3b82f6','xls'=>'#10b981','xlsx'=>'#10b981','jpg'=>'#f59e0b','jpeg'=>'#f59e0b','png'=>'#f59e0b']; + ?> + + +
+
+ +
+
+ KB +
+
+ +
+ +
+
+ + +
+ + +
+ + +
+
+ + +
+ + +
+ +
+ + +
+ + + + +
+ +
+ + +
+ +
+ + Sin comentarios aún +
+ + strtoupper($p[0]), array_slice(explode(' ', $c['autor_nombre']), 0, 2))); + ?> +
+
+
+
+ + · + + Privado + +
+
+
+
+ + +
+
+ + +
+
+ +

Sin historial registrado.

+ + +
+
+ +
+
+ + · + + + +
+ +
+ +
+
+ + +
+
+
+ +
+ +
+
+ + + + + diff --git a/views/oficios/editar.php b/views/oficios/editar.php new file mode 100644 index 0000000..ccaf03a --- /dev/null +++ b/views/oficios/editar.php @@ -0,0 +1,212 @@ +buscarPorId($id); + +if (!$oficio) redirect(APP_URL . '/views/oficios/lista.php?error='.urlencode('Oficio no encontrado.')); + +$usuario = new UsuarioModel(); +$etiquetas = $model->etiquetas(); +$usuarios = $usuario->usuariosParaSelector(); +$selEtiq = array_column($model->etiquetasDeOficio($id), 'id'); + +$errores = $_SESSION['errores_form'] ?? []; +$datos = $_SESSION['datos_form'] ?? $oficio; +unset($_SESSION['errores_form'], $_SESSION['datos_form']); + +$pageTitle = 'Editar Oficio: '.$oficio['numero_oficio']; +$activeNav = 'lista'; + +include __DIR__ . '/../../views/layout/header.php'; +include __DIR__ . '/../../views/layout/sidebar.php'; +include __DIR__ . '/../../views/layout/topbar.php'; +?> +
+ + + + + + +
+ +
    +
  • +
+
+ + +
+ + + +
+ +
+
+
+ + Datos del Oficio +
+
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+
+ +
+
Agregar Adjuntos
+
+
+ +

Arrastra archivos o haz clic

+

PDF, Word, Excel, imágenes · Máximo 10 MB

+ +
+
+
+
+ +
+
+
Control y Seguimiento
+
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+ +
+ + + +
+
+ +
+ +
+
+
+ +
+ + + Cancelar + +
+
+
+
+
+ + diff --git a/views/oficios/lista.php b/views/oficios/lista.php new file mode 100644 index 0000000..4fcc1e6 --- /dev/null +++ b/views/oficios/lista.php @@ -0,0 +1,280 @@ + $_GET['tipo'] ?? '', + 'estado' => $_GET['estado'] ?? '', + 'prioridad' => $_GET['prioridad'] ?? '', + 'responsable_id'=> $_GET['responsable_id']?? '', + 'fecha_desde' => $_GET['fecha_desde'] ?? '', + 'fecha_hasta' => $_GET['fecha_hasta'] ?? '', + 'semaforo' => $_GET['semaforo'] ?? '', + 'etiqueta_id' => $_GET['etiqueta_id'] ?? '', + 'busqueda' => $_GET['busqueda'] ?? '', +]; + +$oficios = $oficio->listar($filtros, $soloPropio, $_SESSION['usuario_id']); +$etiquetas = $oficio->etiquetas(); + +// Título dinámico según tipo +$titulos = [ + 'recibido' => 'Bandeja de Entrada', + 'enviado' => 'Bandeja de Salida', + '' => 'Todos los Oficios', +]; +$pageTitle = $titulos[$filtros['tipo']] ?? 'Oficios'; +$activeNav = $filtros['tipo'] === 'recibido' ? 'entrada' : ($filtros['tipo'] === 'enviado' ? 'salida' : 'lista'); + +$badgeEstado = [ + 'recibido' => 'badge-primary', + 'en_proceso' => 'badge-warning', + 'respondido' => 'badge-success', + 'vencido' => 'badge-danger', + 'archivado' => 'badge-secondary', +]; + +$badgePrioridad = [ + 'alta' => 'badge-danger', + 'media' => 'badge-warning', + 'baja' => 'badge-success', +]; + +include __DIR__ . '/../../views/layout/header.php'; +include __DIR__ . '/../../views/layout/sidebar.php'; +include __DIR__ . '/../../views/layout/topbar.php'; +?> +
+ + + + + + + + +
+
+ + + + + + + + + + + + + + + + + Limpiar +
+
+ + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + 'Vigente','proximo'=>'Próximo','vencido'=>'Vencido', + 'completado'=>'Completado','sin_vencimiento'=>'Sin fecha' + ][$o['semaforo'] ?? ''] ?? ($o['semaforo'] ?? ''); + $diasLabel = ''; + if ($o['fecha_vencimiento']) { + $d = (int)$o['dias_para_vencer']; + $diasLabel = $d < 0 ? abs($d).' días vencido' : ($d === 0 ? 'Hoy' : $d.' días'); + } + ?> + + + + + + + + + + + + + + + +
Nº OficioTipoAsuntoRemitente / DestinatarioResponsableVencePrioridadEstadoSemáforoAcciones
+ + No se encontraron oficios con los filtros aplicados +
+ + + + 0): ?> + + + + + + + Entrada + + Salida + + +
+ +
+ +
+ +
+ +
+
+
+ + + +
+
+ + + +
+ + + + + + + + + + + + + +
+
+
+
+ +
+ + + + diff --git a/views/oficios/papelera.php b/views/oficios/papelera.php new file mode 100644 index 0000000..22d3de8 --- /dev/null +++ b/views/oficios/papelera.php @@ -0,0 +1,94 @@ +papelera(); + +$pageTitle = 'Papelera de Reciclaje'; +$activeNav = 'papelera'; + +include __DIR__ . '/../../views/layout/header.php'; +include __DIR__ . '/../../views/layout/sidebar.php'; +include __DIR__ . '/../../views/layout/topbar.php'; +?> +
+ + + + + +
+ + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
Nº OficioAsuntoResponsableEliminado elAcciones
+ + La papelera está vacía +
+ +
+
+
+
+
+ + + diff --git a/views/reportes/index.php b/views/reportes/index.php new file mode 100644 index 0000000..5904150 --- /dev/null +++ b/views/reportes/index.php @@ -0,0 +1,199 @@ +query( + "SELECT estado, COUNT(*) as total FROM oficios WHERE deleted_at IS NULL GROUP BY estado" +)->fetchAll(); + +$resumenResponsables = $db->query( + "SELECT CONCAT(u.nombre,' ',u.apellido) AS nombre, COUNT(o.id) AS total, + SUM(CASE WHEN o.estado='respondido' THEN 1 ELSE 0 END) AS respondidos, + SUM(CASE WHEN o.estado NOT IN('respondido','archivado') AND o.fecha_vencimiento < CURDATE() THEN 1 ELSE 0 END) AS vencidos + FROM oficios o JOIN usuarios u ON u.id=o.responsable_id + WHERE o.deleted_at IS NULL GROUP BY o.responsable_id + ORDER BY total DESC LIMIT 10" +)->fetchAll(); + +$pageTitle = 'Reportes y Respaldo'; +$activeNav = 'reportes'; + +include __DIR__ . '/../../views/layout/header.php'; +include __DIR__ . '/../../views/layout/sidebar.php'; +include __DIR__ . '/../../views/layout/topbar.php'; +?> +
+ + + + + +
+ + +
+
+ + Reporte de Oficios (PDF) +
+
+

+ Genera un informe completo de todos los oficios con filtros aplicados, en formato PDF. +

+
+ +
+ + +
+
+ + +
+ +
+
+
+ + +
+
+ + Exportar a Excel/CSV +
+
+

+ Descarga los oficios en formato Excel o CSV para análisis externo. +

+ +

+ + + Cargar masiva de oficios (Excel/CSV) + +

+
+
+ + + +
+
+ + Respaldo Base de Datos +
+
+

+ Descarga un respaldo completo de la base de datos en formato SQL. +

+ + Descargar Respaldo SQL + +
+ + Guarde el respaldo en un lugar seguro. Contiene datos sensibles. +
+
+
+ + +
+ + +
+
+
+ + Resumen por Estado +
+
+ + + + 'badge-primary','en_proceso'=>'badge-warning','respondido'=>'badge-success','vencido'=>'badge-danger','archivado'=>'badge-secondary']; + foreach ($resumenEstados as $r): + $pct = $total_gral > 0 ? round($r['total']/$total_gral*100,1) : 0; + ?> + + + + + + + +
EstadoTotal%
+
+
+
+
+ % +
+
+
+
+ + +
+
+ + Rendimiento por Responsable +
+
+ + + + + + + + + + + + +
ResponsableTotalRespondidosVencidos
0 ? "{$r['vencidos']}" : '0' ?>
+
+
+
+ +
+ + diff --git a/views/usuarios/lista.php b/views/usuarios/lista.php new file mode 100644 index 0000000..3031fed --- /dev/null +++ b/views/usuarios/lista.php @@ -0,0 +1,179 @@ +todos(); + +$pageTitle = 'Gestión de Usuarios'; +$activeNav = 'usuarios'; + +include __DIR__ . '/../../views/layout/header.php'; +include __DIR__ . '/../../views/layout/sidebar.php'; +include __DIR__ . '/../../views/layout/topbar.php'; +?> +
+ + + + +
+
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + +
NombreUsuarioEmailÁreaRolÚltimo accesoEstadoAcciones
+
+
+ +
+
+
+ +
+ +
+
+
+ + + + + Nunca' ?> + + + + + +
+ + + + + + + + +
+
+
+
+
+
+ + + + + +