# TanStack Query - Server State Management

## Cuándo Usar

**TanStack Query es para SERVER STATE:**
- ✅ Datos que vienen de APIs
- ✅ Listas de entidades (accounts, policies, claims)
- ✅ Detalles de entidades
- ✅ Resultados de búsqueda

**NO usar para UI STATE (usar Zustand):**
- ❌ Filtros activos
- ❌ Elemento seleccionado
- ❌ Estado de modals/tabs
- ❌ Datos de formulario en progreso

---

## Setup

### QueryProvider

Ya configurado en `src/providers/QueryProvider.tsx`:

```typescript
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 5 * 60 * 1000,      // Cache 5 min
      retry: 1,
      refetchOnWindowFocus: false,   // Widgets embebidos
    },
  },
});

export function QueryProvider({ children }: { children: ReactNode }) {
  return (
    <QueryClientProvider client={queryClient}>
      {children}
      {import.meta.env.DEV && <ReactQueryDevtools initialIsOpen={false} />}
    </QueryClientProvider>
  );
}
```

---

## Patrones

### Query Básica (Lectura)

```typescript
// hooks/useAccounts.ts
import { useQuery } from '@tanstack/react-query';
import { getAccounts } from '../services/repositories/accountsRepository';

export function useAccounts() {
  return useQuery({
    queryKey: ['accounts'],
    queryFn: ({ signal }) => getAccounts(signal),
  });
}
```

**Uso en componente:**
```typescript
function AccountList() {
  const { data: accounts, isLoading, error } = useAccounts();

  if (isLoading) return <DSkeleton />;
  if (error) return <DAlert type="error">{error.message}</DAlert>;

  return (
    <div>
      {accounts?.map(account => (
        <AccountCard key={account.id} account={account} />
      ))}
    </div>
  );
}
```

---

### Query con Parámetro

```typescript
// hooks/useAccount.ts
export function useAccount(id: string | null) {
  return useQuery({
    queryKey: ['account', id],
    queryFn: ({ signal }) => getAccountById(id!, signal),
    enabled: !!id,  // Solo fetch si id existe
  });
}
```

**Uso:**
```typescript
const { selectedAccountId } = useUIStore();
const { data: account, isLoading } = useAccount(selectedAccountId);
```

---

### Query con Filtros

```typescript
// hooks/useMovements.ts
interface MovementFilters {
  accountId: string;
  startDate?: string;
  endDate?: string;
  type?: 'income' | 'expense';
}

export function useMovements(filters: MovementFilters) {
  return useQuery({
    queryKey: ['movements', filters],
    queryFn: ({ signal }) => getMovements(filters, signal),
    enabled: !!filters.accountId,
  });
}
```

**Importante:** Los filtros son parte del `queryKey`. Cambiar filtros = nuevo cache entry.

---

### Mutation (Escritura)

```typescript
// hooks/useCreateAccount.ts
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { createAccount } from '../services/repositories/accountsRepository';

export function useCreateAccount() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: createAccount,
    onSuccess: () => {
      // Invalidar cache para refetch automático
      queryClient.invalidateQueries({ queryKey: ['accounts'] });
    },
  });
}
```

**Uso:**
```typescript
function CreateAccountForm() {
  const { mutate: create, isPending, error } = useCreateAccount();

  const handleSubmit = (data: CreateAccountData) => {
    create(data, {
      onSuccess: () => {
        toast.success('Account created');
        closeModal();
      },
    });
  };

  return (
    <form onSubmit={handleSubmit}>
      {/* fields */}
      <DButton type="submit" loading={isPending}>
        Create
      </DButton>
    </form>
  );
}
```

---

### Mutation con Actualización Optimista

```typescript
// hooks/useUpdateAccount.ts
export function useUpdateAccount() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: ({ id, data }: { id: string; data: UpdateAccountData }) =>
      updateAccount(id, data),

    // Optimistic update
    onMutate: async ({ id, data }) => {
      await queryClient.cancelQueries({ queryKey: ['account', id] });

      const previous = queryClient.getQueryData(['account', id]);

      queryClient.setQueryData(['account', id], (old: Account) => ({
        ...old,
        ...data,
      }));

      return { previous };
    },

    onError: (err, { id }, context) => {
      // Rollback on error
      queryClient.setQueryData(['account', id], context?.previous);
    },

    onSettled: (_, __, { id }) => {
      // Refetch para sincronizar
      queryClient.invalidateQueries({ queryKey: ['account', id] });
      queryClient.invalidateQueries({ queryKey: ['accounts'] });
    },
  });
}
```

---

## Query Keys - Convenciones

```typescript
// ✅ Correcto - Jerárquico y predecible
['accounts']                          // Lista
['account', accountId]                // Detalle
['account', accountId, 'movements']   // Relacionados
['movements', { accountId, filters }] // Con filtros

// ❌ Incorrecto
['getAccounts']           // No usar verbos
['account-detail']        // No usar strings compuestos
[accountId, 'account']    // Orden inconsistente
```

**Invalidación por prefijo:**
```typescript
// Invalida: ['accounts'], ['account', '1'], ['account', '2', 'movements']
queryClient.invalidateQueries({ queryKey: ['account'] });
```

---

## Estados de Query

```typescript
const {
  data,           // Datos (undefined mientras carga)
  isLoading,      // Primera carga (no hay data aún)
  isFetching,     // Cualquier fetch (incluye refetch)
  isError,        // Hubo error
  error,          // Objeto error
  isSuccess,      // Fetch exitoso
  refetch,        // Función para refetch manual
} = useQuery({...});
```

**Patrón común:**
```typescript
if (isLoading) return <Loading />;
if (isError) return <Error message={error.message} onRetry={refetch} />;
return <Content data={data} />;
```

---

## Estados de Mutation

```typescript
const {
  mutate,         // Función para ejecutar (fire-and-forget)
  mutateAsync,    // Función async (retorna Promise)
  isPending,      // En progreso
  isError,        // Hubo error
  error,          // Objeto error
  isSuccess,      // Éxito
  reset,          // Reset estado
} = useMutation({...});
```

---

## Errores Comunes

### ❌ Fetch en useEffect

```typescript
// MAL - No usar useEffect para fetch
useEffect(() => {
  const fetchData = async () => {
    const data = await getAccounts();
    setAccounts(data);
  };
  fetchData();
}, []);
```

```typescript
// BIEN - Usar useQuery
const { data: accounts } = useAccounts();
```

### ❌ Estado duplicado

```typescript
// MAL - Duplicar data en useState
const { data } = useAccounts();
const [accounts, setAccounts] = useState(data); // ❌
```

```typescript
// BIEN - Usar data directamente
const { data: accounts } = useAccounts();
// accounts ya es reactivo
```

### ❌ QueryKey incorrecto

```typescript
// MAL - Key no incluye dependencias
const { data } = useQuery({
  queryKey: ['movements'],  // ❌ Falta accountId
  queryFn: () => getMovements(accountId),
});
```

```typescript
// BIEN - Key incluye todas las dependencias
const { data } = useQuery({
  queryKey: ['movements', accountId],  // ✅
  queryFn: ({ signal }) => getMovements(accountId, signal),
  enabled: !!accountId,
});
```

---

## Integración con Zustand

**Server state en Query, UI state en Zustand:**

```typescript
function AccountDashboard() {
  // Server state (TanStack Query)
  const { data: accounts, isLoading } = useAccounts();

  // UI state (Zustand)
  const { selectedAccountId, setSelectedAccountId } = useUIStore();

  // Derived: cuenta seleccionada (combina ambos)
  const selectedAccount = accounts?.find(a => a.id === selectedAccountId);

  return (
    <div>
      <AccountList
        accounts={accounts}
        selectedId={selectedAccountId}
        onSelect={setSelectedAccountId}
      />
      {selectedAccount && <AccountDetail account={selectedAccount} />}
    </div>
  );
}
```

---

## DevTools

En desarrollo, React Query DevTools aparece automáticamente (botón flotante).

Muestra:
- Queries activas y su estado
- Cache entries
- Tiempo de stale/fresh
- Histórico de fetches

---

## Checklist

Antes de crear un hook de query:

- [ ] ¿Es server state? (Si no, usar Zustand)
- [ ] ¿QueryKey incluye todas las dependencias?
- [ ] ¿Usa `signal` para cancelación?
- [ ] ¿Tiene `enabled` si depende de parámetro opcional?
- [ ] ¿Mutation invalida queries relacionadas?
