624 lines
15 KiB
Markdown
624 lines
15 KiB
Markdown
|
|
# 🔧 Guía de Integración Práctica
|
||
|
|
|
||
|
|
## 🎯 Integración Rápida (5 minutos)
|
||
|
|
|
||
|
|
### Paso 1: Inicializar en App.vue
|
||
|
|
|
||
|
|
```vue
|
||
|
|
<script setup lang="ts">
|
||
|
|
import { invoke } from '@tauri-apps/api/core';
|
||
|
|
import { appDataDir } from '@tauri-apps/api/path';
|
||
|
|
import { onMounted } from 'vue';
|
||
|
|
|
||
|
|
// Importar nuevos componentes
|
||
|
|
import ActivityLog from './components/ActivityLog.vue';
|
||
|
|
import ErrorPanel from './components/ErrorPanel.vue';
|
||
|
|
import BackupManager from './components/BackupManager.vue';
|
||
|
|
|
||
|
|
onMounted(async () => {
|
||
|
|
// 1. Obtener directorio de datos
|
||
|
|
const dataDir = await appDataDir();
|
||
|
|
|
||
|
|
// 2. Inicializar managers
|
||
|
|
try {
|
||
|
|
await invoke('init_managers', { appDataDir: dataDir });
|
||
|
|
console.log('✅ Managers inicializados correctamente');
|
||
|
|
} catch (error) {
|
||
|
|
console.error('❌ Error inicializando managers:', error);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
</script>
|
||
|
|
```
|
||
|
|
|
||
|
|
### Paso 2: Añadir a la Navegación
|
||
|
|
|
||
|
|
```vue
|
||
|
|
<script setup lang="ts">
|
||
|
|
const currentView = ref<'editor' | 'activity' | 'errors' | 'backups'>('editor');
|
||
|
|
|
||
|
|
const menuItems = [
|
||
|
|
{ id: 'editor', icon: '📝', label: 'Editor' },
|
||
|
|
{ id: 'activity', icon: '📋', label: 'Actividad', badge: activityCount },
|
||
|
|
{ id: 'errors', icon: '🐛', label: 'Problemas', badge: errorCount },
|
||
|
|
{ id: 'backups', icon: '💾', label: 'Respaldos' },
|
||
|
|
];
|
||
|
|
|
||
|
|
// Contadores
|
||
|
|
const activityCount = ref(0);
|
||
|
|
const errorCount = ref(0);
|
||
|
|
|
||
|
|
// Actualizar contadores
|
||
|
|
watch(currentView, async (view) => {
|
||
|
|
if (view === 'activity') {
|
||
|
|
const logs = await invoke('get_activity_logs');
|
||
|
|
activityCount.value = logs.length;
|
||
|
|
} else if (view === 'errors') {
|
||
|
|
const diagnostics = await invoke('get_diagnostics');
|
||
|
|
errorCount.value = diagnostics.filter(d => d.severity === 'error').length;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<template>
|
||
|
|
<div class="app">
|
||
|
|
<Sidebar
|
||
|
|
:items="menuItems"
|
||
|
|
:current="currentView"
|
||
|
|
@change="currentView = $event"
|
||
|
|
/>
|
||
|
|
|
||
|
|
<div class="main-content">
|
||
|
|
<MonacoEditor v-if="currentView === 'editor'" />
|
||
|
|
<ActivityLog v-if="currentView === 'activity'" />
|
||
|
|
<ErrorPanel v-if="currentView === 'errors'" />
|
||
|
|
<BackupManager v-if="currentView === 'backups'" />
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</template>
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 📋 Caso de Uso 1: Rastrear Ediciones
|
||
|
|
|
||
|
|
### En MonacoEditor.vue
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
import { invoke } from '@tauri-apps/api/core';
|
||
|
|
|
||
|
|
// Referencias
|
||
|
|
const editor = ref<monaco.editor.IStandaloneCodeEditor | null>(null);
|
||
|
|
const currentFile = ref<string>('');
|
||
|
|
const lastSaveTime = ref(0);
|
||
|
|
|
||
|
|
// Detectar cambios
|
||
|
|
editor.value?.onDidChangeModelContent(async (e) => {
|
||
|
|
const content = editor.value!.getValue();
|
||
|
|
const now = Date.now();
|
||
|
|
|
||
|
|
// Registrar edición (debounce de 5 segundos)
|
||
|
|
if (now - lastSaveTime.value > 5000) {
|
||
|
|
await invoke('save_activity_log', {
|
||
|
|
entry: {
|
||
|
|
type: 'edit',
|
||
|
|
action: 'Archivo modificado',
|
||
|
|
file: currentFile.value,
|
||
|
|
lines: content.split('\n').length,
|
||
|
|
details: `${e.changes.length} cambios realizados`
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
lastSaveTime.value = now;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Analizar errores en tiempo real
|
||
|
|
await invoke('analyze_file_diagnostics', {
|
||
|
|
filePath: currentFile.value,
|
||
|
|
content: content
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
// Guardar archivo
|
||
|
|
const saveFile = async () => {
|
||
|
|
const content = editor.value!.getValue();
|
||
|
|
|
||
|
|
try {
|
||
|
|
// 1. Guardar archivo
|
||
|
|
await invoke('write_file_content', {
|
||
|
|
filePath: currentFile.value,
|
||
|
|
content: content
|
||
|
|
});
|
||
|
|
|
||
|
|
// 2. Registrar actividad
|
||
|
|
await invoke('save_activity_log', {
|
||
|
|
entry: {
|
||
|
|
type: 'save',
|
||
|
|
action: 'Archivo guardado',
|
||
|
|
file: currentFile.value,
|
||
|
|
lines: content.split('\n').length,
|
||
|
|
details: 'Guardado exitoso'
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// 3. Limpiar errores previos
|
||
|
|
await invoke('clear_file_diagnostics', {
|
||
|
|
filePath: currentFile.value
|
||
|
|
});
|
||
|
|
|
||
|
|
console.log('✅ Archivo guardado y registrado');
|
||
|
|
} catch (error) {
|
||
|
|
console.error('❌ Error guardando:', error);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// Abrir archivo
|
||
|
|
const openFile = async (filePath: string) => {
|
||
|
|
try {
|
||
|
|
const content = await invoke('read_file_content', { filePath });
|
||
|
|
|
||
|
|
// Actualizar editor
|
||
|
|
editor.value?.setValue(content);
|
||
|
|
currentFile.value = filePath;
|
||
|
|
|
||
|
|
// Registrar apertura
|
||
|
|
await invoke('save_activity_log', {
|
||
|
|
entry: {
|
||
|
|
type: 'open',
|
||
|
|
action: 'Archivo abierto',
|
||
|
|
file: filePath,
|
||
|
|
details: 'Abierto para edición'
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// Analizar errores iniciales
|
||
|
|
await invoke('analyze_file_diagnostics', {
|
||
|
|
filePath: filePath,
|
||
|
|
content: content
|
||
|
|
});
|
||
|
|
} catch (error) {
|
||
|
|
console.error('❌ Error abriendo archivo:', error);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🐛 Caso de Uso 2: Panel de Errores Interactivo
|
||
|
|
|
||
|
|
### En ErrorPanel.vue (uso extendido)
|
||
|
|
|
||
|
|
```vue
|
||
|
|
<script setup lang="ts">
|
||
|
|
import { ref, onMounted } from 'vue';
|
||
|
|
import { invoke } from '@tauri-apps/api/core';
|
||
|
|
|
||
|
|
const errorPanelRef = ref<InstanceType<typeof ErrorPanel> | null>(null);
|
||
|
|
|
||
|
|
// Navegar al error
|
||
|
|
const handleErrorNavigation = async (error: DiagnosticError) => {
|
||
|
|
// 1. Abrir archivo
|
||
|
|
await openFile(error.file);
|
||
|
|
|
||
|
|
// 2. Ir a la línea
|
||
|
|
editor.value?.revealLineInCenter(error.line);
|
||
|
|
editor.value?.setPosition({
|
||
|
|
lineNumber: error.line,
|
||
|
|
column: error.column
|
||
|
|
});
|
||
|
|
|
||
|
|
// 3. Seleccionar código problemático
|
||
|
|
editor.value?.setSelection({
|
||
|
|
startLineNumber: error.line,
|
||
|
|
startColumn: error.column,
|
||
|
|
endLineNumber: error.line,
|
||
|
|
endColumn: error.column + 10
|
||
|
|
});
|
||
|
|
|
||
|
|
// 4. Foco en editor
|
||
|
|
editor.value?.focus();
|
||
|
|
};
|
||
|
|
|
||
|
|
// Quick Fix automático
|
||
|
|
const applyQuickFix = async (error: DiagnosticError) => {
|
||
|
|
const content = editor.value!.getValue();
|
||
|
|
const lines = content.split('\n');
|
||
|
|
|
||
|
|
if (error.code === 'no-console') {
|
||
|
|
// Remover console.log
|
||
|
|
lines[error.line - 1] = lines[error.line - 1].replace(/console\.log\(.*?\);?/, '');
|
||
|
|
} else if (error.code === 'no-var') {
|
||
|
|
// Reemplazar var por const
|
||
|
|
lines[error.line - 1] = lines[error.line - 1].replace(/\bvar\b/, 'const');
|
||
|
|
} else if (error.code === 'eqeqeq') {
|
||
|
|
// Reemplazar == por ===
|
||
|
|
lines[error.line - 1] = lines[error.line - 1].replace(/ == /, ' === ');
|
||
|
|
}
|
||
|
|
|
||
|
|
// Actualizar editor
|
||
|
|
editor.value?.setValue(lines.join('\n'));
|
||
|
|
|
||
|
|
// Remover error de la lista
|
||
|
|
await invoke('clear_file_diagnostics', {
|
||
|
|
filePath: error.file
|
||
|
|
});
|
||
|
|
|
||
|
|
// Re-analizar
|
||
|
|
await invoke('analyze_file_diagnostics', {
|
||
|
|
filePath: error.file,
|
||
|
|
content: lines.join('\n')
|
||
|
|
});
|
||
|
|
};
|
||
|
|
|
||
|
|
// Actualizar cada 5 segundos
|
||
|
|
onMounted(() => {
|
||
|
|
setInterval(async () => {
|
||
|
|
await errorPanelRef.value?.refreshErrors();
|
||
|
|
}, 5000);
|
||
|
|
});
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<template>
|
||
|
|
<ErrorPanel
|
||
|
|
ref="errorPanelRef"
|
||
|
|
@navigate-to-error="handleErrorNavigation"
|
||
|
|
@apply-fix="applyQuickFix"
|
||
|
|
/>
|
||
|
|
</template>
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 💾 Caso de Uso 3: Sistema de Respaldo Inteligente
|
||
|
|
|
||
|
|
### Auto-respaldo en eventos críticos
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
import { invoke } from '@tauri-apps/api/core';
|
||
|
|
|
||
|
|
// Crear respaldo antes de operaciones peligrosas
|
||
|
|
const refactorCommand = async () => {
|
||
|
|
// 1. Crear respaldo de seguridad
|
||
|
|
try {
|
||
|
|
await invoke('create_backup', {
|
||
|
|
name: 'Pre-refactor backup',
|
||
|
|
description: 'Respaldo automático antes de refactorizar comandos',
|
||
|
|
type: 'manual'
|
||
|
|
});
|
||
|
|
console.log('✅ Respaldo de seguridad creado');
|
||
|
|
} catch (error) {
|
||
|
|
console.error('❌ Error creando respaldo:', error);
|
||
|
|
if (!confirm('No se pudo crear respaldo. ¿Continuar de todos modos?')) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 2. Realizar refactorización
|
||
|
|
await performRefactoring();
|
||
|
|
|
||
|
|
// 3. Registrar actividad
|
||
|
|
await invoke('save_activity_log', {
|
||
|
|
entry: {
|
||
|
|
type: 'edit',
|
||
|
|
action: 'Refactorización completada',
|
||
|
|
file: 'múltiples archivos',
|
||
|
|
details: 'Refactorización de comandos exitosa'
|
||
|
|
}
|
||
|
|
});
|
||
|
|
};
|
||
|
|
|
||
|
|
// Respaldo automático cada 5 minutos
|
||
|
|
let backupTimer: number | null = null;
|
||
|
|
|
||
|
|
const startAutoBackup = () => {
|
||
|
|
backupTimer = window.setInterval(async () => {
|
||
|
|
try {
|
||
|
|
await invoke('create_backup', {
|
||
|
|
name: `Auto-backup ${new Date().toLocaleTimeString()}`,
|
||
|
|
type: 'auto'
|
||
|
|
});
|
||
|
|
console.log('✅ Auto-backup creado');
|
||
|
|
} catch (error) {
|
||
|
|
console.error('❌ Error en auto-backup:', error);
|
||
|
|
}
|
||
|
|
}, 5 * 60 * 1000); // 5 minutos
|
||
|
|
};
|
||
|
|
|
||
|
|
const stopAutoBackup = () => {
|
||
|
|
if (backupTimer) {
|
||
|
|
clearInterval(backupTimer);
|
||
|
|
backupTimer = null;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// Restaurar desde respaldo
|
||
|
|
const restoreFromBackup = async (backupId: string) => {
|
||
|
|
if (!confirm('¿Estás seguro? Esto sobrescribirá el código actual.')) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
// 1. Crear respaldo del estado actual antes de restaurar
|
||
|
|
await invoke('create_backup', {
|
||
|
|
name: 'Pre-restore backup',
|
||
|
|
description: 'Estado antes de restaurar desde respaldo',
|
||
|
|
type: 'manual'
|
||
|
|
});
|
||
|
|
|
||
|
|
// 2. Restaurar
|
||
|
|
await invoke('restore_backup', { backupId });
|
||
|
|
|
||
|
|
// 3. Recargar proyecto
|
||
|
|
await reloadProject();
|
||
|
|
|
||
|
|
alert('✅ Proyecto restaurado exitosamente');
|
||
|
|
} catch (error) {
|
||
|
|
console.error('❌ Error restaurando:', error);
|
||
|
|
alert('Error al restaurar respaldo');
|
||
|
|
}
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🔄 Caso de Uso 4: Comparación de Versiones
|
||
|
|
|
||
|
|
### En BackupManager.vue
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
const compareWithBackup = async (backupId: string) => {
|
||
|
|
try {
|
||
|
|
const { current, backup } = await invoke('compare_backup', { backupId });
|
||
|
|
|
||
|
|
// Crear vista de comparación
|
||
|
|
showComparisonModal.value = true;
|
||
|
|
currentContent.value = current;
|
||
|
|
backupContent.value = backup;
|
||
|
|
|
||
|
|
// Calcular diferencias
|
||
|
|
const diff = calculateDiff(current, backup);
|
||
|
|
|
||
|
|
// Mostrar estadísticas
|
||
|
|
console.log(`
|
||
|
|
📊 Estadísticas de cambios:
|
||
|
|
- Líneas añadidas: ${diff.added}
|
||
|
|
- Líneas eliminadas: ${diff.removed}
|
||
|
|
- Líneas modificadas: ${diff.modified}
|
||
|
|
`);
|
||
|
|
} catch (error) {
|
||
|
|
console.error('❌ Error comparando:', error);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// Función para calcular diff simple
|
||
|
|
const calculateDiff = (current: string, backup: string) => {
|
||
|
|
const currentLines = current.split('\n');
|
||
|
|
const backupLines = backup.split('\n');
|
||
|
|
|
||
|
|
let added = 0;
|
||
|
|
let removed = 0;
|
||
|
|
let modified = 0;
|
||
|
|
|
||
|
|
const maxLength = Math.max(currentLines.length, backupLines.length);
|
||
|
|
|
||
|
|
for (let i = 0; i < maxLength; i++) {
|
||
|
|
if (!backupLines[i]) {
|
||
|
|
added++;
|
||
|
|
} else if (!currentLines[i]) {
|
||
|
|
removed++;
|
||
|
|
} else if (currentLines[i] !== backupLines[i]) {
|
||
|
|
modified++;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return { added, removed, modified };
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🎨 Caso de Uso 5: Notificaciones y Feedback
|
||
|
|
|
||
|
|
### Sistema de notificaciones
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// Crear componente de notificación
|
||
|
|
const showNotification = (type: 'success' | 'error' | 'info', message: string) => {
|
||
|
|
const notification = {
|
||
|
|
id: Date.now(),
|
||
|
|
type,
|
||
|
|
message,
|
||
|
|
timestamp: Date.now()
|
||
|
|
};
|
||
|
|
|
||
|
|
notifications.value.push(notification);
|
||
|
|
|
||
|
|
// Auto-remover después de 3 segundos
|
||
|
|
setTimeout(() => {
|
||
|
|
notifications.value = notifications.value.filter(n => n.id !== notification.id);
|
||
|
|
}, 3000);
|
||
|
|
};
|
||
|
|
|
||
|
|
// Usar en operaciones
|
||
|
|
const saveWithNotification = async () => {
|
||
|
|
try {
|
||
|
|
await saveFile();
|
||
|
|
await invoke('save_activity_log', { entry: {...} });
|
||
|
|
showNotification('success', '✅ Archivo guardado correctamente');
|
||
|
|
} catch (error) {
|
||
|
|
showNotification('error', '❌ Error guardando archivo');
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const backupWithNotification = async () => {
|
||
|
|
try {
|
||
|
|
await invoke('create_backup', { type: 'manual' });
|
||
|
|
showNotification('success', '✅ Respaldo creado exitosamente');
|
||
|
|
} catch (error) {
|
||
|
|
showNotification('error', '❌ Error creando respaldo');
|
||
|
|
}
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🔍 Caso de Uso 6: Búsqueda en Activity Log
|
||
|
|
|
||
|
|
### Filtrado avanzado
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// En ActivityLog.vue
|
||
|
|
const searchTerm = ref('');
|
||
|
|
const dateRange = ref<[Date, Date] | null>(null);
|
||
|
|
const selectedTypes = ref<string[]>(['all']);
|
||
|
|
|
||
|
|
const filteredLogs = computed(() => {
|
||
|
|
let result = logs.value;
|
||
|
|
|
||
|
|
// Filtrar por tipo
|
||
|
|
if (!selectedTypes.value.includes('all')) {
|
||
|
|
result = result.filter(log => selectedTypes.value.includes(log.type));
|
||
|
|
}
|
||
|
|
|
||
|
|
// Filtrar por búsqueda
|
||
|
|
if (searchTerm.value) {
|
||
|
|
const term = searchTerm.value.toLowerCase();
|
||
|
|
result = result.filter(log =>
|
||
|
|
log.action.toLowerCase().includes(term) ||
|
||
|
|
log.file.toLowerCase().includes(term) ||
|
||
|
|
log.details?.toLowerCase().includes(term)
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Filtrar por rango de fechas
|
||
|
|
if (dateRange.value) {
|
||
|
|
const [start, end] = dateRange.value;
|
||
|
|
result = result.filter(log => {
|
||
|
|
const date = new Date(log.timestamp);
|
||
|
|
return date >= start && date <= end;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
return result;
|
||
|
|
});
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 📊 Caso de Uso 7: Dashboard de Estadísticas
|
||
|
|
|
||
|
|
### Crear vista de resumen
|
||
|
|
|
||
|
|
```vue
|
||
|
|
<script setup lang="ts">
|
||
|
|
const stats = ref({
|
||
|
|
totalEdits: 0,
|
||
|
|
totalSaves: 0,
|
||
|
|
totalErrors: 0,
|
||
|
|
totalBackups: 0,
|
||
|
|
mostEditedFiles: [] as Array<{ file: string; count: number }>,
|
||
|
|
errorDistribution: {} as Record<string, number>
|
||
|
|
});
|
||
|
|
|
||
|
|
const updateStats = async () => {
|
||
|
|
// Obtener logs
|
||
|
|
const logs = await invoke('get_activity_logs');
|
||
|
|
|
||
|
|
// Contar por tipo
|
||
|
|
stats.value.totalEdits = logs.filter(l => l.type === 'edit').length;
|
||
|
|
stats.value.totalSaves = logs.filter(l => l.type === 'save').length;
|
||
|
|
|
||
|
|
// Archivos más editados
|
||
|
|
const fileCount: Record<string, number> = {};
|
||
|
|
logs.forEach(log => {
|
||
|
|
fileCount[log.file] = (fileCount[log.file] || 0) + 1;
|
||
|
|
});
|
||
|
|
|
||
|
|
stats.value.mostEditedFiles = Object.entries(fileCount)
|
||
|
|
.sort((a, b) => b[1] - a[1])
|
||
|
|
.slice(0, 5)
|
||
|
|
.map(([file, count]) => ({ file, count }));
|
||
|
|
|
||
|
|
// Obtener errores
|
||
|
|
const errors = await invoke('get_diagnostics');
|
||
|
|
stats.value.totalErrors = errors.length;
|
||
|
|
|
||
|
|
// Distribución de errores
|
||
|
|
errors.forEach(error => {
|
||
|
|
const code = error.code || 'unknown';
|
||
|
|
stats.value.errorDistribution[code] =
|
||
|
|
(stats.value.errorDistribution[code] || 0) + 1;
|
||
|
|
});
|
||
|
|
|
||
|
|
// Obtener respaldos
|
||
|
|
const backups = await invoke('get_backups');
|
||
|
|
stats.value.totalBackups = backups.length;
|
||
|
|
};
|
||
|
|
|
||
|
|
onMounted(() => {
|
||
|
|
updateStats();
|
||
|
|
// Actualizar cada minuto
|
||
|
|
setInterval(updateStats, 60000);
|
||
|
|
});
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<template>
|
||
|
|
<div class="dashboard">
|
||
|
|
<h2>📊 Estadísticas de Desarrollo</h2>
|
||
|
|
|
||
|
|
<div class="stats-grid">
|
||
|
|
<div class="stat-card">
|
||
|
|
<span class="stat-icon">✏️</span>
|
||
|
|
<span class="stat-value">{{ stats.totalEdits }}</span>
|
||
|
|
<span class="stat-label">Ediciones</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="stat-card">
|
||
|
|
<span class="stat-icon">💾</span>
|
||
|
|
<span class="stat-value">{{ stats.totalSaves }}</span>
|
||
|
|
<span class="stat-label">Guardados</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="stat-card">
|
||
|
|
<span class="stat-icon">🐛</span>
|
||
|
|
<span class="stat-value">{{ stats.totalErrors }}</span>
|
||
|
|
<span class="stat-label">Errores</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="stat-card">
|
||
|
|
<span class="stat-icon">💾</span>
|
||
|
|
<span class="stat-value">{{ stats.totalBackups }}</span>
|
||
|
|
<span class="stat-label">Respaldos</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="most-edited">
|
||
|
|
<h3>🔥 Archivos Más Editados</h3>
|
||
|
|
<ul>
|
||
|
|
<li v-for="item in stats.mostEditedFiles" :key="item.file">
|
||
|
|
{{ item.file }} <span class="count">({{ item.count }} ediciones)</span>
|
||
|
|
</li>
|
||
|
|
</ul>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</template>
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## ✅ Checklist de Implementación
|
||
|
|
|
||
|
|
- [ ] Inicializar managers en App.vue
|
||
|
|
- [ ] Añadir componentes al router/navigation
|
||
|
|
- [ ] Integrar activity log en MonacoEditor
|
||
|
|
- [ ] Configurar análisis de errores en tiempo real
|
||
|
|
- [ ] Activar auto-respaldo cada 5 minutos
|
||
|
|
- [ ] Añadir notificaciones de feedback
|
||
|
|
- [ ] Crear atajos de teclado
|
||
|
|
- [ ] Probar restauración de respaldos
|
||
|
|
- [ ] Verificar rendimiento con proyecto grande
|
||
|
|
- [ ] Documentar configuración personalizada
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
**¡Listo para implementar! 🚀**
|