⚠️ Draft documentation
Work in progress — some sections may be incomplete, typos possible. Last updated: 2026-05-10.
EN | DE | RU | FR | ES
ES

Recorrido por el código fuente: src/actions/authentication.js

Archivo: authentication.js en develop (85 líneas)

Estado: sin cambios entre develop → nuestra rama

Probado por: actions/authentication.test.js

Relacionado: reducers/authentication.js — procesa las acciones enviadas aquí

1. Importaciones (línea 1)

import { getServiceURL, handleHTTPErrors } from '../utilities/rest';

Dos utilidades de rest.js:

Sin otras dependencias. El archivo es autocontenido salvo por estas dos funciones auxiliares.

2. Constantes de tipos de acción (líneas 3–5)

export const USER_LOGIN_BEGIN   = 'USER_LOGIN_BEGIN';
export const USER_LOGIN_SUCCESS = 'USER_LOGIN_SUCCESS';
export const USER_LOGIN_FAILURE = 'USER_LOGIN_FAILURE';

Tres constantes de cadena exportadas para uso por el reducer (reducers/authentication.js). El switch(type) del reducer coincide con estas cadenas exactas.

¿Por qué cadenas y no símbolos? Convención de Redux: las cadenas son serializables, depurables en Redux DevTools y funcionan entre módulos sin necesidad de referencias compartidas.

Nota: USER_LOGOUT estaba aquí anteriormente: se eliminó en el commit 7c60c2fbb (jul 2019) cuando el cierre de sesión se simplificó para simplemente enviar USER_LOGIN_BEGIN (reinicia el estado).

3. Creadores de acciones (líneas 7–26)

export const userLoginBegin = () => ({
    type: USER_LOGIN_BEGIN,
});

export const userLoginSuccess = (user, version, buildTimestamp, alertMessage) => ({
    type: USER_LOGIN_SUCCESS,
    payload: { user, version, buildTimestamp, alertMessage },
});

export const userLoginFailure = (error) => ({
    type: USER_LOGIN_FAILURE,
    payload: { error },
});

Creadores de acción — funciones puras que devuelven objetos planos. Los «mensajes» síncronos enviados al reducer.

CreadorFirmaQué hace el reducer con ello
userLoginBegin() () → { type: BEGIN } Reinicia a { loading: true, error: null, user: null } — activa el spinner, borra datos antiguos
userLoginSuccess(user, version, buildTimestamp, alertMessage) (obj, str, str, str?) → { type, payload } Escribe los cuatro campos en el estado: user, version, buildTimestamp, alertMessage. loading → false
userLoginFailure(error) (str) → { type, payload: { error } } Almacena el mensaje de error, borra user, loading → false

Estas funciones se llaman desde los thunks siguientes; nunca se envían directamente desde los componentes.

4. Función auxiliar catchError (línea 28)

const catchError = (dispatch) => (error) => dispatch(userLoginFailure(error.message));

Función curry: catchError(dispatch)(error).

Se usa en las cadenas .catch() de login() y loadUserStatus():

5. loadUserStatus: verificación de sesión (líneas 30–63)

export const loadUserStatus = () => (dispatch) => {
    dispatch(userLoginBegin());                                    // (a)

    return fetch(                                                   // (b)
        getServiceURL('userStatus'),
        { method: 'GET', credentials: 'include' },
    )
        .then(handleHTTPErrors)                                    // (c)
        .then((response) => response.json())                       // (d)
        .then(({ userData, systemData, alertMessage }) => {        // (e)
            dispatch(userLoginSuccess(
                userData,
                systemData.version,
                systemData.buildTimestamp,
                alertMessage,
            ));
        })
        .catch(() => {                                             // (f)
            const { pathname, search } = window.location;
            const href = pathname + search;
            if (!pathname.startsWith('/react/public/login')
                && !pathname.startsWith('/react/public/datatransfer/')) {
                window.location.href =
                    `/react/public/login?url=${encodeURIComponent(href)}`;
            }
            catchError(dispatch)({ message: undefined });
        });
};

Propósito: «¿Quién soy?» — se ejecuta al inicio de la app y tras el inicio de sesión para comprobar si el usuario tiene una sesión válida.

(a) Envía USER_LOGIN_BEGIN: activa el spinner y borra el estado anterior.

(b) GET /rs/userStatus: envía la cookie de sesión (credentials: 'include'). Llama a UserStatusRest.kt:111.

(c) handleHTTPErrors — si status ≠ 2xx, lanza un Error('Fetch failed: Error {status}'). El flujo pasa al .catch().

(d) Analiza la respuesta JSON. Estructura esperada: { userData: {...}, systemData: {version, buildTimestamp}, alertMessage?: string }.

(e) Desestructura y envía USER_LOGIN_SUCCESS con todos los campos. El reducer los escribe en el estado.

(f) Sesión inválida o expirada. Dos casos:

Envío: catchError(dispatch)({ message: undefined })error.message será undefined. El reducer almacena { error: undefined } — borrando efectivamente cualquier error anterior.

Se invoca en:

6. login: autenticación (líneas 65–85)

export const login = (username, password, keepSignedIn) => (dispatch) => {
    dispatch(userLoginBegin());                                    // (a)

    return fetch(                                                   // (b)
        getServiceURL('/rsPublic/login'),
        {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
                username,
                password,
                stayLoggedIn: keepSignedIn,                        // (c)
            }),
            credentials: 'include',
        },
    )
        .then(handleHTTPErrors)                                    // (d)
        .then(() => loadUserStatus()(dispatch))                    // (e)
        .catch(catchError(dispatch));                              // (f)
};

Propósito: Enviar las credenciales al servidor. Si es exitoso, consultar la sesión para obtener los datos del usuario.

(a) Envía USER_LOGIN_BEGIN: activa el spinner.

(b) POST /rsPublic/login con cuerpo JSON. Llama a LoginPageRest.kt:93LoginService.authenticate(). El servidor establece la cookie JSESSIONID al éxito.

(c) stayLoggedIn: keepSignedIn — indicador de «Recordarme». Cuando es true, el servidor también establece una cookie stayLoggedIn (CookieService.kt:201) válida por 30 días. En la próxima visita, esta cookie renueva automáticamente la sesión sin contraseña.

(d) handleHTTPErrors — si el inicio de sesión falla (401, 403, etc.), lanza una excepción y pasa a .catch().

(e) En caso de éxito (200): encadena la ejecución de loadUserStatus(). El inicio de sesión solo autentica; aún necesitamos saber qué usuario ha entrado. loadUserStatus()(dispatch) invoca el thunk manualmente (devuelve (dispatch) => {...}, por lo que lo llamamos pasando dispatch). Por eso la prueba espera 3 acciones: BEGIN (login) + BEGIN (loadUserStatus) + SUCCESS (loadUserStatus).

(f) En caso de fallo: catchError(dispatch) envía USER_LOGIN_FAILURE con el mensaje de error: por ejemplo 'Fetch failed: Error 401' para contraseña incorrecta.

Resumen: flujo de acciones

INICIO DE LA APP:
  ProjectForge.jsx → dispatch(loadUserStatus())
    └─ GET /rs/userStatus
         ├─ 200 → SUCCESS(userData, systemData, alertMessage)
         └─ 401 → redirige a /react/public/login

INICIO DE SESIÓN:
  login(username, password, keepSignedIn)
    ├─ dispatch(BEGIN)
    ├─ POST /rsPublic/login
    ├─ el servidor establece JSESSIONID (+ cookie stayLoggedIn si keepSignedIn)
    ├─ 200 → loadUserStatus()(dispatch)
    │   └─ GET /rs/userStatus → BEGIN → SUCCESS
    └─ 401/error → catchError → FAILURE

DESPUÉS DEL INICIO DE SESIÓN:
  El usuario vuelve a la página original. ProjectForge.jsx se vuelve a montar.
  loadUserStatus() se ejecuta de nuevo; esta vez JSESSIONID es válida → SUCCESS

Documentación relacionada: Comentario del diff de authentication.test.js | Comentario del diff de reducers/authentication.js