diff --git a/mvp/b2c/drizzle/0007_pale_chat.sql b/mvp/b2c/drizzle/0007_pale_chat.sql
new file mode 100644
index 0000000..49973b1
--- /dev/null
+++ b/mvp/b2c/drizzle/0007_pale_chat.sql
@@ -0,0 +1,13 @@
+CREATE TABLE "galeria_fotos" (
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
+ "tenant_id" uuid NOT NULL,
+ "url" text NOT NULL,
+ "titulo" text,
+ "orden" integer DEFAULT 0 NOT NULL,
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL
+);
+--> statement-breakpoint
+ALTER TABLE "tenants" ADD COLUMN "theme_preset" text DEFAULT 'pizarra' NOT NULL;--> statement-breakpoint
+ALTER TABLE "tenants" ADD COLUMN "theme_color" text;--> statement-breakpoint
+ALTER TABLE "galeria_fotos" ADD CONSTRAINT "galeria_fotos_tenant_id_tenants_id_fk" FOREIGN KEY ("tenant_id") REFERENCES "public"."tenants"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+CREATE INDEX "galeria_tenant_idx" ON "galeria_fotos" USING btree ("tenant_id");
\ No newline at end of file
diff --git a/mvp/b2c/drizzle/meta/0007_snapshot.json b/mvp/b2c/drizzle/meta/0007_snapshot.json
new file mode 100644
index 0000000..4964861
--- /dev/null
+++ b/mvp/b2c/drizzle/meta/0007_snapshot.json
@@ -0,0 +1,1583 @@
+{
+ "id": "97f9780b-4aa5-45e4-96d5-f4b7d8334a11",
+ "prevId": "a8e37a10-3711-4a96-bbd5-3031dcbac788",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.catalog_items": {
+ "name": "catalog_items",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "tenant_id": {
+ "name": "tenant_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "categoria": {
+ "name": "categoria",
+ "type": "categoria_material",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "nombre": {
+ "name": "nombre",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "calidad": {
+ "name": "calidad",
+ "type": "calidad",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "precio_unit": {
+ "name": "precio_unit",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "unidad": {
+ "name": "unidad",
+ "type": "unidad_medida",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "descriptor_render": {
+ "name": "descriptor_render",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "es_default": {
+ "name": "es_default",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "sku": {
+ "name": "sku",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "catalog_tenant_idx": {
+ "name": "catalog_tenant_idx",
+ "columns": [
+ {
+ "expression": "tenant_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "catalog_tenant_sku_idx": {
+ "name": "catalog_tenant_sku_idx",
+ "columns": [
+ {
+ "expression": "tenant_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sku",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "catalog_items_tenant_id_tenants_id_fk": {
+ "name": "catalog_items_tenant_id_tenants_id_fk",
+ "tableFrom": "catalog_items",
+ "tableTo": "tenants",
+ "columnsFrom": [
+ "tenant_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.galeria_fotos": {
+ "name": "galeria_fotos",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "tenant_id": {
+ "name": "tenant_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "titulo": {
+ "name": "titulo",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "orden": {
+ "name": "orden",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "galeria_tenant_idx": {
+ "name": "galeria_tenant_idx",
+ "columns": [
+ {
+ "expression": "tenant_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "galeria_fotos_tenant_id_tenants_id_fk": {
+ "name": "galeria_fotos_tenant_id_tenants_id_fk",
+ "tableFrom": "galeria_fotos",
+ "tableTo": "tenants",
+ "columnsFrom": [
+ "tenant_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.lead_estado_history": {
+ "name": "lead_estado_history",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "lead_id": {
+ "name": "lead_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "estado": {
+ "name": "estado",
+ "type": "lead_estado",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "changed_at": {
+ "name": "changed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "changed_by": {
+ "name": "changed_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "lead_estado_history_lead_id_leads_id_fk": {
+ "name": "lead_estado_history_lead_id_leads_id_fk",
+ "tableFrom": "lead_estado_history",
+ "tableTo": "leads",
+ "columnsFrom": [
+ "lead_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.lead_fotos": {
+ "name": "lead_fotos",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "lead_id": {
+ "name": "lead_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "orden": {
+ "name": "orden",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "lead_fotos_lead_id_leads_id_fk": {
+ "name": "lead_fotos_lead_id_leads_id_fk",
+ "tableFrom": "lead_fotos",
+ "tableTo": "leads",
+ "columnsFrom": [
+ "lead_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.lead_pipeline_eventos": {
+ "name": "lead_pipeline_eventos",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "lead_id": {
+ "name": "lead_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "stage": {
+ "name": "stage",
+ "type": "pipeline_stage",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "occurred_at": {
+ "name": "occurred_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "lead_pipeline_eventos_lead_id_leads_id_fk": {
+ "name": "lead_pipeline_eventos_lead_id_leads_id_fk",
+ "tableFrom": "lead_pipeline_eventos",
+ "tableTo": "leads",
+ "columnsFrom": [
+ "lead_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.leads": {
+ "name": "leads",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "tenant_id": {
+ "name": "tenant_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "nombre": {
+ "name": "nombre",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "telefono": {
+ "name": "telefono",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provincia": {
+ "name": "provincia",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tipo_reforma": {
+ "name": "tipo_reforma",
+ "type": "tipo_reforma",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "consent_privacidad": {
+ "name": "consent_privacidad",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "consent_contratacion": {
+ "name": "consent_contratacion",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "pipeline_stage": {
+ "name": "pipeline_stage",
+ "type": "pipeline_stage",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'form_completado'"
+ },
+ "estado": {
+ "name": "estado",
+ "type": "lead_estado",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'nuevo'"
+ },
+ "presupuesto_estimado": {
+ "name": "presupuesto_estimado",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "transcripcion": {
+ "name": "transcripcion",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "entidades": {
+ "name": "entidades",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "render_url": {
+ "name": "render_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "pdf_url": {
+ "name": "pdf_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "audio_url": {
+ "name": "audio_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "notas": {
+ "name": "notas",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "testimonio_solicitado_at": {
+ "name": "testimonio_solicitado_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "m2_suelo": {
+ "name": "m2_suelo",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "altura_techo": {
+ "name": "altura_techo",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "calidad_global": {
+ "name": "calidad_global",
+ "type": "calidad",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "estructural": {
+ "name": "estructural",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "material_selections": {
+ "name": "material_selections",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "desglose_snapshot": {
+ "name": "desglose_snapshot",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "urgencia": {
+ "name": "urgencia",
+ "type": "urgencia",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "presupuesto_target": {
+ "name": "presupuesto_target",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "taste_text": {
+ "name": "taste_text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "preferences_snapshot": {
+ "name": "preferences_snapshot",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "leads_tenant_created_idx": {
+ "name": "leads_tenant_created_idx",
+ "columns": [
+ {
+ "expression": "tenant_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "leads_estado_idx": {
+ "name": "leads_estado_idx",
+ "columns": [
+ {
+ "expression": "estado",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "leads_tenant_id_tenants_id_fk": {
+ "name": "leads_tenant_id_tenants_id_fk",
+ "tableFrom": "leads",
+ "tableTo": "tenants",
+ "columnsFrom": [
+ "tenant_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.plans": {
+ "name": "plans",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "slug": {
+ "name": "slug",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "nombre": {
+ "name": "nombre",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "precio_mensual": {
+ "name": "precio_mensual",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "leads_incluidos": {
+ "name": "leads_incluidos",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "features": {
+ "name": "features",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "activo": {
+ "name": "activo",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "plans_slug_unique": {
+ "name": "plans_slug_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "slug"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.precision_history": {
+ "name": "precision_history",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "lead_id": {
+ "name": "lead_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "estimated": {
+ "name": "estimated",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "final": {
+ "name": "final",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "delta_pct": {
+ "name": "delta_pct",
+ "type": "numeric(6, 2)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "precision_history_lead_id_leads_id_fk": {
+ "name": "precision_history_lead_id_leads_id_fk",
+ "tableFrom": "precision_history",
+ "tableTo": "leads",
+ "columnsFrom": [
+ "lead_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.pricing_config": {
+ "name": "pricing_config",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "tenant_id": {
+ "name": "tenant_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "altura_techo_default": {
+ "name": "altura_techo_default",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 2.5
+ },
+ "factor_zona": {
+ "name": "factor_zona",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "mano_obra": {
+ "name": "mano_obra",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "pricing_config_tenant_id_tenants_id_fk": {
+ "name": "pricing_config_tenant_id_tenants_id_fk",
+ "tableFrom": "pricing_config",
+ "tableTo": "tenants",
+ "columnsFrom": [
+ "tenant_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "pricing_config_tenant_id_unique": {
+ "name": "pricing_config_tenant_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "tenant_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sessions": {
+ "name": "sessions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token_hash": {
+ "name": "token_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "sessions_user_idx": {
+ "name": "sessions_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "sessions_user_id_users_id_fk": {
+ "name": "sessions_user_id_users_id_fk",
+ "tableFrom": "sessions",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "sessions_token_hash_unique": {
+ "name": "sessions_token_hash_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "token_hash"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tenants": {
+ "name": "tenants",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "slug": {
+ "name": "slug",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "nombre_empresa": {
+ "name": "nombre_empresa",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "logo_url": {
+ "name": "logo_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "provincia": {
+ "name": "provincia",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "whatsapp_business": {
+ "name": "whatsapp_business",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "seo_title": {
+ "name": "seo_title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "seo_description": {
+ "name": "seo_description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "about_enabled": {
+ "name": "about_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "about_foto_url": {
+ "name": "about_foto_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "about_texto": {
+ "name": "about_texto",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "anios_experiencia": {
+ "name": "anios_experiencia",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "theme_preset": {
+ "name": "theme_preset",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pizarra'"
+ },
+ "theme_color": {
+ "name": "theme_color",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cif": {
+ "name": "cif",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "direccion": {
+ "name": "direccion",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "telefono": {
+ "name": "telefono",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "web": {
+ "name": "web",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "plan_id": {
+ "name": "plan_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "subscription_status": {
+ "name": "subscription_status",
+ "type": "subscription_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'trial'"
+ },
+ "envio_presupuesto": {
+ "name": "envio_presupuesto",
+ "type": "envio_presupuesto_mode",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'automatico'"
+ },
+ "trial_ends_at": {
+ "name": "trial_ends_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "stripe_customer_id": {
+ "name": "stripe_customer_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "tenants_plan_id_plans_id_fk": {
+ "name": "tenants_plan_id_plans_id_fk",
+ "tableFrom": "tenants",
+ "tableTo": "plans",
+ "columnsFrom": [
+ "plan_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "tenants_slug_unique": {
+ "name": "tenants_slug_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "slug"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.testimonio_fotos": {
+ "name": "testimonio_fotos",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "testimonio_id": {
+ "name": "testimonio_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "orden": {
+ "name": "orden",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "testimonio_fotos_testimonio_id_testimonios_id_fk": {
+ "name": "testimonio_fotos_testimonio_id_testimonios_id_fk",
+ "tableFrom": "testimonio_fotos",
+ "tableTo": "testimonios",
+ "columnsFrom": [
+ "testimonio_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.testimonios": {
+ "name": "testimonios",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "tenant_id": {
+ "name": "tenant_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "lead_id": {
+ "name": "lead_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "nombre": {
+ "name": "nombre",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "contexto": {
+ "name": "contexto",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rating": {
+ "name": "rating",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "texto": {
+ "name": "texto",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "estado": {
+ "name": "estado",
+ "type": "testimonio_estado",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pendiente'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "testimonios_tenant_estado_idx": {
+ "name": "testimonios_tenant_estado_idx",
+ "columns": [
+ {
+ "expression": "tenant_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "estado",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "testimonios_lead_idx": {
+ "name": "testimonios_lead_idx",
+ "columns": [
+ {
+ "expression": "lead_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "testimonios_tenant_id_tenants_id_fk": {
+ "name": "testimonios_tenant_id_tenants_id_fk",
+ "tableFrom": "testimonios",
+ "tableTo": "tenants",
+ "columnsFrom": [
+ "tenant_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "testimonios_lead_id_leads_id_fk": {
+ "name": "testimonios_lead_id_leads_id_fk",
+ "tableFrom": "testimonios",
+ "tableTo": "leads",
+ "columnsFrom": [
+ "lead_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.users": {
+ "name": "users",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "password_hash": {
+ "name": "password_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "nombre": {
+ "name": "nombre",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "role": {
+ "name": "role",
+ "type": "user_role",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'reformista'"
+ },
+ "tenant_id": {
+ "name": "tenant_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "user_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'activo'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "users_tenant_idx": {
+ "name": "users_tenant_idx",
+ "columns": [
+ {
+ "expression": "tenant_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "users_tenant_id_tenants_id_fk": {
+ "name": "users_tenant_id_tenants_id_fk",
+ "tableFrom": "users",
+ "tableTo": "tenants",
+ "columnsFrom": [
+ "tenant_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "users_email_unique": {
+ "name": "users_email_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "email"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {
+ "public.calidad": {
+ "name": "calidad",
+ "schema": "public",
+ "values": [
+ "basica",
+ "media",
+ "premium"
+ ]
+ },
+ "public.categoria_material": {
+ "name": "categoria_material",
+ "schema": "public",
+ "values": [
+ "suelo",
+ "pared",
+ "pintura",
+ "mobiliario"
+ ]
+ },
+ "public.envio_presupuesto_mode": {
+ "name": "envio_presupuesto_mode",
+ "schema": "public",
+ "values": [
+ "automatico",
+ "revision"
+ ]
+ },
+ "public.lead_estado": {
+ "name": "lead_estado",
+ "schema": "public",
+ "values": [
+ "nuevo",
+ "contactado",
+ "visita_agendada",
+ "presupuesto_enviado",
+ "ganado",
+ "perdido"
+ ]
+ },
+ "public.pipeline_stage": {
+ "name": "pipeline_stage",
+ "schema": "public",
+ "values": [
+ "form_completado",
+ "fotos_subidas",
+ "prellamada_enviada",
+ "llamada_completada",
+ "render_generado",
+ "presupuesto_generado",
+ "whatsapp_entregado"
+ ]
+ },
+ "public.subscription_status": {
+ "name": "subscription_status",
+ "schema": "public",
+ "values": [
+ "trial",
+ "activo",
+ "cancelado",
+ "vencido"
+ ]
+ },
+ "public.testimonio_estado": {
+ "name": "testimonio_estado",
+ "schema": "public",
+ "values": [
+ "pendiente",
+ "publicado",
+ "oculto"
+ ]
+ },
+ "public.tipo_reforma": {
+ "name": "tipo_reforma",
+ "schema": "public",
+ "values": [
+ "cocina",
+ "bano",
+ "salon",
+ "comedor",
+ "integral",
+ "otro"
+ ]
+ },
+ "public.unidad_medida": {
+ "name": "unidad_medida",
+ "schema": "public",
+ "values": [
+ "m2",
+ "ml",
+ "ud"
+ ]
+ },
+ "public.urgencia": {
+ "name": "urgencia",
+ "schema": "public",
+ "values": [
+ "alta",
+ "media",
+ "baja"
+ ]
+ },
+ "public.user_role": {
+ "name": "user_role",
+ "schema": "public",
+ "values": [
+ "reformista",
+ "admin"
+ ]
+ },
+ "public.user_status": {
+ "name": "user_status",
+ "schema": "public",
+ "values": [
+ "activo",
+ "deshabilitado"
+ ]
+ }
+ },
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/mvp/b2c/drizzle/meta/_journal.json b/mvp/b2c/drizzle/meta/_journal.json
index 0ce359e..1eef9c9 100644
--- a/mvp/b2c/drizzle/meta/_journal.json
+++ b/mvp/b2c/drizzle/meta/_journal.json
@@ -50,6 +50,13 @@
"when": 1780308810691,
"tag": "0006_aspiring_susan_delgado",
"breakpoints": true
+ },
+ {
+ "idx": 7,
+ "version": "7",
+ "when": 1780313493522,
+ "tag": "0007_pale_chat",
+ "breakpoints": true
}
]
}
\ No newline at end of file
diff --git a/mvp/b2c/src/app/[slug]/page.tsx b/mvp/b2c/src/app/[slug]/page.tsx
index ff20356..2b9d7f2 100644
--- a/mvp/b2c/src/app/[slug]/page.tsx
+++ b/mvp/b2c/src/app/[slug]/page.tsx
@@ -5,9 +5,11 @@ import ReformaSlider from '@/components/ReformaSlider/ReformaSlider';
import Features from '@/components/Features/Features';
import QuienesSomos from '@/components/funnel/QuienesSomos';
import TestimoniosCliente from '@/components/funnel/TestimoniosCliente';
+import GaleriaTrabajos from '@/components/funnel/GaleriaTrabajos';
import Footer from '@/components/Footer/Footer';
import TenantBrand from '@/components/funnel/TenantBrand';
-import { getTenantBySlug, getPublishedTestimonios } from '@/lib/funnel/public-queries';
+import { getTenantBySlug, getPublishedTestimonios, getGaleria } from '@/lib/funnel/public-queries';
+import { resolveTheme, themeStyle } from '@/lib/funnel/themes';
export const dynamic = 'force-dynamic';
@@ -32,11 +34,19 @@ export default async function FunnelPage({ params }: { params: Promise<{ slug: s
const tenant = await getTenantBySlug(slug);
if (!tenant) notFound();
- const testimonios = await getPublishedTestimonios(tenant.id);
+ const [testimonios, galeria] = await Promise.all([
+ getPublishedTestimonios(tenant.id),
+ getGaleria(tenant.id),
+ ]);
+
+ const theme = resolveTheme(tenant.themePreset, tenant.themeColor);
return (
- <>
-
Estos datos y el logo aparecen en la cabecera de los presupuestos en PDF que recibe el cliente. Manténlos al día. @@ -184,7 +185,7 @@ export default async function EmpresaPage() { /> -
+ Elige los colores y la tipografía con los que tus clientes ven tu landing. Puedes partir + de un preset y, si quieres, fijar tu propio color de marca. +
++ Sube fotos de reformas que ya has hecho. Aparecen en tu funnel para dar confianza al + cliente antes de pedir presupuesto. +
++ Aún no has subido ninguna foto. La galería no se mostrará en tu funnel hasta que añadas la + primera. +
+ ) : ( +Las opiniones que te dejan tus clientes. Aprueba las que quieras mostrar en tu funnel; solo las publicadas aparecen en tu página. diff --git a/mvp/b2c/src/app/panel/page.tsx b/mvp/b2c/src/app/panel/page.tsx index 70e88a9..d9f9a0e 100644 --- a/mvp/b2c/src/app/panel/page.tsx +++ b/mvp/b2c/src/app/panel/page.tsx @@ -1,14 +1,7 @@ import Link from 'next/link'; import { getLeads, getResumen, type LeadFiltro } from '@/db/queries'; -import { - ESTADOS, - ESTADO_BADGE, - ESTADO_LABEL, - PIPELINE_LABEL, - PIPELINE_NEXT, - formatEuros, - formatFecha, -} from '@/lib/funnel'; +import { ESTADOS, ESTADO_LABEL } from '@/lib/funnel'; +import LeadsView, { type PanelLead } from '@/components/panel/LeadsView'; import { getCurrentTenantId } from '@/lib/auth/current-user'; import { db } from '@/db'; import { tenants, plans } from '@/db/schema'; @@ -32,6 +25,20 @@ export default async function PanelPage({ const filtro: LeadFiltro = (FILTROS.find((f) => f.value === estado)?.value ?? 'todos') as LeadFiltro; const [leads, resumen] = await Promise.all([getLeads(filtro), getResumen()]); + const leadsView: PanelLead[] = leads.map((l) => ({ + id: l.id, + nombre: l.nombre, + telefono: l.telefono, + provincia: l.provincia, + tipoReforma: l.tipoReforma, + estado: l.estado, + pipelineStage: l.pipelineStage, + presupuestoEstimado: l.presupuestoEstimado, + renderUrl: l.renderUrl, + createdAtMs: l.createdAt.getTime(), + m2Suelo: l.m2Suelo, + calidadGlobal: l.calidadGlobal, + })); const tenantId = await getCurrentTenantId(); const [tenant] = await db.select().from(tenants).where(eq(tenants.id, tenantId)).limit(1); @@ -82,96 +89,7 @@ export default async function PanelPage({ })}
| Render | -Cliente | -Fecha | -Estado | -Presupuesto | -Siguiente paso | -
|---|---|---|---|---|---|
|
-
- {l.renderUrl ? (
- // eslint-disable-next-line @next/next/no-img-element
- |
-
-
- {l.nombre}
-
- {l.telefono}
- |
- {formatFecha(l.createdAt)} | -- - {ESTADO_LABEL[l.estado]} - - | -- {formatEuros(l.presupuestoEstimado)} - | -
- {PIPELINE_LABEL[l.pipelineStage]}
- {PIPELINE_NEXT[l.pipelineStage]}
- |
-
Define los precios unitarios y la mano de obra. El motor calcula el presupuesto a
partir de estos valores y las medidas del lead.
diff --git a/mvp/b2c/src/app/signup/page.tsx b/mvp/b2c/src/app/signup/page.tsx
index 7c5b86e..640708d 100644
--- a/mvp/b2c/src/app/signup/page.tsx
+++ b/mvp/b2c/src/app/signup/page.tsx
@@ -1,29 +1,73 @@
'use client';
+import Link from 'next/link';
import { useActionState } from 'react';
import { signup } from './actions';
+import AuthShell from '@/components/auth/AuthShell';
+
+const inputClass =
+ 'rounded-lg border border-gray-300 px-3 py-2.5 text-sm focus:border-gray-900 focus:outline-none focus:ring-1 focus:ring-gray-900';
export default function SignupPage() {
const [error, formAction, pending] = useActionState(signup, null);
return (
- Sin tarjeta. Configura tu catálogo y recibe leads.
+ Sin tarjeta. Configura tu catálogo y recibe leads.
+ {error}
+ ¿Ya tienes cuenta?{' '}
+
+ Entrar
+
+ Empieza gratis 14 días
- Empieza gratis 14 días
+