📋 API de Chamadas - Documentação Completa

Sistema completo de gerenciamento de chamadas de alunos
Registro, consulta e controle de presença de alunos em aulas com suporte a justificativas, rascunhos e níveis hierárquicos

📋 Visão Geral

O que é este sistema?

Um sistema completo de gerenciamento de chamadas de alunos que permite:

Por que você precisa disto?

Problema Solução Resultado
Controle manual de presença Sistema automatizado de chamadas Controle eficiente e centralizado
Falta de rastreamento Registro completo com auditoria Histórico completo de presenças
Justificativas desorganizadas Campo estruturado com validações Gestão profissional de ausências
Sem controle de duplicidade Validação de chamadas concluídas Integridade dos dados
Dificuldade em pesquisar Filtros avançados e paginação Consulta rápida e eficiente

🏗️ Arquitetura

Fluxo de Dados

Controller (ChamadasController)
    ↓
Service (ChamadaService)
    ↓
Repository (ChamadaRepository)
    ↓
Database (AdminDbContext → Tabela Chamadas)

Componentes Principais

🔧 Componentes

Controller: ChamadasController

Responsável por:

Service: ChamadaService

Responsável por:

Repository: ChamadaRepository

Responsável por:

Entidade: Chamada

Estrutura da entidade:

🌐 Endpoints

GET /api/Chamadas

Descrição: Pesquisa chamadas com filtros e paginação. Retorna dados agrupados por ChamadaId, onde cada grupo contém arrays de informações (id, usuario, presenca, justificativa) e campos fixos compartilhados (rascunho, ativo, dataChamada, nivelHierarquico, campos de auditoria).

Observação: A paginação conta grupos únicos (ChamadaId), não registros individuais. Todos os registros do mesmo ChamadaId compartilham os mesmos valores de campos fixos.

Comportamento Padrão: Por padrão, o endpoint retorna apenas chamadas ativas (Ativo=true). Para visualizar chamadas inativas, passe explicitamente o parâmetro Ativo=false na query string.

Parâmetros de Query:

Parâmetro Tipo Obrigatório Descrição
Data DateTime? Não Data específica (formato: yyyy-MM-dd)
MesAno string? Não Mês/ano (MM/yyyy) ou apenas ano (yyyy)
UsuarioId string? Não ID do usuário (aluno)
Presenca int? Não Status: 0=Ausente, 1=Presente, 2=Justificado
Rascunho bool? Não true=rascunho, false=concluída
Ativo bool? Não true=ativo, false=inativo. Por padrão, retorna apenas registros ativos. Para ver inativos, passe explicitamente Ativo=false.
ChamadaId int? Não ID que agrupa múltiplos registros criados no mesmo POST. Permite buscar todas as chamadas de uma única operação.
Pagina int Não Número da página (default: 1)
TamanhoPagina int Não Itens por página (default: 20)

Resposta (200 OK):

{
  "data": [
    {
      "chamadaId": 5,
      "id": [1, 2, 3],
      "usuario": ["user123", "user456", "user789"],
      "presenca": [1, 0, 2],
      "justificativa": [null, null, "Faltou por motivo médico"],
      "rascunho": false,
      "ativo": true,
      "dataChamada": "2026-01-06",
      "nivelHierarquico": {
        "horarioId": [1]
      },
      "criacao": "2026-01-06T10:00:00",
      "atualizacao": "2026-01-06T10:05:00",
      "usuarioCriacao": "prof123",
      "usuarioAtualizacao": "prof123"
    },
    {
      "chamadaId": 6,
      "id": [4, 5],
      "usuario": ["user111", "user222"],
      "presenca": [1, 1],
      "justificativa": [null, null],
      "rascunho": true,
      "ativo": true,
      "dataChamada": "2026-01-07",
      "nivelHierarquico": {
        "turmaId": [2]
      },
      "criacao": "2026-01-07T08:00:00",
      "atualizacao": null,
      "usuarioCriacao": "prof456",
      "usuarioAtualizacao": null
    }
  ],
  "pagination": {
    "page": 1,
    "pageSize": 20,
    "totalPages": 1,
    "totalRecords": 2,
    "hasNextPage": false,
    "hasPreviousPage": false
  }
}

Estrutura do Response Agrupado:

Importante: A paginação conta grupos únicos (ChamadaId), não registros individuais. O campo totalRecords representa o número de grupos, não o número total de registros.

GET /api/Chamadas/{id}

Descrição: Obtém uma chamada por ID

Parâmetros:

Resposta (200 OK): Objeto ChamadaResponse

{
  "id": 1,
  "chamadaId": 5,
  "usuarioId": "user123",
  "presenca": 1,
  "justificativa": null,
  "ativo": true,
  "dataChamada": "2026-01-06",
  "rascunho": false,
  "nivelHierarquico": {
    "horarioId": [1]
  },
  "criacao": "2026-01-06T10:00:00",
  "atualizacao": null,
  "usuarioCriacao": "prof123",
  "usuarioAtualizacao": null
}

Resposta (404 Not Found): "Chamada não encontrada"

POST /api/Chamadas

Descrição: Cria uma ou múltiplas chamadas em lote (processamento em lote). O processamento é totalmente atômico: se qualquer registro falhar na validação, nenhum registro será criado e o erro específico do usuário que falhou será retornado.

Atomicidade: Todas as chamadas são validadas antes de qualquer criação. Se qualquer validação falhar, a operação é abortada imediatamente e nenhuma chamada é criada. Se todas as validações passarem, todas as chamadas são criadas dentro de uma transação única, garantindo que ou todas são criadas ou nenhuma é criada.

Body (JSON):

{
  "usuarioIds": ["user123", "user456", "user789"],
  "presenca": [1, 0, 2],
  "justificativa": [null, null, "Atestado médico"],
  "dataChamada": "2026-01-06",
  "rascunho": false,
  "nivelHierarquico": {
    "horarioId": [1]
  }
}

Resposta (201 Created): Objeto CriarChamadasResponse quando todas as chamadas foram criadas com sucesso

{
  "sucessos": [
    {
      "id": 1,
      "chamadaId": 5,
      "usuarioId": "user123",
      "presenca": 1,
      "justificativa": null,
      "dataChamada": "2026-01-06",
      "rascunho": false,
      "ativo": true,
      "nivelHierarquico": {
        "horarioId": [1]
      }
    },
    {
      "id": 2,
      "chamadaId": 5,
      "usuarioId": "user456",
      "presenca": 0,
      "justificativa": null,
      "dataChamada": "2026-01-06",
      "rascunho": false,
      "ativo": true,
      "nivelHierarquico": {
        "horarioId": [1]
      }
    }
  ],
  "erros": [],
  "totalProcessado": 2,
  "totalSucesso": 2,
  "totalErro": 0
}

Resposta (400 Bad Request): Retornado quando qualquer registro falha na validação. A mensagem de erro indica o usuário específico que falhou e o motivo. Nenhuma chamada é criada quando há erro.

{
  "status": false,
  "message": "Erro ao criar chamada para o usuário user456: A data da chamada não pode ser futura.",
  "data": {
    "sucessos": [],
    "erros": [
      {
        "indice": 1,
        "usuarioId": "user456",
        "mensagem": "A data da chamada não pode ser futura."
      }
    ],
    "totalProcessado": 2,
    "totalSucesso": 0,
    "totalErro": 1
  }
}

Comportamento Atômico: Se qualquer registro falhar na validação, a operação é abortada imediatamente e nenhuma chamada é criada. O erro retornado indica especificamente qual usuário falhou e o motivo. Isso garante integridade dos dados e evita estados inconsistentes.

Nota: Os arrays usuarioIds, presenca e justificativa devem ter o mesmo tamanho. Cada posição dos arrays corresponde a um registro individual na tabela chamadas.

PUT /api/Chamadas/chamadaId/{chamadaId}

Descrição: Atualiza todas as chamadas de um mesmo ChamadaId (processamento em lote). Permite editar todos os registros de um mesmo ChamadaId de uma vez, usando arrays para dados individuais e campos fixos para dados compartilhados.

Parâmetros:

Body (JSON):

{
  "usuarioIds": ["user123", "user456", "user789"],
  "presenca": [1, 0, 2],
  "justificativa": [null, null, "Atestado médico"],
  "rascunho": false,
  "ativo": true,
  "dataChamada": "2026-01-06",
  "nivelHierarquico": {
    "horarioId": [1]
  }
}

Estrutura do Request:

Permissões de Edição:

Validações:

Resposta (400 Bad Request): Retornado quando:

Resposta (200 OK): Objeto AtualizarChamadasResponse com sucessos e erros

{
  "sucessos": [
    {
      "id": 1,
      "chamadaId": 5,
      "usuarioId": "user123",
      "presenca": 1,
      "justificativa": null,
      "ativo": true,
      "dataChamada": "2026-01-06",
      "rascunho": false,
      "nivelHierarquico": {
        "horarioId": [1]
      },
      "criacao": "2026-01-06T10:00:00",
      "atualizacao": "2026-01-06T14:30:00",
      "usuarioCriacao": "prof123",
      "usuarioAtualizacao": "prof123"
    },
    {
      "id": 2,
      "chamadaId": 5,
      "usuarioId": "user456",
      "presenca": 0,
      "justificativa": null,
      "ativo": true,
      "dataChamada": "2026-01-06",
      "rascunho": false,
      "nivelHierarquico": {
        "horarioId": [1]
      },
      "criacao": "2026-01-06T10:00:00",
      "atualizacao": "2026-01-06T14:30:00",
      "usuarioCriacao": "prof123",
      "usuarioAtualizacao": "prof123"
    }
  ],
  "erros": [
    {
      "indice": 2,
      "usuarioId": "user789",
      "mensagem": "Justificativa obrigatória para Presenca=2"
    }
  ],
  "totalProcessado": 3,
  "totalSucesso": 2,
  "totalErro": 1
}

Notas Importantes:

DELETE /api/Chamadas/chamadaId/{chamadaId}

Descrição: Exclui todas as chamadas de um grupo (soft delete via Ativo=false). Inativa todos os registros com o mesmo ChamadaId, ou seja, todos os registros criados juntos no mesmo POST.

Parâmetros:

Nota Importante: Quando você exclui uma chamada usando o ChamadaId, todos os registros que foram criados juntos no mesmo POST serão inativados simultaneamente. Isso garante que o grupo de chamadas seja tratado como uma unidade lógica.

Resposta (200 OK):

{
  "message": "Chamada excluída com sucesso",
  "success": true
}

Resposta (200 OK) - Múltiplos registros:

{
  "message": "3 chamadas excluídas com sucesso",
  "success": true
}

Resposta (404 Not Found): "Chamada não encontrada" - Retornado quando não existe nenhum registro com o ChamadaId informado.

📝 Campos e Validações

CriarChamadaRequest

Campo Tipo Obrigatório Validação Descrição
UsuarioIds List<string> Sim Máx. 450 caracteres por elemento, não pode estar vazio, deve ter mesmo tamanho dos outros arrays Lista de IDs dos usuários (alunos). Cada posição corresponde a um registro.
Presenca List<int> Sim 0, 1 ou 2 por elemento, não pode estar vazio, deve ter mesmo tamanho dos outros arrays Lista de status de presença: 0=Ausente, 1=Presente, 2=Justificado. Cada posição corresponde a um registro.
Justificativa List<string?> Sim Máx. 200 caracteres por elemento, não pode estar vazio, deve ter mesmo tamanho dos outros arrays Lista de justificativas. Obrigatória se Presenca[i]=2, opcional se Presenca[i]=0, ignorada automaticamente (definida como null) se Presenca[i]=1. Cada posição corresponde a um registro.
DataChamada DateTime Sim Formato: yyyy-MM-dd Data da chamada (aplicada a todos os registros criados)
Rascunho bool Não Default: true Indica se as chamadas são rascunho (true) ou concluídas (false). Aplicado a todos os registros criados.
NivelHierarquico NivelHierarquicoDto? Não JSON válido Níveis hierárquicos (instituicaoId, unidadeId, turmaId, horarioId). Aplicado a todos os registros criados.

Validações de Arrays:

CriarChamadasResponse

Campo Tipo Descrição
Sucessos List<ChamadaResponse> Lista de chamadas criadas com sucesso
Erros List<ErroChamada> Lista de erros ocorridos durante a criação
TotalProcessado int Total de registros processados
TotalSucesso int Total de registros criados com sucesso
TotalErro int Total de registros com erro

ChamadaResponse

Campo Tipo Descrição
Id int Identificador único da chamada (auto-incremento)
ChamadaId int ID que agrupa múltiplos registros criados no mesmo POST. Gerado automaticamente por sequence thread-safe.
UsuarioId string ID do usuário (aluno) relacionado à chamada
Presenca int Status de presença: 0=Ausente, 1=Presente, 2=Justificado
Justificativa string? Justificativa para ausência ou justificativa (obrigatória quando Presenca=2)
Ativo bool Indica se a chamada está ativa (soft delete)
DataChamada DateTime Data da chamada (formato: yyyy-MM-dd)
Rascunho bool Indica se a chamada é rascunho (true) ou concluída (false)
NivelHierarquico NivelHierarquicoDto? Níveis hierárquicos (instituicaoId, unidadeId, turmaId, horarioId) em formato JSON
Criacao DateTime? Data/hora de criação
Atualizacao DateTime? Data/hora de atualização
UsuarioCriacao string ID do usuário que criou a chamada
UsuarioAtualizacao string ID do usuário que atualizou a chamada

ChamadaAgrupadaResponse

DTO de resposta para chamadas agrupadas por ChamadaId. Usado no endpoint GET /api/Chamadas.

Campo Tipo Descrição
ChamadaId int ID que agrupa múltiplos registros criados no mesmo POST
Id List<int> Array de IDs dos registros individuais
Usuario List<string> Array de IDs de usuários (alunos)
Presenca List<int> Array de status de presença: 0=Ausente, 1=Presente, 2=Justificado
Justificativa List<string?> Array de justificativas (pode conter null)
Rascunho bool Status de rascunho compartilhado entre todos os registros do grupo
Ativo bool Status ativo compartilhado entre todos os registros do grupo
DataChamada DateTime Data da chamada compartilhada entre todos os registros do grupo
NivelHierarquico NivelHierarquicoDto? Nível hierárquico compartilhado entre todos os registros do grupo
Criacao DateTime? Data/hora de criação compartilhada entre todos os registros do grupo
Atualizacao DateTime? Data/hora de atualização compartilhada entre todos os registros do grupo
UsuarioCriacao string ID do usuário que criou a chamada (compartilhado entre todos os registros do grupo)
UsuarioAtualizacao string ID do usuário que atualizou a chamada (compartilhado entre todos os registros do grupo)
Atualizacao DateTime? Data/hora da última atualização
UsuarioCriacao string ID do usuário que criou a chamada
UsuarioAtualizacao string ID do usuário que atualizou a chamada

ChamadaAgrupadaResponse

DTO de resposta para chamadas agrupadas por ChamadaId. Usado no endpoint GET /api/Chamadas.

Campo Tipo Descrição
ChamadaId int ID que agrupa múltiplos registros criados no mesmo POST
Id List<int> Array de IDs dos registros individuais
Usuario List<string> Array de IDs de usuários (alunos)
Presenca List<int> Array de status de presença: 0=Ausente, 1=Presente, 2=Justificado
Justificativa List<string?> Array de justificativas (pode conter null)
Rascunho bool Status de rascunho compartilhado entre todos os registros do grupo
Ativo bool Status ativo compartilhado entre todos os registros do grupo
DataChamada DateTime Data da chamada compartilhada entre todos os registros do grupo
NivelHierarquico NivelHierarquicoDto? Nível hierárquico compartilhado entre todos os registros do grupo
Criacao DateTime? Data/hora de criação compartilhada entre todos os registros do grupo
Atualizacao DateTime? Data/hora de atualização compartilhada entre todos os registros do grupo
UsuarioCriacao string ID do usuário que criou a chamada (compartilhado entre todos os registros do grupo)
UsuarioAtualizacao string ID do usuário que atualizou a chamada (compartilhado entre todos os registros do grupo)

Observação: Os campos fixos (rascunho, ativo, dataChamada, nivelHierarquico, campos de auditoria) são compartilhados entre todos os registros do mesmo ChamadaId, pois são criados no mesmo POST. Os arrays (id, usuario, presenca, justificativa) contêm os valores individuais de cada registro do grupo.

ErroChamada

Campo Tipo Descrição
Indice int Índice do array onde ocorreu o erro
UsuarioId string ID do usuário relacionado ao erro
Mensagem string Mensagem de erro descritiva

Regras de Validação

Justificativa

Nota Importante: Quando Presenca=1 (Presente), o sistema automaticamente define Justificativa=null antes de processar a requisição. Isso significa que mesmo que você acidentalmente envie uma justificativa junto com Presenca=1, ela será ignorada e definida como null automaticamente. Não é necessário remover o campo do request, mas ele será ignorado.

Data

Duplicidade

💼 Casos de Uso

Caso 1: Registrar Presença em Lote

Cenário: Professor registra presença de múltiplos alunos em uma aula

POST /api/Chamadas
Content-Type: application/json
Authorization: Bearer {token}

{
  "usuarioIds": ["aluno123", "aluno456", "aluno789"],
  "presenca": [1, 1, 0],
  "justificativa": [null, null, null],
  "dataChamada": "2026-01-06",
  "rascunho": false,
  "nivelHierarquico": {
    "horarioId": [1]
  }
}

Resposta (201 Created):

{
  "sucessos": [
    {
      "id": 1,
      "chamadaId": 5,
      "usuarioId": "aluno123",
      "presenca": 1,
      "justificativa": null,
      "dataChamada": "2026-01-06",
      "rascunho": false,
      "ativo": true
    },
    {
      "id": 2,
      "chamadaId": 5,
      "usuarioId": "aluno456",
      "presenca": 1,
      "justificativa": null,
      "dataChamada": "2026-01-06",
      "rascunho": false,
      "ativo": true
    },
    {
      "id": 3,
      "chamadaId": 5,
      "usuarioId": "aluno789",
      "presenca": 0,
      "justificativa": null,
      "dataChamada": "2026-01-06",
      "rascunho": false,
      "ativo": true
    }
  ],
  "erros": [],
  "totalProcessado": 3,
  "totalSucesso": 3,
  "totalErro": 0
}

Caso 1.1: Registrar Presença Individual (Compatibilidade)

Cenário: Registrar presença de um único aluno (usando array com um elemento)

POST /api/Chamadas
Content-Type: application/json
Authorization: Bearer {token}

{
  "usuarioIds": ["aluno123"],
  "presenca": [1],
  "justificativa": [null],
  "dataChamada": "2026-01-06",
  "rascunho": false,
  "nivelHierarquico": {
    "horarioId": [1]
  }
}

Caso 2: Registrar Ausências Justificadas em Lote

Cenário: Professor registra ausências justificadas de múltiplos alunos com atestados médicos

POST /api/Chamadas
Content-Type: application/json
Authorization: Bearer {token}

{
  "usuarioIds": ["aluno123", "aluno456"],
  "presenca": [2, 2],
  "justificativa": [
    "Aluno apresentou atestado médico válido até 10/01/2026",
    "Aluno apresentou atestado médico válido até 12/01/2026"
  ],
  "dataChamada": "2026-01-06",
  "rascunho": false,
  "nivelHierarquico": {
    "horarioId": [1]
  }
}

Caso 2.1: Erro na Validação (Atomicidade Total)

Cenário: Um registro falha na validação - todos os registros são rejeitados (atomicidade total)

POST /api/Chamadas
Content-Type: application/json
Authorization: Bearer {token}

{
  "usuarioIds": ["aluno123", "aluno456", "aluno789"],
  "presenca": [1, 2, 0],
  "justificativa": [null, "Atestado médico", null],
  "dataChamada": "2026-01-20",
  "rascunho": false,
  "nivelHierarquico": {
    "horarioId": [1]
  }
}

Resposta (400 Bad Request) - Erro na validação (nenhuma chamada criada):

{
  "status": false,
  "message": "Erro ao criar chamada para o usuário aluno456: A data da chamada não pode ser futura. Apenas administradores da instituição com permissão no nível hierárquico podem criar chamadas para datas futuras.",
  "data": {
    "sucessos": [],
    "erros": [
      {
        "indice": 1,
        "usuarioId": "aluno456",
        "mensagem": "A data da chamada não pode ser futura. Apenas administradores da instituição com permissão no nível hierárquico podem criar chamadas para datas futuras."
      }
    ],
    "totalProcessado": 3,
    "totalSucesso": 0,
    "totalErro": 1
  }
}

Comportamento: Como o registro do índice 1 (aluno456) falhou na validação de data futura, a operação foi abortada imediatamente. Nenhuma chamada foi criada, nem mesmo a do aluno123 que estava válida. O sistema retorna o erro específico do usuário que falhou (aluno456) e o motivo da falha.

Caso 3: Erro - Arrays com Tamanhos Diferentes

Cenário: Tentativa de criar chamadas com arrays de tamanhos diferentes (deve falhar)

POST /api/Chamadas
Content-Type: application/json
Authorization: Bearer {token}

{
  "usuarioIds": ["aluno123", "aluno456"],
  "presenca": [1],
  "justificativa": [null, null],
  "dataChamada": "2026-01-06",
  "rascunho": false,
  "nivelHierarquico": {
    "horarioId": [1]
  }
}

Resposta (400 Bad Request):

{
  "usuarioIds": ["Os arrays UsuarioIds, Presenca e Justificativa devem ter o mesmo tamanho."],
  "presenca": ["Os arrays UsuarioIds, Presenca e Justificativa devem ter o mesmo tamanho."],
  "justificativa": ["Os arrays UsuarioIds, Presenca e Justificativa devem ter o mesmo tamanho."]
}

Caso 4: Pesquisar Chamadas do Mês

Cenário: Buscar todas as chamadas de janeiro de 2026

GET /api/Chamadas?MesAno=01/2026&Pagina=1&TamanhoPagina=20
Authorization: Bearer {token}

Caso 4.1: Buscar Chamadas por ChamadaId

Cenário: Buscar todas as chamadas criadas em uma única operação (mesmo POST)

GET /api/Chamadas?ChamadaId=5
Authorization: Bearer {token}

Resposta: Retorna todos os registros com ChamadaId=5, ou seja, todos os alunos que foram chamados juntos na mesma operação.

Uso: Útil para histórico, auditoria e relatórios de presença por grupo de chamadas.

Caso 5: Salvar Rascunho em Lote

Cenário: Professor salva múltiplas chamadas como rascunho para concluir depois

POST /api/Chamadas
Content-Type: application/json
Authorization: Bearer {token}

{
  "usuarioIds": ["aluno123", "aluno456", "aluno789"],
  "presenca": [0, 1, 0],
  "justificativa": [null, null, null],
  "dataChamada": "2026-01-06",
  "rascunho": true,
  "nivelHierarquico": {
    "horarioId": [1]
  }
}

Caso 6: Atualizar Chamadas por ChamadaId (Processamento em Lote)

Cenário: Professor atualiza todas as chamadas de um mesmo grupo (ChamadaId) de uma vez

PUT /api/Chamadas/chamadaId/5
Content-Type: application/json
Authorization: Bearer {token}

{
  "usuarioIds": ["aluno123", "aluno456", "aluno789"],
  "presenca": [1, 0, 2],
  "justificativa": [null, null, "Atestado médico atualizado"],
  "rascunho": false,
  "ativo": true,
  "dataChamada": "2026-01-06",
  "nivelHierarquico": {
    "horarioId": [1]
  }
}

Resposta (200 OK):

{
  "sucessos": [
    {
      "id": 1,
      "chamadaId": 5,
      "usuarioId": "aluno123",
      "presenca": 1,
      "justificativa": null,
      "dataChamada": "2026-01-06",
      "rascunho": false,
      "ativo": true
    },
    {
      "id": 2,
      "chamadaId": 5,
      "usuarioId": "aluno456",
      "presenca": 0,
      "justificativa": null,
      "dataChamada": "2026-01-06",
      "rascunho": false,
      "ativo": true
    },
    {
      "id": 3,
      "chamadaId": 5,
      "usuarioId": "aluno789",
      "presenca": 2,
      "justificativa": "Atestado médico atualizado",
      "dataChamada": "2026-01-06",
      "rascunho": false,
      "ativo": true
    }
  ],
  "erros": [],
  "totalProcessado": 3,
  "totalSucesso": 3,
  "totalErro": 0
}

Nota: Este endpoint permite atualizar todos os registros de um mesmo ChamadaId de uma vez. Os campos fixos (rascunho, ativo, dataChamada, nivelHierarquico) são aplicados a todos os registros, enquanto os arrays (usuarioIds, presenca, justificativa) são aplicados individualmente a cada registro. Diferente do POST que é atômico, o PUT permite processamento parcial: alguns registros podem ser atualizados com sucesso enquanto outros falham.

⚙️ Regras de Negócio

Permissões por Perfil

Criação (POST)

Perfil Restrições de Data Pode Ativar/Inativar
Professor Não pode criar para datas futuras
Não pode criar para datas anteriores a 14 dias
Não
Admin Instituição (TipoUsuarioId=8) Sem restrições (passado ou futuro) se tiver permissão no nível hierárquico da chamada
Validação usa engenharia reversa da hierarquia
Sim

Edição (PUT)

Perfil Restrições de Edição Pode Editar Concluídas
Professor Dentro de 14 dias: Pode editar rascunhos e concluídas
Após 14 dias: Só pode editar rascunhos (Rascunho = true)
Sim (apenas dentro de 14 dias)
Admin Instituição (TipoUsuarioId=8) Sem restrições (pode editar sempre) se tiver permissão no nível hierárquico da chamada
Pode editar rascunhos e concluídas, independente da data
Sim

Validação de Admin Instituição:

Status de Presença

Valor Nome Justificativa
0 Ausente Opcional
1 Presente Não permitida
2 Justificado Obrigatória

Rascunho vs Concluída

🔧 Detalhes Técnicos

Sistema de ChamadaId e Sequence

Visão Geral: O sistema de ChamadaId foi implementado para agrupar múltiplos registros de chamada criados no mesmo POST, permitindo rastreamento histórico e agrupamento lógico de chamadas relacionadas.

Como Funciona

Quando um professor cria chamadas para múltiplos alunos em um único POST:

Exemplo Prático

POST /api/Chamadas com 3 alunos:
- Aluno A → Id=1, ChamadaId=5
- Aluno B → Id=2, ChamadaId=5
- Aluno C → Id=3, ChamadaId=5

Todos têm o mesmo ChamadaId=5, mas Ids diferentes (1, 2, 3)

Geração Thread-Safe

O ChamadaId é gerado usando uma Sequence (tabela auxiliar + stored procedure) que garante:

Decisão Arquitetural: Por Que Sequence?

Durante o desenvolvimento, foram consideradas três opções para gerar o ChamadaId. A Sequence foi escolhida após análise detalhada das alternativas:

Opção 1: MAX(ChamadaId) + 1 (Rejeitada)

Problemas identificados:

Opção 2: GUID/UUID (Rejeitada)

Problemas identificados:

Opção 3: Sequence do Banco de Dados (Escolhida)

Vantagens que justificam a escolha:

Implementação no MySQL:

Como MySQL não suporta Sequence nativamente (como SQL Server), implementamos uma solução equivalente usando:

Fluxo de Criação

1. POST /api/Chamadas recebe múltiplos alunos
   ↓
2. Service valida TODOS os registros PRIMEIRO
   ↓
3. Se QUALQUER registro falhar na validação:
   ↓
   → Retorna erro imediatamente (nenhuma chamada criada)
   → Erro indica usuário específico que falhou e motivo
   ↓
4. Se TODOS os registros passarem na validação:
   ↓
5. Inicia transação (atomicidade)
   ↓
6. Obtém próximo ChamadaId (thread-safe)
   ↓
7. Aplica o mesmo ChamadaId a todos os registros
   ↓
8. Adiciona todos ao contexto (AddRange)
   ↓
9. Salva tudo de uma vez (SaveChanges único)
   ↓
10. Commit da transação
   ↓
11. Retorna resposta com todas as chamadas criadas

Casos de Uso

Garantias

Para mais detalhes técnicos, consulte o documento ChamadaId_Sequence_Sistema.md.

Timezone

Todas as operações de data/hora utilizam DateTimeHelper.AgoraBrasil() para garantir consistência com o timezone do Brasil (UTC-3).

Nível Hierárquico

O campo NivelHierarquico é armazenado como JSON no banco de dados. Formato:

{
  "instituicaoId": [1, 2],
  "unidadeId": [3],
  "turmaId": [4],
  "horarioId": [5]
}

Todos os campos são opcionais e podem ser arrays de inteiros.

Soft Delete

A exclusão de chamadas é feita via soft delete, definindo Ativo=false. Os registros permanecem no banco de dados para auditoria.

Auditoria

A entidade Chamada herda de EntityTracking, que fornece automaticamente:

📊 Códigos de Resposta HTTP

Código Descrição Quando é Retornado
200 OK Sucesso GET, PUT, DELETE bem-sucedidos
201 Created Criado com sucesso POST bem-sucedido
400 Bad Request Erro de validação Campos inválidos, regras de negócio violadas
401 Unauthorized Não autenticado Token ausente ou inválido
403 Forbidden Usuário não identificado Token não contém UsuarioId
404 Not Found Não encontrado Chamada não existe ou não encontrada

🧪 Exemplos de Teste

cURL

# Criar chamadas em lote - Múltiplos alunos
curl -X POST "https://localhost:5001/api/Chamadas" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer {token}" \
  -d '{
    "usuarioIds": ["aluno123", "aluno456", "aluno789"],
    "presenca": [1, 1, 0],
    "justificativa": [null, null, null],
    "dataChamada": "2026-01-06",
    "rascunho": false,
    "nivelHierarquico": {
      "horarioId": [1]
    }
  }'

# Pesquisar chamadas
curl -X GET "https://localhost:5001/api/Chamadas?MesAno=01/2026&Pagina=1&TamanhoPagina=20" \
  -H "Authorization: Bearer {token}"

# Buscar chamadas por ChamadaId (todas as chamadas criadas juntas)
curl -X GET "https://localhost:5001/api/Chamadas?ChamadaId=5" \
  -H "Authorization: Bearer {token}"

# Atualizar chamadas por ChamadaId (processamento em lote)
curl -X PUT "https://localhost:5001/api/Chamadas/chamadaId/5" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer {token}" \
  -d '{
    "usuarioIds": ["aluno123", "aluno456", "aluno789"],
    "presenca": [1, 0, 2],
    "justificativa": [null, null, "Atestado médico"],
    "rascunho": false,
    "ativo": true,
    "dataChamada": "2026-01-06",
    "nivelHierarquico": {
      "horarioId": [1]
    }
  }'

C# / .NET

using System.Net.Http;
using System.Text;
using System.Text.Json;

var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = 
    new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);

// Criar chamadas em lote
var request = new
{
    usuarioIds = new[] { "aluno123", "aluno456", "aluno789" },
    presenca = new[] { 1, 1, 0 },
    justificativa = new string[] { null, null, null },
    dataChamada = new DateTime(2026, 1, 6),
    rascunho = false,
    nivelHierarquico = new
    {
        horarioId = new[] { 1 }
    }
};

var json = JsonSerializer.Serialize(request);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PostAsync("https://localhost:5001/api/Chamadas", content);
var result = await response.Content.ReadAsStringAsync();

// Atualizar chamadas por ChamadaId (processamento em lote)
var updateRequest = new
{
    usuarioIds = new[] { "aluno123", "aluno456", "aluno789" },
    presenca = new[] { 1, 0, 2 },
    justificativa = new string[] { null, null, "Atestado médico" },
    rascunho = false,
    ativo = true,
    dataChamada = new DateTime(2026, 1, 6),
    nivelHierarquico = new
    {
        horarioId = new[] { 1 }
    }
};

var updateJson = JsonSerializer.Serialize(updateRequest);
var updateContent = new StringContent(updateJson, Encoding.UTF8, "application/json");
var updateResponse = await client.PutAsync("https://localhost:5001/api/Chamadas/chamadaId/5", updateContent);
var updateResult = await updateResponse.Content.ReadAsStringAsync();

Arquivo HTTP

Consulte o arquivo InMinds.CordenaAi.Api/Tests/teste-chamadas.http para exemplos completos de testes HTTP.

🔒 Segurança

Autenticação

Todos os endpoints requerem autenticação via Bearer Token (JWT). O token deve ser enviado no header Authorization:

Authorization: Bearer {token}

Autorização

O sistema utiliza PermissionamentoHelper e FinanceiroPermissionamentoHelper para verificar permissões do usuário:

Auditoria

Todas as operações registram automaticamente:

📚 Glossário

Termo Descrição
Chamada Registro de presença de um aluno em uma aula específica
ChamadaId ID que agrupa múltiplos registros de chamada criados no mesmo POST. Permite rastreamento histórico e agrupamento lógico de chamadas relacionadas.
Presenca Status de presença: 0=Ausente, 1=Presente, 2=Justificado
Justificativa Texto explicativo para ausência ou justificativa (obrigatório para Justificado)
Rascunho Chamada salva temporariamente, pode ser editada ou excluída
Concluída Chamada finalizada (Rascunho=false), não pode ser duplicada
NivelHierarquico Estrutura JSON que vincula a chamada a instituição, unidade, turma e horário
Soft Delete Exclusão lógica (Ativo=false) ao invés de exclusão física do banco

✅ Checklist de Implementação

🔧 Troubleshooting

Problema: Justificativa Obrigatória

Sintoma: Erro ao criar chamada com Presenca=2 sem justificativa

Solução: A justificativa é obrigatória quando Presenca=2 (Justificado). Informe o campo justificativa no request.

Problema: Data Futura

Sintoma: Erro ao criar chamada para data futura (perfil Professor)

Solução: Professores não podem criar chamadas para datas futuras. Use uma data atual ou passada (máximo 14 dias). Admin Instituição (TipoUsuarioId=8) com permissão no nível hierárquico pode criar para qualquer data.

Problema: Chamada Duplicada

Sintoma: Erro ao criar chamada concluída duplicada

Solução: Não é permitido criar chamadas concluídas (Rascunho=false) duplicadas para o mesmo usuário, data e horarioId. Use Rascunho=true ou atualize a chamada existente. Se qualquer registro falhar na validação de duplicidade, nenhuma chamada será criada (atomicidade total).

Problema: Justificativa em Presente

Sintoma: Justificativa informada junto com Presenca=1 (Presente)

Solução: Quando Presenca=1 (Presente), o sistema automaticamente define a justificativa como null, mesmo que você envie uma justificativa no request. Não é necessário remover o campo, pois ele será ignorado automaticamente. A chamada será criada/atualizada normalmente com justificativa = null.

Problema: Arrays com Tamanhos Diferentes

Sintoma: Erro ao criar chamadas com arrays de tamanhos diferentes

Solução: Os arrays usuarioIds, presenca e justificativa devem ter obrigatoriamente o mesmo tamanho. Verifique se todos os arrays têm a mesma quantidade de elementos antes de enviar a requisição.

Problema: Erro na Validação (Atomicidade Total)

Sintoma: Erro ao criar chamadas em lote - nenhuma chamada foi criada mesmo que algumas pareçam válidas

Solução: O sistema processa todas as chamadas de forma totalmente atômica. Se qualquer registro falhar na validação, nenhuma chamada será criada e o erro específico do usuário que falhou será retornado. Verifique a mensagem de erro que indica qual usuário falhou e o motivo. Corrija o problema e tente novamente. Isso garante integridade dos dados e evita estados inconsistentes.

Exemplo: Se você tentar criar chamadas para 3 alunos e o segundo aluno tiver uma data inválida, nenhuma das 3 chamadas será criada. O erro retornado indicará especificamente o segundo aluno e o motivo (ex: "A data da chamada não pode ser futura").

Problema: ChamadaId Duplicado

Sintoma: Preocupação sobre possíveis ChamadaIds duplicados em alta concorrência

Solução: O sistema utiliza Sequence (tabela auxiliar + stored procedure) que garante thread-safety. Múltiplas requisições simultâneas não geram valores duplicados. A geração ocorre dentro de uma transação, garantindo atomicidade.

Problema: Editar Chamada Concluída Após 14 Dias (Professor)

Sintoma: Erro ao tentar editar chamada concluída (Rascunho=false) como professor após 14 dias da data da chamada

Solução: Professores podem editar chamadas concluídas apenas dentro de 14 dias após a data da chamada. Após este prazo, apenas rascunhos podem ser editados por professores. Para editar chamadas concluídas após 14 dias, contate um administrador da instituição com permissão no nível hierárquico da chamada. O cálculo é feito como: (data atual - dataChamada) ≤ 14 dias.

Problema: Editar Rascunho Após 14 Dias (Professor)

Sintoma: Professor consegue editar rascunho após 14 dias, mas não consegue editar chamada concluída

Solução: Isso é comportamento esperado. Após 14 dias da data da chamada, professores podem editar apenas rascunhos (Rascunho=true). Chamadas concluídas não podem ser editadas após este prazo. Dentro dos 14 dias, professores podem editar tanto rascunhos quanto chamadas concluídas.

Problema: Gaps na Sequência de ChamadaId

Sintoma: Sequência de ChamadaId apresenta gaps (ex: 5, 7, 8 - falta o 6)

Solução: Gaps na sequência são comportamento esperado e não indicam problema. Podem ocorrer quando uma transação é revertida (rollback) ou quando um erro ocorre após gerar ChamadaId mas antes de salvar. Isso é normal e não afeta a funcionalidade do sistema.

Problema: Atualizar ChamadaId Inexistente

Sintoma: Erro 404 ao tentar atualizar chamadas por ChamadaId

Solução: Verifique se o ChamadaId existe no banco de dados. Use GET /api/Chamadas?ChamadaId={chamadaId} para verificar se existem registros com esse ChamadaId antes de tentar atualizar.

Problema: Arrays com Tamanho Diferente da Quantidade de Registros

Sintoma: Erro ao atualizar chamadas por ChamadaId - "A quantidade de elementos nos arrays não corresponde à quantidade de registros existentes"

Solução: Os arrays usuarioIds, presenca e justificativa devem ter exatamente a mesma quantidade de elementos que existem registros com o ChamadaId especificado. Use GET /api/Chamadas?ChamadaId={chamadaId} para verificar quantos registros existem antes de atualizar.

Problema: Processamento Parcial no PUT

Sintoma: Alguns registros são atualizados com sucesso enquanto outros falham no PUT

Solução: Isso é comportamento esperado. Diferente do POST que é atômico (tudo ou nada), o PUT permite processamento parcial. A resposta incluirá lista de sucessos e erros. Verifique o campo erros na resposta para identificar quais registros falharam e o motivo.

Problema: Tentar Alterar ChamadaId

Sintoma: ChamadaId não é alterado após atualização

Solução: Isso é comportamento esperado. O campo chamadaId não pode ser alterado após a criação e é preservado automaticamente pelo sistema. O mesmo se aplica aos campos id, usuarioCriacao e criacao.


Versão: 1.0
Última Atualização: Janeiro de 2026
Responsável: Equipe de Desenvolvimento CordenaAi