{
  "id": "migration-19-internship-requests",
  "title": "Migration 19 — internship_requests",
  "description": "Contrato da solicitação única de abertura de estágio preenchida pelo discente.",
  "type": "migration-reference",
  "status": "implemented",
  "visibility": "public",
  "tags": [
    "sge/migrations",
    "sge/estagio",
    "sge/formularios"
  ],
  "related": [
    "enum-legalcapacitydeclaration",
    "enum-internshiprequeststatus",
    "migration-base-04-activity-log",
    "migration-19a-emancipation-evidences",
    "cadastros-pendentes-de-supervisor-e-concedente",
    "backlog-e-decisoes",
    "migration-04-affiliations",
    "migration-09-courses",
    "migration-11-internship-types",
    "migration-12-granting-parties",
    "migration-12a-supervisor-registration-requests",
    "migration-12b-granting-party-registration-requests",
    "migration-15-internships",
    "migration-20-internship-request-corrections"
  ],
  "sourceRefs": [],
  "authors": [],
  "updated": null,
  "diagram": null,
  "body": "> [!success] Estado\n> Migration, Model, factory, relações e testes PostgreSQL implementados. A tabela vem depois de `internships` por causa da FK única `internship_id`; o fluxo de envio, análise, aceite e criação transacional do estágio ainda será implementado.\n\n## Contrato\n\n| Campo | Regra |\n| --- | --- |\n| `id` | bigint, chave primária. |\n| `affiliation_id` | FK obrigatória para o vínculo discente dono da solicitação. |\n| `course_id` | FK obrigatória fora de `Draft`; o curso deve estar disponível no contexto do vínculo. |\n| `internship_type_id` | FK obrigatória fora de `Draft`; o tipo deve estar ativo e pertencer ao curso selecionado. |\n| `advisor_affiliation_id` | FK nullable enquanto o Setor ainda não atribuiu orientador; obrigatória no aceite. |\n| `granting_party_id` | FK nullable para concedente validada do campus do vínculo discente. |\n| `granting_party_registration_request_id` | FK nullable para solicitação pendente do mesmo campus; é alternativa a `granting_party_id`, nunca texto livre solto. |\n| `supervisor_affiliation_id` | FK nullable para o supervisor já cadastrado. |\n| `supervisor_registration_request_id` | FK nullable para a solicitação pendente de cadastro; é alternativa a `supervisor_affiliation_id`. |\n| `student_year_semester` | string nullable em `Draft`; período/semestre declarado pelo discente e congelado no snapshot no aceite. |\n| `legal_capacity_declaration` | [`LegalCapacityDeclaration`](doc:enum-legalcapacitydeclaration) nullable em `Draft`; rádio obrigatório no envio: maior, menor ou menor emancipado. |\n| `legal_guardian_name` | texto nullable; obrigatório quando a opção for `minor`. |\n| `legal_guardian_cpf` | `varchar(255)` nullable; obrigatório quando a opção for `minor`, normalizado e validado pelo `CpfCast`. |\n| `legal_guardian_kinship` | `varchar(80)` nullable; obrigatório quando a opção for `minor`. |\n| `legal_guardian_email` | `varchar(255)` nullable; obrigatório quando a opção for `minor`. |\n| `activities` | texto nullable em `Draft`; obrigatório no envio. |\n| `internship_sector` | texto nullable; setor/área de realização, obrigatório se a concedente ou o tipo o exigir. |\n| `weekly_hours` | JSONB nullable em `Draft`; mapa validado com domingo a sábado. |\n| `planned_start_date` | data nullable em `Draft`; obrigatória no envio e usada para calcular o término. |\n| `projected_end_date` | data nullable calculada no servidor; nunca editável pelo discente. |\n| `is_remunerated` | boolean nullable em `Draft`; obrigatório no envio. |\n| `grant_value` | decimal(10,2) nullable; bolsa obrigatória e positiva se remunerado. |\n| `transportation_allowance` | decimal(10,2) nullable; auxílio pode ser zero ou nulo. |\n| `observations` | texto nullable; observações complementares. |\n| `status` | [`InternshipRequestStatus`](doc:enum-internshiprequeststatus): rascunho, enviada, em análise, com pendência, aceita, recusada ou desistida. |\n| `internship_id` | FK nullable e única para o estágio criado depois do aceite. |\n| `terms_accepted_at` | data e hora do aceite explícito; nulo antes do primeiro envio e atualizado em cada reenvio. Não há coluna de versão, hash ou booleano. |\n| timestamps | auditoria temporal. |\n\nHá somente uma solicitação para o processo aberto pelo discente. Ela é atualizada no mesmo registro quando o discente salva ou responde uma pendência; o Livewire pode persistir valores nulos enquanto estiver em `Draft`. Nos demais estados, todos os campos obrigatórios do caminho condicional escolhido devem estar válidos; campos de ramos não escolhidos continuam nulos. O Model registra criação e alterações no [`activity_log`](doc:migration-base-04-activity-log); com o vínculo ativo como autor em requisições autenticadas; Actions e Policies para o fluxo de análise ainda estão pendentes. Os Models validam que a concedente ou pedido pendente referenciado pertence ao campus do vínculo discente. Não há FK de revisor nesta tabela. Ao ser aceita pela primeira vez, os valores validados criam `internships`, que preserva `student_affiliation_id = affiliation_id`, as demais FKs, a jornada inicial e os snapshots históricos próprios. Se a solicitação já possuir `internship_id`, uma correção aprovada atualiza apenas os campos autorizados do estágio existente, recalcula os derivados e preserva os documentos anteriores no histórico.\n\n### Contrato de `weekly_hours`\n\n```json\n{\n  \"sunday\": 0,\n  \"monday\": 4,\n  \"tuesday\": 4,\n  \"wednesday\": 4,\n  \"thursday\": 4,\n  \"friday\": 4,\n  \"saturday\": 0\n}\n```\n\nAs sete chaves sempre existem quando o formulário é enviado; cada valor é inteiro não negativo em horas. A soma semanal deve ser positiva. Para o tipo selecionado, cada dia deve respeitar `internship_types.max_daily_hours` e a soma da semana deve respeitar `internship_types.max_weekly_hours`. Na configuração do tipo, esses limites começam em 6 horas diárias e 30 semanais e podem ser aumentados, nunca reduzidos. `projected_end_date` deverá ser calculada pelo fluxo a partir desta jornada, da carga exigida do tipo, do calendário nacional, estadual e municipal aplicável ao endereço do local de trabalho e das pausas posteriormente registradas. Por enquanto, o Model exige a data preenchida fora de `Draft` e valida que ela não antecede o início; o cálculo automático ainda não existe.\n\n### Caminhos condicionais de cadastro\n\n- Concedente: exatamente uma de `granting_party_id` ou `granting_party_registration_request_id` deve ser informada no envio. A concedente ou solicitação deve pertencer ao campus do vínculo discente, conferido no servidor. A segunda aponta para um registro próprio, com documento, endereço, representante, contatos, área, conselho e processo, aguardando análise do Setor. CPF/CNPJ iguais em outro cadastro não autorizam reutilizá-lo entre campi.\n- Supervisor: exatamente uma de `supervisor_affiliation_id` ou `supervisor_registration_request_id` deve ser informada no envio. A segunda preserva nome, CPF, telefone, e-mail, cargo, qualificação, formação e experiência propostos pelo discente até que o Setor crie/associe o vínculo de supervisor. O supervisor completa ou confirma seus dados profissionais atuais em `user_personal_data` no futuro fluxo do formulário.\n- Capacidade civil: o rádio oferece `adult`, `minor` e `emancipated_minor`. `adult` só é válido para quem tiver 18 anos completos na data do envio. `minor` exige os quatro campos de responsável legal. `emancipated_minor` dispensará responsável legal e exigirá ao menos uma [evidência](doc:migration-19a-emancipation-evidences) enviada; o aceite final exigirá evidência aprovada pelo Setor. A Migration 19A já implementa no Model a exigência de anexo no envio e de evidência aprovada no aceite; a análise manual e a autorização ficam para o fluxo. Se a devolução apontar comprovante inválido, o discente cria novo envio ou troca para `minor` e informa o responsável.\n- Remuneração: `is_remunerated = true` exige `grant_value`; se for `false`, ambos os valores monetários ficam nulos, salvo decisão posterior que admita auxílio sem bolsa.\n\nAs duas solicitações pendentes de cadastro são estruturas de domínio próprias, conforme [Cadastros pendentes de supervisor e concedente](doc:cadastros-pendentes-de-supervisor-e-concedente) e [D-011](doc:backlog-e-decisoes#d-011-solicitacoes-pendentes-sao-registros-proprios); não são JSON genérico nem notificações. As migrations que as criam devem preceder a FK desta tabela. As duas migrations de cadastro pendente já existem no código; esta tabela usa somente FKs para esses caminhos.\n\n## Validações do formulário\n\n- `affiliation_id` deve ser o vínculo discente ativo e autorizado;\n- o curso deve ser elegível para o vínculo;\n- ao escolher um curso, a interface apresenta apenas seus tipos de estágio ativos: um tipo é selecionado automaticamente quando for único; mais de um exige seleção explícita;\n- a validação no servidor confirma a relação entre curso e tipo, exige que os limites configurados no tipo sejam de pelo menos 6 horas diárias e 30 semanais e valida a jornada diária e semanal contra esses limites;\n- a parte concedente deve estar cadastrada antes de ser aceita; o número de processo de credenciamento é opcional e preenche o documento quando aplicável;\n- a data prevista de término é calculada, e não digitada, a partir do tipo e da jornada.\n- envio e reenvio exigirão checkbox explícito de ciência na interface; somente a data e hora do aceite serão persistidas em `terms_accepted_at`.\n\n## Checklist\n\n- [x] Definir uma solicitação única pertencente ao vínculo discente.\n- [x] Definir a associação única com o estágio criado após o aceite.\n- [x] Definir curso, tipo e parte concedente como referências validadas.\n- [x] Separar o enum de status da solicitação do status de `internships`.\n- [x] Mapear todas as colunas do formulário de abertura e seus casts.\n- [x] Fechar os critérios de validação da parte concedente e do supervisor pendentes.\n- [x] Especificar as migrations tipadas das solicitações de cadastro pendente antes desta migration.\n- [x] Definir o histórico privado de evidências e o aceite das normas por data e hora.\n- [x] Criar migration, Model, factory, relações, índice por vínculo/status e FK única para o estágio.\n- [ ] Criar Policies e fluxos de envio, análise, aceite e reenvio.\n- [x] Testar no Model a compatibilidade de curso/tipo, os limites de carga horária e os ramos condicionais.\n- [ ] Implementar seleção na interface, cálculo da data projetada e fluxo de análise da evidência de emancipação e autorização por vínculo.\n\n## Dependências\n\n- [affiliations](doc:migration-04-affiliations)\n- [courses](doc:migration-09-courses)\n- [internship_types](doc:migration-11-internship-types)\n- [granting_parties](doc:migration-12-granting-parties)\n- [supervisor_registration_requests](doc:migration-12a-supervisor-registration-requests)\n- [granting_party_registration_requests](doc:migration-12b-granting-party-registration-requests)\n- [internships](doc:migration-15-internships)\n- [correções de solicitações](doc:migration-20-internship-request-corrections)\n- [provas de emancipação](doc:migration-19a-emancipation-evidences)",
  "sections": [
    {
      "id": "contrato",
      "level": 2,
      "title": "Contrato",
      "text": "| Campo | Regra | | --- | --- | | `id` | bigint, chave primária. | | `affiliation_id` | FK obrigatória para o vínculo discente dono da solicitação. | | `course_id` | FK obrigatória fora de `Draft`; o curso deve estar disponível no contexto do vínculo. | | `internship_type_id` | FK obrigatória fora de `Draft`; o tipo deve estar ativo e pertencer ao curso selecionado. | | `advisor_affiliation_id` | FK nullable enquanto o Setor ainda não atribuiu orientador; obrigatória no aceite. | | `granting_party_id` | FK nullable para concedente validada do campus do vínculo discente. | | `granting_party_registration_request_id` | FK nullable para solicitação pendente do mesmo campus; é alternativa a `granting_party_id`, nunca texto livre solto. | | `supervisor_affiliation_id` | FK nullable para o supervisor já cadastrado. | | `supervisor_registration_request_id` | FK nullable para a solicitação pendente de cadastro; é alternativa a `supervisor_affiliation_id`. | | `student_year_semester` | string nullable em `Draft`; período/semestre declarado pelo discente e congelado no snapshot no aceite. | | `legal_capacity_declaration` | [`LegalCapacityDeclaration`](doc:enum-legalcapacitydeclaration) nullable em `Draft`; rádio obrigatório no envio: maior, menor ou menor emancipado. | | `legal_guardian_name` | texto nullable; obrigatório quando a opção for `minor`. | | `legal_guardian_cpf` | `varchar(255)` nullable; obrigatório quando a opção for `minor`, normalizado e validado pelo `CpfCast`. | | `legal_guardian_kinship` | `varchar(80)` nullable; obrigatório quando a opção for `minor`. | | `legal_guardian_email` | `varchar(255)` nullable; obrigatório quando a opção for `minor`. | | `activities` | texto nullable em `Draft`; obrigatório no envio. | | `internship_sector` | texto nullable; setor/área de realização, obrigatório se a concedente ou o tipo o exigir. | | `weekly_hours` | JSONB nullable em `Draft`; mapa validado com domingo a sábado. | | `planned_start_date` | data nullable em `Draft`; obrigatória no envio e usada para calcular o término. | | `projected_end_date` | data nullable calculada no servidor; nunca editável pelo discente. | | `is_remunerated` | boolean nullable em `Draft`; obrigatório no envio. | | `grant_value` | decimal(10,2) nullable; bolsa obrigatória e positiva se remunerado. | | `transportation_allowance` | decimal(10,2) nullable; auxílio pode ser zero ou nulo. | | `observations` | texto nullable; observações complementares. | | `status` | [`InternshipRequestStatus`](doc:enum-internshiprequeststatus): rascunho, enviada, em análise, com pendência, aceita, recusada ou desistida. | | `internship_id` | FK nullable e única para o estágio criado depois do aceite. | | `terms_accepted_at` | data e hora do aceite explícito; nulo antes do primeiro envio e atualizado em cada reenvio. Não há coluna de versão, hash ou booleano. | | timestamps | auditoria temporal. |  Há somente uma solicitação para o processo aberto pelo discente. Ela é atualizada no mesmo registro quando o discente salva ou responde uma pendência; o Livewire pode persistir valores nulos enquanto estiver em `Draft`. Nos demais estados, todos os campos obrigatórios do caminho condicional escolhido devem estar válidos; campos de ramos não escolhidos continuam nulos. O Model registra criação e alterações no [`activity_log`](doc:migration-base-04-activity-log); com o vínculo ativo como autor em requisições autenticadas; Actions e Policies para o fluxo de análise ainda estão pendentes. Os Models validam que a concedente ou pedido pendente referenciado pertence ao campus do vínculo discente. Não há FK de revisor nesta tabela. Ao ser aceita pela primeira vez, os valores validados criam `internships`, que preserva `student_affiliation_id = affiliation_id`, as demais FKs, a jornada inicial e os snapshots históricos próprios. Se a solicitação já possuir `internship_id`, uma correção aprovada atualiza apenas os campos autorizados do estágio existente, recalcula os derivados e preserva os documentos anteriores no histórico.",
      "line": 4
    },
    {
      "id": "contrato-de-weekly-hours",
      "level": 3,
      "title": "Contrato de weekly_hours",
      "text": "As sete chaves sempre existem quando o formulário é enviado; cada valor é inteiro não negativo em horas. A soma semanal deve ser positiva. Para o tipo selecionado, cada dia deve respeitar `internship_types.max_daily_hours` e a soma da semana deve respeitar `internship_types.max_weekly_hours`. Na configuração do tipo, esses limites começam em 6 horas diárias e 30 semanais e podem ser aumentados, nunca reduzidos. `projected_end_date` deverá ser calculada pelo fluxo a partir desta jornada, da carga exigida do tipo, do calendário nacional, estadual e municipal aplicável ao endereço do local de trabalho e das pausas posteriormente registradas. Por enquanto, o Model exige a data preenchida fora de `Draft` e valida que ela não antecede o início; o cálculo automático ainda não existe.",
      "line": 39
    },
    {
      "id": "caminhos-condicionais-de-cadastro",
      "level": 3,
      "title": "Caminhos condicionais de cadastro",
      "text": "- Concedente: exatamente uma de `granting_party_id` ou `granting_party_registration_request_id` deve ser informada no envio. A concedente ou solicitação deve pertencer ao campus do vínculo discente, conferido no servidor. A segunda aponta para um registro próprio, com documento, endereço, representante, contatos, área, conselho e processo, aguardando análise do Setor. CPF/CNPJ iguais em outro cadastro não autorizam reutilizá-lo entre campi. - Supervisor: exatamente uma de `supervisor_affiliation_id` ou `supervisor_registration_request_id` deve ser informada no envio. A segunda preserva nome, CPF, telefone, e-mail, cargo, qualificação, formação e experiência propostos pelo discente até que o Setor crie/associe o vínculo de supervisor. O supervisor completa ou confirma seus dados profissionais atuais em `user_personal_data` no futuro fluxo do formulário. - Capacidade civil: o rádio oferece `adult`, `minor` e `emancipated_minor`. `adult` só é válido para quem tiver 18 anos completos na data do envio. `minor` exige os quatro campos de responsável legal. `emancipated_minor` dispensará responsável legal e exigirá ao menos uma [evidência](doc:migration-19a-emancipation-evidences) enviada; o aceite final exigirá evidência aprovada pelo Setor. A Migration 19A já implementa no Model a exigência de anexo no envio e de evidência aprovada no aceite; a análise manual e a autorização ficam para o fluxo. Se a devolução apontar comprovante inválido, o discente cria novo envio ou troca para `minor` e informa o responsável. - Remuneração: `is_remunerated = true` exige `grant_value`; se for `false`, ambos os valores monetários ficam nulos, salvo decisão posterior que admita auxílio sem bolsa.  As duas solicitações pendentes de cadastro são estruturas de domínio próprias, conforme [Cadastros pendentes de supervisor e concedente](doc:cadastros-pendentes-de-supervisor-e-concedente) e [D-011](doc:backlog-e-decisoes#d-011-solicitacoes-pendentes-sao-registros-proprios); não são JSON genérico nem notificações. As migrations que as criam devem preceder a FK desta tabela. As duas migrations de cadastro pendente já existem no código; esta tabela usa somente FKs para esses caminhos.",
      "line": 55
    },
    {
      "id": "validacoes-do-formulario",
      "level": 2,
      "title": "Validações do formulário",
      "text": "- `affiliation_id` deve ser o vínculo discente ativo e autorizado; - o curso deve ser elegível para o vínculo; - ao escolher um curso, a interface apresenta apenas seus tipos de estágio ativos: um tipo é selecionado automaticamente quando for único; mais de um exige seleção explícita; - a validação no servidor confirma a relação entre curso e tipo, exige que os limites configurados no tipo sejam de pelo menos 6 horas diárias e 30 semanais e valida a jornada diária e semanal contra esses limites; - a parte concedente deve estar cadastrada antes de ser aceita; o número de processo de credenciamento é opcional e preenche o documento quando aplicável; - a data prevista de término é calculada, e não digitada, a partir do tipo e da jornada. - envio e reenvio exigirão checkbox explícito de ciência na interface; somente a data e hora do aceite serão persistidas em `terms_accepted_at`.",
      "line": 64
    },
    {
      "id": "checklist",
      "level": 2,
      "title": "Checklist",
      "text": "- [x] Definir uma solicitação única pertencente ao vínculo discente. - [x] Definir a associação única com o estágio criado após o aceite. - [x] Definir curso, tipo e parte concedente como referências validadas. - [x] Separar o enum de status da solicitação do status de `internships`. - [x] Mapear todas as colunas do formulário de abertura e seus casts. - [x] Fechar os critérios de validação da parte concedente e do supervisor pendentes. - [x] Especificar as migrations tipadas das solicitações de cadastro pendente antes desta migration. - [x] Definir o histórico privado de evidências e o aceite das normas por data e hora. - [x] Criar migration, Model, factory, relações, índice por vínculo/status e FK única para o estágio. - [ ] Criar Policies e fluxos de envio, análise, aceite e reenvio. - [x] Testar no Model a compatibilidade de curso/tipo, os limites de carga horária e os ramos condicionais. - [ ] Implementar seleção na interface, cálculo da data projetada e fluxo de análise da evidência de emancipação e autorização por vínculo.",
      "line": 74
    },
    {
      "id": "dependencias",
      "level": 2,
      "title": "Dependências",
      "text": "- [affiliations](doc:migration-04-affiliations) - [courses](doc:migration-09-courses) - [internship_types](doc:migration-11-internship-types) - [granting_parties](doc:migration-12-granting-parties) - [supervisor_registration_requests](doc:migration-12a-supervisor-registration-requests) - [granting_party_registration_requests](doc:migration-12b-granting-party-registration-requests) - [internships](doc:migration-15-internships) - [correções de solicitações](doc:migration-20-internship-request-corrections) - [provas de emancipação](doc:migration-19a-emancipation-evidences)",
      "line": 89
    }
  ],
  "sourcePath": "content/migration-19-internship-requests.md",
  "visuals": [],
  "apiVersion": 1
}
