H1📘 MOLINO — FAST PROJECTION ARCHITECTURE (LOCKED — MLV)
H10. SYSTEM IDENTITY
System = Deterministic Core + Projection Engine
- Prisma (DB) → single source of truth
- Entities → canonical business objects
- Document → projection + orchestration surface
- Folio → deterministic renderer
- Actions → only mutation authority
- Projection Engine → external outputs (Google Workspace)
H11. GLOBAL EXECUTION MODEL (SIMPLIFIED)
Truth (Prisma) ↓ Document (projection) ↓ Folio (render) ↓ User / AI Intent ↓ Actions (persist) ↓ Revalidate ↓ Projection (optional) ↓ External Systems (Docs / Calendar / Sheets)
H12. CORE PRINCIPLE (LOCKED)
Compute once → project everywhere
- All logic happens in Next.js
- External systems receive ready-to-render payloads
- No logic outside core system
H13. DOCUMENT SYSTEM (MINIMAL)
H23.1 ROLE
- projection surface
- working buffer
- UI interaction layer
NOT:
- not canonical truth
- not responsible for business logic
H23.2 OUTPUT RESPONSIBILITY
Document can trigger:
- Google Docs export
- Calendar generation
- Reports (Sheets / CSV)
H14. ENTITY CORE (UNCHANGED)
Entities:
- Project
- Trip
- Offer
- Order
- LineItem (core connector)
H24.1 LINE ITEM (CRITICAL)
LineItem {
label
quantity
unitPrice
total
}
Used across:
• documents
• offers
• reports
• exports
⸻
5. ACTION SYSTEM (LOCKED)
Location:
app/(entity)/actions/
Rules:
• ONLY Prisma writes
• ONLY mutation authority
• ALWAYS revalidate
⸻
6. PROJECTION ENGINE (NEW CORE LAYER)
⸻
6.1 ROLE
Projection Engine = external output adapter
• transforms internal data → external systems
• stateless
• reusable across entities
⸻
6.2 STRUCTURE
computeBundle(entity, id)
↓
projectEntity(bundle)
↓
External Systems
⸻
6.3 OUTPUT BUNDLE (CANONICAL)
type OutputBundle = {
meta: {
entity: string;
entityId: string;
title: string;
};
document?: DocumentPayload;
calendar?: CalendarPayload[];
tabular?: TabularPayload;
email?: EmailPayload;
};
⸻
7. OUTPUT STRUCTURES (LOCKED)
⸻
7.1 DOCUMENT
Narrative
type DocumentPayload = {
title: string;
blocks: { type: string; content: string }[];
};
⸻
With Line Items
type DocumentWithLineItems = {
title: string;
header: Record<string, string>;
lineItems: LineItem[];
totals?: { total: number };
};
⸻
7.2 CALENDAR
type CalendarPayload = {
title: string;
start: string;
end: string;
location?: string;
meta?: Record<string, string>;
};
⸻
7.3 TABULAR (CSV / SHEETS)
type TabularPayload = {
columns: string[];
rows: (string | number)[][];
};
⸻
7.4 EMAIL
type EmailPayload = {
to: string;
subject: string;
body: string;
};
⸻
8. PROJECTION FUNCTIONS
⸻
8.1 ENTRY POINT
export async function projectEntity(entity: string, id: string) {
const bundle = await computeBundle(entity, id);
if (bundle.document) await projectToDocs(bundle.document);
if (bundle.calendar) await projectToCalendar(bundle.calendar);
if (bundle.tabular) await projectToSheets(bundle.tabular);
if (bundle.email) await sendEmail(bundle.email);
}
⸻
8.2 RULE
• ONE button
• ONE action
• MULTIPLE outputs
⸻
9. APPS SCRIPT LAYER (LOCKED)
⸻
9.1 ROLE
Apps Script = stateless printer
• no logic
• no computation
• no iteration
⸻
9.2 ENDPOINTS
POST /gas/print/doc
POST /gas/print/calendar
POST /gas/print/sheet
POST /gas/print/email
⸻
9.3 CONTRACT
Input:
{
title,
data
}
Output:
{
success: true,
url
}
⸻
10. USER EXPERIENCE (SIMPLIFIED)
⸻
10.1 UNIVERSAL ACTION
Every entity:
[ Export ]
Optional:
Export →
• Doc
• Calendar
• Sheet
⸻
10.2 USER DOES NOT SEE
• APIs
• Apps Script
• data formats
• integration logic
⸻
11. MINIMAL ENTITY MAPPING
Entity Outputs
Trip Doc + Calendar
Offer Doc + Sheet
Order Doc + Email
Document Doc
LineItems Sheet + CSV
⸻
12. CSV EXPORT (LOCAL)
function toCSV(payload: TabularPayload) {
return [
payload.columns.join(","),
...payload.rows.map(r => r.join(","))
].join("\n");
}
⸻
13. PERFORMANCE MODEL
⸻
13.1 NEXT.JS
• computes everything
• formats everything
• resolves titles
• prepares payload
⸻
13.2 APPS SCRIPT
• receives payload
• renders output
• returns link
⸻
13.3 RESULT
• fast
• predictable
• scalable
⸻
14. DEVELOPMENT PLAN (FAST TRACK)
⸻
DAY 1
• projectToDocs
• computeBundle (Trip minimal)
✔ Google Doc output
⸻
DAY 2
• projectToCalendar
• projectToSheets
✔ full projection system
⸻
DAY 3
• wire entities
• add UI buttons
✔ usable system
⸻
15. RULES (CRITICAL)
⸻
• no logic in Apps Script
• no Google as source of truth
• no duplicate computation
• no multi-step UI
⸻
16. FINAL MODEL
Entity
↓
computeBundle
↓
projectEntity
↓
Apps Script
↓
Google Workspace
⸻
17. FINAL LOCK
You are building:
A fast projection engine over a deterministic system
⸻
18. SUCCESS CRITERIA
✔ click → doc generated
✔ trip → calendar generated
✔ offer → sheet generated
✔ one action works everywhere
⸻
19. FINAL PRINCIPLE
Simplicity over completeness
Projection over duplication
Integration over invention
⸻
🚀 NEXT
→ implement computeBundle(trip)
→ implement /gas/print/doc
→ connect button
STOP. Do not expand until loop works.
✔️ Short Answer
Your schema is already 90% compatible with the Projection Engine.
You do NOT need a redesign.
You only need to add a thin “projection metadata layer” + optional output tracking.
⸻
🟣 1. WHAT YOU ALREADY HAVE (ALIGNED)
You already cover:
✔ Entities (Trip, Offer, Order, LineItem)
✔ Document system (ProjectDocument, DocumentPage)
✔ Calendar (CalendarEvent)
✔ Deliverables (generic output container)
✔ Assets (external integration hooks possible)
→ This is already a full ERP core
⸻
🟢 2. WHAT IS MISSING (MINIMAL ADDITIONS)
You need ONLY 2 additions
⸻
A. Projection Metadata (LIGHTWEIGHT)
Add a universal tracking layer:
model Projection {
id String @id @default(cuid())
entity String // "trip" | "offer" | etc
entityId String
type String // "doc" | "calendar" | "sheet" | "email"
externalId String? // Google Doc ID, Event ID, Sheet ID
externalUrl String?
status String @default("created") // created | updated | failed
payload Json? // snapshot of what was sent
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([entity, entityId])
@@index([type])
}
⸻
WHY THIS MATTERS
Enables:
• re-sync
• updates
• debugging
• audit trail
• multi-output tracking
⸻
B. OPTIONAL: ExternalRef (LIGHTER ALTERNATIVE)
If you want even simpler:
model ExternalRef {
id String @id @default(cuid())
entity String
entityId String
key String // "docId", "calendarEventId"
value String
createdAt DateTime @default(now())
@@index([entity, entityId])
}
⸻
🟡 3. NO CHANGES NEEDED (IMPORTANT)
DO NOT modify:
• Trip
• Offer
• Order
• LineItem
• Document
• Deliverable
They already support projection.
⸻
🔵 4. HOW YOUR CURRENT MODELS MAP TO OUTPUTS
Output Current Model
Docs ProjectDocument / Deliverable
Calendar CalendarEvent
Sheets (derived, no model needed)
CSV (derived, no model needed)
Email (no persistence needed initially)
⸻
🟠 5. MINIMAL INTEGRATION (HOW IT FITS)
⸻
Example: Trip → Calendar
// Action
projectTripToCalendar(tripId)
Flow:
Trip
↓
computeBundle
↓
create CalendarEvent (optional local)
↓
call GAS
↓
store Projection
⸻
Example: Offer → Document
Offer
↓
LineItems
↓
computeBundle
↓
projectToDocs
↓
store Projection
⸻
🔴 6. WHAT YOU SHOULD NOT ADD
❌ No “GoogleDoc” model
❌ No “SheetModel”
❌ No duplication of outputs
❌ No schema for CSV
Reason:
Outputs are projections, not entities
⸻
🟣 7. OPTIONAL (ADVANCED BUT CLEAN)
Attach projection to Deliverable
You already have:
model Deliverable {
...
data Json
}
You can store:
{
"docUrl": "...",
"calendarIds": ["..."],
"sheetUrl": "..."
}
→ No schema change needed
⸻
🟢 8. FINAL MINIMAL ADDITION SET
REQUIRED
+ model Projection
OPTIONAL
+ model ExternalRef
⸻
⚡ 9. RESULTING SYSTEM (UPDATED)
Entity (Trip / Offer)
↓
computeBundle (Next.js)
↓
projectEntity
↓
Apps Script
↓
Google Workspace
↓
Projection (stored)
⸻
🧠 10. STRATEGIC IMPACT
With this small addition:
You now have:
✔ full audit trail
✔ sync capability
✔ multi-output system
✔ extensible integrations
✔ production-grade architecture
⸻
✅ FINAL VERDICT
• Your schema is already correct and sufficient
• Only 1 small model (Projection) completes the system
• No restructuring required
• You are already operating at ERP + Integration layer level
⸻
🚀 NEXT STEP
Add:
model Projection { ... }
Then implement:
projectEntity("trip", tripId)
→ Store result in Projection
That completes the loop.