En este tutorial, aprenderás a implementar un calendario interactivo utilizando FullCalendar, un popular plugin de JavaScript que permite gestionar eventos de manera sencilla y efectiva. Utilizaremos PHP y MySQL para manejar los datos del calendario, así como Bootstrap 5 y jQuery para crear una interfaz de usuario atractiva y funcional. A lo largo del tutorial, abordaremos cómo agregar, editar y eliminar eventos en el calendario, garantizando que tengas una comprensión completa del proceso.
Requisitos Previos
Antes de comenzar, asegúrate de tener instalado lo siguiente:
- Servidor web con PHP (XAMPP, WAMP, etc.)
- Base de datos MySQL
- Conocimientos básicos de PHP, MySQL, HTML, CSS y JavaScript.
Estructura del Proyecto
Crea una carpeta para tu proyecto y dentro de ella, crea la siguiente estructura de archivos:
1 2 3 4 5 6 7 |
/tu-proyecto ├── index.php ├── fetch_events.php ├── add_event.php ├── update_event.php ├── delete_event.php ├── db_connection.php |
Configuración de la Base de Datos
- Accede a tu administrador de base de datos (phpMyAdmin, por ejemplo).
- Crea una base de datos llamada
test_calendario
. - Crea una tabla llamada
eventos
con la siguiente estructura:
1 2 3 4 5 6 |
CREATE TABLE eventos ( id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(255) NOT NULL, start DATETIME NOT NULL, end DATETIME NOT NULL ); |
Creación de Archivos PHP
index.php
Este archivo contendrá el código HTML y JavaScript necesario para cargar y manejar el calendario.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Calendario con FullCalendar, PHP y MySQL</title> <!-- FullCalendar CSS --> <link href="https://cdn.jsdelivr.net/npm/fullcalendar@5.11.3/main.min.css" rel="stylesheet"> <!-- Bootstrap CSS --> <link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <div class="container mt-5"> <h1 class="text-center">Calendario de Eventos</h1> <div id="calendar"></div> </div> <!-- Modal para Crear/Editar Evento --> <div class="modal fade" id="eventModal" tabindex="-1" aria-labelledby="eventModalLabel" aria-hidden="true"> <div class="modal-dialog"> <div class="modal-content"> <form id="eventForm"> <div class="modal-header"> <h5 class="modal-title" id="eventModalLabel">Agregar Evento</h5> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> </div> <div class="modal-body"> <div class="mb-3"> <label for="title" class="form-label">Título del Evento</label> <input type="text" class="form-control" id="title" name="title" required> </div> <div class="mb-3"> <label for="start" class="form-label">Fecha de Inicio</label> <input type="date" class="form-control" id="start" name="start" required> </div> <div class="mb-3"> <label for="end" class="form-label">Fecha de Fin</label> <input type="date" class="form-control" id="end" name="end"> </div> <input type="hidden" id="eventId" name="eventId"> </div> <div class="modal-footer"> <button type="submit" class="btn btn-primary">Guardar</button> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button> <button type="button" id="deleteEventBtn" class="btn btn-danger" style="display: none;">Eliminar Evento</button> </div> </form> </div> </div> </div> <!-- jQuery --> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <!-- Bootstrap JS --> <script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/js/bootstrap.bundle.min.js"></script> <!-- FullCalendar JS --> <script src="https://cdn.jsdelivr.net/npm/fullcalendar@5.11.3/main.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/fullcalendar@5.11.3/locales/es.js"></script> <script> document.addEventListener("DOMContentLoaded", function() { const calendarEl = document.getElementById('calendar'); const calendar = new FullCalendar.Calendar(calendarEl, { locale: 'es', initialView: 'dayGridMonth', editable: true, events: 'fetch_events.php', eventClick: function(info) { // Editar evento al hacer clic $('#eventModal').modal('show'); $('#title').val(info.event.title); $('#start').val(info.event.startStr); $('#end').val(info.event.endStr); $('#eventId').val(info.event.id); $('#eventModalLabel').text('Editar Evento'); $('#deleteEventBtn').show(); // Mostrar botón de eliminar }, dateClick: function(info) { // Crear nuevo evento al hacer clic en una fecha $('#eventModal').modal('show'); $('#eventForm')[0].reset(); $('#start').val(info.dateStr); $('#end').val(''); $('#eventId').val(''); $('#eventModalLabel').text('Agregar Evento'); $('#deleteEventBtn').hide(); // Ocultar botón de eliminar } }); calendar.render(); // Guardar evento (Agregar o Editar) $('#eventForm').on('submit', function(e) { e.preventDefault(); const eventData = { title: $('#title').val(), start: $('#start').val(), end: $('#end').val(), id: $('#eventId').val() }; $.ajax({ url: eventData.id ? 'update_event.php' : 'add_event.php', type: 'POST', dataType: 'json', data: eventData, success: function(response) { $('#eventModal').modal('hide'); calendar.refetchEvents(); // Recargar eventos if (response && response.message) { alert(response.message); } else { alert("Error: respuesta no válida del servidor"); } }, error: function() { alert("Error al guardar el evento."); } }); }); // Eliminar evento $('#deleteEventBtn').on('click', function() { const eventId = $('#eventId').val(); if (eventId) { $.ajax({ url: 'delete_event.php', type: 'POST', dataType: 'json', data: { id: eventId }, success: function(response) { $('#eventModal').modal('hide'); calendar.refetchEvents(); // Recargar eventos alert(response.message); // Mostrar mensaje de éxito o error }, error: function() { alert("Error al eliminar el evento."); } }); } }); }); </script> </body> </html> |
fetch_events.php
Este archivo se encarga de recuperar los eventos de la base de datos y devolverlos en formato JSON.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<?php // Incluir archivo de conexión include 'db_connection.php'; $sql = "SELECT id, title, start, end FROM eventos"; $result = $conexion->query($sql); $events = []; while ($row = $result->fetch_assoc()) { $events[] = $row; } header('Content-Type: application/json'); echo json_encode($events); ?> |
add_event.php
Este archivo permite agregar nuevos eventos a la base de datos.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<?php // Incluir archivo de conexión include 'db_connection.php'; $title = $_POST['title']; $start = $_POST['start']; $end = $_POST['end'] ?: $start; $sql = "INSERT INTO eventos (title, start, end) VALUES (?, ?, ?)"; $stmt = $conexion->prepare($sql); $stmt->bind_param("sss", $title, $start, $end); $stmt->execute(); // Verificar si se insertó el evento if ($stmt->affected_rows > 0) { $response = ['message' => 'Evento agregado exitosamente']; } else { $response = ['message' => 'Error al agregar el evento']; } // Respuesta en JSON header('Content-Type: application/json'); echo json_encode($response); ?> |
update_event.php
Este archivo permite actualizar los eventos existentes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
<?php // Incluir archivo de conexión include 'db_connection.php'; $id = $_POST['id']; $title = $_POST['title']; $start = $_POST['start']; $end = $_POST['end'] ?: $start; $sql = "UPDATE eventos SET title = ?, start = ?, end = ? WHERE id = ?"; $stmt = $conexion->prepare($sql); $stmt->bind_param("sssi", $title, $start, $end, $id); $stmt->execute(); // Verificar si se actualizó el evento if ($stmt->affected_rows > 0) { $response = ['message' => 'Evento actualizado exitosamente']; } else { $response = ['message' => 'Error al actualizar el evento']; } // Respuesta en JSON header('Content-Type: application/json'); echo json_encode($response); ?> |
delete_event.php
Este archivo permite eliminar eventos de la base de datos.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<?php // Incluir archivo de conexión include 'db_connection.php'; $id = $_POST['id']; $sql = "DELETE FROM eventos WHERE id = ?"; $stmt = $conexion->prepare($sql); $stmt->bind_param("i", $id); $stmt->execute(); if ($stmt->affected_rows > 0) { $response = ['message' => 'Evento eliminado exitosamente']; } else { $response = ['message' => 'Error al eliminar el evento']; } header('Content-Type: application/json'); echo json_encode($response); ?> |
db_connection.php
Este archivo maneja la conexión a la base de datos.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<?php // db_connection.php $host = "localhost"; $username = "root"; $password = ""; $dbname = "test_calendario"; // Crear la conexión $conexion = new mysqli($host, $username, $password, $dbname); // Verificar la conexión if ($conexion->connect_error) { die("Conexión fallida: " . $conexion->connect_error); } // Establecer la codificación de caracteres a UTF-8 $conexion->set_charset("utf8"); ?> |
Integración de FullCalendar
En este capítulo, hemos integrado el plugin FullCalendar y lo hemos configurado para cargar eventos desde la base de datos. También hemos creado un modal para agregar y editar eventos.
Agregar Funcionalidad para Editar y Eliminar Eventos
En este capítulo, hemos implementado la lógica para editar y eliminar eventos a través de AJAX, haciendo que nuestra aplicación sea más interactiva y dinámica.
Conclusiones
A lo largo de este tutorial, hemos aprendido a crear un calendario interactivo utilizando FullCalendar, PHP y MySQL. Este conocimiento puede ser extendido y adaptado para desarrollar aplicaciones más complejas que requieran gestión de eventos.
Cabe mencionar que previamente habíamos publicado un tutorial similar en FullCalendar con PHP y MySQL. Sin embargo, ya ha pasado bastante tiempo desde entonces; si gustas, puedes echarle un vistazo. Dado el tiempo transcurrido y la aceptación que tuvo en su momento, decidimos realizar una actualización de ese contenido, adaptándolo a las tecnologías y buenas prácticas actuales.