From 696a95950c2d539fc3898a9a5cdfb48a326ddc41 Mon Sep 17 00:00:00 2001 From: Simone Cavalli Date: Thu, 18 Jun 2026 21:46:17 +0200 Subject: [PATCH] chore: add Notion service import scripts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit scripts/reset-and-import-services.ts — deletes 3 legacy test services, imports 55 services from Notion "DB Offerta: Attività SC" CSV export (Signature Offer / Entry Offer / Retainer Offer, with fase metadata). scripts/import-services-notion.ts — standalone idempotent version. Run via SSH tunnel with DATABASE_URL pointed at 127.0.0.1:54321. Co-Authored-By: Claude Sonnet 4.6 --- scripts/import-services-notion.ts | 141 +++++++++++++++++++++++++++ scripts/reset-and-import-services.ts | 134 +++++++++++++++++++++++++ 2 files changed, 275 insertions(+) create mode 100644 scripts/import-services-notion.ts create mode 100644 scripts/reset-and-import-services.ts diff --git a/scripts/import-services-notion.ts b/scripts/import-services-notion.ts new file mode 100644 index 0000000..8fbbccc --- /dev/null +++ b/scripts/import-services-notion.ts @@ -0,0 +1,141 @@ +// Import 55 services from Notion "DB Offerta: Attività SC" CSV export. +// Idempotent: skips rows where a service with the same name already exists. +// Run via SSH tunnel (same pattern as push-12-offer-tier-schema.ts): +// DATABASE_URL=$(node --env-file=.env.local -e 'const u=new URL(process.env.DATABASE_URL); u.host="127.0.0.1:54321"; process.stdout.write(u.toString())') \ +// npx tsx scripts/import-services-notion.ts +import postgres from "postgres"; +import { customAlphabet } from "nanoid"; + +const nanoid = customAlphabet("0123456789abcdefghijklmnopqrstuvwxyz", 12); + +type ServiceRow = { + name: string; + unit_price: number; + category: string; + fase: string | null; +}; + +// Source: offerta_servizi.csv exported from Notion. +// category = primary Offerta value (first when multi-value). +// fase = null for Retainer Offer rows (no Fase column value in source). +const SERVICES: ServiceRow[] = [ + // ── Shared: Signature Offer + Entry Offer ────────────────────────────────── + { name: "Raccolta e Mappatura Materiali", unit_price: 100, category: "Signature Offer", fase: "Fase 1 → Onboarding / Setup" }, + { name: "Audit iniziale (UX/UI, struttura, conversione)", unit_price: 500, category: "Signature Offer", fase: "Fase 1 → Onboarding / Setup" }, + { name: "Workshop (1° fase)", unit_price: 900, category: "Signature Offer", fase: "Fase 1 → Onboarding / Setup" }, + { name: "Analisi Competitor", unit_price: 400, category: "Signature Offer", fase: "Fase 2 → Analisi / Strategia" }, + // ── Entry Offer ──────────────────────────────────────────────────────────── + { name: "Documento di restituzione (problemi + lista ottimizzazioni)", unit_price: 500, category: "Entry Offer", fase: "Fase 3 → Esecuzione / Core" }, + { name: "Redesign visivo dell'above-the-fold / hero (il 'prima → dopo')", unit_price: 700, category: "Entry Offer", fase: "Fase 3 → Esecuzione / Core" }, + { name: "Call di restituzione", unit_price: 200, category: "Entry Offer", fase: "Fase 5 → Offboarding / Consegna" }, + { name: "Call di presentazione Prima/Dopo", unit_price: 200, category: "Entry Offer", fase: "Fase 5 → Offboarding / Consegna" }, + { name: "Roadmap / istruzioni operative + mini kit", unit_price: 600, category: "Entry Offer", fase: "Fase 5 → Offboarding / Consegna" }, + // ── Signature Offer — Fase 2 ─────────────────────────────────────────────── + { name: "UX Research", unit_price: 1200, category: "Signature Offer", fase: "Fase 2 → Analisi / Strategia" }, + { name: "Customer Journey", unit_price: 900, category: "Signature Offer", fase: "Fase 2 → Analisi / Strategia" }, + { name: "Architettura (sitemap)", unit_price: 500, category: "Signature Offer", fase: "Fase 2 → Analisi / Strategia" }, + { name: "Brand Identity - Visual identity", unit_price: 2000, category: "Signature Offer", fase: "Fase 2 → Analisi / Strategia" }, + { name: "Brand Identity - Voice identity", unit_price: 800, category: "Signature Offer", fase: "Fase 2 → Analisi / Strategia" }, + { name: "Direzione Creative (moodboard)", unit_price: 600, category: "Signature Offer", fase: "Fase 2 → Analisi / Strategia" }, + { name: "Workshop (2° fase)", unit_price: 900, category: "Signature Offer", fase: "Fase 2 → Analisi / Strategia" }, + // ── Signature Offer — Fase 3 ─────────────────────────────────────────────── + { name: "Settaggio CMS (Wordpress Webflow ecc)", unit_price: 500, category: "Signature Offer", fase: "Fase 3 → Esecuzione / Core" }, + { name: "UX - UI", unit_price: 1000, category: "Signature Offer", fase: "Fase 3 → Esecuzione / Core" }, + { name: "Wireframe (Low-Mid Fidelity)", unit_price: 600, category: "Signature Offer", fase: "Fase 3 → Esecuzione / Core" }, + { name: "Homepage (Figma + Dev)", unit_price: 1500, category: "Signature Offer", fase: "Fase 3 → Esecuzione / Core" }, + { name: "Revisione #1", unit_price: 400, category: "Signature Offer", fase: "Fase 3 → Esecuzione / Core" }, + { name: "- Chi siamo", unit_price: 600, category: "Signature Offer", fase: "Fase 3 → Esecuzione / Core" }, + { name: "- Contatti", unit_price: 400, category: "Signature Offer", fase: "Fase 3 → Esecuzione / Core" }, + { name: "- Blog (Archivio)", unit_price: 300, category: "Signature Offer", fase: "Fase 3 → Esecuzione / Core" }, + { name: "- - Blog post (Template Singolo)", unit_price: 600, category: "Signature Offer", fase: "Fase 3 → Esecuzione / Core" }, + { name: "- Case Study (Archivio)", unit_price: 300, category: "Signature Offer", fase: "Fase 3 → Esecuzione / Core" }, + { name: "- - Case Study (Template Singolo)", unit_price: 800, category: "Signature Offer", fase: "Fase 3 → Esecuzione / Core" }, + { name: "- Landing Page (Metodo o Differenziante)", unit_price: 1000, category: "Signature Offer", fase: "Fase 3 → Esecuzione / Core" }, + { name: "- - - Thank You Page", unit_price: 300, category: "Signature Offer", fase: "Fase 3 → Esecuzione / Core" }, + { name: "- - - 404", unit_price: 150, category: "Signature Offer", fase: "Fase 3 → Esecuzione / Core" }, + { name: "Responsive (inclusa nelle pagine?)", unit_price: 800, category: "Signature Offer", fase: "Fase 3 → Esecuzione / Core" }, + { name: "UX writing (Revisione testi)", unit_price: 400, category: "Signature Offer", fase: "Fase 3 → Esecuzione / Core" }, + { name: "Seo Setup (basic on-page)", unit_price: 400, category: "Signature Offer", fase: "Fase 3 → Esecuzione / Core" }, + { name: "Seo Avanzato (+ keyword + ricerca + blog post dentro retainer)", unit_price: 400, category: "Signature Offer", fase: "Fase 3 → Esecuzione / Core" }, + { name: "Revisione #2 finale", unit_price: 400, category: "Signature Offer", fase: "Fase 3 → Esecuzione / Core" }, + // ── Signature Offer — Fase 4 ─────────────────────────────────────────────── + { name: "Web Core Vitals (Optimization)", unit_price: 500, category: "Signature Offer", fase: "Fase 4 → Raffinamento / Extra" }, + { name: "QA - Test Cross Browser e responsive", unit_price: 400, category: "Signature Offer", fase: "Fase 4 → Raffinamento / Extra" }, + { name: "Setting GA4 - Tag Manager - Hotjar/Clarify", unit_price: 500, category: "Signature Offer", fase: "Fase 4 → Raffinamento / Extra" }, + // ── Signature Offer — Fase 5 ─────────────────────────────────────────────── + { name: "Raccolta Feedback Post Lancio", unit_price: 200, category: "Signature Offer", fase: "Fase 5 → Offboarding / Consegna" }, + { name: "Go-live / messa online & accessi", unit_price: 300, category: "Signature Offer", fase: "Fase 5 → Offboarding / Consegna" }, + { name: "Audit uscita", unit_price: 500, category: "Signature Offer", fase: "Fase 5 → Offboarding / Consegna" }, + { name: "Real User Testing", unit_price: 1200, category: "Signature Offer", fase: "Fase 5 → Offboarding / Consegna" }, + { name: "Follow Up Handover", unit_price: 200, category: "Signature Offer", fase: "Fase 5 → Offboarding / Consegna" }, + { name: "Video Tutorial per micro modifiche in autonomia", unit_price: 400, category: "Signature Offer", fase: "Fase 5 → Offboarding / Consegna" }, + { name: "Revisioni Extra illimitate (sicuri illimitati???)", unit_price: 1200, category: "Signature Offer", fase: "Fase 5 → Offboarding / Consegna" }, + { name: "Brand Kit (youtube - linkedin - insta)", unit_price: 600, category: "Signature Offer", fase: "Fase 5 → Offboarding / Consegna" }, + // ── Retainer Offer ───────────────────────────────────────────────────────── + { name: "Mantenimento tecnico (sito sempre online, bello, funzionante)", unit_price: 200, category: "Retainer Offer", fase: null }, + { name: "Monitoraggio dati (GA4 / Hotjar-Clarity)", unit_price: 100, category: "Retainer Offer", fase: null }, + { name: "Report mensile", unit_price: 100, category: "Retainer Offer", fase: null }, + { name: "CRO - analisi UX (hotmap, punti di drop)", unit_price: 200, category: "Retainer Offer", fase: null }, + { name: "CRO - implementazione miglioramenti", unit_price: 300, category: "Retainer Offer", fase: null }, + { name: "Call di Mentorship/Consulenza/allineamento/revisione", unit_price: 800, category: "Retainer Offer", fase: null }, + { name: "Art direction su direzione da prendere (consulente strategico interno disponibile 24/7)", unit_price: 2000, category: "Retainer Offer", fase: null }, + { name: "Extra landing (prezzo singolo per pompare prezzo)", unit_price: 3000, category: "Retainer Offer", fase: null }, + { name: "SEO avanzato (Blog post)", unit_price: 400, category: "Retainer Offer", fase: null }, +]; + +async function run() { + const databaseUrl = process.env.DATABASE_URL; + if (!databaseUrl) { + console.error("DATABASE_URL environment variable is required"); + process.exit(1); + } + + const client = postgres(databaseUrl); + + try { + console.log(`Importing ${SERVICES.length} services from Notion CSV...`); + let inserted = 0; + let skipped = 0; + + for (const svc of SERVICES) { + const existing = await client` + SELECT id FROM services WHERE name = ${svc.name} LIMIT 1 + `; + if (existing.length > 0) { + console.log(` ↷ exists: ${svc.name}`); + skipped++; + continue; + } + + const id = nanoid(); + await client` + INSERT INTO services (id, name, unit_price, category, fase, active, migrated_from) + VALUES ( + ${id}, + ${svc.name}, + ${svc.unit_price}, + ${svc.category}, + ${svc.fase}, + true, + 'notion-csv-2026-06-18' + ) + `; + console.log(` ✓ imported: ${svc.name}`); + inserted++; + } + + console.log(`\n✓ Done — ${inserted} inserted, ${skipped} skipped (already existed)`); + process.exit(0); + } catch (err) { + if (err instanceof Error) { + console.error("Import failed:", err.message); + } else { + console.error("Import failed:", err); + } + process.exit(1); + } finally { + await client.end(); + } +} + +run(); diff --git a/scripts/reset-and-import-services.ts b/scripts/reset-and-import-services.ts new file mode 100644 index 0000000..9aeb2ef --- /dev/null +++ b/scripts/reset-and-import-services.ts @@ -0,0 +1,134 @@ +// One-shot: deletes 3 legacy test services, then imports 55 from Notion CSV. +// offer_tier_services rows cascade-delete automatically (ON DELETE CASCADE). +// Run via SSH tunnel: +// DATABASE_URL=$(node --env-file=.env.local -e 'const u=new URL(process.env.DATABASE_URL); u.host="127.0.0.1:54321"; process.stdout.write(u.toString())') \ +// npx tsx scripts/reset-and-import-services.ts +import postgres from "postgres"; +import { customAlphabet } from "nanoid"; + +const nanoid = customAlphabet("0123456789abcdefghijklmnopqrstuvwxyz", 12); + +type ServiceRow = { + name: string; + unit_price: number; + category: string; + fase: string | null; +}; + +const SERVICES: ServiceRow[] = [ + { name: "Raccolta e Mappatura Materiali", unit_price: 100, category: "Signature Offer", fase: "Fase 1 → Onboarding / Setup" }, + { name: "Audit iniziale (UX/UI, struttura, conversione)", unit_price: 500, category: "Signature Offer", fase: "Fase 1 → Onboarding / Setup" }, + { name: "Workshop (1° fase)", unit_price: 900, category: "Signature Offer", fase: "Fase 1 → Onboarding / Setup" }, + { name: "Analisi Competitor", unit_price: 400, category: "Signature Offer", fase: "Fase 2 → Analisi / Strategia" }, + { name: "Documento di restituzione (problemi + lista ottimizzazioni)", unit_price: 500, category: "Entry Offer", fase: "Fase 3 → Esecuzione / Core" }, + { name: "Redesign visivo dell'above-the-fold / hero (il 'prima → dopo')", unit_price: 700, category: "Entry Offer", fase: "Fase 3 → Esecuzione / Core" }, + { name: "Call di restituzione", unit_price: 200, category: "Entry Offer", fase: "Fase 5 → Offboarding / Consegna" }, + { name: "Call di presentazione Prima/Dopo", unit_price: 200, category: "Entry Offer", fase: "Fase 5 → Offboarding / Consegna" }, + { name: "Roadmap / istruzioni operative + mini kit", unit_price: 600, category: "Entry Offer", fase: "Fase 5 → Offboarding / Consegna" }, + { name: "UX Research", unit_price: 1200, category: "Signature Offer", fase: "Fase 2 → Analisi / Strategia" }, + { name: "Customer Journey", unit_price: 900, category: "Signature Offer", fase: "Fase 2 → Analisi / Strategia" }, + { name: "Architettura (sitemap)", unit_price: 500, category: "Signature Offer", fase: "Fase 2 → Analisi / Strategia" }, + { name: "Brand Identity - Visual identity", unit_price: 2000, category: "Signature Offer", fase: "Fase 2 → Analisi / Strategia" }, + { name: "Brand Identity - Voice identity", unit_price: 800, category: "Signature Offer", fase: "Fase 2 → Analisi / Strategia" }, + { name: "Direzione Creative (moodboard)", unit_price: 600, category: "Signature Offer", fase: "Fase 2 → Analisi / Strategia" }, + { name: "Workshop (2° fase)", unit_price: 900, category: "Signature Offer", fase: "Fase 2 → Analisi / Strategia" }, + { name: "Settaggio CMS (Wordpress Webflow ecc)", unit_price: 500, category: "Signature Offer", fase: "Fase 3 → Esecuzione / Core" }, + { name: "UX - UI", unit_price: 1000, category: "Signature Offer", fase: "Fase 3 → Esecuzione / Core" }, + { name: "Wireframe (Low-Mid Fidelity)", unit_price: 600, category: "Signature Offer", fase: "Fase 3 → Esecuzione / Core" }, + { name: "Homepage (Figma + Dev)", unit_price: 1500, category: "Signature Offer", fase: "Fase 3 → Esecuzione / Core" }, + { name: "Revisione #1", unit_price: 400, category: "Signature Offer", fase: "Fase 3 → Esecuzione / Core" }, + { name: "- Chi siamo", unit_price: 600, category: "Signature Offer", fase: "Fase 3 → Esecuzione / Core" }, + { name: "- Contatti", unit_price: 400, category: "Signature Offer", fase: "Fase 3 → Esecuzione / Core" }, + { name: "- Blog (Archivio)", unit_price: 300, category: "Signature Offer", fase: "Fase 3 → Esecuzione / Core" }, + { name: "- - Blog post (Template Singolo)", unit_price: 600, category: "Signature Offer", fase: "Fase 3 → Esecuzione / Core" }, + { name: "- Case Study (Archivio)", unit_price: 300, category: "Signature Offer", fase: "Fase 3 → Esecuzione / Core" }, + { name: "- - Case Study (Template Singolo)", unit_price: 800, category: "Signature Offer", fase: "Fase 3 → Esecuzione / Core" }, + { name: "- Landing Page (Metodo o Differenziante)", unit_price: 1000, category: "Signature Offer", fase: "Fase 3 → Esecuzione / Core" }, + { name: "- - - Thank You Page", unit_price: 300, category: "Signature Offer", fase: "Fase 3 → Esecuzione / Core" }, + { name: "- - - 404", unit_price: 150, category: "Signature Offer", fase: "Fase 3 → Esecuzione / Core" }, + { name: "Responsive (inclusa nelle pagine?)", unit_price: 800, category: "Signature Offer", fase: "Fase 3 → Esecuzione / Core" }, + { name: "UX writing (Revisione testi)", unit_price: 400, category: "Signature Offer", fase: "Fase 3 → Esecuzione / Core" }, + { name: "Seo Setup (basic on-page)", unit_price: 400, category: "Signature Offer", fase: "Fase 3 → Esecuzione / Core" }, + { name: "Seo Avanzato (+ keyword + ricerca + blog post dentro retainer)", unit_price: 400, category: "Signature Offer", fase: "Fase 3 → Esecuzione / Core" }, + { name: "Revisione #2 finale", unit_price: 400, category: "Signature Offer", fase: "Fase 3 → Esecuzione / Core" }, + { name: "Web Core Vitals (Optimization)", unit_price: 500, category: "Signature Offer", fase: "Fase 4 → Raffinamento / Extra" }, + { name: "QA - Test Cross Browser e responsive", unit_price: 400, category: "Signature Offer", fase: "Fase 4 → Raffinamento / Extra" }, + { name: "Setting GA4 - Tag Manager - Hotjar/Clarify", unit_price: 500, category: "Signature Offer", fase: "Fase 4 → Raffinamento / Extra" }, + { name: "Raccolta Feedback Post Lancio", unit_price: 200, category: "Signature Offer", fase: "Fase 5 → Offboarding / Consegna" }, + { name: "Go-live / messa online & accessi", unit_price: 300, category: "Signature Offer", fase: "Fase 5 → Offboarding / Consegna" }, + { name: "Audit uscita", unit_price: 500, category: "Signature Offer", fase: "Fase 5 → Offboarding / Consegna" }, + { name: "Real User Testing", unit_price: 1200, category: "Signature Offer", fase: "Fase 5 → Offboarding / Consegna" }, + { name: "Follow Up Handover", unit_price: 200, category: "Signature Offer", fase: "Fase 5 → Offboarding / Consegna" }, + { name: "Video Tutorial per micro modifiche in autonomia", unit_price: 400, category: "Signature Offer", fase: "Fase 5 → Offboarding / Consegna" }, + { name: "Revisioni Extra illimitate (sicuri illimitati???)", unit_price: 1200, category: "Signature Offer", fase: "Fase 5 → Offboarding / Consegna" }, + { name: "Brand Kit (youtube - linkedin - insta)", unit_price: 600, category: "Signature Offer", fase: "Fase 5 → Offboarding / Consegna" }, + { name: "Mantenimento tecnico (sito sempre online, bello, funzionante)", unit_price: 200, category: "Retainer Offer", fase: null }, + { name: "Monitoraggio dati (GA4 / Hotjar-Clarity)", unit_price: 100, category: "Retainer Offer", fase: null }, + { name: "Report mensile", unit_price: 100, category: "Retainer Offer", fase: null }, + { name: "CRO - analisi UX (hotmap, punti di drop)", unit_price: 200, category: "Retainer Offer", fase: null }, + { name: "CRO - implementazione miglioramenti", unit_price: 300, category: "Retainer Offer", fase: null }, + { name: "Call di Mentorship/Consulenza/allineamento/revisione", unit_price: 800, category: "Retainer Offer", fase: null }, + { name: "Art direction su direzione da prendere (consulente strategico interno disponibile 24/7)", unit_price: 2000, category: "Retainer Offer", fase: null }, + { name: "Extra landing (prezzo singolo per pompare prezzo)", unit_price: 3000, category: "Retainer Offer", fase: null }, + { name: "SEO avanzato (Blog post)", unit_price: 400, category: "Retainer Offer", fase: null }, +]; + +const LEGACY_IDS = [ + "LLi-DynaQ_Y13vgREFtSB", // Audit Iniziale (test) + "MlhY34V7M_ylSPekeuIsz", // Raccolta e Mappatura Materiali (test, wrong category) + "lDZkctWhcXqsYVDy2nac6", // Analisi Competitor (test, wrong category) +]; + +async function run() { + const databaseUrl = process.env.DATABASE_URL; + if (!databaseUrl) { + console.error("DATABASE_URL environment variable is required"); + process.exit(1); + } + + const client = postgres(databaseUrl); + + try { + // Step 1: delete legacy test services (offer_tier_services cascade-deletes) + console.log("Deleting 3 legacy test services..."); + const deleted = await client` + DELETE FROM services WHERE id = ANY(${LEGACY_IDS}) RETURNING name + `; + for (const row of deleted) console.log(` ✗ deleted: ${row.name}`); + console.log(); + + // Step 2: import 55 Notion services + console.log(`Importing ${SERVICES.length} services...`); + let inserted = 0; + let skipped = 0; + + for (const svc of SERVICES) { + const existing = await client` + SELECT id FROM services WHERE name = ${svc.name} LIMIT 1 + `; + if (existing.length > 0) { + console.log(` ↷ exists: ${svc.name}`); + skipped++; + continue; + } + const id = nanoid(); + await client` + INSERT INTO services (id, name, unit_price, category, fase, active, migrated_from) + VALUES (${id}, ${svc.name}, ${svc.unit_price}, ${svc.category}, ${svc.fase}, true, 'notion-csv-2026-06-18') + `; + console.log(` ✓ ${svc.name}`); + inserted++; + } + + const [{ count }] = await client`SELECT count(*) FROM services`; + console.log(`\n✓ Done — ${inserted} inserted, ${skipped} skipped. Total in DB: ${count}`); + process.exit(0); + } catch (err) { + console.error("Error:", err instanceof Error ? err.message : err); + process.exit(1); + } finally { + await client.end(); + } +} + +run();