Ferromax ERP is a two-tier web application. The Java backend — a Spring Boot 3.2.5 application packaged as a single executable JAR — exposes a RESTful HTTP API and a WebSocket endpoint. The React frontend — a Vite-bundled single-page application — communicates exclusively through those two interfaces: HTTP via Axios for standard CRUD operations, and WebSocket STOMP for real-time push notifications. All persistent state lives in a PostgreSQL 16 database accessed through JPA/Hibernate. The two processes run independently, so they can be deployed together on a single machine during development or separated behind a reverse proxy in production.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/DragonesMagicos/ferromax_v0.8/llms.txt
Use this file to discover all available pages before exploring further.
Project Layout
Backend Layer
The backend follows the standard Spring Boot MVC layering pattern, with each layer having a single responsibility.| Layer | Package | Responsibility |
|---|---|---|
| Controller | controller/ | Receives HTTP requests, validates input with @Valid, delegates to the service layer, and maps the result to an HTTP response code. |
| Service | service/ | Contains all business logic — stock calculations, sale totalling, invoice parsing, alert triggering. Also owns @Transactional boundaries. |
| Repository | repository/ | Spring Data JPA interfaces. Each entity has a corresponding repository; complex queries use JPQL @Query annotations. |
| Model | model/ | JPA @Entity classes mapped to PostgreSQL tables. Lombok @Data / @Builder reduce boilerplate. |
| DTO | dto/ | Separate request and response objects built with MapStruct mappers, keeping the entity model out of the API surface. |
/api, so every endpoint is reachable at http://localhost:8081/api/<resource>. SpringDoc is bundled and exposes an interactive Swagger UI at http://localhost:8081/api/swagger-ui.html.
Frontend Layer
The React SPA is a Vite project underferromax-web/. Routing is handled by react-router-dom v7, with routes declared in App.jsx. There are three route categories:
- Public routes —
/tienda,/tienda/login,/tienda/confirmacion,/tienda/mis-pedidos,/catalogo, and/catalogo/:categoriaare accessible without a token. - Employee routes — wrapped in
<ProtectedRoute requiereEmpleado>, accessible to both ADMIN and EMPLEADO roles. Includes/pos,/productos,/ventas, and/recepcion. - Admin-only routes — wrapped in
<ProtectedRoute requiereAdmin>, restricted to ADMIN. Includes/(dashboard),/remitos,/ajuste-stock, and/ingreso-factura.
src/api/axiosClient.js. A request interceptor reads the JWT from localStorage under the key ferromax_token and attaches it as an Authorization: Bearer <token> header on every outbound request. Higher-level service modules in src/services/ wrap this client with per-resource API calls. UI styling uses TailwindCSS 4, transitions and page-level animations use Framer Motion, and dashboard charts are rendered with Recharts.
Authentication Flow
Ferromax uses stateless JWT authentication. The sequence below covers a complete login-to-protected-request cycle.User submits credentials
The frontend
POSTs { email, password } to /api/auth/login. Spring Security’s DaoAuthenticationProvider verifies the credentials against the BCrypt-hashed password in the database.Server issues a JWT
On success,
AuthController calls JwtTokenProvider to sign a token containing the user’s email, role, and user ID. The response body includes token, rol, nombre, and mensaje.Client stores the token
The frontend stores the token in
localStorage under the key ferromax_token. All subsequent Axios requests include the header Authorization: Bearer <token>, attached automatically by the interceptor in src/api/axiosClient.js.Filter validates each request
JwtAuthenticationFilter intercepts every incoming request, extracts the token from the Authorization header, validates the signature and expiry via JwtTokenProvider, and loads the UserDetails from the database. If valid, it populates the Spring Security SecurityContextHolder for the duration of the request.JWTs expire after 8 hours (
jwt.expiration=28800000 ms). A separate refresh expiration is configured at 7 days (app.jwt.refresh-expiration-ms=604800000 ms). Clients should handle 401 Unauthorized responses by redirecting the user back to the login page.Real-time Updates
Stock changes need to reach every open admin session instantly — polling is too slow for a busy POS environment. Ferromax solves this with a Spring WebSocket STOMP message broker.| Property | Value |
|---|---|
| STOMP endpoint | /ws (externally /api/ws due to servlet context path) |
| SockJS fallback | Enabled (transparent HTTP long-polling for environments that block WebSockets) |
| Topic prefix | /topic |
| App destination prefix | /app |
| Stock update topic | /topic/stock-update |
SimpMessagingTemplate and calls convertAndSend("/topic/stock-update", payload). Every frontend client subscribed to that topic receives the message within milliseconds and updates its local state — no page refresh needed.
The frontend uses @stomp/stompjs and sockjs-client to establish the connection. The connection is initialised in a React useEffect hook and torn down on component unmount to prevent memory leaks.
Database
| Setting | Value |
|---|---|
| Engine | PostgreSQL 16 |
| Schema management | JPA/Hibernate ddl-auto=update |
| Connection pool | HikariCP — max 10 connections, min idle 2 |
| Pool name | FerromaxHikariPool |
| Timezone | America/Argentina/Buenos_Aires (applied at JVM and JDBC levels) |
| Cache | Caffeine — max 500 entries, 10 min write-expiry (expireAfterWrite=600s) |
ddl-auto=update means Hibernate adds missing columns and creates new tables on startup but never drops existing ones. This is safe for development and low-traffic production deployments. For production migrations at scale, replace it with a dedicated tool such as Flyway or Liquibase.
Security Model
Ferromax defines three roles. Spring Security stores them with theROLE_ prefix internally, but the application code and API refer to them as ADMIN, EMPLEADO, and CLIENTE.
| Role | Intended user | Key permissions |
|---|---|---|
ADMIN | Business owner / manager | Full access: dashboard, all products (including precioCompra), all sales history, stock adjustments, remitos, invoice OCR, alert management, supplier CRUD |
EMPLEADO | In-store staff | POS terminal, product lookup (price and stock only — no precioCompra), stock reception, own daily sales |
CLIENTE | Online shopper | Public catalogue browsing, place orders via /pedidos, view own order history via /ventas/mis-compras |
SessionCreationPolicy.STATELESS is set in SecurityConfig, so the server holds no session state between requests.