# Citrus — Sistema de Gestão de Diárias
## Spec 2: Efetivo (Ponto Diário) — Documento de Design

| Campo | Valor |
| :---- | :---- |
| Projeto | Citrus Engenharia — Sistema de Gestão de Diárias |
| Spec | 2 de N (Efetivo / ponto diário) |
| Data | 2026-06-18 |
| Stack | Docker + PHP 8.2 + Yii2 + MySQL 8.0 + Bootstrap5 |
| Branch | `citrus-efetivo` |
| Requisitos-fonte | `Citrus_Requisitos_v1.0.docx.md` §4 (e §10 mídia/GPS) |
| Depende de | Spec 1 (Fundação + Cadastros): Profissional, Funcao, Obra, BaseActiveRecord (soft-delete + auditoria), PermissaoBehavior, app-shell responsivo, ExportHelper |

---

## 0. Contexto e escopo

Este spec entrega o **registro diário de presença** (módulo §4): o gesto central do app, acionado
pelo FAB lime no mobile. O líder registra, em campo, quem trabalhou em cada obra a cada dia.

**No escopo:** seleção de contexto (obra/data), inserção de profissionais com busca inteligente,
horas, observação, GPS automático por registro, foto da equipe por obra+dia, lista do dia com
edição (saída/observação) e exclusão (soft-delete + auditoria), redirecionamento pós-login do líder.

**Fora do escopo (→ Spec 3):** Controle de Efetivo consolidado (filtros, totalizador de diárias,
export Excel — §5.2), dashboard, fechamento de card.

Decisões de design tomadas no brainstorming:
- **Foto da equipe por obra+dia** (uma só), **GPS por registro** de profissional.
- **Fluxo inline** (busca + horas + adicionar numa tela; lista do dia logo abaixo).

---

## 1. Modelo de dados

Estrutura **header/detail** (o cabeçalho do dia guarda a foto da equipe; as linhas guardam cada
profissional e seu GPS):

### `efetivo_dia` (cabeçalho do dia de trabalho numa obra)
| Coluna | Tipo | Observações |
| :-- | :-- | :-- |
| id | pk | |
| obra_id | int FK → obra | |
| data | date | |
| foto | string null | caminho da foto da equipe (fora do webroot) |
| created_by | int FK → profissional, null | quem registrou o dia |
| created_at / updated_at / deleted_at | int | timestamps + soft-delete |

Único `(obra_id, data)` entre não-excluídos (a unicidade é garantida via *find-or-create* + checagem
no app; índice em `(obra_id, data)` para performance).

### `efetivo` (linha por profissional no dia)
| Coluna | Tipo | Observações |
| :-- | :-- | :-- |
| id | pk | |
| efetivo_dia_id | int FK → efetivo_dia | |
| profissional_id | int FK → profissional | |
| hora_entrada | time | obrigatória |
| hora_saida | time null | opcional; editável depois |
| observacao | text null | editável depois |
| latitude / longitude | decimal(10,7) null | GPS no momento do registro; nulo se negado |
| created_at / updated_at / deleted_at | int | timestamps + soft-delete |

Unicidade `(efetivo_dia_id, profissional_id)` entre não-excluídos — **validada no model** (consulta
os não-deletados), pois soft-delete não combina com índice único puro no MySQL; índice não-único em
`(efetivo_dia_id, profissional_id)` para performance.

Ambos os models herdam `BaseActiveRecord` (timestamps + `AuditBehavior` + `SoftDeleteQuery`).
Relações: `EfetivoDia hasMany Efetivo (via efetivo_dia_id)`; `Efetivo belongsTo Profissional`,
`Efetivo belongsTo EfetivoDia`; `EfetivoDia belongsTo Obra`.

---

## 2. Fluxo e telas (inline)

Tela única `efetivo/registrar` (o `create` do FAB). Composição:

### 2.1 Seleção de contexto (topo)
| Campo | Regra |
| :-- | :-- |
| Obra | Select. **Líder:** apenas obras vinculadas a ele (`lider1_id` = ele OU `lider2_id` = ele) e ativas. **Gerente/Admin:** todas as obras ativas. |
| Data | **Líder:** travada em hoje (o servidor ignora qualquer data recebida e usa `date('Y-m-d')`). **Gerente/Admin:** qualquer data, inclusive retroativa (date input). |

### 2.2 Foto da equipe
Tile de upload no cabeçalho do dia — uma foto por (obra, data). Detalhes em §5.

### 2.3 Bloco de inserção de profissional (inline)
| Campo | Regra |
| :-- | :-- |
| Busca nome/CPF | Autocomplete (§3); ao selecionar exibe nome + função. |
| Hora de entrada | `HH:MM`, obrigatória; pré-preenchida com a hora atual (editável). |
| Hora de saída | `HH:MM`, opcional no momento do registro. |
| Observação | Texto, opcional. |
| GPS | Capturado automaticamente no Adicionar (§4); não exibido. |
| Adicionar | *find-or-create* do `efetivo_dia(obra, data)` e cria a linha `efetivo`. |

### 2.4 Lista do efetivo do dia
Abaixo do formulário, lista os profissionais já lançados para aquela obra/data:
- **Mobile:** cards. **Desktop:** tabela (mesmo padrão responsivo do Spec 1; layout em 2 colunas no
  desktop — formulário à esquerda, lista do dia à direita).
- Colunas/itens: Profissional · Função, Hora entrada, Hora saída (**editável**), Observação
  (**editável**), Ação **excluir** (soft-delete + auditoria).

---

## 3. Busca inteligente de profissional

Action AJAX `efetivo/buscar-profissional?q=<termo>` → JSON com profissionais **ativos** (todos, não
restritos à obra) cujo nome **ou** CPF casa com o termo; retorna `id`, `nome`, `funcao`. Autocomplete
client-side simples (sem nova dependência). Filtra fora os profissionais já lançados naquele dia/obra
(para não duplicar).

---

## 4. GPS (silencioso, não bloqueante)

No clique em **Adicionar**, o cliente chama `navigator.geolocation.getCurrentPosition`; em sucesso,
preenche os campos ocultos `latitude`/`longitude` e submete. Em recusa/erro/timeout, submete com os
campos nulos — **o registro nunca é bloqueado por GPS**. Não há UI própria (a permissão do navegador
na 1ª vez é inevitável). Salvo na linha `efetivo`.

---

## 5. Foto da equipe (upload)

- Upload no cabeçalho do dia (uma por `efetivo_dia`).
- Imagem **redimensionada no servidor** com GD (lado máx. ~1280px, re-encode JPEG) para conter o
  tamanho de fotos de celular.
- Armazenada **fora do webroot** em `uploads/efetivo/` (adicionado ao `.gitignore`); nome derivado do
  `efetivo_dia_id`.
- Servida por uma action protegida `efetivo/foto/<efetivoDiaId>` (gate por `PermissaoBehavior`).
- Substituir a foto remove/sobrescreve a anterior.

---

## 6. Permissões e roteamento

- Reuso do `PermissaoBehavior`. A tabela `permissao` (semeada no Spec 1) já concede `efetivo`:
  `lider`/`gerente` (index/view/create/update) e `administrativo` (total). As actions auxiliares
  (`buscar-profissional`, `foto`, `upload-foto`, `atualizar-linha`, `excluir`) são exigidas com
  login + permissão de `efetivo` (mapear via `PermissaoBehavior` com `except`/`AccessControl` para as
  que não casam 1:1 com index/create/update/delete, seguindo o padrão usado em `valor-funcao` no
  Spec 1).
- **Líder** só acessa/registra nas obras vinculadas a ele.
- **Pós-login** (`SiteController`): líder "puro" (`temPerfil('lider')` e **não** gerente/admin) é
  redirecionado para `efetivo/registrar`; demais perfis vão para o dashboard.

---

## 7. Edição, soft-delete e auditoria

- Editar `hora_saida` e `observacao` de uma linha já lançada → `UPDATE` (auditado automaticamente).
- Excluir uma linha → soft-delete (`deleted_at`) + auditoria, via `BaseActiveRecord::delete()`.
- `efetivo_dia` sem nenhuma linha ativa pode permanecer (com a foto); não é excluído automaticamente.

---

## 8. Arquivos

- **Models:** `models/EfetivoDia.php`, `models/Efetivo.php`.
- **Migrations:** `create_efetivo_dia_table`, `create_efetivo_table`.
- **Controller:** `controllers/EfetivoController.php` (substitui o stub) com actions: `registrar`
  (a tela inline; é o destino de `create`/`index` do efetivo), `buscar-profissional`,
  `adicionar` (cria a linha), `atualizar-linha`, `excluir`, `upload-foto`, `foto`.
- **Views:** `views/efetivo/registrar.php` + parciais (`_form-inserir.php`, `_lista-dia.php`).
- **Assets:** `web/js/efetivo.js` (autocomplete de busca, captura GPS, edição inline da lista).
- **Ajuste:** `controllers/SiteController.php` (redirect pós-login por perfil).
- **Infra:** `.gitignore` += `uploads/`; criar `uploads/efetivo/`.

---

## 9. Testes (Codeception, contra `citrus_test`)

- **Unit:** unicidade profissional/dia/obra (não permite duplicar ativo; permite re-lançar após
  soft-delete); *find-or-create* do `efetivo_dia`; validação `hora_saida ≥ hora_entrada` quando
  ambas presentes; data travada em hoje para líder (servidor ignora data enviada).
- **Functional:** líder lança numa obra vinculada (sucesso) e é negado numa obra de outro líder
  (403/sem acesso); admin lança em data retroativa; editar hora de saída de uma linha; excluir uma
  linha (some da lista, auditoria registrada); `buscar-profissional` retorna só ativos e exclui já
  lançados; pós-login do líder cai em `efetivo/registrar`.

---

## 10. Considerações

- Cálculo de **diárias** (totalizador) e a **visão consolidada** ficam no Spec 3 (Controle de Efetivo).
- O modelo header/detail já prepara o terreno para o fechamento de card (Spec 4), que agrupa diárias
  por obra a partir das linhas de `efetivo`.
