| 1 |
<?php
// ============================================================
// SIDE - ARC 1.0
// Archivo: subir_objeto.php
// ============================================================
require_once 'conexion.php';
require_once 'helpers.php';
requerir_admin();
// --- Validar ID del registro ---
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
header('Location: /apps/side-arc/cuadro_clasificacion.php');
exit;
}
$id = (int) $_GET['id'];
// --- Cargar datos del registro ---
$stmt = mysqli_prepare($conexion,
"SELECT id, titulo, nivel_descripcion, objeto_digital
FROM descripcion_isadg WHERE id = ? LIMIT 1");
mysqli_stmt_bind_param($stmt, 'i', $id);
mysqli_stmt_execute($stmt);
$res = mysqli_stmt_get_result($stmt);
$registro = mysqli_fetch_assoc($res);
mysqli_free_result($res);
mysqli_stmt_close($stmt);
if (!$registro) {
header('Location: /apps/side-arc/cuadro_clasificacion.php');
exit;
}
// ============================================================
// CONFIGURACIÓN DE SUBIDA
// ============================================================
define('UPLOAD_DIR', __DIR__ . '/uploads/');
define('UPLOAD_URL', 'uploads/');
define('MAX_TAMANIO', 20 * 1024 * 1024);
define('TIPOS_PERMITIDOS', ['image/jpeg', 'image/png', 'image/gif',
'image/webp', 'application/pdf']);
define('EXTS_PERMITIDAS', ['jpg', 'jpeg', 'png', 'gif', 'webp', 'pdf']);
if (!is_dir(UPLOAD_DIR)) {
mkdir(UPLOAD_DIR, 0755, true);
}
// ============================================================
// PROCESAMIENTO DEL FORMULARIO POST
// ============================================================
$mensaje_error = '';
$mensaje_exito = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (
empty($_POST['csrf_token']) ||
empty($_SESSION['csrf_token']) ||
!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])
) {
$mensaje_error = 'Solicitud inválida. Intente de nuevo.';
} else {
unset($_SESSION['csrf_token']);
if (empty($_FILES['objeto_digital']) ||
$_FILES['objeto_digital']['error'] === UPLOAD_ERR_NO_FILE) {
$mensaje_error = 'No se seleccionó ningún archivo.';
} elseif ($_FILES['objeto_digital']['error'] !== UPLOAD_ERR_OK) {
$errores_upload = [
UPLOAD_ERR_INI_SIZE => 'El archivo supera el límite del servidor.',
UPLOAD_ERR_FORM_SIZE => 'El archivo supera el límite del formulario.',
UPLOAD_ERR_PARTIAL => 'El archivo se subió de forma incompleta.',
UPLOAD_ERR_NO_TMP_DIR => 'No existe carpeta temporal en el servidor.',
UPLOAD_ERR_CANT_WRITE => 'No se pudo escribir el archivo en disco.',
UPLOAD_ERR_EXTENSION => 'Una extensión de PHP bloqueó la subida.',
];
$codigo = $_FILES['objeto_digital']['error'];
$mensaje_error = $errores_upload[$codigo]
?? 'Error desconocido al subir el archivo (código ' . $codigo . ').';
} else {
$archivo = $_FILES['objeto_digital'];
$tamanio = $archivo['size'];
$tmp_path = $archivo['tmp_name'];
$nombre_orig = $archivo['name'];
$ext_orig = strtolower(pathinfo($nombre_orig, PATHINFO_EXTENSION));
if ($tamanio > MAX_TAMANIO) {
$mensaje_error = 'El archivo supera el límite permitido de 20 MB.';
} elseif (!in_array($ext_orig, EXTS_PERMITIDAS, true)) {
$mensaje_error = 'Tipo de archivo no permitido. '
. 'Solo se aceptan: JPG, PNG, GIF, WEBP, PDF.';
} else {
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime_real = $finfo->file($tmp_path);
if (!in_array($mime_real, TIPOS_PERMITIDOS, true)) {
$mensaje_error = 'El contenido real del archivo no corresponde '
. 'a un tipo permitido (MIME: ' . $mime_real . ').';
} else {
$nombre_seguro = 'obj_' . $id . '_'
. time() . '_'
. bin2hex(random_bytes(6))
. '.' . $ext_orig;
$ruta_destino = UPLOAD_DIR . $nombre_seguro;
$ruta_bd = UPLOAD_URL . $nombre_seguro;
if (!move_uploaded_file($tmp_path, $ruta_destino)) {
$mensaje_error = 'Error al mover el archivo al servidor. '
. 'Verifique los permisos de la carpeta /uploads/.';
} else {
if (!empty($registro['objeto_digital'])) {
$ruta_anterior = __DIR__ . '/' . $registro['objeto_digital'];
if (is_file($ruta_anterior)) {
unlink($ruta_anterior);
}
}
$stmt_upd = mysqli_prepare($conexion,
"UPDATE descripcion_isadg SET objeto_digital = ? WHERE id = ?");
mysqli_stmt_bind_param($stmt_upd, 'si', $ruta_bd, $id);
if (mysqli_stmt_execute($stmt_upd)) {
$registro['objeto_digital'] = $ruta_bd;
$mensaje_exito = 'Objeto digital cargado correctamente.';
} else {
$mensaje_error = 'Error al actualizar la base de datos: '
. mysqli_stmt_error($stmt_upd);
if (is_file($ruta_destino)) {
unlink($ruta_destino);
}
}
mysqli_stmt_close($stmt_upd);
}
}
}
}
}
}
// --- Eliminar objeto digital (acción GET) ---
if (isset($_GET['eliminar_objeto']) && $_GET['eliminar_objeto'] === '1') {
if (!empty($registro['objeto_digital'])) {
$ruta_fisica = __DIR__ . '/' . $registro['objeto_digital'];
if (is_file($ruta_fisica)) {
unlink($ruta_fisica);
}
$stmt_del = mysqli_prepare($conexion,
"UPDATE descripcion_isadg SET objeto_digital = NULL WHERE id = ?");
mysqli_stmt_bind_param($stmt_del, 'i', $id);
mysqli_stmt_execute($stmt_del);
mysqli_stmt_close($stmt_del);
$registro['objeto_digital'] = null;
$mensaje_exito = 'Objeto digital eliminado correctamente.';
}
}
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
$titulo_pagina = 'Objeto Digital: ' . h($registro['titulo']);
require_once 'header.php';
?>
<div class="form-pagina">
<div class="form-pagina__encabezado">
<div style="display:flex; justify-content:space-between; align-items:flex-start;">
<div>
<h1 class="form-pagina__titulo">Objeto Digital</h1>
<p class="form-pagina__contexto">
<span class="badge-nivel"><?= h($registro['nivel_descripcion']) ?></span>
<strong><?= h($registro['titulo']) ?></strong>
</p>
</div>
<a href="/apps/side-arc/ficha.php?id=<?= $id ?>" class="btn btn--cancelar"
style="padding:0.55rem 1.25rem; font-size:0.92rem;">
← Volver a la ficha
</a>
</div>
</div>
<?php if (!empty($mensaje_error)): ?>
<div class="alerta alerta--error"><?= h($mensaje_error) ?></div>
<?php endif; ?>
<?php if (!empty($mensaje_exito)): ?>
<div class="alerta alerta--exito">✔ <?= h($mensaje_exito) ?></div>
<?php endif; ?>
<?php if (!empty($registro['objeto_digital'])): ?>
<fieldset class="form-seccion" style="margin-bottom:1.5rem;">
<legend class="form-seccion__titulo">Objeto digital actual</legend>
<?php
$ruta_actual = $registro['objeto_digital'];
$ext_actual = strtolower(pathinfo($ruta_actual, PATHINFO_EXTENSION));
$url_actual = '/apps/side-arc/' . h($ruta_actual);
$es_img = in_array($ext_actual, ['jpg','jpeg','png','gif','webp']);
$es_pdf = ($ext_actual === 'pdf');
?>
<?php if ($es_img): ?>
<img src="<?= $url_actual ?>" alt="Objeto digital"
class="ficha-objeto-digital__img" style="margin-bottom:1rem;">
<?php elseif ($es_pdf): ?>
<iframe src="<?= $url_actual ?>" class="ficha-objeto-digital__pdf"
title="PDF asociado" style="margin-bottom:1rem;"></iframe>
<?php endif; ?>
<p style="font-size:0.82rem; color:var(--color-texto-suave); margin-bottom:1rem;">
Ruta: <code><?= h($ruta_actual) ?></code>
</p>
<div style="display:flex; gap:0.5rem;">
<a href="<?= $url_actual ?>" target="_blank" class="btn btn--primario"
style="padding:0.55rem 1.25rem; font-size:0.92rem;">
Abrir en nueva pestaña
</a>
<a href="/apps/side-arc/subir_objeto.php?id=<?= $id ?>&eliminar_objeto=1"
class="btn btn--peligro"
style="padding:0.55rem 1.25rem; font-size:0.92rem;"
onclick="return confirm('¿Eliminar el objeto digital?')">
Eliminar objeto digital
</a>
</div>
</fieldset>
<?php else: ?>
<div class="alerta alerta--info" style="margin-bottom:1.5rem;">
Este registro no tiene un objeto digital asociado.
</div>
<?php endif; ?>
<fieldset class="form-seccion">
<legend class="form-seccion__titulo">
<?= !empty($registro['objeto_digital']) ? 'Reemplazar objeto digital' : 'Cargar objeto digital' ?>
</legend>
<p style="font-size:0.88rem; color:var(--color-texto-suave); margin-bottom:1rem;">
Formatos aceptados: <strong>JPG, PNG, GIF, WEBP, PDF</strong>.
Tamaño máximo: <strong>20 MB</strong>.
<?php if (!empty($registro['objeto_digital'])): ?>
El archivo anterior será eliminado automáticamente.
<?php endif; ?>
</p>
<form action="/apps/side-arc/subir_objeto.php?id=<?= $id ?>"
method="POST"
enctype="multipart/form-data">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<input type="hidden" name="MAX_FILE_SIZE" value="<?= MAX_TAMANIO ?>">
<div class="form-grid form-grid--1" style="margin-bottom:1rem;">
<div class="form-grupo">
<label for="objeto_digital" class="form-label form-label--req">
Seleccionar archivo
</label>
<input type="file" id="objeto_digital" name="objeto_digital"
class="form-input"
accept=".jpg,.jpeg,.png,.gif,.webp,.pdf" required>
</div>
</div>
<div id="preview-contenedor" style="display:none; margin-bottom:1rem;">
<img id="preview-img" src="" alt="Vista previa"
class="ficha-objeto-digital__img" style="display:none;">
<p id="preview-pdf" style="display:none;" class="alerta alerta--info">
PDF seleccionado. Vista previa no disponible.
</p>
</div>
<div class="form-acciones">
<button type="submit" class="btn btn--primario"
style="padding:0.55rem 1.25rem; font-size:0.92rem;">
⬆ Subir archivo
</button>
</div>
</form>
</fieldset>
</div>
<script>
document.getElementById('objeto_digital').addEventListener('change', function() {
const archivo = this.files[0];
const preview = document.getElementById('preview-contenedor');
const img = document.getElementById('preview-img');
const pdfMsg = document.getElementById('preview-pdf');
if (!archivo) { preview.style.display = 'none'; return; }
preview.style.display = 'block';
if (archivo.type === 'application/pdf') {
img.style.display = 'none';
pdfMsg.style.display = 'block';
} else {
pdfMsg.style.display = 'none';
img.style.display = 'block';
const reader = new FileReader();
reader.onload = e => { img.src = e.target.result; };
reader.readAsDataURL(archivo);
}
});
</script>
<?php
mysqli_close($conexion);
require_once 'footer.php';
?>
|