# Zustand - UI State Management

## Cuándo Usar

**Zustand es para UI STATE:**
- ✅ Filtros activos
- ✅ Elemento seleccionado
- ✅ Estado de modals/drawers
- ✅ Tab activo
- ✅ Paso actual de wizard
- ✅ Preferencias de vista
- ✅ Datos de formulario en progreso

**NO usar para SERVER STATE (usar TanStack Query):**
- ❌ Listas de entidades de API
- ❌ Detalles de entidades
- ❌ Loading/error de fetch (Query lo maneja)

---

## Store Base

El template incluye `src/store/useUIStore.ts` preconfigurado:

```typescript
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';

interface UIState {
  // Estado
  selectedId: string | null;
  searchTerm: string;
  isModalOpen: boolean;

  // Actions
  setSelectedId: (id: string | null) => void;
  setSearchTerm: (term: string) => void;
  toggleModal: () => void;
}

export const useUIStore = create<UIState>()(
  devtools(
    (set) => ({
      selectedId: null,
      searchTerm: '',
      isModalOpen: false,

      setSelectedId: (id) =>
        set({ selectedId: id }, false, 'ui/setSelectedId'),

      setSearchTerm: (term) =>
        set({ searchTerm: term }, false, 'ui/setSearchTerm'),

      toggleModal: () =>
        set((s) => ({ isModalOpen: !s.isModalOpen }), false, 'ui/toggleModal'),
    }),
    { name: 'ui-store' }
  )
);
```

---

## Uso en Componentes

### Lectura de Estado

```typescript
function MyComponent() {
  // Opción 1: Destructuring (re-render en cualquier cambio)
  const { selectedId, searchTerm } = useUIStore();

  // Opción 2: Selector (re-render solo cuando cambia lo seleccionado)
  const selectedId = useUIStore((state) => state.selectedId);

  return <div>Selected: {selectedId}</div>;
}
```

**Recomendación:** Usar selectores para componentes que solo necesitan parte del estado.

### Llamar Actions

```typescript
function AccountList({ accounts }) {
  const setSelectedId = useUIStore((state) => state.setSelectedId);

  return (
    <ul>
      {accounts.map(account => (
        <li
          key={account.id}
          onClick={() => setSelectedId(account.id)}
        >
          {account.name}
        </li>
      ))}
    </ul>
  );
}
```

---

## Patrones Comunes

### Selección Simple

```typescript
interface UIState {
  selectedId: string | null;
  setSelectedId: (id: string | null) => void;
  clearSelection: () => void;
}

// En store
selectedId: null,
setSelectedId: (id) => set({ selectedId: id }, false, 'ui/select'),
clearSelection: () => set({ selectedId: null }, false, 'ui/clearSelection'),
```

### Selección Múltiple

```typescript
interface UIState {
  selectedIds: string[];
  toggleSelection: (id: string) => void;
  selectAll: (ids: string[]) => void;
  clearSelection: () => void;
}

// En store
selectedIds: [],
toggleSelection: (id) => set(
  (state) => ({
    selectedIds: state.selectedIds.includes(id)
      ? state.selectedIds.filter(i => i !== id)
      : [...state.selectedIds, id]
  }),
  false,
  'ui/toggleSelection'
),
selectAll: (ids) => set({ selectedIds: ids }, false, 'ui/selectAll'),
clearSelection: () => set({ selectedIds: [] }, false, 'ui/clearSelection'),
```

### Filtros

```typescript
interface Filters {
  status: string;
  dateRange: { from: string; to: string } | null;
  search: string;
}

interface UIState {
  filters: Filters;
  setFilter: <K extends keyof Filters>(key: K, value: Filters[K]) => void;
  clearFilters: () => void;
}

const initialFilters: Filters = {
  status: 'all',
  dateRange: null,
  search: '',
};

// En store
filters: initialFilters,
setFilter: (key, value) => set(
  (state) => ({ filters: { ...state.filters, [key]: value } }),
  false,
  `ui/setFilter/${key}`
),
clearFilters: () => set({ filters: initialFilters }, false, 'ui/clearFilters'),
```

### Modal/Drawer State

```typescript
interface UIState {
  modal: {
    isOpen: boolean;
    type: 'create' | 'edit' | 'delete' | null;
    data: unknown;
  };
  openModal: (type: string, data?: unknown) => void;
  closeModal: () => void;
}

// En store
modal: { isOpen: false, type: null, data: null },
openModal: (type, data = null) => set(
  { modal: { isOpen: true, type, data } },
  false,
  'ui/openModal'
),
closeModal: () => set(
  { modal: { isOpen: false, type: null, data: null } },
  false,
  'ui/closeModal'
),
```

### Wizard/Stepper

```typescript
interface UIState {
  currentStep: number;
  formData: Record<string, unknown>;
  nextStep: () => void;
  prevStep: () => void;
  goToStep: (step: number) => void;
  updateFormData: (data: Record<string, unknown>) => void;
  resetWizard: () => void;
}

// En store
currentStep: 0,
formData: {},
nextStep: () => set(
  (state) => ({ currentStep: state.currentStep + 1 }),
  false,
  'wizard/next'
),
prevStep: () => set(
  (state) => ({ currentStep: Math.max(0, state.currentStep - 1) }),
  false,
  'wizard/prev'
),
goToStep: (step) => set({ currentStep: step }, false, 'wizard/goTo'),
updateFormData: (data) => set(
  (state) => ({ formData: { ...state.formData, ...data } }),
  false,
  'wizard/updateData'
),
resetWizard: () => set(
  { currentStep: 0, formData: {} },
  false,
  'wizard/reset'
),
```

---

## Múltiples Stores

Para widgets complejos, puedes tener múltiples stores:

```typescript
// store/useFiltersStore.ts
export const useFiltersStore = create<FiltersState>()(
  devtools((set) => ({...}), { name: 'filters-store' })
);

// store/useWizardStore.ts
export const useWizardStore = create<WizardState>()(
  devtools((set) => ({...}), { name: 'wizard-store' })
);

// store/useUIStore.ts (general)
export const useUIStore = create<UIState>()(
  devtools((set) => ({...}), { name: 'ui-store' })
);
```

---

## DevTools

Zustand con `devtools` middleware aparece en Redux DevTools (extensión de browser).

El tercer parámetro de `set` es el action name:
```typescript
set({ selectedId: id }, false, 'ui/setSelectedId')
//                              ^^^^^^^^^^^^^^^^^ Aparece en DevTools
```

---

## Errores Comunes

### ❌ Server state en Zustand

```typescript
// MAL - Datos de API en Zustand
const useStore = create((set) => ({
  accounts: [],  // ❌ Esto debe ser useQuery
  fetchAccounts: async () => {
    const data = await getAccounts();
    set({ accounts: data });
  },
}));
```

```typescript
// BIEN - Server state en Query
const { data: accounts } = useAccounts(); // TanStack Query
```

### ❌ Loading/Error manual

```typescript
// MAL - Manejar loading manualmente
const useStore = create((set) => ({
  isLoading: false,  // ❌ Query lo maneja
  error: null,       // ❌ Query lo maneja
}));
```

### ❌ Re-renders innecesarios

```typescript
// MAL - Selecciona todo el store
const store = useUIStore(); // Re-render en CUALQUIER cambio

// BIEN - Selecciona solo lo necesario
const selectedId = useUIStore(state => state.selectedId);
```

---

## Integración con TanStack Query

```typescript
function Dashboard() {
  // UI state
  const { selectedAccountId, filters } = useUIStore();

  // Server state (usa UI state como parámetros)
  const { data: accounts } = useAccounts();
  const { data: movements } = useMovements({
    accountId: selectedAccountId,
    ...filters,
  });

  // Derived state
  const selectedAccount = accounts?.find(a => a.id === selectedAccountId);
  const filteredMovements = movements?.filter(/* aplicar filtros locales */);

  return (/*...*/);
}
```

---

## Checklist

Antes de agregar estado a Zustand:

- [ ] ¿Es UI state? (Si viene de API, usar Query)
- [ ] ¿El action name es descriptivo? ('ui/setFilter', no 'update')
- [ ] ¿Usando selectores para evitar re-renders?
- [ ] ¿Tiene función reset si es necesario?
