import fs from 'fs'; import path from 'path'; import crypto from 'crypto'; import { EstadoFacturacion } from '../models/CierreFacturacionMensual'; export interface LocalCierreRecord { id: string; // UUID conductor_id: string; nit_o_cedula: string; razon_social: string; correo_electronico: string; mes_calendario: number; anio_calendario: number; total_comisiones_acumuladas: number; total_viajes_realizados: number; estado_facturacion: EstadoFacturacion; fecha_cierre: string; // ISO string numero_factura?: string; cufe?: string; pdf_url?: string; createdAt: string; updatedAt: string; } const DATA_DIR = path.resolve(process.cwd(), 'data'); const STORE_PATH = path.join(DATA_DIR, 'pacifictrans_cierres_facturacion.json'); export class LocalBillingStore { private static instance: LocalBillingStore; private recordsMap = new Map(); // key: `${conductor_id}_${mes}_${anio}` private initialized = false; private constructor() { this.ensureInitialized(); } public static getInstance(): LocalBillingStore { if (!LocalBillingStore.instance) { LocalBillingStore.instance = new LocalBillingStore(); } return LocalBillingStore.instance; } private ensureInitialized() { if (this.initialized) return; try { if (!fs.existsSync(DATA_DIR)) { fs.mkdirSync(DATA_DIR, { recursive: true }); } if (fs.existsSync(STORE_PATH)) { const raw = fs.readFileSync(STORE_PATH, 'utf-8'); const list: LocalCierreRecord[] = JSON.parse(raw || '[]'); this.recordsMap.clear(); for (const r of list) { const key = `${r.conductor_id}_${r.mes_calendario}_${r.anio_calendario}`; this.recordsMap.set(key, r); } } this.initialized = true; } catch (err) { console.warn('⚠️ [LocalBillingStore] Fallo al cargar cierres locales:', err); this.initialized = true; } } private persist() { try { if (!fs.existsSync(DATA_DIR)) { fs.mkdirSync(DATA_DIR, { recursive: true }); } const list = Array.from(this.recordsMap.values()); fs.writeFileSync(STORE_PATH, JSON.stringify(list, null, 2), 'utf-8'); } catch (err) { console.error('❌ [LocalBillingStore] Error al guardar cierres locales:', err); } } public upsertCierre(data: { id?: string; conductor_id: string; nit_o_cedula: string; razon_social: string; correo_electronico: string; mes_calendario: number; anio_calendario: number; total_comisiones_acumuladas: number; total_viajes_realizados: number; estado_facturacion?: EstadoFacturacion; fecha_cierre?: Date | string; }): LocalCierreRecord { this.ensureInitialized(); const key = `${data.conductor_id}_${data.mes_calendario}_${data.anio_calendario}`; const existing = this.recordsMap.get(key); const now = new Date().toISOString(); const record: LocalCierreRecord = { id: existing?.id || data.id || crypto.randomUUID(), conductor_id: data.conductor_id, nit_o_cedula: data.nit_o_cedula || existing?.nit_o_cedula || 'NO-REGISTRADO', razon_social: data.razon_social || existing?.razon_social || 'Conductor Pacific Trans', correo_electronico: data.correo_electronico || existing?.correo_electronico || 'info@pacifictrans.com', mes_calendario: Number(data.mes_calendario), anio_calendario: Number(data.anio_calendario), total_comisiones_acumuladas: Number(data.total_comisiones_acumuladas || 0), total_viajes_realizados: Number(data.total_viajes_realizados || 0), estado_facturacion: data.estado_facturacion || existing?.estado_facturacion || 'pendiente_proceso', fecha_cierre: data.fecha_cierre ? new Date(data.fecha_cierre).toISOString() : existing?.fecha_cierre || now, createdAt: existing?.createdAt || now, updatedAt: now, }; this.recordsMap.set(key, record); this.persist(); return record; } public findByMesAnio(mes: number, anio: number): LocalCierreRecord[] { this.ensureInitialized(); const results: LocalCierreRecord[] = []; for (const r of this.recordsMap.values()) { if (Number(r.mes_calendario) === Number(mes) && Number(r.anio_calendario) === Number(anio)) { results.push({ ...r }); } } return results.sort((a, b) => b.total_comisiones_acumuladas - a.total_comisiones_acumuladas); } public findById(id: string): LocalCierreRecord | null { this.ensureInitialized(); for (const r of this.recordsMap.values()) { if (r.id === id) return { ...r }; } return null; } public marcarComoFacturados(ids: string[]): { updatedCount: number; records: LocalCierreRecord[] } { this.ensureInitialized(); const updated: LocalCierreRecord[] = []; const idSet = new Set(ids); for (const [key, r] of this.recordsMap.entries()) { if (idSet.has(r.id)) { const mod: LocalCierreRecord = { ...r, estado_facturacion: 'facturado_manual', updatedAt: new Date().toISOString(), }; this.recordsMap.set(key, mod); updated.push(mod); } } if (updated.length > 0) { this.persist(); } return { updatedCount: updated.length, records: updated }; } public actualizarFacturaDian( id: string, info: { numero_factura: string; cufe: string; pdf_url: string } ): LocalCierreRecord | null { this.ensureInitialized(); for (const [key, r] of this.recordsMap.entries()) { if (r.id === id) { const mod: LocalCierreRecord = { ...r, estado_facturacion: 'enviado_api' as any, numero_factura: info.numero_factura, cufe: info.cufe, pdf_url: info.pdf_url, updatedAt: new Date().toISOString(), }; this.recordsMap.set(key, mod); this.persist(); return mod; } } return null; } public getAll(): LocalCierreRecord[] { this.ensureInitialized(); return Array.from(this.recordsMap.values()); } } export const localBillingStore = LocalBillingStore.getInstance();