subir_objeto.php Gestión de objetos digitales 1 líneas
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';
?>