LabReserve
Sistema web para consultar disponibilidade e reservar laboratórios e equipamentos, com testes automatizados.
Problema
Reservar laboratórios e equipamentos sem um sistema central leva a conflitos de horário e controle manual. O projeto também testou como construir uma aplicação funcional com agentes de IA seguindo boas práticas de engenharia.
Decisões
- TypeScript de ponta a ponta, com React na interface e SQLite como banco simples, sem servidor à parte.
- Plano antes do código: escopo quebrado em histórias pequenas com critérios de aceitação.
- Test-first: cada mudança de comportamento começa com um teste falhando.
- Gate de validação com typecheck, lint, build, testes e E2E antes de declarar algo pronto.
Stack
- TypeScript
- React
- SQLite
Resultado
Sistema local com consulta de disponibilidade, reservas com detecção de conflito de horário, filtros, cancelamento e histórico de eventos.
Aprendizado
Usar testes, plano e revisão para manter o controle sobre código produzido com agentes de IA.
Como usei IA
Construído com agentes de IA. O CLAUDE.md fixa a arquitetura, as regras de negócio que precisam continuar valendo, o trabalho test-first e o gate completo (typecheck, lint, build, testes e E2E). O AGENT_ISSUES.md é o quadro persistente de handoff entre sessões. A entrega da interface passou por uma autoauditoria e por duas revisões independentes e cegas, em paralelo, registradas em docs/verification/; foi aprovada na sexta rodada.
Evidências
IlustraçãoArquitetura: monólito modular com dependências apontando para dentro, conforme docs/ARCHITECTURE.md do repositório. IlustraçãoFluxo de criação de reserva: validação do intervalo, checagem de conflito e gravação atômica da reserva e do evento. overlaps(other: TimeInterval): boolean { return this.startAt < other.endAt && this.endAt > other.startAt; }Regra de sobreposição no domínio: limites iguais não conflitam. Ver no repositório
createReservation(input: CreateReservationInput): Reservation { const interval = TimeInterval.create(input.startAt, input.endAt); return this.repository.transaction(() => { if (!this.repository.findResource(input.resourceId)) { throw new ApplicationError('RESOURCE_NOT_FOUND', 'Recurso não encontrado.'); } const conflict = this.repository.findConflict(input.resourceId, interval.startAt, interval.endAt); if (conflict) { throw new ApplicationError( 'RESERVATION_CONFLICT', 'O recurso já está reservado nesse período.', { conflictingReservationId: conflict.id }, ); } const createdAt = this.clock.now().toISOString(); const reservation = createConfirmedReservation({ id: this.ids.generate(), resourceId: input.resourceId, startAt: interval.startAt, endAt: interval.endAt, createdAt, }); const event: ReservationEvent = { id: this.ids.generate(), reservationId: reservation.id, type: 'RESERVATION_CREATED', occurredAt: createdAt, }; this.repository.insertReservation(reservation); this.repository.insertEvent(event); return reservation; }); }Caso de uso de criação: conflito e gravação da reserva e do evento na mesma transação. Ver no repositório
test('detecta sobreposição estrita e permite intervalos consecutivos', () => { const existing = TimeInterval.create('2026-09-16T10:00:00Z', '2026-09-16T11:00:00Z'); assert.equal( existing.overlaps(TimeInterval.create('2026-09-16T10:30:00Z', '2026-09-16T11:30:00Z')), true, ); assert.equal( existing.overlaps(TimeInterval.create('2026-09-16T11:00:00Z', '2026-09-16T12:00:00Z')), false, ); });Teste de domínio que fixa a regra: sobreposição parcial conflita, horário consecutivo não. Ver no repositório
Saída real dos testes de domínio, integração e API (node:test), executados em 30/09/2026 no commit 204de08, Node 24.20.0.$ npm test ✔ responde 413 legível para corpos realmente grandes sem resetar a conexão (386.123ms) ✔ lista recursos e cria reserva pelo contrato HTTP (55.2194ms) ✔ lista, cancela e mostra histórico com erros estruturados (77.333ms) ✔ responde 404 para o histórico de uma reserva inexistente (10.1546ms) ✔ protege respostas e rejeita corpos que não sejam JSON (89.2057ms) ✔ registra falhas inesperadas sem expor seus detalhes (8.7134ms) ✔ mantém a resposta interna segura quando o logger falha (12.5998ms) ✔ informa disponibilidade, histórico global e erros de domínio pelo contrato (42.4548ms) ✔ cancela uma reserva confirmada uma única vez (4.9019ms) ✔ normaliza um intervalo ISO 8601 com offset para UTC (3.9276ms) ✔ rejeita um intervalo cujo término seja igual ao início (1.2695ms) ✔ rejeita uma data que não esteja em ISO 8601 com fuso explícito (0.5249ms) ✔ rejeita datas ISO 8601 sintaticamente válidas, mas inexistentes (0.415ms) ✔ detecta sobreposição estrita e permite intervalos consecutivos (0.5614ms) ✔ o projeto declara scripts reproduzíveis sem versões flutuantes (3.5348ms) ✔ cancelamento gera evento, libera intervalo e suporta filtros (9.6265ms) ✔ cria reserva e evento atomicamente, recusa conflito e aceita horário consecutivo (6.282ms) ✔ reverte a reserva quando a gravação do evento falha (1.5119ms) ✔ um banco novo recebe laboratório e equipamento determinísticos (5.8135ms) ℹ tests 19 ℹ suites 0 ℹ pass 19 ℹ fail 0 ℹ cancelled 0 ℹ skipped 0 ℹ todo 0 ℹ duration_ms 1194.8207
Saída real dos cenários E2E com Playwright (Chromium), na mesma execução.$ npm run test:e2e Running 3 tests using 2 workers ok 1 tests\e2e\reservations.spec.ts:73:1 › consulta período com data vazia sem deixar carregamento pendente (3.0s) ok 2 tests\e2e\reservations.spec.ts:31:1 › cria, rejeita conflito, permite período consecutivo, cancela e mostra histórico (4.1s) ok 3 tests\e2e\reservations.spec.ts:85:1 › aplica filtros de reserva (3.1s) 3 passed (12.5s)- Código-fonte, plano de implementação e registros de verificação no GitHub