Files
reservas/docs/RUNBOOK.md

11 KiB

Runbook de desenvolvimento e operação

Desenvolvimento local

  1. Copiar .env.example para .env.
  2. Executar docker compose build.
  3. Gerar a chave com docker compose run --rm app php artisan key:generate.
  4. Iniciar com docker compose up -d.
  5. Preparar dados com docker compose exec app php artisan migrate --seed.
  6. Abrir o portal em http://localhost:8000 e emails em http://localhost:8025.

Diagnóstico

docker compose ps
docker compose logs --tail=100 app
docker compose logs --tail=100 queue
docker compose exec app php artisan about
docker compose exec app php artisan migrate:status
docker compose exec postgres pg_isready -U reserva -d reserva
docker compose exec redis redis-cli ping
curl --fail http://localhost:8000/health/live
curl --fail http://localhost:8000/health/ready

/health/live confirma apenas resposta HTTP. /health/ready verifica PostgreSQL e cache e deve ser usado pelo balanceador. Ambos omitem detalhes de serviços. Se readiness falhar, consultar os logs da aplicação e testar PostgreSQL/Redis separadamente com os comandos anteriores.

Página em branco no desenvolvimento

Se o HTML responder mas a interface React não aparecer, confirmar que public/hot contém http://localhost:5173 e não http://0.0.0.0:5173. Depois validar os módulos:

curl -I -H 'Origin: http://localhost:8000' http://localhost:5173/@vite/client
curl -I -H 'Origin: http://localhost:8000' http://localhost:5173/resources/js/app.tsx

Para executar a validação backend use docker compose exec app composer test, não php artisan test diretamente. O script isola as variáveis de teste das variáveis local injetadas pelo Docker Compose e evita que jobs únicos deixem locks no Redis de desenvolvimento.

Ambos devem responder com estado 200 e Access-Control-Allow-Origin: http://localhost:8000. Reiniciar o serviço com docker compose restart node se o ficheiro public/hot estiver desatualizado.

Confirmar também que o HTML inicial já contém conteúdo SSR:

curl -s http://localhost:8000/login | grep 'data-server-rendered="true"'

Se o endpoint Vite devolver um erro SSR, procurar primeiro componentes globais montados em withApp que chamem hooks Inertia como usePage(). Os elementos irmãos do argumento app não pertencem ao respetivo contexto; devem receber as propriedades através do segundo argumento de withApp.

Login regressa ao mesmo ecrã

Um POST /login com resposta 302 seguido novamente de /login indica normalmente credenciais recusadas, não indisponibilidade do portal. Em desenvolvimento, confirmar primeiro se a base tem dados:

docker compose exec app php artisan tinker --execute="dump(App\\Models\\User::count());"

Se o resultado for zero e esta for a base local descartável, repor apenas os dados fictícios com:

docker compose exec app php artisan db:seed --force

As contas são demo@escola.pt e admin@escola.pt, ambas com a palavra-passe de desenvolvimento password. Nunca executar o seeder para corrigir acessos em produção; aí deve ser usado o processo administrativo de contas/recuperação.

Atualização de código

docker compose exec app composer install
docker compose exec node npm install
docker compose exec app php artisan migrate --force
docker compose exec app php artisan optimize:clear
docker compose restart queue scheduler

Integração Snipe-IT

Definir no ambiente, sem colocar tokens no repositório:

SNIPE_IT_URL=https://inventario.exemplo.pt
SNIPE_IT_READ_TOKEN=
SNIPE_IT_OPERATIONAL_TOKEN=
SNIPE_IT_TIMEOUT=10
SNIPE_IT_PAGE_SIZE=100
SNIPE_IT_RETRIES=3
SNIPE_IT_RETRY_DELAY_MS=250

O token de leitura deve pertencer a uma conta técnica com acesso apenas de leitura aos ativos. O token operacional deve ser separado e ter apenas as permissões necessárias para checkout/check-in. A aplicação acrescenta /api/v1 automaticamente quando a URL não o contém.

Operações:

# Sincronização imediata e resultado no terminal
docker compose exec app php artisan snipe:sync-assets

# Colocar na fila
docker compose exec app php artisan snipe:sync-assets --queue

# Confirmar o agendamento horário
docker compose exec app php artisan schedule:list

Administradores podem testar a ligação, iniciar uma execução e consultar as últimas dez sincronizações em http://localhost:8000/administracao/snipe-it. Uma execução falhada mantém o último catálogo válido e não desativa registos ausentes. Consultar sync_runs, os logs do worker e o painel antes de repetir.

Microsoft Entra ID

Registar uma aplicação single tenant do tipo Web. O redirect URI tem de corresponder exatamente ao domínio do portal e ao caminho /auth/microsoft/callback. Em produção é obrigatório HTTPS.

MICROSOFT_ENABLED=true
MICROSOFT_CLIENT_ID=
MICROSOFT_CLIENT_SECRET=
MICROSOFT_TENANT_ID=
MICROSOFT_REDIRECT_URI=https://reservas.exemplo.pt/auth/microsoft/callback
MICROSOFT_TIMEOUT=10
MICROSOFT_METADATA_CACHE_SECONDS=3600

Depois de aplicar a configuração e limpar a cache (php artisan optimize:clear):

  1. entrar com a conta local;
  2. abrir Definições > Segurança e confirmar novamente a palavra-passe;
  3. escolher Associar conta Microsoft e concluir a autenticação institucional;
  4. terminar sessão e validar Entrar com a conta Microsoft no login;
  5. confirmar os eventos identity.microsoft_linked e identity.microsoft_login em auditoria.

O portal não associa contas por email. Uma identidade não associada recebe uma instrução para entrar localmente e fazer a associação explícita. Remover a associação também exige confirmação recente da palavra-passe e cria identity.microsoft_unlinked. Nunca copiar códigos ou tokens para tickets/logs.

Entregas, devoluções e reconciliação

Utilizadores com perfil inventory_operator ou administrator acedem a http://localhost:8000/operacoes. A fila inclui as reservas do dia e todos os processos entregues, atrasados, devolvidos ou com incidente ainda pendente. O campo inicial aceita referência, nome, email, etiqueta, ID Snipe-IT ou a saída de um leitor QR/código de barras configurado como teclado.

Procedimento de entrega:

  1. preparar a requisição;
  2. confirmar fisicamente todos os equipamentos e o estado à saída;
  3. confirmar o ID do destinatário no Snipe-IT;
  4. registar a entrega e verificar a mensagem de sucesso;
  5. se existir erro, não voltar a submeter antes de consultar o estado do ativo no Snipe-IT.

Procedimento de devolução:

  1. localizar a requisição ou ler a etiqueta;
  2. verificar equipamento, acessórios, danos e localização;
  3. registar a devolução;
  4. criar um incidente e bloqueio temporal quando o ativo não deva voltar ao catálogo;
  5. concluir o processo apenas depois de todos os itens estarem devolvidos.

Uma operação parcial fica em incident. Consultar handover_records para movimentos confirmados e os eventos reservation.delivery_failed/reservation.return_failed em audit_logs. Comparar cada item com o estado atual no Snipe-IT; corrigir apenas os itens ainda ready ou delivered. Não alterar estados diretamente na base de dados nem repetir em bloco, porque as escritas externas são deliberadamente não idempotentes e não têm retry automático.

Ciclo de vida das pré-reservas

As requisições pendentes têm, por defeito, um prazo de decisão de 48 horas. O scheduler coloca a expiração na fila a cada cinco minutos. Para operar ou diagnosticar manualmente:

# Expirar imediatamente os pedidos vencidos
docker compose exec app php artisan reservations:expire

# Colocar a operação na fila
docker compose exec app php artisan reservations:expire --queue

# Confirmar ambos os agendamentos
docker compose exec app php artisan schedule:list

Uma expiração ou cancelamento muda itens held, reserved, ready e delivered para cancelled, libertando o período. Confirmar os eventos reservation.expired ou reservation.cancelled em audit_logs quando existir uma divergência operacional.

Aprovações

Utilizadores com perfil approver ou administrator acedem a http://localhost:8000/aprovacoes. Uma decisão válida deve criar uma linha em approval_actions e outra em audit_logs. Se a aprovação indicar que a disponibilidade mudou, confirmar primeiro a atribuição/estado sincronizado do ativo e conflitos entretanto criados; não alterar o estado diretamente na base de dados.

As decisões geram jobs de notificação. Em desenvolvimento, confirmar o email em http://localhost:8025 e a notificação interna em http://localhost:8000/notificacoes. Se uma mensagem não chegar, consultar primeiro docker compose logs --tail=100 queue e a tabela failed_jobs; não repetir a decisão já confirmada.

Administração e relatórios

Administradores usam:

  • http://localhost:8000/administracao/utilizadores para contas, perfis e IDs Snipe-IT;
  • http://localhost:8000/administracao/regras para políticas aplicadas a novos pedidos;
  • http://localhost:8000/administracao/notificacoes/modelos para assuntos e mensagens;
  • http://localhost:8000/administracao/snipe-it para diagnóstico da integração.

As alterações são registadas em audit_logs. A margem de uma reserva já submetida não muda quando a política é alterada; confirmar buffer_starts_at e buffer_ends_at do item ao investigar um conflito.

Administradores e auditores consultam http://localhost:8000/relatorios e http://localhost:8000/auditoria. O CSV usa ;, UTF-8 com BOM e neutralização de fórmulas para abertura segura em aplicações de folha de cálculo. Cada pedido está limitado a 366 dias; para análises plurianuais, exportar períodos separados. Aprovadores, operadores, administradores e auditores consultam o planeamento consolidado em http://localhost:8000/calendario-global.

Produção

Antes de disponibilizar:

  • definir APP_ENV=production, APP_DEBUG=false e HTTPS;
  • substituir todas as palavras-passe do exemplo;
  • montar segredos fora do repositório;
  • remover exposição pública das portas PostgreSQL, Vite e Mailpit;
  • configurar volumes persistentes e backups cifrados;
  • executar php artisan optimize e frontend compilado;
  • definir health checks, alertas e rotação de logs;
  • testar recuperação de backup e falha Snipe-IT.

Backups

Salvaguardar diariamente PostgreSQL e uploads. Manter retenção semanal/mensal e uma cópia fora do servidor. Segredos são salvaguardados através de procedimento separado. Executar um teste de restauro periódico e registar o resultado.

# Cria dump custom, valida-o e acrescenta checksum SHA-256
ops/backup-postgres.sh /caminho/cifrado/backups

# Restaura numa base temporária isolada e elimina-a no fim
ops/test-restore-postgres.sh /caminho/cifrado/backups/reserva-AAAAMMDDTHHMMSSZ.dump

Os scripts obtêm o nome da base e utilizador diretamente do contentor PostgreSQL, recusam identificadores inesperados e nunca restauram sobre a base principal. O destino tem de estar cifrado e deve ser replicado para fora do servidor. Monitorizar a execução e a idade do último ficheiro; um dump sem ensaio de restauro não conta como recuperação validada.

Usar docs/PRODUCTION_CHECKLIST.md para registar responsáveis e evidências antes do piloto.