mejora semanal
This commit is contained in:
175
content.js
175
content.js
@@ -195,13 +195,22 @@
|
|||||||
data.weeklyResetText = resetText;
|
data.weeklyResetText = resetText;
|
||||||
|
|
||||||
// Parsear "Se restablece vie, 4:59" para obtener día y hora
|
// Parsear "Se restablece vie, 4:59" para obtener día y hora
|
||||||
const resetMatch = resetText.match(/Se restablece\s+(\w+),?\s*([\d:]+)/i);
|
const resetMatch = resetText.match(/Se restablece\s+([a-záéíóúñü]+),?\s*([\d:]+)/i);
|
||||||
if (resetMatch) {
|
if (resetMatch) {
|
||||||
const dayAbbr = resetMatch[1].toLowerCase();
|
const dayAbbr = resetMatch[1].toLowerCase();
|
||||||
data.resetHour = resetMatch[2];
|
data.resetHour = resetMatch[2];
|
||||||
|
|
||||||
// Encontrar el índice del día
|
// Encontrar el índice del día (comparar primeras 3 letras sin acentos)
|
||||||
data.resetDayIndex = DAYS_ES.findIndex(d => d === dayAbbr || d.startsWith(dayAbbr.substring(0, 3)));
|
const normalize = s => s.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
|
||||||
|
const dayAbbrNorm = normalize(dayAbbr);
|
||||||
|
data.resetDayIndex = DAYS_ES.findIndex(d => {
|
||||||
|
const dNorm = normalize(d);
|
||||||
|
return dNorm === dayAbbrNorm || dNorm.startsWith(dayAbbrNorm.substring(0, 3)) || dayAbbrNorm.startsWith(dNorm.substring(0, 3));
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('[Claude Usage Tracker] Reset parsed:', dayAbbr, '->', data.resetDayIndex, 'hour:', data.resetHour);
|
||||||
|
} else {
|
||||||
|
console.log('[Claude Usage Tracker] Reset text no match:', resetText);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -236,6 +245,7 @@
|
|||||||
/**
|
/**
|
||||||
* Calcula el desglose diario basado en los datos reales
|
* Calcula el desglose diario basado en los datos reales
|
||||||
* Usa HORAS transcurridas para mayor precisión
|
* Usa HORAS transcurridas para mayor precisión
|
||||||
|
* Divide el día de reinicio en dos partes: inicio (después del reinicio) y fin (antes del reinicio)
|
||||||
*/
|
*/
|
||||||
function calculateDailyUsage(pageData) {
|
function calculateDailyUsage(pageData) {
|
||||||
const { weeklyUsage, resetDayIndex, resetHour } = pageData;
|
const { weeklyUsage, resetDayIndex, resetHour } = pageData;
|
||||||
@@ -246,14 +256,16 @@
|
|||||||
|
|
||||||
// Calcular el momento exacto del último reinicio
|
// Calcular el momento exacto del último reinicio
|
||||||
const [resetH, resetM] = (resetHour || '5:00').split(':').map(Number);
|
const [resetH, resetM] = (resetHour || '5:00').split(':').map(Number);
|
||||||
|
const resetHourDecimal = resetH + (resetM || 0) / 60;
|
||||||
|
|
||||||
// Encontrar la fecha del último reinicio
|
// Encontrar la fecha del último reinicio (usar effectiveResetDayIndex más abajo)
|
||||||
|
const effectiveResetIdx = resetDayIndex >= 0 ? resetDayIndex : 5;
|
||||||
let lastResetDate = new Date(now);
|
let lastResetDate = new Date(now);
|
||||||
let daysSinceReset = todayIndex - resetDayIndex;
|
let daysSinceReset = todayIndex - effectiveResetIdx;
|
||||||
if (daysSinceReset < 0) daysSinceReset += 7;
|
if (daysSinceReset < 0) daysSinceReset += 7;
|
||||||
if (daysSinceReset === 0) {
|
if (daysSinceReset === 0) {
|
||||||
// Es el día de reinicio, ¿ya pasó la hora?
|
// Es el día de reinicio, ¿ya pasó la hora?
|
||||||
if (now.getHours() < resetH || (now.getHours() === resetH && now.getMinutes() < resetM)) {
|
if (currentHour < resetHourDecimal) {
|
||||||
daysSinceReset = 7; // Aún no ha reiniciado, usar el de la semana pasada
|
daysSinceReset = 7; // Aún no ha reiniciado, usar el de la semana pasada
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -279,88 +291,126 @@
|
|||||||
const avgUsagePerHour = hoursElapsed > 0 ? weeklyUsage / hoursElapsed : 0;
|
const avgUsagePerHour = hoursElapsed > 0 ? weeklyUsage / hoursElapsed : 0;
|
||||||
const idealUsagePerHour = 100 / totalWeekHours;
|
const idealUsagePerHour = 100 / totalWeekHours;
|
||||||
|
|
||||||
// La semana empieza el mismo día del reinicio (ej: viernes)
|
// Nombre del día de reinicio (con fallback, reutilizar effectiveResetIdx)
|
||||||
// El primer día es el día del reinicio, desde la hora de reinicio
|
const effectiveResetDayIndex = effectiveResetIdx;
|
||||||
const startDayIndex = resetDayIndex;
|
const resetDayName = DAYS_ES_DISPLAY[effectiveResetDayIndex] || 'Vie';
|
||||||
|
const resetDayFullName = DAYS_FULL_ES[effectiveResetDayIndex] || 'Viernes';
|
||||||
// Calcular la posición del día actual dentro de la semana de facturación
|
|
||||||
// Posición 0 = día del reinicio, posición 1 = día siguiente, etc.
|
|
||||||
let todayPositionInWeek = todayIndex - resetDayIndex;
|
|
||||||
if (todayPositionInWeek < 0) todayPositionInWeek += 7;
|
|
||||||
|
|
||||||
const dailyBreakdown = [];
|
const dailyBreakdown = [];
|
||||||
|
|
||||||
for (let i = 0; i < 7; i++) {
|
// Estructura: 8 segmentos
|
||||||
const dayIndex = (startDayIndex + i) % 7;
|
// 0: Día reinicio (inicio) - desde resetHour hasta 24:00
|
||||||
const isToday = (i === todayPositionInWeek);
|
// 1-6: Días completos (sáb, dom, lun, mar, mié, jue si reinicio es viernes)
|
||||||
const isPast = (i < todayPositionInWeek);
|
// 7: Día reinicio (fin) - desde 00:00 hasta resetHour
|
||||||
const isFuture = (i > todayPositionInWeek);
|
|
||||||
|
|
||||||
// Calcular cuántas horas de este día han transcurrido
|
for (let i = 0; i < 8; i++) {
|
||||||
|
let dayIndex, dayName, dayFullName, dayTotalHours, isResetDayStart, isResetDayEnd;
|
||||||
|
|
||||||
|
if (i === 0) {
|
||||||
|
// Primer segmento: día de reinicio (parte después del reinicio)
|
||||||
|
dayIndex = effectiveResetDayIndex;
|
||||||
|
dayName = resetDayName;
|
||||||
|
dayFullName = resetDayFullName + ' (inicio)';
|
||||||
|
dayTotalHours = 24 - resetHourDecimal;
|
||||||
|
isResetDayStart = true;
|
||||||
|
isResetDayEnd = false;
|
||||||
|
} else if (i === 7) {
|
||||||
|
// Último segmento: día de reinicio (parte antes del reinicio)
|
||||||
|
dayIndex = effectiveResetDayIndex;
|
||||||
|
dayName = resetDayName;
|
||||||
|
dayFullName = resetDayFullName + ' (fin)';
|
||||||
|
dayTotalHours = resetHourDecimal;
|
||||||
|
isResetDayStart = false;
|
||||||
|
isResetDayEnd = true;
|
||||||
|
} else {
|
||||||
|
// Días intermedios (completos)
|
||||||
|
dayIndex = (effectiveResetDayIndex + i) % 7;
|
||||||
|
dayName = DAYS_ES_DISPLAY[dayIndex];
|
||||||
|
dayFullName = DAYS_FULL_ES[dayIndex];
|
||||||
|
dayTotalHours = 24;
|
||||||
|
isResetDayStart = false;
|
||||||
|
isResetDayEnd = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calcular posición del día actual
|
||||||
|
let segmentPosition;
|
||||||
|
if (todayIndex === effectiveResetDayIndex) {
|
||||||
|
// Hoy es el día de reinicio
|
||||||
|
if (currentHour >= resetHourDecimal) {
|
||||||
|
segmentPosition = 0; // Estamos en la parte de inicio
|
||||||
|
} else {
|
||||||
|
segmentPosition = 7; // Estamos en la parte de fin (antes del reinicio)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Día normal
|
||||||
|
let pos = todayIndex - effectiveResetDayIndex;
|
||||||
|
if (pos <= 0) pos += 7;
|
||||||
|
segmentPosition = pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isToday = (i === segmentPosition);
|
||||||
|
const isPast = (i < segmentPosition);
|
||||||
|
const isFuture = (i > segmentPosition);
|
||||||
|
|
||||||
|
// Calcular horas transcurridas en este segmento
|
||||||
let dayHoursElapsed = 0;
|
let dayHoursElapsed = 0;
|
||||||
|
|
||||||
if (isPast) {
|
if (isPast) {
|
||||||
// Día completo ya pasado
|
dayHoursElapsed = dayTotalHours;
|
||||||
if (i === 0) {
|
|
||||||
// Primer día (día del reinicio): desde hora de reinicio hasta medianoche
|
|
||||||
dayHoursElapsed = 24 - (resetH + (resetM || 0) / 60);
|
|
||||||
} else {
|
|
||||||
dayHoursElapsed = 24;
|
|
||||||
}
|
|
||||||
} else if (isToday) {
|
} else if (isToday) {
|
||||||
if (i === 0) {
|
if (i === 0) {
|
||||||
// Hoy es el día del reinicio: desde hora de reinicio hasta ahora
|
// Hoy es el día de reinicio, parte inicio
|
||||||
dayHoursElapsed = currentHour - (resetH + (resetM || 0) / 60);
|
dayHoursElapsed = currentHour - resetHourDecimal;
|
||||||
if (dayHoursElapsed < 0) dayHoursElapsed = 0;
|
if (dayHoursElapsed < 0) dayHoursElapsed = 0;
|
||||||
|
} else if (i === 7) {
|
||||||
|
// Hoy es el día de reinicio, parte fin
|
||||||
|
dayHoursElapsed = currentHour;
|
||||||
} else {
|
} else {
|
||||||
// Día actual normal: desde medianoche hasta ahora
|
// Día normal
|
||||||
dayHoursElapsed = currentHour;
|
dayHoursElapsed = currentHour;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Días futuros: dayHoursElapsed = 0
|
// Días futuros: dayHoursElapsed = 0
|
||||||
|
|
||||||
// Calcular horas totales del día (para el porcentaje de llenado)
|
// Porcentaje de llenado del segmento (0-100%)
|
||||||
let dayTotalHours = 24;
|
|
||||||
if (i === 0) {
|
|
||||||
// Primer día: solo cuenta desde la hora de reinicio
|
|
||||||
dayTotalHours = 24 - (resetH + (resetM || 0) / 60);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Porcentaje de llenado del día (0-100%)
|
|
||||||
const dayFillPercent = dayTotalHours > 0 ? (dayHoursElapsed / dayTotalHours) * 100 : 0;
|
const dayFillPercent = dayTotalHours > 0 ? (dayHoursElapsed / dayTotalHours) * 100 : 0;
|
||||||
|
|
||||||
// Uso asignado a este día (promedio distribuido)
|
// Uso asignado a este segmento (promedio distribuido)
|
||||||
let usage = 0;
|
let usage = 0;
|
||||||
if (dayHoursElapsed > 0 && hoursElapsed > 0) {
|
if (dayHoursElapsed > 0 && hoursElapsed > 0) {
|
||||||
// Distribuir el uso total proporcionalmente a las horas transcurridas
|
|
||||||
usage = (dayHoursElapsed / hoursElapsed) * weeklyUsage;
|
usage = (dayHoursElapsed / hoursElapsed) * weeklyUsage;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Estado basado en si el uso del día está dentro del ideal
|
// Estado basado en si el uso está dentro del ideal
|
||||||
let status = 'pending';
|
let status = 'pending';
|
||||||
if (dayHoursElapsed > 0) {
|
if (dayHoursElapsed > 0) {
|
||||||
const idealForThisDay = (dayHoursElapsed / 24) * idealDailyPercent;
|
const idealForThisSegment = (dayHoursElapsed / totalWeekHours) * 100;
|
||||||
status = usage <= idealForThisDay * 1.1 ? 'good' :
|
const actualForThisSegment = usage;
|
||||||
usage <= idealForThisDay * 1.5 ? 'warning' : 'danger';
|
status = actualForThisSegment <= idealForThisSegment * 1.1 ? 'good' :
|
||||||
|
actualForThisSegment <= idealForThisSegment * 1.5 ? 'warning' : 'danger';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Offset para el primer día (horas antes del reinicio que no cuentan)
|
// Offset visual (solo para el segmento de inicio del día de reinicio)
|
||||||
const dayStartOffsetPercent = (i === 0) ? ((resetH + (resetM || 0) / 60) / 24) * 100 : 0;
|
const dayStartOffsetPercent = isResetDayStart ? (resetHourDecimal / 24) * 100 : 0;
|
||||||
|
// Offset final (solo para el segmento de fin del día de reinicio)
|
||||||
|
const dayEndOffsetPercent = isResetDayEnd ? ((24 - resetHourDecimal) / 24) * 100 : 0;
|
||||||
|
|
||||||
dailyBreakdown.push({
|
dailyBreakdown.push({
|
||||||
dayIndex,
|
dayIndex,
|
||||||
dayName: DAYS_ES_DISPLAY[dayIndex],
|
dayName,
|
||||||
dayFullName: DAYS_FULL_ES[dayIndex],
|
dayFullName,
|
||||||
isPast,
|
isPast,
|
||||||
isToday,
|
isToday,
|
||||||
isFuture,
|
isFuture,
|
||||||
isResetDay: i === 0,
|
isResetDayStart,
|
||||||
|
isResetDayEnd,
|
||||||
usage,
|
usage,
|
||||||
idealUsage: idealDailyPercent,
|
idealUsage: (dayTotalHours / totalWeekHours) * 100,
|
||||||
dayFillPercent, // % del día transcurrido (para llenar la barra)
|
dayFillPercent,
|
||||||
dayHoursElapsed,
|
dayHoursElapsed,
|
||||||
dayTotalHours,
|
dayTotalHours,
|
||||||
dayStartOffsetPercent, // % del día antes del reinicio (hueco a la izquierda)
|
dayStartOffsetPercent,
|
||||||
|
dayEndOffsetPercent,
|
||||||
status
|
status
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -410,7 +460,7 @@
|
|||||||
container.id = 'claude-usage-tracker';
|
container.id = 'claude-usage-tracker';
|
||||||
|
|
||||||
const usagePercent = pageData.weeklyUsage;
|
const usagePercent = pageData.weeklyUsage;
|
||||||
const resetDayName = DAYS_FULL_ES[pageData.resetDayIndex] || 'próximo reinicio';
|
const resetDayName = DAYS_FULL_ES[pageData.resetDayIndex >= 0 ? pageData.resetDayIndex : 5] || 'próximo reinicio';
|
||||||
const boostStatus = getBoostStatus();
|
const boostStatus = getBoostStatus();
|
||||||
|
|
||||||
container.innerHTML = `
|
container.innerHTML = `
|
||||||
@@ -434,21 +484,22 @@
|
|||||||
|
|
||||||
<div class="weekly-progress-container">
|
<div class="weekly-progress-container">
|
||||||
${dailyData.dailyBreakdown.map(day => `
|
${dailyData.dailyBreakdown.map(day => `
|
||||||
<div class="day-segment ${day.isToday ? 'today' : ''} ${day.isFuture ? 'future' : ''} ${day.isResetDay ? 'reset-day' : ''}">
|
<div class="day-segment ${day.isToday ? 'today' : ''} ${day.isFuture ? 'future' : ''} ${day.isResetDayStart ? 'reset-day-start' : ''} ${day.isResetDayEnd ? 'reset-day-end' : ''}">
|
||||||
<div class="day-tooltip">
|
<div class="day-tooltip">
|
||||||
${day.dayFullName}: ${day.usage.toFixed(1)}% usado
|
${day.dayFullName}: ${day.usage.toFixed(1)}% usado (${day.dayTotalHours.toFixed(1)}h)
|
||||||
${day.isToday ? ` (${day.dayHoursElapsed.toFixed(1)}h transcurridas)` : day.isPast ? ' (completo)' : ''}
|
${day.isToday ? ` — ${day.dayHoursElapsed.toFixed(1)}h transcurridas` : day.isPast ? ' — completo' : ''}
|
||||||
${day.isResetDay ? `<br>Reinicio a las ${pageData.resetHour}` : ''}
|
${day.isResetDayStart ? `<br>🔄 Desde reinicio (${pageData.resetHour})` : ''}
|
||||||
<br>Ideal para ${day.dayHoursElapsed.toFixed(0)}h: ${((day.dayHoursElapsed / 24) * day.idealUsage).toFixed(1)}%
|
${day.isResetDayEnd ? `<br>⏳ Hasta reinicio (${pageData.resetHour})` : ''}
|
||||||
</div>
|
</div>
|
||||||
<div class="day-bar">
|
<div class="day-bar">
|
||||||
${day.isResetDay ? `<div class="day-bar-offset" style="width: ${day.dayStartOffsetPercent}%"></div>` : ''}
|
${day.isResetDayStart ? `<div class="day-bar-offset start" style="width: ${day.dayStartOffsetPercent}%"></div>` : ''}
|
||||||
<div class="day-bar-bg" style="left: ${day.dayStartOffsetPercent}%; width: ${(100 - day.dayStartOffsetPercent) * day.dayFillPercent / 100}%"></div>
|
${day.isResetDayEnd ? `<div class="day-bar-offset end" style="left: ${100 - day.dayEndOffsetPercent}%; width: ${day.dayEndOffsetPercent}%"></div>` : ''}
|
||||||
|
<div class="day-bar-bg" style="left: ${day.dayStartOffsetPercent}%; width: ${day.dayFillPercent * (100 - day.dayStartOffsetPercent - day.dayEndOffsetPercent) / 100}%"></div>
|
||||||
<div class="day-bar-fill ${day.status === 'good' ? 'under-budget' : day.status === 'warning' ? 'on-track' : day.status === 'danger' ? 'over-budget' : ''}"
|
<div class="day-bar-fill ${day.status === 'good' ? 'under-budget' : day.status === 'warning' ? 'on-track' : day.status === 'danger' ? 'over-budget' : ''}"
|
||||||
style="left: ${day.dayStartOffsetPercent}%; width: ${(100 - day.dayStartOffsetPercent) * day.dayFillPercent / 100}%">
|
style="left: ${day.dayStartOffsetPercent}%; width: ${day.dayFillPercent * (100 - day.dayStartOffsetPercent - day.dayEndOffsetPercent) / 100}%">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span class="day-label">${day.dayName}</span>
|
<span class="day-label">${day.isResetDayStart ? day.dayName + '↓' : day.isResetDayEnd ? day.dayName + '↑' : day.dayName}</span>
|
||||||
</div>
|
</div>
|
||||||
`).join('')}
|
`).join('')}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
15
styles.css
15
styles.css
@@ -87,9 +87,18 @@
|
|||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.day-segment.reset-day .day-label::after {
|
.day-segment.reset-day-start .day-label {
|
||||||
content: ' 🔄';
|
color: #10b981;
|
||||||
font-size: 8px;
|
}
|
||||||
|
|
||||||
|
.day-segment.reset-day-end .day-label {
|
||||||
|
color: #f59e0b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.day-bar-offset.end {
|
||||||
|
left: auto;
|
||||||
|
right: 0;
|
||||||
|
border-radius: 0 4px 4px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.day-bar-fill.under-budget {
|
.day-bar-fill.under-budget {
|
||||||
|
|||||||
Reference in New Issue
Block a user