> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dokstamp.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Sincronização da Estrutura Académica

> Guia passo a passo para sincronizar cursos, módulos, turmas, estudantes e certificados de um sistema de terceiros para o DokStamp.

# Sincronização da Estrutura Académica

Esta página explica como sincronizar cada entidade académica do seu sistema para o DokStamp, na ordem correta de dependências.

<Info>
  Siga sempre a ordem indicada abaixo. Criar um certificado antes de existirem as suas dependências resultará em erros de validação `422`.
</Info>

***

## Ordem de sincronização

| Passo | Evento no seu sistema                 | Ação no DokStamp                              |
| ----- | ------------------------------------- | --------------------------------------------- |
| 1     | Instituição configurada               | `POST /institutions` (ou configuração manual) |
| 2     | Curso criado / atualizado             | `POST /courses` ou `PATCH /courses/{uuid}`    |
| 3     | Módulo/disciplina criado / atualizado | `POST /modules` ou `PATCH /modules/{uuid}`    |
| 4     | Módulo adicionado ao curso            | `POST /courses/{uuid}/attach/modules`         |
| 5     | Módulos organizados em grupos         | `POST /courses/{uuid}/modules/groups`         |
| 6     | Turma (classe de entrada) criada      | `POST /cohorts`                               |
| 7     | Estudante torna-se elegível           | pesquisa → `POST /students` (se novo)         |
| 8     | Estudante matriculado no curso        | `POST /enrollments`                           |
| 9     | Emissão de certificado ativada        | `POST /files` + `POST /certificates`          |

***

## Passo 2 — Sincronizar um curso

Quando um curso é criado ou atualizado no seu sistema, replique a alteração no DokStamp.

<CodeGroup>
  ```javascript Create theme={null}
  async function syncCourse(course) {
    // Search by code to check if it already exists
    const existing = await api.get('/courses', {
      params: { 'where[code]': course.code }
    });

    if (existing.data.length > 0) {
      // Update existing
      await api.patch(`/courses/${existing.data[0].uuid}`, {
        name: course.name,
        description: course.description,
        workload_hours: course.workloadHours,
        status: course.isActive ? 'active' : 'archived',
      });
      return existing.data[0].uuid;
    }

    // Create new
    const res = await api.post('/courses', {
      name: course.name,
      code: course.code,
      institution_uuid: INSTITUTION_UUID,
      workload_hours: course.workloadHours,
      status: 'active',
    });
    return res.data.uuid;
  }
  ```

  ```php PHP theme={null}
  function syncCourse(array $course): string
  {
      $existing = Http::withToken($token)
          ->withHeaders(['X-Tenant' => $tenant])
          ->get('/courses', ['where[code]' => $course['code']])
          ->json('data');

      if (!empty($existing)) {
          Http::withToken($token)
              ->withHeaders(['X-Tenant' => $tenant])
              ->patch("/courses/{$existing[0]['uuid']}", [
                  'name'          => $course['name'],
                  'workload_hours'=> $course['workload_hours'],
                  'status'        => $course['is_active'] ? 'active' : 'archived',
              ]);
          return $existing[0]['uuid'];
      }

      return Http::withToken($token)
          ->withHeaders(['X-Tenant' => $tenant])
          ->post('/courses', $course)
          ->json('data.uuid');
  }
  ```
</CodeGroup>

***

## Passo 3 — Sincronizar módulos/disciplinas

```javascript theme={null}
async function syncModule(module) {
  const existing = await api.get('/modules', {
    params: {
      'where[code]': module.code,
      'where[institution][uuid]': INSTITUTION_UUID,
    }
  });

  if (existing.data.length > 0) {
    await api.patch(`/modules/${existing.data[0].uuid}`, {
      name: module.name,
      workload: module.workload,
      credits: module.credits,
    });
    return existing.data[0].uuid;
  }

  const res = await api.post('/modules', {
    name: module.name,
    code: module.code,
    institution_uuid: INSTITUTION_UUID,
    workload: module.workload,
    credits: module.credits,
    level: module.level,       // 'undergraduate' | 'graduate' | 'technical' | 'open'
    modality: module.modality, // 'in_person' | 'online' | 'hybrid'
  });
  return res.data.uuid;
}
```

***

## Passo 4 — Associar módulos a um curso

Após criar/sincronizar o curso e os seus módulos, associe-os:

```javascript theme={null}
await api.post(`/courses/${courseUuid}/attach/modules`, {
  modules: moduleUuids.map((uuid, index) => ({
    module_uuid,
    order: index + 1,
    is_required: true,
  }))
});
```

<Warning>
  A associação é idempotente para novos módulos, mas re-associar um módulo já associado devolverá um erro `422`. Consulte primeiro `GET /courses/{uuid}?includes[modules]=1` para obter a lista de módulos ainda não associados.
</Warning>

***

## Passo 5 — Grupos de módulos (opcional)

Se o seu sistema organiza disciplinas em semestres ou períodos, replique essa estrutura:

```javascript theme={null}
async function syncModuleGroup(courseUuid, group) {
  const res = await api.post(`/courses/${courseUuid}/modules/groups`, {
    name: group.name,   // e.g. "1st Semester", "Core Modules"
    order: group.order,
  });
  return res.data.uuid;
}
```

De seguida, re-associe os módulos especificando o grupo:

```javascript theme={null}
await api.post(`/courses/${courseUuid}/attach/modules`, {
  modules: [{
    module_uuid: moduleUuid,
    order: 1,
    course_module_group_uuid: groupUuid,
  }]
});
```

***

## Passo 6 — Sincronizar turmas

Uma turma corresponde a uma classe de conclusão específica (ex.: "Turma Noturna 2024/1"):

```javascript theme={null}
async function syncCohort(cohort) {
  const existing = await api.get('/cohorts', {
    params: { 'where[code]': cohort.code }
  });

  if (existing.data.length > 0) return existing.data[0].uuid;

  const res = await api.post('/cohorts', {
    course_uuid: cohort.courseUuid,
    code: cohort.code,
    modality: cohort.modality,
    start_date: cohort.startDate,
    end_date: cohort.endDate,
  });
  return res.data.uuid;
}
```

***

## Passo 7 — Registar estudantes elegíveis

Quando o sistema determina que um estudante é elegível para um certificado, registe-o no DokStamp (ou verifique se já existe):

```javascript theme={null}
async function syncStudent(student) {
  // Always search by email first
  const existing = await api.get('/students', {
    params: { 'where[user][email]': student.email }
  });

  if (existing.data.length > 0) return existing.data[0].uuid;

  const res = await api.post('/students', {
    name: student.name,
    email: student.email,
    date_of_birth: student.dateOfBirth, // YYYY-MM-DD
    gender: student.gender,
  });
  return res.data.uuid;
}
```

***

## Passo 8 — Criar a matrícula

```javascript theme={null}
const enrollment = await api.post('/enrollments', {
  student_uuid: studentUuid,
  course_uuid: courseUuid,
  cohort_uuid: cohortUuid,       // optional
  enrolled_at: student.enrolledAt,
  completion_status: 'completed',
  grade: student.finalGrade,
  completed_at: student.completedAt,
});
```

***

## Passo 9 — Emitir o certificado

```javascript theme={null}
// 1. Upload the PDF
const fileRes = await api.post('/files', formDataWithPdf);
const fileUuid = fileRes.data[0].uuid;

// 2. Issue the certificate
const cert = await api.post('/certificates', {
  institution_uuid: INSTITUTION_UUID,
  course_uuid: courseUuid,
  student_uuid: studentUuid,
  cohort_uuid: cohortUuid,
  enrollment_uuid: enrollmentUuid,
  file_uuid: fileUuid,
  status: 'issued',
  issued_at: new Date().toISOString(),
});

console.log('Verification URL:', cert.data.public_verification_url);
```

***

## Exemplo assíncrono completo

Para integrações baseadas em fila, encapsule cada passo num trabalho:

```javascript theme={null}
// jobs/SyncCourseJob.js
export async function handle({ course }) {
  try {
    const uuid = await syncCourse(course);
    await syncModules(uuid, course.modules);
    logger.info(`Course synced: ${uuid}`);
  } catch (err) {
    if (err.response?.status >= 500 || err.response?.status === 429) {
      throw err; // Will be retried by the queue
    }
    logger.error(`Sync failed (no retry): ${err.message}`, { course });
  }
}
```

Repita o pedido em caso de `5xx` e `429`. Registe e descarte erros `4xx` (indicam um problema de dados, não uma falha transitória).
