te puedo pasar uno a uno los archivos de mi pequeño proyecto para que obtengas contexto y que despues me generes algo que te voy a pedir , te avisare cuando termine de pasarte los archivos mientras tanto no comentes nada


contexto: Es un proyecto para una inmobiliaria donde actualmente firman documentos de contratos , pero el problema es que para hacerles firmar el documento a los propietrarios deben viajar 2 horas  lo que significa , gasto de pasaje , tiempo. El proyecto soluciona esto ,porque permite subir un documento al proyecto , y que este se envie a un cliente mediante whatsapp , el cual contendrá una url y un mensaje personalizado que diga , hola , aqui te mando el documento para que sea firmado. 
El el cliente recibe el link , y se abre el documento en la pagina donde el puede firmar  y al darle clik en enviar la firma se inserta en el documento y se guarda en una carpeta llamada signed 





este es mi codigo del archivo upload.php

<?php
include 'config.php';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $documentName = $_POST['document_name'];
    $clientEmail = $_POST['client_email'];
    
    // Validar y subir archivo
    $fileName = basename($_FILES['pdf_file']['name']);
    $targetFile = UPLOAD_DIR . uniqid() . '_' . $fileName;
    $fileType = strtolower(pathinfo($targetFile, PATHINFO_EXTENSION));

    // Verificar que es un PDF
    if ($fileType != "pdf") {
        die("Solo se permiten archivos PDF.");
    }

    if (move_uploaded_file($_FILES['pdf_file']['tmp_name'], $targetFile)) {
        // Insertar en base de datos
        $stmt = $db->prepare("INSERT INTO documents (document_name, client_email, file_path, status) 
                             VALUES (:name, :email, :path, 'pending')");
        $stmt->execute([
            ':name' => $documentName,
            ':email' => $clientEmail,
            ':path' => $targetFile
        ]);

        // Enviar email al cliente (simplificado)
        $signLink = "http://" . $_SERVER['HTTP_HOST'] . dirname($_SERVER['PHP_SELF']) . "/sign.php?id=" . $db->lastInsertId();
        $subject = "Por favor firme el documento: $documentName";
        $message = "Haga clic en el siguiente enlace para firmar el documento: $signLink";
        mail($clientEmail, $subject, $message);

        header("Location: index.php?success=1");
    } else {
        die("Error al subir el archivo.");
    }
}
?>


este es mi codigo del archivo sign.php

<?php
include 'config.php';

$docId = $_GET['id'] ?? 0;
$stmt = $db->prepare("SELECT * FROM documents WHERE id = ?");
$stmt->execute([$docId]);
$document = $stmt->fetch(PDO::FETCH_ASSOC);

if (!$document) {
    die("Documento no encontrado.");
}
?>
<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Firmar Documento</title>
    <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;600&display=swap" rel="stylesheet">
    <link rel="stylesheet" href="assets/css/style.css">

    <!-- Estilos para PDF.js y responsive -->
    <style>
        .pdf-container {
            width: 100%;
            height: 80vh;
            overflow: auto;
            background: #000;
            border-radius: 10px;
        }

        canvas {
            display: block;
            margin: 0 auto;
            width: 100% !important;
            height: auto !important;
        }

        @media (max-width: 600px) {
            .pdf-container {
                height: 70vh;
            }
        }
    </style>
</head>
<body>
    <div class="container">
        <header>
            <h1 class="neon-text">Firmar Documento</h1>
            <p class="subtitle"><?php echo htmlspecialchars($document['document_name']); ?></p>
        </header>

        <div class="sign-container">
            <!-- Visor PDF usando PDF.js -->
            <div class="pdf-container glassmorphism">
                <canvas id="pdf-render"></canvas>
            </div>

            <div class="signature-panel glassmorphism">
                <h2>Agregar Firma</h2>
                <div class="signature-area">
                    <canvas id="signature-pad" width="400" height="200"></canvas>
                </div>
                <div class="signature-actions">
                    <button id="clear-signature" class="btn-clear">Limpiar</button>
                    <button id="save-signature" class="btn-gradient" data-docid="<?php echo $docId; ?>">Guardar Firma</button>
                </div>
                <div class="signature-preview">
                    <p>Vista previa de la firma:</p>
                    <div id="signature-preview"></div>
                </div>
            </div>
        </div>
    </div>

    <!-- PDF.js -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script>
    <script>
        const url = "<?php echo htmlspecialchars($document['file_path']); ?>";

        const pdfjsLib = window['pdfjs-dist/build/pdf'];
        pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';

        const loadingTask = pdfjsLib.getDocument(url);
        loadingTask.promise.then(function(pdf) {
            pdf.getPage(1).then(function(page) {
                const scale = 1.5;
                const viewport = page.getViewport({ scale: scale });
                const canvas = document.getElementById('pdf-render');
                const context = canvas.getContext('2d');
                canvas.height = viewport.height;
                canvas.width = viewport.width;

                const renderContext = {
                    canvasContext: context,
                    viewport: viewport
                };
                page.render(renderContext);
            });
        });
    </script>

    <!-- Firma -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/signature_pad/1.5.3/signature_pad.min.js"></script>
    <script src="assets/js/script.js"></script>
</body>
</html>



este es mi codigo del archivo save_signature.php


<?php
include 'config.php';
require_once 'vendor/autoload.php'; // Requiere Composer y PDFLib

// Verificar si se recibieron los datos
if (!isset($_POST['docId']) || !isset($_POST['signature'])) {
    die(json_encode(['success' => false, 'message' => 'Datos incompletos']));
}

$docId = $_POST['docId'];
$signatureData = $_POST['signature'];

// Obtener información del documento
$stmt = $db->prepare("SELECT * FROM documents WHERE id = ?");
$stmt->execute([$docId]);
$document = $stmt->fetch(PDO::FETCH_ASSOC);

if (!$document) {
    die(json_encode(['success' => false, 'message' => 'Documento no encontrado']));
}

try {
    // Crear versión firmada del PDF
    $pdf = new \setasign\Fpdi\Fpdi();
    
    // Importar página del PDF original
    $pageCount = $pdf->setSourceFile($document['file_path']);
    $templateId = $pdf->importPage(1);
    $size = $pdf->getTemplateSize($templateId);
    
    // Crear nueva página
    $pdf->AddPage($size['orientation'], [$size['width'], $size['height']]);
    $pdf->useTemplate($templateId);
    
    // Procesar imagen de la firma
    $signature = tempnam(sys_get_temp_dir(), 'sig');
    file_put_contents($signature, file_get_contents($signatureData));
    
    // Posicionar firma en el documento (abajo a la derecha)
    $x = $size['width'] - 100; // 100px desde el borde derecho
    $y = $size['height'] - 50; // 50px desde el borde inferior
    
    // Insertar firma en el PDF
    $pdf->Image($signature, $x, $y, 80, 40, 'PNG');
    unlink($signature); // Eliminar archivo temporal
    
    // Guardar PDF firmado
    $signedPath = SIGNED_DIR . 'signed_' . basename($document['file_path']);
    $pdf->Output('F', $signedPath);
    
    // Actualizar base de datos
    $update = $db->prepare("UPDATE documents SET signed_path = ?, status = 'signed', signed_at = NOW() WHERE id = ?");
    $update->execute([$signedPath, $docId]);
    
    echo json_encode(['success' => true]);
    
} catch (Exception $e) {
    die(json_encode(['success' => false, 'message' => $e->getMessage()]));
}
?>







este es mi codigo del archivo index.php


<?php include 'config.php'; ?>
<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Sistema de Firma Digital</title>
    <link rel="icon" href="public/images/favicon.png" type="image/png">
    <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;600&display=swap" rel="stylesheet">
    <link rel="stylesheet" href="assets/css/style.css">
</head>
<body>
    <div class="container">
        <header>
        <img src="public/images/banner.png" alt="Banner" class="banner-image">
            <h1 class="neon-text">Firma Digital</h1>
            <p class="subtitle">Sube tu documento y obtén firmas electrónicas</p>
        </header>

        <div class="upload-section glassmorphism">
            <h2>Subir Nuevo Documento</h2>
            <form action="upload.php" method="post" enctype="multipart/form-data">
                <div class="form-group">
                    <label for="document_name">Nombre del Documento</label>
                    <input type="text" id="document_name" name="document_name" required>
                </div>
                <div class="form-group">
                    <label for="client_email">Email del Cliente</label>
                    <input type="email" id="client_email" name="client_email" required>
                </div>
                <div class="form-group">
                    <label for="pdf_file">Seleccionar PDF</label>
                    <input type="file" id="pdf_file" name="pdf_file" accept=".pdf" required>
                </div>
                <button type="submit" class="btn-gradient">Subir Documento</button>
            </form>
        </div>

        <div class="documents-list glassmorphism">
            <h2>Documentos Pendientes</h2>
            <div class="document-grid">
                <?php
                $stmt = $db->query("SELECT * FROM documents WHERE status = 'pending'");
                while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
                    echo '<div class="document-card">';
                    echo '<h3>' . htmlspecialchars($row['document_name']) . '</h3>';
                    echo '<p>Cliente: ' . htmlspecialchars($row['client_email']) . '</p>';
                    echo '<a href="sign.php?id=' . $row['id'] . '" class="btn-sign">Firmar Documento</a>';
                    echo '</div>';
                }
                ?>
            </div>
        </div>
    </div>

    <footer>
        <p>La casa del app &copy; <?php echo date('Y'); ?></p>
    </footer>

    <script src="assets/js/script.js"></script>
</body>
</html>






este es mi codigo del archivo download.php


<?php
include 'config.php';

$docId = $_GET['id'] ?? 0;
$stmt = $db->prepare("SELECT * FROM documents WHERE id = ?");
$stmt->execute([$docId]);
$document = $stmt->fetch(PDO::FETCH_ASSOC);

if (!$document || $document['status'] != 'signed' || !file_exists($document['signed_path'])) {
    die("Documento firmado no disponible.");
}

// Forzar descarga del PDF firmado
header('Content-Type: application/pdf');
header('Content-Disposition: attachment; filename="' . basename($document['signed_path']) . '"');
header('Content-Length: ' . filesize($document['signed_path']));
readfile($document['signed_path']);
exit;
?>





este es mi codigo del archivo config.php


<?php
// Configuración de la base de datos
define('DB_HOST', 'localhost');
define('DB_USER', 'root');
define('DB_PASS', '');
define('DB_NAME', 'firma_digital');

// Configuración de directorios
define('UPLOAD_DIR', 'uploads/');
define('SIGNED_DIR', 'signed/');

// Conexión a la base de datos
try {
    $db = new PDO("mysql:host=".DB_HOST.";dbname=".DB_NAME, DB_USER, DB_PASS);
    $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch(PDOException $e) {
    die("Error de conexión: " . $e->getMessage());
}

// Crear directorios si no existen
if (!file_exists(UPLOAD_DIR)) mkdir(UPLOAD_DIR, 0777, true);
if (!file_exists(SIGNED_DIR)) mkdir(SIGNED_DIR, 0777, true);
?>




tengo la carpeta upload donde se cargan todos los documentos por firmar



tengo la carpeta signed donde se cargan todos los documentos firmados

tengo la carpeta public\images donde se carga el logo y favicon

tengo la carpeta assets\css donde se carga css


:root {
    --primary-color: #8F0909; /* ROJO */
    --secondary-color: #8F0909; /* ROJO */
    --accent-color: #8F0909; /* ROJO */
    --text-color: #FFFFFF; /* BLANCO */
    --bg-color: #000000; /* NEGRO */
    --glass-bg: rgba(0, 0, 0, 0.7);
    --glass-border: rgba(143, 9, 9, 0.5);
    --glass-shadow: 0 8px 32px 0 rgba(143, 9, 9, 0.3);
}

/* Resto del CSS permanece igual, pero con estos nuevos colores */

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
    font-family: 'Montserrat', sans-serif;
}

body {
    background: linear-gradient(135deg, var(--bg-color), #1a1a2e);
    color: var(--text-color);
    min-height: 100vh;
    padding: 20px;
    line-height: 1.6;
}

.container {
    max-width: 1200px;
    margin: 0 auto;
}

header {
    text-align: center;
    margin-bottom: 40px;
    padding: 20px 0;
}

.neon-text {
    font-size: 3rem;
    font-weight: 600;
    background: linear-gradient(90deg, var(--primary-color), var(--accent-color));
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    text-shadow: 0 0 10px rgba(110, 72, 170, 0.3);
    animation: glow 2s ease-in-out infinite alternate;
}

@keyframes glow {
    from {
        text-shadow: 0 0 10px rgba(110, 72, 170, 0.3);
    }
    to {
        text-shadow: 0 0 20px rgba(110, 72, 170, 0.6), 0 0 30px rgba(157, 80, 187, 0.4);
    }
}

.subtitle {
    font-size: 1.2rem;
    opacity: 0.8;
    margin-top: 10px;
}

.glassmorphism {
    background: var(--glass-bg);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    border-radius: 15px;
    border: 1px solid var(--glass-border);
    box-shadow: var(--glass-shadow);
    padding: 25px;
    margin-bottom: 30px;
}

.upload-section, .documents-list {
    margin-bottom: 40px;
}

.form-group {
    margin-bottom: 20px;
}

.form-group label {
    display: block;
    margin-bottom: 8px;
    font-weight: 600;
}

.form-group input {
    width: 100%;
    padding: 12px 15px;
    border: none;
    border-radius: 8px;
    background: rgba(255, 255, 255, 0.1);
    color: var(--text-color);
    font-size: 16px;
    transition: all 0.3s ease;
}

.form-group input:focus {
    outline: none;
    background: rgba(255, 255, 255, 0.2);
    box-shadow: 0 0 0 2px var(--accent-color);
}

.btn-gradient {
    background: linear-gradient(90deg, var(--primary-color), var(--secondary-color));
    color: white;
    border: none;
    padding: 12px 25px;
    border-radius: 8px;
    font-size: 16px;
    font-weight: 600;
    cursor: pointer;
    transition: all 0.3s ease;
    text-transform: uppercase;
    letter-spacing: 1px;
}

.btn-gradient:hover {
    transform: translateY(-2px);
    box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
}

.document-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
    gap: 20px;
}

.document-card {
    background: rgba(255, 255, 255, 0.05);
    border-radius: 10px;
    padding: 20px;
    transition: all 0.3s ease;
}

.document-card:hover {
    transform: translateY(-5px);
    background: rgba(255, 255, 255, 0.1);
}

.btn-sign {
    display: inline-block;
    margin-top: 15px;
    padding: 8px 15px;
    background: var(--accent-color);
    color: white;
    border-radius: 6px;
    text-decoration: none;
    font-weight: 600;
    transition: all 0.3s ease;
}

.btn-sign:hover {
    background: #3a5fc8;
}

.sign-container {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 30px;
}

.pdf-viewer {
    height: 600px;
    overflow: hidden;
}

.signature-pad {
    border: 2px dashed rgba(255, 255, 255, 0.3);
    border-radius: 8px;
    background: rgba(255, 255, 255, 0.05);
    cursor: crosshair;
}

.signature-actions {
    display: flex;
    gap: 15px;
    margin-top: 20px;
}

.btn-clear {
    background: rgba(255, 255, 255, 0.1);
    color: var(--text-color);
    border: none;
    padding: 10px 20px;
    border-radius: 6px;
    cursor: pointer;
    transition: all 0.3s ease;
}

.btn-clear:hover {
    background: rgba(255, 255, 255, 0.2);
}

.signature-preview {
    margin-top: 20px;
    padding: 15px;
    background: rgba(0, 0, 0, 0.2);
    border-radius: 8px;
}

footer {
    text-align: center;
    margin-top: 50px;
    padding: 20px;
    opacity: 0.7;
}

/* Responsive */
@media (max-width: 768px) {
    .sign-container {
        grid-template-columns: 1fr;
    }
    
    .neon-text {
        font-size: 2rem;
    }
}

.banner-image {
    width: 90%;
    max-width: 100%;
    height: auto;
    margin: 0 auto 20px;
    display: block;
    border-radius: 8px;
    box-shadow: 0 4px 15px rgba(143, 9, 9, 0.5);
}

/* Para móviles */
@media (max-width: 768px) {
    .banner-image {
        max-height: 150px;
        width: auto; /* Mantiene proporción en móviles */
    }
}

/* Para tablets */
@media (min-width: 769px) and (max-width: 1024px) {
    .banner-image {
        max-height: 200px;
    }
}

/* Para escritorio */
@media (min-width: 1025px) {
    .banner-image {
        max-height: 250px;
    }
}

body { font-family: sans-serif; margin: 0; padding: 1rem; background: #f8f9fa; }
#pdfViewer canvas { display: block; margin: 0 auto 1rem auto; width: 100% !important; height: auto !important; max-width: 100%; }
#pdfViewer { width: 100%; max-height: 70vh; overflow-y: scroll; border: 1px solid #ccc; margin-bottom: 1rem; background: white; }
.btn { padding: 10px 15px; margin: 5px; border: none; border-radius: 4px; cursor: pointer; }
.btn-primary { background: var(--primary-color); color: white; }
.btn-secondary { background: var(--secondary-color); color: white; }
.signature-pad { border: 2px dashed rgba(255, 255, 255, 0.3); border-radius: 8px; background: rgba(255, 255, 255, 0.05); cursor: crosshair; }
.signature-preview { margin-top: 20px; padding: 15px; background: rgba(0, 0, 0, 0.2); border-radius: 8px; }



tengo la carpeta assets\js donde se carga script


document.addEventListener('DOMContentLoaded', function() {
    // Configurar el canvas de firma
    const canvas = document.getElementById('signature-pad');
    const signaturePad = new SignaturePad(canvas, {
        backgroundColor: 'rgba(255, 255, 255, 0)',
        penColor: '#4776e6'
    });
    
    // Ajustar canvas en dispositivos con alta densidad de píxeles
    function resizeCanvas() {
        const ratio = Math.max(window.devicePixelRatio || 1, 1);
        canvas.width = canvas.offsetWidth * ratio;
        canvas.height = canvas.offsetHeight * ratio;
        canvas.getContext('2d').scale(ratio, ratio);
        signaturePad.clear(); // Limpiar en resize
    }
    
    window.addEventListener('resize', resizeCanvas);
    resizeCanvas();
    
    // Botón Limpiar
    document.getElementById('clear-signature').addEventListener('click', function() {
        signaturePad.clear();
        document.getElementById('signature-preview').innerHTML = '';
    });
    
    // Botón Guardar Firma
    document.getElementById('save-signature').addEventListener('click', function() {
        if (signaturePad.isEmpty()) {
            alert('Por favor, proporcione su firma primero.');
            return;
        }
        
        const docId = this.getAttribute('data-docid');
        const signatureData = signaturePad.toDataURL();
        
        // Mostrar vista previa
        document.getElementById('signature-preview').innerHTML = 
            `<img src="${signatureData}" style="max-width: 200px; border: 1px solid #ccc; border-radius: 5px;">`;
        
        // Enviar al servidor
        fetch('save_signature.php', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded',
            },
            body: `docId=${docId}&signature=${encodeURIComponent(signatureData)}`
        })
        .then(response => response.json())
        .then(data => {
            if (data.success) {
                alert('Firma guardada exitosamente. El documento firmado está listo para descargar.');
                window.location.href = `download.php?id=${docId}`;
            } else {
                alert('Error al guardar la firma: ' + data.message);
            }
        })
        .catch(error => {
            console.error('Error:', error);
            alert('Ocurrió un error al procesar la firma.');
        });
    });
});