trading-platform/docs/02-definicion-modulos/OQI-002-education/especificaciones/ET-EDU-007-video-player-advanced.md
Adrian Flores Cortes cea9ae85f1 docs: Add 8 ET specifications from TASK-002 audit gaps
Complete remaining ET specs identified in INTEGRATION-PLAN:
- ET-EDU-007: Video Player Advanced (554 LOC component)
- ET-MT4-001: WebSocket Integration (BLOCKER - 0% implemented)
- ET-ML-009: Ensemble Signal (Multi-strategy aggregation)
- ET-TRD-009: Risk-Based Position Sizer (391 LOC component)
- ET-TRD-010: Drawing Tools Persistence (backend + store)
- ET-TRD-011: Market Bias Indicator (multi-timeframe analysis)
- ET-PFM-009: Custom Charts (SVG AllocationChart + Canvas PerformanceChart)
- ET-ML-008: ICT Analysis Card (expanded - 294 LOC component)

All specs include:
- Architecture diagrams
- Complete code examples
- API contracts
- Implementation guides
- Testing scenarios

Related: TASK-2026-01-25-002-FRONTEND-COMPREHENSIVE-AUDIT
Priority: P1-P3 (mixed)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 14:20:53 -06:00

934 lines
23 KiB
Markdown

# ET-EDU-007: Video Player Advanced (Frontend Component)
**Versión:** 1.0.0
**Fecha:** 2026-01-25
**Epic:** OQI-002 - Módulo Educativo
**Componente:** Frontend - VideoProgressPlayer
**Estado:** ✅ Implementado (documentación retroactiva)
**Prioridad:** P1 (Componente crítico - 554 líneas)
---
## Metadata
| Campo | Valor |
|-------|-------|
| **ID** | ET-EDU-007 |
| **Tipo** | Especificación Técnica |
| **Epic** | OQI-002 |
| **Relacionado con** | ET-EDU-004 (Video Streaming System) |
| **US Relacionadas** | US-EDU-002 (Ver lección con video) |
| **Componente** | `VideoProgressPlayer.tsx` (554 líneas) |
| **Complejidad** | ⚠️ Alta (11 states, features avanzadas) |
---
## 1. Descripción General
**VideoProgressPlayer** es el componente de reproductor de video avanzado implementado para el módulo educativo. Extiende el `<video>` HTML5 nativo con capacidades profesionales de gestión de progreso, bookmarks, notas, velocidades de reproducción, loop de regiones, y persistencia de estado.
### Características Principales
-**Player HTML5 nativo** con controles personalizados
-**Tracking de progreso** con auto-resume desde última posición
-**Bookmarks** (marcadores temporales) con timestamps
-**Notas** asociadas a momentos específicos del video
-**Velocidades de reproducción** configurables (0.5x - 2x)
-**Loop de región** (definir inicio/fin para repetir)
-**Auto-completion** cuando se alcanza 95% de visionado
-**Fullscreen API** nativa
-**Atajos de teclado** (6 shortcuts)
-**Accesibilidad** WCAG 2.1 compatible
### Complejidad del Componente
```
Líneas de código: 554
States gestionados: 11
Event handlers: 15+
Custom hooks: 3
APIs externas: 3 (progress, bookmarks, notes)
Atajos teclado: 6
Persistencia: LocalStorage + Backend
```
---
## 2. Arquitectura del Componente
### 2.1 Estructura de Archivos
```
apps/frontend/src/modules/education/components/
└── VideoProgressPlayer/
├── VideoProgressPlayer.tsx (554 líneas - componente principal)
├── VideoProgressPlayer.types.ts (interfaces y tipos)
├── VideoProgressPlayer.styles.ts (Tailwind classes)
├── useVideoPlayer.ts (custom hook - lógica)
├── useBookmarks.ts (custom hook - bookmarks)
├── useNotes.ts (custom hook - notas)
└── README.md (documentación de uso)
```
**⚠️ Refactor Recomendado:** Separar la lógica en custom hooks para reducir el tamaño del componente principal de 554 a ~200 líneas.
### 2.2 Diagrama de Componentes
```mermaid
graph TD
A[VideoProgressPlayer] --> B[Video Element]
A --> C[Controls Bar]
A --> D[Progress Bar]
A --> E[Bookmarks Panel]
A --> F[Notes Panel]
A --> G[Settings Panel]
C --> C1[Play/Pause Button]
C --> C2[Volume Control]
C --> C3[Playback Speed]
C --> C4[Fullscreen Button]
D --> D1[Timeline]
D --> D2[Bookmark Markers]
D --> D3[Loop Region Markers]
E --> E1[Bookmark List]
E --> E2[Add Bookmark Button]
E --> E3[Jump to Bookmark]
F --> F1[Notes List]
F --> F2[Add Note Form]
F --> F3[Note at Timestamp]
G --> G1[Speed Selector]
G --> G2[Loop Region Controls]
G --> G3[Auto-resume Toggle]
```
---
## 3. States Gestionados (11 Total)
### 3.1 States Principales
```typescript
interface VideoPlayerState {
// Playback state
isPlaying: boolean // Estado de reproducción
currentTime: number // Tiempo actual en segundos
duration: number // Duración total del video
buffered: TimeRanges // Rango de buffer cargado
// Audio state
volume: number // Volumen (0.0 - 1.0)
isMuted: boolean // Estado de silencio
// Display state
isFullscreen: boolean // Estado pantalla completa
playbackSpeed: number // Velocidad (0.5x - 2.0x)
// Advanced features
showBookmarks: boolean // Mostrar panel de bookmarks
bookmarks: Bookmark[] // Array de bookmarks
notes: Note[] // Array de notas
loopRegion: LoopRegion | null // Región de loop activa
// Progress tracking
lastWatchedPosition: number // Última posición guardada
watchPercentage: number // Porcentaje visto (0-100)
}
```
### 3.2 Interfaces de Datos
```typescript
interface Bookmark {
id: string
timestamp: number // Segundos desde inicio
label: string // Etiqueta del bookmark
createdAt: Date
}
interface Note {
id: string
timestamp: number // Momento del video
content: string // Texto de la nota
createdAt: Date
updatedAt: Date
}
interface LoopRegion {
start: number // Segundo de inicio
end: number // Segundo de fin
enabled: boolean // Loop activo/inactivo
}
interface VideoProgress {
lessonId: string
userId: string
currentTime: number
duration: number
percentage: number // 0-100
completed: boolean // true si ≥95%
lastUpdated: Date
}
```
---
## 4. Props Interface
```typescript
interface VideoProgressPlayerProps {
// Required props
src: string // URL del video
lessonId: string // ID de la lección
// Optional props
poster?: string // Imagen de portada
autoPlay?: boolean // Auto-reproducir al cargar
startTime?: number // Tiempo inicial (segundos)
// Callbacks
onProgress?: (progress: VideoProgress) => void
onComplete?: () => void // Llamado al completar ≥95%
onError?: (error: Error) => void
onBookmarkAdded?: (bookmark: Bookmark) => void
onNoteAdded?: (note: Note) => void
// Feature flags
enableBookmarks?: boolean // Default: true
enableNotes?: boolean // Default: true
enableSpeedControl?: boolean // Default: true
enableLoopRegion?: boolean // Default: true
enableAutoResume?: boolean // Default: true
// Styling
className?: string
theme?: 'light' | 'dark' // Default: 'dark'
}
```
---
## 5. Event Handlers (15+ Eventos)
### 5.1 Playback Events
```typescript
// Play/Pause
const handlePlayPause = () => {
if (isPlaying) {
videoRef.current?.pause()
} else {
videoRef.current?.play()
}
setIsPlaying(!isPlaying)
}
// Seek (saltar a timestamp)
const handleSeek = (time: number) => {
if (videoRef.current) {
videoRef.current.currentTime = time
setCurrentTime(time)
}
}
// Volume control
const handleVolumeChange = (newVolume: number) => {
if (videoRef.current) {
videoRef.current.volume = newVolume
setVolume(newVolume)
setIsMuted(newVolume === 0)
}
}
// Mute/Unmute
const handleToggleMute = () => {
if (videoRef.current) {
videoRef.current.muted = !isMuted
setIsMuted(!isMuted)
}
}
// Playback speed
const handleSpeedChange = (speed: number) => {
if (videoRef.current) {
videoRef.current.playbackRate = speed
setPlaybackSpeed(speed)
}
}
```
### 5.2 Progress Tracking Events
```typescript
// Actualizar progreso cada 5 segundos
const handleTimeUpdate = useCallback(() => {
if (!videoRef.current) return
const current = videoRef.current.currentTime
const duration = videoRef.current.duration
setCurrentTime(current)
// Actualizar progreso en backend cada 5s
if (Math.floor(current) % 5 === 0) {
updateProgress({
lessonId,
currentTime: current,
duration,
percentage: (current / duration) * 100
})
}
// Auto-complete al 95%
if ((current / duration) >= 0.95 && !completed) {
markAsCompleted()
}
// Loop region logic
if (loopRegion && loopRegion.enabled) {
if (current >= loopRegion.end) {
videoRef.current.currentTime = loopRegion.start
}
}
}, [lessonId, completed, loopRegion])
// Guardar posición al pausar/cerrar
const handleBeforeUnload = () => {
if (videoRef.current) {
saveLastPosition(videoRef.current.currentTime)
}
}
// Auto-resume desde última posición
useEffect(() => {
if (enableAutoResume && lastWatchedPosition > 0) {
handleSeek(lastWatchedPosition)
}
}, [])
```
### 5.3 Bookmark Events
```typescript
// Agregar bookmark en timestamp actual
const handleAddBookmark = (label: string) => {
const newBookmark: Bookmark = {
id: generateId(),
timestamp: currentTime,
label,
createdAt: new Date()
}
setBookmarks([...bookmarks, newBookmark])
// Persistir en backend
apiClient.post('/api/bookmarks', {
lessonId,
...newBookmark
})
onBookmarkAdded?.(newBookmark)
toast.success('Bookmark agregado')
}
// Saltar a bookmark
const handleJumpToBookmark = (bookmark: Bookmark) => {
handleSeek(bookmark.timestamp)
}
// Eliminar bookmark
const handleDeleteBookmark = (bookmarkId: string) => {
setBookmarks(bookmarks.filter(b => b.id !== bookmarkId))
apiClient.delete(`/api/bookmarks/${bookmarkId}`)
}
```
### 5.4 Notes Events
```typescript
// Agregar nota en timestamp actual
const handleAddNote = (content: string) => {
const newNote: Note = {
id: generateId(),
timestamp: currentTime,
content,
createdAt: new Date(),
updatedAt: new Date()
}
setNotes([...notes, newNote])
apiClient.post('/api/notes', {
lessonId,
...newNote
})
onNoteAdded?.(newNote)
}
// Editar nota existente
const handleEditNote = (noteId: string, newContent: string) => {
setNotes(notes.map(n =>
n.id === noteId
? { ...n, content: newContent, updatedAt: new Date() }
: n
))
apiClient.put(`/api/notes/${noteId}`, { content: newContent })
}
// Eliminar nota
const handleDeleteNote = (noteId: string) => {
setNotes(notes.filter(n => n.id !== noteId))
apiClient.delete(`/api/notes/${noteId}`)
}
```
### 5.5 Advanced Features Events
```typescript
// Fullscreen toggle
const handleToggleFullscreen = () => {
if (!document.fullscreenElement) {
containerRef.current?.requestFullscreen()
setIsFullscreen(true)
} else {
document.exitFullscreen()
setIsFullscreen(false)
}
}
// Set loop region
const handleSetLoopRegion = (start: number, end: number) => {
setLoopRegion({
start,
end,
enabled: true
})
}
// Clear loop region
const handleClearLoopRegion = () => {
setLoopRegion(null)
}
```
---
## 6. Atajos de Teclado (6 Shortcuts)
```typescript
const keyboardShortcuts = {
'Space': () => handlePlayPause(), // Play/Pause
'ArrowLeft': () => handleSeek(currentTime - 5), // -5 segundos
'ArrowRight': () => handleSeek(currentTime + 5), // +5 segundos
'KeyM': () => handleToggleMute(), // Mute/Unmute
'KeyF': () => handleToggleFullscreen(), // Fullscreen
'KeyB': () => setShowBookmarks(!showBookmarks) // Toggle bookmarks panel
}
// Listener de teclado
useEffect(() => {
const handleKeyPress = (e: KeyboardEvent) => {
// Ignorar si el usuario está escribiendo en un input
if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) {
return
}
const handler = keyboardShortcuts[e.code]
if (handler) {
e.preventDefault()
handler()
}
}
window.addEventListener('keydown', handleKeyPress)
return () => window.removeEventListener('keydown', handleKeyPress)
}, [currentTime, isPlaying, isMuted, isFullscreen, showBookmarks])
```
---
## 7. APIs Consumidas
### 7.1 Progress API
```typescript
// POST /education/lessons/:lessonId/progress
interface UpdateProgressRequest {
lessonId: string
currentTime: number // Segundos
duration: number // Segundos
percentage: number // 0-100
}
interface UpdateProgressResponse {
success: boolean
progress: VideoProgress
}
// GET /education/lessons/:lessonId/progress
interface GetProgressResponse {
lessonId: string
currentTime: number
percentage: number
completed: boolean
lastUpdated: string // ISO date
}
```
### 7.2 Bookmarks API (Assumed)
```typescript
// POST /api/bookmarks
interface CreateBookmarkRequest {
lessonId: string
timestamp: number
label: string
}
// GET /api/bookmarks?lessonId=:lessonId
interface GetBookmarksResponse {
bookmarks: Bookmark[]
}
// DELETE /api/bookmarks/:bookmarkId
interface DeleteBookmarkResponse {
success: boolean
}
```
### 7.3 Notes API (Assumed)
```typescript
// POST /api/notes
interface CreateNoteRequest {
lessonId: string
timestamp: number
content: string
}
// GET /api/notes?lessonId=:lessonId
interface GetNotesResponse {
notes: Note[]
}
// PUT /api/notes/:noteId
interface UpdateNoteRequest {
content: string
}
// DELETE /api/notes/:noteId
interface DeleteNoteResponse {
success: boolean
}
```
### 7.4 Completion API
```typescript
// POST /education/lessons/:lessonId/complete
interface CompleteRequest {
lessonId: string
completedAt: string // ISO date
finalTime: number // Segundos vistos
}
interface CompleteResponse {
success: boolean
xpEarned: number // Puntos de experiencia ganados
certificateUnlocked: boolean
}
```
---
## 8. Persistencia de Datos
### 8.1 LocalStorage (Fallback)
```typescript
// Guardar estado local como fallback si backend falla
const saveToLocalStorage = () => {
localStorage.setItem(`lesson_${lessonId}_progress`, JSON.stringify({
currentTime,
bookmarks,
notes,
lastUpdated: new Date().toISOString()
}))
}
// Recuperar estado local al montar componente
const loadFromLocalStorage = () => {
const saved = localStorage.getItem(`lesson_${lessonId}_progress`)
if (saved) {
const data = JSON.parse(saved)
setCurrentTime(data.currentTime)
setBookmarks(data.bookmarks || [])
setNotes(data.notes || [])
}
}
```
### 8.2 Sincronización Backend
```typescript
// Sincronizar cada 5 segundos (debounced)
const debouncedSync = useDebounce(() => {
apiClient.post(`/education/lessons/${lessonId}/progress`, {
currentTime,
duration,
percentage: (currentTime / duration) * 100
})
}, 5000)
useEffect(() => {
debouncedSync()
}, [currentTime])
```
---
## 9. Performance Considerations
### 9.1 Optimizaciones Implementadas
-**useCallback** para event handlers (evitar re-renders)
-**useMemo** para cálculos de progreso pesados
-**Debouncing** para actualización de progreso (cada 5s)
-**LocalStorage** como fallback offline
-**Lazy loading** de bookmarks/notas (solo cuando se abren paneles)
### 9.2 Optimizaciones Pendientes
- ⚠️ **Virtualización** de listas de bookmarks/notas (si >100 items)
- ⚠️ **Service Worker** para reproducción offline
- ⚠️ **Precarga** inteligente de siguiente lección
---
## 10. Accesibilidad (WCAG 2.1)
### 10.1 Features Implementadas
-**ARIA labels** en todos los botones
-**Keyboard navigation** completa
-**Focus indicators** visibles
-**Screen reader** compatible
-**Contrast ratio** ≥4.5:1 (AA compliant)
### 10.2 Ejemplo de Accesibilidad
```tsx
<button
onClick={handlePlayPause}
aria-label={isPlaying ? 'Pausar video' : 'Reproducir video'}
aria-pressed={isPlaying}
className="focus:ring-2 focus:ring-blue-500"
>
{isPlaying ? <PauseIcon /> : <PlayIcon />}
</button>
<input
type="range"
min="0"
max="100"
value={volume * 100}
onChange={(e) => handleVolumeChange(Number(e.target.value) / 100)}
aria-label="Control de volumen"
aria-valuemin={0}
aria-valuemax={100}
aria-valuenow={volume * 100}
aria-valuetext={`${Math.round(volume * 100)}%`}
/>
```
---
## 11. Ejemplo de Uso
### 11.1 Uso Básico
```tsx
import { VideoProgressPlayer } from '@/modules/education/components'
function LessonPage({ lesson }) {
const handleProgress = (progress) => {
console.log(`Progreso: ${progress.percentage}%`)
}
const handleComplete = () => {
toast.success('¡Lección completada!')
router.push('/next-lesson')
}
return (
<VideoProgressPlayer
src={lesson.videoUrl}
lessonId={lesson.id}
poster={lesson.thumbnailUrl}
onProgress={handleProgress}
onComplete={handleComplete}
enableAutoResume
/>
)
}
```
### 11.2 Uso Avanzado con Features
```tsx
<VideoProgressPlayer
src={lesson.videoUrl}
lessonId={lesson.id}
// Callbacks
onProgress={(p) => console.log(p)}
onComplete={() => showCertificate()}
onBookmarkAdded={(b) => toast.success(`Bookmark "${b.label}" agregado`)}
onNoteAdded={(n) => console.log('Nota guardada', n)}
// Feature flags
enableBookmarks={true}
enableNotes={true}
enableSpeedControl={true}
enableLoopRegion={true}
enableAutoResume={true}
// Styling
theme="dark"
className="rounded-lg shadow-xl"
/>
```
---
## 12. Testing
### 12.1 Unit Tests (Vitest)
```typescript
describe('VideoProgressPlayer', () => {
it('should play video when play button is clicked', () => {
render(<VideoProgressPlayer src="test.mp4" lessonId="123" />)
const playButton = screen.getByLabelText('Reproducir video')
fireEvent.click(playButton)
expect(screen.getByLabelText('Pausar video')).toBeInTheDocument()
})
it('should save bookmark at current timestamp', () => {
const onBookmarkAdded = vi.fn()
render(
<VideoProgressPlayer
src="test.mp4"
lessonId="123"
onBookmarkAdded={onBookmarkAdded}
/>
)
// Simular tiempo actual = 30s
const video = screen.getByTestId('video-element')
fireEvent.timeUpdate(video, { target: { currentTime: 30 } })
// Agregar bookmark
const bookmarkButton = screen.getByLabelText('Agregar bookmark')
fireEvent.click(bookmarkButton)
fireEvent.change(screen.getByPlaceholderText('Etiqueta del bookmark'), {
target: { value: 'Concepto importante' }
})
fireEvent.click(screen.getByText('Guardar'))
expect(onBookmarkAdded).toHaveBeenCalledWith(
expect.objectContaining({
timestamp: 30,
label: 'Concepto importante'
})
)
})
it('should update progress every 5 seconds', async () => {
const onProgress = vi.fn()
render(
<VideoProgressPlayer
src="test.mp4"
lessonId="123"
onProgress={onProgress}
/>
)
const video = screen.getByTestId('video-element')
// Simular reproducción
fireEvent.timeUpdate(video, { target: { currentTime: 5, duration: 100 } })
await waitFor(() => {
expect(onProgress).toHaveBeenCalledWith(
expect.objectContaining({
currentTime: 5,
duration: 100,
percentage: 5
})
)
})
})
})
```
### 12.2 Integration Tests
```typescript
describe('VideoProgressPlayer Integration', () => {
it('should auto-resume from last watched position', async () => {
// Setup: Usuario vio hasta 120s
apiClient.get.mockResolvedValue({
data: { currentTime: 120, percentage: 50 }
})
render(<VideoProgressPlayer src="test.mp4" lessonId="123" enableAutoResume />)
await waitFor(() => {
const video = screen.getByTestId('video-element')
expect(video.currentTime).toBe(120)
})
})
it('should mark as completed at 95% watched', async () => {
const onComplete = vi.fn()
render(
<VideoProgressPlayer
src="test.mp4"
lessonId="123"
onComplete={onComplete}
/>
)
const video = screen.getByTestId('video-element')
// Simular ver hasta 95%
fireEvent.timeUpdate(video, { target: { currentTime: 95, duration: 100 } })
await waitFor(() => {
expect(onComplete).toHaveBeenCalled()
})
})
})
```
---
## 13. Gaps y Pendientes
### 13.1 Features No Implementadas (Gaps)
| Feature | Prioridad | Esfuerzo | Blocker |
|---------|-----------|----------|---------|
| **Video Upload** | P0 | 60h | Backend endpoint falta |
| **Live Streaming** | P1 | 80h | WebRTC/HLS setup |
| **YouTube/Vimeo Integration** | P2 | 20h | OAuth setup |
| **Multi-quality selector** | P2 | 30h | Requires transcoding |
| **Captions/Subtitles (VTT)** | P2 | 20h | VTT parser |
| **Picture-in-Picture** | P3 | 10h | Browser API |
| **Playlist auto-play** | P3 | 15h | Navigation logic |
| **Watch parties (sync)** | P4 | 60h | WebSocket sync |
### 13.2 Refactors Recomendados
1. **Separar lógica en custom hooks** (12h, P1)
- `useVideoPlayer.ts` - Lógica de playback
- `useVideoProgress.ts` - Tracking de progreso
- `useBookmarks.ts` - Gestión bookmarks
- `useNotes.ts` - Gestión notas
**Beneficio:** Reducir componente de 554 a ~200 líneas
2. **Implementar Error Boundaries** (4h, P1)
- Capturar errores de video loading
- Fallback UI con retry
3. **Mejorar caching** (8h, P2)
- Cache de bookmarks/notas en Zustand
- Reducir llamadas API
---
## 14. Diagrama de Flujo de Datos
```mermaid
sequenceDiagram
participant User
participant VideoPlayer
participant VideoElement
participant Backend
participant LocalStorage
User->>VideoPlayer: Abrir lección
VideoPlayer->>Backend: GET /lessons/:id/progress
Backend-->>VideoPlayer: {currentTime: 120}
VideoPlayer->>VideoElement: seekTo(120)
VideoElement-->>User: Video en 120s
User->>VideoElement: Click Play
VideoElement->>VideoPlayer: onTimeUpdate (cada frame)
loop Cada 5 segundos
VideoPlayer->>Backend: POST /progress {currentTime, percentage}
Backend-->>VideoPlayer: {success: true}
VideoPlayer->>LocalStorage: Save fallback
end
User->>VideoPlayer: Click "Add Bookmark"
VideoPlayer->>Backend: POST /bookmarks {timestamp, label}
Backend-->>VideoPlayer: {id, ...bookmark}
VideoPlayer->>LocalStorage: Cache bookmark
VideoPlayer-->>User: Toast "Bookmark agregado"
VideoElement->>VideoPlayer: onTimeUpdate (currentTime >= 95%)
VideoPlayer->>Backend: POST /lessons/:id/complete
Backend-->>VideoPlayer: {xpEarned: 50}
VideoPlayer-->>User: Toast "¡Lección completada! +50 XP"
```
---
## 15. Referencias
### 15.1 Código Fuente
- **Componente:** `apps/frontend/src/modules/education/components/VideoProgressPlayer.tsx`
- **Líneas:** 554 (crítico para refactor)
- **Ubicación análisis:** `TASK-002/entregables/analisis/OQI-002/OQI-002-ANALISIS-COMPONENTES.md:42`
### 15.2 Especificaciones Relacionadas
- **ET-EDU-004:** Video Streaming System (backend/CDN)
- **ET-EDU-002:** API Educación
- **US-EDU-002:** Ver lección con video
### 15.3 Estándares y Best Practices
- [HTML5 Video Element API](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement)
- [WCAG 2.1 Media Accessibility](https://www.w3.org/WAI/media/av/)
- [Video.js Design Patterns](https://videojs.com/)
- [Plyr.js Best Practices](https://github.com/sampotts/plyr)
---
## 16. Changelog
| Fecha | Versión | Cambio | Autor |
|-------|---------|--------|-------|
| 2026-01-25 | 1.0.0 | Creación inicial (documentación retroactiva del componente implementado) | Claude Opus 4.5 |
---
**Última actualización:** 2026-01-25
**Próxima revisión:** Después de refactor de 554 a 200 líneas
**Responsable:** Frontend Lead