diff --git a/api/track_time.php b/api/track_time.php
new file mode 100644
index 0000000..d59fa98
--- /dev/null
+++ b/api/track_time.php
@@ -0,0 +1,86 @@
+ false,
+ 'message' => 'Invalid request'
+];
+
+function get_last_status($pdo, $employee_id) {
+ $stmt = $pdo->prepare("SELECT * FROM time_records WHERE employee_id = ? ORDER BY id DESC LIMIT 1");
+ $stmt->execute([$employee_id]);
+ return $stmt->fetch(PDO::FETCH_ASSOC);
+}
+
+if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+ $data = json_decode(file_get_contents('php://input'), true);
+ $action = $data['action'] ?? null;
+ $employee_id = $data['employee_id'] ?? null;
+
+ if ($action && $employee_id) {
+ try {
+ $pdo = db();
+ $last_record = get_last_status($pdo, $employee_id);
+
+ if ($action === 'clock_in') {
+ if ($last_record && $last_record['clock_out'] === null) {
+ $response['message'] = 'Ya has fichado la entrada. Debes fichar la salida primero.';
+ } else {
+ $stmt = $pdo->prepare("INSERT INTO time_records (employee_id, clock_in) VALUES (?, NOW())");
+ $stmt->execute([$employee_id]);
+ $response['success'] = true;
+ $response['message'] = 'Entrada registrada con éxito.';
+ $response['status'] = 'Fichado a las ' . date('H:i:s');
+ $response['action'] = 'clock_in';
+ }
+ } elseif ($action === 'clock_out') {
+ if (!$last_record || $last_record['clock_out'] !== null) {
+ $response['message'] = 'No has fichado la entrada. Debes fichar la entrada primero.';
+ } else {
+ $stmt = $pdo->prepare("UPDATE time_records SET clock_out = NOW() WHERE id = ?");
+ $stmt->execute([$last_record['id']]);
+ $response['success'] = true;
+ $response['message'] = 'Salida registrada con éxito.';
+ $response['status'] = 'Salida registrada a las ' . date('H:i:s');
+ $response['action'] = 'clock_out';
+ }
+ } else {
+ $response['message'] = 'Acción no válida.';
+ }
+ } catch (PDOException $e) {
+ $response['message'] = 'Error de base de datos: ' . $e->getMessage();
+ }
+ } else {
+ $response['message'] = 'Faltan datos en la solicitud.';
+ }
+} elseif ($_SERVER['REQUEST_METHOD'] === 'GET') {
+ $employee_id = $_GET['employee_id'] ?? null;
+ if ($employee_id) {
+ try {
+ $pdo = db();
+ $last_record = get_last_status($pdo, $employee_id);
+ if ($last_record) {
+ if($last_record['clock_out'] === null) {
+ $response['status'] = 'Fichado a las ' . date('H:i:s', strtotime($last_record['clock_in']));
+ $response['last_action'] = 'clock_in';
+ } else {
+ $response['status'] = 'Salida registrada a las ' . date('H:i:s', strtotime($last_record['clock_out']));
+ $response['last_action'] = 'clock_out';
+ }
+ } else {
+ $response['status'] = 'Listo para fichar la entrada.';
+ $response['last_action'] = 'clock_out';
+ }
+ $response['success'] = true;
+ } catch (PDOException $e) {
+ $response['message'] = 'Error de base de datos: ' . $e->getMessage();
+ }
+ }
+}
+
+echo json_encode($response);
diff --git a/assets/css/custom.css b/assets/css/custom.css
new file mode 100644
index 0000000..f774fc6
--- /dev/null
+++ b/assets/css/custom.css
@@ -0,0 +1,110 @@
+
+body {
+ background-color: #F4F7F6;
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 100vh;
+ margin: 0;
+ color: #333;
+}
+
+.clock-container {
+ background-color: #FFFFFF;
+ padding: 40px;
+ border-radius: 12px;
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
+ text-align: center;
+ width: 100%;
+ max-width: 400px;
+}
+
+h1 {
+ font-size: 24px;
+ margin-bottom: 10px;
+}
+
+#current-time {
+ font-size: 48px;
+ font-weight: 600;
+ margin-bottom: 20px;
+ letter-spacing: 2px;
+}
+
+#status-message {
+ font-size: 16px;
+ color: #555;
+ margin-bottom: 30px;
+ min-height: 24px;
+}
+
+.btn {
+ border: none;
+ padding: 15px 30px;
+ font-size: 18px;
+ font-weight: 500;
+ border-radius: 8px;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ width: 100%;
+ margin-bottom: 10px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 10px;
+}
+
+.btn-primary {
+ background-color: #4A90E2;
+ color: white;
+}
+
+.btn-primary:hover {
+ background-color: #357ABD;
+}
+
+.btn-danger {
+ background-color: #D0021B;
+ color: white;
+}
+
+.btn-danger:hover {
+ background-color: #A80115;
+}
+
+.btn:disabled {
+ background-color: #D8D8D8;
+ cursor: not-allowed;
+}
+
+.toast {
+ position: fixed;
+ top: 20px;
+ right: 20px;
+ background-color: #333;
+ color: white;
+ padding: 15px 20px;
+ border-radius: 8px;
+ box-shadow: 0 5px 15px rgba(0,0,0,0.2);
+ opacity: 0;
+ visibility: hidden;
+ transition: opacity 0.3s, visibility 0.3s, transform 0.3s;
+ transform: translateY(-20px);
+ z-index: 1000;
+}
+
+.toast.show {
+ opacity: 1;
+ visibility: visible;
+ transform: translateY(0);
+}
+
+.toast.success {
+ background-color: #50E3C2;
+ color: #333;
+}
+
+.toast.error {
+ background-color: #D0021B;
+}
diff --git a/assets/js/main.js b/assets/js/main.js
new file mode 100644
index 0000000..a180530
--- /dev/null
+++ b/assets/js/main.js
@@ -0,0 +1,92 @@
+document.addEventListener('DOMContentLoaded', function () {
+ const timeElement = document.getElementById('current-time');
+ const statusMessage = document.getElementById('status-message');
+ const clockInButton = document.getElementById('clock-in-btn');
+ const clockOutButton = document.getElementById('clock-out-btn');
+
+ function updateTime() {
+ const now = new Date();
+ timeElement.textContent = now.toLocaleTimeString('es-ES');
+ }
+
+ setInterval(updateTime, 1000);
+ updateTime();
+
+ function showToast(message, type = 'success') {
+ const toast = document.createElement('div');
+ toast.className = `toast ${type}`;
+ toast.textContent = message;
+ document.body.appendChild(toast);
+
+ setTimeout(() => {
+ toast.classList.add('show');
+ }, 100);
+
+ setTimeout(() => {
+ toast.classList.remove('show');
+ setTimeout(() => {
+ document.body.removeChild(toast);
+ }, 300);
+ }, 3000);
+ }
+
+ async function handleClockAction(action) {
+ clockInButton.disabled = true;
+ clockOutButton.disabled = true;
+
+ try {
+ const response = await fetch('api/track_time.php', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({ action: action, employee_id: 1 }) // Hardcoded employee_id
+ });
+
+ const result = await response.json();
+
+ if (result.success) {
+ showToast(result.message);
+ statusMessage.textContent = result.status;
+ if (result.action === 'clock_in') {
+ clockOutButton.disabled = false;
+ } else {
+ clockInButton.disabled = false;
+ }
+ } else {
+ showToast(result.message, 'error');
+ // Re-enable buttons based on assumed last state if error
+ if(action === 'clock_in') clockInButton.disabled = false; else clockOutButton.disabled = false;
+ }
+ } catch (error) {
+ showToast('Error de conexión con el servidor.', 'error');
+ if(action === 'clock_in') clockInButton.disabled = false; else clockOutButton.disabled = false;
+ }
+ }
+
+ clockInButton.addEventListener('click', () => handleClockAction('clock_in'));
+ clockOutButton.addEventListener('click', () => handleClockAction('clock_out'));
+
+ // Initial state check
+ async function checkInitialState() {
+ try {
+ const response = await fetch('api/track_time.php?employee_id=1');
+ const result = await response.json();
+ if(result.status) {
+ statusMessage.textContent = result.status;
+ if(result.last_action === 'clock_in'){
+ clockInButton.disabled = true;
+ clockOutButton.disabled = false;
+ } else {
+ clockInButton.disabled = false;
+ clockOutButton.disabled = true;
+ }
+ }
+ } catch(e) {
+ // assume default state
+ clockOutButton.disabled = true;
+ }
+ }
+
+ checkInitialState();
+});
diff --git a/db/config.php b/db/config.php
index 62ed3b7..5a5b782 100644
--- a/db/config.php
+++ b/db/config.php
@@ -15,3 +15,17 @@ function db() {
}
return $pdo;
}
+
+function run_migrations() {
+ $pdo = db();
+ $migration_files = glob(__DIR__ . '/migrations/*.sql');
+ foreach ($migration_files as $file) {
+ try {
+ $sql = file_get_contents($file);
+ $pdo->exec($sql);
+ } catch (PDOException $e) {
+ // Optionally log this error instead of dying
+ error_log("Migration failed for file: $file. Error: " . $e->getMessage());
+ }
+ }
+}
diff --git a/db/migrations/001_create_time_records_table.sql b/db/migrations/001_create_time_records_table.sql
new file mode 100644
index 0000000..4fa672c
--- /dev/null
+++ b/db/migrations/001_create_time_records_table.sql
@@ -0,0 +1,8 @@
+-- 001_create_time_records_table.sql
+CREATE TABLE IF NOT EXISTS `time_records` (
+ `id` INT AUTO_INCREMENT PRIMARY KEY,
+ `employee_id` INT NOT NULL,
+ `clock_in` DATETIME NOT NULL,
+ `clock_out` DATETIME DEFAULT NULL,
+ `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
diff --git a/index.php b/index.php
index 6f7ffab..11c5059 100644
--- a/index.php
+++ b/index.php
@@ -1,131 +1,37 @@
-
-
-
+
+
-
-
- New Style
-
-
-
-
+
+
+ Control de Presencia
+
+
+
+
+
+
-
-
-
Analyzing your requirements and generating your website…
-
- Loading…
-
-
= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWiZZy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.
-
This page will update automatically as the plan is implemented.
-
Runtime: PHP = htmlspecialchars($phpVersion) ?> — UTC = htmlspecialchars($now) ?>
+
+
+
Control de Presencia
+
--:--:--
+
Cargando estado...
+
+
+
+
-
-
+
+
+
-
+
\ No newline at end of file