105 lines
3.3 KiB
TypeScript
105 lines
3.3 KiB
TypeScript
'use server';
|
|
|
|
import { and, eq } from 'drizzle-orm';
|
|
import { revalidatePath } from 'next/cache';
|
|
import { db } from '@/db';
|
|
import { leads, leadEstadoHistory, leadPipelineEventos, precisionHistory } from '@/db/schema';
|
|
import { getCurrentTenantId as getTenantId } from '@/lib/auth/current-user';
|
|
import { getPricingConfig, getCatalog } from '@/db/pricing-queries';
|
|
import { computeBudget } from '@/budget';
|
|
import type { BudgetInputs } from '@/budget/types';
|
|
|
|
type Estado = (typeof leads.estado.enumValues)[number];
|
|
|
|
export async function cambiarEstado(leadId: string, estado: Estado) {
|
|
const tenantId = await getTenantId();
|
|
|
|
const [updated] = await db
|
|
.update(leads)
|
|
.set({ estado, updatedAt: new Date() })
|
|
.where(and(eq(leads.id, leadId), eq(leads.tenantId, tenantId)))
|
|
.returning();
|
|
|
|
if (!updated) throw new Error('Lead no encontrado.');
|
|
|
|
await db.insert(leadEstadoHistory).values({ leadId, estado });
|
|
|
|
revalidatePath('/panel');
|
|
revalidatePath(`/panel/${leadId}`);
|
|
}
|
|
|
|
export async function marcarGanado(leadId: string, precioFinalEuros: number) {
|
|
const tenantId = await getTenantId();
|
|
|
|
const [lead] = await db
|
|
.select()
|
|
.from(leads)
|
|
.where(and(eq(leads.id, leadId), eq(leads.tenantId, tenantId)))
|
|
.limit(1);
|
|
|
|
if (!lead) throw new Error('Lead no encontrado.');
|
|
if (lead.presupuestoEstimado == null) {
|
|
throw new Error('El lead no tiene presupuesto estimado, no se puede calcular la precisión.');
|
|
}
|
|
|
|
const finalCents = Math.round(precioFinalEuros * 100);
|
|
const deltaPct = ((finalCents - lead.presupuestoEstimado) / lead.presupuestoEstimado) * 100;
|
|
|
|
await db
|
|
.update(leads)
|
|
.set({ estado: 'ganado', updatedAt: new Date() })
|
|
.where(and(eq(leads.id, leadId), eq(leads.tenantId, tenantId)));
|
|
await db.insert(leadEstadoHistory).values({ leadId, estado: 'ganado' });
|
|
await db.insert(precisionHistory).values({
|
|
leadId,
|
|
estimated: lead.presupuestoEstimado,
|
|
final: finalCents,
|
|
deltaPct: deltaPct.toFixed(2),
|
|
});
|
|
|
|
revalidatePath('/panel');
|
|
revalidatePath(`/panel/${leadId}`);
|
|
}
|
|
|
|
export async function recalcularPresupuesto(leadId: string) {
|
|
const tenantId = await getTenantId();
|
|
const [lead] = await db
|
|
.select()
|
|
.from(leads)
|
|
.where(and(eq(leads.id, leadId), eq(leads.tenantId, tenantId)))
|
|
.limit(1);
|
|
if (!lead) throw new Error('Lead no encontrado.');
|
|
|
|
const [config, catalog] = await Promise.all([getPricingConfig(), getCatalog()]);
|
|
|
|
const inputs: BudgetInputs = {
|
|
tipoReforma: lead.tipoReforma ?? 'otro',
|
|
m2Suelo: lead.m2Suelo ?? null,
|
|
alturaTecho: lead.alturaTecho ?? null,
|
|
calidadGlobal: lead.calidadGlobal ?? 'media',
|
|
estructural: lead.estructural,
|
|
provincia: lead.provincia ?? null,
|
|
materialSelections: (lead.materialSelections as Record<string, string>) ?? {},
|
|
};
|
|
|
|
const result = computeBudget(inputs, config, catalog);
|
|
|
|
await db
|
|
.update(leads)
|
|
.set({
|
|
presupuestoEstimado: result.total,
|
|
desgloseSnapshot: { stage: lead.pipelineStage, result },
|
|
updatedAt: new Date(),
|
|
})
|
|
.where(and(eq(leads.id, leadId), eq(leads.tenantId, tenantId)));
|
|
|
|
await db.insert(leadPipelineEventos).values({
|
|
leadId,
|
|
stage: 'presupuesto_generado',
|
|
metadata: { total: result.total, confianza: result.confianza },
|
|
});
|
|
|
|
revalidatePath('/panel');
|
|
revalidatePath(`/panel/${leadId}`);
|
|
}
|