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
Um sistema completo de gerenciamento de chamadas de alunos que permite:
| 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 |
Controller (ChamadasController)
↓
Service (ChamadaService)
↓
Repository (ChamadaRepository)
↓
Database (AdminDbContext → Tabela Chamadas)
Responsável por:
Responsável por:
Responsável por:
Estrutura da entidade:
Id: Identificador único (int, auto-incremento)ChamadaId: ID que agrupa múltiplos registros criados no mesmo POST (int, gerado por sequence thread-safe)UsuarioId: ID do aluno (string, 450 caracteres)Presenca: Status (int: 0=Ausente, 1=Presente, 2=Justificado)Justificativa: Texto opcional (string, 200 caracteres)NivelHierarquico: JSON com níveis (instituicaoId, unidadeId, turmaId, horarioId)DataChamada: Data da chamada (DATE)Rascunho: Indica se é rascunho (bool, default: true)Ativo: Indica se está ativo (bool, default: true)UsuarioCriacao, Criacao, UsuarioAtualizacao, AtualizacaoDescriçã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:
chamadaId: ID que agrupa múltiplos registros criados no mesmo POSTid: Array de IDs dos registros individuaisusuario: Array de IDs de usuários (alunos)presenca: Array de status de presença (0=Ausente, 1=Presente, 2=Justificado)justificativa: Array de justificativas (pode conter null)rascunho: Status de rascunho compartilhado entre todos os registros do grupoativo: Status ativo compartilhado entre todos os registros do grupodataChamada: Data da chamada compartilhada entre todos os registros do gruponivelHierarquico: Nível hierárquico compartilhado entre todos os registros do grupocriacao, atualizacao, usuarioCriacao, usuarioAtualizacao) compartilhados entre todos os registros do grupoImportante: 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.
Descrição: Obtém uma chamada por ID
Parâmetros:
id (path): ID da chamada (int)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"
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.
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:
chamadaId (path): ID do grupo de chamadas (int). Todos os registros com este ChamadaId serão atualizados.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:
usuarioIds[], presenca[], justificativa[]rascunho, ativo, dataChamada, nivelHierarquicoPermissões de Edição:
Validações:
usuarioIds, presenca e justificativa devem ter o mesmo tamanhoPresenca[i] = 1, Justificativa[i] é automaticamente definida como nullResposta (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:
chamadaId não pode ser alterado (preservado automaticamente)id, usuarioCriacao e criacao são preservados (não podem ser alterados)usuarioAtualizacao e atualizacao são atualizados automaticamenteDescriçã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:
chamadaId (path): ID do grupo de chamadas (int). Todos os registros com este ChamadaId serão inativados.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.
| 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:
UsuarioIds, Presenca e Justificativa devem ter obrigatoriamente o mesmo tamanhoUsuarioIds deve ser válido (não nulo/vazio e máximo 450 caracteres)Presenca deve ser 0, 1 ou 2Justificativa deve ter no máximo 200 caracteres (se não for null)| 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 |
| 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 |
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 |
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.
| 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 |
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.
HorarioId=27 e o Admin tem permissão apenas para InstituicaoId=2, o sistema verifica se o horário pertence à instituição (Horario → Turma → Unidade → Instituição)usuariopermissao verificando UsuarioId, TipoUsuarioId=8 e acesso ao nível hierárquicoCená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
}
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]
}
}
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]
}
}
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.
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."]
}
Cenário: Buscar todas as chamadas de janeiro de 2026
GET /api/Chamadas?MesAno=01/2026&Pagina=1&TamanhoPagina=20
Authorization: Bearer {token}
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.
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]
}
}
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.
| 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 |
| 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:
usuariopermissao: UsuarioId, TipoUsuarioId=8 e Status=trueValidarAcessoNivelHierarquicoAsync faz engenharia reversa automaticamente:
HorarioId=27 e o Admin tem InstituicaoId=2, verifica se o horário pertence à instituição (Horario → Turma → Unidade → Instituição)TurmaId=5 e o Admin tem UnidadeId=3, verifica se a turma pertence à unidade (Turma → Unidade)UnidadeId=4 e o Admin tem InstituicaoId=1, verifica se a unidade pertence à instituição (Unidade → Instituição)| Valor | Nome | Justificativa |
|---|---|---|
| 0 | Ausente | Opcional |
| 1 | Presente | Não permitida |
| 2 | Justificado | Obrigatória |
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.
Quando um professor cria chamadas para múltiplos alunos em um único POST:
Id únicoChamadaId permite buscar todas as chamadas criadas juntasPOST /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)
O ChamadaId é gerado usando uma Sequence (tabela auxiliar + stored procedure) que garante:
Durante o desenvolvimento, foram consideradas três opções para gerar o ChamadaId. A Sequence foi escolhida após análise detalhada das alternativas:
Problemas identificados:
Problemas identificados:
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:
ChamadaIdSequence: Armazena o último valor geradoGetNextChamadaId(): Garante thread-safety com transaçãoObterProximoChamadaIdAsync(): Interface C# para obter próximo valor1. 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
GET /api/Chamadas?ChamadaId=5Para mais detalhes técnicos, consulte o documento ChamadaId_Sequence_Sistema.md.
Todas as operações de data/hora utilizam DateTimeHelper.AgoraBrasil() para garantir consistência com o timezone do Brasil (UTC-3).
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.
A exclusão de chamadas é feita via soft delete, definindo Ativo=false. Os registros permanecem no banco de dados para auditoria.
A entidade Chamada herda de EntityTracking, que fornece automaticamente:
UsuarioCriacao: ID do usuário que criouCriacao: Data/hora de criaçãoUsuarioAtualizacao: ID do usuário que atualizouAtualizacao: Data/hora da última atualização| 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 |
# 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]
}
}'
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();
Consulte o arquivo InMinds.CordenaAi.Api/Tests/teste-chamadas.http para exemplos completos de testes HTTP.
Todos os endpoints requerem autenticação via Bearer Token (JWT). O token deve ser enviado no header Authorization:
Authorization: Bearer {token}
O sistema utiliza PermissionamentoHelper e FinanceiroPermissionamentoHelper para verificar permissões do usuário:
Todas as operações registram automaticamente:
| 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 |
Chamadas criada no banco de dadosChamada e enum PresencaStatus criadosPesquisarAsync e filtros dinâmicosSintoma: 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.
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.
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).
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.
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.
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").
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.
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.
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.
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.
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.
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.
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.
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