- Fix 7 backend controllers: rename columns to match actual DB schema (name_room→room_number, id_room→id, bed_type→room_type, id_employee→id, status_employee→status) - Fix 10 frontend files with matching property renames - Add ThemeContext with dark/light toggle (localStorage persisted) - Redesign theme.css with [data-theme] attribute selectors - Add Google Fonts (Syne, DM Sans, JetBrains Mono) - Redesign sidebar, topbar, and login page CSS - Migrate ~44 legacy CSS files from hardcoded colors to CSS variables - Remove Dashboards/Tableros section from menu - Clean up commented-out CSS blocks across codebase Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
192 lines
7.2 KiB
JavaScript
192 lines
7.2 KiB
JavaScript
const pool = require('../db/connection');
|
|
|
|
const getReservations = async (req, res) => {
|
|
try {
|
|
const { status, from_date, to_date, search } = req.query;
|
|
let query = `
|
|
SELECT r.*, g.first_name, g.last_name, g.phone, g.email,
|
|
rm.room_number, rm.room_type
|
|
FROM reservations r
|
|
JOIN guests g ON g.id = r.guest_id
|
|
LEFT JOIN rooms rm ON rm.id = r.room_id
|
|
WHERE 1=1
|
|
`;
|
|
const params = [];
|
|
let paramIndex = 1;
|
|
|
|
if (status && status !== 'all') {
|
|
query += ` AND r.status = $${paramIndex++}`;
|
|
params.push(status);
|
|
}
|
|
if (from_date) {
|
|
query += ` AND r.check_in >= $${paramIndex++}`;
|
|
params.push(from_date);
|
|
}
|
|
if (to_date) {
|
|
query += ` AND r.check_out <= $${paramIndex++}`;
|
|
params.push(to_date);
|
|
}
|
|
if (search) {
|
|
query += ` AND (g.first_name ILIKE $${paramIndex} OR g.last_name ILIKE $${paramIndex})`;
|
|
params.push(`%${search}%`);
|
|
paramIndex++;
|
|
}
|
|
query += ' ORDER BY r.created_at DESC LIMIT 100';
|
|
|
|
const result = await pool.query(query, params);
|
|
res.json({ reservations: result.rows });
|
|
} catch (error) {
|
|
console.error(error);
|
|
res.status(500).json({ message: 'Error al obtener reservaciones' });
|
|
}
|
|
};
|
|
|
|
const createReservation = async (req, res) => {
|
|
try {
|
|
const { room_id, guest_id, guest, check_in, check_out, channel, total_amount, adults, children, notes } = req.body;
|
|
const created_by = req.user?.user_id;
|
|
|
|
// Create guest if new
|
|
let finalGuestId = guest_id;
|
|
if (!finalGuestId && guest) {
|
|
const guestResult = await pool.query(
|
|
'INSERT INTO guests (first_name, last_name, email, phone, nationality) VALUES ($1, $2, $3, $4, $5) RETURNING id',
|
|
[guest.first_name, guest.last_name, guest.email || null, guest.phone || null, guest.nationality || null]
|
|
);
|
|
finalGuestId = guestResult.rows[0].id;
|
|
}
|
|
|
|
// Check availability
|
|
const overlap = await pool.query(
|
|
`SELECT id FROM reservations
|
|
WHERE room_id = $1 AND status IN ('confirmed', 'checked_in')
|
|
AND check_in < $3 AND check_out > $2`,
|
|
[room_id, check_in, check_out]
|
|
);
|
|
if (overlap.rows.length > 0) {
|
|
return res.status(409).json({ message: 'La habitacion no esta disponible para esas fechas' });
|
|
}
|
|
|
|
const result = await pool.query(
|
|
`INSERT INTO reservations (room_id, guest_id, check_in, check_out, status, channel, total_amount, adults, children, notes, created_by)
|
|
VALUES ($1, $2, $3, $4, 'pending', $5, $6, $7, $8, $9, $10) RETURNING *`,
|
|
[room_id, finalGuestId, check_in, check_out, channel || 'direct', total_amount, adults || 1, children || 0, notes, created_by]
|
|
);
|
|
|
|
res.status(201).json({ reservation: result.rows[0], message: 'Reservacion creada correctamente' });
|
|
} catch (error) {
|
|
console.error(error);
|
|
res.status(500).json({ message: 'Error al crear reservacion' });
|
|
}
|
|
};
|
|
|
|
const updateReservation = async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const { room_id, check_in, check_out, channel, total_amount, adults, children, notes } = req.body;
|
|
|
|
const result = await pool.query(
|
|
`UPDATE reservations SET room_id = COALESCE($1, room_id), check_in = COALESCE($2, check_in),
|
|
check_out = COALESCE($3, check_out), channel = COALESCE($4, channel),
|
|
total_amount = COALESCE($5, total_amount), adults = COALESCE($6, adults),
|
|
children = COALESCE($7, children), notes = COALESCE($8, notes), updated_at = NOW()
|
|
WHERE id = $9 RETURNING *`,
|
|
[room_id, check_in, check_out, channel, total_amount, adults, children, notes, id]
|
|
);
|
|
|
|
if (result.rows.length === 0) return res.status(404).json({ message: 'Reservacion no encontrada' });
|
|
res.json({ reservation: result.rows[0] });
|
|
} catch (error) {
|
|
console.error(error);
|
|
res.status(500).json({ message: 'Error al actualizar reservacion' });
|
|
}
|
|
};
|
|
|
|
const updateReservationStatus = async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const { status } = req.body;
|
|
const userId = req.user?.user_id;
|
|
|
|
const current = await pool.query('SELECT * FROM reservations WHERE id = $1', [id]);
|
|
if (current.rows.length === 0) return res.status(404).json({ message: 'Reservacion no encontrada' });
|
|
|
|
const reservation = current.rows[0];
|
|
const validTransitions = {
|
|
pending: ['confirmed', 'cancelled'],
|
|
confirmed: ['checked_in', 'cancelled'],
|
|
checked_in: ['checked_out'],
|
|
};
|
|
|
|
const allowed = validTransitions[reservation.status];
|
|
if (!allowed || !allowed.includes(status)) {
|
|
return res.status(400).json({ message: `No se puede cambiar de ${reservation.status} a ${status}` });
|
|
}
|
|
|
|
await pool.query('UPDATE reservations SET status = $1, updated_at = NOW() WHERE id = $2', [status, id]);
|
|
|
|
// Cascading side effects
|
|
if (status === 'checked_in') {
|
|
// Set room to occupied
|
|
await pool.query("UPDATE rooms SET status = 'occupied' WHERE id = $1", [reservation.room_id]);
|
|
await pool.query(
|
|
'INSERT INTO room_status_log (room_id, previous_status, new_status, changed_by) VALUES ($1, $2, $3, $4)',
|
|
[reservation.room_id, 'available', 'occupied', userId]
|
|
);
|
|
// Create guest stay record
|
|
await pool.query(
|
|
'INSERT INTO guest_stays (guest_id, reservation_id, room_id, check_in) VALUES ($1, $2, $3, NOW())',
|
|
[reservation.guest_id, reservation.id, reservation.room_id]
|
|
);
|
|
}
|
|
|
|
if (status === 'checked_out') {
|
|
// Set room to cleaning
|
|
await pool.query("UPDATE rooms SET status = 'cleaning' WHERE id = $1", [reservation.room_id]);
|
|
await pool.query(
|
|
'INSERT INTO room_status_log (room_id, previous_status, new_status, changed_by) VALUES ($1, $2, $3, $4)',
|
|
[reservation.room_id, 'occupied', 'cleaning', userId]
|
|
);
|
|
// Create housekeeping task
|
|
await pool.query(
|
|
`INSERT INTO housekeeping_tasks (room_id, priority, type, status) VALUES ($1, 'high', 'checkout', 'pending')`,
|
|
[reservation.room_id]
|
|
);
|
|
// Close guest stay
|
|
await pool.query(
|
|
'UPDATE guest_stays SET check_out = NOW(), total_charged = $1 WHERE reservation_id = $2 AND check_out IS NULL',
|
|
[reservation.total_amount, reservation.id]
|
|
);
|
|
}
|
|
|
|
if (status === 'cancelled') {
|
|
// Free room if it was occupied
|
|
if (reservation.status === 'checked_in') {
|
|
await pool.query("UPDATE rooms SET status = 'available' WHERE id = $1", [reservation.room_id]);
|
|
}
|
|
}
|
|
|
|
res.json({ message: 'Estado de reservacion actualizado' });
|
|
} catch (error) {
|
|
console.error(error);
|
|
res.status(500).json({ message: 'Error al actualizar estado' });
|
|
}
|
|
};
|
|
|
|
const checkAvailability = async (req, res) => {
|
|
try {
|
|
const { room_id, check_in, check_out } = req.query;
|
|
const overlap = await pool.query(
|
|
`SELECT id FROM reservations WHERE room_id = $1 AND status IN ('confirmed', 'checked_in')
|
|
AND check_in < $3 AND check_out > $2`,
|
|
[room_id, check_in, check_out]
|
|
);
|
|
res.json({ available: overlap.rows.length === 0 });
|
|
} catch (error) {
|
|
console.error(error);
|
|
res.status(500).json({ message: 'Error al verificar disponibilidad' });
|
|
}
|
|
};
|
|
|
|
module.exports = { getReservations, createReservation, updateReservation, updateReservationStatus, checkAvailability };
|