Files
hotel-hacienda/docs/FRONTEND.md
Consultoria AS 411d517ba4 docs: documentación extensiva del proyecto
- README.md con guía completa de instalación y arquitectura
- docs/BACKEND.md — guía detallada del API
- docs/FRONTEND.md — guía del SPA React
- docs/DATABASE.md — funciones SQL y patrones de datos
- docs/API.md — referencia completa de endpoints REST
- docs/DEPLOYMENT.md — guía de despliegue en producción
- docs/TROUBLESHOOTING.md — solución de problemas comunes
- CHANGELOG.md — historial de cambios
- CONTRIBUTING.md — guía de contribución
2026-06-09 16:26:09 -07:00

14 KiB

🎨 Documentación del Frontend — Frontend-Hotel

Índice

  1. Introducción
  2. Estructura de Carpetas
  3. Entry Points
  4. Enrutamiento
  5. Manejo de Estado Global
  6. Sistema de Permisos (Roles)
  7. Comunicación con el Backend
  8. Componentes Reutilizables
  9. Estilos
  10. Páginas por Dominio
  11. Guía para Agregar una Nueva Página
  12. Bugs y Limitaciones Conocidas

Introducción

El frontend es una Single Page Application (SPA) construida con React 19, Vite y React Router DOM. Es una aplicación cliente que consume la API REST del backend.

Tecnologías clave:

  • React 19.1.1 (JSX, JavaScript puro, sin TypeScript)
  • Vite 7.1.2 con SWC para compilación rápida
  • React Router DOM 7.8.2 (BrowserRouter)
  • Estado global: React Context API
  • HTTP: mezcla de fetch nativo y axios
  • Estilos: Tailwind CSS + Bootstrap 5 + CSS puro

Estructura de Carpetas

Frontend-Hotel/
├── index.html                    # Entry HTML (título: "Hacienda San Angel")
├── vite.config.js               # Config Vite (puerto 5172)
├── tailwind.config.cjs          # Config Tailwind
├── postcss-config.js            # PostCSS con Tailwind + Autoprefixer
├── eslint.config.js             # ESLint 9 flat config
├── public/
│   ├── logoHotel.png
│   ├── IconoHotel.svg
│   └── ...
├── src/
│   ├── main.jsx                 # Entry point React
│   ├── App.jsx                  # Definición central de rutas
│   ├── index.css                # Tailwind directives + estilos base
│   ├── App.css
│   ├── constants/
│   │   └── menuconfig.js        # Configuración del menú de navegación
│   ├── context/
│   │   ├── AuthContext.jsx      # Estado de autenticación
│   │   └── LenguageContext.jsx  # Estado de idioma (EN/ES)
│   ├── routes/
│   │   └── ProtectedRoute.jsx   # Ruta protegida (NO usada en App.jsx)
│   ├── services/
│   │   ├── api.js               # Instancia axios (baseURL desde env)
│   │   ├── employeeService.js
│   │   ├── incomeService.js
│   │   ├── userService.js
│   │   └── contractService.js
│   ├── styles/
│   │   ├── global.css
│   │   ├── Dashboard.css
│   │   ├── Login.css
│   │   ├── Layout.css
│   │   └── Sidebar.css
│   ├── assets/
│   │   └── pages/login.jsx
│   ├── components/
│   │   ├── Layout2.jsx          # Layout activo (Sidebar + Topbar + Outlet)
│   │   ├── Layout.jsx           # Layout antiguo (comentado)
│   │   ├── Sidebar.jsx
│   │   ├── topbar/Topbar.jsx
│   │   ├── Navbar/Navbar.jsx
│   │   ├── Table/HotelTable.jsx
│   │   ├── ExcelExportButton.jsx
│   │   ├── SummaryCard.jsx
│   │   ├── FormInput.jsx
│   │   ├── FormSelect.jsx
│   │   ├── Switch.jsx
│   │   ├── Modals/
│   │   ├── Buttons/
│   │   ├── Filters/
│   │   └── Inputs/
│   └── pages/                   # ~50+ páginas organizadas por dominio
│       ├── Login.jsx
│       ├── Dashboard.jsx
│       ├── Dashboard/
│       ├── Expenses/
│       ├── ExpensesToBeApproval/
│       ├── Inventory/
│       ├── Hotel/
│       ├── Income/
│       ├── Payroll/
│       ├── Settings/
│       └── ...

Entry Points

index.html

Carga la fuente Roboto desde Google Fonts y el script src/main.jsx.

src/main.jsx

import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import { AuthProvider } from "./context/AuthContext";
import { LangProvider } from "./context/LenguageContext";
import App from "./App.jsx";

ReactDOM.createRoot(document.getElementById("root")).render(
  <AuthProvider>
    <LangProvider>
      <BrowserRouter>
        <App />
      </BrowserRouter>
    </LangProvider>
  </AuthProvider>
);

Jerarquía de providers:

  1. AuthProvider — disponible en toda la app
  2. LangProvider — depende de AuthContext (lee el rol para forzar español a housekeepers)
  3. BrowserRouter — enrutamiento

Enrutamiento

src/App.jsx

Define todas las rutas de la aplicación. No usa ProtectedRoute. Todas las rutas internas están bajo /app y renderizan <Layout /> (que es Layout2.jsx).

<Route path="/" element={<Login />} />
<Route path="/app" element={<Layout />}>
  <Route path="income" element={<Income/>} />
  <Route path="employees" element={<Employees />} />
  <Route path="contracts" element={<Contracts />} />
  <Route path="hotelpl" element={<HotelPL />} />
  <Route path="restaurantpl" element={<RestaurantPL />} />
  <Route path="expenses" element={<Expenses />} />
  <Route path="new-expense" element={<NewExpense />} />
  <Route path="products" element={<Products />} />
  <Route path="payroll" element={<Payroll />} />
  {/* ... ~40 rutas más */}
</Route>

Convenciones de rutas:

  • Páginas de listado: plural simple (/products, /payroll)
  • Páginas de creación: prefijo new- (/new-product, /new-expense)
  • Páginas de edición: :id como parámetro (/expenses/edit/:id, /payroll/employee/:id)
  • Detalles: segmento fijo + :id (/expenses/:id, /properties/:id)

Manejo de Estado Global

AuthContext (src/context/AuthContext.jsx)

export const AuthContext = createContext();

function AuthProvider({ children }) {
  const [user, setUser] = useState(null);     // rol numérico
  const [userData, setUserData] = useState(null);

  useEffect(() => {
    const savedUser = localStorage.getItem("rol");
    if (savedUser) setUser(JSON.parse(savedUser));
  }, []);

  const login = (userData, data) => {
    setUserData(data);
    setUser(userData);
    localStorage.setItem("rol", JSON.stringify(userData));
  };

  const logout = () => {
    setUser(null);
    localStorage.removeItem("rol");
  };

  return (
    <AuthContext.Provider value={{ user, login, logout, userData }}>
      {children}
    </AuthContext.Provider>
  );
}

Observaciones:

  • Guarda el rol como número en localStorage bajo la clave "rol".
  • NO exporta useAuth, lo cual es un bug conocido.
  • No usa JWT ni tokens.

LangContext (src/context/LenguageContext.jsx)

export const langContext = createContext();

export const LangProvider = ({ children }) => {
    const authContext = useContext(AuthContext);
    const user = authContext?.user || null;

    const [lang, setLang] = useState(user === 6 ? "es" : "en");

    const toggleLang = (event) => setLang(event.target.value);

    return (
        <langContext.Provider value={{ lang, toggleLang }}>
            {children}
        </langContext.Provider>
    );
};

export const useLang = () => useContext(langContext);

Observaciones:

  • Forza español si user === 6 (Housekeeper).
  • No persiste en localStorage.
  • Se accede con useContext(langContext) o useLang().

Sistema de Permisos (Roles)

Los permisos son numéricos y hardcodeados en Layout2.jsx. No hay backend de ACL.

Roles Definidos

Rol Acceso
1 Admin — Todo
2 Supervisor limitado — Dashboards, Expenses (Report/Monthly Report), Payroll (Report/Attendance/Employees/Contracts), Expenses to be approved
3-4 Rangos intermedios (según lógica de rangos)
5 Compras — Solo: New Expense, Purchase Entries, New Suppliers
6 Housekeeper — Solo Outcomes, forzado a español

Lógica de Permisos en Layout2.jsx

const menuConfigWithPermissions = Object.values(menuConfig).map(section => ({
  ...section,
  hidden:
    section.label === "Dashboards" ? (user >= 1 && user <= 2 ? false : true) :
    section.label === "Expenses to be approved" ? (user === 1 || user === 2 ? false : true) :
    section.label === "Expenses" ? (user >= 1 && user <= 5 ? false : true) :
    section.label === "Inventory" ? (user >= 1 && user <= 5 ? false : true) :
    section.label === "Payroll" ? (user >= 1 && user <= 4 ? false : true) :
    section.label === "Hotel" ? (user === 1 ? false : true) :
    section.label === "Income" ? (user >= 1 && user <= 4 ? false : true) :
    section.label === "Housekeeper" ? (user === 6 ? false : true) :
    false,
  // ... lógica de submenús
}));

Regla: Si hidden === true, la sección no aparece en el sidebar.


Comunicación con el Backend

Variable de Entorno

VITE_API_BASE_URL=http://localhost:4000/api

Se accede en el frontend como:

import.meta.env.VITE_API_BASE_URL

Instancia Axios (src/services/api.js)

import axios from "axios";

const api = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL,
  timeout: 15000,
});

export default api;

Problema: Esta instancia está infrautilizada. Muchas páginas usan fetch directo o axios sin la instancia base.

Patrón Fetch (más común)

fetch(import.meta.env.VITE_API_BASE_URL + '/purchases/getpurchases')
  .then(res => res.json())
  .then(data => setState(data))
  .catch(err => console.error(err));

Patrón Axios

axios.put(`${import.meta.env.VITE_API_BASE_URL}/purchases/entry/${id}`, {
  checking: parseInt(checking)
})
.then(res => { ... })
.catch(err => { ... });

Problemas conocidos:

  • Varios archivos en services/ tienen URLs hardcodeadas a localhost:3000 o localhost:4000.
  • No hay interceptores para manejo centralizado de errores.
  • No se envían headers de autorización.

Componentes Reutilizables

HotelTable.jsx (src/components/Table/)

Tabla genérica que recibe:

  • columns: array de { header, key, render?, headerStyle? }
  • data: array de objetos
<Table 
  columns={[
    { header: "Nombre", key: "name" },
    { header: "Acciones", key: "id", render: (id) => <button>Editar</button> }
  ]} 
  data={products} 
/>

ExcelExportButton.jsx

Exporta datos a .xlsx usando la librería xlsx.

SummaryCard.jsx

Card de resumen con indicador de loading.

FormInput.jsx / FormSelect.jsx

Wrappers básicos de inputs HTML con estilos consistentes.

Modales (src/components/Modals/)

  • ConfirmationModal.jsx — Confirmación genérica
  • DiscardConfirmModal.jsx — Confirmación de descarte
  • ConfirmationMontlyPay.jsx — Confirmación de pago mensual
  • ConfirmationOutcome.jsx — Confirmación de salida

Estilos

Mezcla de Tecnologías

El proyecto usa tres sistemas de estilos simultáneamente:

  1. Tailwind CSS — Configurado pero usado esporádicamente.
  2. Bootstrap 5 — Clases como btn btn-primary, btn btn-secondary.
  3. CSS Puro — Un archivo .css por página/componente.

Convención Actual

Cada página tiene su propio archivo CSS:

src/pages/Expenses/NewExpense.css
src/pages/Inventory/Products.css
src/components/Table/Table.css

Fuente

Roboto cargada desde Google Fonts en index.html.


Páginas por Dominio

Dominio Páginas Ruta Base
Dashboards Income, HotelPL, RestaurantPL, RoomAnalysis, RestaurantAnalysis, Budget, CostPerRoom, Expenses /app/income, /app/hotelpl...
Income NewIncome, IncomeReport /app/new-income-form, /app/new-income-report
Expenses to Approve PendingApproval, Approved, Rejected /app/pending-approval...
Expenses NewExpense, EditExpense, ExpenseDetail, ReportExpense, Payments, MonthlyPayments, MonthlyReport, NewMonthlyPayment, NewSuppliers, PurchaseEntries /app/new-expense...
Inventory Products, NewProduct, AlterProduct, InventoryReport, Adjustments, Outcomes, HousekeeperOutcomes, DiscardProduct /app/products...
Hotel Properties, PropertiesId /app/properties...
Payroll Payroll, Plantillapayroll, EditPayroll, PayrollEmployees, NewEmployee, PayrollAttendance, PayrollContract, ContractsDetail /app/payroll...
Settings Settings, SettingsId, RoomsManagement /app/settings...

Guía para Agregar una Nueva Página

  1. Crear el componente en src/pages/[Dominio]/NombrePagina.jsx.
  2. Crear los estilos opcionales en src/pages/[Dominio]/NombrePagina.css.
  3. Agregar la ruta en src/App.jsx dentro de <Route path="/app" element={<Layout />}>.
  4. Agregar al menú (si aplica):
    • Editar src/constants/menuconfig.js
    • Agregar la ruta a la sección correspondiente
  5. Actualizar permisos en src/components/Layout2.jsx:
    • Agregar lógica de hidden para la sección/submenú
    • Agregar detección de ruta en activeSection si la ruta no sigue el patrón estándar
  6. Agregar traducciones EN/ES si es texto visible.
  7. Consumir la API usando import.meta.env.VITE_API_BASE_URL.

Bugs y Limitaciones Conocidas

  1. useAuth no existeAuthContext.jsx no exporta useAuth, pero ProtectedRoute.jsx y Navbar.jsx lo intentan importar.
  2. No hay rutas protegidasApp.jsx no usa ProtectedRoute. Cualquiera puede acceder a /app/*.
  3. URLs hardcodeadas — Varios *Service.js apuntan a localhost en vez de usar VITE_API_BASE_URL.
  4. Código comentado masivo — Especialmente en Layout.jsx y páginas grandes.
  5. multer en frontend — Es un middleware de Node.js, inapropiado para React.
  6. Sin tests — No hay Jest, Vitest, ni Playwright configurados.