Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,19 @@ startup update script refreshes dependencies. Standard build/test/run commands l
### Flutter app — `frontend/arah.app`
- `flutter pub get`, then `flutter analyze --no-fatal-infos` (CI tolerates info-level lints) and
`flutter test`. It talks only to the **BFF**, not the API directly (`--dart-define=BFF_BASE_URL=...`).
- l10n: arb files in `lib/l10n/app_pt.arb` (template) + `app_en.arb`. `flutter gen-l10n` writes to the
synthetic package (`.dart_tool/flutter_gen/gen_l10n/`), but the app imports the **committed**
`lib/l10n/app_localizations*.dart`. After editing arb files, run `flutter gen-l10n` then copy the 3
generated files from `.dart_tool/flutter_gen/gen_l10n/` over `lib/l10n/`.
- `flutter_map` 8.x markers: a `GestureDetector` inside a `Marker` child does **not** fire reliably on
web. Handle taps via `MapOptions.onTap` + nearest-pin matching (see `map_screen.dart`).
- New app journeys must be registered in the BFF `BffJourneyRegistry` (constant + `JourneyToApiPathBase`
+ `AllEndpoints` + `CacheableGetEndpoints` + `AllPathPrefixes`), or BFF tests/`/bff/journeys` break.

### Keep docs in sync with every delivery (important)
When shipping a feature (app/BFF/API), update the relevant docs **in the same PR**:
- `README.md` (phase/status + "App (Flutter) — Entregas Recentes"), `docs/CHANGELOG.md`,
`docs/STABLE_RELEASE_APP_ONBOARDING.md` (app implemented + próximos passos),
`docs/FEATURE_MATRIX_API_BFF_APP.md` (API/BFF/App columns), and the phase docs under
`docs/backlog-api/` + `docs/STATUS_FASES.md` when a backlog phase status changes.
Treat "documentação desatualizada" as a bug (see `.cursorrules`).
17 changes: 15 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,19 @@ O Arah está em **desenvolvimento ativo** com **16 fases completas** (Fases 1-16

---

### 📱 App (Flutter) — Entregas Recentes

O app consome o backend via **BFF** (jornadas `/api/v2/journeys/*`). Entregas recentes que expõem capacidades já existentes no backend:

- ✅ **Login com Google na UI** — botão "Entrar com Google" no fluxo de login (requer `GOOGLE_SIGN_IN_CLIENT_ID` + config Firebase para E2E).
- ✅ **Criação de eventos** — formulário com data/hora e local (jornada `events/create-event`).
- ✅ **Governança/Votações** — listar, votar, ver resultados e criar votações (jornada `governance`, expõe a Fase 14).
- ✅ **Deep-links no mapa** — tocar num pin abre detalhes e navega para evento/asset/alerta/feed.

Detalhes e o que falta no app: [Release estável – App e Onboarding](./docs/STABLE_RELEASE_APP_ONBOARDING.md) · [Matriz API/BFF/App](./docs/FEATURE_MATRIX_API_BFF_APP.md).

---

### 🔒 Segurança e Confiabilidade (Cross-Phase)

- ✅ JWT com secret de 32+ caracteres via variáveis de ambiente
Expand Down Expand Up @@ -471,9 +484,9 @@ Ver documentação completa: [`docs/ENTERPRISE_COVERAGE_PHASES_7_8_9_STATUS.md`]
1. **Fase 17**: Compra Coletiva (P0 Crítico) - Organização de compras coletivas, agrupamento de pedidos
2. **Fase 18**: Hospedagem Territorial (P0 Crítico) - Sistema de hospedagem, agenda, aprovação
3. **Fase 19**: Demandas e Ofertas (P0 Crítico) - Moradores cadastram demandas, outros fazem ofertas
4. **Frontend**: Começar desenvolvimento da interface (Vue/React)
4. **App (Flutter)**: provisionar config do Google Sign-In (OAuth/Firebase); push/FCM; tela de detalhe de post; upload de avatar
5. **Testes**: Validar cobertura de 90%+ (2171+ testes passando)
6. **Documentação**: Manter wiki sincronizado com código
6. **Documentação**: Manter wiki, README e docs de fases sincronizados com cada entrega (app/BFF/API)
7. **Admin Dashboard**: Ferramentas de observabilidade para moderadores
8. **Escalabilidade**: Preparar para múltiplos territórios/usuários em produção

Expand Down
9 changes: 9 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@ e este projeto adere ao [Semantic Versioning](https://semver.org/lang/pt-BR/).
- Reorganização federal da documentação (estrutura por domínios, unificação de duplicados, archive de PRs).
- Changelog unificado (conteúdo de 40_CHANGELOG incorporado).

### Adicionado - App (Flutter): entregas recentes (2026-06)

- ✅ **Documentação de Arquitetura C4** publicada no DevPortal (`/architecture/`) e referenciada no Wiki (`docs/14_C4_ARCHITECTURE.md`).
- ✅ **Governança/Votações no app**: nova jornada `governance` no BFF (proxy para `api/v1/territories/{id}/votings`) e tela de votações (listar com filtro de status, votar inline, ver resultados, criar votação).
- ✅ **Criação de eventos no app**: formulário com início/término (date/time pickers) e local, via jornada `events/create-event`.
- ✅ **Deep-links no mapa**: toque no pin abre detalhes e navega para evento/asset/alerta/feed (tratamento de toque no nível do mapa).
- ✅ **Login com Google na UI**: botão "Entrar com Google" no fluxo de login (requer `GOOGLE_SIGN_IN_CLIENT_ID` + config Firebase para E2E).
- ✅ Documentação sincronizada: `README.md`, `docs/STABLE_RELEASE_APP_ONBOARDING.md`, `docs/FEATURE_MATRIX_API_BFF_APP.md`.

### Alterado - Fase 12 Encerrada (2026-01-25)

- ✅ **Fase 12 declarada 100% encerrada.** Todas as funcionalidades críticas entregues; melhorias contínuas (cobertura >90%, P95 < 200ms) fora do escopo de fechamento.
Expand Down
7 changes: 4 additions & 3 deletions docs/FEATURE_MATRIX_API_BFF_APP.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ O **BFF** expõe tudo sob `/api/v2/journeys/<jornada>/<path>` e faz proxy para a
| **Onboarding** (suggested-territories, complete) | ✅ | ✅ | ✅ | App: lista sugeridos, completa com território selecionado. |
| **Territories** (listar, paged, detalhe, enter, chat/channels) | ✅ | ✅ | ✅ | App: paged (explorar), get by id (mapa), enter, canais de chat. |
| **Feed** (territory-feed, create-post, interact, post-comments, delete-post) | ✅ | ✅ | ✅ | App: feed paginado, like/comment/share, thread, mídia, excluir, filtros. |
| **Events** (territory-events, participate) | ✅ | ✅ | ✅ | App: lista eventos, participar (interesse/confirmado). |
| **Map** (pins, entities) | ✅ | ✅ | ✅ | App: GET map/pins para exibir pins no mapa. |
| **Events** (territory-events, create-event, participate) | ✅ | ✅ | ✅ | App: lista eventos, **criar evento** (título, descrição, início/término, local), participar (interesse/confirmado). |
| **Map** (pins, entities) | ✅ | ✅ | ✅ | App: GET map/pins para exibir pins no mapa; **toque no pin abre detalhes (deep-link)** para eventos/assets/alertas/feed. |
| **Me** (profile, preferences, interests, devices) | ✅ | ✅ | ✅ | App: profile, interests, preferences, registro automático de device. |
| **Notifications** (listar paginado, marcar lida) | ✅ | ✅ | ✅ | App: notifications/paged, notifications/{id}/read. |
| **Membership** (me, become-resident, verify-residency) | ✅ | ✅ | ✅ | App: tela Membership com status, solicitar residência e verificação geo. |
Expand All @@ -54,6 +54,7 @@ O **BFF** expõe tudo sob `/api/v2/journeys/<jornada>/<path>` e faz proxy para a
| **Marketplace V1** (cart, stores, items) | ✅ | ✅ | ✅ | App: cart v1, busca/checkout v2 e gestão de loja própria (`stores/me`, criar/atualizar). |
| **Subscription plans / Subscriptions** | ✅ | ✅ | ✅ | App: planos, minha assinatura, assinar e cancelar. |
| **Moderation** (work-items, cases, evidences) | ✅ | ✅ | ✅ | App: fila, casos (decidir), evidências (download + decidir residência). |
| **Governance** (votings: listar, criar, votar, fechar, resultados) | ✅ | ✅ | ✅ | App: tela Governança (jornada `governance`) — lista votações com filtro de status, votar inline, ver resultados e criar votação. |
| **Chat** (conversations, messages, participants) | ✅ | ✅ | ✅ | App: canais, grupos (criar), mensagens e envio. |
| **Alerts** (listar, criar) | ✅ | ✅ | ✅ | App: listagem e criação de alertas. |
| **Admin** (seed, cache-metrics, configs) | ✅ | ✅ | ➖ | Uso administrativo; não no app usuário. |
Expand Down Expand Up @@ -89,7 +90,7 @@ Prioridade sugerida para **evoluir** com novas fases do backlog (API → BFF →

## Documentos a revisar (app já existe)

> **App Flutter**: Existe uma versão estável do app (auth, onboarding, feed, mapa, eventos, perfil, notificações, publicar, marketplace, chat, membership, alertas). Ver [Release estável – App e Onboarding](./STABLE_RELEASE_APP_ONBOARDING.md) e esta matriz.
> **App Flutter**: Existe uma versão estável do app (auth + **login com Google na UI**, onboarding, feed, mapa com **deep-links nos pins**, eventos com **criação**, **governança/votações**, perfil, notificações, publicar, marketplace, chat, membership, alertas). Ver [Release estável – App e Onboarding](./STABLE_RELEASE_APP_ONBOARDING.md) e esta matriz.

---

Expand Down
24 changes: 13 additions & 11 deletions docs/STABLE_RELEASE_APP_ONBOARDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Este documento descreve a **versão estável** atual do app (Flutter), do fluxo

### App (Flutter – `frontend/Arah.app`)

- **Autenticação**: login com e-mail (check-email → senha ou criar conta), signup com nome e senha.
- **Autenticação**: login com e-mail (check-email → senha ou criar conta), signup com nome e senha; **login com Google na UI** (botão "Entrar com Google" no fluxo de login — requer `GOOGLE_SIGN_IN_CLIENT_ID` e config Firebase para funcionar end-to-end).
- **Onboarding**: tela de seleção de território após login/cadastro quando não há território salvo.
- Lista “Próximos a você” (sugeridos por lat/lng).
- Seleção na lista **só altera o mapa e o destaque**; o botão **Continuar** é o único que conclui o onboarding e leva ao feed.
Expand All @@ -27,7 +27,9 @@ Este documento descreve a **versão estável** atual do app (Flutter), do fluxo
- **Feed**: listagem do feed do território selecionado, com paginação e pull-to-refresh.
- **Explorar**: troca de território (lista paginada); ao “entrar” em outro território, o feed e o mapa refletem o escolhido.
- **Mapa**: pins do território, contorno (polígono/círculo) em verde floresta, marcador do usuário.
- **Eventos**: lista de eventos do território, interesse e confirmação de presença.
- **Eventos**: lista de eventos do território, **criação de evento** (título, descrição, início/término com seletores de data/hora, local), interesse e confirmação de presença.
- **Governança**: tela de votações do território (jornada `governance`) — lista com filtro de status (Todas/Abertas/Fechadas), votar inline, ver resultados (barras) e criar votação (tipo, visibilidade, opções).
- **Mapa (deep-links)**: tocar num pin abre uma folha com "Ver detalhes" que navega para o conteúdo correspondente (evento → Eventos, asset → Assets, alerta → Alertas, post → feed).
- **Perfil**: exibição e edição de nome e bio; preferências de notificação (estrutura).
- **Publicar**: criação de post (título, conteúdo, tipo, visibilidade) no território ativo.

Expand Down Expand Up @@ -123,15 +125,15 @@ Evolução planejada, em ordem de prioridade sugerida:

| Área | O que falta |
|------|-------------|
| **Mídia / imagens** | Fotos e mídia nos posts (upload, exibição). |
| **Interações no feed** | Curtir (like), comentar, compartilhar. |
| **Gestão de posts** | Excluir o próprio post. |
| **Filtros** | Opções para filtrar o feed (por tipo, tags, etc.) e preferência de o que filtrar. |
| **Preferências no perfil** | Definir interesses/preferências do usuário para uso em filtros e sugestões. |
| **Tipo de post** | Escolher tipo ao publicar (ex.: geral, alerta, evento) de forma explícita na UI. |
| **Marketplace** | Lojas, listagens, carrinho, checkout no app. |

O backend cobre parte dessas capacidades (feed, eventos, perfil, notificações); o app será evoluído aos poucos para expor essas funcionalidades.
| **Login Google (config)** | UI já existe; falta provisionar `GOOGLE_SIGN_IN_CLIENT_ID` (OAuth Web) e config Firebase para o fluxo funcionar end-to-end. |
| **Push / FCM** | Recepção de notificações push e deep-link a partir da notificação (hoje só o registro de device token). |
| **Mapa → post detalhe** | Deep-link de pin do tipo `post` hoje leva ao feed; falta tela de detalhe de post dedicada. |
| **Perfil** | Upload de avatar; ligar o histórico de governança (`me/profile/governance`) na UI. |
| **Marketplace (aprofundamento)** | Tela de carrinho dedicada, gestão de itens do vendedor e fluxo de pagamento (avaliar frente aos valores do produto). |

> **Entregue recentemente no app**: login com Google na UI, criação de eventos, governança/votações (listar, votar, resultados, criar) e deep-links nos pins do mapa. Feed já cobre mídia, curtir/comentar/compartilhar, exclusão e filtros.

O backend cobre amplamente essas capacidades; o app é evoluído de forma incremental (API → BFF → App) para expô-las.

---

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,21 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
}
}

Future<void> _loginWithGoogle() async {
await ref.read(authStateProvider.notifier).loginWithGoogle();
if (!mounted) return;
final auth = ref.read(authStateProvider);
if (auth.hasError) {
final msg = auth.error is ApiException
? (auth.error! as ApiException).userMessage
: auth.error.toString();
showErrorSnackBar(context, msg);
} else if (auth.valueOrNull?.accessToken.isNotEmpty ?? false) {
context.go('/onboarding');
}
// valueOrNull == null → usuário cancelou o seletor do Google: nenhuma ação.
}

Future<void> _submitSignUp() async {
if (!(_formKey.currentState?.validate() ?? false)) return;
final displayName = _displayNameController.text.trim();
Expand Down Expand Up @@ -167,7 +182,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
if (_step == LoginStep.email) ..._buildEmailStep(l10n),
if (_step == LoginStep.email) ..._buildEmailStep(l10n, auth.isLoading),
if (_step == LoginStep.password) ..._buildPasswordStep(l10n, auth.isLoading),
if (_step == LoginStep.signup) ..._buildSignUpStep(l10n, auth.isLoading),
],
Expand All @@ -182,7 +197,8 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
);
}

List<Widget> _buildEmailStep(AppLocalizations l10n) {
List<Widget> _buildEmailStep(AppLocalizations l10n, bool authLoading) {
final busy = _checkEmailLoading || authLoading;
return [
TextFormField(
controller: _emailController,
Expand All @@ -205,6 +221,28 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
loading: _checkEmailLoading,
expand: true,
),
const SizedBox(height: AppConstants.spacingMd),
Row(
children: [
const Expanded(child: Divider()),
Padding(
padding: const EdgeInsets.symmetric(horizontal: AppConstants.spacingSm),
child: Text(
l10n.loginOr,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
),
const Expanded(child: Divider()),
],
),
const SizedBox(height: AppConstants.spacingMd),
OutlinedButton.icon(
onPressed: busy ? null : _loginWithGoogle,
icon: const Icon(Icons.g_mobiledata, size: 28),
label: Text(l10n.loginWithGoogle),
),
];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,28 @@ void main() {
expect(find.byType(TextFormField), findsWidgets);
});

testWidgets('LoginScreen shows "Entrar com Google" on email step', (WidgetTester tester) async {
await tester.pumpWidget(
ProviderScope(
overrides: [
authStateProvider.overrideWith(() => _FakeAuthStateNotifier()),
],
child: MaterialApp(
theme: AppTheme.dark,
locale: const Locale('pt'),
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: const LoginScreen(bffBaseUrl: 'http://test'),
),
),
);
await tester.pumpAndSettle();

expect(find.text('Entrar com Google'), findsOneWidget);
// O botão fica na etapa de e-mail, ao lado do fluxo e-mail-first ("ou").
expect(find.text('ou'), findsOneWidget);
});

testWidgets('LoginScreen shows signup form when email does not exist', (WidgetTester tester) async {
final fakeRepo = FakeAuthRepository(
config: const AppConfig(bffBaseUrl: 'http://test'),
Expand Down
Loading