diff --git a/CHANGELOG.md b/CHANGELOG.md
index e0c14ac..f56224c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,27 @@ O formato segue [Keep a Changelog](https://keepachangelog.com/pt-BR/).
Este projeto adota [Versionamento Semântico](https://semver.org/pt-BR/).
+## [1.39.0] - 2025-12-16
+
+### Added
+- **PWA para iOS** - Aplicativo instalável em iPhone/iPad
+ - Service Worker com estratégias de cache offline-first
+ - Cache versionado (v1.39.0) com limpeza automática de versões antigas
+ - Estratégias de cache por tipo: Cache-First (imagens/fontes), Network-First (API), Stale-While-Revalidate (HTML/CSS/JS)
+ - Registro automático do Service Worker em main.jsx
+ - Verificação de atualizações a cada 1 hora
+ - Prompt de atualização quando nova versão disponível
+ - manifest.json otimizado: scope, categories (finance/productivity/business)
+ - Meta tags iOS: apple-mobile-web-app-capable, black-translucent status bar
+ - Meta tag format-detection para desabilitar auto-detecção de telefones
+ - Ícone Apple Touch Icon (180x180) no manifest
+ - Suporte completo para instalação via Safari iOS
+
+### Changed
+- **Service Worker** atualizado para versão 1.39.0
+- **manifest.json** - Adicionado prefer_related_applications: false
+- **index.html** - Status bar style alterado de "default" para "black-translucent" (melhor em iOS)
+
## [1.38.0] - 2025-12-15
### Added
diff --git a/VERSION b/VERSION
index ebeef2f..5edffce 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.38.0
+1.39.0
diff --git a/docs/INSTALACAO_iOS.md b/docs/INSTALACAO_iOS.md
new file mode 100644
index 0000000..eaa8f69
--- /dev/null
+++ b/docs/INSTALACAO_iOS.md
@@ -0,0 +1,132 @@
+# 📱 Como Instalar WebMoney no iPhone/iPad
+
+## ✅ Requisitos
+- iOS 11.3 ou superior
+- Safari (navegador nativo do iOS)
+
+## 📲 Passo a Passo da Instalação
+
+### 1. Abrir no Safari
+Abra o Safari e acesse: **https://webmoney.cnxifly.com**
+
+> ⚠️ **IMPORTANTE**: Deve ser o Safari! Chrome e Firefox no iOS não suportam PWA.
+
+### 2. Fazer Login
+Entre com suas credenciais:
+- Email: `marco@cnxifly.com`
+- Senha: `M@ster9354`
+
+### 3. Adicionar à Tela Inicial
+1. Toque no ícone de **Compartilhar** (quadrado com seta para cima)
+2. Role para baixo e toque em **"Adicionar à Tela de Início"**
+3. Confirme o nome "WebMoney" (ou personalize)
+4. Toque em **"Adicionar"**
+
+### 4. Usar como App
+- Um ícone do WebMoney aparecerá na sua tela inicial
+- Toque no ícone para abrir como aplicativo
+- O app abrirá em tela cheia (sem barras do Safari)
+- Status bar translúcido integrado ao design
+
+## 🎨 Recursos PWA
+
+### ✅ O que Funciona
+- **Instalação na tela inicial** - Ícone próprio como app nativo
+- **Modo standalone** - Sem barras do navegador
+- **Cache offline** - Assets ficam em cache para carregamento rápido
+- **Service Worker** - Atualizações automáticas
+- **Ícone Apple Touch** - 180x180px otimizado para iOS
+- **Status bar integrado** - Estilo black-translucent
+
+### 🚫 Limitações do PWA no iOS
+- **Sem notificações push** (Safari não suporta)
+- **Sem sincronização em background** (requer app nativo)
+- **Cache limitado** - iOS pode limpar cache se memória baixa
+- **Sem acesso a APIs nativas** (câmera, localização, etc.)
+
+## 🔄 Atualizações
+
+O app verifica atualizações automaticamente:
+- **A cada 1 hora** enquanto app estiver aberto
+- **Ao recarregar** a página
+- Quando houver atualização, aparece um prompt perguntando se deseja atualizar
+
+## 🆙 Upgrade Futuro: App Store
+
+Se no futuro desejarmos publicar na App Store (US$ 99/ano):
+
+### Opção 1: Capacitor (Recomendado)
+- **Tempo**: 30 minutos
+- **Código**: Zero mudanças necessárias
+- **Recursos**: Notificações push, background sync, APIs nativas
+- **Build**: Ionic Capacitor (`npx cap add ios`)
+
+### Opção 2: React Native
+- **Tempo**: 2-3 dias
+- **Código**: Reescrever em React Native
+- **Recursos**: Acesso total a APIs nativas
+- **Custo**: Alto esforço de migração
+
+## 📊 Comparação: PWA vs App Nativo
+
+| Recurso | PWA (Atual) | Capacitor | App Nativo |
+|---------|-------------|-----------|------------|
+| **Custo** | Gratuito | $99/ano | $99/ano |
+| **Instalação** | Via Safari | Via App Store | Via App Store |
+| **Tempo desenvolvimento** | Pronto | 30 min | 2-3 dias |
+| **Notificações** | ❌ | ✅ | ✅ |
+| **Offline** | ✅ Parcial | ✅ Total | ✅ Total |
+| **APIs Nativas** | ❌ | ✅ | ✅ |
+| **Atualizações** | Automático | Automático | App Store Review |
+| **Compatibilidade código** | - | 100% | 0% (reescrever) |
+
+## 🐛 Troubleshooting
+
+### App não aparece na tela inicial
+- Certifique-se de usar **Safari** (não Chrome/Firefox)
+- Verifique se está em https://webmoney.cnxifly.com (não IP)
+- Tente recarregar a página (⌘+R no Safari)
+
+### App não funciona offline
+- Service Worker leva alguns segundos para cachear
+- Navegue pelo app após instalação para cachear rotas
+- Cache só funciona após primeira visita
+
+### Ícone não aparece correto
+- Force reload do Safari: **⌘+Shift+R**
+- Remova o app e reinstale
+- Limpe cache do Safari (Ajustes > Safari > Limpar Histórico)
+
+## 📝 Notas Técnicas
+
+### Service Worker
+- **Versão**: 1.39.0
+- **Estratégias**:
+ - **Cache-First**: Imagens, fontes (resposta imediata)
+ - **Network-First**: Chamadas API (dados frescos)
+ - **Stale-While-Revalidate**: HTML/CSS/JS (balance)
+
+### Manifest
+- **scope**: `/` (todo o site)
+- **display**: `standalone` (tela cheia)
+- **orientation**: `portrait-primary` (retrato)
+- **categories**: finance, productivity, business
+
+### Meta Tags iOS
+```html
+
+
+
+
+```
+
+## 🎯 Conclusão
+
+O PWA do WebMoney oferece:
+- ✅ **Experiência de app nativo** sem custo da App Store
+- ✅ **Instalação simples** via Safari
+- ✅ **Performance otimizada** com cache offline
+- ✅ **Atualizações automáticas** sem revisão da Apple
+- ✅ **Caminho de upgrade** para Capacitor quando necessário
+
+Para mais informações, consulte [CHANGELOG.md](../CHANGELOG.md) versão 1.39.0.
diff --git a/frontend/index.html b/frontend/index.html
index dc8e806..4736ac1 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -14,8 +14,12 @@
-
+
+
+
+
+
diff --git a/frontend/public/manifest.json b/frontend/public/manifest.json
index 40a94f9..db05745 100644
--- a/frontend/public/manifest.json
+++ b/frontend/public/manifest.json
@@ -3,10 +3,13 @@
"short_name": "WebMoney",
"description": "Sistema de Gestão Financeira Pessoal",
"start_url": "/",
+ "scope": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#1a365d",
"orientation": "portrait-primary",
+ "categories": ["finance", "productivity", "business"],
+ "prefer_related_applications": false,
"icons": [
{
"src": "/favicon-16x16.png",
@@ -18,6 +21,12 @@
"sizes": "32x32",
"type": "image/png"
},
+ {
+ "src": "/apple-touch-icon.png",
+ "sizes": "180x180",
+ "type": "image/png",
+ "purpose": "any"
+ },
{
"src": "/logo-192.png",
"sizes": "192x192",
diff --git a/frontend/public/sw.js b/frontend/public/sw.js
new file mode 100644
index 0000000..aaacfb6
--- /dev/null
+++ b/frontend/public/sw.js
@@ -0,0 +1,166 @@
+// WebMoney Service Worker - PWA Support
+const CACHE_VERSION = 'webmoney-v1.39.0';
+const CACHE_STATIC = `${CACHE_VERSION}-static`;
+const CACHE_DYNAMIC = `${CACHE_VERSION}-dynamic`;
+const CACHE_IMMUTABLE = `${CACHE_VERSION}-immutable`;
+
+// Recursos para cache imediato (install)
+const STATIC_ASSETS = [
+ '/',
+ '/index.html',
+ '/manifest.json',
+ '/logo-192.png',
+ '/logo-512.png',
+ '/apple-touch-icon.png',
+];
+
+// Estratégia: Cache First com fallback para Network
+const CACHE_FIRST_PATTERNS = [
+ /\.(png|jpg|jpeg|svg|gif|webp|ico)$/,
+ /\.(woff|woff2|ttf|eot)$/,
+ /bootstrap-icons/,
+];
+
+// Estratégia: Network First com fallback para Cache
+const NETWORK_FIRST_PATTERNS = [
+ /\/api\//,
+];
+
+// Install - cacheia recursos estáticos
+self.addEventListener('install', (event) => {
+ console.log('[SW] Installing Service Worker...', CACHE_VERSION);
+
+ event.waitUntil(
+ caches.open(CACHE_STATIC).then((cache) => {
+ console.log('[SW] Caching static assets');
+ return cache.addAll(STATIC_ASSETS);
+ })
+ .then(() => self.skipWaiting()) // Ativa imediatamente
+ );
+});
+
+// Activate - limpa caches antigos
+self.addEventListener('activate', (event) => {
+ console.log('[SW] Activating Service Worker...', CACHE_VERSION);
+
+ event.waitUntil(
+ caches.keys().then((cacheNames) => {
+ return Promise.all(
+ cacheNames
+ .filter((name) => name.startsWith('webmoney-') && name !== CACHE_STATIC && name !== CACHE_DYNAMIC && name !== CACHE_IMMUTABLE)
+ .map((name) => {
+ console.log('[SW] Deleting old cache:', name);
+ return caches.delete(name);
+ })
+ );
+ })
+ .then(() => self.clients.claim()) // Assume controle imediatamente
+ );
+});
+
+// Fetch - estratégias de cache
+self.addEventListener('fetch', (event) => {
+ const { request } = event;
+ const url = new URL(request.url);
+
+ // Ignora requisições não-GET
+ if (request.method !== 'GET') {
+ return;
+ }
+
+ // Ignora chrome-extension e outras URLs especiais
+ if (!url.protocol.startsWith('http')) {
+ return;
+ }
+
+ // Cache First para assets estáticos
+ if (CACHE_FIRST_PATTERNS.some(pattern => pattern.test(request.url))) {
+ event.respondWith(cacheFirst(request));
+ return;
+ }
+
+ // Network First para API calls
+ if (NETWORK_FIRST_PATTERNS.some(pattern => pattern.test(request.url))) {
+ event.respondWith(networkFirst(request));
+ return;
+ }
+
+ // Stale-While-Revalidate para HTML/CSS/JS
+ event.respondWith(staleWhileRevalidate(request));
+});
+
+// Estratégia: Cache First (imagens, fontes)
+async function cacheFirst(request) {
+ const cache = await caches.open(CACHE_IMMUTABLE);
+ const cached = await cache.match(request);
+
+ if (cached) {
+ return cached;
+ }
+
+ try {
+ const response = await fetch(request);
+ if (response.ok) {
+ cache.put(request, response.clone());
+ }
+ return response;
+ } catch (error) {
+ console.log('[SW] Fetch failed for:', request.url);
+ // Retorna cache mesmo se expirado
+ return cached || new Response('Network error', { status: 408 });
+ }
+}
+
+// Estratégia: Network First (API)
+async function networkFirst(request) {
+ const cache = await caches.open(CACHE_DYNAMIC);
+
+ try {
+ const response = await fetch(request);
+ if (response.ok) {
+ cache.put(request, response.clone());
+ }
+ return response;
+ } catch (error) {
+ console.log('[SW] Network failed, trying cache:', request.url);
+ const cached = await cache.match(request);
+ return cached || new Response(JSON.stringify({ error: 'Offline' }), {
+ status: 503,
+ headers: { 'Content-Type': 'application/json' }
+ });
+ }
+}
+
+// Estratégia: Stale-While-Revalidate (HTML, CSS, JS)
+async function staleWhileRevalidate(request) {
+ const cache = await caches.open(CACHE_DYNAMIC);
+ const cached = await cache.match(request);
+
+ const fetchPromise = fetch(request).then((response) => {
+ if (response.ok) {
+ cache.put(request, response.clone());
+ }
+ return response;
+ });
+
+ return cached || fetchPromise;
+}
+
+// Mensagens do cliente
+self.addEventListener('message', (event) => {
+ if (event.data === 'SKIP_WAITING') {
+ self.skipWaiting();
+ }
+
+ if (event.data === 'CLEAR_CACHE') {
+ event.waitUntil(
+ caches.keys().then((cacheNames) => {
+ return Promise.all(
+ cacheNames.map((name) => caches.delete(name))
+ );
+ })
+ );
+ }
+});
+
+console.log('[SW] Service Worker loaded:', CACHE_VERSION);
diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx
index 1a6bb85..f9a6e2c 100644
--- a/frontend/src/main.jsx
+++ b/frontend/src/main.jsx
@@ -23,3 +23,42 @@ createRoot(document.getElementById('root')).render(
,
)
+
+// Registrar Service Worker para PWA
+if ('serviceWorker' in navigator) {
+ window.addEventListener('load', () => {
+ navigator.serviceWorker
+ .register('/sw.js')
+ .then((registration) => {
+ console.log('[PWA] Service Worker registered:', registration.scope);
+
+ // Verificar atualizações a cada 1 hora
+ setInterval(() => {
+ registration.update();
+ }, 60 * 60 * 1000);
+
+ // Notificar quando houver atualização
+ registration.addEventListener('updatefound', () => {
+ const newWorker = registration.installing;
+ newWorker.addEventListener('statechange', () => {
+ if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
+ console.log('[PWA] Nova versão disponível! Recarregue a página.');
+ // Opcional: mostrar notificação ao usuário
+ if (window.confirm('Nova versão disponível! Deseja atualizar?')) {
+ newWorker.postMessage({ type: 'SKIP_WAITING' });
+ window.location.reload();
+ }
+ }
+ });
+ });
+ })
+ .catch((error) => {
+ console.error('[PWA] Service Worker registration failed:', error);
+ });
+
+ // Recarregar quando Service Worker assumir controle
+ navigator.serviceWorker.addEventListener('controllerchange', () => {
+ window.location.reload();
+ });
+ });
+}