diff --git a/mvp/b2c/src/db/schema.ts b/mvp/b2c/src/db/schema.ts index f9e026f..3df83d6 100644 --- a/mvp/b2c/src/db/schema.ts +++ b/mvp/b2c/src/db/schema.ts @@ -9,6 +9,8 @@ import { timestamp, jsonb, index, + doublePrecision, + uniqueIndex, } from 'drizzle-orm/pg-core'; // Estado comercial del lead — RF-D-03. Lo que el reformista gestiona a mano. @@ -42,6 +44,17 @@ export const tipoReforma = pgEnum('tipo_reforma', [ 'otro', ]); +export const calidad = pgEnum('calidad', ['basica', 'media', 'premium']); + +export const categoriaMaterial = pgEnum('categoria_material', [ + 'suelo', + 'pared', + 'pintura', + 'mobiliario', +]); + +export const unidadMedida = pgEnum('unidad_medida', ['m2', 'ml', 'ud']); + // Reformista (cliente del SaaS). MVP = "Reformas Ejemplo" hardcoded. // Multi-tenant real es F1.5; la tabla ya queda lista para ello. export const tenants = pgTable('tenants', { @@ -91,6 +104,17 @@ export const leads = pgTable( audioUrl: text('audio_url'), notas: text('notas'), + + // Inputs del motor de presupuesto (capturados de menos a más en el funnel) + m2Suelo: doublePrecision('m2_suelo'), + alturaTecho: doublePrecision('altura_techo'), + calidadGlobal: calidad('calidad_global'), + estructural: boolean('estructural').notNull().default(false), + materialSelections: jsonb('material_selections') + .$type>() + .notNull() + .default({}), + desgloseSnapshot: jsonb('desglose_snapshot'), }, (table) => [ index('leads_tenant_created_idx').on(table.tenantId, table.createdAt), @@ -144,6 +168,42 @@ export const precisionHistory = pgTable('precision_history', { createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), }); +// Configuración de precios del reformista (1 fila por tenant). RF-D-07. +export const pricingConfig = pgTable('pricing_config', { + id: uuid('id').primaryKey().defaultRandom(), + tenantId: uuid('tenant_id') + .notNull() + .references(() => tenants.id, { onDelete: 'cascade' }) + .unique(), + alturaTechoDefault: doublePrecision('altura_techo_default').notNull().default(2.5), + factorZona: jsonb('factor_zona').$type>().notNull().default({}), + manoObra: jsonb('mano_obra').$type>().notNull().default({}), + updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), +}); + +// Catálogo de materiales del reformista. Importable por CSV. +export const catalogItems = pgTable( + 'catalog_items', + { + id: uuid('id').primaryKey().defaultRandom(), + tenantId: uuid('tenant_id') + .notNull() + .references(() => tenants.id, { onDelete: 'cascade' }), + categoria: categoriaMaterial('categoria').notNull(), + nombre: text('nombre').notNull(), + calidad: calidad('calidad').notNull(), + precioUnit: integer('precio_unit').notNull(), // céntimos por unidad + unidad: unidadMedida('unidad').notNull(), + descriptorRender: text('descriptor_render').notNull().default(''), + esDefault: boolean('es_default').notNull().default(false), + sku: text('sku').notNull(), + }, + (table) => [ + index('catalog_tenant_idx').on(table.tenantId), + uniqueIndex('catalog_tenant_sku_idx').on(table.tenantId, table.sku), + ] +); + export type Tenant = typeof tenants.$inferSelect; export type Lead = typeof leads.$inferSelect; export type NewLead = typeof leads.$inferInsert; @@ -151,3 +211,6 @@ export type LeadFoto = typeof leadFotos.$inferSelect; export type LeadEstadoHistory = typeof leadEstadoHistory.$inferSelect; export type LeadPipelineEvento = typeof leadPipelineEventos.$inferSelect; export type PrecisionHistory = typeof precisionHistory.$inferSelect; +export type PricingConfigRow = typeof pricingConfig.$inferSelect; +export type CatalogItemRow = typeof catalogItems.$inferSelect; +export type NewCatalogItem = typeof catalogItems.$inferInsert;