Imported from camargo1409/zrp-challenge (
AGENTS.md). Install upstream withnpx skills add camargo1409/zrp-challenge. Copyright stays with the author.
Visão geral da arquitetura
Este projeto tem como objetivo listar os episódios de Rick and Morty usando a API REST (https://rickandmortyapi.com/api) e, ao acessar um episódio, listar os personagens em ordem alfabética.
Arquitetura principal:
- Backend for Frontend (BFF): NestJS
- Frontend web: Next.js
- Aplicativo móvel: Flutter
Ao requisitar os detalhes de um episódio pelo NestJS, o fluxo esperado é:
- Buscar o episódio em
https://rickandmortyapi.com/api/episode/{id}; - Usar os endpoints de
charactersretornados para buscar os personagens; - Consolidar os dados em um formato pronto para os frontends (web e mobile).
Siga os padrões do NestJS: crie um use case que execute essa orquestração como um provider e outro provider responsável pela integração com a API, injetado via DI. Escreva testes com Jest antes de implementar qualquer caso de uso(TDD). Use tipagem completa e DTOs quando necessário. A orquestração deve ocorrer via Docker. O design system será feito com Tailwind.
Listagem de episódios
Esta é a tela de listagem e busca de episódios:
Lista de episódios
Endpoints de listagem:
https://rickandmortyapi.com/api/episodehttps://rickandmortyapi.com/api/episode?page=2(página específica)https://rickandmortyapi.com/api/episode?name=rick(buscar por nome)https://rickandmortyapi.com/api/episode?episode=S01E01(por código de episódio)
Exemplo de retorno da listagem:
""" { "info": { "count": 51, "pages": 3, "next": "https://rickandmortyapi.com/api/episode?page=2", "prev": null }, "results": [ { "id": 1, "name": "Pilot", "air_date": "December 2, 2013", "episode": "S01E01", "characters": [ "https://rickandmortyapi.com/api/character/1", "https://rickandmortyapi.com/api/character/2" ], "url": "https://rickandmortyapi.com/api/episode/1", "created": "2017-11-10T12:56:33.798Z" } ] } """
Buscar múltiplos personagens
Endpoint:
https://rickandmortyapi.com/api/character/1,183
Exemplo de retorno:
""" [ { "id": 1, "name": "Rick Sanchez", "status": "Alive", "species": "Human", "type": "", "gender": "Male", "origin": { "name": "Earth (C-137)", "url": "https://rickandmortyapi.com/api/location/1" }, "location": { "name": "Earth (Replacement Dimension)", "url": "https://rickandmortyapi.com/api/location/20" }, "image": "https://rickandmortyapi.com/api/character/avatar/1.jpeg", "episode": [ "https://rickandmortyapi.com/api/episode/1", "https://rickandmortyapi.com/api/episode/2" ], "url": "https://rickandmortyapi.com/api/character/1", "created": "2017-11-04T18:48:46.250Z" }, { "id": 183, "name": "Johnny Depp", "status": "Alive", "species": "Human", "type": "", "gender": "Male", "origin": { "name": "Earth (C-500A)", "url": "https://rickandmortyapi.com/api/location/23" }, "location": { "name": "Earth (C-500A)", "url": "https://rickandmortyapi.com/api/location/23" }, "image": "https://rickandmortyapi.com/api/character/avatar/183.jpeg", "episode": [ "https://rickandmortyapi.com/api/episode/8" ], "url": "https://rickandmortyapi.com/api/character/183", "created": "2017-12-29T18:51:29.693Z" } ] """
A página de detalhes do episódio inclui a listagem de personagens:
Detalhe do episódio
Observação: alguns elementos do protótipo podem não corresponder ao retorno da API; não utilize campos inexistentes.
Versão mobile do detalhe:
Detalhe mobile
Fetching e SSR
- A listagem/pesquisa de episódios será feita no client-side (fetching). Recomenda-se usar
axiosem uma pastaservices/para encapsular a integração com a API (listagem, paginação e buscas). - O detalhe do episódio será renderizado via SSR; o NestJS atuará como BFF, consolidando os dados de
episodeecharacters. - O campo de busca deve usar debounce (
lodash.debounce, ~400ms) para evitar disparar uma requisição a cada tecla digitada. - A listagem de episódios deve usar
@tanstack/react-querypara cache e revalidação do fetch (useQuerycomqueryKeyincluindo página e termo de busca, eplaceholderData: keepPreviousDatapara manter os dados anteriores enquanto uma nova página/busca carrega).
Design System (Tailwind)
Tokens de cores:
primary: #10B981secondary: #064E3Btertiary: #34D399neutral: #090A0B
Fontes:
- Headline: Space Grotesk
- Body e label: Geist
Spacing:
- Use o padrão do Tailwind ou ajuste conforme a necessidade.
Componentes
É importante que tudo seja componentizado. Componentes sugeridos:
Navbar(compartilhada entre todas as páginas)SearchBar(aparece apenas na página de listagem de episódios)EpisodeCard(componente para listagem de episódios)PaginationCharacterCardCardgenérico (opcional, para episódios e personagens)Sidebar- Botões com variantes:
primary,secondary,outlined
App mobile (Flutter)
O app mobile possui apenas duas telas, seguindo os protótipos episode-list.png (painel mobile) e episode-detail-mobile.png:
episode_list_screen.dart— listagem/busca de episódios (busca com debounce de ~400ms viaTimer, paginação).episode_detail_screen.dart— detalhe do episódio com a lista de personagens em ordem alfabética.
O Design System é centralizado em lib/theme/ e replica exatamente os tokens usados no tailwind.config.js do web:
- Cores (
app_colors.dart):primary#10B981,secondary#064E3B,tertiary#34D399,neutral#090A0B, além desurface/surface-alt/borderpara fundos e bordas. - Tipografia (
app_typography.dart, viagoogle_fonts): Space Grotesk para headline, Inter para corpo/labels (mesma fonte de corpo usada no web, já que o pacotegeistnão é compatível). - Espaçamento e raio de borda (
app_spacing.dart).
Componentes (lib/widgets/), equivalentes aos do web:
AppNavbar— barra superior compartilhada (título configurável: "C-137 DIRECTORY" na listagem, "Portal C-137" no detalhe).AppSearchBar— campo de busca.EpisodeCard— item de episódio na listagem.CharacterCard— item de personagem no detalhe.AppPagination— paginação.AppCard— card genérico.AppButton— botão com variantesprimary,secondary,outlined.
Integração com a API em lib/services/rick_and_morty_api.dart:
- Listagem/busca de episódios: chamada direta à API pública (
https://rickandmortyapi.com/api/episode), mesma lógica de detecção de código de episódio (S01E01) vs. nome usada no web. - Detalhe do episódio: chamada ao BFF (
GET /api/rick-and-morty/episodes/:id), com fallback para a API pública + busca de personagens em lote caso o BFF esteja indisponível. - A URL do BFF é configurável via
--dart-define=BFF_URL=...(padrãohttp://10.0.2.2:3333, loopback do emulador Android para o host).
Observação: o app mobile não faz parte da orquestração via Docker (roda localmente com o Flutter SDK).
Stack tecnológico completo
- NestJS (backend / BFF)
- Next.js (frontend)
- Flutter (app mobile)
- Docker (orquestração)
- Tailwind (design system)
- axios (data fetching)
- react-icons (ícones)
- lodash (debounce da busca de episódios)
- @tanstack/react-query (cache e revalidação da listagem de episódios)
- jest (testes unitários / use cases do backend)
- playwright (E2E front)
- http (data fetching no Flutter)
- google_fonts (Space Grotesk + Inter no Flutter)
Variáveis de ambiente
RICK_AND_MORTY_API=https://rickandmortyapi.com/api
Estrutura do diretório de conteúdo
- /
- bff/
- modules/
- rick-and-morty/
- rick-and-morty.module.ts
- rick-and-morty.service.ts (business logic)
- rick-and-morty.api.ts (API integration)
- rick-and-morty.test.ts
- rick-and-morty.controller.ts
- rick-and-morty/
- modules/
- web/
- pages/
- [episodes]/
- [id].tsx
- index.tsx
- [episodes]/
- services/
- rick-and-morty-api.ts
- components/
- navbar
- searchbar
- episodeList
- episodeCard
- characterCard
- button
- sidebar
- card
- pagination
- tailwind.config.ts (configurações do tema, cores, spacing, etc.)
- pages/
- mobile/
- lib/
- main.dart
- theme/
- app_colors.dart
- app_typography.dart
- app_spacing.dart
- app_theme.dart
- services/
- rick_and_morty_api.dart
- models/
- episode.dart
- episode_page.dart
- character.dart
- widgets/
- app_navbar.dart
- app_search_bar.dart
- episode_card.dart
- character_card.dart
- app_pagination.dart
- app_card.dart
- app_button.dart
- screens/
- episode_list_screen.dart
- episode_detail_screen.dart
- lib/
- bff/