Automatic Scheduling
Estudo Organizado automatically generates study events based on your planning configuration. This system creates a 14-day rolling schedule that adapts as you mark events as complete, keeping your calendar fresh and relevant.
Event Generation Overview
The scheduling system (implemented in logic.js:705-763) creates events by:
Reading your study plan sequence
Finding the next uncompleted blocks
Generating events for the next 14 days
Respecting active days (in weekly mode)
Cleaning up old auto-generated events
The syncCicloToEventos Function
This is the core scheduling engine:
export function syncCicloToEventos () {
if ( ! state . planejamento ||
! state . planejamento . ativo ||
! state . planejamento . sequencia ||
state . planejamento . sequencia . length === 0 ) return ;
const today = new Date ();
today . setHours ( 0 , 0 , 0 , 0 );
// 1. Delete future auto-generated events (except studied ones)
state . eventos = state . eventos . filter ( e => {
if ( ! e . isAutoGenerated ) return true ; // Keep manual events
if ( e . status === 'estudei' || ( e . tempoAcumulado && e . tempoAcumulado > 0 )) {
return true ; // Keep events with study time
}
const evDate = new Date ( e . data + 'T00:00:00' );
if ( evDate < today ) return true ; // Keep past events
return false ; // Remove future unused auto-events
});
const seq = state . planejamento . sequencia ;
const materiasPorDia = state . config . materiasPorDia || 3 ;
let currentSeqIdx = 0 ;
// Find first uncompleted block
const firstPendentIndex = seq . findIndex ( s => ! s . concluido );
if ( firstPendentIndex !== - 1 ) currentSeqIdx = firstPendentIndex ;
// Generate events for next 14 days
for ( let diasOffset = 0 ; diasOffset < 14 ; diasOffset ++ ) {
const d = new Date ( today );
d . setDate ( d . getDate () + diasOffset );
const dayOfWeek = d . getDay ();
// Skip inactive days in weekly mode
if ( state . planejamento . tipo === 'semanal' &&
state . planejamento . horarios &&
state . planejamento . horarios . diasAtivos ) {
if ( ! state . planejamento . horarios . diasAtivos . includes ( dayOfWeek )) continue ;
}
const dtStr = getLocalDateStr ( d );
// Create N events per day (materiasPorDia)
for ( let m = 0 ; m < materiasPorDia ; m ++ ) {
const seqItem = seq [ currentSeqIdx ];
const discEntry = getDisc ( seqItem . discId );
state . eventos . push ({
id: 'auto_' + dtStr + '_' + currentSeqIdx + '_' + uid (),
titulo: `Estudar ${ discEntry ?. disc . nome || 'Disciplina' } ` ,
data: dtStr ,
duracao: seqItem . minutosAlvo ,
status: 'agendado' ,
tempoAcumulado: 0 ,
tipo: 'conteudo' ,
discId: seqItem . discId ,
assId: null ,
habito: null ,
seqId: seqItem . id ,
criadoEm: new Date (). toISOString (),
isAutoGenerated: true
});
currentSeqIdx = ( currentSeqIdx + 1 ) % seq . length ; // Wrap around
}
}
}
Key Scheduling Concepts
1. Auto-Generated vs. Manual Events
Events have an isAutoGenerated flag:
Auto-generated (true): Created by the scheduling system, can be deleted and regenerated
Manual (false or undefined): Created by the user, never deleted automatically
Only auto-generated events are removed when syncing. Your manually created events are always safe.
2. Event Retention Logic
An auto-generated event is kept if:
✓ It has status: 'estudei' (you marked it as studied)
✓ It has tempoAcumulado > 0 (you spent time on it)
✓ Its date is in the past
Future events with no activity
Deleted and regenerated — this allows the schedule to adapt to your progress
Past events or events with time
Preserved — your study history is never lost
Manual events
Always preserved, regardless of date or status
3. The 14-Day Rolling Window
for ( let diasOffset = 0 ; diasOffset < 14 ; diasOffset ++ ) {
const d = new Date ( today );
d . setDate ( d . getDate () + diasOffset );
// ... create events
}
Why 14 days?
Short enough : Schedule doesn’t become stale
Long enough : You can see your upcoming week and plan ahead
Adaptive : As days pass, new days are added to the end
The 14-day window is a rolling window. Each time you open the app or trigger a sync, events are regenerated for “today + 14 days.”
materiasPorDia Setting
This config controls how many different subjects you study each day.
Configuration Location
const materiasPorDia = state . config . materiasPorDia || 3 ;
Default: 3 subjects per day
Impact on Schedule
With materiasPorDia: 3, each day gets 3 events from sequential blocks:
Example sequence : Math → Portuguese → History → Math → Portuguese → …
Day 1 : Math, Portuguese, History
Day 2 : Math, Portuguese, History
Day 3 : Math, Portuguese, History
With materiasPorDia: 1, each day gets 1 event:
Day 1 : Math
Day 2 : Portuguese
Day 3 : History
Day 4 : Math
Choosing the Right Value
Value Effect Best For 1 One subject per day Deep focus, long sessions 2 Two subjects per day Moderate variety 3 Three subjects per day (default) Balanced variety and focus 4-5 High subject rotation Maximum coverage, shorter sessions 6+ Very high rotation Reviewing many subjects quickly
Lower values (1-2) : Less context switching, deeper focus, but slower progress through all subjects.Higher values (4+) : More context switching, broader daily coverage, but requires more mental energy to switch gears frequently.Most students find 3 to be the sweet spot.
Syncing Cycles to Events
When Sync Happens
The syncCicloToEventos() function is called:
When you create/update a plan : After running the planning wizard
// From logic.js:654
state . planejamento = plan ;
syncCicloToEventos ();
When you delete a plan : To remove all auto-events
// From logic.js:666
state . planejamento = { ativo: false , ... };
syncCicloToEventos ();
When you reorder cycle blocks : To regenerate with new order
// From logic.js:772
syncCicloToEventos ();
When you undo a block : To regenerate from that point
// From logic.js:782
syncCicloToEventos ();
Manual Trigger
You can force a sync by:
Reopening the planning wizard and saving
Reordering cycle blocks
Deleting and recreating the plan
There’s no explicit “Sync Now” button — syncing happens automatically when you modify the plan.
Sequence Index Tracking
Finding the Next Block
const firstPendentIndex = seq . findIndex ( s => ! s . concluido );
if ( firstPendentIndex !== - 1 ) currentSeqIdx = firstPendentIndex ;
The system always starts generating events from the first uncompleted block , ensuring you don’t skip subjects.
Wraparound Behavior
currentSeqIdx = ( currentSeqIdx + 1 ) % seq . length ;
When the index reaches the end of the sequence, it wraps back to 0. This creates the “cycle” effect where subjects repeat infinitely.
Example with 5 blocks :
Index sequence: 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, ...
Event Structure
Each auto-generated event contains:
{
id : 'auto_2026-03-03_5_abc123' , // Unique ID
titulo : 'Estudar Matemática' , // Subject name
data : '2026-03-03' , // Date (YYYY-MM-DD)
duracao : 120 , // Target duration in minutes
status : 'agendado' , // Initial status
tempoAcumulado : 0 , // Tracked time starts at 0
tipo : 'conteudo' , // Event type
discId : 'disc_123' , // Reference to subject
assId : null , // No specific topic initially
habito : null , // No habit linked
seqId : 'seq_abc' , // Reference to sequence block
criadoEm : '2026-03-03T10:30:00.000Z' , // Creation timestamp
isAutoGenerated : true // Marks as auto-generated
}
Important Fields
seqId : Links the event to its sequence block, allowing completion tracking
isAutoGenerated : Enables safe deletion during sync
duracao : Target time from minutosAlvo in the sequence
data : Determines which day the event appears on
Weekly Mode vs. Cycle Mode
Weekly Mode Filtering
if ( state . planejamento . tipo === 'semanal' &&
state . planejamento . horarios &&
state . planejamento . horarios . diasAtivos ) {
if ( ! state . planejamento . horarios . diasAtivos . includes ( dayOfWeek )) continue ;
}
In weekly mode, the scheduler skips inactive days .
Example :
Active days: Monday, Wednesday, Friday
Result: Events only created for Mon/Wed/Fri, none for Tue/Thu/Sat/Sun
Cycle Mode (No Day Filtering)
In cycle mode, the day filter is skipped, but you can still configure diasAtivos for estimation purposes. Events are generated for all days.
Weekly mode is about strict scheduling — “I study Math on Mondays.” If it’s not Monday, no Math.Cycle mode is about flexibility — “I study when I can, following a sequence.” Any day works.
Starting an Event from Sequence
When you click “Start” on a cycle block (see logic.js:674-703):
export function iniciarEtapaPlanejamento ( seqId ) {
const seq = state . planejamento . sequencia . find ( s => s . id === seqId );
if ( ! seq ) return ;
const d = getDisc ( seq . discId );
const evento = {
id: 'ev_' + uid (),
titulo: `Estudar ${ d ?. disc . nome || 'Disciplina' } ` ,
data: todayStr (),
duracao: seq . minutosAlvo ,
status: 'agendado' ,
tempoAcumulado: 0 ,
tipo: 'conteudo' ,
discId: seq . discId ,
assId: null ,
habito: null ,
seqId: seq . id ,
criadoEm: new Date (). toISOString ()
};
state . eventos . push ( evento );
scheduleSave ();
toggleTimer ( evento . id ); // Start timer immediately
navigate ( 'cronometro' ); // Switch to timer view
}
Event creation
A new event is created for today with the block’s target duration
Timer start
The timer starts automatically — no need to click “Play”
Navigation
You’re taken to the Cronômetro view to see the running timer
This is a manual event , not auto-generated. It won’t be deleted during sync.
Editing Block Hours
You can adjust the target hours for individual blocks:
window . editCicloSeqHours = function ( idx ) {
const seqItem = state . planejamento . sequencia [ idx ];
const currentHours = ( seqItem . minutosAlvo / 60 ). toFixed ( 1 );
const novaStr = window . prompt (
`Digite a nova carga horária (em horas): \n Ex: 1 = Uma hora | 1.5 = 1h30min` ,
currentHours
);
if ( novaStr === null ) return ;
const novaHoras = parseFloat ( novaStr . replace ( ',' , '.' ));
if ( isNaN ( novaHoras ) || novaHoras <= 0 ) {
// Show error toast
return ;
}
seqItem . minutosAlvo = Math . round ( novaHoras * 60 );
syncCicloToEventos (); // Regenerate events with new duration
scheduleSave ();
renderCurrentView ();
};
After editing, all future auto-generated events will use the new duration.
Reordering Blocks
You can change the sequence order:
window . moveCicloSeq = function ( idx , dir ) {
const seq = state . planejamento . sequencia ;
if ( idx + dir < 0 || idx + dir >= seq . length ) return ;
// Swap blocks
const temp = seq [ idx ];
seq [ idx ] = seq [ idx + dir ];
seq [ idx + dir ] = temp ;
syncCicloToEventos (); // Regenerate with new order
scheduleSave ();
renderCurrentView ();
};
Reordering triggers a full resync, regenerating all events in the new order.
Undoing a Block
If you mark a block as complete by mistake:
window . desfazerEtapa = function ( seqId ) {
const idx = state . planejamento . sequencia . findIndex ( s => s . id === seqId );
if ( idx > - 1 ) {
state . planejamento . sequencia [ idx ]. concluido = false ;
syncCicloToEventos (); // Regenerate from this block
scheduleSave ();
}
};
Undoing sets concluido: false, and the block becomes the new starting point for scheduling.
Best Practices
Set realistic materiasPorDia
3-4 subjects per day is ideal for most students. Too many causes fatigue.
Let the schedule regenerate
Don’t manually create tons of events — let the auto-scheduling handle it.
Mark events as studied promptly
This updates the sequence and shifts the schedule to the next blocks.
Review the 14-day window
Check your upcoming schedule regularly to ensure it aligns with your availability.
Troubleshooting
Events not appearing
Causes :
No active plan: Check that state.planejamento.ativo === true
Empty sequence: Ensure subjects are selected and weights calculated
All blocks completed: In cycle mode, reset the cycle to start over
Wrong subjects scheduled
Fix : The schedule is based on the sequence order. To change it:
Open planning wizard
Adjust relevance weights or reorder manually
Save to trigger resync
Too many/few events per day
Fix : Adjust materiasPorDia in Settings → Study Configuration
Events on inactive days (weekly mode)
Cause : The day is marked active in diasAtivos
Fix : Edit the plan and uncheck that day
See Also