← Tutte le idee Sport e benessere

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.