Añade revisión pre-envío del reformista y PDF de presupuesto pulido
Adelanta de F1.5 a F2 la validación pre-envío: el panel permite elegir modo de envío (automático/revisión), editar los conceptos del presupuesto y enviar al cliente por WhatsApp (simulado). Añade datos de empresa y logo configurables en /panel/empresa y genera el presupuesto como PDF real descargable con esa marca vía @react-pdf/renderer, sustituyendo la vista HTML imprimible. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,21 @@
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { db } from './index';
|
||||
import { pricingConfig, catalogItems } from './schema';
|
||||
import { pricingConfig, catalogItems, tenants } from './schema';
|
||||
import type { PricingConfig, CatalogItem, ManoObraKey } from '@/budget/types';
|
||||
import { getCurrentTenantId as getTenantId } from '@/lib/auth/current-user';
|
||||
|
||||
export type EnvioMode = (typeof tenants.envioPresupuesto.enumValues)[number];
|
||||
|
||||
export async function getEnvioMode(): Promise<EnvioMode> {
|
||||
const tenantId = await getTenantId();
|
||||
const [row] = await db
|
||||
.select({ modo: tenants.envioPresupuesto })
|
||||
.from(tenants)
|
||||
.where(eq(tenants.id, tenantId))
|
||||
.limit(1);
|
||||
return row?.modo ?? 'automatico';
|
||||
}
|
||||
|
||||
const MANO_OBRA_DEFAULT: Record<ManoObraKey, number> = {
|
||||
demolicion: 0,
|
||||
fontaneria: 0,
|
||||
|
||||
@@ -65,17 +65,28 @@ export const subscriptionStatus = pgEnum('subscription_status', [
|
||||
'vencido',
|
||||
]);
|
||||
|
||||
// Cómo entrega el reformista el presupuesto al cliente final.
|
||||
// 'automatico' = el funnel lo envía solo; 'revision' = se para para que el reformista lo revise/edite antes de enviar.
|
||||
export const envioPresupuestoMode = pgEnum('envio_presupuesto_mode', ['automatico', 'revision']);
|
||||
|
||||
// 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', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
slug: text('slug').notNull().unique(),
|
||||
nombreEmpresa: text('nombre_empresa').notNull(),
|
||||
logoUrl: text('logo_url'),
|
||||
logoUrl: text('logo_url'), // data URI base64 del logo (no hay storage externo aún)
|
||||
provincia: text('provincia'),
|
||||
whatsappBusiness: text('whatsapp_business'),
|
||||
// Datos de empresa para la cabecera del presupuesto (RF-D-07).
|
||||
cif: text('cif'),
|
||||
direccion: text('direccion'),
|
||||
telefono: text('telefono'),
|
||||
email: text('email'),
|
||||
web: text('web'),
|
||||
planId: uuid('plan_id').references((): AnyPgColumn => plans.id),
|
||||
subscriptionStatus: subscriptionStatus('subscription_status').notNull().default('trial'),
|
||||
envioPresupuesto: envioPresupuestoMode('envio_presupuesto').notNull().default('automatico'),
|
||||
trialEndsAt: timestamp('trial_ends_at', { withTimezone: true }),
|
||||
stripeCustomerId: text('stripe_customer_id'),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
|
||||
46
mvp/b2c/src/db/tenant-queries.ts
Normal file
46
mvp/b2c/src/db/tenant-queries.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { db } from './index';
|
||||
import { tenants } from './schema';
|
||||
import { getCurrentTenantId as getTenantId } from '@/lib/auth/current-user';
|
||||
|
||||
export type TenantPerfil = {
|
||||
nombreEmpresa: string;
|
||||
logoUrl: string | null;
|
||||
provincia: string | null;
|
||||
cif: string | null;
|
||||
direccion: string | null;
|
||||
telefono: string | null;
|
||||
email: string | null;
|
||||
web: string | null;
|
||||
};
|
||||
|
||||
export async function getTenantPerfil(): Promise<TenantPerfil> {
|
||||
const tenantId = await getTenantId();
|
||||
const [row] = await db
|
||||
.select({
|
||||
nombreEmpresa: tenants.nombreEmpresa,
|
||||
logoUrl: tenants.logoUrl,
|
||||
provincia: tenants.provincia,
|
||||
cif: tenants.cif,
|
||||
direccion: tenants.direccion,
|
||||
telefono: tenants.telefono,
|
||||
email: tenants.email,
|
||||
web: tenants.web,
|
||||
})
|
||||
.from(tenants)
|
||||
.where(eq(tenants.id, tenantId))
|
||||
.limit(1);
|
||||
|
||||
return (
|
||||
row ?? {
|
||||
nombreEmpresa: 'Reformix',
|
||||
logoUrl: null,
|
||||
provincia: null,
|
||||
cif: null,
|
||||
direccion: null,
|
||||
telefono: null,
|
||||
email: null,
|
||||
web: null,
|
||||
}
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user