feat(creatives): task 1 - migration, models and seed for creatives module

This commit is contained in:
Marcos Fargas
2026-10-12 13:08:45 +02:00
parent 3cb3296518
commit f08bcf2bb8
2 changed files with 394 additions and 0 deletions

185
src/lib/creatives-types.ts Normal file
View File

@@ -0,0 +1,185 @@
/**
* Tipos y Modelos de datos para el módulo Creatividades (CREATIVOS_BRIEF.md)
* Todas las entidades se estructuran con `tenant_id` (multi-tenant obligatorio).
*/
export type CreativeFormat =
| 'estatico'
| 'carrusel'
| 'video'
| 'image'
| 'carousel'
| 'reel'
| 'story'
| 'rsa'
| 'pmax'
| 'pmax_landscape'
| 'pmax_square'
| 'pmax_portrait'
| 'pmax_vertical'
| 'pmax_logo'
| 'youtube'
| 'other';
export type CreativeStatus =
| 'borrador'
| 'pendiente_aprobacion'
| 'aprobada'
| 'rechazada'
| 'publicada'
| 'fatigada'
| 'retirada'
| 'draft'
| 'in_review'
| 'approved'
| 'rejected';
export type CreativeVisibility = 'client' | 'agency';
export type CreativeCreatedBy = 'agency' | 'claude' | 'client';
export interface Creative {
id: string;
tenant_id: string;
campaign_name: string | null;
adset_id: string | null;
meta_ad_id: string | null;
format: CreativeFormat;
angle: string | null;
hook: string | null;
primary_text: string | null;
headline: string | null;
cta: string | null;
asset_url: string | null;
thumbnail_url: string | null;
status: CreativeStatus;
visibility: CreativeVisibility;
parent_id: string | null;
generation_request_id: string | null;
created_by: CreativeCreatedBy | string;
created_at: string;
updated_at: string;
// Campos relacionales opcionales al consultar con joins
parent?: Creative | null;
metrics_daily?: CreativeMetricsDaily[];
}
export interface CreativeMetricsDaily {
id: string;
tenant_id: string;
creative_id: string;
meta_ad_id: string;
date: string;
impressions: number;
reach: number;
frequency: number;
clicks: number;
ctr: number;
cpm: number;
cpc: number;
spend: number;
results: number;
cpa: number | null;
raw?: Record<string, unknown> | null;
created_at: string;
}
export interface FatigueRules {
id: string;
tenant_id: string;
lookback_days: number;
min_impressions: number;
max_frequency: number;
ctr_drop_pct: number;
cpa_rise_pct: number;
signals_required: number;
auto_request_relief: boolean;
updated_at: string;
}
export type GenerationRequestReason = 'fatiga' | 'calendario' | 'nueva_campana' | 'manual';
export type GenerationRequestStatus = 'pendiente' | 'en_proceso' | 'completada' | 'fallida';
export interface GenerationRequestBrief {
tenant_id: string;
client_id?: string; // alias compatible si n8n lo envía
client_slug: string;
reason: GenerationRequestReason;
source_creative_id?: string | null;
format: 'estatico' | 'carrusel' | 'video';
aspect_ratio?: string;
variants_requested: number;
avoid_angles?: string[];
campaign_name?: string | null;
adset_id?: string | null;
language?: string;
deadline?: string;
}
export interface GenerationRequestResult {
creatives: string[];
notes?: string;
skills_used?: string[];
}
export interface GenerationRequest {
id: string;
tenant_id: string;
reason: GenerationRequestReason;
source_creative_id: string | null;
brief: GenerationRequestBrief;
status: GenerationRequestStatus;
result: GenerationRequestResult | null;
cost_estimate: Record<string, unknown> | null;
error: string | null;
requested_at: string;
completed_at: string | null;
}
export type ApprovalActorRole = 'client' | 'agency';
export type ApprovalDecision = 'aprobada' | 'rechazada';
export type ApprovalChannel = 'app' | 'slack';
export interface Approval {
id: string;
tenant_id: string;
creative_id: string;
user_id: string | null;
actor_role: ApprovalActorRole;
decision: ApprovalDecision;
comment: string | null;
channel: ApprovalChannel;
created_at: string;
}
export type CreativeEventType =
| 'status_change'
| 'metrics_ingested'
| 'fatigue_detected'
| 'generation_requested'
| 'published'
| 'error';
export interface CreativeEvent {
id: string;
tenant_id: string;
creative_id: string | null;
event_type: CreativeEventType | string;
actor: string;
from_status: CreativeStatus | null;
to_status: CreativeStatus | null;
payload: Record<string, unknown> | null;
visibility: CreativeVisibility;
created_at: string;
}
export interface TenantWithCreativesMeta {
id: string;
name: string;
slug: string;
meta_ad_account_id?: string | null;
slack_channel_id?: string | null;
drive_folder_id?: string | null;
creatives_enabled: boolean;
}