← Tutte le idee Marketing e social

Apogee

Carica centinaia di pubblicità su Meta in pochi minuti

Incasso mensile: 4200 USD (fonte indicata nella scheda)

Cosa fa

Strumento per chi gestisce pubblicità su Meta: caricare in blocco centinaia di immagini e video, un lavoro che con gli strumenti di Meta è lentissimo.

Come guadagna

Ha entrate ricorrenti ogni mese, verificate su TrustMRR. Prezzi e piani esatti sono indicati sul sito del progetto.

Rendilo tuo

  • Per agenzie italiane con tanti clienti piccoli.
  • Nomi delle campagne generati in automatico con una regola.

Le frasi da scrivere all'AI

1. Base del sito e grafica

Crea un'app web in italiano chiamata "Carica Tutto": permette a chi gestisce pubblicità su Facebook e Instagram di caricare in blocco centinaia di immagini e video come annunci, con nomi e testi ordinati, in pochi minuti. Usa React, Vite, TypeScript e Tailwind CSS.

Pagine da creare:
1. Pagina iniziale con il confronto "3 ore a mano / 5 minuti" e prezzi
2. Registrazione e collegamento degli account pubblicitari Meta
3. Nuovo caricamento: trascina i file, scegli campagna e gruppo, imposta i testi
4. Avanzamento del caricamento in tempo reale
5. Cronologia dei caricamenti

Stile: efficiente e tecnico: sfondo grigio chiaro #F1F5F9, blu #2563EB, verde #16A34A per i completati, rosso #DC2626 per gli errori, carattere "Inter", tabelle compatte. 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:
- "caricamenti": account, campagna, gruppo di annunci, impostazioni comuni (testo, titolo, link, invito all'azione), collegati all'utente
- "coda": un elemento per ogni file da caricare, con stato (in attesa, in corso, fatto, errore), tentativi ed eventuale messaggio di errore

Regole di sicurezza obbligatorie:
- ogni utente vede solo i suoi caricamenti e la sua coda
- i token di Meta stanno solo lato server
- la coda la lavora solo la Edge Function

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. Il caricamento in coda

Collega uno o più account pubblicitari con il login di Facebook e le API ufficiali di Meta (Marketing API).

Nel nuovo caricamento l'utente trascina fino a 500 file, sceglie campagna e gruppo di annunci, e imposta testi comuni con segnaposto ({nome_file}, {numero}). Una tabella mostra ogni annuncio che verrà creato, modificabile riga per riga.

I file vanno nello Storage e ognuno diventa un elemento della "coda". Una Edge Function, chiamata ogni minuto, prende 20 elementi alla volta (codice nei Dettagli tecnici), li carica su Meta e crea gli annunci in pausa. Rispetta i limiti di Meta: se Meta risponde "troppe richieste", aspetta e riprova; dopo 3 errori segna l'elemento come "errore" con il motivo.

4. Avanzamento e cronologia

Pagina di avanzamento aggiornata in tempo reale: barra con fatti/totale, tabella con lo stato di ogni file, e per gli errori il motivo in italiano ("Immagine troppo piccola: minimo 600 pixel") con il pulsante "Riprova".

Cronologia dei caricamenti con data, numero di annunci, errori e link per aprire la campagna in Meta.

5. Pagamenti con Stripe

Aggiungi un abbonamento a 29 € al mese. Gratis l'utente ha: 50 annunci al mese e un solo account. Con il piano Pro sblocca: annunci illimitati, più account pubblicitari e modelli di testo salvati.

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. Coda: chiudi il browser durante un caricamento di 100 file e riaprilo: il caricamento deve essere andato avanti da solo e nessun file deve essere caricato due volte.
Correggi quello che non va e scrivimi cosa hai cambiato.

Dettagli tecnici

La coda di lavoro

Ogni file è un lavoro in coda. La funzione prende un gruppo di lavori in modo sicuro: anche se due esecuzioni partono insieme, nessun file viene preso due volte (grazie a "skip locked").

create table caricamenti (
  id uuid primary key default gen_random_uuid(),
  utente_id uuid not null references auth.users(id) on delete cascade,
  account_meta text not null,
  gruppo_annunci text not null,
  impostazioni jsonb not null,
  creato_il timestamptz not null default now()
);

create table coda (
  id bigint generated always as identity primary key,
  caricamento_id uuid not null references caricamenti(id) on delete cascade,
  utente_id uuid not null references auth.users(id) on delete cascade,
  file_path text not null,
  stato text not null default 'in_attesa'
    check (stato in ('in_attesa', 'in_corso', 'fatto', 'errore')),
  tentativi int not null default 0,
  errore text,
  id_annuncio_meta text,
  aggiornato_il timestamptz not null default now()
);

alter table caricamenti enable row level security;
create policy "i miei caricamenti" on caricamenti
  for all using (auth.uid() = utente_id) with check (auth.uid() = utente_id);
alter table coda enable row level security;
create policy "leggo la mia coda" on coda for select using (auth.uid() = utente_id);

-- Prende fino a "quanti" lavori e li segna in corso, senza doppioni
create function prendi_lavori(quanti int) returns setof coda
language sql security definer set search_path = public as $
  update coda set stato = 'in_corso', tentativi = tentativi + 1, aggiornato_il = now()
  where id in (
    select id from coda
    where stato = 'in_attesa'
       or (stato = 'in_corso' and aggiornato_il < now() - interval '10 minutes')  -- bloccati
    order by id
    limit quanti
    for update skip locked
  )
  returning *;
$;
revoke execute on function prendi_lavori(int) from public, anon, authenticated;
grant execute on function prendi_lavori(int) to service_role;

Riprovare quando Meta dice "troppe richieste"

Se l'API risponde con un limite di velocità, aspetta sempre di più prima di riprovare (1, 2, 4 secondi...).

export async function conRiprova<T>(azione: () => Promise<Response>, tentativi = 4): Promise<T> {
  for (let i = 0; i < tentativi; i++) {
    const r = await azione();
    if (r.ok) return r.json();
    const errore = await r.json().catch(() => ({}));
    const limite = r.status === 429 || [4, 17, 32, 613].includes(errore?.error?.code);
    if (!limite || i === tentativi - 1) {
      throw new Error(errore?.error?.message ?? "Errore " + r.status);
    }
    await new Promise((ok) => setTimeout(ok, 1000 * 2 ** i));   // 1s, 2s, 4s...
  }
  throw new Error("Troppi tentativi");
}

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.
Cron
Un'operazione programmata che parte da sola a orari fissi, per esempio ogni lunedì mattina.
JSONB
Un tipo di colonna che contiene dati strutturati flessibili, come un piccolo documento.
Coda di lavoro
Un elenco di compiti da fare uno dopo l'altro sul server, che continua anche se l'utente chiude il browser.