import express from 'express'; import multer from 'multer'; import path from 'path'; import fs from 'fs'; import { proxyToLaravel } from '../utils/proxy.js'; import axios from 'axios'; import FormData from 'form-data'; const router = express.Router(); console.log('✅ Loading Billing Routes (Proxy to Laravel)...'); // --- Configuración de Multer para Evidencias de Retiros --- const storage = multer.diskStorage({ destination: (req, file, cb) => { let uploadDir = path.join(process.cwd(), '../../uploads/evidencias'); // Organizar por sucursal si viene en el body para mantener orden const sucursalRaw = req.body.sucursal_nombre || 'general'; const sucursalFolder = sucursalRaw.replace(/[^a-zA-Z0-9]/g, '_').toLowerCase(); uploadDir = path.join(uploadDir, sucursalFolder); if (!fs.existsSync(uploadDir)) { fs.mkdirSync(uploadDir, { recursive: true }); } cb(null, uploadDir); }, filename: (req, file, cb) => { const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9); cb(null, `egreso-${uniqueSuffix}${path.extname(file.originalname)}`); } }); const upload = multer({ storage: storage, limits: { fileSize: 5 * 1024 * 1024 } // 5MB por foto }); // --- Configuración de Multer para Certificados Digitales (.pem) --- const certStorage = multer.diskStorage({ destination: (req, file, cb) => { let uploadDir = path.join(process.cwd(), '../../uploads/certificados'); if (file.fieldname === 'logo_path') { const ruc = req.body.ruc || 'sin_ruc'; uploadDir = path.join(process.cwd(), `../../uploads/empresas/${ruc}/logo`); } if (!fs.existsSync(uploadDir)) { fs.mkdirSync(uploadDir, { recursive: true }); } cb(null, uploadDir); }, filename: (req, file, cb) => { const ruc = req.body.ruc || 'sin_ruc'; const ext = path.extname(file.originalname); if (file.fieldname === 'certificate_file') { cb(null, `cert-${ruc}-${Date.now()}.pem`); } else { cb(null, `logo${ext || '.jpg'}`); } } }); const uploadCert = multer({ storage: certStorage, limits: { fileSize: 5 * 1024 * 1024 } // 5MB para soportar logos pesados }); // --- Pagos --- router.post('/Pago/Crear', (req, res) => { proxyToLaravel(req, res, '/Billing/Pago/Crear', 'post'); }); router.post('/Pago/POS', (req, res) => { proxyToLaravel(req, res, '/Billing/Pago/POS', 'post'); }); router.post('/Pago/Efectivo', (req, res) => { proxyToLaravel(req, res, '/Billing/Pago/Efectivo', 'post'); }); router.post('/Pago/RegistrarYFacturar', (req, res) => { proxyToLaravel(req, res, '/Billing/Pago/RegistrarYFacturar', 'post'); }); router.post('/Pago/SolicitarEfectivo', (req, res) => { proxyToLaravel(req, res, '/Billing/Pago/SolicitarEfectivo', 'post'); }); router.get('/Pago/:id', (req, res) => { proxyToLaravel(req, res, `/Billing/Pago/${req.params.id}`); }); router.post('/Pago/Webhook', (req, res) => { proxyToLaravel(req, res, '/Billing/Pago/Webhook', 'post'); }); // --- Facturación Electrónica SUNAT (Nueva Integración) --- router.post('/Comprobante/Emitir', (req, res) => { proxyToLaravel(req, res, '/Billing/Comprobante/Emitir', 'post'); }); router.post('/Comprobante/Manual', (req, res) => { proxyToLaravel(req, res, '/Billing/Comprobante/Manual', 'post'); }); router.get('/Comprobante/CheckConfig/:id', (req, res) => { proxyToLaravel(req, res, `/Billing/Comprobante/CheckConfig/${req.params.id}`); }); router.post('/Comprobante/:id/Transmitir', (req, res) => { proxyToLaravel(req, res, `/Billing/Comprobante/${req.params.id}/Transmitir`, 'post'); }); router.post('/VentaExpress', (req, res) => { proxyToLaravel(req, res, '/Billing/VentaExpress', 'post'); }); router.get('/Comprobante/:id/PDF', (req, res) => { req.query.format = req.query.format || '80mm'; proxyToLaravel(req, res, `/Billing/Comprobante/${req.params.id}/PDF`); }); router.get('/Comprobante/:id/XML', (req, res) => { proxyToLaravel(req, res, `/Billing/Comprobante/${req.params.id}/XML`); }); router.get('/Comprobante/:id/CDR', (req, res) => { proxyToLaravel(req, res, `/Billing/Comprobante/${req.params.id}/CDR`); }); router.get('/Comprobantes', (req, res) => { proxyToLaravel(req, res, '/Billing/Comprobantes'); }); router.get('/Ticket/Generar', (req, res) => { const { orden_id, format, tipo } = req.query; proxyToLaravel(req, res, `/Billing/Ticket/Generar?orden_id=${orden_id}&format=${format || '80mm'}&tipo=${tipo || 'nota-venta'}`); }); const BILLING_API = process.env.BILLING_API || 'https://www.piterpro.com/facturacion/api'; // --- Configuración Fiscal --- router.post(['/Configuracion', '/configuracion'], uploadCert.fields([{ name: 'certificate_file', maxCount: 1 }, { name: 'logo_path', maxCount: 1 }]), async (req, res) => { try { const certificateFile = req.files && req.files['certificate_file'] ? req.files['certificate_file'][0] : null; const logoFile = req.files && req.files['logo_path'] ? req.files['logo_path'][0] : null; const form = new FormData(); form.append('environment', req.body.environment || 'beta'); if (req.body.certificate_password && req.body.certificate_password.trim() !== '') { form.append('certificate_password', req.body.certificate_password); } form.append('ruc', req.body.ruc || ''); form.append('razon_social', req.body.razon_social || ''); form.append('nombre_comercial', req.body.nombre_comercial || ''); form.append('direccion', req.body.direccion || ''); form.append('ubigeo', req.body.ubigeo || ''); form.append('distrito', req.body.distrito || ''); form.append('provincia', req.body.provincia || ''); form.append('departamento', req.body.departamento || ''); form.append('usuario_sol', req.body.usuario_sol || ''); if (req.body.clave_sol && req.body.clave_sol.trim() !== '') { form.append('clave_sol', req.body.clave_sol); } if (certificateFile) { form.append('certificate_file', fs.createReadStream(certificateFile.path)); } if (logoFile) { form.append('logo_path', fs.createReadStream(logoFile.path)); } const config = { headers: { ...form.getHeaders(), Authorization: req.headers?.authorization || '', } }; const response = await axios.post(`${BILLING_API}/setup/configure-sunat`, form, config); return res.status(response.status).json(response.data); } catch (error) { console.error("❌ Error en proxy /Configuracion:", error.message); console.error("🔍 RESPUESTA DETALLADA DE LARAVEL (500):", error.response?.data); res.status(error.response?.status || 500).json(error.response?.data || { success: false, message: error.message }); } }); router.get(['/Configuracion', '/configuracion'], (req, res) => { const { ruc, company_id } = req.query; let url = '/setup/get-config'; if (ruc) url += `?ruc=${ruc}`; else if (company_id) url += `?company_id=${company_id}`; proxyToLaravel(req, res, url, 'get', BILLING_API); }); // --- Ubigeos --- router.get(['/Ubigeos/:id', '/ubigeos/:id'], (req, res) => { const { id } = req.params; proxyToLaravel(req, res, `/ubigeos/${id}`, 'get', BILLING_API); }); // --- Caja Diaria --- // Iniciar Caja router.post('/Caja/Iniciar', (req, res) => { proxyToLaravel(req, res, '/Billing/Caja/Iniciar', 'post'); }); // Finalizar Caja router.post('/Caja/Finalizar', (req, res) => { proxyToLaravel(req, res, '/Billing/Caja/Finalizar', 'post'); }); // Historial de Caja (Eventos de apertura/cierre) router.get('/Caja/Historial/:id', (req, res) => { proxyToLaravel(req, res, `/Billing/Caja/Historial/${req.params.id}`); }); import { getPool, sql } from '../config/database.js'; // Tickets de la Caja (Ventas/Órdenes asociadas a esta caja) router.get('/Caja/Tickets/:id', async (req, res) => { try { const pool = await getPool(); const result = await pool.request() .input('cajaId', sql.UniqueIdentifier, req.params.id) .query(` SELECT o.id_ordenes as pedido_id, o.id_ordenes as id, o.monto_total as monto, o.monto_total as total_pagado, o.creado_at as created_at, 'GENERAL' as mesa, 'TICKET' as tipo_comprobante FROM Ventas_Ordenes o WITH (NOLOCK) JOIN Pagos_Ingresos i WITH (NOLOCK) ON o.id_ordenes = i.orden_id WHERE i.caja_diaria_id = @cajaId AND o.estado NOT IN ('CANCELADA', 'ANULADA', 'CANCELADO', 'ANULADO') ORDER BY o.creado_at DESC `); res.json({ success: true, data: result.recordset }); } catch (e) { console.error("Error fetching tickets for caja:", e); res.status(500).json({ success: false, message: e.message }); } }); // Tickets de la Sucursal (Ventas sin comprobante para Dashboard) router.get('/Tickets/Sucursal/:id', async (req, res) => { try { const { fecha_inicio, fecha_fin } = req.query; let dateFilter = ''; if (fecha_inicio && fecha_fin) { dateFilter = 'AND o.creado_at >= @fecha_inicio AND o.creado_at <= @fecha_fin'; } const pool = await getPool(); const request = pool.request() .input('sucursalId', sql.UniqueIdentifier, req.params.id); if (fecha_inicio && fecha_fin) { request.input('fecha_inicio', sql.DateTime, new Date(fecha_inicio + 'T00:00:00')) .input('fecha_fin', sql.DateTime, new Date(fecha_fin + 'T23:59:59.999')); } const result = await request.query(` SELECT TOP 100 o.id_ordenes as id, o.monto_total as total, o.creado_at as fecha_emision, s.nombre as cliente, 'TICKET' as tipo, 'GENERAL' as origen, 'ACEPTADO' as estado FROM Ventas_Ordenes o WITH (NOLOCK) JOIN Nucleo_Sucursales s WITH (NOLOCK) ON o.sucursal_id = s.id_sucursal LEFT JOIN Facturacion_Comprobantes c WITH (NOLOCK) ON o.id_ordenes = c.orden_id WHERE o.sucursal_id = @sucursalId AND c.id IS NULL -- Solo tickets que NO tienen comprobante electrónico AND o.estado NOT IN ('CANCELADA', 'ANULADA', 'CANCELADO', 'ANULADO') ${dateFilter} ORDER BY o.creado_at DESC `); res.json({ success: true, data: result.recordset }); } catch (e) { console.error("Error fetching tickets for sucursal:", e); res.status(500).json({ success: false, message: e.message }); } }); // Registrar Egreso router.post('/Caja/Egreso', upload.array('fotos', 10), (req, res) => { // Si hay archivos, construir las URLs relativas if (req.files && req.files.length > 0) { const sucursalRaw = req.body.sucursal_nombre || 'general'; const sucursalFolder = sucursalRaw.replace(/[^a-zA-Z0-9]/g, '_').toLowerCase(); req.body.fotos_urls = req.files.map(file => { return `evidencias/${sucursalFolder}/${file.filename}`; }); } // Proxy a Laravel proxyToLaravel(req, res, '/Billing/Caja/Egreso', 'post'); }); // Obtener Resumen router.get('/Caja/Resumen/:id', (req, res) => { proxyToLaravel(req, res, `/Billing/Caja/Resumen/${req.params.id}`); }); // Listar Egresos de la caja (Turno Actual) router.get('/Caja/Egresos/:id', (req, res) => { proxyToLaravel(req, res, `/Billing/Caja/Egresos/${req.params.id}`); }); // Listar Egresos por Sucursal (Historial Completo) router.get('/Caja/Egresos/Sucursal/:id', (req, res) => { proxyToLaravel(req, res, `/Billing/Caja/Egresos/Sucursal/${req.params.id}`); }); export default router;