Mapa SMCC — guia do projeto
Catálogo digital dos espaços do Santa Mônica Clube de Campo: lista de locais com busca, filtros e ficha de cada espaço. Um painel administrativo permite à secretaria manter tudo sem um desenvolvedor.
1. O que é
Um site público de uma única página (/) e um painel administrativo (/admin).
Para o associado
Busca por nome, filtro por categoria, ficha com foto, horário, telefone, "Como chegar" (Google Maps) e compartilhamento.
Para a secretaria
Cadastra e edita locais, categorias, horários, fotos e a planta do clube pelo painel, sem tocar em código.
Para integrações
Uma API JSON pública em /api/mapa, pensada para totem, app futuro ou outro sistema.
Stack
| Camada | Tecnologia | Observação |
|---|---|---|
| Framework | Laravel 13 (PHP 8.3) | composer.json |
| Painel admin | Filament 5 | CRUD, upload, repeater e reordenação |
| Front-end público | Blade + JavaScript puro | Sem Vite no caminho do site: public/js/app.js e public/css/app.css são servidos como estão |
| Banco | SQLite (dev) / MySQL (produção atual) | Trocar é mudar o .env |
| Cache | Tabela cache do banco | CACHE_STORE=database |
| Imagens | GD (nativa do PHP) | Converte para WebP com php artisan mapa:otimizar-imagens |
2. Onde está cada coisa
mapa-smcc/
├── app/
│ ├── Console/Commands/OtimizarImagens.php comando mapa:otimizar-imagens
│ ├── Filament/
│ │ ├── Resources/Locals/ CRUD de locais (formulário e tabela)
│ │ ├── Resources/Categorias/ CRUD de categorias
│ │ ├── Resources/MapVersions/ plantas do clube + âncora geográfica
│ │ └── Widgets/ ResumoClube, LocaisMaisVistos (dashboard)
│ ├── Http/Controllers/
│ │ ├── MapaController.php página /, ficha /locais/{slug}
│ │ └── MetricaController.php contador de visualizações
│ ├── Models/ Local, Categoria, LocalHorario, LocalView, MapVersion, User
│ ├── Observers/ LocalObserver (calcula lat/lng), MapVersionObserver (1 planta ativa), ...
│ ├── Services/
│ │ ├── MapaService.php monta o payload (cache de 1 h)
│ │ └── OtimizadorImagem.php conversão para WebP
│ └── Support/GoogleMapsLocal.php formata/valida as coordenadas do admin
├── config/mapa.php contato, logo, origem padrão, reservas ← ajustes do clube
├── database/
│ ├── migrations/ estrutura das tabelas
│ ├── seeders/ClubeSeeder.php dados iniciais (locais, categorias, planta)
│ └── database.sqlite banco local (SQLite)
├── docs/
│ ├── api-locais.md documentação da API pública
│ └── guia-do-projeto.html este guia
├── public/ o que o navegador acessa diretamente
│ ├── css/app.css design do site (tokens de cor no topo, :root)
│ ├── js/app.js busca, filtro, ficha, compartilhar, beacon
│ ├── storage/ → storage/app/public link simbólico criado por storage:link
│ └── index.php entrada da aplicação
├── resources/views/
│ ├── components/layouts/app.blade.php layout base (fontes, CSS, JS)
│ ├── locais/index.blade.php página principal
│ ├── locais/partials/ header, card, ficha, footer
│ └── filament/mapa-picker.blade.php seletor visual da planta (ver seção 11)
├── routes/web.php todas as rotas do site
├── storage/app/public/ fotos, planta, logo (o que vai para o ar)
├── tests/Feature/ AdminTest, MapaTest, MetricaTest
├── .env configuração do ambiente (não versionar)
└── .env.example modelo do .env
Tabela de respostas rápidas
| Quero… | Onde mexer |
|---|---|
| Editar um local (texto, telefone, horário, foto) | Painel /admin/locals |
| Mudar o telefone central, endereço ou horário do rodapé | config/mapa.php, chave contato |
| Trocar a logo | config/mapa.php, chave logo |
| Mudar cor ou fonte do site | public/css/app.css, variáveis em :root |
| Mudar o texto ou a lógica da busca e da ficha | public/js/app.js |
| Mudar o layout da página | resources/views/locais/ |
| Criar nova rota | routes/web.php |
| Ativar reserva online | config/mapa.php, reservas.url |
| Ver quantas visualizações um local teve | Dashboard do painel (/admin) |
3. Como funciona
Uma visita à página principal percorre este caminho:
/routes/web.phpMapaController@indexMapaService::payload() (cache 1 h)Por que não tem AJAX na busca?
Os 47 locais chegam junto com a página, dentro de um <script type="application/json" id="mapa-payload">. Filtrar é só alternar hidden nos cards. Isso mantém o site rápido no 4G do clube e indexável pelo Google.
Ficha do local
A ficha é um <dialog> nativo. Ao abrir, a URL muda para /locais/{slug} (sem recarregar), e a ficha só conta como visualização se ficar aberta por 2 segundos. O envio é um sendBeacon para POST /locais/{slug}/view, limitado a 60 chamadas por minuto por IP.
Cache e atualização
O payload do banco fica em cache por 1 hora. Os observers de Local, Categoria, LocalHorario e MapVersion apagam essa chave ao salvar, então a secretaria vê a mudança na hora. Já o que vem de config/mapa.php é mesclado a cada requisição, de fora do cache.
Imagens
Cada foto vira duas versões WebP: 1200 px para a ficha e 400 px para o card. A planta vira WebP de 2400 px. O comando precisa ser rodado sempre que uma foto for trocada pelo painel.
4. Configurar
Requisitos
- PHP 8.3 com as extensões usuais do Laravel (incluindo
pdo,gd,mbstring,sqlite3oumysql). - Composer 2.
- Node.js (opcional: o site não usa npm).
php -v responde 8.3.28 e composer -V responde 2.10.3. O README antigo menciona o caminho do WampServer (C:/wamp64/bin/php/php8.3.28/php.exe); use ele apenas se o php do PATH não funcionar.
Passo a passo (ambiente novo)
- Copiar o ambiente:
copy .env.example .env(no Linux/macOS:cp). - Gerar a chave:
php artisan key:generate. - Ajustar o
.env(tabela abaixo). - Instalar dependências PHP:
composer install. - Criar o banco e os dados:
php artisan migrate --seed. O seeder é idempotente: pode rodar de novo sem duplicar nada. - Criar o link da pasta pública:
php artisan storage:link. - Otimizar as imagens:
php artisan mapa:otimizar-imagens. - Criar um usuário do painel: veja a seção 8.
Variáveis do .env que importam
| Chave | Para que serve | Local (dev) | Produção (atual) |
|---|---|---|---|
APP_ENV | Ambiente | local | production |
APP_DEBUG | Mostra erros detalhados. Nunca true em produção | true | false |
APP_URL | Endereço público. Gera as URLs das imagens e o prefixo das rotas | http://localhost:8000 | https://servicos2.santamonica.rec.br/mapa-online-smcc |
ASSET_URL | Base dos arquivos estáticos (produção sob subpasta) | vazio | mesmo valor do APP_URL |
DB_CONNECTION | sqlite ou mysql | sqlite | mysql |
DB_* | Host, porta, nome, usuário e senha do MySQL | não usado | preencher |
CACHE_STORE | Onde fica o cache do payload | database | database |
SESSION_PATH | Caminho do cookie de sessão (importante em subpasta) | / | /mapa-online-smcc |
MAIL_MAILER | E-mail (o site não envia e-mails hoje) | log | log |
.env atual é o de produção
Ele tem APP_ENV=production, APP_DEBUG=false, MySQL e a URL do servidor. Por isso, rodar php artisan serve com este arquivo tenta conectar no MySQL e gera links apontando para o servidor, não para localhost:8000. Para desenvolver localmente, use um .env separado com os valores da coluna "Local (dev)".
Ajustes do clube: config/mapa.php
| Chave | O que faz |
|---|---|
origem_padrao | Posição da portaria na planta (x: 31.5, y: 48.0). Hoje é enviada no payload, mas nenhuma tela a usa. |
logo | URL da logo, servida pelo site do clube (hotlink). |
contato.telefone, telefone_link | Telefone central, exibido no rodapé e nas reservas. telefone_link é o formato do tel:. |
contato.endereco, rodape, rodape2 | Endereço e horários do rodapé. |
contato.google_maps | Destino geral do "Como chegar" (place do clube). Usado quando o local não tem link próprio. |
reservas.url | null = botão liga para a secretaria. Preencher com um link torna o botão um link de reserva. |
reservas.aviso | Texto exibido junto ao botão Reservar. |
icone_todos | Ícone do filtro "Todos os locais". |
Como config/ não dispara os observers, mudanças aqui aparecem na hora. Se o servidor usar php artisan config:cache, rode php artisan config:clear depois de editar.
5. Rodar
Desenvolvimento local
php artisan serve # http://localhost:8000
php artisan test # roda os testes
- Site: http://localhost:8000
- Painel: http://localhost:8000/admin
Se mudar a porta, mude o APP_URL junto. Senão as fotos quebram.
Em produção
O site responde em https://servicos2.santamonica.rec.br/mapa-online-smcc/. O script test_deploy_check.sh (na raiz) faz uma checagem rápida: página principal, redirecionamento de /mapa-online-smcc e o título no HTML.
Comandos do dia a dia
| Comando | Quando usar |
|---|---|
php artisan migrate --seed | Primeira vez ou depois de nova migração. Idempotente. |
php artisan storage:link | Uma vez por ambiente (cria public/storage). |
php artisan mapa:otimizar-imagens | Depois de trocar foto ou planta pelo painel. |
php artisan mapa:otimizar-imagens --force | Regerar todos os WebP, mesmo os já existentes. |
php artisan cache:clear | Forçar a releitura do payload (normalmente os observers já fazem isso). |
php artisan route:list | Ver todas as rotas. |
php artisan tinker | Mexer no banco direto pelo terminal. |
6. Os dois números: x/y e latitude/longitude
Um local tem duas coordenadas de natureza diferente. Entender a diferença é a chave para o resto do sistema:
| Par | Unidade | Onde é guardado | Quem preenche | Para que serve |
|---|---|---|---|---|
| x, y | porcentagem da imagem da planta (0 a 100) x: esquerda → direita · y: topo → base |
colunas locais.x e locais.y (decimal 5,2, podem ser nulas) |
a intenção era o clique na planta; hoje não há campo no formulário (ver seção 11) | fonte da verdade da posição. Gera a latitude/longitude abaixo |
| latitude, longitude | graus decimais (WGS84) | colunas locais.latitude e locais.longitude |
calculadas automaticamente pelo LocalObserver a partir de x/y |
expostas na API (/api/mapa) para integrações |
A conversão (x/y → lat/lng)
Implementada em MapVersion::coordenada(). A planta ativa define o centro e a extensão:
latitude = base_lat + (50 - y) / 100 × span_lat
longitude = base_lng + (x - 50) / 100 × span_lng
Com a planta atual (base_lat -22.741900, base_lng -47.312600, span_lat 0.0072, span_lng 0.0087): o centro da planta (x=50, y=50) cai exatamente em -22.741900, -47.312600. O y cresce para baixo na tela, por isso o sinal é invertido na latitude.
Experimente
Digite um x e um y e veja o ponto na planta e a latitude/longitude correspondente. Use os mesmos valores do banco.
Ponto laranja: portaria (origem padrão). Ponto azul: o local digitado.
Exemplo: a Secretaria usa x 44.4 e y 50.2.
As duas coordenadas do Google Maps
O campo "Coordenadas" no formulário do local é outra coisa. Ele guarda o link google_maps_url, usado no botão "Como chegar" da ficha. Não é o mesmo par que x/y e não atualiza a latitude/longitude automaticamente. O formato esperado é latitude, longitude (com vírgula), e o sistema monta o link de rota a pé a partir dele.
A âncora da planta (-22.74, -47.31, região de Campinas/SP) não coincide com o endereço real do clube (-25.38, -49.15, Paraná, DDD 41). Por isso, a latitude/longitude derivada de x/y está deslocada. O README registra isso como pendência. Enquanto não for corrigido, use o campo "Coordenadas" para o "Como chegar" e não confie na lat/lng derivada.
7. Como ajustar
Adicionar um local
- No painel, em Locais, clique em Criar.
- Digite o nome. O slug (usado na URL) é preenchido sozinho. Ele não muda depois de criado, porque links compartilhados dependem dele.
- Escolha a categoria e preencha resumo, destaque e descrição.
- Em Horários, adicione as faixas (ex.: "Terça a sexta" / "10h às 20h").
- Envie a foto e rode
php artisan mapa:otimizar-imagens.
Desativar sem apagar
Desligue o toggle ativo. O local some da lista, mas o histórico de visualizações é preservado. A página /locais/{slug} dele passa a responder 404.
Mudar a ordem da lista
Campo ordem (menor aparece primeiro) ou arraste na tabela do painel.
Trocar a planta do clube
- Em Plantas do clube, envie a nova imagem.
- Marque ativa. As outras são desativadas automaticamente (só pode haver uma).
- Ajuste a âncora (latitude/longitude do centro e extensões em graus) se necessário. Mudar isso move o destino de "Como chegar" de todos os locais que dependem de x/y.
- Rode
php artisan mapa:otimizar-imagens.
Mudar cores e fontes
Tokens de cor no topo de public/css/app.css, dentro de :root (--brand: #0F2091, --ink, etc.). A fonte é Plus Jakarta Sans, carregada do Google Fonts no layout resources/views/components/layouts/app.blade.php.
8. Painel administrativo
Acesso em /admin. Só entra quem tem admin = true na tabela users. Sem isso, o login é aceito mas a conta recebe 403.
Criar um usuário
php artisan tinker --execute="App\Models\User::create(['name'=>'Secretaria','email'=>'secretaria@exemplo.com','password'=>bcrypt('uma-senha-forte'),'admin'=>true]);"
Seções do painel
| Seção | O que tem |
|---|---|
| Dashboard | Total de locais publicados, categorias ativas, visualizações em 7 e 30 dias e ranking dos 10 mais consultados. |
| Locais | CRUD completo, toggles "ativo" e "reservável" direto na tabela, endereço editável na linha, filtro por categoria, repeater de horários. |
| Categorias | CRUD com preview do ícone SVG e reordenação. |
| Plantas do clube | Upload da planta, marcação de ativa e âncora geográfica. |
9. API e rotas
| Método | Caminho | Retorna |
|---|---|---|
GET | / | Página principal (HTML) |
GET | /locais | Redireciona (301) para / |
GET | /locais/{slug} | Mesma página, com a ficha aberta e meta tags para compartilhamento. 404 se inativo |
POST | /locais/{slug}/view | 204. Conta uma visualização do dia. Limite de 60/min por IP |
GET | /api/mapa | JSON com locais, categorias, contato, reservas, origem padrão e logo |
GET | /admin | Painel Filament (login) |
A documentação completa do JSON, com exemplos e campos, está em docs/api-locais.md. Não há endpoint de escrita: edição só pelo painel.
10. Testes e manutenção
php artisan test
Testes em tests/Feature/: AdminTest (acesso ao painel), MapaTest (página, API, coordenadas, cache, idempotência do seeder) e MetricaTest (contador de visualizações).
Rotina sugerida
- Backup diário do banco e de
storage/app/public. - Após trocar foto ou planta:
php artisan mapa:otimizar-imagens. - Após atualizar código no servidor:
php artisan migrate --forceephp artisan config:clear. - Logs em
storage/logs/. ComAPP_DEBUG=false, erros aparecem só no log.
11. Atenção: divergências encontradas
Ao montar este guia, comparei o que o README descreve com o código. Estes pontos merecem ação:
O README diz que, no admin, "clique na planta e o marcador vai para lá". O arquivo resources/views/filament/mapa-picker.blade.php existe, mas LocalForm não o referencia e não há campos x/y no formulário. Resultado: a posição só pode ser definida pelo tinker ou pelo banco. A tabela mostra "não marcada" para os locais sem posição.
O README afirma que arrastar o marcador corrige o link do "Como chegar". Na prática, o LocalObserver só atualiza latitude/longitude. O botão usa google_maps_url (campo "Coordenadas") ou, se vazio, o link geral do clube. São dados separados.
O README descreve distância a pé a partir da portaria, geolocalização do visitante e ordenação por distância. Nada disso está em app.js ou nas views. A origem padrão chega no payload, mas não é usada. Ou a funcionalidade foi removida, ou ainda não foi feita. Vale confirmar antes de divulgar.
O README traz a senha temporária do usuário provisionado. Como o repositório pode ser compartilhado, troque a senha e remova o trecho do README.
O .env está com valores de produção (ver seção 4). Os comandos do README para rodar localmente pressupõem outro .env. Manter credenciais de produção num arquivo que roda na máquina de desenvolvimento aumenta o risco de um comando apagar ou alterar dados do servidor.
Telefones por local (DDD 19, de outra região), as 33 churrasqueiras sem endereço, posição nem foto própria, e a âncora geográfica da planta. Detalhes na seção "Dados que ainda são do protótipo" do README.
12. Pendências antes do go-live
Lista do README, para acompanhar:
- Trocar a senha do usuário provisionado
- Substituir os telefones por local pelos ramais reais
- Preencher endereço, posição e foto das 33 churrasqueiras
- Definir o canal de reserva (
reservas.url) - Corrigir a âncora geográfica da planta, ou manter a geolocalização desligada
- Conferir com a secretaria os textos e horários
- HTTPS (sem TLS, não há geolocalização nem compartilhamento nativo)
- Definir
APP_URLde produção - Auto-hospedar a fonte e fechar a política de conteúdo (hoje vem do Google Fonts)
- Backup diário do banco e de
storage/app/public - Páginas 404 e 500 com a identidade do clube
- Resolver as divergências da seção 11