> ## Documentation Index
> Fetch the complete documentation index at: https://devperez08-platform-list-anime-54.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Progress Tracking

> Track your episode viewing progress with EpiNeko's comprehensive progress tracking system

## Overview

EpiNeko's progress tracking system allows users to mark individual episodes as watched, automatically updating their library statistics and watch status. The system integrates seamlessly with the library management and provides visual feedback on viewing progress.

<CardGroup cols={2}>
  <Card title="Episode Tracking" icon="list-ol">
    Mark individual episodes as watched with one click
  </Card>

  <Card title="Auto Status Update" icon="wand-magic-sparkles">
    Status automatically changes based on progress
  </Card>

  <Card title="Visual Progress" icon="chart-simple">
    Progress bars show completion percentage
  </Card>

  <Card title="Episode Metadata" icon="info-circle">
    View episode titles, dates, and filler indicators
  </Card>
</CardGroup>

## Episodes Watched Field

Each library item tracks the number of episodes you've watched:

```typescript theme={null}
export interface LibraryItem {
  // ... other fields
  episodes_watched?: number;  // Current progress (0 to total)
}
```

**Database Schema** (supabase/migrations/20260218\_initial\_schema.sql:55):

```sql theme={null}
create table public.user_library (
  -- ...
  episodes_watched integer default 0,
  -- ...
);
```

<Note>
  The `episodes_watched` field defaults to 0 and represents the highest episode number you've completed. For example, `episodes_watched: 5` means you've watched episodes 1-5.
</Note>

## Episode Data Structure

Episode information is fetched from the Jikan API:

```typescript theme={null}
export interface JikanEpisode {
  mal_id: number;              // Episode number
  url: string;                 // MyAnimeList URL
  title: string;               // Episode title
  title_japanese: string | null;
  title_romanji: string | null;
  duration: number | null;     // Minutes
  aired: string | null;        // Air date
  filler: boolean;             // Is this a filler episode?
  recap: boolean;              // Is this a recap episode?
  forum_url: string | null;
}
```

## EpisodeList Component

The main component for displaying and tracking episode progress:

```tsx theme={null}
import EpisodeList from '@/components/anime/EpisodeList';

<EpisodeList 
  animeId={52991}
  totalEpisodes={28}
  title="Sousou no Frieren"
  imageUrl="https://cdn.myanimelist.net/images/anime/..."
/>
```

### Component Props

<ParamField path="animeId" type="number" required>
  The MyAnimeList ID (Jikan ID) of the anime
</ParamField>

<ParamField path="totalEpisodes" type="number | null" required>
  Total episode count for the series. Can be null for unknown or ongoing series.
</ParamField>

<ParamField path="title" type="string" required>
  Anime title for library operations
</ParamField>

<ParamField path="imageUrl" type="string" required>
  Poster image URL for library operations
</ParamField>

## Marking Episodes as Watched

### Toggle Episode Function

The core function for marking episodes:

```typescript theme={null}
const handleToggleEpisode = async (episodeNum: number) => {
  // Determine if adding to library or updating
  const isAdding = !isInLibrary;
  
  // Calculate new watched count
  const newCount = watchedCount === episodeNum ? episodeNum - 1 : episodeNum;
  
  // Check if completed
  const isCompleted = totalEpisodes ? newCount >= totalEpisodes : false;
  const newStatus: LibraryStatus = isCompleted ? 'completed' : 'watching';

  // Optimistic update
  setWatchedCount(newCount);
  
  try {
    if (isAdding) {
      // First time tracking - add to library
      await addToLibrary({
        anime_id_jikan: animeId,
        title: title,
        image_url: imageUrl,
        status: newStatus,
        episodes_watched: newCount,
      });
    } else {
      // Update existing item
      await updateLibraryItem(animeId, { 
        episodes_watched: newCount,
        status: newCount === 0 ? 'plan_to_watch' : newStatus
      });
    }
  } catch (error) {
    // Rollback on error
    setWatchedCount(watchedCount);
  }
};
```

**Implementation Reference:** src/components/anime/EpisodeList.tsx:44-85

### Automatic Behaviors

<Tabs>
  <Tab title="Auto Add to Library">
    If you mark an episode as watched and the anime isn't in your library, it's automatically added:

    ```typescript theme={null}
    if (isAdding) {
      const item: LibraryItem = {
        anime_id_jikan: animeId,
        title: title,
        image_url: imageUrl,
        status: newStatus,
        episodes_watched: newCount,
      };
      await addToLibrary(item);
      setIsInLibrary(true);
    }
    ```
  </Tab>

  <Tab title="Auto Status Change">
    Status updates automatically based on progress:

    ```typescript theme={null}
    // Completed when all episodes watched
    if (newCount >= totalEpisodes) {
      status = 'completed';
    }
    // Watching when some episodes watched
    else if (newCount > 0) {
      status = 'watching';
    }
    // Plan to watch when no episodes watched
    else {
      status = 'plan_to_watch';
    }
    ```
  </Tab>

  <Tab title="Optimistic Updates">
    UI updates immediately before server confirmation:

    ```typescript theme={null}
    // Update UI first
    setWatchedCount(newCount);
    setIsInLibrary(true);

    // Then sync to database
    await updateLibraryItem(animeId, { episodes_watched: newCount });

    // Rollback if failed
    catch (error) {
      setWatchedCount(oldCount);
      setIsInLibrary(false);
    }
    ```
  </Tab>
</Tabs>

## Visual Components

### Progress Header

Displays current progress at the top of the episode list:

```tsx theme={null}
<div className="flex items-center justify-between mb-8">
  <h2 className="text-2xl font-black italic border-l-4 border-primary pl-4">
    EPISODIOS
  </h2>
  <span className="text-zinc-500 text-sm font-bold bg-zinc-900 px-4 py-1 rounded-full">
    {watchedCount} / {totalEpisodes || displayEpisodes.length} VISTOS
  </span>
</div>
```

**Features:**

* Shows watched count vs total
* Updates in real-time as you mark episodes
* Styled with primary border accent

### Episode Cards

Each episode is displayed as an interactive card:

```tsx theme={null}
<div className={`
  flex items-center justify-between p-4 rounded-2xl transition-all
  ${isWatched 
    ? 'bg-emerald-500/10 border-emerald-500/30 ring-1 ring-emerald-500/20' 
    : 'bg-zinc-900/40 border-white/5 hover:border-white/10'
  }
`}>
  {/* Episode identifier */}
  <div className={`
    px-4 py-1.5 rounded-full font-black italic
    ${isWatched ? 'bg-emerald-500 text-black' : 'bg-zinc-800 text-zinc-400'}
  `}>
    T1 E{epNum}
  </div>
  
  {/* Episode title */}
  <h3 className={isWatched ? 'text-emerald-100' : 'text-zinc-200'}>
    {ep.title}
  </h3>
  
  {/* Checkbox button */}
  <button onClick={() => handleToggleEpisode(epNum)}>
    <svg><!-- Checkmark icon --></svg>
  </button>
</div>
```

**Visual States** (src/components/anime/EpisodeList.tsx:121-169):

* **Unwatched:** Gray background, white/5 border
* **Watched:** Green background, emerald border with glow
* **Hover:** Border brightens, checkbox highlights
* **Active:** Scale animation on click

### Episode Information

Additional metadata displayed for each episode:

<Steps>
  <Step title="Episode Number">
    Format: T1 E{number} (Season 1, Episode X)

    Displayed in rounded pill badge
  </Step>

  <Step title="Episode Title">
    English or romaji title from Jikan

    Truncates if too long
  </Step>

  <Step title="Japanese Title">
    Original Japanese title (if available)

    Shown in smaller text below main title
  </Step>

  <Step title="Filler/Recap Indicators">
    Future enhancement - show badges for filler/recap episodes
  </Step>
</Steps>

## Loading and Empty States

### Loading State

Displays spinner while fetching episode data:

```tsx theme={null}
if (isLoading) {
  return (
    <div className="flex justify-center py-12">
      <span className="loading loading-spinner loading-lg text-primary"></span>
    </div>
  );
}
```

### No Episodes Available

When Jikan has no episode data but total count is known:

```typescript theme={null}
const displayEpisodes = episodes.length > 0 
  ? episodes 
  : Array.from({ length: totalEpisodes || 0 }, (_, i) => ({
      mal_id: i + 1,
      title: `Episodio ${i + 1}`,
    } as JikanEpisode));
```

**Fallback Behavior** (src/components/anime/EpisodeList.tsx:96-101):

* Generates placeholder episodes if Jikan data unavailable
* Uses episode numbers as titles
* Still fully functional for progress tracking

### Truly Empty

No episodes and no total count:

```tsx theme={null}
if (displayEpisodes.length === 0) {
  return (
    <div className="bg-zinc-900/30 rounded-2xl p-8 border border-white/5 text-center">
      <p className="text-zinc-500 italic">
        No hay información de episodios disponible.
      </p>
    </div>
  );
}
```

## Batch Operations

### Mark Multiple Episodes

Mark multiple episodes at once by clicking the highest episode number:

```typescript theme={null}
// Clicking episode 10 marks episodes 1-10 as watched
const newCount = 10;

await updateLibraryItem(animeId, { 
  episodes_watched: newCount 
});
```

**User Flow:**

1. User clicks episode 10 checkbox
2. All episodes 1-10 are considered watched
3. Visually, all episode cards 1-10 show as completed
4. Progress counter updates: "10 / 28 VISTOS"

### Unmark Episodes

Clicking the current highest episode decrements the count:

```typescript theme={null}
// If watchedCount is 10, clicking episode 10 sets it to 9
const newCount = watchedCount === episodeNum ? episodeNum - 1 : episodeNum;
```

**Example:**

* Currently watched through episode 10
* Click episode 10 again
* Count decreases to 9
* Episode 10 shows as unwatched

## Integration with Library

Progress tracking is tightly integrated with library management:

<CodeGroup>
  ```typescript Add to Library theme={null}
  // Marking an episode adds anime to library
  await addToLibrary({
    anime_id_jikan: animeId,
    title: title,
    image_url: imageUrl,
    status: 'watching',
    episodes_watched: 1,  // First episode
  });
  ```

  ```typescript Update Progress theme={null}
  // Updates existing library item
  await updateLibraryItem(animeId, { 
    episodes_watched: 15,
    status: 'watching'
  });
  ```

  ```typescript Auto Complete theme={null}
  // Watching last episode auto-completes
  if (newCount >= totalEpisodes) {
    await updateLibraryItem(animeId, { 
      episodes_watched: totalEpisodes,
      status: 'completed'
    });
  }
  ```
</CodeGroup>

## Error Handling

Comprehensive error handling with rollback:

```typescript theme={null}
try {
  // Optimistic update
  setWatchedCount(newCount);
  setIsInLibrary(true);
  
  // Sync to database
  await updateLibraryItem(animeId, { episodes_watched: newCount });
  
  router.refresh();
} catch (error: any) {
  // Rollback optimistic updates
  setWatchedCount(watchedCount);
  setIsInLibrary(false);
  
  // Show user-friendly error
  if (error.message?.includes('logged in')) {
    alert('Debes iniciar sesión para marcar episodios como vistos.');
  } else {
    alert('Error al actualizar progreso: ' + error.message);
  }
}
```

**Error Scenarios** (src/components/anime/EpisodeList.tsx:73-84):

* User not authenticated
* Network failure
* Database constraint violation
* Rate limiting

## Fetching Episode Data

Episodes are fetched from Jikan API:

```typescript theme={null}
import { getAnimeEpisodes } from '@/services/jikan';

const episodeResponse = await getAnimeEpisodes(animeId, page);

// Returns paginated episodes
console.log(episodeResponse.data);        // Episode array
console.log(episodeResponse.pagination);  // Page info
```

**Data Fetch Pattern** (src/components/anime/EpisodeList.tsx:22-42):

```typescript theme={null}
useEffect(() => {
  async function fetchData() {
    const [epRes, libItem] = await Promise.all([
      getAnimeEpisodes(animeId),    // Episode data
      getLibraryItem(animeId)       // Current progress
    ]);
    
    setEpisodes(epRes.data || []);
    if (libItem) {
      setWatchedCount(libItem.episodes_watched || 0);
    }
  }
  fetchData();
}, [animeId]);
```

## Progress Visualization

Progress is visualized in multiple places:

### Episode List Header

Shows fraction and percentage:

```tsx theme={null}
<span>
  {watchedCount} / {totalEpisodes || displayEpisodes.length} VISTOS
</span>
```

### Profile Statistics

Progress bars on profile page:

```tsx theme={null}
<ProgressBar 
  label="Sousou no Frieren"
  value={watchedCount}
  total={totalEpisodes}
  color="bg-primary"
/>
```

### Library Cards

Could add progress indicators to library cards:

```tsx theme={null}
<div className="progress-bar">
  <div 
    style={{ width: `${(watchedCount / totalEpisodes) * 100}%` }}
    className="bg-primary"
  />
</div>
```

## Best Practices

<CardGroup cols={2}>
  <Card title="Optimistic Updates" icon="bolt">
    Update UI immediately for responsive feel

    ```typescript theme={null}
    setCount(newCount); // First
    await updateLibrary(); // Then
    ```
  </Card>

  <Card title="Always Rollback" icon="rotate-left">
    Revert optimistic changes on error

    ```typescript theme={null}
    catch (e) {
      setCount(oldCount);
    }
    ```
  </Card>

  <Card title="Batch Fetch" icon="layer-group">
    Fetch episodes and library data in parallel

    ```typescript theme={null}
    Promise.all([getEpisodes(), getLibrary()])
    ```
  </Card>

  <Card title="Loading States" icon="spinner">
    Show spinners during data fetches and updates
  </Card>
</CardGroup>

## Advanced Features

### Sequential Watching

Future enhancement - enforce sequential watching:

```typescript theme={null}
const canMarkAsWatched = (epNum: number) => {
  return epNum === watchedCount + 1;
};
```

### Partial Episodes

Track partial episode progress:

```typescript theme={null}
interface LibraryItem {
  episodes_watched: number;
  partial_episode_progress?: number; // 0-100 percentage
}
```

### Watch History

Track when episodes were watched:

```typescript theme={null}
interface EpisodeHistory {
  episode_number: number;
  watched_at: string;
  rewatch_count: number;
}
```

## Related Features

* [Library Management](/features/library-management) - Backend storage
* [Watch Status](/features/watch-status) - Status auto-updates
* [Anime Discovery](/features/anime-discovery) - Episode data source
* [Profile Statistics](/features/profile-settings) - Progress visualization
