The room management system tracks room capacity, bed availability, and real-time occupancy status with automatic updates based on reservation state changes.
The listar_habitaciones() function in functions.php:369-386 provides room overview with bed counts:
function listar_habitaciones($conexion){ try { $stmt = $conexion->query(" SELECT h.id, h.numero, h.capacidad, COUNT(c.id) as total_camas, COUNT(CASE WHEN c.estado = 'libre' THEN 1 END) as camas_libres FROM habitaciones h LEFT JOIN camas c ON h.id = c.id_habitacion GROUP BY h.id, h.numero, h.capacidad ORDER BY h.numero "); return $stmt->fetchAll(PDO::FETCH_ASSOC); } catch (PDOException $e) { error_log("Error al listar habitaciones: " . $e->getMessage()); return []; }}
The obtener_disponibilidad() function in functions.php:395-425 checks bed availability for specific dates:
function obtener_disponibilidad($conexion, $fecha_inicio, $fecha_fin){ try { $stmt = $conexion->prepare(" SELECT c.id, c.numero, c.id_habitacion, h.numero as habitacion_numero, CASE WHEN EXISTS ( SELECT 1 FROM reservas_camas rc INNER JOIN reservas r ON rc.id_reserva = r.id WHERE rc.id_cama = c.id AND r.estado IN ('pendiente', 'reservada') AND (r.fecha_inicio <= :fecha_fin AND r.fecha_fin >= :fecha_inicio) ) THEN 'ocupada' ELSE 'libre' END as disponibilidad FROM camas c JOIN habitaciones h ON c.id_habitacion = h.id ORDER BY h.numero, c.numero "); $stmt->bindParam(':fecha_inicio', $fecha_inicio); $stmt->bindParam(':fecha_fin', $fecha_fin); $stmt->execute(); return $stmt->fetchAll(PDO::FETCH_ASSOC); } catch (PDOException $e) { error_log("Error al obtener disponibilidad: " . $e->getMessage()); return []; }}
The availability check considers both pending and approved reservations to prevent double-booking.
The contar_camas_libres_por_fecha() function in functions.php:451-476 counts available beds for a specific date:
function contar_camas_libres_por_fecha($conexion, $fecha){ try { $stmt = $conexion->prepare(" SELECT COUNT(*) as libres FROM camas c WHERE NOT EXISTS ( SELECT 1 FROM reservas_camas rc INNER JOIN reservas r ON rc.id_reserva = r.id WHERE rc.id_cama = c.id AND r.estado IN ('pendiente', 'reservada') AND :fecha BETWEEN r.fecha_inicio AND r.fecha_fin ) "); $stmt->bindParam(':fecha', $fecha); $stmt->execute(); $result = $stmt->fetch(PDO::FETCH_ASSOC); return (int) $result['libres']; } catch (PDOException $e) { error_log("Error al contar camas libres: " . $e->getMessage()); return 0; }}
The obtener_camas_disponibles() function in functions.php:487-527 finds specific available beds in a room:
function obtener_camas_disponibles($conexion, $id_habitacion, $fecha_inicio, $fecha_fin, $id_reserva_excluir = null){ try { $sql = " SELECT id, numero FROM camas WHERE id_habitacion = :id_habitacion AND id NOT IN ( SELECT DISTINCT c.id FROM camas c INNER JOIN reservas_camas rc ON c.id = rc.id_cama INNER JOIN reservas r ON rc.id_reserva = r.id WHERE c.id_habitacion = :id_habitacion AND r.estado IN ('pendiente', 'reservada') AND (r.fecha_inicio <= :fecha_fin AND r.fecha_fin >= :fecha_inicio) "; // Exclude current reservation when editing if ($id_reserva_excluir !== null) { $sql .= " AND r.id != :id_reserva_excluir"; } $sql .= " ) ORDER BY numero "; $stmt = $conexion->prepare($sql); $stmt->bindParam(':id_habitacion', $id_habitacion, PDO::PARAM_INT); $stmt->bindParam(':fecha_inicio', $fecha_inicio); $stmt->bindParam(':fecha_fin', $fecha_fin); if ($id_reserva_excluir !== null) { $stmt->bindParam(':id_reserva_excluir', $id_reserva_excluir, PDO::PARAM_INT); } $stmt->execute(); return $stmt->fetchAll(PDO::FETCH_ASSOC); } catch (PDOException $e) { error_log("Error al obtener camas disponibles: " . $e->getMessage()); return []; }}
The $id_reserva_excluir parameter allows a reservation to be edited without considering its own beds as occupied.
The obtener_habitaciones_disponibles() function in functions.php:559-610 returns rooms with available bed counts:
function obtener_habitaciones_disponibles($conexion, $fecha_inicio, $fecha_fin){ try { // Check for whole refuge reservation $stmt_check = $conexion->prepare(" SELECT COUNT(*) as total FROM reservas WHERE id_habitacion IS NULL AND estado IN ('pendiente', 'reservada') AND (fecha_inicio <= :fecha_fin AND fecha_fin >= :fecha_inicio) "); $stmt_check->bindParam(':fecha_inicio', $fecha_inicio); $stmt_check->bindParam(':fecha_fin', $fecha_fin); $stmt_check->execute(); $resultado_check = $stmt_check->fetch(PDO::FETCH_ASSOC); // If whole refuge is reserved, no rooms available if ($resultado_check['total'] > 0) { return []; } // Get rooms with available bed counts $stmt = $conexion->prepare(" SELECT h.id, h.numero, h.capacidad, (SELECT COUNT(*) FROM camas WHERE id_habitacion = h.id) as camas_totales, (SELECT COUNT(*) FROM camas WHERE id_habitacion = h.id) - (SELECT COUNT(DISTINCT rc.id_cama) FROM reservas_camas rc INNER JOIN reservas r ON rc.id_reserva = r.id INNER JOIN camas c ON rc.id_cama = c.id WHERE c.id_habitacion = h.id AND r.estado IN ('pendiente', 'reservada') AND (r.fecha_inicio <= :fecha_fin AND r.fecha_fin >= :fecha_inicio) ) as camas_disponibles FROM habitaciones h HAVING camas_disponibles > 0 ORDER BY h.numero "); $stmt->bindParam(':fecha_inicio', $fecha_inicio); $stmt->bindParam(':fecha_fin', $fecha_fin); $stmt->execute(); return $stmt->fetchAll(PDO::FETCH_ASSOC); } catch (PDOException $e) { error_log("Error al obtener habitaciones disponibles: " . $e->getMessage()); return []; }}
If the entire refuge is reserved (id_habitacion IS NULL), this function returns an empty array to prevent partial bookings.
When creating or updating reservations, beds are assigned in numerical order:
SELECT id FROM camasWHERE id_habitacion = :id_habitacionAND id NOT IN ( -- Exclude already reserved beds)ORDER BY numero -- Assign lowest numbered beds firstLIMIT :numero_camas
This ensures beds are assigned consistently and predictably, filling rooms from the lowest bed number upward.