Files
gestoria-lp/admin/tramite-detalle.php
2026-03-02 00:30:03 +00:00

586 lines
26 KiB
PHP

<?php
$pageTitle = 'Detalle Tr&aacute;mite';
require_once __DIR__ . '/../includes/db.php';
require_once __DIR__ . '/../includes/config.php';
require_once __DIR__ . '/includes/admin-header.php';
$db = getDB();
// Label maps
$tipoLabels = [
'visa' => 'Visa',
'sentri' => 'Sentri/Global',
'pasaporte' => 'Pasaporte',
'adelanto_cita' => 'Adelanto Cita',
'doble_nacionalidad' => 'Doble Nacionalidad',
];
$estadoLabels = [
'nuevo' => 'Nuevo',
'en_proceso' => 'En Proceso',
'en_revision' => 'En Revisi&oacute;n',
'completado' => 'Completado',
'cancelado' => 'Cancelado',
];
// Determine mode: edit (id exists) or create (no id)
$tramiteId = isset($_GET['id']) ? (int)$_GET['id'] : 0;
$isEdit = $tramiteId > 0;
$tramite = null;
$errors = [];
$success = '';
// ── Handle POST actions ─────────────────────────────────────────
if ($_SERVER['REQUEST_METHOD'] === 'POST' && csrfValidate()) {
$action = $_POST['action'] ?? 'save_tramite';
switch ($action) {
// ── Save tramite (create / update) ──────────────────────
case 'save_tramite':
$clienteId = (int)($_POST['cliente_id'] ?? 0);
$tipo = trim($_POST['tipo'] ?? '');
$estado = trim($_POST['estado'] ?? 'nuevo');
$fechaSolicitud = trim($_POST['fecha_solicitud'] ?? '');
$fechaCita = trim($_POST['fecha_cita'] ?? '');
$fechaResolucion = trim($_POST['fecha_resolucion'] ?? '');
$precio = $_POST['precio'] ?? '';
$notas = trim($_POST['notas'] ?? '');
// Validate required fields
if ($clienteId <= 0) {
$errors[] = 'Debe seleccionar un cliente.';
}
if (!array_key_exists($tipo, $tipoLabels)) {
$errors[] = 'El tipo de tr&aacute;mite no es v&aacute;lido.';
}
if (!array_key_exists($estado, $estadoLabels)) {
$errors[] = 'El estado no es v&aacute;lido.';
}
// Sanitize optional dates
$fechaSolicitud = $fechaSolicitud !== '' ? $fechaSolicitud : null;
$fechaCita = $fechaCita !== '' ? $fechaCita : null;
$fechaResolucion = $fechaResolucion !== '' ? $fechaResolucion : null;
$precio = $precio !== '' ? (float)$precio : null;
if (empty($errors)) {
if ($isEdit) {
$stmt = $db->prepare("UPDATE tramites
SET cliente_id=?, tipo=?, estado=?, fecha_solicitud=?, fecha_cita=?,
fecha_resolucion=?, precio=?, notas=?, updated_at=NOW()
WHERE id=?");
$stmt->execute([
$clienteId, $tipo, $estado,
$fechaSolicitud, $fechaCita, $fechaResolucion,
$precio, $notas, $tramiteId
]);
header("Location: tramite-detalle.php?id=$tramiteId&saved=1");
exit;
} else {
$stmt = $db->prepare("INSERT INTO tramites
(cliente_id, tipo, estado, fecha_solicitud, fecha_cita, fecha_resolucion, precio, notas)
VALUES (?,?,?,?,?,?,?,?)");
$stmt->execute([
$clienteId, $tipo, $estado,
$fechaSolicitud, $fechaCita, $fechaResolucion,
$precio, $notas
]);
$newId = $db->lastInsertId();
header("Location: tramite-detalle.php?id=$newId&saved=1");
exit;
}
}
break;
// ── Upload document ─────────────────────────────────────
case 'upload_document':
if (!$isEdit) break;
$docNombre = trim($_POST['doc_nombre'] ?? '');
$file = $_FILES['doc_archivo'] ?? null;
if ($docNombre === '') {
$errors[] = 'El nombre del documento es obligatorio.';
}
if (!$file || $file['error'] !== UPLOAD_ERR_OK) {
$errors[] = 'Debe seleccionar un archivo v&aacute;lido.';
} else {
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if (!in_array($ext, ALLOWED_EXTENSIONS)) {
$errors[] = 'Tipo de archivo no permitido. Permitidos: ' . implode(', ', ALLOWED_EXTENSIONS);
}
if ($file['size'] > MAX_FILE_SIZE) {
$errors[] = 'El archivo excede el tama&ntilde;o m&aacute;ximo de ' . (MAX_FILE_SIZE / 1024 / 1024) . ' MB.';
}
}
if (empty($errors)) {
// Load tramite to get cliente_id for directory
$stmtT = $db->prepare("SELECT cliente_id FROM tramites WHERE id=?");
$stmtT->execute([$tramiteId]);
$tData = $stmtT->fetch();
if ($tData) {
$uploadDir = UPLOAD_DIR . 'client_' . $tData['cliente_id'] . '/';
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
$safeFilename = preg_replace('/[^a-zA-Z0-9_\-.]/', '_', $file['name']);
$filename = time() . '_' . $safeFilename;
$destPath = $uploadDir . $filename;
if (move_uploaded_file($file['tmp_name'], $destPath)) {
$rutaArchivo = 'client_' . $tData['cliente_id'] . '/' . $filename;
$stmt = $db->prepare("INSERT INTO documentos (cliente_id, tramite_id, nombre, ruta_archivo, tipo) VALUES (?,?,?,?,?)");
$stmt->execute([$tData['cliente_id'], $tramiteId, $docNombre, $rutaArchivo, $ext]);
header("Location: tramite-detalle.php?id=$tramiteId&doc_saved=1#documentos");
exit;
} else {
$errors[] = 'Error al subir el archivo. Intente de nuevo.';
}
}
}
break;
// ── Delete document ─────────────────────────────────────
case 'delete_document':
if (!$isEdit) break;
$docId = (int)($_POST['doc_id'] ?? 0);
if ($docId > 0) {
$stmt = $db->prepare("SELECT ruta_archivo FROM documentos WHERE id=? AND tramite_id=?");
$stmt->execute([$docId, $tramiteId]);
$doc = $stmt->fetch();
if ($doc) {
$filePath = UPLOAD_DIR . $doc['ruta_archivo'];
if (file_exists($filePath)) {
unlink($filePath);
}
$stmt = $db->prepare("DELETE FROM documentos WHERE id=? AND tramite_id=?");
$stmt->execute([$docId, $tramiteId]);
}
}
header("Location: tramite-detalle.php?id=$tramiteId&doc_deleted=1#documentos");
exit;
// ── Delete tramite ──────────────────────────────────────
case 'delete_tramite':
if (!$isEdit) break;
// Delete associated documents from disk
$stmtDocs = $db->prepare("SELECT ruta_archivo FROM documentos WHERE tramite_id=?");
$stmtDocs->execute([$tramiteId]);
$docsToDelete = $stmtDocs->fetchAll();
foreach ($docsToDelete as $doc) {
$filePath = UPLOAD_DIR . $doc['ruta_archivo'];
if (file_exists($filePath)) {
unlink($filePath);
}
}
// Delete document records
$stmt = $db->prepare("DELETE FROM documentos WHERE tramite_id=?");
$stmt->execute([$tramiteId]);
// Delete tramite
$stmt = $db->prepare("DELETE FROM tramites WHERE id=?");
$stmt->execute([$tramiteId]);
header("Location: tramites.php?deleted=1");
exit;
}
}
// ── Handle document download (GET) ──────────────────────────────
if (isset($_GET['download']) && $isEdit) {
$docId = (int)$_GET['download'];
$stmt = $db->prepare("SELECT nombre, ruta_archivo, tipo FROM documentos WHERE id=? AND tramite_id=?");
$stmt->execute([$docId, $tramiteId]);
$doc = $stmt->fetch();
if ($doc) {
$filePath = UPLOAD_DIR . $doc['ruta_archivo'];
if (file_exists($filePath)) {
$mimeTypes = [
'pdf' => 'application/pdf',
'jpg' => 'image/jpeg',
'jpeg' => 'image/jpeg',
'png' => 'image/png',
'doc' => 'application/msword',
'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
];
$mime = $mimeTypes[$doc['tipo']] ?? 'application/octet-stream';
$downloadName = $doc['nombre'] . '.' . $doc['tipo'];
header('Content-Type: ' . $mime);
header('Content-Disposition: attachment; filename="' . $downloadName . '"');
header('Content-Length: ' . filesize($filePath));
header('Cache-Control: no-cache, must-revalidate');
readfile($filePath);
exit;
}
}
$errors[] = 'Documento no encontrado.';
}
// ── Load tramite data for edit mode ─────────────────────────────
if ($isEdit) {
$stmt = $db->prepare("SELECT t.*, c.nombre AS cliente_nombre
FROM tramites t
LEFT JOIN clientes c ON t.cliente_id = c.id
WHERE t.id=?");
$stmt->execute([$tramiteId]);
$tramite = $stmt->fetch();
if (!$tramite) {
header('Location: tramites.php');
exit;
}
// Fetch documents linked to this tramite
$stmtDoc = $db->prepare("SELECT * FROM documentos WHERE tramite_id=? ORDER BY created_at DESC");
$stmtDoc->execute([$tramiteId]);
$documentos = $stmtDoc->fetchAll();
}
// ── Load all clients for dropdown ───────────────────────────────
$stmtClientes = $db->prepare("SELECT id, nombre FROM clientes ORDER BY nombre ASC");
$stmtClientes->execute();
$allClientes = $stmtClientes->fetchAll();
// ── Pre-select client from query param ──────────────────────────
$preClienteId = 0;
if (!$isEdit && isset($_GET['cliente_id'])) {
$preClienteId = (int)$_GET['cliente_id'];
}
// ── Preserve POST data on validation errors ─────────────────────
$prefill = [
'cliente_id' => $preClienteId,
'tipo' => '',
'estado' => 'nuevo',
'fecha_solicitud' => date('Y-m-d'),
'fecha_cita' => '',
'fecha_resolucion' => '',
'precio' => '',
'notas' => '',
];
if (!empty($errors) && ($_POST['action'] ?? '') === 'save_tramite') {
$prefill['cliente_id'] = (int)($_POST['cliente_id'] ?? 0);
$prefill['tipo'] = $_POST['tipo'] ?? '';
$prefill['estado'] = $_POST['estado'] ?? 'nuevo';
$prefill['fecha_solicitud'] = $_POST['fecha_solicitud'] ?? '';
$prefill['fecha_cita'] = $_POST['fecha_cita'] ?? '';
$prefill['fecha_resolucion'] = $_POST['fecha_resolucion'] ?? '';
$prefill['precio'] = $_POST['precio'] ?? '';
$prefill['notas'] = $_POST['notas'] ?? '';
}
// Success messages from redirects
if (isset($_GET['saved'])) $success = 'Tr&aacute;mite guardado correctamente.';
if (isset($_GET['doc_saved'])) $success = 'Documento subido correctamente.';
if (isset($_GET['doc_deleted'])) $success = 'Documento eliminado.';
$pageTitle = $isEdit ? 'Tr&aacute;mite: ' . ($tipoLabels[$tramite['tipo']] ?? $tramite['tipo']) : 'Nuevo Tr&aacute;mite';
?>
<div class="admin-content__header">
<h1>
<i class="fas fa-<?= $isEdit ? 'edit' : 'plus-circle' ?>"></i>
<?= $isEdit ? htmlspecialchars($tipoLabels[$tramite['tipo']] ?? $tramite['tipo']) : 'Nuevo Tr&aacute;mite' ?>
<?php if ($isEdit): ?>
<span class="badge badge--<?= htmlspecialchars($tramite['estado']) ?>" style="font-size: 0.6em; vertical-align: middle;">
<?= $estadoLabels[$tramite['estado']] ?? htmlspecialchars($tramite['estado']) ?>
</span>
<?php endif; ?>
</h1>
<p>
<a href="tramites.php" class="btn btn--sm btn--secondary">
<i class="fas fa-arrow-left"></i> Volver a Tr&aacute;mites
</a>
<?php if ($isEdit && $tramite['cliente_id']): ?>
<a href="cliente-detalle.php?id=<?= (int)$tramite['cliente_id'] ?>" class="btn btn--sm btn--info" style="margin-left: 0.5rem;">
<i class="fas fa-user"></i> Ver Cliente: <?= htmlspecialchars($tramite['cliente_nombre'] ?? '') ?>
</a>
<?php endif; ?>
</p>
</div>
<!-- Alerts -->
<?php if ($success): ?>
<div class="alert alert--success alert--dismissible">
<i class="fas fa-check-circle"></i>
<span><?= $success ?></span>
<button class="alert__close" onclick="this.parentElement.remove()">&times;</button>
</div>
<?php endif; ?>
<?php if (!empty($errors)): ?>
<div class="alert alert--danger">
<i class="fas fa-exclamation-circle"></i>
<div>
<?php foreach ($errors as $err): ?>
<div><?= $err ?></div>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
<!-- ============================================================
SECTION A: Tramite Data Form
============================================================ -->
<div class="card" style="margin-bottom: 1.5rem;">
<div class="card__header">
<h2><i class="fas fa-file-alt"></i> Datos del Tr&aacute;mite</h2>
</div>
<div class="card__body">
<form method="POST" action="tramite-detalle.php<?= $isEdit ? '?id=' . $tramiteId : '' ?>">
<?= csrfField() ?>
<input type="hidden" name="action" value="save_tramite">
<div class="form-row">
<div class="form-group">
<label>Cliente <span class="required">*</span></label>
<select name="cliente_id" class="form-control" required id="clienteSelect">
<option value="">-- Seleccionar cliente --</option>
<?php
$selectedClienteId = $isEdit ? $tramite['cliente_id'] : $prefill['cliente_id'];
foreach ($allClientes as $cli):
?>
<option value="<?= (int)$cli['id'] ?>" <?= (int)$cli['id'] === $selectedClienteId ? 'selected' : '' ?>>
<?= htmlspecialchars($cli['nombre']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group">
<label>Tipo <span class="required">*</span></label>
<select name="tipo" class="form-control" required>
<option value="">-- Seleccionar tipo --</option>
<?php
$selectedTipo = $isEdit ? $tramite['tipo'] : $prefill['tipo'];
foreach ($tipoLabels as $val => $label):
?>
<option value="<?= htmlspecialchars($val) ?>" <?= $selectedTipo === $val ? 'selected' : '' ?>>
<?= htmlspecialchars($label) ?>
</option>
<?php endforeach; ?>
</select>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Estado</label>
<select name="estado" class="form-control">
<?php
$selectedEstado = $isEdit ? $tramite['estado'] : $prefill['estado'];
foreach ($estadoLabels as $val => $label):
?>
<option value="<?= htmlspecialchars($val) ?>" <?= $selectedEstado === $val ? 'selected' : '' ?>>
<?= $label ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group">
<label>Precio</label>
<input type="number" name="precio" class="form-control" step="0.01" min="0"
placeholder="0.00"
value="<?= htmlspecialchars($isEdit ? ($tramite['precio'] ?? '') : $prefill['precio']) ?>">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Fecha Solicitud</label>
<input type="date" name="fecha_solicitud" class="form-control"
value="<?= htmlspecialchars($isEdit ? ($tramite['fecha_solicitud'] ?? '') : $prefill['fecha_solicitud']) ?>">
</div>
<div class="form-group">
<label>Fecha Cita</label>
<input type="date" name="fecha_cita" class="form-control"
value="<?= htmlspecialchars($isEdit ? ($tramite['fecha_cita'] ?? '') : $prefill['fecha_cita']) ?>">
</div>
<div class="form-group">
<label>Fecha Resoluci&oacute;n</label>
<input type="date" name="fecha_resolucion" class="form-control"
value="<?= htmlspecialchars($isEdit ? ($tramite['fecha_resolucion'] ?? '') : $prefill['fecha_resolucion']) ?>">
</div>
</div>
<div class="form-group">
<label>Notas</label>
<textarea name="notas" class="form-control" rows="3" placeholder="Observaciones, detalles adicionales..."><?= htmlspecialchars($isEdit ? ($tramite['notas'] ?? '') : $prefill['notas']) ?></textarea>
</div>
<div style="display: flex; gap: 0.75rem; align-items: center;">
<button type="submit" class="btn btn--primary">
<i class="fas fa-save"></i> <?= $isEdit ? 'Guardar Cambios' : 'Crear Tr&aacute;mite' ?>
</button>
<?php if ($isEdit): ?>
<span style="font-size: var(--admin-font-xs); color: var(--admin-gray-500);">
Creado: <?= date('d/m/Y H:i', strtotime($tramite['created_at'])) ?>
<?php if ($tramite['updated_at'] && $tramite['updated_at'] !== $tramite['created_at']): ?>
&mdash; Actualizado: <?= date('d/m/Y H:i', strtotime($tramite['updated_at'])) ?>
<?php endif; ?>
</span>
<?php endif; ?>
</div>
</form>
</div>
</div>
<?php if ($isEdit): ?>
<!-- ============================================================
SECTION B: Documents
============================================================ -->
<div class="card" id="documentos" style="margin-bottom: 1.5rem;">
<div class="card__header">
<h2><i class="fas fa-folder-open"></i> Documentos del Tr&aacute;mite</h2>
</div>
<div class="card__body">
<!-- Existing documents -->
<?php if (!empty($documentos)): ?>
<div class="table-responsive" style="margin-bottom: 1.5rem;">
<table class="admin-table">
<thead>
<tr>
<th>Nombre</th>
<th>Tipo</th>
<th>Fecha</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
<?php foreach ($documentos as $doc): ?>
<tr>
<td>
<i class="fas fa-file-<?= in_array($doc['tipo'], ['jpg','jpeg','png']) ? 'image' : ($doc['tipo'] === 'pdf' ? 'pdf' : 'word') ?>"></i>
<?= htmlspecialchars($doc['nombre']) ?>
</td>
<td><span class="badge badge--secondary"><?= htmlspecialchars(strtoupper($doc['tipo'] ?? 'N/A')) ?></span></td>
<td><?= date('d/m/Y', strtotime($doc['created_at'])) ?></td>
<td style="display: flex; gap: 0.5rem;">
<a href="tramite-detalle.php?id=<?= $tramiteId ?>&download=<?= (int)$doc['id'] ?>" class="btn btn--sm btn--info" title="Descargar">
<i class="fas fa-download"></i>
</a>
<form method="POST" style="display:inline;" onsubmit="return confirm('&iquest;Eliminar este documento? El archivo se borrar&aacute; permanentemente.')">
<?= csrfField() ?>
<input type="hidden" name="action" value="delete_document">
<input type="hidden" name="doc_id" value="<?= (int)$doc['id'] ?>">
<button type="submit" class="btn btn--sm btn--outline-danger" title="Eliminar">
<i class="fas fa-trash"></i>
</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php else: ?>
<p class="text-muted" style="margin-bottom: 1rem;">No hay documentos subidos para este tr&aacute;mite.</p>
<?php endif; ?>
<!-- Upload document form -->
<div class="detail-section">
<h3 class="detail-section__title"><i class="fas fa-upload"></i> Subir Documento</h3>
<form method="POST" action="tramite-detalle.php?id=<?= $tramiteId ?>" enctype="multipart/form-data">
<?= csrfField() ?>
<input type="hidden" name="action" value="upload_document">
<div class="form-row">
<div class="form-group">
<label>Nombre del Documento <span class="required">*</span></label>
<input type="text" name="doc_nombre" class="form-control" placeholder="Ej: Comprobante pago, Forma DS-160..." required>
</div>
<div class="form-group">
<label>Archivo <span class="required">*</span></label>
<input type="file" name="doc_archivo" class="form-control" accept=".pdf,.jpg,.jpeg,.png,.doc,.docx" required>
<span class="form-text">M&aacute;x. <?= MAX_FILE_SIZE / 1024 / 1024 ?> MB. Formatos: PDF, JPG, PNG, DOC, DOCX</span>
</div>
</div>
<button type="submit" class="btn btn--success btn--sm">
<i class="fas fa-upload"></i> Subir Documento
</button>
</form>
</div>
</div>
</div>
<!-- ============================================================
SECTION C: Delete Tramite
============================================================ -->
<div class="card" style="margin-bottom: 1.5rem; border-color: var(--admin-danger, #dc3545);">
<div class="card__header" style="background: rgba(220,53,69,0.05);">
<h2 style="color: var(--admin-danger, #dc3545);"><i class="fas fa-exclamation-triangle"></i> Zona de Peligro</h2>
</div>
<div class="card__body">
<p style="margin-bottom: 1rem; color: var(--admin-gray-600);">
Eliminar este tr&aacute;mite borrar&aacute; tambi&eacute;n todos los documentos asociados. Esta acci&oacute;n no se puede deshacer.
</p>
<form method="POST" action="tramite-detalle.php?id=<?= $tramiteId ?>"
onsubmit="return confirm('&iquest;Est&aacute; seguro de eliminar este tr&aacute;mite y todos sus documentos? Esta acci&oacute;n NO se puede deshacer.')">
<?= csrfField() ?>
<input type="hidden" name="action" value="delete_tramite">
<button type="submit" class="btn btn--danger">
<i class="fas fa-trash"></i> Eliminar Tr&aacute;mite
</button>
</form>
</div>
</div>
<?php endif; /* end isEdit sections */ ?>
<!-- JavaScript for searchable client select -->
<script>
(function() {
var select = document.getElementById('clienteSelect');
if (!select) return;
// Simple search filter for the client dropdown
var wrapper = document.createElement('div');
wrapper.style.position = 'relative';
select.parentNode.insertBefore(wrapper, select);
wrapper.appendChild(select);
var searchInput = document.createElement('input');
searchInput.type = 'text';
searchInput.className = 'form-control';
searchInput.placeholder = 'Buscar cliente...';
searchInput.style.marginBottom = '0.5rem';
wrapper.insertBefore(searchInput, select);
searchInput.addEventListener('input', function() {
var filter = this.value.toLowerCase();
var options = select.options;
for (var i = 1; i < options.length; i++) {
var text = options[i].textContent.toLowerCase();
options[i].style.display = text.indexOf(filter) > -1 ? '' : 'none';
}
});
// If client is already selected, show name in search
if (select.selectedIndex > 0) {
searchInput.value = select.options[select.selectedIndex].textContent.trim();
}
select.addEventListener('change', function() {
if (this.selectedIndex > 0) {
searchInput.value = this.options[this.selectedIndex].textContent.trim();
} else {
searchInput.value = '';
}
});
})();
</script>
<?php require_once __DIR__ . '/includes/admin-footer.php'; ?>