325 lines
15 KiB
PHP
325 lines
15 KiB
PHP
<?php
|
|
/**
|
|
* crear.php — Formulario para crear un nuevo oficio
|
|
*/
|
|
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();
|
|
|
|
$oficio = new OficioModel();
|
|
$usuario = new UsuarioModel();
|
|
|
|
$etiquetas = $oficio->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';
|
|
?>
|
|
<div class="page-content">
|
|
|
|
<div class="breadcrumb">
|
|
<a href="<?= APP_URL ?>/dashboard.php"><i class="fa-solid fa-house"></i></a>
|
|
<i class="fa-solid fa-chevron-right sep"></i>
|
|
<a href="<?= APP_URL ?>/views/oficios/lista.php">Oficios</a>
|
|
<i class="fa-solid fa-chevron-right sep"></i>
|
|
<span>Nuevo Oficio</span>
|
|
</div>
|
|
|
|
<div class="page-header">
|
|
<div class="page-header-content">
|
|
<h1>Crear Nuevo Oficio</h1>
|
|
<p>Complete todos los campos requeridos para registrar el oficio</p>
|
|
</div>
|
|
</div>
|
|
|
|
<?php if (!empty($errores)): ?>
|
|
<div class="alert alert-danger">
|
|
<i class="fa-solid fa-circle-exclamation"></i>
|
|
<div>
|
|
<strong>Por favor corrija los siguientes errores:</strong>
|
|
<ul style="margin:.3rem 0 0 1.2rem">
|
|
<?php foreach ($errores as $e): ?>
|
|
<li><?= htmlspecialchars($e) ?></li>
|
|
<?php endforeach; ?>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<form method="POST" action="<?= APP_URL ?>/controllers/OficioController.php?action=crear" enctype="multipart/form-data" novalidate id="formOficio">
|
|
<?= csrfField() ?>
|
|
|
|
<div class="grid-2" style="gap:1.5rem;align-items:start">
|
|
|
|
<!-- Columna 1: Datos principales -->
|
|
<div>
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<i class="fa-solid fa-file-lines text-primary"></i>
|
|
<span class="card-title">Datos del Oficio</span>
|
|
</div>
|
|
<div class="card-body">
|
|
|
|
<div class="form-row" style="grid-template-columns:1fr 1fr">
|
|
<div class="form-group">
|
|
<label class="form-label" for="numero_oficio">
|
|
Número de Oficio <span class="required">*</span>
|
|
</label>
|
|
<div class="d-flex gap-2">
|
|
<input type="text" class="form-control <?= isset($errores['numero_oficio'])?'is-invalid':'' ?>"
|
|
id="numero_oficio" name="numero_oficio"
|
|
value="<?= htmlspecialchars($datos['numero_oficio'] ?? $numSugerido) ?>"
|
|
placeholder="Ej. REC-2026-0001"
|
|
required>
|
|
<button type="button" class="btn btn-secondary btn-sm" id="autoGenNumero" title="Autogenerar número" style="flex-shrink:0">
|
|
<i class="fa-solid fa-wand-magic-sparkles"></i>
|
|
</button>
|
|
</div>
|
|
<div class="form-text">El número debe ser único en el sistema</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="form-label" for="tipo">
|
|
Tipo <span class="required">*</span>
|
|
</label>
|
|
<select class="form-control" id="tipo" name="tipo" required>
|
|
<option value="recibido" <?= ($datos['tipo']??$tipoDefault)==='recibido'?'selected':'' ?>>📥 Recibido</option>
|
|
<option value="enviado" <?= ($datos['tipo']??$tipoDefault)==='enviado' ?'selected':'' ?>>📤 Enviado</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="form-label" for="remitente">
|
|
Remitente <span class="required">*</span>
|
|
</label>
|
|
<input type="text" class="form-control" id="remitente" name="remitente"
|
|
value="<?= htmlspecialchars($datos['remitente'] ?? '') ?>"
|
|
placeholder="Nombre del remitente o institución"
|
|
required>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="form-label" for="destinatario">
|
|
Destinatario <span class="required">*</span>
|
|
</label>
|
|
<input type="text" class="form-control" id="destinatario" name="destinatario"
|
|
value="<?= htmlspecialchars($datos['destinatario'] ?? '') ?>"
|
|
placeholder="Nombre del destinatario o institución"
|
|
required>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="form-label" for="asunto">
|
|
Asunto <span class="required">*</span>
|
|
</label>
|
|
<textarea class="form-control" id="asunto" name="asunto" rows="3"
|
|
placeholder="Describa brevemente el asunto del oficio"
|
|
data-maxlen="500"
|
|
required><?= htmlspecialchars($datos['asunto'] ?? '') ?></textarea>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="form-label" for="descripcion">Descripción detallada</label>
|
|
<textarea class="form-control" id="descripcion" name="descripcion" rows="4"
|
|
placeholder="Información adicional o contexto del oficio"
|
|
data-maxlen="2000"><?= htmlspecialchars($datos['descripcion'] ?? '') ?></textarea>
|
|
<div style="margin-top:.5rem">
|
|
<label style="font-size:.75rem;font-weight:600;color:var(--text-muted)">Plantillas rápidas:</label>
|
|
<div class="d-flex gap-2 flex-wrap" style="margin-top:.3rem">
|
|
<?php foreach ($plantillas as $p): ?>
|
|
<button type="button" class="btn btn-sm btn-secondary plantilla-btn"
|
|
data-texto="<?= htmlspecialchars($p['contenido']) ?>">
|
|
<i class="fa-solid fa-file-lines"></i>
|
|
<?= htmlspecialchars(mb_strimwidth($p['titulo'], 0, 25, '…')) ?>
|
|
</button>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Adjuntos -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<i class="fa-solid fa-paperclip text-primary"></i>
|
|
<span class="card-title">Documentos Adjuntos</span>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="dropzone" id="dropzoneArea">
|
|
<i class="fa-solid fa-cloud-arrow-up" style="font-size:2rem;margin-bottom:.75rem;opacity:.5;display:block"></i>
|
|
<p class="dropzone-label" style="font-weight:600;margin-bottom:.25rem">
|
|
Arrastra archivos aquí o haz clic para seleccionar
|
|
</p>
|
|
<p style="font-size:.78rem">PDF, Word, Excel, imágenes · Máximo 10 MB por archivo</p>
|
|
<input type="file" name="adjuntos[]" id="adjuntos" style="display:none" multiple accept=".pdf,.doc,.docx,.jpg,.jpeg,.png,.gif,.xls,.xlsx">
|
|
</div>
|
|
<div id="filesPreview" style="margin-top:.75rem;display:flex;flex-wrap:wrap;gap:.5rem"></div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- Columna 2: Metadatos -->
|
|
<div>
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<i class="fa-solid fa-sliders text-primary"></i>
|
|
<span class="card-title">Control y Seguimiento</span>
|
|
</div>
|
|
<div class="card-body">
|
|
|
|
<div class="form-row" style="grid-template-columns:1fr 1fr">
|
|
<div class="form-group">
|
|
<label class="form-label" for="fecha_recepcion">
|
|
Fecha Recepción <span class="required">*</span>
|
|
</label>
|
|
<input type="date" class="form-control" id="fecha_recepcion" name="fecha_recepcion"
|
|
value="<?= htmlspecialchars($datos['fecha_recepcion'] ?? date('Y-m-d')) ?>"
|
|
required>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="form-label" for="fecha_vencimiento">Fecha Vencimiento</label>
|
|
<input type="date" class="form-control" id="fecha_vencimiento" name="fecha_vencimiento"
|
|
value="<?= htmlspecialchars($datos['fecha_vencimiento'] ?? '') ?>"
|
|
min="<?= date('Y-m-d') ?>">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-row" style="grid-template-columns:1fr 1fr">
|
|
<div class="form-group">
|
|
<label class="form-label" for="prioridad">Prioridad <span class="required">*</span></label>
|
|
<select class="form-control" id="prioridad" name="prioridad" required>
|
|
<option value="alta" <?= ($datos['prioridad']??'')==='alta' ?'selected':'' ?>>🔴 Alta</option>
|
|
<option value="media" <?= ($datos['prioridad']??'media')==='media'?'selected':'' ?>>🟡 Media</option>
|
|
<option value="baja" <?= ($datos['prioridad']??'')==='baja' ?'selected':'' ?>>🟢 Baja</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="form-label" for="estado">Estado <span class="required">*</span></label>
|
|
<select class="form-control" id="estado" name="estado" required>
|
|
<option value="recibido" <?= ($datos['estado']??'recibido')==='recibido' ?'selected':'' ?>>Recibido</option>
|
|
<option value="en_proceso" <?= ($datos['estado']??'')==='en_proceso'?'selected':'' ?>>En Proceso</option>
|
|
<option value="respondido" <?= ($datos['estado']??'')==='respondido'?'selected':'' ?>>Respondido</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="form-label" for="responsable_id">Responsable Asignado</label>
|
|
<select class="form-control" id="responsable_id" name="responsable_id">
|
|
<option value="">— Sin asignar —</option>
|
|
<?php foreach ($usuarios as $u): ?>
|
|
<option value="<?= $u['id'] ?>" <?= ($datos['responsable_id']??'')==$u['id']?'selected':'' ?>>
|
|
<?= htmlspecialchars($u['nombre_completo']) ?>
|
|
<?php if ($u['area']): ?>(<?= htmlspecialchars($u['area']) ?>)<?php endif; ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="form-label">Etiquetas</label>
|
|
<div style="display:flex;flex-wrap:wrap;gap:.45rem;padding:.5rem;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm)">
|
|
<?php
|
|
$selectedEtiquetas = $datos['etiquetas'] ?? [];
|
|
foreach ($etiquetas as $et): ?>
|
|
<label style="display:flex;align-items:center;gap:.3rem;cursor:pointer;font-size:.8rem">
|
|
<input type="checkbox" name="etiquetas[]" value="<?= $et['id'] ?>"
|
|
<?= in_array($et['id'], $selectedEtiquetas)?'checked':'' ?>>
|
|
<span class="badge" style="background:<?= $et['color'] ?>30;color:<?= $et['color'] ?>">
|
|
<?= htmlspecialchars($et['nombre']) ?>
|
|
</span>
|
|
</label>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label style="display:flex;align-items:center;gap:.5rem;cursor:pointer;font-size:.85rem;font-weight:500">
|
|
<input type="checkbox" name="es_confidencial" value="1"
|
|
<?= !empty($datos['es_confidencial'])?'checked':'' ?>>
|
|
<i class="fa-solid fa-lock text-warning"></i>
|
|
Oficio Confidencial
|
|
</label>
|
|
<div class="form-text">Los oficios confidenciales solo son visibles para el responsable y administradores</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Botones de acción -->
|
|
<div style="display:flex;gap:.75rem;flex-direction:column">
|
|
<button type="submit" class="btn btn-primary btn-lg w-100">
|
|
<i class="fa-solid fa-floppy-disk"></i>
|
|
Guardar Oficio
|
|
</button>
|
|
<a href="<?= APP_URL ?>/views/oficios/lista.php" class="btn btn-secondary btn-lg w-100">
|
|
<i class="fa-solid fa-xmark"></i>
|
|
Cancelar
|
|
</a>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</form>
|
|
|
|
</div><!-- /.page-content -->
|
|
|
|
<script>
|
|
// ── Plantillas de respuesta ──────────────────────────────────────────────────
|
|
document.querySelectorAll('.plantilla-btn').forEach(btn => {
|
|
btn.addEventListener('click', function() {
|
|
const descripcion = document.getElementById('descripcion');
|
|
if (descripcion) {
|
|
descripcion.value = this.dataset.texto;
|
|
descripcion.dispatchEvent(new Event('input'));
|
|
}
|
|
});
|
|
});
|
|
|
|
// ── Preview de archivos seleccionados ────────────────────────────────────────
|
|
document.getElementById('adjuntos').addEventListener('change', function() {
|
|
const preview = document.getElementById('filesPreview');
|
|
preview.innerHTML = '';
|
|
[...this.files].forEach(f => {
|
|
const tag = document.createElement('div');
|
|
tag.style.cssText = 'background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);padding:.3rem .7rem;font-size:.78rem;display:flex;align-items:center;gap:.4rem;';
|
|
const ext = f.name.split('.').pop().toLowerCase();
|
|
const icons = { pdf:'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' };
|
|
tag.innerHTML = `<i class="fa-solid ${icons[ext]||'fa-file'} text-primary"></i>${f.name} <span style="color:var(--text-muted)">(${(f.size/1024).toFixed(0)}KB)</span>`;
|
|
preview.appendChild(tag);
|
|
});
|
|
});
|
|
|
|
// ── Dropzone conectar con input ──────────────────────────────────────────────
|
|
document.getElementById('dropzoneArea').addEventListener('click', (e) => {
|
|
if (e.target.closest('#autoGenNumero')) return;
|
|
document.getElementById('adjuntos').click();
|
|
});
|
|
</script>
|
|
|
|
<?php include __DIR__ . '/../../views/layout/footer.php'; ?>
|