diff --git a/backend/app/api/researchers.py b/backend/app/api/researchers.py
index 927aafa..c13b8e3 100644
--- a/backend/app/api/researchers.py
+++ b/backend/app/api/researchers.py
@@ -15,7 +15,7 @@ from app.schema.researcher import (
ResearcherWithPublicationsSchema,
)
from app.services.normalizer import PublicationNormalizer
-from app.services.orcid_client import get_works_summary, get_work_detail
+from app.services.orcid_client import get_display_name, get_works_summary, get_work_detail
from app.schema.publication import PublicationSchema
from app.db.models import PublicationDownload
from app.security.jwt import get_optional_current_researcher
@@ -159,6 +159,16 @@ def build_search_response(orcid_id: str, db: Session, current: Researcher | None
db.add(researcher)
db.flush()
+ # Si todavía no conocemos el nombre del investigador (por ejemplo, recién
+ # creado al sincronizarse desde el buscador), lo resolvemos contra el
+ # endpoint `/record` público de ORCID. No tocamos un nombre ya existente
+ # para no pisar valores establecidos por el flujo de autenticación.
+ if not researcher.name:
+ display_name = get_display_name(orcid_id)
+ if display_name:
+ researcher.name = display_name
+ db.flush()
+
publications = _upsert_researcher_publications(researcher, orcid_id, db)
publications_out = _decorate_downloaded_by_me(db=db, current=current, publications=publications)
stats = build_researcher_stats(publications_out)
diff --git a/backend/app/services/orcid_client.py b/backend/app/services/orcid_client.py
index 2e15add..f0535dd 100644
--- a/backend/app/services/orcid_client.py
+++ b/backend/app/services/orcid_client.py
@@ -148,3 +148,50 @@ def get_works_summary(orcid_id: str) -> dict:
def get_work_detail(orcid_id: str, put_code: int) -> dict | None:
client = ORCIDClient()
return client.fetch_work_detail(orcid_id, put_code)
+
+
+def get_record(orcid_id: str) -> dict:
+ client = ORCIDClient()
+ return client.fetch_record(orcid_id)
+
+
+def extract_display_name(record: dict | None) -> str | None:
+ """
+ Devuelve un nombre legible a partir de la respuesta de `/record` de ORCID.
+
+ Prioriza `credit-name` (el nombre tal y como el investigador prefiere mostrarlo);
+ si no está disponible, compone `given-names` + `family-name`.
+ """
+ if not record:
+ return None
+
+ name = (record.get("person") or {}).get("name") or {}
+
+ credit = name.get("credit-name")
+ if isinstance(credit, dict):
+ credit_value = credit.get("value")
+ if credit_value:
+ return credit_value
+
+ given_obj = name.get("given-names")
+ family_obj = name.get("family-name")
+ given = given_obj.get("value") if isinstance(given_obj, dict) else None
+ family = family_obj.get("value") if isinstance(family_obj, dict) else None
+
+ full = " ".join(part for part in (given, family) if part)
+ return full or None
+
+
+def get_display_name(orcid_id: str) -> str | None:
+ """
+ Obtiene el nombre público del investigador desde ORCID.
+
+ Devuelve `None` (sin propagar la excepción) si la API de ORCID no responde
+ o el `record` no contiene un nombre utilizable, para no romper el flujo de
+ búsqueda cuando solo falla la resolución del nombre.
+ """
+ try:
+ record = get_record(orcid_id)
+ except Exception:
+ return None
+ return extract_display_name(record)
diff --git a/frontend/index.html b/frontend/index.html
index d02d4e4..5120c51 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -4,7 +4,7 @@
-
orcid-system
+ ORCID2SWORD
diff --git a/frontend/src/components/layout/AppHeader.jsx b/frontend/src/components/layout/AppHeader.jsx
index fd10421..029e66c 100644
--- a/frontend/src/components/layout/AppHeader.jsx
+++ b/frontend/src/components/layout/AppHeader.jsx
@@ -1,18 +1,16 @@
import { Link, useNavigate } from "react-router-dom";
import { toast } from "sonner";
-import { ArrowLeftIcon, LayersIcon, LogoutIcon, UserCheckIcon } from "../ui/Icons";
+import { LogoutIcon, UserCheckIcon } from "../ui/Icons";
import { useAuth } from "../../contexts/AuthContext";
/**
* Institutional navy header used across all views.
*
- * Variants:
- * - `landing` → logo + full product name.
- * - `dashboard` → back button to `/` + auth indicator + logout (if logged in).
- * - `group` → back button to `/` + group label + auth indicator.
+ * Brand: ORCID2SWORD — "2" in orcid-green, rest in white.
+ * Authenticated users see their name (or "Mi Perfil") + logout button.
*/
export function AppHeader({ variant = "landing" }) {
- const { isAuthenticated, userOrcidId, logout } = useAuth();
+ const { isAuthenticated, userOrcidId, userName, logout } = useAuth();
const navigate = useNavigate();
function handleLogout() {
@@ -23,82 +21,42 @@ export function AppHeader({ variant = "landing" }) {
navigate("/");
}
- if (variant === "dashboard" || variant === "group") {
- return (
-
+ const profileLabel = userName ?? "Mi Perfil";
+ const profileHref = userOrcidId ? `/dashboard/${userOrcidId}` : "/";
+
+ return (
+
+
+ Sesión activa
+
+ Verás publicaciones nuevas marcadas en el dashboard
+
+
+ ) : (
- >
- )}
-
-
-
-
- {isAuthenticated ? "TU ORCID iD" : "O INTRODUCE TU ORCID iD"}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {error && (
-
- {error}
-
)}
-
- {isAuthenticated
- ? "Busca un investigador o usa «Cerrar sesión» arriba."
- : "Pulsa «Iniciar sesión» para autenticarte, o «Buscar» de forma anónima."}
+
+
+
+
+ {isAuthenticated ? "TU ORCID iD" : "O INTRODUCE TU ORCID iD"}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {error && (
+
+ {error}
+
+ )}
+
+ {isAuthenticated
+ ? "Busca un investigador o usa «Cerrar sesión» arriba."
+ : "Pulsa «Iniciar sesión» para autenticarte, o «Buscar» de forma anónima."}
+
+
+
+
+ {/* ── Right: group search ── */}
+
+
+
+
+ Búsqueda grupal de investigadores
+
+
+
+ Pega varios ORCID iDs separados por comas, espacios o saltos de
+ línea para buscar y comparar varios investigadores a la vez.
+
- {/* Group search card */}
-
-
-
-
- Búsqueda grupal de investigadores
-
-
-
- Pega varios ORCID iDs separados por comas, espacios o saltos de
- línea para buscar y comparar varios investigadores a la vez.
-