Files
gestoria-lp/admin/solicitud-detalle.php
Gestoría LP 676e8981b8 feat: admin requests inbox with conversion to clients
Add solicitudes list page with estado filtering, pagination, and new
request count badge. Add detail page showing all form data with
actions to change status, create new client, link to existing client,
and create tramite from linked client.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 00:33:31 +00:00

405 lines
16 KiB
PHP

<?php
$pageTitle = 'Detalle Solicitud';
require_once __DIR__ . '/../includes/db.php';
require_once __DIR__ . '/../includes/servicios-data.php';
require_once __DIR__ . '/includes/admin-header.php';
$db = getDB();
// Label maps
$servicioLabels = [
'visa' => 'Visa',
'sentri' => 'Sentri/Global',
'pasaporte' => 'Pasaporte',
'adelanto_cita' => 'Adelanto Cita',
'doble_nacionalidad' => 'Doble Nacionalidad',
];
$estadoSolicitudLabels = [
'nueva' => 'Nueva',
'contactada' => 'Contactada',
'convertida' => 'Convertida',
'descartada' => 'Descartada',
];
$estadoBadgeClass = [
'nueva' => 'primary',
'contactada' => 'warning',
'convertida' => 'success',
'descartada' => 'danger',
];
// Must have an ID
$solicitudId = isset($_GET['id']) ? (int)$_GET['id'] : 0;
if ($solicitudId <= 0) {
header('Location: solicitudes.php');
exit;
}
$errors = [];
$success = '';
// ── Handle POST actions ─────────────────────────────────────────
if ($_SERVER['REQUEST_METHOD'] === 'POST' && csrfValidate()) {
$action = $_POST['action'] ?? '';
switch ($action) {
// ── Update estado ────────────────────────────────────────
case 'update_estado':
$nuevoEstado = trim($_POST['estado'] ?? '');
if (!array_key_exists($nuevoEstado, $estadoSolicitudLabels)) {
$errors[] = 'El estado seleccionado no es v&aacute;lido.';
} else {
$stmt = $db->prepare("UPDATE solicitudes SET estado = ? WHERE id = ?");
$stmt->execute([$nuevoEstado, $solicitudId]);
header("Location: solicitud-detalle.php?id=$solicitudId&estado_saved=1");
exit;
}
break;
// ── Vincular a cliente existente ─────────────────────────
case 'vincular_cliente':
$clienteId = (int)($_POST['cliente_id'] ?? 0);
if ($clienteId <= 0) {
$errors[] = 'Debe seleccionar un cliente.';
} else {
// Verify client exists
$stmtCheck = $db->prepare("SELECT id FROM clientes WHERE id = ?");
$stmtCheck->execute([$clienteId]);
if (!$stmtCheck->fetch()) {
$errors[] = 'El cliente seleccionado no existe.';
} else {
$stmt = $db->prepare("UPDATE solicitudes SET cliente_id = ?, estado = 'convertida' WHERE id = ?");
$stmt->execute([$clienteId, $solicitudId]);
header("Location: solicitud-detalle.php?id=$solicitudId&vinculado=1");
exit;
}
}
break;
}
}
// ── Load solicitud data ──────────────────────────────────────────
$stmt = $db->prepare("SELECT s.*, c.nombre AS cliente_nombre
FROM solicitudes s
LEFT JOIN clientes c ON s.cliente_id = c.id
WHERE s.id = ?");
$stmt->execute([$solicitudId]);
$solicitud = $stmt->fetch();
if (!$solicitud) {
header('Location: solicitudes.php');
exit;
}
// Decode form data
$datosFormulario = [];
if (!empty($solicitud['datos_formulario'])) {
$datosFormulario = json_decode($solicitud['datos_formulario'], true) ?: [];
}
// Build human-readable labels for form fields from servicios-data
$campoLabels = [];
$servicio = $solicitud['servicio'] ?? '';
if (isset($SERVICIOS[$servicio]['campos_formulario'])) {
foreach ($SERVICIOS[$servicio]['campos_formulario'] as $campo) {
$campoLabels[$campo['name']] = $campo['label'];
}
}
// Load all clients for vincular dropdown (only if not already linked)
$allClientes = [];
if (empty($solicitud['cliente_id'])) {
$stmtClientes = $db->prepare("SELECT id, nombre FROM clientes ORDER BY nombre ASC");
$stmtClientes->execute();
$allClientes = $stmtClientes->fetchAll();
}
// Success messages from redirects
if (isset($_GET['estado_saved'])) $success = 'Estado actualizado correctamente.';
if (isset($_GET['vinculado'])) $success = 'Solicitud vinculada al cliente y marcada como convertida.';
$pageTitle = 'Solicitud: ' . $solicitud['nombre'];
?>
<div class="admin-content__header">
<h1>
<i class="fas fa-envelope-open-text"></i>
<?= htmlspecialchars($solicitud['nombre']) ?>
<span class="badge badge--<?= htmlspecialchars($estadoBadgeClass[$solicitud['estado']] ?? 'secondary') ?>" style="font-size: 0.6em; vertical-align: middle;">
<?= htmlspecialchars($estadoSolicitudLabels[$solicitud['estado']] ?? $solicitud['estado']) ?>
</span>
</h1>
<p>
<a href="solicitudes.php" class="btn btn--sm btn--secondary">
<i class="fas fa-arrow-left"></i> Volver a Solicitudes
</a>
</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: Solicitud Info
============================================================ -->
<div class="card" style="margin-bottom: 1.5rem;">
<div class="card__header">
<h2><i class="fas fa-info-circle"></i> Datos de la Solicitud</h2>
</div>
<div class="card__body">
<div class="detail-grid">
<div class="detail-grid__item">
<span class="detail-grid__label">Nombre</span>
<span class="detail-grid__value"><?= htmlspecialchars($solicitud['nombre']) ?></span>
</div>
<div class="detail-grid__item">
<span class="detail-grid__label">Tel&eacute;fono</span>
<span class="detail-grid__value">
<?php if ($solicitud['telefono']): ?>
<a href="tel:<?= htmlspecialchars($solicitud['telefono']) ?>"><?= htmlspecialchars($solicitud['telefono']) ?></a>
<?php else: ?>
-
<?php endif; ?>
</span>
</div>
<div class="detail-grid__item">
<span class="detail-grid__label">Email</span>
<span class="detail-grid__value">
<?php if ($solicitud['email']): ?>
<a href="mailto:<?= htmlspecialchars($solicitud['email']) ?>"><?= htmlspecialchars($solicitud['email']) ?></a>
<?php else: ?>
-
<?php endif; ?>
</span>
</div>
<div class="detail-grid__item">
<span class="detail-grid__label">Servicio</span>
<span class="detail-grid__value"><?= htmlspecialchars($servicioLabels[$solicitud['servicio']] ?? $solicitud['servicio']) ?></span>
</div>
<div class="detail-grid__item">
<span class="detail-grid__label">Fecha de Solicitud</span>
<span class="detail-grid__value"><?= date('d/m/Y H:i', strtotime($solicitud['created_at'])) ?></span>
</div>
<div class="detail-grid__item">
<span class="detail-grid__label">Estado Actual</span>
<span class="detail-grid__value">
<span class="badge badge--<?= htmlspecialchars($estadoBadgeClass[$solicitud['estado']] ?? 'secondary') ?>">
<?= htmlspecialchars($estadoSolicitudLabels[$solicitud['estado']] ?? $solicitud['estado']) ?>
</span>
</span>
</div>
</div>
</div>
</div>
<!-- ============================================================
SECTION B: Service-specific form data
============================================================ -->
<?php if (!empty($datosFormulario)): ?>
<div class="card" style="margin-bottom: 1.5rem;">
<div class="card__header">
<h2><i class="fas fa-list-alt"></i> Datos del Formulario</h2>
</div>
<div class="card__body">
<div class="detail-grid">
<?php foreach ($datosFormulario as $key => $value): ?>
<?php
// Skip empty values
if ($value === '' || $value === null) continue;
// Get human label or fall back to key
$label = $campoLabels[$key] ?? ucfirst(str_replace('_', ' ', $key));
?>
<div class="detail-grid__item">
<span class="detail-grid__label"><?= htmlspecialchars($label) ?></span>
<span class="detail-grid__value"><?= nl2br(htmlspecialchars($value)) ?></span>
</div>
<?php endforeach; ?>
</div>
</div>
</div>
<?php endif; ?>
<!-- ============================================================
SECTION C: Linked Client
============================================================ -->
<?php if ($solicitud['cliente_id']): ?>
<div class="card" style="margin-bottom: 1.5rem;">
<div class="card__header">
<h2><i class="fas fa-user-check"></i> Cliente Vinculado</h2>
</div>
<div class="card__body">
<p style="margin-bottom: 1rem;">
Esta solicitud est&aacute; vinculada al cliente:
<a href="cliente-detalle.php?id=<?= (int)$solicitud['cliente_id'] ?>" class="btn btn--sm btn--info" style="margin-left: 0.5rem;">
<i class="fas fa-user"></i> <?= htmlspecialchars($solicitud['cliente_nombre'] ?? 'Cliente #' . $solicitud['cliente_id']) ?>
</a>
</p>
<a href="tramite-detalle.php?cliente_id=<?= (int)$solicitud['cliente_id'] ?>" class="btn btn--sm btn--success">
<i class="fas fa-plus"></i> Crear Tr&aacute;mite
</a>
</div>
</div>
<?php endif; ?>
<!-- ============================================================
SECTION D: Actions
============================================================ -->
<div class="card" style="margin-bottom: 1.5rem;">
<div class="card__header">
<h2><i class="fas fa-cogs"></i> Acciones</h2>
</div>
<div class="card__body">
<!-- Change estado -->
<div class="detail-section" style="margin-bottom: 1.5rem;">
<h3 class="detail-section__title"><i class="fas fa-exchange-alt"></i> Cambiar Estado</h3>
<form method="POST" action="solicitud-detalle.php?id=<?= $solicitudId ?>" style="display: flex; gap: 0.75rem; align-items: flex-end; flex-wrap: wrap;">
<?= csrfField() ?>
<input type="hidden" name="action" value="update_estado">
<div class="form-group" style="margin-bottom: 0;">
<select name="estado" class="form-control" style="width: auto; min-width: 180px;">
<?php foreach ($estadoSolicitudLabels as $val => $label): ?>
<option value="<?= htmlspecialchars($val) ?>" <?= $solicitud['estado'] === $val ? 'selected' : '' ?>>
<?= htmlspecialchars($label) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<button type="submit" class="btn btn--sm btn--primary">
<i class="fas fa-save"></i> Guardar Estado
</button>
</form>
</div>
<?php if (empty($solicitud['cliente_id'])): ?>
<!-- Create new client -->
<div class="detail-section" style="margin-bottom: 1.5rem;">
<h3 class="detail-section__title"><i class="fas fa-user-plus"></i> Crear Cliente Nuevo</h3>
<p style="margin-bottom: 0.75rem; color: var(--admin-gray-600); font-size: var(--admin-font-sm);">
Se crear&aacute; un nuevo cliente con los datos de esta solicitud (nombre, tel&eacute;fono, email).
</p>
<a href="cliente-detalle.php?from_solicitud=<?= $solicitudId ?>" class="btn btn--sm btn--success">
<i class="fas fa-user-plus"></i> Crear Cliente Nuevo
</a>
</div>
<!-- Link to existing client -->
<div class="detail-section">
<h3 class="detail-section__title"><i class="fas fa-link"></i> Vincular a Cliente Existente</h3>
<p style="margin-bottom: 0.75rem; color: var(--admin-gray-600); font-size: var(--admin-font-sm);">
Vincule esta solicitud a un cliente ya registrado. El estado cambiar&aacute; autom&aacute;ticamente a &ldquo;Convertida&rdquo;.
</p>
<?php if (!empty($allClientes)): ?>
<form method="POST" action="solicitud-detalle.php?id=<?= $solicitudId ?>" style="display: flex; gap: 0.75rem; align-items: flex-end; flex-wrap: wrap;">
<?= csrfField() ?>
<input type="hidden" name="action" value="vincular_cliente">
<div class="form-group" style="margin-bottom: 0;">
<select name="cliente_id" class="form-control" required style="width: auto; min-width: 250px;" id="vincularClienteSelect">
<option value="">-- Seleccionar cliente --</option>
<?php foreach ($allClientes as $cli): ?>
<option value="<?= (int)$cli['id'] ?>"><?= htmlspecialchars($cli['nombre']) ?></option>
<?php endforeach; ?>
</select>
</div>
<button type="submit" class="btn btn--sm btn--primary">
<i class="fas fa-link"></i> Vincular
</button>
</form>
<?php else: ?>
<p class="text-muted">No hay clientes registrados. <a href="cliente-detalle.php?from_solicitud=<?= $solicitudId ?>">Crear uno nuevo</a>.</p>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
</div>
<!-- Inline styles for detail grid -->
<style>
.detail-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1rem;
}
.detail-grid__item {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.detail-grid__label {
font-size: var(--admin-font-xs, 0.75rem);
font-weight: 600;
color: var(--admin-gray-500, #6c757d);
text-transform: uppercase;
letter-spacing: 0.5px;
}
.detail-grid__value {
font-size: var(--admin-font-sm, 0.875rem);
color: var(--admin-gray-800, #212529);
}
.detail-grid__value a {
color: var(--admin-primary, #0C7C8C);
text-decoration: none;
}
.detail-grid__value a:hover {
text-decoration: underline;
}
</style>
<!-- JavaScript for searchable client select -->
<script>
(function() {
var select = document.getElementById('vincularClienteSelect');
if (!select) return;
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';
}
});
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'; ?>