Skip to main content

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:
  1. Reading your study plan sequence
  2. Finding the next uncompleted blocks
  3. Generating events for the next 14 days
  4. Respecting active days (in weekly mode)
  5. 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
1

Future events with no activity

Deleted and regenerated — this allows the schedule to adapt to your progress
2

Past events or events with time

Preserved — your study history is never lost
3

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

ValueEffectBest For
1One subject per dayDeep focus, long sessions
2Two subjects per dayModerate variety
3Three subjects per day (default)Balanced variety and focus
4-5High subject rotationMaximum coverage, shorter sessions
6+Very high rotationReviewing 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:
  1. When you create/update a plan: After running the planning wizard
    // From logic.js:654
    state.planejamento = plan;
    syncCicloToEventos();
    
  2. When you delete a plan: To remove all auto-events
    // From logic.js:666
    state.planejamento = { ativo: false, ... };
    syncCicloToEventos();
    
  3. When you reorder cycle blocks: To regenerate with new order
    // From logic.js:772
    syncCicloToEventos();
    
  4. 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
}
1

Event creation

A new event is created for today with the block’s target duration
2

Timer start

The timer starts automatically — no need to click “Play”
3

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):\nEx: 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

1

Set realistic materiasPorDia

3-4 subjects per day is ideal for most students. Too many causes fatigue.
2

Let the schedule regenerate

Don’t manually create tons of events — let the auto-scheduling handle it.
3

Mark events as studied promptly

This updates the sequence and shifts the schedule to the next blocks.
4

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:
  1. Open planning wizard
  2. Adjust relevance weights or reorder manually
  3. 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

Build docs developers (and LLMs) love