Calsy
Fotografi il piatto e sai calorie e proteine
Incasso mensile: 612 USD (fonte indicata nella scheda)
Cosa fa
Si scatta una foto del pasto e l'AI stima calorie, proteine, carboidrati e grassi, e dice se è una buona scelta.
Come guadagna
Ha entrate ricorrenti ogni mese, verificate su TrustMRR. Prezzi e piani esatti sono indicati sul sito del progetto.
Rendilo tuo
- Per la cucina italiana: riconosce pasta, pizza, piatti regionali e porzioni tipiche.
- Niente promesse mediche: è una stima.
Le frasi da scrivere all'AI
1. Base del sito e grafica
Crea un'app web in italiano chiamata "Foto Piatto": stima calorie, proteine, carboidrati e grassi di un pasto da una foto, e tiene un diario alimentare semplice con obiettivi indicativi. Usa React, Vite, TypeScript e Tailwind CSS.
Pagine da creare:
1. Pagina iniziale con un esempio di analisi e prezzi
2. Registrazione con età, obiettivo (mantenere, perdere, aumentare) e conferma della maggiore età
3. Scatta o carica la foto del pasto
4. Risultato con alimenti riconosciuti, porzioni modificabili e valori
5. Diario del giorno e della settimana
Stile: fresco e motivante: sfondo bianco, verde #22C55E, arancione #F97316, blu #3B82F6 per proteine/carboidrati/grassi, carattere "Nunito", anelli di avanzamento colorati. Nessuna immagine generica di stock.
Tutto deve funzionare perfettamente da telefono. Tutti i testi in italiano, chiari e senza parole tecniche.
2. Database, account e sicurezza
Collega il progetto a Supabase e attiva la registrazione con email e password.
Crea queste tabelle:
- "pasti": foto, alimenti riconosciuti con quantità e valori in JSON, totali, data, collegati all'utente
- "obiettivi": calorie e macronutrienti giornalieri indicativi, uno per utente
Regole di sicurezza obbligatorie:
- ogni utente vede solo i suoi pasti e obiettivi
- le foto stanno in uno spazio privato
- il piano non si modifica dal browser
Usa esattamente il codice SQL che trovi nei "Dettagli tecnici" di questa scheda: eseguilo nel SQL Editor di Supabase prima di continuare, poi collega le pagine alle tabelle.
3. La stima dalla foto
L'utente scatta la foto del piatto. La Edge Function "analizza-piatto" (codice nei Dettagli tecnici) la mostra a Claude, che riconosce gli alimenti e stima per ognuno la quantità in grammi e i valori, restituendo JSON.
Il risultato mostra ogni alimento con la quantità modificabile: cambiando i grammi, i valori si ricalcolano in proporzione. Se l'AI non è sicura ("salsa non riconoscibile"), lo dice e chiede all'utente.
Avviso sempre visibile: "Stime indicative dalla foto. Per esigenze di salute rivolgiti a un medico o a un nutrizionista". Nessun obiettivo sotto le 1.200 calorie e nessun obiettivo per i minorenni.
4. Diario e andamento
Diario del giorno con gli anelli di calorie, proteine, carboidrati e grassi rispetto agli obiettivi indicativi, e l'elenco dei pasti con la foto. Vista settimanale con la media giornaliera.
Pasti preferiti da aggiungere in un tocco senza rifare la foto, e inserimento manuale. Esportazione CSV.
5. Pagamenti con Stripe
Aggiungi un abbonamento a 4,99 € al mese. Gratis l'utente ha: 3 foto al giorno. Con il piano Pro sblocca: foto illimitate, statistiche settimanali, pasti preferiti ed esportazione.
A pagamento riuscito il webhook porta l'utente al piano Pro; se l'abbonamento viene disdetto o non rinnovato lo riporta al piano gratuito.
Per la sessione di pagamento e il webhook segui la guida "Pagamenti con Stripe" di Fattibile.
6. Controlli specifici di questo progetto
Oltre ai controlli della guida "Controllo finale", verifica in particolare:
1. Salute: verifica che non si possa impostare un obiettivo sotto 1.200 calorie e che senza conferma della maggiore età non si possano usare gli obiettivi.
Correggi quello che non va e scrivimi cosa hai cambiato.
Dettagli tecnici
Riconoscere il piatto con Claude
Mostra la foto a Claude e riceve gli alimenti con quantità e valori stimati, in JSON.
import Anthropic from "npm:@anthropic-ai/sdk";
import { encodeBase64 } from "jsr:@std/encoding/base64";
const claude = new Anthropic({ apiKey: Deno.env.get("ANTHROPIC_API_KEY")! });
export type Alimento = { nome: string; grammi: number; kcal: number; proteine: number; carboidrati: number; grassi: number; sicuro: boolean };
export async function analizzaPiatto(foto: Uint8Array, tipo: "image/jpeg" | "image/png"): Promise<Alimento[] | null> {
const risposta = await claude.beta.messages.create({
model: "claude-opus-5",
max_tokens: 16000,
betas: ["server-side-fallback-2026-07-01"],
fallbacks: "default",
system: "Sei un nutrizionista. Riconosci gli alimenti nella foto, stima i grammi di ciascuno guardando " +
"le proporzioni del piatto, e calcola calorie e macronutrienti con valori medi di riferimento. " +
"Se non riconosci bene un alimento metti sicuro: false. Rispondi solo con JSON: " +
"{\"alimenti\": [{\"nome\", \"grammi\", \"kcal\", \"proteine\", \"carboidrati\", \"grassi\", \"sicuro\"}]}",
messages: [{
role: "user",
content: [
{ type: "image", source: { type: "base64", media_type: tipo, data: encodeBase64(foto) } },
{ type: "text", text: "Analizza questo pasto." },
],
}],
});
if (risposta.stop_reason === "refusal") return null;
const testo = risposta.content.filter((b) => b.type === "text").map((b) => b.text).join("");
return JSON.parse(testo).alimenti;
}
// Nel browser: cambiando i grammi, i valori si ricalcolano in proporzione
export const conGrammi = (a: Alimento, grammi: number): Alimento => {
const f = grammi / a.grammi;
return { ...a, grammi, kcal: Math.round(a.kcal * f), proteine: +(a.proteine * f).toFixed(1),
carboidrati: +(a.carboidrati * f).toFixed(1), grassi: +(a.grassi * f).toFixed(1) };
};Pasti e totali del giorno
Il diario dei pasti con le foto private e la query dei totali di oggi.
create table pasti (
id uuid primary key default gen_random_uuid(),
utente_id uuid not null references auth.users(id) on delete cascade,
foto_path text,
alimenti jsonb not null,
kcal int not null,
proteine numeric(6, 1) not null,
carboidrati numeric(6, 1) not null,
grassi numeric(6, 1) not null,
mangiato_il timestamptz not null default now()
);
alter table pasti enable row level security;
create policy "le mie righe" on pasti
for all using (auth.uid() = utente_id) with check (auth.uid() = utente_id);
insert into storage.buckets (id, name, public) values ('piatti', 'piatti', false);
create policy "la mia cartella piatti" on storage.objects for all to authenticated
using (bucket_id = 'piatti' and (storage.foldername(name))[1] = auth.uid()::text)
with check (bucket_id = 'piatti' and (storage.foldername(name))[1] = auth.uid()::text);
select sum(kcal) as kcal, sum(proteine) as proteine,
sum(carboidrati) as carboidrati, sum(grassi) as grassi
from pasti
where utente_id = auth.uid() and mangiato_il >= date_trunc('day', now());
Pezzi comuni a tutti i progetti
Glossario
- Storage
- Lo spazio di Supabase dove si salvano i file, come foto e audio, con le sue regole di accesso.
- JSONB
- Un tipo di colonna che contiene dati strutturati flessibili, come un piccolo documento.
- CSV
- Un file di testo con i dati separati da virgole, che si apre con Excel.