# HuLibraries — Backoffice Reference

Este módulo es la implementación de referencia de HuLibraries en el **admin/backoffice**. Sirve como fuente de verdad para entender la arquitectura, patrones y convenciones a seguir en la implementación web.

---

## Estructura de directorios

```
HuLibraries/
├── Layout/
│   ├── index.tsx                  # Layout principal con Helmet + HuGoThemeProvider + Sidebar
│   ├── RootArticleLayout.tsx      # Guard de :rootId, monta debajo de /:rootId
│   ├── ChildArticleLayout.tsx     # Guard de :childId, monta debajo de /:rootId/child/:childId
│   └── articleActionRoutes.tsx    # Subárbol de acciones (segmentation, report) reutilizado en root y child
├── Home/
│   └── index.tsx                  # Página de inicio (placeholder actual)
├── Article/
│   ├── index.tsx                  # Página de artículo
│   └── components/
│       ├── ArticleBreadcrumbs.tsx
│       ├── ArticleDetail/
│       │   ├── index.tsx
│       │   └── components/
│       │       ├── ArticleTitle.tsx
│       │       ├── ArticleBody.tsx
│       │       ├── ArticleAttachments.tsx
│       │       ├── ArticleChildren.tsx
│       │       ├── ArticleNewAlert.tsx
│       │       ├── ArticleNotSegmentedAlert.tsx
│       │       └── ArticleDetailSkeleton.tsx
│       └── ArticleHeader/
│           ├── index.tsx
│           ├── DeleteArticleModal.tsx
│           ├── DisabledArticleModal.tsx
│           ├── ActivateArticleModal.tsx
│           ├── NotificationsSettingsDrawer.tsx
│           └── DuplicateArticleDrawer/
│               ├── index.tsx
│               ├── DuplicateArticleForm.tsx
│               └── CancelDuplicateArticleModal.tsx
├── Segmentation/
│   ├── index.tsx
│   ├── types.ts
│   ├── constants.ts
│   ├── services.ts
│   ├── components/
│   │   ├── ArticleAudience.tsx
│   │   ├── ArticleAudienceForm.tsx
│   │   ├── ArticleAudienceFormSkeleton.tsx
│   │   └── ArticleAudienceFooter.tsx
│   └── hooks/
│       ├── useSaveArticleAudience.ts   # artículo-específico
│       └── useGetAudienceItems.ts      # artículo-específico
├── hooks/
│   ├── useArticleId.ts
│   ├── useArticleNavigation.ts
│   ├── useNavigateToCurrentArticle.ts
│   ├── useArticleBreadcrumb.ts
│   ├── useLibraryAccessGuard.ts
│   ├── useGetArticle.ts
│   ├── useGetArticleTree.ts
│   ├── useArticleInformation.ts
│   ├── useLibrariesTitle.ts
│   ├── useLibraryNotFoundHandler.ts
│   ├── useChangeArticleName.ts
│   ├── useUpdateArticleStatus.ts
│   ├── useUpdateArticleBody.ts
│   ├── useDeleteArticle.ts
│   ├── useDuplicateArticle.ts
│   ├── useCreateArticleAttachment.ts
│   ├── useCreateArticleAttachmentBulk.ts
│   ├── useDeleteArticleAttachment.ts
│   ├── useUpdateArticleAttachmentName.ts
│   ├── useUpdateArticleAttachmentsPositions.ts
│   ├── useHeaderArticleForm.ts
│   ├── useUpdateCoverPicture.ts
│   ├── useRemoveCoverPicture.ts
│   ├── useGetCollaborators.ts
│   ├── useGetEditors.ts
│   ├── useUpdateEditors.ts
│   ├── useSetArticleQueryData.ts
│   ├── useCoverPictureActions/
│   │   ├── index.tsx
│   │   └── components/
│   │       └── RemoveCoverPictureModal.tsx
│   ├── useEditPermissionsDrawer/
│   │   ├── index.tsx
│   │   └── components/
│   │       ├── EditPermissionsFormContent.tsx
│   │       └── CancelEditPermissionsModal.tsx
│   └── useSharePermissionsActions/
│       └── index.tsx
├── components/
│   └── ArticleInternalHeader.tsx
├── utils/
│   └── libraryErrorUtils.ts
├── types.ts
├── queries.ts
├── constants.ts
├── services.ts
├── utils.ts
└── routes.ts
```

---

## Reglas de estructura

1. **Organización por feature, no por tipo**: cada sub-feature (Article, Segmentation) tiene su propio directorio con sus propios `types.ts`, `services.ts`, `utils.ts`, `hooks/`, `components/`.
2. **Archivos raíz del módulo**: `types.ts`, `queries.ts`, `constants.ts`, `services.ts`, `utils.ts`, `routes.ts` — comparten funcionalidad entre sub-features.
3. **Hooks que renderizan UI** viven en subdirectorio propio con `index.tsx` + `components/` internos. Ej: `useCoverPictureActions/`, `useEditPermissionsDrawer/`.
4. **Componentes puramente presentacionales** van en `components/` sin subcarpeta, a menos que tengan sub-componentes propios.
5. **No crear archivos barrel** (`index.ts`) salvo en casos donde explícitamente se requiera re-exportar desde fuera del módulo.
6. **Utils: archivo único vs directorio**:
   - Si hay un solo archivo de utils → `utils.ts` en la raíz del scope.
   - Si hay más de uno → directorio `utils/` con archivos en `camelCase` sin sufijo `Utils` (ej: `utils/libraryError.ts`, no `utils/libraryErrorUtils.ts`).

---

## Reglas de componentes

### Estructura de un componente

```tsx
// 1. Imports React/externos
// 2. Imports del design system (@material-hu/...)
// 3. Imports internos del módulo (hooks, services, utils, types)
// 4. Definición de Props (interface)
// 5. Componente como arrow function con export nombrado o default
// 6. Export default al final

interface ArticleTitleProps {
  article: Article;
}

const ArticleTitle = ({ article }: ArticleTitleProps) => {
  // ...
};

export default ArticleTitle;
```

### Patrones de componentes

- **Inline save**: Para campos editables (título, body), usar `onBlur` para disparar la mutación. Envolver con `FormProvider` + `useForm`.
- **Lazy load de modales**: Los modales de confirmación se importan con `lazy()` dentro del componente que los usa para no inflar el bundle inicial.
- **Skeleton como estado de carga**: Cada sección con carga asíncrona tiene su propio componente `*Skeleton.tsx` (ej: `ArticleDetailSkeleton`, `ArticleAudienceFormSkeleton`).
- **Alertas contextuales**: Usar `ArticleNewAlert` / `ArticleNotSegmentedAlert` para guiar al usuario según el estado del artículo.
- **Renderizado condicional**: Usar flags booleanas explícitas (`showLoading`, `showDetail`, `showError`) en vez de ternarios anidados.

```tsx
const showLoading = isLoading;
const showError = !isLoading && isError;
const showContent = !isLoading && !isError && !!article;

return (
  <>
    {showLoading && <ArticleSkeleton place="article" />}
    {showError && <ArticleError />}
    {showContent && <ArticleDetail article={article} />}
  </>
);
```

---

## Reglas de hooks

### Nomenclatura de hooks

Los hooks se clasifican en cuatro categorías con convenciones de nombre distintas:

| Categoría | Patrón | Ejemplos |
|-----------|--------|---------|
| **Query** (fetching de datos) | `useGet[Data]` | `useGetArticle`, `useGetAudienceCount`, `useGetEditors` |
| **Mutation** (acciones que modifican datos) | `use[Action][Data]` | `useDeleteArticle`, `useUpdateArticleStatus`, `useCreateArticleAttachment` |
| **Drawer** (hooks que encapsulan un drawer) | `use[Data]Drawer` | `useEditPermissionsDrawer`, `useDuplicateArticleDrawer` |
| **Otros** (utilidades, estado, lógica de UI) | `use[Something]` | `useArticleId`, `useArticleInformation`, `useLibrariesTitle` |

### Hooks de queries (fetching)

Si el hook depende de un parámetro opcional (ej: un ID de ruta), se debe validar dentro del `queryFn` y lanzar un error descriptivo. No usar el operador `!` para forzar el tipo.

```ts
// hooks/useGetArticleTree.ts
import { useQuery } from 'react-query';
import { librariesKeys } from '../queries';
import { getArticleTree } from '../services';

export const useGetArticleTree = (articleId: number | undefined) => {
  return useQuery({
    queryKey: librariesKeys.articleTree(articleId),
    queryFn: () => {
      if (!articleId) {
        throw new Error('[useGetArticleTree] Article ID is required');
      }
      return getArticleTree(articleId);
    },
    select: response => response.data,
    enabled: !!articleId,
  });
};
```

- El `enabled: !!articleId` evita que se ejecute sin el ID.
- El guard dentro de `queryFn` protege el tipo en tiempo de ejecución y hace el error trazable.
- Usar `select: response => response.data` para exponer directamente el tipo de dominio.
- Pasar `options` como último parámetro para permitir overrides desde el consumidor.

### Hooks de mutations

```ts
// hooks/useDeleteArticle.ts
export const useDeleteArticle = () => {
  const { enqueueSnackbar } = useHuSnackbar();
  const queryClient = useQueryClient();
  const navigate = useNavigate();

  return useMutation({
    mutationFn: (articleId: number) => deleteArticle(articleId),
    onSuccess: () => {
      enqueueSnackbar({ title: t('delete.success'), variant: 'success' });
      queryClient.invalidateQueries(librariesKeys.all());
      navigate(librariesRoutes.base());
    },
    onError: (err) => {
      // Manejar errores específicos antes del fallback genérico
      if (isArticleSpecificError(err)) {
        enqueueSnackbar({ title: t('specific.error'), variant: 'error' });
        return;
      }
      enqueueSnackbar({ title: t('delete.error'), variant: 'error' });
    },
  });
};
```

- Invalidar queries relacionadas en `onSuccess`.
- Manejar errores específicos (por código) antes del fallback genérico.
- Mostrar snackbar de éxito/error en el hook, no en el componente.
- Navegar en el hook cuando aplica (ej: después de delete).

### Hooks de drawers (con UI)

Para hooks que encapsulan un drawer, usar la convención de subdirectorio:

```
hooks/useEditPermissionsDrawer/
├── index.tsx          ← exporta el hook (nombrado, no default)
└── components/
    ├── EditPermissionsFormContent.tsx
    └── CancelEditPermissionsModal.tsx
```

El hook retorna un objeto con la UI rendereable y las funciones para mostrarla:

```tsx
export const useEditPermissionsDrawer = () => {
  const { drawer, showDrawer } = useDrawer(EditPermissionsFormContent);
  const { modal, showModal } = useLazyModal(CancelEditPermissionsModal);

  return {
    editPermissionsDrawer: drawer,
    cancelPermissionsModal: modal,
    showEditPermissionsDrawer: showDrawer,
  };
};
```

### Hooks de utilidad del módulo

- `useArticleId()`: Extrae y parsea los route params. Devuelve `{ rootId, childId, articleId }` donde `articleId = childId ?? rootId` (id del artículo activo).
- `useArticleInformation(article)`: Calcula flags de permisos y estado (`canEdit`, `canDelete`, `isActive`, etc.).
- `useLibrariesTitle()`: Obtiene el título localizado del módulo con prefijo "HU".
- `useLibraryNotFoundHandler()`: Registra handler global de 404 (errores de la API) y navega al base route. Deduplica errores con ventana de 500ms.
- `useLibraryAccessGuard({ resetKey, isInvalid })`: Hook genérico usado por los layouts de ruta para validar URL params y redirigir al home si son inválidos. Ver sección **Routes & Navigation**.
- `useNavigateToCurrentArticle()`: Devuelve un callback que navega al artículo del contexto actual (útil para botones de "back" desde sub-pantallas como segmentation/report).
- `useArticleNavigation()`: Devuelve `navigateBack` con lógica de prioridad search → parent → home.
- `useSetArticleQueryData()`: Actualiza el cache de React Query de forma type-safe sin invalidar.

---

## Reglas de queries/mutations/services

### services.ts — Capa de API

```ts
// services.ts
export const getArticle = (articleId: number) =>
  api.get<ArticleResponse>(`/articles/${articleId}`);

export const updateArticleStatus = (payload: UpdateArticleStatusPayload) =>
  api.patch<ArticleResponse>('/articles/status', payload);
```

- Funciones puras que retornan la promesa del HTTP client.
- Sin lógica de negocio ni manejo de errores: eso va en los hooks.
- Tipar la respuesta con el genérico del cliente HTTP.
- Agrupar por entidad en el mismo archivo (artículo, adjuntos, etc.).

### queries.ts — React Query Key Factory

```ts
// queries.ts
export const librariesKeys = {
  all: () => ['libraries'] as const,
  article: (id?: number) => [...librariesKeys.all(), 'article', id] as const,
  articleTree: (id?: number) => [...librariesKeys.all(), 'tree', id] as const,
  collaborators: (id?: number, search?: string) =>
    [...librariesKeys.all(), 'collaborators', id, search] as const,
  editors: (id?: number) => [...librariesKeys.all(), 'editors', id] as const,
  audience: {
    all: (id?: number) => [...librariesKeys.all(), 'audience', id] as const,
    count: (id?: number) => [...librariesKeys.audience.all(id), 'count'] as const,
  },
};
```

- Usar `as const` para type safety.
- Estructura jerárquica: `all > entidad > id > sub-recurso`.
- La jerarquía permite invalidar en cascada: `librariesKeys.all()` invalida todo.
- Nunca hardcodear strings de query keys fuera de este archivo.

---

## Reglas de utils/constants

### utils — archivo único vs directorio

- **Un solo dominio de utils** → usar `utils.ts` directamente.
- **Múltiples dominios** → crear directorio `utils/` con archivos en `camelCase` sin sufijo `Utils`:

```
utils/
├── breadcrumb.ts      # getBreadcrumbItems, etc.
├── editors.ts         # splitEditors, formatEditorsPayload, etc.
└── libraryError.ts    # isLibraryQuery, isNotFoundError, etc.
```

```ts
// utils.ts (caso de un solo archivo)
export const getBreadcrumbItems = (tree: ArticleTree[]): BreadcrumbLink[] => { ... };
export const splitEditors = (editors: ArticleCollaborator[]) => { ... };
export const formatEditorsPayload = (editors: ArticleCollaborator[]) => { ... };
export const getPillConfig = (status: ArticleStatus) => { ... };
```

```ts
// utils/libraryError.ts (caso de directorio — nombre sin sufijo "Utils")
export const isLibraryQuery = (queryKey: unknown[]) => { ... };
export const isLibraryMutation = (mutationKey: unknown[]) => { ... };
export const isNotFoundError = (error: unknown) => { ... };
export const isLibraryNotFoundError = (queryKey: unknown[], error: unknown) => { ... };
```

- Solo transformaciones puras de datos.
- Sin side effects, sin llamadas a hooks, sin imports de react.
- Testeable unitariamente.

### constants.ts

```ts
export const ARTICLE_TITLE_MAX_LENGTH = 240;
export const MAX_EDITORS_LENGTH = 150;
```

- Solo valores literales que se usan en múltiples lugares del módulo.
- No mezclar con tipos ni funciones.

---

## Reglas de types

### types.ts — Tipos del dominio

```ts
// Enums de estado
export enum ArticleStatus {
  ENABLED = 'ENABLED',
  DISABLED = 'DISABLED',
}

// Tipos de dominio (respuesta de API)
export type Article = {
  id: number;
  title: string;
  body: string;
  status: ArticleStatus;
  attachments: ArticleAttachment[];
  segmentation: ArticleSegmentation;
  children: ArticleChild[];
  // ...
};

// Tipos de payload para mutaciones
export type UpdateArticleStatusPayload = {
  articleId: number;
  status: ArticleStatus;
};
```

- Separar enums, tipos de dominio y tipos de payload.
- Los tipos de payload llevan sufijo `Payload` o `Request`.
- Los tipos de respuesta de API son los mismos que los de dominio (se mapean en `select`).
- Tipos de sub-feature van en `Segmentation/types.ts`, no en el raíz.

---

## Segmentation — Arquitectura

La segmentación es el sub-sistema más complejo. Documenta su propia arquitectura.

### Tipos de condición (`SegmentationConditionType`)

| Tipo | Descripción |
|------|-------------|
| `ALL` | Todos los usuarios |
| `USERS` | IDs específicos de usuario |
| `ITEMS` | IDs de items de segmentación |
| `EXPRESSION` | Condiciones anidadas con operadores AND/OR |
| `USERS_OR_ITEMS` | Mezcla de usuarios e items |

### Flujo de datos

```
API (AudienceQuery)
  → audienceQueryToCriterias()    # API → form criteria  [src/components/Audience/utils]
  → [FormState]
  → criteriasToSegmentationExpression()   # form criteria → API payload  [src/components/Audience/utils]
  → API
```

### Hooks de segmentación

Los hooks genéricos de audiencia viven en `src/components/Audience/hooks/` y son compartidos por otros módulos (ServiceManagement, RolesAndPermissions, Competencies). Solo permanecen aquí los hooks específicos del dominio de artículo.

**Hooks artículo-específicos** (`Segmentation/hooks/`):

| Hook | Propósito |
|------|-----------|
| `useGetAudienceItems` | Carga grupos e items de segmentación con mapas indexados por ID |
| `useSaveArticleAudience` | Mutación para persistir la expresión en el artículo |

**Hooks compartidos** (`src/components/Audience/hooks/`):

| Hook | Propósito |
|------|-----------|
| `useGetAudienceCount` | Cuenta usuarios que cumplen una expresión de segmentación |
| `useGetAudienceUsers` | Query infinita para búsqueda de usuarios paginada |
| `useSelectedUsers` | Fetch datos completos de usuarios seleccionados por ID |
| `useAudienceFormCount` | Count reactivo desde criterias del formulario de Audience |
| `useSegmentationDrawerCount` | Count para el drawer de segmentación |
| `useIndividualDrawerCount` | Count para el drawer de selección individual |
| `useSegmentationFieldItems` | Carga dinámica de campos (grupos) con filtro de exclusión |
| `useSegmentationValueItems` | Carga dinámica de valores con paginación infinita |
| `useSegmentationDrawerService` | Crea search service desde condiciones de segmentación |
| `useIndividualDrawerService` | Crea search service desde IDs de usuarios |

**Utils compartidas** (`src/components/Audience/utils/`):

| Función | Propósito |
|---------|-----------|
| `criteriasToSegmentationExpression` | Form criteria → `SegmentationExpressionPayload` |
| `criteriaToSegmentationExpression` | Un `CriteriaEntry` → `SegmentationExpressionCondition` |
| `audienceQueryToCriterias` | `AudienceQuery` (API) → form criteria |
| `audienceQueryToSegmentationExpression` | `AudienceQuery` (API) → `SegmentationExpressionPayload` |
| `getSegmentationDescription` | Texto legible para una entrada de segmentación |
| `getIndividualDescription` | Texto legible para una entrada individual (resuelve nombres) |
| `getCollaboratorsService` | Factory de search service vinculado a una expresión |

**Archivos compartidos en raíz** (`src/components/Audience/`):

| Archivo | Propósito |
|---------|-----------|
| `services.ts` | `countAudienceByExpression`, `searchAudienceByExpression` |
| `queryKeys.ts` | `audienceExpressionKeys` — query key factory para React Query |
| `types.ts` | Tipos del dominio: `AudienceQuery`, `SegmentationExpressionPayload`, etc. |

---

## Patrones de modales y drawers

### Modal de confirmación

```tsx
// En el componente que dispara el modal
const DeleteArticleModal = lazy(() => import('./DeleteArticleModal'));
const { isOpen, openModal, closeModal } = useModalState();

return (
  <>
    <Button onClick={openModal}>Eliminar</Button>
    {isOpen && (
      <Suspense fallback={null}>
        <DeleteArticleModal onClose={closeModal} onConfirm={handleDelete} />
      </Suspense>
    )}
  </>
);
```

### Drawer con formulario

```tsx
// Drawer con FormProvider interno
const DuplicateArticleDrawer = () => {
  const form = useForm<DuplicateFormValues>();

  return (
    <FormProvider {...form}>
      <Drawer>
        <DuplicateArticleForm />
        <CancelDuplicateArticleModal />  {/* modal anidado para confirmar cierre */}
      </Drawer>
    </FormProvider>
  );
};
```

---

## Routes & Navigation

### URL shape

El módulo soporta dos niveles de artículo en la URL: **root** y **child** (un descendiente cualquiera del root). El `rootId` es siempre el primer segmento porque el sidebar fetchea por root.

```
/library                                             → Home
/library/:rootId                                     → Article (root)
/library/:rootId/segmentation                        → Segmentation (root)
/library/:rootId/report                              → Report (root)
/library/:rootId/child/:childId                      → Article (child)
/library/:rootId/child/:childId/segmentation         → Segmentation (child)
/library/:rootId/child/:childId/report               → Report (child)
```

### Estructura de rutas (anidada)

Las rutas se declaran en `src/routes.tsx`. La key del diseño: las acciones (`segmentation`, `report`) se declaran **una sola vez** como un array reutilizable (`articleActionRoutes`) y se montan tanto bajo el layout root como bajo el layout child.

```tsx
{
  path: 'library',
  element: <HuLibrariesLayout />,             // sidebar + theme + search
  children: [
    { index: true, element: <HuLibrariesHome /> },
    {
      path: ':rootId',
      element: <RootArticleLayout />,         // valida rootId, redirige si inválido
      children: [
        { index: true, element: <Article /> },
        ...articleActionRoutes,               // segmentation, report
        {
          path: 'child/:childId',
          element: <ChildArticleLayout />,    // valida que childId sea descendiente
          children: [
            { index: true, element: <Article /> },
            ...articleActionRoutes,           // mismas acciones, contexto child
          ],
        },
      ],
    },
  ],
}
```

`articleActionRoutes` vive en [`Layout/articleActionRoutes.tsx`](Layout/articleActionRoutes.tsx) dentro del módulo (no en `src/routes.tsx`) para mantener ownership.

**Por qué anidado**: en una estructura plana habría que declarar cada acción dos veces (una bajo `:rootId/...` y otra bajo `:rootId/child/:childId/...`). La estructura anidada permite reutilizar el subárbol y agregar acciones nuevas en una sola línea.

### URL builders (`routes.ts`)

```ts
// routes.ts
export const librariesRoutes = {
  base,                                                              // /library
  article,                                                           // /library/:rootId | .../child/:childId
  report:       (rootId, childId?) => `${article(rootId, childId)}/report`,
  segmentation: (rootId, childId?) => `${article(rootId, childId)}/segmentation`,
};
```

- Siempre usar estos helpers, nunca strings hardcodeados.
- `article(rootId, childId?)` agrega el segmento `/child/:childId` solo cuando `childId` está presente y difiere de `rootId`. Esto significa que el caller no tiene que normalizar `childId === rootId ? undefined : childId` en cada call site — el helper lo hace.

### Layouts como guards

Los dos layouts intermedios (`RootArticleLayout` y `ChildArticleLayout`) son guards que validan los params contra los datos cargados, y redirigen al home con un snackbar si la URL es inválida. Renderizan `<Outlet />` siempre, así los hijos manejan su propio loading sin bloquearse.

**[`RootArticleLayout`](Layout/RootArticleLayout.tsx)** — monta debajo de `:rootId`:
- Valida que `rootId` parsee a número.
- Valida que exista en `useGetRootArticles()`.
- Espera a que la query no esté `isLoading` ni `isFetching` (importante: tras una mutación que invalida `rootList`, no debe redirigir prematuramente).

**[`ChildArticleLayout`](Layout/ChildArticleLayout.tsx)** — monta debajo de `:rootId/child/:childId`:
- Valida que `childId` parsee a número.
- Valida que el árbol del childId tenga como root el `rootId` (vía `useGetArticleTree`).

Ambos delegan la mecánica del redirect (snackbar + dedup + `replace: true`) al hook compartido [`useLibraryAccessGuard`](hooks/useLibraryAccessGuard.ts).

### Hooks de navegación

| Hook | Qué hace |
|---|---|
| [`useArticleId`](hooks/useArticleId.ts) | Parsea `params.rootId` y `params.childId`. Devuelve `{ rootId, childId, articleId }`. `articleId = childId ?? rootId` es el id del artículo activo. |
| [`useNavigateToCurrentArticle`](hooks/useNavigateToCurrentArticle.ts) | Devuelve un callback que navega al artículo del contexto actual (`article(rootId, childId)`). Usado en `ArticleInternalHeader.handleBack` y `ArticleAudienceForm` (volver tras guardar/cancelar segmentation). |
| [`useArticleNavigation`](hooks/useArticleNavigation.ts) | Devuelve `navigateBack` con prioridad: (1) si `state.search` está seteado → `navigate(-1)` para restaurar resultados de búsqueda; (2) si hay `parentId` → ir al padre en el árbol; (3) fallback al home. |
| [`useArticleBreadcrumb`](hooks/useArticleBreadcrumb.ts) | Construye los items del breadcrumb a partir del árbol. Cada click navega vía `librariesRoutes.article(rootId, ancestorId)`. |
| [`useLibraryAccessGuard`](hooks/useLibraryAccessGuard.ts) | Hook genérico usado por los dos layouts. Recibe `{ resetKey, isInvalid }`. Cuando `isInvalid` es true, muestra snackbar y redirige al home. Dedupe interno por `resetKey`. |

### Estado de navegación tipado

Cuando se navega desde un resultado de búsqueda hacia un artículo, [`SearchItem`](Article/components/ArticleSearch/SearchItem.tsx) pasa el query actual como `state` para que después el back lo restaure. El contrato está tipado en `types.ts`:

```ts
export type LibraryNavigationState = {
  search?: string;
};
```

- **Productor**: `SearchItem.handleSelect` hace `navigate(url, { state: { search } satisfies LibraryNavigationState })`.
- **Consumidor**: `useArticleNavigation.navigateBack` lee `location.state as LibraryNavigationState | null` y, si encuentra `search`, hace `navigate(-1)` para que el usuario regrese a los resultados de búsqueda con el query intacto.

### Reglas para agregar features

- **Nueva acción de artículo (e.g. `analytics`)**: agregar una entrada en `articleActionRoutes` y un builder en `librariesRoutes`. Funciona automáticamente para root y child.
- **Nueva URL del módulo que NO es acción de artículo**: declararla bajo el layout correspondiente en `src/routes.tsx`.
- **Cambiar la shape de una URL**: tocar solo `routes.ts` (los builders) y `src/routes.tsx` (los `path` declarados). Mantener ambos sincronizados — son las dos fuentes que React Router consume.
- **Nueva navegación desde un componente**: usar `librariesRoutes.*` para construir la URL; nunca strings hardcodeados.

---

## Convenciones de naming

| Concepto | Convención | Ejemplo |
|----------|-----------|---------|
| Componente | PascalCase | `ArticleTitle` |
| Hook query | `useGet[Data]` | `useGetArticle`, `useGetAudienceCount` |
| Hook mutation | `use[Action][Data]` | `useDeleteArticle`, `useUpdateArticleStatus` |
| Hook drawer | `use[Data]Drawer` | `useEditPermissionsDrawer` |
| Hook otros | `use[Something]` | `useArticleId`, `useArticleInformation` |
| Service function | camelCase | `getArticle`, `updateArticleStatus` |
| Query key factory | camelCase + `Keys` | `librariesKeys` |
| Enum | PascalCase | `ArticleStatus` |
| Enum value | UPPER_SNAKE_CASE | `ArticleStatus.ENABLED` |
| Tipo de payload | PascalCase + `Payload` | `UpdateArticleStatusPayload` |
| Constante | UPPER_SNAKE_CASE | `ARTICLE_TITLE_MAX_LENGTH` |
| Ruta helper | camelCase + `Routes` | `librariesRoutes` |

---

## Stack tecnológico del módulo

- **React** — componentes funcionales + hooks
- **react-router-dom** — navegación, `useParams`, `useNavigate`, `useMatch`
- **react-hook-form** — gestión de formularios con `FormProvider` + `useWatch`
- **@tanstack/react-query** — fetching, caching, invalidación
- **@material-hu/mui** — componentes UI (Stack, Typography, Box, etc.)
- **@material-hu/components** — design system propio (HuSnackbar, HuPills, etc.)
- **react-helmet-async** — gestión del `<title>` del documento
