feat(clientes-maestro): importar y exportar clientes del archivo maestro
- 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
This commit is contained in:
66
api-gateway/scripts/importar-clientes-maestro.js
Executable file
66
api-gateway/scripts/importar-clientes-maestro.js
Executable file
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env node
|
||||
import { PrismaClient } from '@prisma/client'
|
||||
import XLSX from 'xlsx'
|
||||
|
||||
const prisma = new PrismaClient()
|
||||
const toStr = (v) => v == null ? null : String(v).trim() || null
|
||||
const toNum = (v) => {
|
||||
if (v == null) return null
|
||||
const n = Number(String(v).replace(/[$,\s]/g, ''))
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
|
||||
const wb = XLSX.readFile('/root/Reporte maestro de clientesAGO26.xlsx')
|
||||
const rows = XLSX.utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]], { header: 1, defval: null })
|
||||
console.log(`Filas: ${rows.length}`)
|
||||
|
||||
const map = new Map()
|
||||
for (let i = 1; i < rows.length; i++) {
|
||||
const nc = toStr(rows[i][0])
|
||||
if (nc) map.set(nc, rows[i])
|
||||
}
|
||||
console.log(`Clientes: ${map.size}`)
|
||||
|
||||
let ok = 0, fail = 0
|
||||
let idx = 0
|
||||
for (const [nc, r] of map) {
|
||||
idx++
|
||||
const d = {
|
||||
numCliente: nc,
|
||||
nombre: toStr(r[1]) || 'SIN NOMBRE',
|
||||
apellidoPaterno: toStr(r[2]) || '',
|
||||
apellidoMaterno: toStr(r[3]),
|
||||
sexo: toStr(r[4]),
|
||||
telefono: toStr(r[5]),
|
||||
email: toStr(r[6]),
|
||||
estadoCivil: toStr(r[7]),
|
||||
rangoEdad: toStr(r[8]),
|
||||
ocupacion: toStr(r[16]),
|
||||
rangoIngresos: toStr(r[15]),
|
||||
credito: toStr(r[10]),
|
||||
institucion: toStr(r[11]),
|
||||
medioContacto: toStr(r[13]),
|
||||
canal: toStr(r[14]),
|
||||
desarrollo: toStr(r[19]),
|
||||
manzana: toStr(r[20]),
|
||||
lote: toStr(r[21]),
|
||||
interior: toStr(r[22]),
|
||||
tipoCasa: toStr(r[24]),
|
||||
etapa: toStr(r[27]),
|
||||
valorTotal: toNum(r[26]),
|
||||
estatus: toStr(r[27]),
|
||||
fechaLiberacion: r[28] instanceof Date ? r[28] : null,
|
||||
vendedor: toStr(r[29]),
|
||||
vendedorId: toStr(r[30]),
|
||||
}
|
||||
try {
|
||||
await prisma.clienteMaestro.upsert({ where: { numCliente: nc }, update: d, create: d })
|
||||
ok++
|
||||
} catch (e) {
|
||||
fail++
|
||||
if (fail <= 5) console.log(`ERR ${nc}: ${e.message.slice(0, 120)}`)
|
||||
}
|
||||
if (idx % 500 === 0) console.log(` ${idx}/${map.size} ok=${ok} fail=${fail}`)
|
||||
}
|
||||
console.log(`\nListo: ${ok} ok, ${fail} fail / ${map.size}`)
|
||||
await prisma.$disconnect()
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Importa el archivo maestro de estatus de venta (Reporte_EstatusVenta.xlsx)
|
||||
* Importa el archivo maestro de estatus de venta (/root/Reporte_EstatusVenta.xlsx)
|
||||
* a la tabla CapturaDepartamento.
|
||||
*
|
||||
* Uso:
|
||||
@@ -152,7 +152,7 @@ function detectarDesarrollo(nombreHoja) {
|
||||
// ── Main ─────────────────────────────────────────────────────────────
|
||||
|
||||
async function main() {
|
||||
const rutaExcel = process.argv[2] || join(__dirname, '..', '..', 'Reporte_EstatusVenta.xlsx')
|
||||
const rutaExcel = process.argv[2] || '/root/Reporte_EstatusVenta.xlsx'
|
||||
const usuarioId = process.argv[3] || 'importacion-maestro'
|
||||
|
||||
console.log(`Leyendo archivo: ${rutaExcel}`)
|
||||
@@ -203,12 +203,19 @@ async function main() {
|
||||
let hojaActualizados = 0
|
||||
|
||||
for (const fila of filasDatos) {
|
||||
// Detectar unidad: primera columna con valor
|
||||
const unidadCodigo = String(fila[0] ?? '').trim()
|
||||
// Detectar unidad: columna 1 (Depto) o combinación UBICACIÓN+MZA+LOTE
|
||||
let unidadCodigo = String(fila[1] ?? '').trim() // columna 1 = Depto
|
||||
if (!unidadCodigo) {
|
||||
// Fallback: combinar UBICACIÓN (col 10) + MZA (col 11) + LOTE (col 12)
|
||||
const ubic = String(fila[10] ?? '').trim()
|
||||
const mza = String(fila[11] ?? '').trim()
|
||||
const lote = String(fila[12] ?? '').trim()
|
||||
if (ubic || mza || lote) unidadCodigo = [ubic, mza, lote].filter(Boolean).join('-')
|
||||
}
|
||||
if (!unidadCodigo) continue
|
||||
|
||||
// Skip rows that are clearly headers/section markers
|
||||
if (normalizeHeader(unidadCodigo).includes('TOTAL')) continue
|
||||
if (normalizeHeader(unidadCodigo).includes('TOTAL') || normalizeHeader(unidadCodigo).includes('DDI')) continue
|
||||
|
||||
for (let col = 0; col < headers.length; col++) {
|
||||
const mapeo = mapaColumnas[col]
|
||||
|
||||
Reference in New Issue
Block a user