Index: backend/src/main/java/com/trekr/backend/config/SecurityConfig.java
===================================================================
--- backend/src/main/java/com/trekr/backend/config/SecurityConfig.java	(revision 8449b9cc520ba82bb2945db031d4b9973a038e59)
+++ backend/src/main/java/com/trekr/backend/config/SecurityConfig.java	(revision 4f85b41e4eaa6d668912791fb3cff6efbe7dc1a4)
@@ -50,6 +50,7 @@
                 "http://localhost:5173",
                 "http://127.0.0.1:5173"));
-        config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
-        config.setAllowedHeaders(List.of("Authorization", "Content-Type"));
+        config.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
+        config.setAllowedHeaders(List.of("Authorization", "Content-Type", "Accept", "Origin", "X-Requested-With"));
+        config.setAllowCredentials(true);
 
         UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
Index: backend/src/main/java/com/trekr/backend/controller/DisciplineController.java
===================================================================
--- backend/src/main/java/com/trekr/backend/controller/DisciplineController.java	(revision 4f85b41e4eaa6d668912791fb3cff6efbe7dc1a4)
+++ backend/src/main/java/com/trekr/backend/controller/DisciplineController.java	(revision 4f85b41e4eaa6d668912791fb3cff6efbe7dc1a4)
@@ -0,0 +1,122 @@
+package com.trekr.backend.controller;
+
+import com.trekr.backend.dto.discipline.CreateTaskRequest;
+import com.trekr.backend.dto.discipline.ComputeDailyCompletionResponse;
+import com.trekr.backend.dto.discipline.DailyCompletionsResponse;
+import com.trekr.backend.dto.discipline.TaskDto;
+import com.trekr.backend.dto.discipline.TasksResponse;
+import com.trekr.backend.dto.discipline.TrackingStatusResponse;
+import com.trekr.backend.dto.discipline.UpdateTaskFinishedRequest;
+import com.trekr.backend.dto.discipline.UpdateTaskRequest;
+import com.trekr.backend.security.UserPrincipal;
+import com.trekr.backend.service.DisciplineService;
+import jakarta.validation.Valid;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.core.Authentication;
+import org.springframework.web.bind.annotation.*;
+
+import java.time.LocalDate;
+
+@RestController
+@RequestMapping("/api/discipline")
+public class DisciplineController {
+
+    private final DisciplineService disciplineService;
+
+    public DisciplineController(DisciplineService disciplineService) {
+        this.disciplineService = disciplineService;
+    }
+
+    @GetMapping("/status")
+    public TrackingStatusResponse status(Authentication authentication) {
+        UserPrincipal principal = (UserPrincipal) authentication.getPrincipal();
+        boolean tracking = disciplineService.isTracking(principal.getUserId());
+        return new TrackingStatusResponse(tracking);
+    }
+
+    @PostMapping("/start")
+    @ResponseStatus(HttpStatus.CREATED)
+    public TrackingStatusResponse start(Authentication authentication) {
+        UserPrincipal principal = (UserPrincipal) authentication.getPrincipal();
+        disciplineService.startTracking(principal.getUserId());
+        return new TrackingStatusResponse(true);
+    }
+
+    @GetMapping("/tasks")
+    public TasksResponse tasks(
+            @RequestParam(defaultValue = "0") int page,
+            @RequestParam(defaultValue = "20") int size,
+            Authentication authentication) {
+        if (size < 1)
+            size = 20;
+        if (size > 200)
+            size = 200;
+        if (page < 0)
+            page = 0;
+
+        UserPrincipal principal = (UserPrincipal) authentication.getPrincipal();
+        return disciplineService.getTasks(principal.getUserId(), page, size);
+    }
+
+    @PostMapping("/tasks")
+    @ResponseStatus(HttpStatus.CREATED)
+    public TaskDto createTask(
+            @Valid @RequestBody CreateTaskRequest request,
+            Authentication authentication) {
+        UserPrincipal principal = (UserPrincipal) authentication.getPrincipal();
+        return disciplineService.createTask(principal.getUserId(), request);
+    }
+
+    @PutMapping("/tasks/{taskId}")
+    public TaskDto updateTask(
+            @PathVariable Long taskId,
+            @Valid @RequestBody UpdateTaskRequest request,
+            Authentication authentication) {
+        UserPrincipal principal = (UserPrincipal) authentication.getPrincipal();
+        return disciplineService.updateTask(principal.getUserId(), taskId, request);
+    }
+
+    @PatchMapping("/tasks/{taskId}/finished")
+    public TaskDto updateTaskFinished(
+            @PathVariable Long taskId,
+            @Valid @RequestBody UpdateTaskFinishedRequest request,
+            Authentication authentication) {
+        UserPrincipal principal = (UserPrincipal) authentication.getPrincipal();
+        return disciplineService.updateTaskFinished(principal.getUserId(), taskId, request);
+    }
+
+    @DeleteMapping("/tasks/{taskId}")
+    @ResponseStatus(HttpStatus.NO_CONTENT)
+    public void deleteTask(
+            @PathVariable Long taskId,
+            Authentication authentication) {
+        UserPrincipal principal = (UserPrincipal) authentication.getPrincipal();
+        disciplineService.deleteTask(principal.getUserId(), taskId);
+    }
+
+    @PostMapping("/daily-completions/compute")
+    public ComputeDailyCompletionResponse computeDailyCompletion(
+            @RequestParam(required = false) LocalDate date,
+            Authentication authentication) {
+        UserPrincipal principal = (UserPrincipal) authentication.getPrincipal();
+        LocalDate target = (date != null) ? date : LocalDate.now();
+        return disciplineService.computeDailyCompletion(principal.getUserId(), target);
+    }
+
+    @GetMapping("/daily-completions")
+    public DailyCompletionsResponse dailyCompletions(
+            @RequestParam(defaultValue = "0") int page,
+            @RequestParam(defaultValue = "14") int size,
+            Authentication authentication) {
+        if (size < 1)
+            size = 14;
+        if (size > 365)
+            size = 365;
+        if (page < 0)
+            page = 0;
+
+        UserPrincipal principal = (UserPrincipal) authentication.getPrincipal();
+        return disciplineService.getDailyCompletions(principal.getUserId(), page, size);
+    }
+}
+
Index: backend/src/main/java/com/trekr/backend/dto/discipline/ComputeDailyCompletionResponse.java
===================================================================
--- backend/src/main/java/com/trekr/backend/dto/discipline/ComputeDailyCompletionResponse.java	(revision 4f85b41e4eaa6d668912791fb3cff6efbe7dc1a4)
+++ backend/src/main/java/com/trekr/backend/dto/discipline/ComputeDailyCompletionResponse.java	(revision 4f85b41e4eaa6d668912791fb3cff6efbe7dc1a4)
@@ -0,0 +1,5 @@
+package com.trekr.backend.dto.discipline;
+
+public record ComputeDailyCompletionResponse(boolean created, DailyCompletionDto completion) {
+}
+
Index: backend/src/main/java/com/trekr/backend/dto/discipline/CreateTaskRequest.java
===================================================================
--- backend/src/main/java/com/trekr/backend/dto/discipline/CreateTaskRequest.java	(revision 4f85b41e4eaa6d668912791fb3cff6efbe7dc1a4)
+++ backend/src/main/java/com/trekr/backend/dto/discipline/CreateTaskRequest.java	(revision 4f85b41e4eaa6d668912791fb3cff6efbe7dc1a4)
@@ -0,0 +1,21 @@
+package com.trekr.backend.dto.discipline;
+
+import jakarta.validation.constraints.NotBlank;
+
+public class CreateTaskRequest {
+
+    @NotBlank
+    private String name;
+
+    public CreateTaskRequest() {
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+}
+
Index: backend/src/main/java/com/trekr/backend/dto/discipline/DailyCompletionDto.java
===================================================================
--- backend/src/main/java/com/trekr/backend/dto/discipline/DailyCompletionDto.java	(revision 4f85b41e4eaa6d668912791fb3cff6efbe7dc1a4)
+++ backend/src/main/java/com/trekr/backend/dto/discipline/DailyCompletionDto.java	(revision 4f85b41e4eaa6d668912791fb3cff6efbe7dc1a4)
@@ -0,0 +1,8 @@
+package com.trekr.backend.dto.discipline;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+
+public record DailyCompletionDto(Long dailyCompletionId, LocalDate date, BigDecimal procent) {
+}
+
Index: backend/src/main/java/com/trekr/backend/dto/discipline/DailyCompletionsResponse.java
===================================================================
--- backend/src/main/java/com/trekr/backend/dto/discipline/DailyCompletionsResponse.java	(revision 4f85b41e4eaa6d668912791fb3cff6efbe7dc1a4)
+++ backend/src/main/java/com/trekr/backend/dto/discipline/DailyCompletionsResponse.java	(revision 4f85b41e4eaa6d668912791fb3cff6efbe7dc1a4)
@@ -0,0 +1,7 @@
+package com.trekr.backend.dto.discipline;
+
+import java.util.List;
+
+public record DailyCompletionsResponse(List<DailyCompletionDto> completions, boolean hasMore) {
+}
+
Index: backend/src/main/java/com/trekr/backend/dto/discipline/TaskDto.java
===================================================================
--- backend/src/main/java/com/trekr/backend/dto/discipline/TaskDto.java	(revision 4f85b41e4eaa6d668912791fb3cff6efbe7dc1a4)
+++ backend/src/main/java/com/trekr/backend/dto/discipline/TaskDto.java	(revision 4f85b41e4eaa6d668912791fb3cff6efbe7dc1a4)
@@ -0,0 +1,5 @@
+package com.trekr.backend.dto.discipline;
+
+public record TaskDto(Long taskId, String name, boolean isFinished) {
+}
+
Index: backend/src/main/java/com/trekr/backend/dto/discipline/TasksResponse.java
===================================================================
--- backend/src/main/java/com/trekr/backend/dto/discipline/TasksResponse.java	(revision 4f85b41e4eaa6d668912791fb3cff6efbe7dc1a4)
+++ backend/src/main/java/com/trekr/backend/dto/discipline/TasksResponse.java	(revision 4f85b41e4eaa6d668912791fb3cff6efbe7dc1a4)
@@ -0,0 +1,7 @@
+package com.trekr.backend.dto.discipline;
+
+import java.util.List;
+
+public record TasksResponse(List<TaskDto> tasks, boolean hasMore) {
+}
+
Index: backend/src/main/java/com/trekr/backend/dto/discipline/TrackingStatusResponse.java
===================================================================
--- backend/src/main/java/com/trekr/backend/dto/discipline/TrackingStatusResponse.java	(revision 4f85b41e4eaa6d668912791fb3cff6efbe7dc1a4)
+++ backend/src/main/java/com/trekr/backend/dto/discipline/TrackingStatusResponse.java	(revision 4f85b41e4eaa6d668912791fb3cff6efbe7dc1a4)
@@ -0,0 +1,5 @@
+package com.trekr.backend.dto.discipline;
+
+public record TrackingStatusResponse(boolean tracking) {
+}
+
Index: backend/src/main/java/com/trekr/backend/dto/discipline/UpdateTaskFinishedRequest.java
===================================================================
--- backend/src/main/java/com/trekr/backend/dto/discipline/UpdateTaskFinishedRequest.java	(revision 4f85b41e4eaa6d668912791fb3cff6efbe7dc1a4)
+++ backend/src/main/java/com/trekr/backend/dto/discipline/UpdateTaskFinishedRequest.java	(revision 4f85b41e4eaa6d668912791fb3cff6efbe7dc1a4)
@@ -0,0 +1,21 @@
+package com.trekr.backend.dto.discipline;
+
+import jakarta.validation.constraints.NotNull;
+
+public class UpdateTaskFinishedRequest {
+
+    @NotNull
+    private Boolean isFinished;
+
+    public UpdateTaskFinishedRequest() {
+    }
+
+    public Boolean getIsFinished() {
+        return isFinished;
+    }
+
+    public void setIsFinished(Boolean isFinished) {
+        this.isFinished = isFinished;
+    }
+}
+
Index: backend/src/main/java/com/trekr/backend/dto/discipline/UpdateTaskRequest.java
===================================================================
--- backend/src/main/java/com/trekr/backend/dto/discipline/UpdateTaskRequest.java	(revision 4f85b41e4eaa6d668912791fb3cff6efbe7dc1a4)
+++ backend/src/main/java/com/trekr/backend/dto/discipline/UpdateTaskRequest.java	(revision 4f85b41e4eaa6d668912791fb3cff6efbe7dc1a4)
@@ -0,0 +1,21 @@
+package com.trekr.backend.dto.discipline;
+
+import jakarta.validation.constraints.NotBlank;
+
+public class UpdateTaskRequest {
+
+    @NotBlank
+    private String name;
+
+    public UpdateTaskRequest() {
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+}
+
Index: backend/src/main/java/com/trekr/backend/repository/DailyCompletionRepository.java
===================================================================
--- backend/src/main/java/com/trekr/backend/repository/DailyCompletionRepository.java	(revision 4f85b41e4eaa6d668912791fb3cff6efbe7dc1a4)
+++ backend/src/main/java/com/trekr/backend/repository/DailyCompletionRepository.java	(revision 4f85b41e4eaa6d668912791fb3cff6efbe7dc1a4)
@@ -0,0 +1,17 @@
+package com.trekr.backend.repository;
+
+import com.trekr.backend.entity.discipline.DailyCompletion;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.time.LocalDate;
+import java.util.Optional;
+
+public interface DailyCompletionRepository extends JpaRepository<DailyCompletion, Long> {
+
+    Optional<DailyCompletion> findByUser_UserIdAndDate(Long userId, LocalDate date);
+
+    Page<DailyCompletion> findByUser_UserIdOrderByDateDescDailyCompletionIdDesc(Long userId, Pageable pageable);
+}
+
Index: backend/src/main/java/com/trekr/backend/repository/DisciplineUserRepository.java
===================================================================
--- backend/src/main/java/com/trekr/backend/repository/DisciplineUserRepository.java	(revision 4f85b41e4eaa6d668912791fb3cff6efbe7dc1a4)
+++ backend/src/main/java/com/trekr/backend/repository/DisciplineUserRepository.java	(revision 4f85b41e4eaa6d668912791fb3cff6efbe7dc1a4)
@@ -0,0 +1,8 @@
+package com.trekr.backend.repository;
+
+import com.trekr.backend.entity.discipline.DisciplineUser;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface DisciplineUserRepository extends JpaRepository<DisciplineUser, Long> {
+}
+
Index: backend/src/main/java/com/trekr/backend/repository/TaskDailyCompletionRepository.java
===================================================================
--- backend/src/main/java/com/trekr/backend/repository/TaskDailyCompletionRepository.java	(revision 4f85b41e4eaa6d668912791fb3cff6efbe7dc1a4)
+++ backend/src/main/java/com/trekr/backend/repository/TaskDailyCompletionRepository.java	(revision 4f85b41e4eaa6d668912791fb3cff6efbe7dc1a4)
@@ -0,0 +1,9 @@
+package com.trekr.backend.repository;
+
+import com.trekr.backend.entity.discipline.TaskDailyCompletion;
+import com.trekr.backend.entity.discipline.TaskDailyCompletionId;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface TaskDailyCompletionRepository extends JpaRepository<TaskDailyCompletion, TaskDailyCompletionId> {
+}
+
Index: backend/src/main/java/com/trekr/backend/repository/TaskRepository.java
===================================================================
--- backend/src/main/java/com/trekr/backend/repository/TaskRepository.java	(revision 4f85b41e4eaa6d668912791fb3cff6efbe7dc1a4)
+++ backend/src/main/java/com/trekr/backend/repository/TaskRepository.java	(revision 4f85b41e4eaa6d668912791fb3cff6efbe7dc1a4)
@@ -0,0 +1,29 @@
+package com.trekr.backend.repository;
+
+import com.trekr.backend.entity.discipline.Task;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+
+import java.util.List;
+
+public interface TaskRepository extends JpaRepository<Task, Long> {
+
+    Page<Task> findByDisciplineUser_UserIdOrderByTaskIdDesc(Long userId, Pageable pageable);
+
+    int deleteByTaskIdAndDisciplineUser_UserId(Long taskId, Long userId);
+
+    long countByDisciplineUser_UserId(Long userId);
+
+    long countByDisciplineUser_UserIdAndFinishedTrue(Long userId);
+
+    List<Task> findByDisciplineUser_UserIdAndFinishedTrue(Long userId);
+
+    @Modifying
+    @Query("update Task t set t.finished = false where t.disciplineUser.userId = :userId")
+    int resetFinishedForUser(@Param("userId") Long userId);
+}
+
Index: backend/src/main/java/com/trekr/backend/service/DisciplineService.java
===================================================================
--- backend/src/main/java/com/trekr/backend/service/DisciplineService.java	(revision 4f85b41e4eaa6d668912791fb3cff6efbe7dc1a4)
+++ backend/src/main/java/com/trekr/backend/service/DisciplineService.java	(revision 4f85b41e4eaa6d668912791fb3cff6efbe7dc1a4)
@@ -0,0 +1,244 @@
+package com.trekr.backend.service;
+
+import com.trekr.backend.dto.discipline.CreateTaskRequest;
+import com.trekr.backend.dto.discipline.DailyCompletionDto;
+import com.trekr.backend.dto.discipline.DailyCompletionsResponse;
+import com.trekr.backend.dto.discipline.TaskDto;
+import com.trekr.backend.dto.discipline.TasksResponse;
+import com.trekr.backend.dto.discipline.ComputeDailyCompletionResponse;
+import com.trekr.backend.dto.discipline.UpdateTaskFinishedRequest;
+import com.trekr.backend.dto.discipline.UpdateTaskRequest;
+import com.trekr.backend.entity.User;
+import com.trekr.backend.entity.discipline.DisciplineUser;
+import com.trekr.backend.entity.discipline.DailyCompletion;
+import com.trekr.backend.entity.discipline.Task;
+import com.trekr.backend.entity.discipline.TaskDailyCompletion;
+import com.trekr.backend.entity.discipline.TaskDailyCompletionId;
+import com.trekr.backend.repository.DailyCompletionRepository;
+import com.trekr.backend.repository.DisciplineUserRepository;
+import com.trekr.backend.repository.TaskRepository;
+import com.trekr.backend.repository.TaskDailyCompletionRepository;
+import com.trekr.backend.repository.UserRepository;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.time.LocalDate;
+import java.util.List;
+
+@Service
+public class DisciplineService {
+
+    private final DisciplineUserRepository disciplineUserRepository;
+    private final TaskRepository taskRepository;
+    private final DailyCompletionRepository dailyCompletionRepository;
+    private final TaskDailyCompletionRepository taskDailyCompletionRepository;
+    private final UserRepository userRepository;
+
+    public DisciplineService(
+            DisciplineUserRepository disciplineUserRepository,
+            TaskRepository taskRepository,
+            DailyCompletionRepository dailyCompletionRepository,
+            TaskDailyCompletionRepository taskDailyCompletionRepository,
+            UserRepository userRepository) {
+        this.disciplineUserRepository = disciplineUserRepository;
+        this.taskRepository = taskRepository;
+        this.dailyCompletionRepository = dailyCompletionRepository;
+        this.taskDailyCompletionRepository = taskDailyCompletionRepository;
+        this.userRepository = userRepository;
+    }
+
+    public boolean isTracking(Long userId) {
+        return disciplineUserRepository.existsById(userId);
+    }
+
+    @Transactional
+    public void startTracking(Long userId) {
+        if (disciplineUserRepository.existsById(userId)) {
+            return;
+        }
+
+        User user = userRepository.findById(userId)
+                .orElseThrow(() -> new RuntimeException("User not found"));
+
+        DisciplineUser disciplineUser = new DisciplineUser();
+        disciplineUser.setUser(user);
+        disciplineUserRepository.save(disciplineUser);
+    }
+
+    public TasksResponse getTasks(Long userId, int page, int size) {
+        if (!disciplineUserRepository.existsById(userId)) {
+            return new TasksResponse(List.of(), false);
+        }
+
+        Page<Task> result = taskRepository.findByDisciplineUser_UserIdOrderByTaskIdDesc(
+                userId, PageRequest.of(page, size));
+
+        List<TaskDto> tasks = result.getContent().stream()
+                .map(t -> new TaskDto(t.getTaskId(), t.getName(), t.isFinished()))
+                .toList();
+
+        return new TasksResponse(tasks, result.hasNext());
+    }
+
+    @Transactional
+    public TaskDto createTask(Long userId, CreateTaskRequest request) {
+        DisciplineUser disciplineUser = disciplineUserRepository.findById(userId)
+                .orElseThrow(() -> new RuntimeException("Discipline tracking is not enabled for this user"));
+
+        String name = request.getName();
+        if (name == null || name.isBlank()) {
+            throw new RuntimeException("Task name is required");
+        }
+
+        if (name.length() > 200) {
+            throw new RuntimeException("Task name is too long");
+        }
+
+        Task task = new Task();
+        task.setName(name.trim());
+        task.setFinished(false);
+        task.setDisciplineUser(disciplineUser);
+
+        Task saved = taskRepository.save(task);
+        return new TaskDto(saved.getTaskId(), saved.getName(), saved.isFinished());
+    }
+
+    @Transactional
+    public TaskDto updateTask(Long userId, Long taskId, UpdateTaskRequest request) {
+        Task task = taskRepository.findById(taskId)
+                .orElseThrow(() -> new RuntimeException("Task not found"));
+
+        if (task.getDisciplineUser() == null || !userId.equals(task.getDisciplineUser().getUserId())) {
+            throw new RuntimeException("Task not found");
+        }
+
+        String name = request.getName();
+        if (name == null || name.isBlank()) {
+            throw new RuntimeException("Task name is required");
+        }
+
+        task.setName(name.trim());
+        Task saved = taskRepository.save(task);
+        return new TaskDto(saved.getTaskId(), saved.getName(), saved.isFinished());
+    }
+
+    @Transactional
+    public TaskDto updateTaskFinished(Long userId, Long taskId, UpdateTaskFinishedRequest request) {
+        Task task = taskRepository.findById(taskId)
+                .orElseThrow(() -> new RuntimeException("Task not found"));
+
+        if (task.getDisciplineUser() == null || !userId.equals(task.getDisciplineUser().getUserId())) {
+            throw new RuntimeException("Task not found");
+        }
+
+        Boolean finished = request.getIsFinished();
+        if (finished == null) {
+            throw new RuntimeException("isFinished is required");
+        }
+
+        task.setFinished(finished);
+        Task saved = taskRepository.save(task);
+        return new TaskDto(saved.getTaskId(), saved.getName(), saved.isFinished());
+    }
+
+    @Transactional
+    public void deleteTask(Long userId, Long taskId) {
+        if (taskId == null) {
+            throw new RuntimeException("Task not found");
+        }
+
+        int deleted = taskRepository.deleteByTaskIdAndDisciplineUser_UserId(taskId, userId);
+        if (deleted == 0) {
+            throw new RuntimeException("Task not found");
+        }
+    }
+
+    /**
+     * Computes the daily completion percentage for the given date:
+     * finished_tasks / total_tasks * 100
+     *
+     * - Persisted once per user+date. If already computed, it is not re-computed.
+     * - After computing, all tasks for that user are reset to unfinished.
+     */
+    @Transactional
+    public ComputeDailyCompletionResponse computeDailyCompletion(Long userId, LocalDate date) {
+        if (!disciplineUserRepository.existsById(userId)) {
+            throw new RuntimeException("Discipline tracking is not enabled for this user");
+        }
+
+        if (date == null) {
+            throw new RuntimeException("Date is required");
+        }
+
+        if (date.isAfter(LocalDate.now())) {
+            throw new RuntimeException("Date cannot be in the future");
+        }
+
+        DailyCompletion existing = dailyCompletionRepository.findByUser_UserIdAndDate(userId, date).orElse(null);
+        if (existing != null) {
+            return new ComputeDailyCompletionResponse(false,
+                    new DailyCompletionDto(existing.getDailyCompletionId(), existing.getDate(), existing.getProcent()));
+        }
+
+        long total = taskRepository.countByDisciplineUser_UserId(userId);
+        long finished = taskRepository.countByDisciplineUser_UserIdAndFinishedTrue(userId);
+
+        BigDecimal percent;
+        if (total <= 0) {
+            percent = BigDecimal.ZERO;
+        } else {
+            percent = BigDecimal.valueOf(finished)
+                    .multiply(new BigDecimal("100"))
+                    .divide(BigDecimal.valueOf(total), 2, RoundingMode.HALF_UP);
+        }
+
+        User user = userRepository.findById(userId)
+                .orElseThrow(() -> new RuntimeException("User not found"));
+
+        DailyCompletion completion = new DailyCompletion();
+        completion.setUser(user);
+        completion.setDate(date);
+        completion.setProcent(percent);
+        DailyCompletion savedCompletion = dailyCompletionRepository.save(completion);
+
+        // Link all tasks that were finished at calculation time
+        // (not strictly required for now, but matches schema and enables future analytics)
+        if (finished > 0) {
+            List<Task> finishedTasks = taskRepository.findByDisciplineUser_UserIdAndFinishedTrue(userId);
+            for (Task t : finishedTasks) {
+                TaskDailyCompletion link = new TaskDailyCompletion();
+                link.setTask(t);
+                link.setDailyCompletion(savedCompletion);
+                link.setId(new TaskDailyCompletionId(t.getTaskId(), savedCompletion.getDailyCompletionId()));
+                taskDailyCompletionRepository.save(link);
+            }
+        }
+
+        // Reset all tasks for next day
+        taskRepository.resetFinishedForUser(userId);
+
+        DailyCompletionDto dto = new DailyCompletionDto(savedCompletion.getDailyCompletionId(),
+                savedCompletion.getDate(), savedCompletion.getProcent());
+        return new ComputeDailyCompletionResponse(true, dto);
+    }
+
+    public DailyCompletionsResponse getDailyCompletions(Long userId, int page, int size) {
+        if (!disciplineUserRepository.existsById(userId)) {
+            return new DailyCompletionsResponse(List.of(), false);
+        }
+
+        Page<DailyCompletion> result = dailyCompletionRepository
+                .findByUser_UserIdOrderByDateDescDailyCompletionIdDesc(userId, PageRequest.of(page, size));
+
+        List<DailyCompletionDto> items = result.getContent().stream()
+                .map(dc -> new DailyCompletionDto(dc.getDailyCompletionId(), dc.getDate(), dc.getProcent()))
+                .toList();
+
+        return new DailyCompletionsResponse(items, result.hasNext());
+    }
+}
+
