Saltar para o conteúdo principal

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.
Siga sempre a ordem indicada abaixo. Criar um certificado antes de existirem as suas dependências resultará em erros de validação 422.

Ordem de sincronização

PassoEvento no seu sistemaAção no DokStamp
1Instituição configuradaPOST /institutions (ou configuração manual)
2Curso criado / atualizadoPOST /courses ou PATCH /courses/{uuid}
3Módulo/disciplina criado / atualizadoPOST /modules ou PATCH /modules/{uuid}
4Módulo adicionado ao cursoPOST /courses/{uuid}/attach/modules
5Módulos organizados em gruposPOST /courses/{uuid}/modules/groups
6Turma (classe de entrada) criadaPOST /cohorts
7Estudante torna-se elegívelpesquisa → POST /students (se novo)
8Estudante matriculado no cursoPOST /enrollments
9Emissão de certificado ativadaPOST /files + POST /certificates

Passo 2 — Sincronizar um curso

Quando um curso é criado ou atualizado no seu sistema, replique a alteração no DokStamp.
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;
}

Passo 3 — Sincronizar módulos/disciplinas

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:
await api.post(`/courses/${courseUuid}/attach/modules`, {
  modules: moduleUuids.map((uuid, index) => ({
    uuid,
    order: index + 1,
    is_required: true,
  }))
});
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}/attach/modules para obter a lista de módulos ainda não associados.

Passo 5 — Grupos de módulos (opcional)

Se o seu sistema organiza disciplinas em semestres ou períodos, replique essa estrutura:
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:
await api.post(`/courses/${courseUuid}/attach/modules`, {
  modules: [{
    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”):
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):
async function syncStudent(student) {
  // Always search by email first
  const existing = await api.get('/students', {
    params: { 'where[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

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

// 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:
// 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).