Files
flux-srl/prisma/schema.prisma
T
davidherran b9201a437c feat: hero carousel CMS + responsive mobile/iPad fix + flat-scope assets
Replaces the filesystem-scan hero (fs.readdirSync of /public/footage/main)
with a fully CMS-driven HeroSlide model. Editors can now drag-drop reorder,
toggle slides on/off, set focal points for proper mobile cropping, and
auto-translate per-slide captions.

NEW SCHEMA (additive — does not touch existing tables)
- HeroSlide: mediaUrl, mediaType, altText, order, isActive, focalPointX,
  focalPointY, translationsJson, timestamps
- SiteSetting: key-value JSON store for site-wide config (favicon, logo,
  footer, OG image) — wired up in next commit
- Migration 20260504120000_add_hero_slides_and_site_settings/migration.sql
  uses CREATE TABLE IF NOT EXISTS, additive only

HERO REEL REFACTOR (Bug #4 — responsive mobile/iPad)
- Switches from `images: string[]` to `slides: HeroSlideData[]` while
  keeping a backwards-compat path so legacy callers still work
- w-screen → w-full max-w-[100vw] (no horizontal scroll on iOS)
- h-[100vh] → h-[100svh] so iOS Safari URL bar doesn't push content
- Reduces title font sizes on small viewports (text-3xl → text-4xl
  → text-5xl → text-[5.5rem]) so the headline stays inside the canvas
- objectPosition driven by focal-point fields per slide
- Native <video> support for video slides

HQ COMMAND — /hq-command/dashboard/hero
- Drag-drop reorder, click-to-set-focal-point, inline alt-text editing
- Auto-save with "Saving…" / "Saved ✓" indicators
- Per-slide caption overrides (title, subtitle, descriptions)
- Optional one-click AI translation to IT, VEC, ES, DE
- Drop-zone uploader → /api/assets (scope=footage, flat folder)

API — /api/assets
- New flat scopes: "footage" (writes to /public/footage/main) and
  "branding" (writes to /public/branding) — slug-less for site-wide assets
- New buildPublicUrl helper centralises the URL convention
- Revalidate helper expanded with branding + settings scopes

HOME PAGE
- Reads hero slides from DB first; falls back to filesystem scan when
  HeroSlide table is empty (so production keeps working immediately
  after migration runs but before the editor populates rows)

DEPLOY NOTES
- After git pull on VPS, run the migration ONCE:
    docker compose exec app npx prisma migrate deploy
  Then:
    docker compose up -d --build app
  Existing data (AdminUser w/ 2FA, ClientUser, GlobalNode, Application,
  TimelineEvent, NewsArticle, HeritageSection, SparePart, OperationsSignal,
  NotificationRoute, PageContent) is NOT touched. Migration only creates
  two new tables.
2026-05-04 09:34:49 -05:00

316 lines
12 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
//url = env("DATABASE_URL")
}
// ------------------------------------------------------
// 1. BÓVEDA DE SEGURIDAD (Usuarios del CMS)
// ------------------------------------------------------
model AdminUser {
id String @id @default(cuid())
username String @unique
email String? // 🔥 NUEVO CAMPO: Correo del administrador
passwordHash String
twoFactorSecret String?
is2FAEnabled Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
// ------------------------------------------------------
// 2. EL GLOBO HOLOGRÁFICO (Nodos y Casos de Estudio Profundos)
// ------------------------------------------------------
model GlobalNode {
id String @id @default(cuid())
title String // Ej: "Toray Advanced Textiles"
location String
// Ej: "Tokyo, Japan"
lat Float // Ej: 35.6895
lon Float // Ej: 139.6917
// Taxonomía
nodeType String @default("installation") // "installation", "event", "hq"
application String // Ej: "textile-drying", "hq", "event"
stats String // Ej: "2,400 kg/h throughput"
isActive Boolean @default(true) // Permite ocultar un nodo sin borrarlo
// 📖 GEO-CHRONICLE (THE STORY)
projectOverview String? // El Artículo completo / Resumen del evento (Markdown)
energySavings String? // Métrica (Ej: "-45% vs Conventional" o "Stand 4B")
eventDate DateTime?// Fecha para controlar si el evento es pasado o futuro
// 🔥 NUEVOS CAMPOS FASE 1: MULTIMEDIA Y DATASHEET ESPECÍFICO 🔥
mediaFileName String? // Imagen de Portada principal
galleryJson String? @default("[]") // Array de imágenes extra
videosJson String? @default("[]") // Links a videos reales
specificDatasheetJson String? @default("[]") // Ficha técnica de ESTA máquina
model3DPath String? // Ruta al archivo GLB/USDZ
rendersJson String? @default("[]") // Renders 3D fotorrealistas
model3DDimsJson String? // Dimensiones físicas AR: { w, h, d, unit, weight }
// 🌍 MOTOR DE TRADUCCIONES
translationsJson String? @default("{}")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
// ------------------------------------------------------
// 3. LA BASE DE CONOCIMIENTO (Páginas de Aplicaciones)
// ------------------------------------------------------
model Application {
id String @id @default(cuid())
slug String @unique // Ej: "textile-drying" (Debe coincidir con la URL)
title String
subtitle String
category String
// 🔥 NUEVO: La descripción corta para las tarjetas de la página principal
shortDescription String @default("Learn more about this FLUX RF technology application.")
heroDescription String // Recibirá MARKDOWN para la teoría científica general
// JSONs para estructuras complejas
sectionsJson String
advantagesJson String
datasheetJson String
// Métricas Rápidas para el Dashboard
dashboardMetricsJson String? @default("[]")
isActive Boolean @default(true) // 🔥 NUEVO: Para poder ocultarlas
// 🌍 MOTOR DE TRADUCCIONES
translationsJson String? @default("{}")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
// ------------------------------------------------------
// 4. NUESTRA HISTORIA (Línea de tiempo de la empresa)
// ------------------------------------------------------
model TimelineEvent {
id String @id @default(cuid())
year String // Ej: "1978" o "1990s"
title String
description String
order Int @default(0) // Para ordenar cronológicamente
isActive Boolean @default(true)
// 🌍 MOTOR DE TRADUCCIONES
translationsJson String? @default("{}")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
// ------------------------------------------------------
// 5. INSIDE FLUX (Motor de Noticias y Detrás de Cámaras)
// ------------------------------------------------------
model NewsArticle {
id String @id @default(cuid())
slug String @unique
title String
excerpt String // Resumen corto para la tarjeta
content String // El artículo completo (Markdown)
coverImage String? // Ej: "team-meeting.jpg"
category String @default("News")
publishedAt DateTime @default(now())
isActive Boolean @default(true)
// Editor avanzado
order Int @default(0) // Para ordenar las noticias
galleryJson String? @default("[]") // Galería de imágenes extra
linkedinUrl String? // Enlace oficial para LinkedIn
// 🌍 MOTOR DE TRADUCCIONES
translationsJson String? @default("{}")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
// ------------------------------------------------------
// 6. OUR HERITAGE (La Historia Profunda de Patrizio)
// ------------------------------------------------------
model HeritageSection {
id String @id @default(cuid())
type String @default("text") // "text", "image", "video"
title String?
content String? // Párrafos de la historia
mediaUrl String? // Ej: "patrizio-1980.jpg" o enlace de YouTube
order Int @default(0) // Para ordenar cómo se lee la página
// 🌍 MOTOR DE TRADUCCIONES
translationsJson String? @default("{}")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
// ------------------------------------------------------
// 7. COMPONENT MATRIX (Catálogo de Repuestos)
// ------------------------------------------------------
model SparePart {
id String @id @default(cuid())
sku String @unique // Identificador único / Referencia (Ej: "FLX-GEN-001")
title String // Nombre de la pieza en Inglés
description String // Descripción técnica / Función (Markdown)
// Multimedia & Ficha Técnica
mediaJson String? @default("[]") // Imágenes, videos, renders 3D
specsJson String? @default("[]") // Array de métricas [{label: "Voltage", value: "24V"}]
// Estrategia de Ventas
price Float? // Precio (Opcional)
showPrice Boolean @default(false) // Interruptor: true = mostrar precio, false = "Request Quote"
isActive Boolean @default(true) // Para ocultar repuestos descontinuados
// 🌍 MOTOR DE TRADUCCIONES (Integración con aiTranslator.ts)
translationsJson String? @default("{}")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
// ------------------------------------------------------
// 8. OPERATIONS INBOX (Signal Hub - Mesa de Ayuda y Órdenes)
// ------------------------------------------------------
model OperationsSignal {
id String @id @default(cuid())
ticketId String @unique
ticketNumber Int @default(autoincrement()) // Sequential for analytics
type String // "ORDER", "DIAGNOSTIC", "CONSULTATION"
status String @default("PENDING") // "PENDING", "REVIEWING", "RESOLVED"
// Client data
clientName String
clientEmail String
clientCompany String
clientPhone String?
message String?
// Payloads
cartPayload String? @default("[]")
attachedFiles String? @default("[]")
aiAnalysis String?
// Email delivery tracking
emailSentTo String? // Comma-separated list of emails that received notification
emailSentAt DateTime? // When the email was dispatched
emailError String? // Error message if email failed
// 🔥 NUEVO: Relación opcional con el Cliente Registrado (Para el futuro CRM)
clientId String?
client ClientUser? @relation(fields: [clientId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([type])
@@index([status])
@@index([createdAt(sort: Desc)])
}
// ------------------------------------------------------
// 9. RUTAS DE NOTIFICACIÓN (Gestión de Emails)
// ------------------------------------------------------
model NotificationRoute {
id String @id @default(cuid())
routeType String @unique // Ej: "ORDER", "DIAGNOSTIC", "CONSULTATION"
emails String // Correos separados por coma (Ej: "sales@fluxsrl.com, tech@fluxsrl.com")
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
// ------------------------------------------------------
// 10. PAGE CONTENT (Metadata y Textos de Páginas)
// ------------------------------------------------------
model PageContent {
id String @id @default(cuid())
slug String @unique // Identificador de la página (Ej: "parts-catalog")
title String
subtitle String?
description String?
// 🌍 MOTOR DE TRADUCCIONES
translationsJson String? @default("{}")
updatedAt DateTime @updatedAt
}
// ------------------------------------------------------
// 11. HERO REEL (Carrusel principal del Home)
// ------------------------------------------------------
// Manages the rotating images/videos shown in the home hero section.
// Replaces the previous filesystem-scan approach (fs.readdirSync of
// /public/footage/main) with full CMS control: ordering, on/off toggle,
// focal-point per slide for proper responsive cropping on mobile/tablet,
// and per-slide alt text for SEO.
model HeroSlide {
id String @id @default(cuid())
mediaUrl String // Public path, e.g. "/footage/main/01_tifas.png"
mediaType String @default("image") // "image" | "video"
altText String? // For accessibility + SEO; falls back to title if null
order Int @default(0)
isActive Boolean @default(true)
// Focal point for object-position on mobile/tablet crops (01 range).
// Lets the editor pick "what should stay visible when the image is cropped".
focalPointX Float @default(0.5)
focalPointY Float @default(0.5)
// Optional per-slide caption that overrides the global Hero text.
// Stored as JSON keyed by locale: {"en":{"title":"...","subtitle":"..."}}
translationsJson String? @default("{}")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
// ------------------------------------------------------
// 12. SITE SETTINGS (Favicon, Footer, Branding global)
// ------------------------------------------------------
// Single-row pattern (key-value) for global site config that doesn't
// fit any other model: favicon, logos, footer, OG image, social links.
model SiteSetting {
id String @id @default(cuid())
key String @unique // e.g. "favicon", "footer", "logo", "og_image", "hero_text"
valueJson String @default("{}") // Flexible JSON payload per key
// 🌍 Translation engine (used for things like footer link labels)
translationsJson String? @default("{}")
updatedAt DateTime @updatedAt
}
// ------------------------------------------------------
// 13. CLIENT PORTAL (Usuarios B2B Aprobados)
// ------------------------------------------------------
model ClientUser {
id String @id @default(cuid())
email String @unique
passwordHash String
fullName String
companyName String
phone String?
// Control de Acceso
isApproved Boolean @default(false) // Requiere aprobación del Admin
// Historial de Compras/Tickets
signals OperationsSignal[]
lastLoginAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}