# Eco Sistema — Backend API

API REST em **Laravel 12** para o MVP Eco Sistema (turismo + meio ambiente).

## Tecnologias

- **PHP** 8.3.29
- **Laravel** 12.x
- **MySQL** 8.4.8
- **Sanctum** 4.3 (autenticação Bearer token)
- **l5-swagger** 11.0 (OpenAPI 3.0)

## Estrutura

```
backend-api/
├── app/
│   ├── Http/
│   │   ├── Controllers/Api/V1/     # Controllers (só orquestram)
│   │   ├── Middleware/              # AdminOuParceiro, SomenteAdmin, ForceJsonResponse
│   │   └── Resources/               # Transformers (Turistico/ e Ambiental/)
│   ├── Models/                      # Eloquent models
│   ├── Observers/                   # CheckinObserver (gera user_route_item)
│   ├── Repositories/                # Acesso a dados isolado
│   ├── Services/                    # Regras de negócio
│   └── Traits/ApiResponse.php       # Padronização { success, data, message, errors }
├── bootstrap/app.php                # Exception handlers e middlewares globais
├── config/                          # Configurações (cors, sanctum, l5-swagger)
├── database/
│   ├── factories/
│   ├── migrations/
│   └── seeders/
├── lang/pt_BR/                      # Mensagens de validação em português
├── routes/api.php                   # Rotas v1
├── storage/api-docs/                # Swagger gerado
└── tests/Feature/                   # 67 testes automatizados
```

## Instalação

```bash
# 1. Dependências
composer install

# 2. Configuração
cp .env.example .env
php artisan key:generate

# 3. Configurar MySQL no .env
# DB_CONNECTION=mysql
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=eco_sistema
# DB_USERNAME=root
# DB_PASSWORD=***

# 4. Criar banco
mysql -u root -p -e "CREATE DATABASE eco_sistema CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"

# 5. Migrations + seeders
php artisan migrate --seed

# 6. Storage link (para imagens)
php artisan storage:link

# 7. Gerar Swagger
php artisan l5-swagger:generate
```

## Endpoints

Base URL: `http://localhost/eco_sistema/backend-api/public/api/v1/`

### Autenticação

| Método | Rota | Descrição | Auth |
|---|---|---|---|
| POST | `/auth/register` | Cadastrar usuário | — |
| POST | `/auth/login` | Login | — |
| POST | `/auth/logout` | Logout | Bearer |
| POST | `/auth/refresh` | Renovar token | Bearer |
| GET  | `/auth/perfil` | Dados do usuário logado | Bearer |

### Pontos Turísticos

| Método | Rota | Descrição | Auth |
|---|---|---|---|
| GET    | `/pontos-turisticos` | Listar (filtros: tipo, cidade, status) | — |
| GET    | `/pontos-turisticos/proximos` | Busca por raio (Haversine) | — |
| GET    | `/pontos-turisticos/{id}` | Detalhe completo | Opcional |
| GET    | `/pontos-turisticos/{id}/como-chegar` | Distância + link Google Maps | — |
| GET    | `/pontos-turisticos/{id}/avaliacoes` | Listar avaliações | — |
| POST   | `/pontos-turisticos` | Criar | admin/parceiro |
| PUT    | `/pontos-turisticos/{id}` | Atualizar | admin/parceiro |
| DELETE | `/pontos-turisticos/{id}` | Remover (soft delete) | admin |
| POST   | `/pontos-turisticos/{id}/imagens` | Upload imagem | admin/parceiro |
| DELETE | `/pontos-turisticos/{id}/imagens/{imagemId}` | Remover imagem | admin/parceiro |

### Restaurantes

| Método | Rota | Descrição | Auth |
|---|---|---|---|
| GET    | `/restaurantes` | Listar | — |
| GET    | `/restaurantes/proximos` | Busca por raio | — |
| GET    | `/restaurantes/{id}` | Detalhe | — |
| GET    | `/restaurantes/{id}/avaliacoes` | Avaliações | — |
| POST   | `/restaurantes` | Criar | admin/parceiro |
| PUT    | `/restaurantes/{id}` | Atualizar | admin/parceiro |
| DELETE | `/restaurantes/{id}` | Remover | admin |
| POST   | `/restaurantes/{id}/imagens` | Upload imagem | admin/parceiro |

### Avaliações, Check-ins, Roteiro

| Método | Rota | Descrição | Auth |
|---|---|---|---|
| POST | `/avaliacoes` | Criar/atualizar avaliação (1 por usuário por local) | Bearer |
| POST | `/checkins` | Registrar check-in (validação GPS 200m + 24h) | Bearer |
| GET  | `/checkins/meus` | Meus check-ins | Bearer |
| GET  | `/meu-roteiro` | Roteiro com estatísticas | Bearer |

### Ecopontos

| Método | Rota | Descrição | Auth |
|---|---|---|---|
| GET    | `/ecopontos` | Listar (filtros: status, tipo_residuo, cidade) | — |
| GET    | `/ecopontos/proximos` | Busca por raio | — |
| GET    | `/ecopontos/{id}` | Detalhe | — |
| POST   | `/ecopontos` | Criar | admin/parceiro |
| PUT    | `/ecopontos/{id}` | Atualizar | admin/parceiro |
| DELETE | `/ecopontos/{id}` | Remover | admin |

### Doações

| Método | Rota | Descrição | Auth |
|---|---|---|---|
| POST  | `/doacoes` | Registrar doação | Bearer |
| GET   | `/doacoes/minhas` | Minhas doações | Bearer |
| GET   | `/doacoes` | Listar todas | admin |
| PATCH | `/doacoes/{id}/confirmar` | Confirmar pendente | admin |
| PATCH | `/doacoes/{id}/cancelar` | Cancelar | admin |

**Referência completa com exemplos:** [doc/API.md](../doc/API.md)
**Swagger interativo:** `http://localhost/eco_sistema/backend-api/public/api/documentation`

## Regras de negócio críticas

### Check-in
1. Usuário **DEVE** enviar lat/lng
2. Distância ≤ **200 metros** do ponto (Haversine) — senão retorna 422
3. Sem duplicata no mesmo `ponto_id` nas últimas **24 horas**
4. Ao criar, o `CheckinObserver` gera um `user_route_item` automaticamente

### Avaliações
- **Unique constraint**: `[user_id, avaliavel_type, avaliavel_id]` — uma avaliação por user por local
- `POST /avaliacoes` faz `updateOrCreate`
- Polimórficas (servem para pontos turísticos e restaurantes)
- Média sempre calculada via `withAvg('avaliacoes', 'nota')`, nunca armazenada

### Doações
- Status inicial: `pendente`
- Transições válidas:
  - `pendente` → `confirmada` (PATCH /confirmar)
  - `pendente`/`confirmada` → `cancelada` (PATCH /cancelar)
- Tentar confirmar já confirmada → 422
- Tentar cancelar já cancelada → 422

## Padrão de resposta

Todas as respostas seguem o formato:

```json
{
  "success": true,
  "data": { ... },
  "message": "OK",
  "errors": null
}
```

Em erro de validação (422):
```json
{
  "success": false,
  "data": null,
  "message": "Os dados informados são inválidos.",
  "errors": {
    "campo": ["O campo X é obrigatório."]
  }
}
```

## Testes

```bash
# Rodar todos os testes
php artisan test

# Apenas feature tests
php artisan test --testsuite=Feature

# Um teste específico
php artisan test --filter=CheckinTest
```

**Suítes de teste** (67 testes, 239 assertions):

| Arquivo | Testes | Cobertura |
|---|---|---|
| `AuthTest.php` | 10 | register, login, logout, refresh, perfil, 401 |
| `PontoTuristicoTest.php` | 13 | CRUD, filtros, Haversine, permissões por role |
| `RestauranteTest.php` | 6 | CRUD, filtros, Haversine |
| `AvaliacaoTest.php` | 5 | polimórfico, upsert, listagem |
| `CheckinTest.php` | 8 | GPS, distância, duplicação, observer |
| `EcopontoTest.php` | 10 | CRUD, filtros, 404 |
| `DoacaoTest.php` | 12 | transições de status, permissões admin |

**Observação sobre SQLite em testes:** Os testes usam SQLite em memória. Como SQLite não tem funções trigonométricas nativas, o `AppServiceProvider::boot()` registra `radians`, `cos`, `sin`, `acos` automaticamente quando a conexão é SQLite. Em produção (MySQL), essas funções já existem nativamente.

## Troubleshooting

**"no such function: radians" em testes**
O `AppServiceProvider::boot()` deve registrar as funções no SQLite. Se houver erro, verifique se o boot foi chamado.

**API retorna HTML em vez de JSON**
Certifique-se de enviar o header `Accept: application/json`. O middleware `ForceJsonResponse` força esse header em todas as rotas `/api/*`.

**Validação aparece em inglês**
Os arquivos pt_BR estão em `lang/pt_BR/`. Se estiverem faltando, rode `php artisan lang:publish`.

**Apache não serve as rotas**
O Laravel precisa do `.htaccess` do diretório `public/`. Verifique que `AllowOverride All` está configurado no `httpd.conf` para `C:/ambiente/projetos`.

## Desenvolvimento

```bash
# Regenerar Swagger após alterar anotações
php artisan l5-swagger:generate

# Limpar caches
php artisan optimize:clear

# Recriar banco do zero
php artisan migrate:fresh --seed
```
