# Hotel Front-Office System Implementation Plan > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. **Goal:** Add all front-office modules (room dashboard, reservations, guests, housekeeping, room service, events/venues, scheduling, reports) to the existing hotel management system, plus JWT auth upgrade, i18n, and dark theme. **Architecture:** Express 5 backend with PostgreSQL (direct SQL for new tables, consistent with existing stored-function pattern). React 19 frontend with React Router DOM 7, Tailwind 4 + new dark theme CSS tokens. JWT auth with httpOnly refresh cookies. react-i18next for bilingual ES/EN. **Tech Stack:** Node.js 20, Express 5, PostgreSQL 15, React 19, Vite 7, Tailwind CSS 4, jsonwebtoken, bcryptjs, react-i18next --- ## Key File Paths Reference ``` Backend root: backend/hotel_hacienda/ Backend src: backend/hotel_hacienda/src/ Frontend root: frontend/Frontend-Hotel/ Frontend src: frontend/Frontend-Hotel/src/ ``` **Existing patterns to follow:** - Routes: `src/routes/.routes.js` using `express.Router()` - Controllers: `src/controllers/.controller.js` with `pool.query()` calls - Frontend pages: `src/pages//.jsx` - Frontend components: `src/components//.jsx` - Menu config: `src/constants/menuconfig.js` (label + spanish_label for each item) - API calls: `axios` via `src/services/api.js` instance --- ## Task 1: Database Schema Migration **Files:** - Create: `backend/hotel_hacienda/src/db/migrations/001_front_office_tables.sql` **Step 1: Create migrations directory and SQL file** ```sql -- 001_front_office_tables.sql -- Add status column to existing rooms table ALTER TABLE rooms ADD COLUMN IF NOT EXISTS status VARCHAR(20) DEFAULT 'available'; ALTER TABLE rooms ADD COLUMN IF NOT EXISTS floor INTEGER DEFAULT 1; -- Guests CREATE TABLE IF NOT EXISTS guests ( id SERIAL PRIMARY KEY, first_name VARCHAR(100) NOT NULL, last_name VARCHAR(100) NOT NULL, email VARCHAR(255), phone VARCHAR(50), id_type VARCHAR(50), id_number VARCHAR(100), nationality VARCHAR(100), address TEXT, notes TEXT, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); -- Reservations CREATE TABLE IF NOT EXISTS reservations ( id SERIAL PRIMARY KEY, room_id INTEGER NOT NULL, guest_id INTEGER NOT NULL REFERENCES guests(id), check_in DATE NOT NULL, check_out DATE NOT NULL, status VARCHAR(20) DEFAULT 'pending', channel VARCHAR(50) DEFAULT 'direct', total_amount DECIMAL(12,2), adults INTEGER DEFAULT 1, children INTEGER DEFAULT 0, notes TEXT, created_by INTEGER, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); CREATE INDEX idx_reservations_status ON reservations(status); CREATE INDEX idx_reservations_dates ON reservations(check_in, check_out); CREATE INDEX idx_reservations_room ON reservations(room_id); -- Guest Stay History CREATE TABLE IF NOT EXISTS guest_stays ( id SERIAL PRIMARY KEY, guest_id INTEGER NOT NULL REFERENCES guests(id), reservation_id INTEGER REFERENCES reservations(id), room_id INTEGER NOT NULL, check_in TIMESTAMP, check_out TIMESTAMP, total_charged DECIMAL(12,2), rating INTEGER CHECK (rating >= 1 AND rating <= 5), feedback TEXT, created_at TIMESTAMP DEFAULT NOW() ); -- Room Status Audit Log CREATE TABLE IF NOT EXISTS room_status_log ( id SERIAL PRIMARY KEY, room_id INTEGER NOT NULL, previous_status VARCHAR(20), new_status VARCHAR(20), changed_by INTEGER, changed_at TIMESTAMP DEFAULT NOW() ); -- Housekeeping Tasks CREATE TABLE IF NOT EXISTS housekeeping_tasks ( id SERIAL PRIMARY KEY, room_id INTEGER NOT NULL, assigned_to INTEGER, priority VARCHAR(10) DEFAULT 'normal', type VARCHAR(20) NOT NULL, status VARCHAR(20) DEFAULT 'pending', notes TEXT, started_at TIMESTAMP, completed_at TIMESTAMP, created_at TIMESTAMP DEFAULT NOW() ); CREATE INDEX idx_housekeeping_status ON housekeeping_tasks(status); -- Menu Items (Room Service) CREATE TABLE IF NOT EXISTS menu_items ( id SERIAL PRIMARY KEY, name VARCHAR(200) NOT NULL, name_es VARCHAR(200), description TEXT, description_es TEXT, price DECIMAL(10,2) NOT NULL, category VARCHAR(50), available BOOLEAN DEFAULT TRUE, created_at TIMESTAMP DEFAULT NOW() ); -- Room Service Orders CREATE TABLE IF NOT EXISTS room_service_orders ( id SERIAL PRIMARY KEY, room_id INTEGER NOT NULL, guest_id INTEGER REFERENCES guests(id), status VARCHAR(20) DEFAULT 'pending', total DECIMAL(10,2), notes TEXT, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); -- Order Items CREATE TABLE IF NOT EXISTS order_items ( id SERIAL PRIMARY KEY, order_id INTEGER NOT NULL REFERENCES room_service_orders(id) ON DELETE CASCADE, menu_item_id INTEGER NOT NULL REFERENCES menu_items(id), quantity INTEGER NOT NULL DEFAULT 1, price DECIMAL(10,2) NOT NULL, notes TEXT ); -- Venues CREATE TABLE IF NOT EXISTS venues ( id SERIAL PRIMARY KEY, name VARCHAR(200) NOT NULL, capacity INTEGER, area_sqm DECIMAL(8,2), price_per_hour DECIMAL(10,2), amenities JSONB DEFAULT '[]', description TEXT, status VARCHAR(20) DEFAULT 'available', created_at TIMESTAMP DEFAULT NOW() ); -- Events CREATE TABLE IF NOT EXISTS events ( id SERIAL PRIMARY KEY, venue_id INTEGER NOT NULL REFERENCES venues(id), name VARCHAR(200) NOT NULL, organizer VARCHAR(200), event_date DATE NOT NULL, start_time TIME NOT NULL, end_time TIME NOT NULL, guest_count INTEGER, status VARCHAR(20) DEFAULT 'confirmed', notes TEXT, total_amount DECIMAL(12,2), created_at TIMESTAMP DEFAULT NOW() ); -- Employee Schedules CREATE TABLE IF NOT EXISTS employee_schedules ( id SERIAL PRIMARY KEY, employee_id INTEGER NOT NULL, schedule_date DATE NOT NULL, shift_type VARCHAR(20) NOT NULL, start_time TIME, end_time TIME, notes TEXT, UNIQUE(employee_id, schedule_date) ); -- Refresh Tokens (JWT) CREATE TABLE IF NOT EXISTS refresh_tokens ( id SERIAL PRIMARY KEY, user_id INTEGER NOT NULL, token VARCHAR(500) NOT NULL, expires_at TIMESTAMP NOT NULL, revoked BOOLEAN DEFAULT FALSE, created_at TIMESTAMP DEFAULT NOW() ); CREATE INDEX idx_refresh_tokens_token ON refresh_tokens(token); CREATE INDEX idx_refresh_tokens_user ON refresh_tokens(user_id); ``` **Step 2: Run migration against database** Run: `docker exec -i psql -U oposgres -d haciendasanangel < backend/hotel_hacienda/src/db/migrations/001_front_office_tables.sql` If not using Docker yet, provide the SQL file to the user for manual execution. **Step 3: Commit** ```bash git add backend/hotel_hacienda/src/db/migrations/ git commit -m "feat: add database migration for front-office tables" ``` --- ## Task 2: JWT Authentication Upgrade **Files:** - Modify: `backend/hotel_hacienda/package.json` (add jsonwebtoken, bcryptjs, cookie-parser) - Create: `backend/hotel_hacienda/src/middlewares/authMiddleware.js` - Modify: `backend/hotel_hacienda/src/controllers/auth.controller.js` - Modify: `backend/hotel_hacienda/src/routes/auth.routes.js` - Modify: `backend/hotel_hacienda/src/app.js` (add cookie-parser, apply auth middleware) - Modify: `frontend/Frontend-Hotel/src/services/api.js` (add interceptors) - Modify: `frontend/Frontend-Hotel/src/context/AuthContext.jsx` (token management) **Step 1: Install backend dependencies** Run: `cd backend/hotel_hacienda && npm install jsonwebtoken bcryptjs cookie-parser` **Step 2: Create auth middleware** Create `backend/hotel_hacienda/src/middlewares/authMiddleware.js`: ```javascript const jwt = require('jsonwebtoken'); const JWT_SECRET = process.env.JWT_SECRET || 'hotel-system-secret-key-change-in-production'; const authMiddleware = (req, res, next) => { const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { return res.status(401).json({ message: 'Token de acceso requerido' }); } const token = authHeader.split(' ')[1]; try { const decoded = jwt.verify(token, JWT_SECRET); req.user = decoded; next(); } catch (error) { if (error.name === 'TokenExpiredError') { return res.status(401).json({ message: 'Token expirado', code: 'TOKEN_EXPIRED' }); } return res.status(403).json({ message: 'Token invalido' }); } }; module.exports = { authMiddleware, JWT_SECRET }; ``` **Step 3: Update auth controller to issue JWT tokens** Replace `backend/hotel_hacienda/src/controllers/auth.controller.js` login function to: - On successful login, generate access token (15min) and refresh token (7 days) - Store refresh token in DB and set as httpOnly cookie - Add `refreshToken` function - Add `logoutUser` function ```javascript const pool = require('../db/connection'); const transporter = require('../services/mailService'); const crypto = require('crypto'); const jwt = require('jsonwebtoken'); const { JWT_SECRET } = require('../middlewares/authMiddleware'); const REFRESH_SECRET = process.env.JWT_REFRESH_SECRET || 'hotel-refresh-secret-change-in-production'; function generateAccessToken(payload) { return jwt.sign(payload, JWT_SECRET, { expiresIn: '15m' }); } function generateRefreshToken(payload) { return jwt.sign(payload, REFRESH_SECRET, { expiresIn: '7d' }); } const login = async (req, res) => { const { name_mail_user, user_pass } = req.body; try { const result = await pool.query('SELECT * from validarusuario($1, $2)', [name_mail_user, user_pass]); const { status, rol, user_id, user_name } = result.rows[0]; if (status !== 1) { return res.json({ message: 'Usuario o contrasena incorrectos', status }); } const tokenPayload = { user_id, user_name, rol }; const accessToken = generateAccessToken(tokenPayload); const refreshToken = generateRefreshToken(tokenPayload); // Store refresh token in DB const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); await pool.query( 'INSERT INTO refresh_tokens (user_id, token, expires_at) VALUES ($1, $2, $3)', [user_id, refreshToken, expiresAt] ); // Set refresh token as httpOnly cookie res.cookie('refreshToken', refreshToken, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'strict', maxAge: 7 * 24 * 60 * 60 * 1000 }); res.json({ rol, user_id, user_name, accessToken, message: 'Usuario autenticado correctamente' }); } catch (error) { console.error(error); res.status(500).json({ status: 500, message: 'Error interno del servidor' }); } }; const refreshTokenHandler = async (req, res) => { const token = req.cookies?.refreshToken; if (!token) { return res.status(401).json({ message: 'Refresh token requerido' }); } try { const decoded = jwt.verify(token, REFRESH_SECRET); // Check if token exists in DB and is not revoked const result = await pool.query( 'SELECT * FROM refresh_tokens WHERE token = $1 AND revoked = false AND expires_at > NOW()', [token] ); if (result.rows.length === 0) { return res.status(403).json({ message: 'Refresh token invalido o revocado' }); } const tokenPayload = { user_id: decoded.user_id, user_name: decoded.user_name, rol: decoded.rol }; const newAccessToken = generateAccessToken(tokenPayload); res.json({ accessToken: newAccessToken }); } catch (error) { return res.status(403).json({ message: 'Refresh token invalido' }); } }; const logoutUser = async (req, res) => { const token = req.cookies?.refreshToken; if (token) { await pool.query('UPDATE refresh_tokens SET revoked = true WHERE token = $1', [token]); res.clearCookie('refreshToken'); } res.json({ message: 'Sesion cerrada correctamente' }); }; // Keep existing createuser and passRecover unchanged... ``` **Step 4: Update auth routes** Add to `backend/hotel_hacienda/src/routes/auth.routes.js`: ```javascript router.post('/refresh', authController.refreshTokenHandler); router.post('/logout', authController.logoutUser); ``` **Step 5: Update app.js to use cookie-parser and auth middleware on protected routes** In `backend/hotel_hacienda/src/app.js`, add: ```javascript const cookieParser = require('cookie-parser'); const { authMiddleware } = require('./middlewares/authMiddleware'); app.use(cookieParser()); // Apply authMiddleware to all routes EXCEPT auth // Add before route registration: app.use('/api/employees', authMiddleware, employeeRoutes); app.use('/api/contracts', authMiddleware, contractsRoutes); // ... apply to all routes except /api/auth ``` Update `corsOptions` to include `credentials: true`. **Step 6: Update frontend API service with token interceptors** Update `frontend/Frontend-Hotel/src/services/api.js`: ```javascript import axios from "axios"; const api = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL, timeout: 15000, withCredentials: true, }); // Request interceptor: attach access token api.interceptors.request.use((config) => { const token = sessionStorage.getItem('accessToken'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }); // Response interceptor: handle 401 and refresh api.interceptors.response.use( (response) => response, async (error) => { const originalRequest = error.config; if (error.response?.status === 401 && error.response?.data?.code === 'TOKEN_EXPIRED' && !originalRequest._retry) { originalRequest._retry = true; try { const { data } = await axios.post( `${import.meta.env.VITE_API_BASE_URL}/auth/refresh`, {}, { withCredentials: true } ); sessionStorage.setItem('accessToken', data.accessToken); originalRequest.headers.Authorization = `Bearer ${data.accessToken}`; return api(originalRequest); } catch (refreshError) { sessionStorage.removeItem('accessToken'); window.location.href = '/'; return Promise.reject(refreshError); } } return Promise.reject(error); } ); export default api; ``` **Step 7: Update AuthContext for JWT** Update `frontend/Frontend-Hotel/src/context/AuthContext.jsx` to: - Store access token in sessionStorage (not localStorage) - Store user data (role, name) in state - Call `/api/auth/logout` on logout - Remove old localStorage "rol" approach ```javascript import { createContext, useState, useEffect } from "react"; import api from "../services/api"; export const AuthContext = createContext(); function AuthProvider({ children }) { const [user, setUser] = useState(null); const [userData, setUserData] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { const token = sessionStorage.getItem("accessToken"); const savedUser = sessionStorage.getItem("userRole"); const savedUserData = sessionStorage.getItem("userData"); if (token && savedUser) { setUser(JSON.parse(savedUser)); if (savedUserData) setUserData(JSON.parse(savedUserData)); } setLoading(false); }, []); const login = (role, data, accessToken) => { setUser(role); setUserData(data); sessionStorage.setItem("accessToken", accessToken); sessionStorage.setItem("userRole", JSON.stringify(role)); sessionStorage.setItem("userData", JSON.stringify(data)); }; const logout = async () => { try { await api.post("/auth/logout"); } catch (e) { // ignore } setUser(null); setUserData(null); sessionStorage.removeItem("accessToken"); sessionStorage.removeItem("userRole"); sessionStorage.removeItem("userData"); }; return ( {children} ); } export { AuthProvider }; ``` **Step 8: Update Login page to use new JWT response** In the Login page, update the login handler to call `login(rol, { user_id, user_name }, accessToken)` using the new response shape. **Step 9: Add JWT_SECRET and JWT_REFRESH_SECRET to .env.example** ``` JWT_SECRET=your-jwt-secret-here JWT_REFRESH_SECRET=your-jwt-refresh-secret-here ``` **Step 10: Commit** ```bash git add -A git commit -m "feat: upgrade authentication to JWT with refresh tokens" ``` --- ## Task 3: i18n Setup with react-i18next **Files:** - Modify: `frontend/Frontend-Hotel/package.json` (add i18next packages) - Create: `frontend/Frontend-Hotel/src/i18n/index.js` - Create: `frontend/Frontend-Hotel/src/i18n/locales/en.json` - Create: `frontend/Frontend-Hotel/src/i18n/locales/es.json` - Modify: `frontend/Frontend-Hotel/src/main.jsx` (add I18nextProvider) - Modify: `frontend/Frontend-Hotel/src/context/LenguageContext.jsx` (integrate with i18next) **Step 1: Install i18n packages** Run: `cd frontend/Frontend-Hotel && npm install react-i18next i18next i18next-browser-languagedetector` **Step 2: Create i18n configuration** Create `frontend/Frontend-Hotel/src/i18n/index.js`: ```javascript import i18n from 'i18next'; import { initReactI18next } from 'react-i18next'; import LanguageDetector from 'i18next-browser-languagedetector'; import en from './locales/en.json'; import es from './locales/es.json'; i18n .use(LanguageDetector) .use(initReactI18next) .init({ resources: { en: { translation: en }, es: { translation: es }, }, fallbackLng: 'en', interpolation: { escapeValue: false, }, }); export default i18n; ``` **Step 3: Create English locale file** Create `frontend/Frontend-Hotel/src/i18n/locales/en.json`: ```json { "nav": { "dashboards": "Dashboards", "roomDashboard": "Room Dashboard", "reservations": "Reservations", "guests": "Guests", "housekeeping": "Housekeeping", "roomService": "Room Service", "eventsVenues": "Events & Venues", "schedules": "Schedules", "operationalReports": "Operational Reports", "income": "Income", "expenses": "Expenses", "expensesApproval": "Expenses to be approved", "inventory": "Inventory", "payroll": "Payroll", "hotel": "Hotel", "housekeeper": "Housekeeper", "services": "Services", "operations": "Operations", "staff": "Staff" }, "common": { "save": "Save", "cancel": "Cancel", "delete": "Delete", "edit": "Edit", "create": "Create", "search": "Search", "filter": "Filter", "loading": "Loading...", "noResults": "No results found", "confirm": "Confirm", "back": "Back", "actions": "Actions", "status": "Status", "date": "Date", "total": "Total", "notes": "Notes" }, "rooms": { "title": "Room Dashboard", "available": "Available", "occupied": "Occupied", "cleaning": "Cleaning", "maintenance": "Maintenance", "occupancyRate": "Occupancy Rate", "availableRooms": "Available Rooms", "todayCheckIns": "Today's Check-ins", "todayCheckOuts": "Today's Check-outs", "dailyRevenue": "Daily Revenue", "floor": "Floor", "roomNumber": "Room Number", "roomType": "Room Type", "pricePerNight": "Price per Night", "amenities": "Amenities", "guestInfo": "Guest Information" }, "reservations": { "title": "Reservations", "newReservation": "New Reservation", "checkIn": "Check-in", "checkOut": "Check-out", "guestName": "Guest Name", "roomType": "Room Type", "channel": "Channel", "duration": "Duration", "nights": "nights", "status": { "pending": "Pending", "confirmed": "Confirmed", "checkedIn": "Checked In", "checkedOut": "Checked Out", "cancelled": "Cancelled" }, "channels": { "direct": "Direct", "booking": "Booking.com", "expedia": "Expedia", "airbnb": "Airbnb", "other": "Other" } }, "guests": { "title": "Guests", "newGuest": "New Guest", "firstName": "First Name", "lastName": "Last Name", "email": "Email", "phone": "Phone", "idType": "ID Type", "idNumber": "ID Number", "nationality": "Nationality", "address": "Address", "stayHistory": "Stay History", "currentRoom": "Current Room" }, "housekeeping": { "title": "Housekeeping", "pendingTasks": "Pending Tasks", "inProgress": "In Progress", "completedTasks": "Completed", "assignTo": "Assign to", "priority": { "high": "High", "normal": "Normal", "low": "Low" }, "type": { "checkout": "Checkout", "maintenance": "Maintenance", "deepClean": "Deep Clean", "turndown": "Turndown" }, "startTask": "Start", "completeTask": "Complete", "staffAvailability": "Staff Availability" }, "roomService": { "title": "Room Service", "activeOrders": "Active Orders", "orderHistory": "Order History", "newOrder": "New Order", "menuManagement": "Menu Management", "preparing": "Preparing", "delivering": "Delivering", "delivered": "Delivered", "menuItem": "Menu Item", "price": "Price", "quantity": "Quantity", "category": "Category" }, "events": { "title": "Events & Venues", "venues": "Venues", "upcomingEvents": "Upcoming Events", "newEvent": "New Event", "venueName": "Venue Name", "capacity": "Capacity", "area": "Area", "pricePerHour": "Price per Hour", "eventName": "Event Name", "organizer": "Organizer", "guestCount": "Guest Count", "eventDate": "Date", "startTime": "Start Time", "endTime": "End Time" }, "schedules": { "title": "Schedules", "shifts": { "morning": "Morning", "afternoon": "Afternoon", "night": "Night", "off": "Off" }, "department": "Department", "employee": "Employee", "week": "Week" }, "reports": { "title": "Operational Reports", "occupancyRate": "Occupancy Rate", "revenue": "Revenue", "guestSatisfaction": "Guest Satisfaction", "bookingSources": "Booking Sources", "period": { "week": "Week", "month": "Month", "quarter": "Quarter", "year": "Year" }, "vsTarget": "vs Target", "trend": "Trend" }, "auth": { "login": "Login", "logout": "Logout", "email": "Email", "password": "Password", "forgotPassword": "Forgot Password?" } } ``` **Step 4: Create Spanish locale file** Create `frontend/Frontend-Hotel/src/i18n/locales/es.json`: ```json { "nav": { "dashboards": "Tableros", "roomDashboard": "Panel de Habitaciones", "reservations": "Reservaciones", "guests": "Huespedes", "housekeeping": "Limpieza", "roomService": "Servicio a Habitacion", "eventsVenues": "Eventos y Salones", "schedules": "Horarios", "operationalReports": "Reportes Operativos", "income": "Ingresos", "expenses": "Gastos", "expensesApproval": "Gastos por aprobar", "inventory": "Inventario", "payroll": "Nomina", "hotel": "Hotel", "housekeeper": "Cuidador de Habitaciones", "services": "Servicios", "operations": "Operaciones", "staff": "Personal" }, "common": { "save": "Guardar", "cancel": "Cancelar", "delete": "Eliminar", "edit": "Editar", "create": "Crear", "search": "Buscar", "filter": "Filtrar", "loading": "Cargando...", "noResults": "Sin resultados", "confirm": "Confirmar", "back": "Regresar", "actions": "Acciones", "status": "Estado", "date": "Fecha", "total": "Total", "notes": "Notas" }, "rooms": { "title": "Panel de Habitaciones", "available": "Disponible", "occupied": "Ocupada", "cleaning": "Limpieza", "maintenance": "Mantenimiento", "occupancyRate": "Tasa de Ocupacion", "availableRooms": "Habitaciones Disponibles", "todayCheckIns": "Check-ins de Hoy", "todayCheckOuts": "Check-outs de Hoy", "dailyRevenue": "Ingreso Diario", "floor": "Piso", "roomNumber": "Numero de Habitacion", "roomType": "Tipo de Habitacion", "pricePerNight": "Precio por Noche", "amenities": "Amenidades", "guestInfo": "Informacion del Huesped" }, "reservations": { "title": "Reservaciones", "newReservation": "Nueva Reservacion", "checkIn": "Entrada", "checkOut": "Salida", "guestName": "Nombre del Huesped", "roomType": "Tipo de Habitacion", "channel": "Canal", "duration": "Duracion", "nights": "noches", "status": { "pending": "Pendiente", "confirmed": "Confirmada", "checkedIn": "Registrado", "checkedOut": "Check-out", "cancelled": "Cancelada" }, "channels": { "direct": "Directo", "booking": "Booking.com", "expedia": "Expedia", "airbnb": "Airbnb", "other": "Otro" } }, "guests": { "title": "Huespedes", "newGuest": "Nuevo Huesped", "firstName": "Nombre", "lastName": "Apellido", "email": "Correo", "phone": "Telefono", "idType": "Tipo de ID", "idNumber": "Numero de ID", "nationality": "Nacionalidad", "address": "Direccion", "stayHistory": "Historial de Estadias", "currentRoom": "Habitacion Actual" }, "housekeeping": { "title": "Limpieza", "pendingTasks": "Tareas Pendientes", "inProgress": "En Progreso", "completedTasks": "Completadas", "assignTo": "Asignar a", "priority": { "high": "Alta", "normal": "Normal", "low": "Baja" }, "type": { "checkout": "Check-out", "maintenance": "Mantenimiento", "deepClean": "Limpieza Profunda", "turndown": "Preparacion Nocturna" }, "startTask": "Iniciar", "completeTask": "Completar", "staffAvailability": "Disponibilidad del Personal" }, "roomService": { "title": "Servicio a Habitacion", "activeOrders": "Ordenes Activas", "orderHistory": "Historial de Ordenes", "newOrder": "Nueva Orden", "menuManagement": "Gestion del Menu", "preparing": "Preparando", "delivering": "Entregando", "delivered": "Entregado", "menuItem": "Platillo", "price": "Precio", "quantity": "Cantidad", "category": "Categoria" }, "events": { "title": "Eventos y Salones", "venues": "Salones", "upcomingEvents": "Proximos Eventos", "newEvent": "Nuevo Evento", "venueName": "Nombre del Salon", "capacity": "Capacidad", "area": "Area", "pricePerHour": "Precio por Hora", "eventName": "Nombre del Evento", "organizer": "Organizador", "guestCount": "Numero de Invitados", "eventDate": "Fecha", "startTime": "Hora de Inicio", "endTime": "Hora de Fin" }, "schedules": { "title": "Horarios", "shifts": { "morning": "Matutino", "afternoon": "Vespertino", "night": "Nocturno", "off": "Descanso" }, "department": "Departamento", "employee": "Empleado", "week": "Semana" }, "reports": { "title": "Reportes Operativos", "occupancyRate": "Tasa de Ocupacion", "revenue": "Ingresos", "guestSatisfaction": "Satisfaccion del Huesped", "bookingSources": "Fuentes de Reservacion", "period": { "week": "Semana", "month": "Mes", "quarter": "Trimestre", "year": "Ano" }, "vsTarget": "vs Objetivo", "trend": "Tendencia" }, "auth": { "login": "Iniciar Sesion", "logout": "Cerrar Sesion", "email": "Correo", "password": "Contrasena", "forgotPassword": "Olvido su contrasena?" } } ``` **Step 5: Initialize i18n in main.jsx** Add `import './i18n';` at the top of `frontend/Frontend-Hotel/src/main.jsx` (before App import). **Step 6: Update LenguageContext to use i18next** Update `frontend/Frontend-Hotel/src/context/LenguageContext.jsx`: ```javascript import { createContext, useContext } from "react"; import { useTranslation } from "react-i18next"; export const langContext = createContext(); export const LangProvider = ({ children }) => { const { i18n } = useTranslation(); const lang = i18n.language?.startsWith('es') ? 'es' : 'en'; const toggleLang = (event) => { const newLang = event.target.value; i18n.changeLanguage(newLang); }; return ( {children} ); }; export const useLang = () => useContext(langContext); ``` **Step 7: Commit** ```bash git add -A git commit -m "feat: add react-i18next bilingual support (ES/EN)" ``` --- ## Task 4: Dark Theme Design System **Files:** - Create: `frontend/Frontend-Hotel/src/styles/theme.css` - Modify: `frontend/Frontend-Hotel/src/styles/Dashboard.css` (update sidebar, topbar, content) - Modify: `frontend/Frontend-Hotel/src/styles/global.css` (update base styles) **Step 1: Create theme.css with CSS custom properties** Create `frontend/Frontend-Hotel/src/styles/theme.css`: ```css :root { /* Background layers */ --bg-primary: #0a0a0a; --bg-secondary: #1a1a1a; --bg-elevated: #2a2a2a; --bg-surface: #1e1e1e; /* Accent */ --accent-gold: #d4a574; --accent-gold-hover: #e0b88a; --accent-gold-muted: rgba(212, 165, 116, 0.15); /* Text */ --text-primary: #ffffff; --text-secondary: #a0a0a0; --text-muted: #666666; /* Status colors */ --status-available: #22c55e; --status-occupied: #3b82f6; --status-cleaning: #eab308; --status-maintenance: #ef4444; /* Semantic colors */ --success: #22c55e; --warning: #eab308; --error: #ef4444; --info: #3b82f6; /* Borders */ --border-color: #333333; --border-subtle: #2a2a2a; /* Shadows */ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3); --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.4); --shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.5); /* Border radius */ --radius-sm: 6px; --radius-md: 8px; --radius-lg: 12px; --radius-xl: 16px; /* Spacing */ --space-xs: 4px; --space-sm: 8px; --space-md: 16px; --space-lg: 24px; --space-xl: 32px; /* Typography */ --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; --font-mono: 'JetBrains Mono', 'Fira Code', monospace; } /* Base resets for dark theme */ body { background-color: var(--bg-primary); color: var(--text-primary); font-family: var(--font-sans); margin: 0; } /* Utility classes for new components */ .card { background: var(--bg-secondary); border: 1px solid var(--border-color); border-radius: var(--radius-lg); padding: var(--space-lg); } .card-elevated { background: var(--bg-elevated); border: 1px solid var(--border-color); border-radius: var(--radius-lg); padding: var(--space-lg); } .badge { display: inline-flex; align-items: center; padding: 2px 10px; border-radius: 20px; font-size: 0.75rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; } .badge-available { background: rgba(34, 197, 94, 0.15); color: var(--status-available); } .badge-occupied { background: rgba(59, 130, 246, 0.15); color: var(--status-occupied); } .badge-cleaning { background: rgba(234, 179, 8, 0.15); color: var(--status-cleaning); } .badge-maintenance { background: rgba(239, 68, 68, 0.15); color: var(--status-maintenance); } .badge-pending { background: rgba(234, 179, 8, 0.15); color: var(--status-cleaning); } .badge-confirmed { background: rgba(59, 130, 246, 0.15); color: var(--status-occupied); } .badge-success { background: rgba(34, 197, 94, 0.15); color: var(--success); } .badge-error { background: rgba(239, 68, 68, 0.15); color: var(--error); } .kpi-card { background: var(--bg-secondary); border: 1px solid var(--border-color); border-radius: var(--radius-lg); padding: var(--space-lg); display: flex; flex-direction: column; gap: var(--space-sm); } .kpi-card .kpi-value { font-size: 2rem; font-weight: 700; color: var(--text-primary); } .kpi-card .kpi-label { font-size: 0.875rem; color: var(--text-secondary); } .kpi-card .kpi-trend { font-size: 0.75rem; font-weight: 600; } .kpi-trend.positive { color: var(--success); } .kpi-trend.negative { color: var(--error); } /* Button styles */ .btn-gold { background: var(--accent-gold); color: #000; border: none; padding: 10px 20px; border-radius: var(--radius-md); font-weight: 600; cursor: pointer; transition: background 0.2s; } .btn-gold:hover { background: var(--accent-gold-hover); } .btn-outline { background: transparent; color: var(--text-primary); border: 1px solid var(--border-color); padding: 10px 20px; border-radius: var(--radius-md); cursor: pointer; transition: all 0.2s; } .btn-outline:hover { border-color: var(--accent-gold); color: var(--accent-gold); } /* Form inputs dark theme */ .input-dark { background: var(--bg-elevated); border: 1px solid var(--border-color); color: var(--text-primary); padding: 10px 14px; border-radius: var(--radius-md); font-size: 0.875rem; width: 100%; outline: none; transition: border-color 0.2s; } .input-dark:focus { border-color: var(--accent-gold); } .input-dark::placeholder { color: var(--text-muted); } /* Table dark theme */ .table-dark { width: 100%; border-collapse: collapse; } .table-dark th { text-align: left; padding: 12px 16px; color: var(--text-secondary); font-size: 0.75rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; border-bottom: 1px solid var(--border-color); } .table-dark td { padding: 12px 16px; border-bottom: 1px solid var(--border-subtle); color: var(--text-primary); font-size: 0.875rem; } .table-dark tr:hover { background: var(--bg-elevated); } /* Grid layout helpers */ .grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--space-lg); } .grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-lg); } .grid-4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--space-lg); } .grid-5 { display: grid; grid-template-columns: repeat(5, 1fr); gap: var(--space-lg); } @media (max-width: 1024px) { .grid-4, .grid-5 { grid-template-columns: repeat(2, 1fr); } .grid-3 { grid-template-columns: repeat(2, 1fr); } } @media (max-width: 640px) { .grid-2, .grid-3, .grid-4, .grid-5 { grid-template-columns: 1fr; } } ``` **Step 2: Update Dashboard.css for dark theme** Update sidebar gradient, topbar, content background to use CSS variables. Change: - Sidebar: `background: linear-gradient(to bottom, var(--bg-secondary), var(--bg-primary))` - Topbar: `background-color: var(--bg-secondary)` with gold accent border-bottom - Content area: `background-color: var(--bg-primary)` - Submenu links: gold accent styling **Step 3: Import theme.css in global.css** Add `@import './theme.css';` at the top of `frontend/Frontend-Hotel/src/styles/global.css`. **Step 4: Commit** ```bash git add -A git commit -m "feat: add dark theme design system with gold accents" ``` --- ## Task 5: Room Dashboard & Status Management (Backend) **Files:** - Create: `backend/hotel_hacienda/src/controllers/rooms.controller.js` - Create: `backend/hotel_hacienda/src/routes/rooms.routes.js` - Create: `backend/hotel_hacienda/src/controllers/dashboard.controller.js` - Create: `backend/hotel_hacienda/src/routes/dashboard.routes.js` - Modify: `backend/hotel_hacienda/src/app.js` (register new routes) **Step 1: Create rooms controller** Create `backend/hotel_hacienda/src/controllers/rooms.controller.js`: ```javascript const pool = require('../db/connection'); const getRoomsWithStatus = async (req, res) => { try { const result = await pool.query(` SELECT r.*, res.id as reservation_id, g.first_name || ' ' || g.last_name as guest_name, g.phone as guest_phone, res.check_in, res.check_out FROM rooms r LEFT JOIN reservations res ON res.room_id = r.id_room AND res.status = 'checked_in' LEFT JOIN guests g ON g.id = res.guest_id ORDER BY r.floor, r.name_room `); // Note: adjust column names based on actual rooms table schema res.json({ rooms: result.rows }); } catch (error) { console.error(error); res.status(500).json({ message: 'Error al obtener estado de habitaciones' }); } }; const updateRoomStatus = async (req, res) => { try { const { id } = req.params; const { status } = req.body; const userId = req.user?.user_id; const validStatuses = ['available', 'occupied', 'cleaning', 'maintenance']; if (!validStatuses.includes(status)) { return res.status(400).json({ message: 'Estado invalido' }); } // Get current status for audit log const current = await pool.query('SELECT status FROM rooms WHERE id_room = $1', [id]); if (current.rows.length === 0) { return res.status(404).json({ message: 'Habitacion no encontrada' }); } const previousStatus = current.rows[0].status; // Update room status await pool.query('UPDATE rooms SET status = $1 WHERE id_room = $2', [status, id]); // Log status change await pool.query( 'INSERT INTO room_status_log (room_id, previous_status, new_status, changed_by) VALUES ($1, $2, $3, $4)', [id, previousStatus, status, userId] ); // If set to "cleaning", auto-create housekeeping task if (status === 'cleaning') { await pool.query( `INSERT INTO housekeeping_tasks (room_id, priority, type, status) VALUES ($1, 'high', 'checkout', 'pending')`, [id] ); } res.json({ message: 'Estado actualizado correctamente' }); } catch (error) { console.error(error); res.status(500).json({ message: 'Error al actualizar estado' }); } }; module.exports = { getRoomsWithStatus, updateRoomStatus }; ``` **Step 2: Create rooms routes** Create `backend/hotel_hacienda/src/routes/rooms.routes.js`: ```javascript const express = require('express'); const router = express.Router(); const roomsController = require('../controllers/rooms.controller'); router.get('/status', roomsController.getRoomsWithStatus); router.put('/:id/status', roomsController.updateRoomStatus); module.exports = router; ``` **Step 3: Create dashboard controller** Create `backend/hotel_hacienda/src/controllers/dashboard.controller.js`: ```javascript const pool = require('../db/connection'); const getKPIs = async (req, res) => { try { const today = new Date().toISOString().split('T')[0]; const [totalRooms, availableRooms, checkIns, checkOuts] = await Promise.all([ pool.query('SELECT COUNT(*) as count FROM rooms'), pool.query("SELECT COUNT(*) as count FROM rooms WHERE status = 'available'"), pool.query("SELECT COUNT(*) as count FROM reservations WHERE check_in = $1 AND status IN ('confirmed', 'checked_in')", [today]), pool.query("SELECT COUNT(*) as count FROM reservations WHERE check_out = $1 AND status = 'checked_in'", [today]), ]); const total = parseInt(totalRooms.rows[0].count); const available = parseInt(availableRooms.rows[0].count); const occupancy = total > 0 ? Math.round(((total - available) / total) * 100) : 0; res.json({ occupancy, availableRooms: available, totalRooms: total, todayCheckIns: parseInt(checkIns.rows[0].count), todayCheckOuts: parseInt(checkOuts.rows[0].count), }); } catch (error) { console.error(error); res.status(500).json({ message: 'Error al obtener KPIs' }); } }; const getWeeklyRevenue = async (req, res) => { try { const result = await pool.query(` SELECT DATE(check_out) as day, COALESCE(SUM(total_amount), 0) as revenue FROM reservations WHERE check_out >= CURRENT_DATE - INTERVAL '7 days' AND status = 'checked_out' GROUP BY DATE(check_out) ORDER BY day `); res.json({ weeklyRevenue: result.rows }); } catch (error) { console.error(error); res.status(500).json({ message: 'Error al obtener ingresos semanales' }); } }; const getTodayArrivals = async (req, res) => { try { const today = new Date().toISOString().split('T')[0]; const result = await pool.query(` SELECT r.id, r.check_in, r.check_out, r.room_id, r.status, g.first_name, g.last_name, g.phone FROM reservations r JOIN guests g ON g.id = r.guest_id WHERE (r.check_in = $1 OR r.check_out = $1) AND r.status IN ('confirmed', 'checked_in') ORDER BY r.check_in `, [today]); res.json({ arrivals: result.rows }); } catch (error) { console.error(error); res.status(500).json({ message: 'Error al obtener llegadas' }); } }; module.exports = { getKPIs, getWeeklyRevenue, getTodayArrivals }; ``` **Step 4: Create dashboard routes** Create `backend/hotel_hacienda/src/routes/dashboard.routes.js`: ```javascript const express = require('express'); const router = express.Router(); const dashboardController = require('../controllers/dashboard.controller'); router.get('/kpis', dashboardController.getKPIs); router.get('/weekly-revenue', dashboardController.getWeeklyRevenue); router.get('/today-arrivals', dashboardController.getTodayArrivals); module.exports = router; ``` **Step 5: Register routes in app.js** Add to `backend/hotel_hacienda/src/app.js`: ```javascript const roomsRoutes = require('./routes/rooms.routes'); const dashboardRoutes = require('./routes/dashboard.routes'); app.use('/api/rooms', authMiddleware, roomsRoutes); app.use('/api/dashboard', authMiddleware, dashboardRoutes); ``` **Step 6: Commit** ```bash git add -A git commit -m "feat: add room status and dashboard KPI API endpoints" ``` --- ## Task 6: Room Dashboard (Frontend) **Files:** - Create: `frontend/Frontend-Hotel/src/pages/RoomDashboard/RoomDashboard.jsx` - Create: `frontend/Frontend-Hotel/src/pages/RoomDashboard/components/KPIBar.jsx` - Create: `frontend/Frontend-Hotel/src/pages/RoomDashboard/components/RoomGrid.jsx` - Create: `frontend/Frontend-Hotel/src/pages/RoomDashboard/components/RoomDetailModal.jsx` - Create: `frontend/Frontend-Hotel/src/services/roomService.js` - Modify: `frontend/Frontend-Hotel/src/App.jsx` (add route) - Modify: `frontend/Frontend-Hotel/src/constants/menuconfig.js` (add menu entry) **Step 1: Create room service** Create `frontend/Frontend-Hotel/src/services/roomService.js`: ```javascript import api from "./api"; export const getRoomsWithStatus = () => api.get("/rooms/status"); export const updateRoomStatus = (id, status) => api.put(`/rooms/${id}/status`, { status }); export const getDashboardKPIs = () => api.get("/dashboard/kpis"); export const getWeeklyRevenue = () => api.get("/dashboard/weekly-revenue"); export const getTodayArrivals = () => api.get("/dashboard/today-arrivals"); ``` **Step 2: Create KPIBar component** Create `frontend/Frontend-Hotel/src/pages/RoomDashboard/components/KPIBar.jsx`: A horizontal bar with 4-5 KPI cards showing occupancy %, available rooms, today's check-ins, today's check-outs. Use `kpi-card` class from theme.css. Use `useTranslation()` for labels. **Step 3: Create RoomGrid component** Create `frontend/Frontend-Hotel/src/pages/RoomDashboard/components/RoomGrid.jsx`: Grid of room cards grouped by floor. Each card shows room number, status color indicator, guest name if occupied, price. Color coding: - Available: `var(--status-available)` border-left - Occupied: `var(--status-occupied)` border-left - Cleaning: `var(--status-cleaning)` border-left - Maintenance: `var(--status-maintenance)` border-left Click opens RoomDetailModal. **Step 4: Create RoomDetailModal component** Create `frontend/Frontend-Hotel/src/pages/RoomDashboard/components/RoomDetailModal.jsx`: Modal showing full room details: room number, type, guest info, amenities, current reservation dates, price per night. Include status change buttons. **Step 5: Create RoomDashboard page** Create `frontend/Frontend-Hotel/src/pages/RoomDashboard/RoomDashboard.jsx`: ```javascript import React, { useState, useEffect } from "react"; import { useTranslation } from "react-i18next"; import KPIBar from "./components/KPIBar"; import RoomGrid from "./components/RoomGrid"; import RoomDetailModal from "./components/RoomDetailModal"; import { getRoomsWithStatus, getDashboardKPIs, updateRoomStatus } from "../../services/roomService"; import "../../styles/theme.css"; export default function RoomDashboard() { const { t } = useTranslation(); const [rooms, setRooms] = useState([]); const [kpis, setKpis] = useState({}); const [selectedRoom, setSelectedRoom] = useState(null); const [loading, setLoading] = useState(true); const fetchData = async () => { try { const [roomsRes, kpisRes] = await Promise.all([ getRoomsWithStatus(), getDashboardKPIs(), ]); setRooms(roomsRes.data.rooms); setKpis(kpisRes.data); } catch (error) { console.error(error); } finally { setLoading(false); } }; useEffect(() => { fetchData(); }, []); const handleStatusChange = async (roomId, newStatus) => { await updateRoomStatus(roomId, newStatus); fetchData(); setSelectedRoom(null); }; if (loading) return

{t('common.loading')}

; return (

{t('rooms.title')}

{selectedRoom && ( setSelectedRoom(null)} onStatusChange={handleStatusChange} /> )}
); } ``` **Step 6: Add route in App.jsx** Add import and route: ```javascript import RoomDashboard from "./pages/RoomDashboard/RoomDashboard.jsx"; // Inside : } /> ``` **Step 7: Add to menu config** Add to `frontend/Frontend-Hotel/src/constants/menuconfig.js`: ```javascript operations: { label: "Operations", spanish_label: "Operaciones", basePath: "/app/room-dashboard", submenu: [ { label: "Room Dashboard", spanish_label: "Panel de Habitaciones", route: "/app/room-dashboard" }, { label: "Reservations", spanish_label: "Reservaciones", route: "/app/reservations" }, { label: "Guests", spanish_label: "Huespedes", route: "/app/guests" }, ], }, ``` **Step 8: Commit** ```bash git add -A git commit -m "feat: add room dashboard with KPI bar and floor grid" ``` --- ## Task 7: Reservations Module (Backend) **Files:** - Create: `backend/hotel_hacienda/src/controllers/reservations.controller.js` - Create: `backend/hotel_hacienda/src/routes/reservations.routes.js` - Modify: `backend/hotel_hacienda/src/app.js` **Step 1: Create reservations controller** Create `backend/hotel_hacienda/src/controllers/reservations.controller.js` with: - `getReservations(req, res)` - GET with query params: status, from_date, to_date, search (guest name). Uses JOIN on guests table. - `createReservation(req, res)` - POST. Validates room availability (no overlapping checked_in/confirmed reservations for same room and date range). Creates guest if guest_id not provided (inline guest creation). Returns new reservation. - `updateReservation(req, res)` - PUT /:id. Updates editable fields. - `updateReservationStatus(req, res)` - PUT /:id/status. State machine validation: - pending -> confirmed, cancelled - confirmed -> checked_in, cancelled - checked_in -> checked_out - On checked_in: set room status to 'occupied', create guest_stay record - On checked_out: set room status to 'cleaning', create housekeeping task, update guest_stay check_out - `checkAvailability(req, res)` - GET /availability?room_id=&check_in=&check_out=. Returns boolean. **Step 2: Create reservations routes** ```javascript const express = require('express'); const router = express.Router(); const ctrl = require('../controllers/reservations.controller'); router.get('/', ctrl.getReservations); router.post('/', ctrl.createReservation); router.put('/:id', ctrl.updateReservation); router.put('/:id/status', ctrl.updateReservationStatus); router.get('/availability', ctrl.checkAvailability); module.exports = router; ``` **Step 3: Register in app.js** ```javascript const reservationsRoutes = require('./routes/reservations.routes'); app.use('/api/reservations', authMiddleware, reservationsRoutes); ``` **Step 4: Commit** ```bash git add -A git commit -m "feat: add reservations CRUD with status state machine" ``` --- ## Task 8: Reservations Module (Frontend) **Files:** - Create: `frontend/Frontend-Hotel/src/pages/Reservations/Reservations.jsx` - Create: `frontend/Frontend-Hotel/src/pages/Reservations/NewReservation.jsx` - Create: `frontend/Frontend-Hotel/src/pages/Reservations/components/ReservationCard.jsx` - Create: `frontend/Frontend-Hotel/src/services/reservationService.js` - Modify: `frontend/Frontend-Hotel/src/App.jsx` **Step 1: Create reservation service** ```javascript import api from "./api"; export const getReservations = (params) => api.get("/reservations", { params }); export const createReservation = (data) => api.post("/reservations", data); export const updateReservation = (id, data) => api.put(`/reservations/${id}`, data); export const updateReservationStatus = (id, status) => api.put(`/reservations/${id}/status`, { status }); export const checkAvailability = (params) => api.get("/reservations/availability", { params }); ``` **Step 2: Create ReservationCard component** Card showing: guest name, room, dates (check-in/out), duration in nights, total amount, status badge, channel badge, phone. Status-contextual action buttons (Confirm, Check-in, Check-out, Cancel). **Step 3: Create Reservations list page** Filterable card grid with status tabs (All, Pending, Confirmed, Checked-in, Checked-out). Search by guest name. Date range filter. "New Reservation" button. **Step 4: Create NewReservation form page** Form with: date pickers (check-in/out), room selector (with availability check), guest selector (search existing or create new inline), channel dropdown, adults/children count, notes, total amount. Use react-hook-form + yup validation. **Step 5: Add routes in App.jsx** ```javascript import Reservations from "./pages/Reservations/Reservations.jsx"; import NewReservation from "./pages/Reservations/NewReservation.jsx"; } /> } /> ``` **Step 6: Commit** ```bash git add -A git commit -m "feat: add reservations UI with card grid and creation form" ``` --- ## Task 9: Guest Management (Backend + Frontend) **Files:** - Create: `backend/hotel_hacienda/src/controllers/guests.controller.js` - Create: `backend/hotel_hacienda/src/routes/guests.routes.js` - Create: `frontend/Frontend-Hotel/src/pages/Guests/Guests.jsx` - Create: `frontend/Frontend-Hotel/src/pages/Guests/GuestDetail.jsx` - Create: `frontend/Frontend-Hotel/src/services/guestService.js` - Modify: `backend/hotel_hacienda/src/app.js` - Modify: `frontend/Frontend-Hotel/src/App.jsx` **Step 1: Create guests controller** - `getGuests` - GET with search (name, email, phone) and pagination (limit/offset) - `getGuestById` - GET /:id with current stay info (JOIN reservations WHERE status='checked_in') - `createGuest` - POST with validation - `updateGuest` - PUT /:id - `getGuestStays` - GET /:id/stays (JOIN guest_stays + rooms) **Step 2: Create guests routes** ```javascript router.get('/', ctrl.getGuests); router.get('/:id', ctrl.getGuestById); router.post('/', ctrl.createGuest); router.put('/:id', ctrl.updateGuest); router.get('/:id/stays', ctrl.getGuestStays); ``` **Step 3: Register in app.js** ```javascript const guestsRoutes = require('./routes/guests.routes'); app.use('/api/guests', authMiddleware, guestsRoutes); ``` **Step 4: Create guestService.js** ```javascript import api from "./api"; export const getGuests = (params) => api.get("/guests", { params }); export const getGuestById = (id) => api.get(`/guests/${id}`); export const createGuest = (data) => api.post("/guests", data); export const updateGuest = (id, data) => api.put(`/guests/${id}`, data); export const getGuestStays = (id) => api.get(`/guests/${id}/stays`); ``` **Step 5: Create Guests list page** Card grid with guest cards (avatar with initials, name, phone, email, current room if checked-in). Search bar. Click to navigate to detail. **Step 6: Create GuestDetail page** Profile view: personal info, contact, ID info. Stay history table (dates, room, amount, rating). Edit button opens inline form. **Step 7: Add routes** ```javascript import Guests from "./pages/Guests/Guests.jsx"; import GuestDetail from "./pages/Guests/GuestDetail.jsx"; } /> } /> ``` **Step 8: Commit** ```bash git add -A git commit -m "feat: add guest management with profiles and stay history" ``` --- ## Task 10: Housekeeping Module (Backend + Frontend) **Files:** - Create: `backend/hotel_hacienda/src/controllers/housekeeping.controller.js` - Create: `backend/hotel_hacienda/src/routes/housekeeping.routes.js` - Create: `frontend/Frontend-Hotel/src/pages/Housekeeping/Housekeeping.jsx` - Create: `frontend/Frontend-Hotel/src/pages/Housekeeping/components/TaskCard.jsx` - Create: `frontend/Frontend-Hotel/src/pages/Housekeeping/components/StaffPanel.jsx` - Create: `frontend/Frontend-Hotel/src/services/housekeepingService.js` - Modify: `backend/hotel_hacienda/src/app.js` - Modify: `frontend/Frontend-Hotel/src/App.jsx` - Modify: `frontend/Frontend-Hotel/src/constants/menuconfig.js` **Step 1: Create housekeeping controller** - `getTasks` - GET with filters: status (pending/in_progress/completed), priority, assigned_to. JOIN rooms for room name, employees for assigned staff name. - `createTask` - POST (manual task creation) - `updateTask` - PUT /:id. Handles: - Assign staff (set assigned_to) - Start task (set status='in_progress', started_at=NOW()) - Complete task (set status='completed', completed_at=NOW(), set room status to 'available') - `getHousekeepingStaff` - GET /staff. Employees in housekeeping department with active/break status. **Step 2: Create routes, register in app.js** **Step 3: Create frontend service** **Step 4: Create Housekeeping page** Two-column layout: - Left: Pending tasks (filterable by priority) - Right: In-progress tasks - Bottom or sidebar: Staff availability panel Each TaskCard shows: room number, task type badge, priority indicator, assigned staff, action buttons (Assign, Start, Complete based on current status). **Step 5: Add to menu config under new "Services" section** ```javascript services: { label: "Services", spanish_label: "Servicios", basePath: "/app/housekeeping", submenu: [ { label: "Housekeeping", spanish_label: "Limpieza", route: "/app/housekeeping" }, { label: "Room Service", spanish_label: "Servicio a Habitacion", route: "/app/room-service" }, { label: "Events & Venues", spanish_label: "Eventos y Salones", route: "/app/events" }, ], }, ``` **Step 6: Add route in App.jsx** **Step 7: Commit** ```bash git add -A git commit -m "feat: add housekeeping task management with staff assignment" ``` --- ## Task 11: Room Service Module (Backend + Frontend) **Files:** - Create: `backend/hotel_hacienda/src/controllers/roomservice.controller.js` - Create: `backend/hotel_hacienda/src/routes/roomservice.routes.js` - Create: `frontend/Frontend-Hotel/src/pages/RoomService/RoomServiceOrders.jsx` - Create: `frontend/Frontend-Hotel/src/pages/RoomService/MenuManager.jsx` - Create: `frontend/Frontend-Hotel/src/pages/RoomService/NewOrder.jsx` - Create: `frontend/Frontend-Hotel/src/services/roomServiceService.js` **Step 1: Create roomservice controller** - `getOrders` - Active orders (status != 'delivered' AND status != 'cancelled') JOIN rooms, guests. Include order_items + menu_items. - `createOrder` - POST with items array. Calculate total from item prices. Validate room is occupied. - `updateOrderStatus` - PUT /:id/status (pending -> preparing -> delivering -> delivered) - `getMenu` - GET all menu items - `createMenuItem` - POST - `updateMenuItem` - PUT /:id (including toggle available) **Step 2: Frontend** - Orders page: active orders grid with status badges, room number, items list, total, time since created. Status transition buttons. - Menu manager: table of items with name, price, category, available toggle. Add new item form. - New order form: select occupied room (dropdown), add items with quantities, notes field. **Step 3: Add routes in App.jsx** ```javascript } /> } /> } /> ``` **Step 4: Commit** ```bash git add -A git commit -m "feat: add room service orders and menu management" ``` --- ## Task 12: Events & Venues Module (Backend + Frontend) **Files:** - Create: `backend/hotel_hacienda/src/controllers/events.controller.js` - Create: `backend/hotel_hacienda/src/routes/events.routes.js` - Create: `frontend/Frontend-Hotel/src/pages/Events/Venues.jsx` - Create: `frontend/Frontend-Hotel/src/pages/Events/Events.jsx` - Create: `frontend/Frontend-Hotel/src/pages/Events/NewEvent.jsx` - Create: `frontend/Frontend-Hotel/src/services/eventService.js` **Step 1: Create events controller** - `getVenues` - GET all venues - `createVenue` - POST - `updateVenue` - PUT /:id - `getEvents` - GET with date filters. JOIN venues. - `createEvent` - POST. Validate venue availability (no overlapping events). - `updateEvent` - PUT /:id **Step 2: Frontend** - Venues page: card grid showing venue name, capacity, area m2, price/hour, amenities tags, status badge. - Events page: upcoming events list with venue, date/time, organizer, guest count. Filter by date. - New event form: venue selector, date, start/end time, organizer, guest count, notes. **Step 3: Routes and menu config** **Step 4: Commit** ```bash git add -A git commit -m "feat: add events and venues management" ``` --- ## Task 13: Employee Scheduling Module (Backend + Frontend) **Files:** - Create: `backend/hotel_hacienda/src/controllers/schedules.controller.js` - Create: `backend/hotel_hacienda/src/routes/schedules.routes.js` - Create: `frontend/Frontend-Hotel/src/pages/Schedules/Schedules.jsx` - Create: `frontend/Frontend-Hotel/src/pages/Schedules/components/ShiftCell.jsx` - Create: `frontend/Frontend-Hotel/src/services/scheduleService.js` **Step 1: Create schedules controller** - `getSchedules` - GET with params: week_start (date), department. Returns employees with their daily shifts for the week. JOIN employees for name and department. - `saveSchedules` - POST with array of { employee_id, schedule_date, shift_type }. Uses UPSERT (INSERT ON CONFLICT UPDATE). - `getEmployeeSchedule` - GET /employee/:id with date range. **Step 2: Frontend** - Weekly grid: Y-axis = employees (filtered by department), X-axis = Mon-Sun - Each cell is a ShiftCell: colored by shift type. Click to cycle through: Morning (yellow, 7-15) -> Afternoon (blue, 15-23) -> Night (purple, 23-7) -> Off (gray) -> Morning... - Department filter dropdown at top - Save button to bulk-save changes **Step 3: Add to menu config under payroll section as "Schedules" or create a new "Staff" section** Add to payroll submenu: ```javascript { label: "Schedules", spanish_label: "Horarios", route: "/app/schedules" } ``` **Step 4: Add route in App.jsx** **Step 5: Commit** ```bash git add -A git commit -m "feat: add employee shift scheduling with weekly grid" ``` --- ## Task 14: Operational Reports Module (Backend + Frontend) **Files:** - Create: `backend/hotel_hacienda/src/controllers/operational-reports.controller.js` - Create: `backend/hotel_hacienda/src/routes/operational-reports.routes.js` - Create: `frontend/Frontend-Hotel/src/pages/Reports/OperationalReports.jsx` - Create: `frontend/Frontend-Hotel/src/pages/Reports/components/OccupancyChart.jsx` - Create: `frontend/Frontend-Hotel/src/pages/Reports/components/BookingSourcesPie.jsx` - Create: `frontend/Frontend-Hotel/src/services/operationalReportService.js` **Step 1: Create reports controller** - `getOccupancyReport` - Occupancy % by day/week/month. Calculated from room_status_log or daily snapshots. - `getRevenueReport` - Revenue grouped by room type (from reservations + rooms JOIN). Period: week/month/quarter/year. - `getBookingSourcesReport` - Count and percentage by channel field in reservations. - `getSatisfactionReport` - Average rating from guest_stays, grouped by period. **Step 2: Frontend** - Period selector (Week, Month, Quarter, Year) at the top - KPI row: occupancy rate with trend, revenue vs target, guest satisfaction stars - Charts: occupancy trend (line), revenue by room type (bar), booking sources (pie/donut) - Note: For charts, use simple CSS-based charts or install a lightweight charting library like `recharts` **Step 3: Install recharts if needed** Run: `cd frontend/Frontend-Hotel && npm install recharts` **Step 4: Routes, menu config** Add under dashboards or create new Reports section in menu. **Step 5: Commit** ```bash git add -A git commit -m "feat: add operational reports with occupancy, revenue, and booking analytics" ``` --- ## Task 15: Update Sidebar Navigation & Final Integration **Files:** - Modify: `frontend/Frontend-Hotel/src/constants/menuconfig.js` (final structure) - Modify: `frontend/Frontend-Hotel/src/components/Layout2.jsx` (permissions for new sections) - Modify: `frontend/Frontend-Hotel/src/App.jsx` (verify all routes registered) **Step 1: Finalize menu config** Update `menuconfig.js` with the complete navigation structure: ```javascript export const menuConfig = { operations: { label: "Operations", spanish_label: "Operaciones", basePath: "/app/room-dashboard", submenu: [ { label: "Room Dashboard", spanish_label: "Panel de Habitaciones", route: "/app/room-dashboard" }, { label: "Reservations", spanish_label: "Reservaciones", route: "/app/reservations" }, { label: "Guests", spanish_label: "Huespedes", route: "/app/guests" }, ], }, services: { label: "Services", spanish_label: "Servicios", basePath: "/app/housekeeping", submenu: [ { label: "Housekeeping", spanish_label: "Limpieza", route: "/app/housekeeping" }, { label: "Room Service", spanish_label: "Servicio a Habitacion", route: "/app/room-service" }, { label: "Events & Venues", spanish_label: "Eventos y Salones", route: "/app/events" }, ], }, dashboards: { /* existing - unchanged */ }, income: { /* existing - unchanged */ }, expensesToApprove: { /* existing - unchanged */ }, expenses: { /* existing - unchanged */ }, inventory: { /* existing - unchanged */ }, payroll: { /* existing + add Schedules */ submenu: [ /* ...existing items... */ { label: "Schedules", spanish_label: "Horarios", route: "/app/schedules" }, ], }, reports: { label: "Reports", spanish_label: "Reportes", basePath: "/app/operational-reports", submenu: [ { label: "Operational", spanish_label: "Operativos", route: "/app/operational-reports" }, ], }, housekeeper: { /* existing - unchanged */ }, }; ``` **Step 2: Update Layout2 permissions** Add permission rules for the new sections in `menuConfigWithPermissions`. The new Operations and Services sections should be visible to admin (role 1) and front-desk roles. Adjust based on actual role numbers. **Step 3: Verify all routes in App.jsx** Ensure all new routes are registered: - `/app/room-dashboard` - `/app/reservations`, `/app/reservations/new` - `/app/guests`, `/app/guests/:id` - `/app/housekeeping` - `/app/room-service`, `/app/room-service/menu`, `/app/room-service/new-order` - `/app/events`, `/app/events/new`, `/app/venues` - `/app/schedules` - `/app/operational-reports` **Step 4: Commit** ```bash git add -A git commit -m "feat: integrate all front-office modules into navigation" ``` --- ## Task 16: Environment & Docker Updates **Files:** - Modify: `backend/hotel_hacienda/.env.example` - Modify: `docker-compose.yml` **Step 1: Update .env.example** Add: ``` JWT_SECRET=change-this-secret-in-production JWT_REFRESH_SECRET=change-this-refresh-secret-in-production ``` **Step 2: Verify Docker setup works with new dependencies** Run: `docker compose build` to verify containers build correctly with new npm packages. **Step 3: Commit** ```bash git add -A git commit -m "chore: update environment config and Docker build for front-office modules" ``` --- ## Summary | Task | Module | Type | |------|--------|------| | 1 | Database Schema | Backend | | 2 | JWT Authentication | Full-stack | | 3 | i18n Setup | Frontend | | 4 | Dark Theme | Frontend | | 5 | Room Dashboard API | Backend | | 6 | Room Dashboard UI | Frontend | | 7 | Reservations API | Backend | | 8 | Reservations UI | Frontend | | 9 | Guest Management | Full-stack | | 10 | Housekeeping | Full-stack | | 11 | Room Service | Full-stack | | 12 | Events & Venues | Full-stack | | 13 | Employee Scheduling | Full-stack | | 14 | Operational Reports | Full-stack | | 15 | Navigation Integration | Frontend | | 16 | Environment & Docker | DevOps |