- Tabla ClienteMaestro (27 campos, unique numCliente) - Importador Excel: 1535 clientes del Reporte maestro - API: listar/filtros/exportar/detalle/importar - Frontend: tabla paginada, filtros, exportar Excel - Dark mode completo, 52 tests verdes
189 lines
6.2 KiB
TypeScript
189 lines
6.2 KiB
TypeScript
import { prisma } from './prisma.js'
|
|
|
|
// ── Tipos ───────────────────────────────────────────────────────────
|
|
|
|
export interface FiltrosClientesMaestro {
|
|
busqueda?: string
|
|
desarrollo?: string
|
|
estatus?: string
|
|
pagina?: number
|
|
limite?: number
|
|
}
|
|
|
|
export type ClienteMaestroDTO = {
|
|
id: string
|
|
numCliente: string
|
|
nombre: string
|
|
apellidoPaterno: string
|
|
apellidoMaterno: string | null
|
|
sexo: string | null
|
|
telefono: string | null
|
|
email: string | null
|
|
estadoCivil: string | null
|
|
rangoEdad: string | null
|
|
ocupacion: string | null
|
|
rangoIngresos: string | null
|
|
credito: string | null
|
|
institucion: string | null
|
|
medioContacto: string | null
|
|
canal: string | null
|
|
desarrollo: string | null
|
|
manzana: string | null
|
|
lote: string | null
|
|
interior: string | null
|
|
tipoCasa: string | null
|
|
etapa: string | null
|
|
valorTotal: number | null
|
|
estatus: string | null
|
|
fechaLiberacion: Date | null
|
|
vendedor: string | null
|
|
vendedorId: string | null
|
|
importadoEn: Date
|
|
}
|
|
|
|
// ── Helpers ─────────────────────────────────────────────────────────
|
|
|
|
function construirWhere(filtros: FiltrosClientesMaestro) {
|
|
const where: Record<string, unknown> = {}
|
|
|
|
if (filtros.desarrollo) {
|
|
where.desarrollo = filtros.desarrollo
|
|
}
|
|
if (filtros.estatus) {
|
|
where.estatus = filtros.estatus
|
|
}
|
|
if (filtros.busqueda) {
|
|
const term = filtros.busqueda.trim()
|
|
where.OR = [
|
|
{ nombre: { contains: term } },
|
|
{ apellidoPaterno: { contains: term } },
|
|
{ apellidoMaterno: { contains: term } },
|
|
{ telefono: { contains: term } },
|
|
{ numCliente: { contains: term } },
|
|
]
|
|
}
|
|
|
|
return where
|
|
}
|
|
|
|
// ── Service ─────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Lista clientes maestro con filtros y paginación.
|
|
*/
|
|
export async function listar(filtros: FiltrosClientesMaestro): Promise<{
|
|
datos: ClienteMaestroDTO[]
|
|
total: number
|
|
pagina: number
|
|
limite: number
|
|
}> {
|
|
const pagina = filtros.pagina ?? 1
|
|
const limite = filtros.limite ?? 50
|
|
const skip = (pagina - 1) * limite
|
|
const where = construirWhere(filtros)
|
|
|
|
const [datos, total] = await Promise.all([
|
|
prisma.clienteMaestro.findMany({
|
|
where,
|
|
orderBy: [{ apellidoPaterno: 'asc' }, { nombre: 'asc' }],
|
|
skip,
|
|
take: limite,
|
|
}),
|
|
prisma.clienteMaestro.count({ where }),
|
|
])
|
|
|
|
return { datos, total, pagina, limite }
|
|
}
|
|
|
|
/**
|
|
* Obtiene un cliente por numCliente.
|
|
*/
|
|
export async function obtener(numCliente: string): Promise<ClienteMaestroDTO | null> {
|
|
return prisma.clienteMaestro.findUnique({ where: { numCliente } })
|
|
}
|
|
|
|
/**
|
|
* Cuenta clientes con filtros.
|
|
*/
|
|
export async function contar(filtros: Omit<FiltrosClientesMaestro, 'pagina' | 'limite'>): Promise<number> {
|
|
const where = construirWhere(filtros)
|
|
return prisma.clienteMaestro.count({ where })
|
|
}
|
|
|
|
/**
|
|
* Devuelve array de objetos para exportar a xlsx.
|
|
*/
|
|
export async function exportar(
|
|
filtros: Omit<FiltrosClientesMaestro, 'pagina' | 'limite'>,
|
|
): Promise<ClienteMaestroDTO[]> {
|
|
const where = construirWhere(filtros)
|
|
return prisma.clienteMaestro.findMany({
|
|
where,
|
|
orderBy: [{ apellidoPaterno: 'asc' }, { nombre: 'asc' }],
|
|
take: 50000, // safety cap
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Bulk upsert: recibe un array de datos y los inserta/actualiza por numCliente.
|
|
*/
|
|
export async function importarDesdeExcel(datos: Array<Record<string, unknown>>): Promise<{ insertados: number; actualizados: number; errores: number }> {
|
|
let insertados = 0
|
|
let actualizados = 0
|
|
let errores = 0
|
|
|
|
for (const fila of datos) {
|
|
const numCliente = String(fila.numCliente ?? '').trim()
|
|
const nombre = String(fila.nombre ?? '').trim()
|
|
if (!numCliente || !nombre) {
|
|
errores++
|
|
continue
|
|
}
|
|
|
|
try {
|
|
const existente = await prisma.clienteMaestro.findUnique({ where: { numCliente } })
|
|
|
|
const data = {
|
|
numCliente,
|
|
nombre,
|
|
apellidoPaterno: String(fila.apellidoPaterno ?? '').trim(),
|
|
apellidoMaterno: fila.apellidoMaterno ? String(fila.apellidoMaterno).trim() : null,
|
|
sexo: fila.sexo ? String(fila.sexo).trim() : null,
|
|
telefono: fila.telefono ? String(fila.telefono).trim() : null,
|
|
email: fila.email ? String(fila.email).trim() : null,
|
|
estadoCivil: fila.estadoCivil ? String(fila.estadoCivil).trim() : null,
|
|
rangoEdad: fila.rangoEdad ? String(fila.rangoEdad).trim() : null,
|
|
ocupacion: fila.ocupacion ? String(fila.ocupacion).trim() : null,
|
|
rangoIngresos: fila.rangoIngresos ? String(fila.rangoIngresos).trim() : null,
|
|
credito: fila.credito ? String(fila.credito).trim() : null,
|
|
institucion: fila.institucion ? String(fila.institucion).trim() : null,
|
|
medioContacto: fila.medioContacto ? String(fila.medioContacto).trim() : null,
|
|
canal: fila.canal ? String(fila.canal).trim() : null,
|
|
desarrollo: fila.desarrollo ? String(fila.desarrollo).trim() : null,
|
|
manzana: fila.manzana ? String(fila.manzana).trim() : null,
|
|
lote: fila.lote ? String(fila.lote).trim() : null,
|
|
interior: fila.interior ? String(fila.interior).trim() : null,
|
|
tipoCasa: fila.tipoCasa ? String(fila.tipoCasa).trim() : null,
|
|
etapa: fila.etapa ? String(fila.etapa).trim() : null,
|
|
valorTotal: typeof fila.valorTotal === 'number' ? fila.valorTotal : null,
|
|
estatus: fila.estatus ? String(fila.estatus).trim() : null,
|
|
fechaLiberacion: fila.fechaLiberacion ? new Date(String(fila.fechaLiberacion)) : null,
|
|
vendedor: fila.vendedor ? String(fila.vendedor).trim() : null,
|
|
vendedorId: fila.vendedorId ? String(fila.vendedorId).trim() : null,
|
|
}
|
|
|
|
if (existente) {
|
|
await prisma.clienteMaestro.update({ where: { numCliente }, data })
|
|
actualizados++
|
|
} else {
|
|
await prisma.clienteMaestro.create({ data })
|
|
insertados++
|
|
}
|
|
} catch {
|
|
errores++
|
|
}
|
|
}
|
|
|
|
return { insertados, actualizados, errores }
|
|
}
|