chore: initial project setup with documentation and design assets
Add project foundation: CLAUDE.md, requirements tracking system, technical architecture docs, Firestore setup guide, device testing guide, and Stitch design mockups for Precision Vitality app. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
416
docs/DEVICE_TESTING.md
Normal file
416
docs/DEVICE_TESTING.md
Normal file
@@ -0,0 +1,416 @@
|
||||
# Test su Device e Creazione APK senza Android Studio
|
||||
|
||||
> Guida per testare l'app su dispositivo reale e generare APK usando solo CLI (senza Android Studio).
|
||||
> Ultimo aggiornamento: 16/04/2026
|
||||
|
||||
---
|
||||
|
||||
## 1. Prerequisiti
|
||||
|
||||
### Software Necessario
|
||||
| Software | Versione | Download | Note |
|
||||
|----------|----------|----------|------|
|
||||
| Node.js | 18+ | nodejs.org | Già installato |
|
||||
| Java JDK | 17+ | adoptium.net | Per Gradle |
|
||||
| Android SDK (cmdline-tools) | Latest | developer.android.com | Solo CLI, no IDE |
|
||||
| Gradle | 8+ | Incluso nel progetto | Via wrapper |
|
||||
|
||||
### Installare Android SDK Command Line Tools (senza Android Studio)
|
||||
|
||||
#### Windows
|
||||
```bash
|
||||
# 1. Scaricare command line tools da:
|
||||
# https://developer.android.com/studio#command-line-tools-only
|
||||
|
||||
# 2. Creare struttura directory
|
||||
mkdir -p C:/Android/cmdline-tools/latest
|
||||
|
||||
# 3. Estrarre lo zip in C:/Android/cmdline-tools/latest/
|
||||
# (i file bin/, lib/ etc. devono essere DENTRO latest/)
|
||||
|
||||
# 4. Configurare variabili d'ambiente (aggiungere al profilo bash o System env)
|
||||
export ANDROID_HOME=C:/Android
|
||||
export ANDROID_SDK_ROOT=C:/Android
|
||||
export PATH=$PATH:$ANDROID_HOME/cmdline-tools/latest/bin
|
||||
export PATH=$PATH:$ANDROID_HOME/platform-tools
|
||||
```
|
||||
|
||||
#### Installare componenti SDK
|
||||
```bash
|
||||
# Accettare licenze
|
||||
sdkmanager --licenses
|
||||
|
||||
# Installare componenti necessari
|
||||
sdkmanager "platform-tools"
|
||||
sdkmanager "platforms;android-34"
|
||||
sdkmanager "build-tools;34.0.0"
|
||||
|
||||
# Verificare installazione
|
||||
sdkmanager --list | head -20
|
||||
```
|
||||
|
||||
### Configurare JAVA_HOME
|
||||
```bash
|
||||
# Windows — verificare path Java
|
||||
# Il JDK 17+ deve essere installato
|
||||
export JAVA_HOME="C:/Program Files/Eclipse Adoptium/jdk-17.x.x-hotspot"
|
||||
# oppure
|
||||
export JAVA_HOME="C:/Program Files/Java/jdk-17"
|
||||
|
||||
# Verificare
|
||||
java -version
|
||||
javac -version
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Preparazione Progetto per Android
|
||||
|
||||
### Build dell'app Ionic
|
||||
```bash
|
||||
# Build di produzione
|
||||
ionic build --prod
|
||||
|
||||
# Oppure build di sviluppo (più veloce, con source maps)
|
||||
ionic build
|
||||
```
|
||||
|
||||
### Aggiungere piattaforma Android (prima volta)
|
||||
```bash
|
||||
npx cap add android
|
||||
```
|
||||
|
||||
### Sync dopo ogni build
|
||||
```bash
|
||||
npx cap sync android
|
||||
```
|
||||
|
||||
### Copiare solo i file web (senza sync dipendenze native)
|
||||
```bash
|
||||
npx cap copy android
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Build APK da Riga di Comando
|
||||
|
||||
### APK di Debug (per test)
|
||||
```bash
|
||||
# Navigare nella directory android
|
||||
cd android
|
||||
|
||||
# Build con Gradle wrapper (non serve Gradle installato globalmente)
|
||||
./gradlew assembleDebug
|
||||
|
||||
# L'APK si trova in:
|
||||
# android/app/build/outputs/apk/debug/app-debug.apk
|
||||
```
|
||||
|
||||
### APK di Release (per distribuzione)
|
||||
```bash
|
||||
# 1. Generare keystore (solo la prima volta)
|
||||
keytool -genkey -v \
|
||||
-keystore calorie-tracker-release.keystore \
|
||||
-alias calorie-tracker \
|
||||
-keyalg RSA \
|
||||
-keysize 2048 \
|
||||
-validity 10000
|
||||
|
||||
# 2. Configurare signing in android/app/build.gradle
|
||||
# Aggiungere nella sezione android {}:
|
||||
```
|
||||
|
||||
```groovy
|
||||
// In android/app/build.gradle
|
||||
android {
|
||||
// ... configurazione esistente ...
|
||||
|
||||
signingConfigs {
|
||||
release {
|
||||
storeFile file('calorie-tracker-release.keystore')
|
||||
storePassword 'LA_TUA_PASSWORD'
|
||||
keyAlias 'calorie-tracker'
|
||||
keyPassword 'LA_TUA_PASSWORD'
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
signingConfig signingConfigs.release
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
# 3. Build release APK
|
||||
cd android
|
||||
./gradlew assembleRelease
|
||||
|
||||
# L'APK si trova in:
|
||||
# android/app/build/outputs/apk/release/app-release.apk
|
||||
```
|
||||
|
||||
### AAB (Android App Bundle) per Google Play
|
||||
```bash
|
||||
cd android
|
||||
./gradlew bundleRelease
|
||||
|
||||
# L'AAB si trova in:
|
||||
# android/app/build/outputs/bundle/release/app-release.aab
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Installare APK su Device
|
||||
|
||||
### Via USB (ADB)
|
||||
```bash
|
||||
# 1. Abilitare "Opzioni sviluppatore" sul telefono:
|
||||
# Impostazioni → Info telefono → Tocca 7 volte "Numero build"
|
||||
|
||||
# 2. Abilitare "Debug USB" nelle opzioni sviluppatore
|
||||
|
||||
# 3. Collegare il telefono via USB
|
||||
|
||||
# 4. Verificare che il device sia rilevato
|
||||
adb devices
|
||||
# Deve mostrare il device con stato "device"
|
||||
|
||||
# 5. Installare l'APK
|
||||
adb install android/app/build/outputs/apk/debug/app-debug.apk
|
||||
|
||||
# Per reinstallare (sovrascrivere)
|
||||
adb install -r android/app/build/outputs/apk/debug/app-debug.apk
|
||||
```
|
||||
|
||||
### Via WiFi (ADB wireless)
|
||||
```bash
|
||||
# 1. Collegare prima via USB
|
||||
adb devices
|
||||
|
||||
# 2. Abilitare TCP/IP
|
||||
adb tcpip 5555
|
||||
|
||||
# 3. Trovare IP del telefono (Impostazioni → WiFi → IP)
|
||||
adb connect 192.168.1.XXX:5555
|
||||
|
||||
# 4. Scollegare USB — ora si può installare via WiFi
|
||||
adb install android/app/build/outputs/apk/debug/app-debug.apk
|
||||
```
|
||||
|
||||
### Senza ADB — Trasferimento diretto
|
||||
1. Copiare l'APK sul telefono (via USB, email, cloud, etc.)
|
||||
2. Sul telefono: abilitare "Installa da origini sconosciute"
|
||||
3. Aprire il file APK → Installa
|
||||
|
||||
---
|
||||
|
||||
## 5. Test in Tempo Reale (Live Reload)
|
||||
|
||||
### Live Reload su Device via WiFi
|
||||
Il modo più veloce per testare durante lo sviluppo:
|
||||
|
||||
```bash
|
||||
# 1. Trovare l'IP della macchina di sviluppo
|
||||
# Windows:
|
||||
ipconfig | grep "IPv4"
|
||||
# Es: 192.168.1.100
|
||||
|
||||
# 2. Avviare Ionic con external host
|
||||
ionic serve --external --host=0.0.0.0
|
||||
|
||||
# 3. Modificare capacitor.config.ts per il live reload
|
||||
```
|
||||
|
||||
```typescript
|
||||
// capacitor.config.ts — SOLO PER SVILUPPO
|
||||
const config: CapacitorConfig = {
|
||||
appId: 'com.precisionvitality.app',
|
||||
appName: 'Precision Vitality',
|
||||
webDir: 'www',
|
||||
server: {
|
||||
// SOLO per live reload development!
|
||||
url: 'http://192.168.1.100:8100', // IP della tua macchina
|
||||
cleartext: true
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
```bash
|
||||
# 4. Sync e reinstallare
|
||||
npx cap sync android
|
||||
cd android && ./gradlew assembleDebug && cd ..
|
||||
adb install -r android/app/build/outputs/apk/debug/app-debug.apk
|
||||
|
||||
# 5. Aprire l'app sul device — le modifiche al codice si rifletteranno in real-time!
|
||||
```
|
||||
|
||||
> **IMPORTANTE**: Rimuovere la sezione `server.url` da `capacitor.config.ts` prima del build di produzione!
|
||||
|
||||
---
|
||||
|
||||
## 6. Debug Remoto
|
||||
|
||||
### Chrome DevTools (per WebView)
|
||||
1. Collegare device via USB con debug abilitato
|
||||
2. Aprire Chrome sul PC → `chrome://inspect`
|
||||
3. Il WebView dell'app apparirà nella lista
|
||||
4. Cliccare "inspect" → si apre DevTools con console, network, elements
|
||||
|
||||
### Logcat (log nativi Android)
|
||||
```bash
|
||||
# Tutti i log
|
||||
adb logcat
|
||||
|
||||
# Filtrare per l'app
|
||||
adb logcat | grep -i "capacitor\|precision"
|
||||
|
||||
# Solo errori
|
||||
adb logcat *:E
|
||||
|
||||
# Pulire e seguire
|
||||
adb logcat -c && adb logcat
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Script Automazione
|
||||
|
||||
### Script build + install completo
|
||||
Creare `scripts/build-and-install.sh`:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "=== Building Ionic app ==="
|
||||
ionic build
|
||||
|
||||
echo "=== Syncing Capacitor ==="
|
||||
npx cap sync android
|
||||
|
||||
echo "=== Building APK debug ==="
|
||||
cd android
|
||||
./gradlew assembleDebug
|
||||
cd ..
|
||||
|
||||
APK_PATH="android/app/build/outputs/apk/debug/app-debug.apk"
|
||||
|
||||
echo "=== Installing on device ==="
|
||||
adb install -r "$APK_PATH"
|
||||
|
||||
echo "=== Done! APK: $APK_PATH ==="
|
||||
echo "=== App launched ==="
|
||||
adb shell am start -n com.precisionvitality.app/.MainActivity
|
||||
```
|
||||
|
||||
### Script build release
|
||||
Creare `scripts/build-release.sh`:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "=== Building Ionic production ==="
|
||||
ionic build --prod
|
||||
|
||||
echo "=== Syncing Capacitor ==="
|
||||
npx cap sync android
|
||||
|
||||
echo "=== Building Release APK ==="
|
||||
cd android
|
||||
./gradlew assembleRelease
|
||||
cd ..
|
||||
|
||||
APK_PATH="android/app/build/outputs/apk/release/app-release.apk"
|
||||
echo "=== Release APK: $APK_PATH ==="
|
||||
ls -lh "$APK_PATH"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Troubleshooting
|
||||
|
||||
### Errore: "ANDROID_HOME not set"
|
||||
```bash
|
||||
# Verificare che le variabili siano settate
|
||||
echo $ANDROID_HOME
|
||||
echo $ANDROID_SDK_ROOT
|
||||
|
||||
# Se vuote, settarle nel profilo (~/.bashrc o ~/.bash_profile)
|
||||
export ANDROID_HOME=C:/Android
|
||||
export ANDROID_SDK_ROOT=C:/Android
|
||||
```
|
||||
|
||||
### Errore: "SDK location not found"
|
||||
```bash
|
||||
# Creare/verificare android/local.properties
|
||||
echo "sdk.dir=C:\\\\Android" > android/local.properties
|
||||
```
|
||||
|
||||
### Errore: "No connected devices"
|
||||
```bash
|
||||
# Verificare driver USB (Windows)
|
||||
# Installare Google USB Driver o driver OEM del produttore
|
||||
|
||||
# Verificare che il device sia in modalità debug
|
||||
adb kill-server
|
||||
adb start-server
|
||||
adb devices
|
||||
```
|
||||
|
||||
### Errore: "INSTALL_FAILED_UPDATE_INCOMPATIBLE"
|
||||
```bash
|
||||
# Disinstallare la versione precedente
|
||||
adb uninstall com.precisionvitality.app
|
||||
# Poi reinstallare
|
||||
adb install android/app/build/outputs/apk/debug/app-debug.apk
|
||||
```
|
||||
|
||||
### Build lento
|
||||
```bash
|
||||
# Aggiungere a android/gradle.properties:
|
||||
org.gradle.jvmargs=-Xmx4096m
|
||||
org.gradle.daemon=true
|
||||
org.gradle.parallel=true
|
||||
org.gradle.caching=true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Checklist Test su Device
|
||||
|
||||
### Prima del test
|
||||
- [ ] Node.js e Ionic CLI installati
|
||||
- [ ] Android SDK command line tools installati
|
||||
- [ ] Java JDK 17+ installato
|
||||
- [ ] ANDROID_HOME e JAVA_HOME configurati
|
||||
- [ ] Device in modalità debug USB
|
||||
|
||||
### Test funzionali
|
||||
- [ ] App si avvia correttamente
|
||||
- [ ] Navigazione tra tab funzionante
|
||||
- [ ] FAB visibile e cliccabile
|
||||
- [ ] Modal input numerico funzionante
|
||||
- [ ] Modal input LLM funzionante
|
||||
- [ ] Tastiera nativa si apre correttamente
|
||||
- [ ] Scroll liste fluido
|
||||
- [ ] Grafico statistiche visibile
|
||||
- [ ] Ricerca funzionante
|
||||
- [ ] Connessione a Firestore (online)
|
||||
- [ ] Funzionamento offline (attivare modalità aereo)
|
||||
|
||||
### Test performance
|
||||
- [ ] Tempo avvio app < 3 secondi
|
||||
- [ ] Scroll fluido (60fps)
|
||||
- [ ] Risposta AI < 5 secondi
|
||||
- [ ] Nessun crash in 10 minuti di utilizzo
|
||||
|
||||
### Test compatibilità
|
||||
- [ ] Android 10+ (API 29+)
|
||||
- [ ] Schermo piccolo (5") e grande (6.5"+)
|
||||
- [ ] Orientamento portrait
|
||||
- [ ] Tastiera non copre input
|
||||
349
docs/FIRESTORE_SETUP.md
Normal file
349
docs/FIRESTORE_SETUP.md
Normal file
@@ -0,0 +1,349 @@
|
||||
# Guida Configurazione Firebase & Firestore
|
||||
|
||||
> Guida passo-passo per configurare Firebase, Firestore e Cloud Functions per CalorieTracker.
|
||||
> Ultimo aggiornamento: 16/04/2026
|
||||
|
||||
---
|
||||
|
||||
## 1. Creazione Progetto Firebase
|
||||
|
||||
### Passo 1 — Console Firebase
|
||||
1. Andare su [console.firebase.google.com](https://console.firebase.google.com)
|
||||
2. Cliccare "Aggiungi progetto"
|
||||
3. Nome progetto: `calorie-tracker` (o simile disponibile)
|
||||
4. **Disabilitare** Google Analytics (non necessario per ora)
|
||||
5. Cliccare "Crea progetto"
|
||||
|
||||
### Passo 2 — Aggiungere App Web
|
||||
1. Nella dashboard progetto, cliccare l'icona **Web** (`</>`)
|
||||
2. Nome app: `CalorieTracker Web`
|
||||
3. **Non** abilitare Firebase Hosting (non necessario)
|
||||
4. Copiare la configurazione Firebase:
|
||||
```javascript
|
||||
const firebaseConfig = {
|
||||
apiKey: "AIza...",
|
||||
authDomain: "calorie-tracker-XXXXX.firebaseapp.com",
|
||||
projectId: "calorie-tracker-XXXXX",
|
||||
storageBucket: "calorie-tracker-XXXXX.appspot.com",
|
||||
messagingSenderId: "123456789",
|
||||
appId: "1:123456789:web:abcdef"
|
||||
};
|
||||
```
|
||||
5. Salvare questi valori in `environment.ts` e `environment.prod.ts`
|
||||
|
||||
---
|
||||
|
||||
## 2. Abilitare Firestore
|
||||
|
||||
### Passo 1 — Creare Database
|
||||
1. Nel menu laterale Firebase: **Build → Firestore Database**
|
||||
2. Cliccare "Crea database"
|
||||
3. Selezionare località: **europe-west1 (Belgio)** — più vicino all'Italia
|
||||
4. Selezionare "Inizia in modalità produzione"
|
||||
5. Cliccare "Abilita"
|
||||
|
||||
### Passo 2 — Regole di Sicurezza
|
||||
Nella tab **Regole** di Firestore, incollare:
|
||||
|
||||
```
|
||||
rules_version = '2';
|
||||
|
||||
service cloud.firestore {
|
||||
match /databases/{database}/documents {
|
||||
|
||||
// Nessun accesso di default
|
||||
match /{document=**} {
|
||||
allow read, write: if false;
|
||||
}
|
||||
|
||||
// Accesso pasti: solo utente proprietario
|
||||
match /users/{userId}/meals/{mealId} {
|
||||
allow read, write: if request.auth != null
|
||||
&& request.auth.uid == userId;
|
||||
|
||||
// Validazione scrittura
|
||||
allow create: if request.auth != null
|
||||
&& request.auth.uid == userId
|
||||
&& request.resource.data.calories is number
|
||||
&& request.resource.data.calories > 0
|
||||
&& request.resource.data.calories <= 9999
|
||||
&& request.resource.data.inputType in ['manual', 'llm']
|
||||
&& request.resource.data.timestamp is timestamp
|
||||
&& request.resource.data.createdAt is timestamp;
|
||||
|
||||
allow update: if request.auth != null
|
||||
&& request.auth.uid == userId;
|
||||
|
||||
allow delete: if request.auth != null
|
||||
&& request.auth.uid == userId;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Passo 3 — Indici
|
||||
Nella tab **Indici** di Firestore, creare indice composto:
|
||||
|
||||
| Collection | Campi | Query Scope |
|
||||
|------------|-------|-------------|
|
||||
| `users/{userId}/meals` | `timestamp` DESC | Collection |
|
||||
|
||||
> **Nota**: Firestore creerà automaticamente gli indici necessari quando l'app esegue le prime query. In alternativa, il file `firestore.indexes.json` nel progetto definisce gli indici programmaticamente.
|
||||
|
||||
---
|
||||
|
||||
## 3. Abilitare Authentication
|
||||
|
||||
### Passo 1 — Anonymous Auth
|
||||
1. Nel menu laterale: **Build → Authentication**
|
||||
2. Cliccare "Inizia"
|
||||
3. Tab **Metodo di accesso**
|
||||
4. Abilitare **Anonimo** → switch ON → Salva
|
||||
|
||||
### Passo 2 — (Futuro) Google Sign-In
|
||||
> Per upgrade futuro da Anonymous a Google Sign-In:
|
||||
> 1. Abilitare provider "Google" in Authentication
|
||||
> 2. Configurare OAuth consent screen in Google Cloud Console
|
||||
> 3. Implementare linking account (da anonimo a Google)
|
||||
|
||||
---
|
||||
|
||||
## 4. Configurazione App Android
|
||||
|
||||
### Passo 1 — Aggiungere App Android
|
||||
1. Dashboard Firebase → "Aggiungi app" → icona Android
|
||||
2. Package name: `com.precisionvitality.app` (deve corrispondere a `capacitor.config.ts`)
|
||||
3. App nickname: `CalorieTracker Android`
|
||||
4. SHA-1: (opzionale per ora, necessario per Google Sign-In)
|
||||
|
||||
### Passo 2 — Scaricare google-services.json
|
||||
1. Scaricare `google-services.json`
|
||||
2. Copiare in `android/app/google-services.json` (dopo `npx cap add android`)
|
||||
|
||||
---
|
||||
|
||||
## 5. Configurazione App iOS
|
||||
|
||||
### Passo 1 — Aggiungere App iOS
|
||||
1. Dashboard Firebase → "Aggiungi app" → icona iOS
|
||||
2. Bundle ID: `com.precisionvitality.app`
|
||||
3. App nickname: `CalorieTracker iOS`
|
||||
|
||||
### Passo 2 — Scaricare GoogleService-Info.plist
|
||||
1. Scaricare `GoogleService-Info.plist`
|
||||
2. Copiare in `ios/App/App/GoogleService-Info.plist` (dopo `npx cap add ios`)
|
||||
|
||||
---
|
||||
|
||||
## 6. Setup Firebase Cloud Functions (Proxy AI)
|
||||
|
||||
### Passo 1 — Upgrade a Blaze Plan (Pay-as-you-go)
|
||||
> **ATTENZIONE**: Le Cloud Functions richiedono il piano Blaze (pay-as-you-go).
|
||||
> Il piano Blaze include comunque il tier gratuito di Firestore e Auth.
|
||||
> Non ci saranno costi se il traffico resta nel free tier.
|
||||
|
||||
1. Console Firebase → icona ingranaggio → Utilizzo e fatturazione
|
||||
2. Upgrade a Blaze
|
||||
3. Impostare budget alert: $5/mese
|
||||
|
||||
### Alternativa gratuita: Se non si vuole il piano Blaze
|
||||
- Usare un backend NestJS su VPS esistente come proxy
|
||||
- Oppure usare Cloudflare Workers (free tier: 100k req/giorno)
|
||||
|
||||
### Passo 2 — Inizializzare Cloud Functions
|
||||
```bash
|
||||
# Nella root del progetto
|
||||
npm install -g firebase-tools
|
||||
firebase login
|
||||
firebase init functions
|
||||
|
||||
# Selezionare:
|
||||
# - Linguaggio: TypeScript
|
||||
# - ESLint: Sì
|
||||
# - Installare dipendenze: Sì
|
||||
```
|
||||
|
||||
### Passo 3 — Configurare Secret per API Key
|
||||
```bash
|
||||
# Salvare API key come secret (non in codice!)
|
||||
firebase functions:secrets:set ANTHROPIC_API_KEY
|
||||
# Inserire: sk-ant-...
|
||||
```
|
||||
|
||||
### Passo 4 — Implementare la Function
|
||||
File `functions/src/index.ts`:
|
||||
|
||||
```typescript
|
||||
import { onRequest } from 'firebase-functions/v2/https';
|
||||
import { defineSecret } from 'firebase-functions/params';
|
||||
import Anthropic from '@anthropic-ai/sdk';
|
||||
|
||||
const anthropicKey = defineSecret('ANTHROPIC_API_KEY');
|
||||
|
||||
export const estimateCalories = onRequest(
|
||||
{
|
||||
cors: true,
|
||||
region: 'europe-west1',
|
||||
secrets: [anthropicKey],
|
||||
timeoutSeconds: 15,
|
||||
memory: '256MiB',
|
||||
},
|
||||
async (req, res) => {
|
||||
// Verificare metodo POST
|
||||
if (req.method !== 'POST') {
|
||||
res.status(405).send('Method Not Allowed');
|
||||
return;
|
||||
}
|
||||
|
||||
// Verificare auth token
|
||||
const authHeader = req.headers.authorization;
|
||||
if (!authHeader?.startsWith('Bearer ')) {
|
||||
res.status(401).send('Unauthorized');
|
||||
return;
|
||||
}
|
||||
|
||||
// Estrarre e validare input
|
||||
const { description } = req.body;
|
||||
if (!description || typeof description !== 'string' || description.length > 500) {
|
||||
res.status(400).send('Invalid description');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const client = new Anthropic({ apiKey: anthropicKey.value() });
|
||||
|
||||
const message = await client.messages.create({
|
||||
model: 'claude-haiku-4-5-20251001',
|
||||
max_tokens: 256,
|
||||
system: `Sei un nutrizionista. Stima le calorie del pasto descritto.
|
||||
Rispondi SOLO con JSON: {"calories": number, "confidence": "high"|"medium"|"low", "explanation": "string"}`,
|
||||
messages: [{ role: 'user', content: description }],
|
||||
});
|
||||
|
||||
// Parsare risposta
|
||||
const text = message.content[0].type === 'text' ? message.content[0].text : '';
|
||||
const jsonStr = text.replace(/```json?\n?/g, '').replace(/```/g, '').trim();
|
||||
const result = JSON.parse(jsonStr);
|
||||
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
console.error('Estimation error:', error);
|
||||
res.status(500).json({ error: 'Estimation failed' });
|
||||
}
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
### Passo 5 — Deploy
|
||||
```bash
|
||||
cd functions
|
||||
npm install @anthropic-ai/sdk
|
||||
firebase deploy --only functions
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Integrazione Angular
|
||||
|
||||
### Installare dipendenze
|
||||
```bash
|
||||
npm install @angular/fire firebase
|
||||
```
|
||||
|
||||
### Configurare in app.config.ts (Angular 17+ standalone)
|
||||
```typescript
|
||||
import { provideFirebaseApp, initializeApp } from '@angular/fire/app';
|
||||
import { provideFirestore, getFirestore, enableIndexedDbPersistence } from '@angular/fire/firestore';
|
||||
import { provideAuth, getAuth } from '@angular/fire/auth';
|
||||
import { environment } from '../environments/environment';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
provideFirebaseApp(() => initializeApp(environment.firebase)),
|
||||
provideFirestore(() => {
|
||||
const firestore = getFirestore();
|
||||
enableIndexedDbPersistence(firestore);
|
||||
return firestore;
|
||||
}),
|
||||
provideAuth(() => getAuth()),
|
||||
// ... altri providers
|
||||
]
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. File di Configurazione Locale
|
||||
|
||||
### `firestore.rules` (nella root del progetto)
|
||||
Copiare le regole dal Passo 2 della Sezione 2.
|
||||
|
||||
### `firestore.indexes.json`
|
||||
```json
|
||||
{
|
||||
"indexes": [
|
||||
{
|
||||
"collectionGroup": "meals",
|
||||
"queryScope": "COLLECTION",
|
||||
"fields": [
|
||||
{ "fieldPath": "timestamp", "order": "DESCENDING" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"fieldOverrides": []
|
||||
}
|
||||
```
|
||||
|
||||
### `.firebaserc`
|
||||
```json
|
||||
{
|
||||
"projects": {
|
||||
"default": "calorie-tracker-XXXXX"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `firebase.json`
|
||||
```json
|
||||
{
|
||||
"firestore": {
|
||||
"rules": "firestore.rules",
|
||||
"indexes": "firestore.indexes.json"
|
||||
},
|
||||
"functions": {
|
||||
"source": "functions",
|
||||
"runtime": "nodejs18"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Limiti Free Tier Firebase (Spark / Blaze)
|
||||
|
||||
| Risorsa | Limite Gratuito | Note |
|
||||
|---------|----------------|------|
|
||||
| Firestore letture | 50.000/giorno | ~1.600 utenti × 30 letture/giorno |
|
||||
| Firestore scritture | 20.000/giorno | ~2.000 utenti × 10 pasti/giorno |
|
||||
| Firestore storage | 1 GiB | Sufficiente per ~5M documenti pasto |
|
||||
| Auth utenti | Illimitati | Anonymous + email + social |
|
||||
| Cloud Functions invocazioni | 2M/mese | ~66.000/giorno |
|
||||
| Cloud Functions compute | 400.000 GB-secondi/mese | Sufficiente per proxy AI |
|
||||
|
||||
---
|
||||
|
||||
## 10. Checklist Configurazione
|
||||
|
||||
- [ ] Progetto Firebase creato
|
||||
- [ ] Firestore abilitato (europe-west1)
|
||||
- [ ] Regole sicurezza configurate
|
||||
- [ ] Anonymous Auth abilitato
|
||||
- [ ] App Web registrata + config copiata
|
||||
- [ ] App Android registrata + google-services.json scaricato
|
||||
- [ ] (Opzionale) App iOS registrata + GoogleService-Info.plist scaricato
|
||||
- [ ] Cloud Functions inizializzate
|
||||
- [ ] ANTHROPIC_API_KEY salvata come secret
|
||||
- [ ] Cloud Function deployata
|
||||
- [ ] @angular/fire installato e configurato
|
||||
- [ ] Test lettura/scrittura funzionante
|
||||
- [ ] Offline persistence verificata
|
||||
312
docs/REQUIREMENTS.md
Normal file
312
docs/REQUIREMENTS.md
Normal file
@@ -0,0 +1,312 @@
|
||||
# Requisiti — CalorieTracker (Precision Vitality)
|
||||
|
||||
> Documento di riferimento per tutti i requisiti funzionali e sistemistici del progetto.
|
||||
> Ultimo aggiornamento: 16/04/2026
|
||||
|
||||
---
|
||||
|
||||
## Indice Requisiti
|
||||
|
||||
| ID | Tipo | Priorità | Titolo | Dipendenze |
|
||||
|----|------|----------|--------|------------|
|
||||
| REQ-001 | SISTEMISTICO | CRITICA | Scaffolding Ionic + Capacitor | — |
|
||||
| REQ-002 | SISTEMISTICO | CRITICA | Setup Firebase Firestore | REQ-001 |
|
||||
| REQ-003 | EVOLUTIVO | ALTA | Home screen + FAB | REQ-002 |
|
||||
| REQ-004 | EVOLUTIVO | ALTA | Modal input numerico | REQ-003 |
|
||||
| REQ-005 | EVOLUTIVO | ALTA | Modal input LLM | REQ-003, REQ-006 |
|
||||
| REQ-006 | EVOLUTIVO | ALTA | Servizio Claude API | REQ-001 |
|
||||
| REQ-007 | EVOLUTIVO | ALTA | CRUD pasti Firestore | REQ-002 |
|
||||
| REQ-008 | EVOLUTIVO | MEDIA | Statistiche giornaliere | REQ-007 |
|
||||
| REQ-009 | EVOLUTIVO | MEDIA | Statistiche settimanali | REQ-007 |
|
||||
| REQ-010 | EVOLUTIVO | MEDIA | Ricerca pasti | REQ-007 |
|
||||
| REQ-011 | REWORK | MEDIA | Design brief Google Stitch | — |
|
||||
|
||||
---
|
||||
|
||||
## REQ-001 — Scaffolding Ionic + Capacitor
|
||||
|
||||
**Tipo**: SISTEMISTICO | **Priorità**: CRITICA
|
||||
|
||||
**Obiettivo**: Scaffolding del progetto Ionic 7+ con Capacitor 5+. Configurazione build nativa Android/iOS. Setup ambiente con struttura cartelle, linting e variabili d'ambiente.
|
||||
|
||||
**Stato Target**:
|
||||
- Progetto Ionic Angular inizializzato con routing e tab layout (Journal, Stats, Plans, Profile)
|
||||
- Capacitor configurato per Android e iOS
|
||||
- Variabili ambiente: `ANTHROPIC_API_KEY`, `FIREBASE_*`
|
||||
- Struttura moduli: `core/`, `shared/`, `features/home`, `features/stats`, `features/search`
|
||||
- Proxy sviluppo configurato per CORS Anthropic API
|
||||
|
||||
**Passi Implementativi**:
|
||||
1. `ionic start calorie-tracker tabs --type=angular --capacitor`
|
||||
2. Aggiungere `@capacitor/android` e `@capacitor/ios`
|
||||
3. Configurare `capacitor.config.ts` con appId e server URL
|
||||
4. Creare struttura moduli `features/` con lazy loading
|
||||
5. Aggiungere `@ionic/storage-angular` per cache locale
|
||||
6. Configurare `environment.ts` e `environment.prod.ts`
|
||||
7. Verificare build `ionic build` + `npx cap sync`
|
||||
|
||||
**Criteri Accettazione**:
|
||||
- [ ] `ionic serve` avvia l'app senza errori
|
||||
- [ ] `ionic build` produce `www/` senza errori
|
||||
- [ ] `npx cap sync` completa senza errori
|
||||
- [ ] Struttura a 4 tab visibile nel browser (Journal, Stats, Plans, Profile)
|
||||
- [ ] Variabili d'ambiente lette correttamente in `environment.ts`
|
||||
|
||||
---
|
||||
|
||||
## REQ-002 — Setup Firebase Firestore
|
||||
|
||||
**Tipo**: SISTEMISTICO | **Priorità**: CRITICA | **Dipende da**: REQ-001
|
||||
|
||||
**Obiettivo**: Setup Firebase con Firestore come database. Schema dati, regole sicurezza e SDK Angular.
|
||||
|
||||
**Schema Firestore**:
|
||||
```
|
||||
users/{userId}/
|
||||
meals/{mealId}/
|
||||
- id: string
|
||||
- timestamp: Timestamp
|
||||
- calories: number
|
||||
- inputType: 'manual' | 'llm'
|
||||
- description?: string
|
||||
- llmConfidence?: 'high' | 'medium' | 'low'
|
||||
- llmExplanation?: string
|
||||
- createdAt: Timestamp
|
||||
```
|
||||
|
||||
**Regole Sicurezza**:
|
||||
- Lettura/scrittura solo all'utente autenticato proprietario
|
||||
- Nessun accesso cross-user
|
||||
|
||||
**Auth**: Anonymous auth abilitato (upgrade Google Sign-In futuro)
|
||||
|
||||
**Passi Implementativi**:
|
||||
1. Creare Firebase project su console
|
||||
2. Abilitare Firestore in modalità produzione
|
||||
3. Abilitare Anonymous Authentication
|
||||
4. Scaricare `google-services.json` e `GoogleService-Info.plist`
|
||||
5. Installare `@angular/fire` e configurare in `app.module.ts`
|
||||
6. Definire regole Firestore in `firestore.rules`
|
||||
7. Creare `FirestoreService` con CRUD per collection `meals`
|
||||
8. Testare lettura/scrittura da `ionic serve`
|
||||
|
||||
**Criteri Accettazione**:
|
||||
- [ ] Scrittura documento su Firestore funzionante
|
||||
- [ ] Lettura documenti da Firestore funzionante
|
||||
- [ ] Regole sicurezza bloccano accesso non autenticato
|
||||
- [ ] `FirestoreService` espone: `addMeal()`, `getMealsByDate()`, `getMealsRange()`
|
||||
- [ ] Offline persistence abilitata e testata
|
||||
|
||||
---
|
||||
|
||||
## REQ-003 — Home Screen + FAB
|
||||
|
||||
**Tipo**: EVOLUTIVO | **Priorità**: ALTA | **Dipende da**: REQ-002
|
||||
|
||||
**Obiettivo**: Home screen (tab Journal) con totale kcal giornaliero, lista pasti e FAB per aggiunta.
|
||||
|
||||
**Specifiche UI** (da design Precision Vitality):
|
||||
- Header: data odierna centrata (es. "MERCOLEDÌ 16 APRILE")
|
||||
- Titolo: "Il tuo progresso giornaliero"
|
||||
- Numero kcal grande (56px, Lexend Bold, verde `#006b1b`)
|
||||
- Label "calorie oggi"
|
||||
- Barra progresso: kcal / obiettivo (default 2000)
|
||||
- Sezione "Diario Alimentare": lista card pasti (ora, descrizione, kcal)
|
||||
- Badge "AI" (arancione) o "M" (grigio) su ogni pasto
|
||||
- FAB arancione `#FF7043` in basso a destra con icona "+"
|
||||
- Empty state: "Nessun pasto registrato oggi. Inizia!"
|
||||
|
||||
**Criteri Accettazione**:
|
||||
- [ ] Totale kcal odierno visibile e aggiornato real-time
|
||||
- [ ] Lista pasti del giorno visibile e scrollabile
|
||||
- [ ] FAB visibile e cliccabile su tutti i device
|
||||
- [ ] Empty state quando nessun pasto registrato
|
||||
- [ ] Aggiornamento immediato dopo salvataggio nuovo pasto
|
||||
|
||||
---
|
||||
|
||||
## REQ-004 — Modal Input Numerico
|
||||
|
||||
**Tipo**: EVOLUTIVO | **Priorità**: ALTA | **Dipende da**: REQ-003
|
||||
|
||||
**Obiettivo**: Modal per inserimento diretto kcal con tastiera numerica.
|
||||
|
||||
**Specifiche UI** (da design):
|
||||
- Titolo: "Inserisci calorie"
|
||||
- Icona fulmine verde
|
||||
- Motivazionale: "Ogni dato conta per il tuo obiettivo."
|
||||
- Campo numerico grande (font 40px), placeholder "0", label "KCAL"
|
||||
- Campo "Descrizione (opzionale)"
|
||||
- Bottone "Salva" verde a tutta larghezza
|
||||
- Suggerimenti veloci: 150 (Spuntino), 450 (Pranzo), 600 (Cena)
|
||||
|
||||
**Validazione**: valore > 0 e ≤ 9999
|
||||
|
||||
**Criteri Accettazione**:
|
||||
- [ ] Modal si apre dal FAB
|
||||
- [ ] Tastiera numerica nativa su mobile
|
||||
- [ ] Validazione blocca valori ≤ 0 e > 9999
|
||||
- [ ] Salvataggio corretto su Firestore
|
||||
- [ ] Toast conferma dopo salvataggio
|
||||
- [ ] Modal si chiude e Home aggiorna totale
|
||||
|
||||
---
|
||||
|
||||
## REQ-005 — Modal Input LLM (Descrizione Pasto)
|
||||
|
||||
**Tipo**: EVOLUTIVO | **Priorità**: ALTA | **Dipende da**: REQ-003, REQ-006
|
||||
|
||||
**Obiettivo**: Modal per descrizione testuale del pasto con stima AI.
|
||||
|
||||
**Specifiche UI** (da design):
|
||||
- Titolo: "Descrivi il pasto"
|
||||
- Sottotitolo: "Usa il linguaggio naturale per stimare i tuoi nutrienti in pochi secondi."
|
||||
- Textarea "IL TUO PASTO" con placeholder
|
||||
- Bottone "Stima calorie" arancione
|
||||
- Risultato: badge "ALTA PRECISIONE" / "MEDIA" / "BASSA"
|
||||
- Kcal stimate in grande + breakdown (carbi, proteine, grassi)
|
||||
- Campo kcal modificabile
|
||||
- Bottone "Salva" verde
|
||||
|
||||
**Criteri Accettazione**:
|
||||
- [ ] Textarea accetta input testuale
|
||||
- [ ] Bottone "Stima calorie" chiama API (REQ-006)
|
||||
- [ ] Loading spinner durante chiamata
|
||||
- [ ] Risultato kcal e badge confidence corretti
|
||||
- [ ] Campo kcal modificabile dopo stima
|
||||
- [ ] Salvataggio su Firestore con `inputType: 'llm'`
|
||||
- [ ] Gestione errore timeout (messaggio + retry)
|
||||
|
||||
---
|
||||
|
||||
## REQ-006 — Servizio Claude API
|
||||
|
||||
**Tipo**: EVOLUTIVO | **Priorità**: ALTA | **Dipende da**: REQ-001
|
||||
|
||||
**Obiettivo**: Servizio Angular per stima calorica via Claude API con proxy sicuro.
|
||||
|
||||
**Architettura**: Firebase Cloud Function come proxy (API key mai nel bundle)
|
||||
|
||||
**Specifiche**:
|
||||
- Model: `claude-haiku-4-5-20251001`
|
||||
- Max tokens: 256
|
||||
- Food dictionary incluso nel system prompt
|
||||
- Parser JSON robusto (strip markdown code blocks)
|
||||
- Timeout: 10 secondi
|
||||
- Risposta: `{ calories, confidence, explanation }`
|
||||
|
||||
**Criteri Accettazione**:
|
||||
- [ ] Stima kcal corretta per descrizioni test
|
||||
- [ ] Food dictionary nel system prompt
|
||||
- [ ] API key non nel bundle app
|
||||
- [ ] Parser gestisce JSON wrapped in markdown
|
||||
- [ ] Timeout 10s con errore esplicito
|
||||
|
||||
---
|
||||
|
||||
## REQ-007 — CRUD Pasti Firestore
|
||||
|
||||
**Tipo**: EVOLUTIVO | **Priorità**: ALTA | **Dipende da**: REQ-002
|
||||
|
||||
**Obiettivo**: Servizio Angular per CRUD pasti su Firestore.
|
||||
|
||||
**Metodi**:
|
||||
- `addMeal(data)` — scrittura con timestamp e userId
|
||||
- `getMealsForDate(date)` — query filtrata per giorno (Observable)
|
||||
- `getMealsInRange(from, to)` — query per range date
|
||||
- `deleteMeal(mealId)` — hard delete
|
||||
- `getTotalCaloriesForDate(date)` — aggregazione client-side
|
||||
|
||||
**Criteri Accettazione**:
|
||||
- [ ] `addMeal()` scrive documento corretto
|
||||
- [ ] `getMealsForDate()` restituisce solo pasti del giorno richiesto
|
||||
- [ ] `getMealsInRange()` restituisce pasti nel range
|
||||
- [ ] `deleteMeal()` rimuove documento
|
||||
- [ ] Observable aggiorna componenti senza reload
|
||||
- [ ] Funziona offline
|
||||
|
||||
---
|
||||
|
||||
## REQ-008 — Statistiche Giornaliere
|
||||
|
||||
**Tipo**: EVOLUTIVO | **Priorità**: MEDIA | **Dipende da**: REQ-007
|
||||
|
||||
**Obiettivo**: Vista giornaliera nel tab Stats con totale kcal, breakdown pasti e progresso obiettivo.
|
||||
|
||||
**Specifiche UI**:
|
||||
- Tab selector: "Oggi" | "Settimana"
|
||||
- Navigazione tra giorni (frecce)
|
||||
- Totale kcal, barra progresso (verde/rosso)
|
||||
- Lista pasti con swipe-to-delete
|
||||
- Obiettivo calorico: salvato in @ionic/storage (default 2000)
|
||||
|
||||
**Criteri Accettazione**:
|
||||
- [ ] Totale kcal giornaliero corretto
|
||||
- [ ] Navigazione tra giorni funzionante
|
||||
- [ ] Barra progresso aggiornata real-time
|
||||
- [ ] Colore cambia se obiettivo superato
|
||||
- [ ] Swipe-to-delete funzionante
|
||||
|
||||
---
|
||||
|
||||
## REQ-009 — Statistiche Settimanali
|
||||
|
||||
**Tipo**: EVOLUTIVO | **Priorità**: MEDIA | **Dipende da**: REQ-007
|
||||
|
||||
**Obiettivo**: Grafico a barre 7 giorni con media settimanale.
|
||||
|
||||
**Specifiche UI** (da design):
|
||||
- Periodo corrente (es. "14-20 apr") con frecce navigazione
|
||||
- Grafico barre: barre verdi ≤ obiettivo, rosse > obiettivo
|
||||
- Asse X: LUN, MAR, MER... (giorno corrente evidenziato)
|
||||
- Legenda: "In target" (verde), "Eccesso" (rosso)
|
||||
- Card "RIEPILOGO PRESTAZIONI": media kcal/giorno
|
||||
- Indicatori: variazione % vs settimana precedente, giorni in target
|
||||
|
||||
**Criteri Accettazione**:
|
||||
- [ ] Grafico a barre con dati reali
|
||||
- [ ] Colori corretti sopra/sotto obiettivo
|
||||
- [ ] Media settimanale calcolata
|
||||
- [ ] Navigazione settimane funzionante
|
||||
- [ ] Tap su barra apre dettaglio giornaliero
|
||||
- [ ] Grafico responsive
|
||||
|
||||
---
|
||||
|
||||
## REQ-010 — Ricerca Pasti
|
||||
|
||||
**Tipo**: EVOLUTIVO | **Priorità**: MEDIA | **Dipende da**: REQ-007
|
||||
|
||||
**Obiettivo**: Tab ricerca per trovare pasti passati per testo o intervallo date.
|
||||
|
||||
**Specifiche**:
|
||||
- Searchbar con debounce 300ms
|
||||
- Filtro date opzionale (da/a)
|
||||
- Risultati: data, ora, kcal, descrizione, badge tipo
|
||||
- Funzione "Ri-registra": crea nuovo pasto con stesse kcal e timestamp corrente
|
||||
- Ordinamento: data DESC
|
||||
- Range: ultimi 30 giorni (client-side)
|
||||
|
||||
**Criteri Accettazione**:
|
||||
- [ ] Ricerca per testo funzionante con debounce
|
||||
- [ ] Filtro date funzionante
|
||||
- [ ] Badge tipo visibili
|
||||
- [ ] "Ri-registra" crea nuovo documento
|
||||
- [ ] Empty state "Nessun risultato"
|
||||
|
||||
---
|
||||
|
||||
## REQ-011 — Design Brief Google Stitch
|
||||
|
||||
**Tipo**: REWORK | **Priorità**: MEDIA
|
||||
|
||||
**Stato**: COMPLETATO — I mockup sono disponibili in `stitch_calorietracker_design_brief_brief/`
|
||||
|
||||
**Schermate generate**:
|
||||
1. Home (Journal) — `home_calorietracker/`
|
||||
2. Bottom sheet scelta input — `scelta_input_calorietracker/`
|
||||
3. Modal input numerico — `inserisci_calorie_calorietracker/`
|
||||
4. Modal descrizione LLM — `descrizione_ai_calorietracker/`
|
||||
5. Statistiche settimanali — `statistiche_calorietracker/`
|
||||
|
||||
**Design System**: `vitality_pulse/DESIGN.md`
|
||||
292
docs/TECHNICAL.md
Normal file
292
docs/TECHNICAL.md
Normal file
@@ -0,0 +1,292 @@
|
||||
# Requisiti Tecnici — CalorieTracker (Precision Vitality)
|
||||
|
||||
> Documento di architettura e decisioni tecniche del progetto.
|
||||
> Ultimo aggiornamento: 16/04/2026
|
||||
|
||||
---
|
||||
|
||||
## 1. Panoramica Architettura
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ App Ionic/Angular │
|
||||
│ ┌───────────┐ ┌───────────┐ ┌─────────┐ │
|
||||
│ │ Journal │ │ Stats │ │ Search │ │
|
||||
│ │ (Home) │ │ Day/Week │ │ │ │
|
||||
│ └─────┬─────┘ └─────┬─────┘ └────┬────┘ │
|
||||
│ │ │ │ │
|
||||
│ ┌─────┴───────────────┴─────────────┴────┐ │
|
||||
│ │ Core Services Layer │ │
|
||||
│ │ MealService | CalorieEstimator | Auth │ │
|
||||
│ └─────┬───────────────┬─────────────────┘ │
|
||||
│ │ │ │
|
||||
└────────┼───────────────┼────────────────────┘
|
||||
│ │
|
||||
┌────┴────┐ ┌──────┴──────┐
|
||||
│Firestore│ │ Firebase CF │
|
||||
│ (DB) │ │ (Proxy AI) │
|
||||
└─────────┘ └──────┬──────┘
|
||||
│
|
||||
┌──────┴──────┐
|
||||
│ Anthropic │
|
||||
│ Claude API │
|
||||
└─────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Stack Tecnologico
|
||||
|
||||
| Layer | Tecnologia | Versione | Motivazione |
|
||||
|-------|------------|----------|-------------|
|
||||
| Framework UI | Ionic | 7+ | Cross-platform, componenti nativi |
|
||||
| Framework App | Angular | 17+ | Standalone components, signals |
|
||||
| Mobile Runtime | Capacitor | 5+ | Build nativo Android/iOS |
|
||||
| Database | Firebase Firestore | 10+ | Free tier, offline, real-time |
|
||||
| Auth | Firebase Auth | 10+ | Anonymous + Google Sign-In |
|
||||
| AI Proxy | Firebase Cloud Functions | Gen2 | Free tier, sicurezza API key |
|
||||
| AI Model | Claude Haiku 4.5 | claude-haiku-4-5-20251001 | Economico, veloce, sufficiente per stime |
|
||||
| Chart | Chart.js + ng2-charts | 4+ | Leggero (~200KB), well-maintained |
|
||||
| Storage locale | @ionic/storage-angular | 4+ | Preferenze utente (obiettivo kcal) |
|
||||
| Linguaggio | TypeScript | 5.3+ | Type safety |
|
||||
|
||||
---
|
||||
|
||||
## 3. Requisiti Ambiente di Sviluppo
|
||||
|
||||
| Requisito | Versione Minima | Note |
|
||||
|-----------|-----------------|------|
|
||||
| Node.js | 18+ | LTS raccomandato |
|
||||
| npm | 9+ | Incluso con Node.js |
|
||||
| Ionic CLI | 7+ | `npm i -g @ionic/cli` |
|
||||
| Angular CLI | 17+ | Incluso come dipendenza |
|
||||
| Java JDK | 17+ | Per build Android (Gradle) |
|
||||
| Android SDK | API 33+ | Per build APK |
|
||||
|
||||
---
|
||||
|
||||
## 4. Configurazione Variabili d'Ambiente
|
||||
|
||||
### `environment.ts` (development)
|
||||
```typescript
|
||||
export const environment = {
|
||||
production: false,
|
||||
firebase: {
|
||||
apiKey: '...',
|
||||
authDomain: '...',
|
||||
projectId: '...',
|
||||
storageBucket: '...',
|
||||
messagingSenderId: '...',
|
||||
appId: '...'
|
||||
},
|
||||
cloudFunctionUrl: 'http://localhost:5001/PROJECT_ID/us-central1/estimateCalories',
|
||||
defaultCalorieTarget: 2000
|
||||
};
|
||||
```
|
||||
|
||||
### `environment.prod.ts` (production)
|
||||
```typescript
|
||||
export const environment = {
|
||||
production: true,
|
||||
firebase: { /* config produzione */ },
|
||||
cloudFunctionUrl: 'https://us-central1-PROJECT_ID.cloudfunctions.net/estimateCalories',
|
||||
defaultCalorieTarget: 2000
|
||||
};
|
||||
```
|
||||
|
||||
**IMPORTANTE**: I file `environment.*.ts` sono nel `.gitignore`. Usare `environment.example.ts` come template.
|
||||
|
||||
---
|
||||
|
||||
## 5. Firebase Cloud Function — Proxy AI
|
||||
|
||||
### Architettura
|
||||
```
|
||||
Client → Cloud Function → Anthropic API → Cloud Function → Client
|
||||
```
|
||||
|
||||
### Endpoint
|
||||
- **URL**: `POST /estimateCalories`
|
||||
- **Body**: `{ description: string }`
|
||||
- **Response**: `{ calories: number, confidence: 'high'|'medium'|'low', explanation: string }`
|
||||
- **Auth**: Firebase Auth token required (header `Authorization: Bearer <token>`)
|
||||
|
||||
### Implementazione Cloud Function
|
||||
```typescript
|
||||
// functions/src/index.ts
|
||||
import { onRequest } from 'firebase-functions/v2/https';
|
||||
import Anthropic from '@anthropic-ai/sdk';
|
||||
|
||||
export const estimateCalories = onRequest(
|
||||
{ cors: true, region: 'europe-west1' },
|
||||
async (req, res) => {
|
||||
// 1. Verificare Firebase Auth token
|
||||
// 2. Estrarre description dal body
|
||||
// 3. Chiamare Claude con food dictionary nel system prompt
|
||||
// 4. Parsare risposta JSON
|
||||
// 5. Restituire risultato
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
### Sicurezza
|
||||
- API key Anthropic in Secret Manager di Firebase
|
||||
- Rate limiting: max 10 richieste/minuto per utente
|
||||
- Validazione input: description max 500 caratteri
|
||||
- Timeout: 10 secondi
|
||||
|
||||
---
|
||||
|
||||
## 6. Schema Dati Firestore
|
||||
|
||||
### Collection `users/{userId}/meals/{mealId}`
|
||||
|
||||
| Campo | Tipo | Obbligatorio | Descrizione |
|
||||
|-------|------|-------------|-------------|
|
||||
| id | string | Sì | Auto-generated document ID |
|
||||
| timestamp | Timestamp | Sì | Data/ora del pasto |
|
||||
| calories | number | Sì | Kcal totali |
|
||||
| inputType | string | Sì | `'manual'` o `'llm'` |
|
||||
| description | string | No | Descrizione testuale |
|
||||
| llmConfidence | string | No | `'high'`, `'medium'`, `'low'` (solo se llm) |
|
||||
| llmExplanation | string | No | Spiegazione AI (solo se llm) |
|
||||
| createdAt | Timestamp | Sì | Data creazione documento |
|
||||
|
||||
### Indici Richiesti
|
||||
```
|
||||
Collection: users/{userId}/meals
|
||||
- (timestamp DESC) — per query giornaliere e range
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Servizi Angular Core
|
||||
|
||||
### `AuthService`
|
||||
```typescript
|
||||
- signInAnonymously(): Promise<User>
|
||||
- getCurrentUser(): Observable<User | null>
|
||||
- getUserId(): string
|
||||
```
|
||||
|
||||
### `MealService`
|
||||
```typescript
|
||||
- addMeal(data: MealInput): Promise<string>
|
||||
- getMealsForDate(date: Date): Observable<Meal[]>
|
||||
- getMealsInRange(from: Date, to: Date): Observable<Meal[]>
|
||||
- deleteMeal(mealId: string): Promise<void>
|
||||
- getTotalCaloriesForDate(date: Date): Observable<number>
|
||||
```
|
||||
|
||||
### `CalorieEstimatorService`
|
||||
```typescript
|
||||
- estimateCalories(description: string): Observable<CalorieEstimate>
|
||||
```
|
||||
|
||||
### `StorageService`
|
||||
```typescript
|
||||
- getCalorieTarget(): Promise<number>
|
||||
- setCalorieTarget(target: number): Promise<void>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Interfacce TypeScript
|
||||
|
||||
```typescript
|
||||
interface Meal {
|
||||
id: string;
|
||||
timestamp: Date;
|
||||
calories: number;
|
||||
inputType: 'manual' | 'llm';
|
||||
description?: string;
|
||||
llmConfidence?: 'high' | 'medium' | 'low';
|
||||
llmExplanation?: string;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
interface MealInput {
|
||||
calories: number;
|
||||
inputType: 'manual' | 'llm';
|
||||
description?: string;
|
||||
llmConfidence?: 'high' | 'medium' | 'low';
|
||||
llmExplanation?: string;
|
||||
}
|
||||
|
||||
interface CalorieEstimate {
|
||||
calories: number;
|
||||
confidence: 'high' | 'medium' | 'low';
|
||||
explanation: string;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Design System — Mapping Tecnico
|
||||
|
||||
### Colori (CSS Custom Properties)
|
||||
```css
|
||||
:root {
|
||||
--pv-primary: #006b1b;
|
||||
--pv-primary-dim: #005d16;
|
||||
--pv-accent: #FF7043;
|
||||
--pv-error: #F44336;
|
||||
--pv-surface: #f6f6f6;
|
||||
--pv-surface-container-low: #f0f1f1;
|
||||
--pv-surface-container-lowest: #ffffff;
|
||||
--pv-on-surface: #2d2f2f;
|
||||
--pv-on-surface-variant: #757575;
|
||||
}
|
||||
```
|
||||
|
||||
### Font (da importare)
|
||||
```css
|
||||
@import url('https://fonts.googleapis.com/css2?family=Lexend:wght@400;700&family=Plus+Jakarta+Sans:wght@400;500;600&display=swap');
|
||||
|
||||
:root {
|
||||
--pv-font-data: 'Lexend', sans-serif;
|
||||
--pv-font-ui: 'Plus Jakarta Sans', sans-serif;
|
||||
}
|
||||
```
|
||||
|
||||
### Regole Chiave
|
||||
1. **No borders** — separazione solo via background shifts
|
||||
2. **No drop shadows su card** — usare tonal layering
|
||||
3. **Border radius**: `1.5rem` (card principali), `1rem` (elementi nested)
|
||||
4. **Spacing**: `24px` vertical padding nelle card
|
||||
5. **Progress bar**: `12px` altezza, full roundedness, gradient `primary-fixed → primary`
|
||||
6. **FAB**: gradient `primary → primary-dim` a 135°, shadow `0px 12px 32px rgba(0,107,27,0.15)`
|
||||
|
||||
---
|
||||
|
||||
## 10. Performance e Limiti
|
||||
|
||||
### Firestore (Spark Plan — Gratuito)
|
||||
- 50.000 letture/giorno
|
||||
- 20.000 scritture/giorno
|
||||
- 1 GiB storage
|
||||
- 10 GiB trasferimento/mese
|
||||
|
||||
### Claude API (Haiku)
|
||||
- ~$0.25 per 1M input tokens
|
||||
- ~$1.25 per 1M output tokens
|
||||
- Stima: ~$0.001 per richiesta singola
|
||||
- Budget suggerito: $5/mese per uso moderato
|
||||
|
||||
### Ottimizzazioni
|
||||
- Cache locale food dictionary (caricato una volta)
|
||||
- Observable Firestore per real-time (no polling)
|
||||
- Lazy loading moduli Angular
|
||||
- Offline persistence Firestore per uso senza rete
|
||||
|
||||
---
|
||||
|
||||
## 11. Sicurezza
|
||||
|
||||
| Rischio | Mitigazione |
|
||||
|---------|-------------|
|
||||
| API key Anthropic esposta | Firebase Cloud Function come proxy |
|
||||
| Accesso dati cross-user | Firestore Security Rules basate su userId |
|
||||
| Input injection nell'LLM | Sanitizzazione input + max 500 caratteri |
|
||||
| Firebase config nel bundle | Le Firebase config sono safe da esporre (solo project identifier) |
|
||||
| Rate abuse | Rate limiting nella Cloud Function (10 req/min/user) |
|
||||
99
docs/TRACKING.md
Normal file
99
docs/TRACKING.md
Normal file
@@ -0,0 +1,99 @@
|
||||
# Tracciamento Requisiti — CalorieTracker
|
||||
|
||||
> Questo documento tiene traccia dello stato di avanzamento di ogni requisito sessione per sessione.
|
||||
> Aggiornare questo file alla fine di ogni sessione di lavoro.
|
||||
|
||||
---
|
||||
|
||||
## Stato Requisiti
|
||||
|
||||
| ID | Titolo | Stato | Sessione | Note |
|
||||
|----|--------|-------|----------|------|
|
||||
| REQ-001 | Scaffolding Ionic + Capacitor | ⬜ DA FARE | — | Prerequisito bloccante |
|
||||
| REQ-002 | Setup Firebase Firestore | ⬜ DA FARE | — | Richiede REQ-001 |
|
||||
| REQ-003 | Home screen + FAB | ⬜ DA FARE | — | Richiede REQ-002 |
|
||||
| REQ-004 | Modal input numerico | ⬜ DA FARE | — | Richiede REQ-003 |
|
||||
| REQ-005 | Modal input LLM | ⬜ DA FARE | — | Richiede REQ-003 + REQ-006 |
|
||||
| REQ-006 | Servizio Claude API | ⬜ DA FARE | — | Richiede REQ-001 |
|
||||
| REQ-007 | CRUD pasti Firestore | ⬜ DA FARE | — | Richiede REQ-002 |
|
||||
| REQ-008 | Statistiche giornaliere | ⬜ DA FARE | — | Richiede REQ-007 |
|
||||
| REQ-009 | Statistiche settimanali | ⬜ DA FARE | — | Richiede REQ-007 |
|
||||
| REQ-010 | Ricerca pasti | ⬜ DA FARE | — | Richiede REQ-007 |
|
||||
| REQ-011 | Design brief Stitch | ✅ COMPLETATO | S0 | Mockup disponibili |
|
||||
|
||||
### Legenda Stati
|
||||
- ⬜ DA FARE — Non ancora iniziato
|
||||
- 🔄 IN CORSO — Lavoro iniziato
|
||||
- ⏸️ IN PAUSA — Iniziato ma bloccato/sospeso
|
||||
- ✅ COMPLETATO — Tutti i criteri accettazione soddisfatti
|
||||
- ❌ BLOCCATO — Bloccato da dipendenza o problema
|
||||
|
||||
---
|
||||
|
||||
## Ordine di Implementazione Raccomandato
|
||||
|
||||
```
|
||||
Fase 1 (Foundation):
|
||||
REQ-001 → REQ-002 → REQ-007
|
||||
|
||||
Fase 2 (Core Features):
|
||||
REQ-003 → REQ-004 (parallelo con REQ-006)
|
||||
REQ-006 → REQ-005
|
||||
|
||||
Fase 3 (Analytics & Search):
|
||||
REQ-008 → REQ-009
|
||||
REQ-010
|
||||
|
||||
Fase 4 (Polish):
|
||||
REQ-011 (già completato — usare come riferimento)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Log Sessioni
|
||||
|
||||
### Sessione 0 — 16/04/2026
|
||||
**Obiettivo**: Setup iniziale progetto
|
||||
**Completato**:
|
||||
- Inizializzazione repository Git
|
||||
- Configurazione remote Gitea (git.i-a.run)
|
||||
- Creazione CLAUDE.md
|
||||
- Creazione documentazione progetto (REQUIREMENTS.md, TRACKING.md, TECHNICAL.md, FIRESTORE_SETUP.md, DEVICE_TESTING.md)
|
||||
- Analisi mockup design Stitch (REQ-011 ✅)
|
||||
|
||||
**Prossima sessione**: Iniziare REQ-001 (scaffolding Ionic)
|
||||
|
||||
---
|
||||
|
||||
## Decisioni Prese
|
||||
|
||||
| Data | Decisione | Motivazione |
|
||||
|------|-----------|-------------|
|
||||
| 16/04/2026 | Proxy API: Firebase Cloud Function | API key non esposta nel bundle mobile |
|
||||
| 16/04/2026 | Cancellazione pasti: hard delete | Semplicità, niente complessità soft delete |
|
||||
| 16/04/2026 | Obiettivo calorico: @ionic/storage locale | Non serve persistenza cloud per preferenza utente |
|
||||
| 16/04/2026 | Ricerca: client-side 30 giorni | Firestore non supporta full-text, sufficiente per uso tipico |
|
||||
| 16/04/2026 | Nome UI: "Precision Vitality" | Da design brief Stitch approvato |
|
||||
| 16/04/2026 | 4 tab: Journal, Stats, Plans, Profile | Da design (diverge dai 3 tab originali dei requisiti) |
|
||||
|
||||
---
|
||||
|
||||
## Problemi Aperti
|
||||
|
||||
| # | Problema | Impatto | Stato |
|
||||
|---|----------|---------|-------|
|
||||
| 1 | Deadline business non definita | Pianificazione | Aperto |
|
||||
| 2 | Target platform prioritario (Android vs iOS) | Build priority | Aperto |
|
||||
| 3 | Strategia Auth lungo termine | REQ-002 | Aperto — per ora Anonymous |
|
||||
| 4 | Logo app | REQ-011 | Aperto |
|
||||
| 5 | Dark mode in scope? | Design | Aperto |
|
||||
|
||||
---
|
||||
|
||||
## Come Usare Questo Documento
|
||||
|
||||
1. **Inizio sessione**: leggere lo stato attuale dei requisiti e il log dell'ultima sessione
|
||||
2. **Durante la sessione**: aggiornare lo stato dei requisiti man mano
|
||||
3. **Fine sessione**: aggiungere entry nel log sessioni con obiettivo, completato e prossimi passi
|
||||
4. **Decisioni**: registrare ogni decisione architetturale con motivazione
|
||||
5. **Problemi**: aggiungere problemi aperti che bloccano o impattano il lavoro
|
||||
Reference in New Issue
Block a user