diff --git a/Codigos/notif_ponto.md b/Codigos/notif_ponto.md index 835fb75..19530c4 100644 --- a/Codigos/notif_ponto.md +++ b/Codigos/notif_ponto.md @@ -2,7 +2,7 @@ title: Notificação de Ponto description: Sistema de notificação ponto published: true -date: 2026-08-15T14:34:50.807Z +date: 2026-08-19T21:23:08.082Z tags: editor: markdown dateCreated: 2026-08-15T01:00:06.922Z @@ -15,7 +15,6 @@ dateCreated: 2026-08-15T01:00:06.922Z Construir uma aplicação em Go que, por mensagens no Telegram, ajude aproximadamente dez usuários a não esquecerem a marcação de ponto. Cada usuário possui sua própria credencial na API de ponto. O sistema consulta o histórico de marcações, calcula os próximos eventos relevantes e envia lembretes de: - retorno do almoço (`entrada2`); - - encerramento do expediente (`saida2`). Nesta primeira fase, o Telegram é a única interface. Uma interface web, destinada a um único administrador, será adicionada posteriormente sem duplicar as regras de negócio. @@ -25,35 +24,22 @@ Nesta primeira fase, o Telegram é a única interface. Uma interface web, destin Incluído: - bot Telegram para usuários e administrador; - - solicitação e aprovação de cadastro pelo Telegram; - - armazenamento em SQLite; - - credencial de API individual por usuário, armazenada de forma criptografada; - - autenticação na API de ponto e renovação automática do token; - - leitura dos últimos oito apontamentos; - - validação de pontos do dia atual; - - criação persistente e envio único de lembretes; - - execução em um container LXC Linux no Proxmox; - - logs, auditoria e backup do banco. Fora do escopo inicial: - painel web administrativo; - - regras para feriados municipais, estaduais e nacionais; - - lembretes de `entrada1` e `saida1`; - - múltiplos administradores; - - integrações além de Telegram e API de ponto. ## 3. Arquitetura @@ -61,65 +47,118 @@ Fora do escopo inicial: O projeto será um monólito modular em Go. Para o volume esperado, um único processo, um SQLite e um LXC são suficientes, simples de manter e adequados para estudo. ```mermaid - flowchart TD - T["Usuários e administrador
Telegram"] --> B["Adaptador Telegram"] + B --> UC["Casos de uso por comando"] - B --> S["Serviços de aplicação"] - - S --> U["Usuários e cadastro"] - - S --> P["Cliente da API de ponto"] - - S --> R["Motor de regras"] - - S --> Q["Agendador de notificações"] - - S --> C["Calendário de dias úteis"] + UC --> U["Usuários e cadastro"] + UC --> P["Consulta de jornada atual"] + UC --> R["Motor de regras e cálculos"] + UC --> Q["Agendador de notificações"] + UC --> C["Calendário de dias úteis"] U --> DB[("SQLite")] - R --> DB - Q --> DB - P --> API["API externa de ponto"] - Q --> TG["API do Telegram"] - W["Fase 2: painel web do administrador"] -. reutiliza os mesmos serviços .-> S - + W["Fase 2: painel web do administrador"] -. reutiliza os mesmos casos de uso .-> UC ``` ### Princípios -- O bot Telegram não contém cálculo de jornada: ele apenas recebe comandos e envia mensagens. - +- O bot Telegram não contém cálculo de jornada: ele converte cada comando em um caso de uso e envia sua resposta. - A API de ponto fica isolada em um módulo próprio. - - As regras de horário ficam em serviços testáveis, sem dependência de Telegram, SQLite ou HTTP. - - O SQLite registra estado suficiente para evitar mensagens duplicadas após reinício. - - Tokens de API ficam somente em memória; senha e token nunca são escritos em logs. +### Arquitetura orientada a comandos + +Cada comando do Telegram terá um caso de uso explícito. Isso evita que o arquivo do bot vire um conjunto de regras misturadas e permite que o futuro painel web reaprove exatamente a mesma lógica. + +```mermaid +flowchart LR + CMD["/entrada2, /saida2,
/status, /cadastrar"] --> H["Handler Telegram"] + H --> U["Caso de uso"] + U --> J["Consulta de jornada"] + J --> API["API de ponto"] + U --> R["Regras de cálculo"] + U --> DB[("SQLite")] + U --> H + H --> MSG["Resposta Telegram"] +``` + +O agendador é outro consumidor dos mesmos casos de uso. Portanto, o cálculo retornado em `/saida2` será exatamente o mesmo usado para criar uma notificação automática de `saida2`. + +## 3.1 Comandos da fase 1 + +| Comando | Quem usa | Atividade | Resposta esperada | +| --- | --- | --- | --- | +| `/entrada2` | Usuário ativo | Consulta o histórico atual e calcula `saida1 + 55 min` quando houver ao menos dois pontos. | Horário previsto de retorno ou explicação do ponto que ainda falta. | +| `/saida2` | Usuário ativo | Consulta o histórico atual e calcula a saída para completar 7h55 quando houver três pontos. | Horário previsto de saída ou explicação do ponto que ainda falta. | +| `/status` | Usuário ativo | Consulta e resume os pontos atuais e os agendamentos do dia. | Situação da jornada e próximos lembretes. | +| `/iniciar` | Qualquer pessoa | Identifica o `chat_id` e apresenta instruções. | Boas-vindas ou orientação para cadastro. | +| `/cadastrar` | Pessoa não cadastrada | Cria solicitação de cadastro para o administrador. | Confirmação de solicitação pendente. | +| `/aprovar ` | Administrador | Ativa um pedido de cadastro. | Confirmação e orientação ao novo usuário. | +| `/usuarios` | Administrador | Lista usuários e solicitações pendentes. | Resumo administrativo. | +| `/ajuda` | Todos | Mostra somente os comandos permitidos ao perfil atual. | Guia de uso. | + +### Regras dos comandos de consulta + +`/entrada2` e `/saida2` fazem uma consulta imediata à API do próprio usuário, independentemente do próximo ciclo do agendador. Eles não enviam lembretes nem mudam um agendamento existente; apenas mostram a projeção atual. + +Exemplos de respostas: + +```text +/entrada2 +Você saiu para almoço às 12:00. +Seu retorno previsto é às 12:55. +``` + +```text +/saida2 +Você trabalhou 4h00 antes do almoço e retornou às 13:00. +Para completar 7h55, sua saída prevista é às 16:55. +``` + +Quando não houver pontos suficientes, a mensagem deve ser clara: + +```text +Ainda não consigo calcular sua saída: falta registrar a entrada após o almoço. +``` + +## 3.2 Evolução: comando `/registrar` + +`/registrar` será uma funcionalidade futura e só poderá efetivamente registrar um ponto se a API externa oferecer um endpoint autorizado de criação de apontamento. A API conhecida até agora apenas autentica e consulta o histórico; ela não confirma a existência dessa operação. + +O desenho recomendado é preparar uma abstração sem fingir que o ponto foi registrado: + +```text +PointGateway +├── Authenticate(user) +├── GetHistory(user) +└── RegisterPoint(user, instant) // futura implementação, se a API permitir +``` + +Possíveis comportamentos futuros: + +- Se houver endpoint oficial: `/registrar` pede confirmação, chama a API e mostra o resultado recebido. +- Se não houver endpoint oficial: o comando não deve dizer que bateu o ponto; pode abrir orientação, registrar uma solicitação manual ou apenas informar que a marcação deve ser feita no sistema oficial. + +Por segurança, o fluxo com endpoint oficial deve exigir confirmação explícita, por exemplo `/registrar` seguido de botão **Confirmar registro às 16:55**. A auditoria deve gravar intenção, horário, resposta da API e resultado. + ## 4. Integração com a API de ponto Cada usuário tem e-mail e senha próprios. 1. Fazer `POST` de autenticação com e-mail e senha. - 2. Receber `client`, `token` e validade do token. - 3. Manter esses valores em memória, associados ao usuário. - 4. Fazer `GET` do histórico com token, client e e-mail. - 5. A resposta contém os últimos oito apontamentos. - 6. Se o histórico retornar erro de autenticação, autenticar novamente, atualizar `client`, `token` e validade e repetir a consulta uma única vez. - 7. Se o novo teste falhar, registrar erro de auditoria e tentar no próximo ciclo do agendador. > As senhas precisam ser criptografadas no SQLite. A chave de criptografia deve ser uma variável de ambiente do LXC, fora do repositório Git. @@ -133,9 +172,7 @@ Antes de consultar agendamentos ou a API, verificar se a data é segunda a sexta Validação de feriados de Belo Horizonte é uma evolução posterior. Desde o início, ela terá uma interface própria (`HolidayProvider`) para que possa ser incluída sem alterar o fluxo central: ```text - IsBusinessDay(data, localidade) = segunda a sexta E não é feriado - ``` Na fase 1, `não é feriado` será considerado verdadeiro; a localidade padrão será `Belo Horizonte/MG`. @@ -145,19 +182,13 @@ Na fase 1, `não é feriado` será considerado verdadeiro; a localidade padrão O histórico vem com até oito registros. A aplicação deve: 1. Converter horários para `America/Sao_Paulo`. - 2. Manter apenas registros da data atual. - 3. Ordenar por hora crescente. - 4. Considerar, no máximo, quatro pontos do dia. - 5. Interpretar a sequência como: ```text - entrada1 → saida1 → entrada2 → saida2 - ``` | Pontos no dia | Situação | @@ -173,9 +204,7 @@ entrada1 → saida1 → entrada2 → saida2 Com dois pontos no dia, considerar que eles são `entrada1` e `saida1`. ```text - horário do lembrete de entrada2 = saida1 + 55 minutos - ``` Exemplo: com saída para almoço às 12:00, o lembrete é agendado para 12:55. @@ -185,11 +214,9 @@ Exemplo: com saída para almoço às 12:00, o lembrete é agendado para 12:55. Com três pontos no dia, considerar que eles são `entrada1`, `saida1` e `entrada2`. O objetivo diário é 7h55 de trabalho efetivo. ```text - trabalho_antes_do_almoco = saida1 - entrada1 tempo_restante = 7h55 - trabalho_antes_do_almoco horario_estimado_saida2 = entrada2 + tempo_restante - ``` O lembrete deve ser agendado para o horário calculado menos uma antecedência configurável. Inicialmente, sugerido: cinco minutos. @@ -218,9 +245,7 @@ bruno14082026saida2 A garantia técnica deve ser a restrição única: ```text - UNIQUE(user_id, reference_date, notification_type) - ``` Isso evita colisões se nome de usuário mudar ou dois nomes forem semelhantes. @@ -246,15 +271,18 @@ flowchart TD I["Ciclo do agendador, por usuário"] --> D{"É dia útil?
Segunda a sexta"} D -->|Não| X["Encerrar ciclo"] D -->|Sim| A{"Existe agendamento vencido
com execute_send = true?"} + A -->|Sim| T["Enviar mensagem Telegram"] T --> M["Registrar status sent e sent_at"] M --> X + A -->|Não| H["Consultar histórico de pontos"] H --> E{"Erro de autenticação?"} E -->|Sim| L["Autenticar novamente"] L --> R["Atualizar client, token e validade"] R --> H E -->|Não| V["Filtrar pontos de hoje e ordenar"] + V --> P{"Quantidade de pontos"} P -->|0 ou 1| X P -->|2| EN["Criar, se inexistente:
entrada2 = saida1 + 55 min"] @@ -268,115 +296,71 @@ flowchart TD ## 8. Estrutura de pastas e arquivos ```text - bot-pontos/ - ├── cmd/ - │ └── bot/ - │ └── main.go - ├── internal/ - │ ├── app/ - │ │ └── application.go - │ ├── config/ - │ │ └── config.go - │ ├── domain/ - │ │ ├── user.go - │ │ ├── workday.go - │ │ ├── notification.go - │ │ └── point.go - +│ ├── usecase/ +│ │ ├── get_entrada2.go +│ │ ├── get_saida2.go +│ │ ├── get_status.go +│ │ ├── register_user.go +│ │ ├── approve_user.go +│ │ └── register_point.go # contrato futuro │ ├── pointsapi/ - │ │ ├── client.go - │ │ ├── auth.go - │ │ └── models.go - │ ├── rules/ - │ │ ├── point_validator.go - │ │ ├── schedule_calculator.go - │ │ └── business_day.go - │ ├── scheduler/ - │ │ └── scheduler.go - │ ├── telegram/ - │ │ ├── bot.go - -│ │ ├── commands.go - +│ │ ├── command_router.go +│ │ ├── entrada2_handler.go +│ │ ├── saida2_handler.go +│ │ ├── status_handler.go +│ │ ├── admin_handler.go │ │ └── messages.go - │ ├── repository/ - │ │ ├── sqlite.go - │ │ ├── users.go - │ │ ├── notifications.go - │ │ └── audit.go - │ ├── security/ - │ │ └── credentials.go - │ └── audit/ - │ └── logger.go - ├── migrations/ - │ ├── 001_initial_schema.sql - │ └── 002_indexes.sql - ├── configs/ - │ └── config.example.yaml - ├── deploy/ - │ ├── bot-pontos.service - │ └── backup-sqlite.sh - ├── tests/ - │ ├── rules_test.go - │ └── scheduler_test.go - ├── .env.example - ├── .gitignore - ├── go.mod - ├── go.sum - ├── README.md - └── PLANO_PROJETO_BOT_PONTOS.md - ``` ### Responsabilidade de cada arquivo @@ -387,6 +371,12 @@ bot-pontos/ | `internal/app/application.go` | Conecta dependências e descreve os casos de uso principais. | | `internal/config/config.go` | Lê variáveis de ambiente e parâmetros: Telegram, banco, criptografia, fuso e intervalo do ciclo. | | `internal/domain/*.go` | Entidades puras: usuário, ponto, jornada e notificação; sem HTTP ou SQLite. | +| `internal/usecase/get_entrada2.go` | Orquestra a consulta imediata e retorna o horário previsto de `entrada2`. | +| `internal/usecase/get_saida2.go` | Orquestra a consulta imediata e retorna o cálculo de `saida2` para completar 7h55. | +| `internal/usecase/get_status.go` | Resume os pontos atuais e os agendamentos do usuário. | +| `internal/usecase/register_user.go` | Cria e consulta solicitações de cadastro. | +| `internal/usecase/approve_user.go` | Aprova cadastro e ativa o usuário. | +| `internal/usecase/register_point.go` | Reserva o contrato do futuro `/registrar`; não registra nada enquanto a API não oferecer suporte oficial. | | `internal/pointsapi/client.go` | Requisições HTTP à API; consulta o histórico. | | `internal/pointsapi/auth.go` | Login, cache em memória e renovação de token por usuário. | | `internal/pointsapi/models.go` | Estruturas de request/response da API externa e conversão para o domínio. | @@ -395,7 +385,8 @@ bot-pontos/ | `internal/rules/business_day.go` | Verifica segunda a sexta; depois receberá o provedor de feriados de Belo Horizonte. | | `internal/scheduler/scheduler.go` | Executa o ciclo periódico: envia vencidos ou consulta a API/cria agendamentos. | | `internal/telegram/bot.go` | Mantém conexão e atualização do bot Telegram. | -| `internal/telegram/commands.go` | Trata `/iniciar`, `/status`, `/cadastrar`, `/aprovar` e comandos administrativos. | +| `internal/telegram/command_router.go` | Autoriza o perfil e encaminha cada comando ao respectivo handler. | +| `internal/telegram/*_handler.go` | Converte o comando Telegram em chamada de caso de uso e formata a resposta. | | `internal/telegram/messages.go` | Centraliza textos das notificações e respostas do bot. | | `internal/repository/sqlite.go` | Abre SQLite, aplica migrations e configura modo WAL. | | `internal/repository/users.go` | CRUD de usuários, pedidos de cadastro e permissões. | @@ -410,81 +401,46 @@ bot-pontos/ ## 9. Modelo inicial de banco ```text - users - - id - - telegram_chat_id (único) - - display_name - - api_email - - encrypted_api_password - - role (user | admin) - - status (pending | active | disabled) - - created_at - - updated_at work_rules - - id - - user_id (único) - - lunch_reminder_after_minutes (padrão 55) - - daily_work_minutes (padrão 475 = 7h55) - - exit_reminder_lead_minutes (padrão 5) - - timezone (America/Sao_Paulo) - - active scheduled_notifications - - id - - notification_key - - user_id - - reference_date - - notification_type (entrada2 | saida2) - - scheduled_at - - execute_send - - status - - sent_at - - telegram_message_id - - created_at - - updated_at audit_logs - - id - - user_id (opcional) - - event_type - - details - - created_at - ``` ## 10. Ambiente LXC no Proxmox @@ -492,21 +448,13 @@ audit_logs Recomendação inicial: - Debian 12; - - 1 vCPU; - - 1 GB de RAM; - - 8 a 16 GB de disco; - - fuso horário `America/Sao_Paulo`; - - saída HTTPS para a API de ponto e para o Telegram; - - serviço Go controlado por `systemd`; - - banco em `/var/lib/bot-pontos/app.db`; - - backup diário do SQLite e backup periódico do próprio LXC pelo Proxmox. Em produção, o LXC precisa apenas do binário Go compilado, certificados CA, dados de fuso horário e SQLite. O compilador Go pode ficar apenas no ambiente de desenvolvimento ou CI. @@ -514,58 +462,36 @@ Em produção, o LXC precisa apenas do binário Go compilado, certificados CA, d ## 11. Sequência de implementação para estudo 1. Criar repositório, estrutura de pastas e projeto Go (`go mod init`). - 2. Implementar configuração, logs e SQLite com migrations. - 3. Criar entidades de domínio e testes dos cálculos de 55 minutos e 7h55. - 4. Implementar o cliente da API com mocks e teste de reautenticação. - 5. Implementar persistência de usuários e agendamentos com a chave única. - 6. Construir o ciclo do agendador usando dados simulados. - 7. Integrar o Telegram: cadastro, aprovação e mensagens. - 8. Fazer testes de ponta a ponta com um único usuário de teste. - 9. Criar LXC, instalar o serviço e configurar backup. - 10. Cadastrar os demais usuários gradualmente. ## 12. Casos de teste mínimos - Sábado ou domingo: nenhuma consulta e nenhuma notificação. - - Dois pontos: cria apenas um agendamento de `entrada2` para `saida1 + 55 min`. - - Três pontos: cria apenas um agendamento de `saida2` para completar 7h55. - - Quatro pontos: não cria lembrete. - - Mesmo ciclo rodado duas vezes: não duplica a mesma chave de agendamento. - - Reinício da aplicação: agendamento persiste e é enviado uma vez. - - Token expirado: autentica novamente e repete a consulta. - - Falha contínua na API ou Telegram: registra auditoria sem travar os demais usuários. ## 13. Evoluções posteriores - Feriados de Belo Horizonte/MG e feriados nacionais por um `HolidayProvider` (API ou calendário local com atualização controlada). - - Painel web para o administrador com autenticação. - - Tela de usuários, regras, agendamentos, auditoria e erros. - - Revalidação da API antes de cada envio para eliminar lembretes obsoletos. - - Painel de saúde da aplicação e alertas para falhas repetidas. - - Migração para PostgreSQL apenas se o número de usuários, concorrência ou necessidade de relatórios crescer substancialmente. ## 14. Resultado esperado O projeto é viável para uso diário: o volume é baixo, as regras são objetivas e SQLite atende bem. Também é um bom projeto de estudo porque exercita Go, HTTP, autenticação, persistência, criptografia, agendamento, testes, operações Linux e integração com Telegram, sem introduzir complexidade de microserviços cedo demais. -