diff --git a/pacifictrans-app/public/index.html b/pacifictrans-app/public/index.html
index 96bd1a5..7f829e6 100644
--- a/pacifictrans-app/public/index.html
+++ b/pacifictrans-app/public/index.html
@@ -1494,6 +1494,9 @@
+
+ 🏛️ Facturación Electrónica Oficial
+
@@ -1591,11 +1594,12 @@
- |
+ |
Cargando información fiscal...
|
diff --git a/pacifictrans-app/public/js/app.js b/pacifictrans-app/public/js/app.js
index 7a4eeda..f536b25 100644
--- a/pacifictrans-app/public/js/app.js
+++ b/pacifictrans-app/public/js/app.js
@@ -5218,11 +5218,63 @@ function renderMonthlyBillingTable(cierres, mes, anio) {
${fechaCierreFmt}
|
+
+ ${isApi || c.numero_factura ? `
+
+ ` : `
+
+ `}
+ |
`;
}).join('');
attachBillingItemCheckboxListeners();
+ attachBillingActionListeners();
+}
+
+function attachBillingActionListeners() {
+ const emitBtns = document.querySelectorAll('.btn-emit-invoice-dian');
+ emitBtns.forEach(btn => {
+ btn.addEventListener('click', async (e) => {
+ e.stopPropagation();
+ const id = btn.getAttribute('data-id');
+ if (!confirm('¿Deseas emitir la Factura Electrónica oficial ante la DIAN para este cierre?')) {
+ return;
+ }
+ btn.disabled = true;
+ btn.textContent = 'Emitiendo...';
+ try {
+ const res = await fetch('/api/admin/billing/emitir-factura', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ idCierre: id })
+ });
+ const data = await res.json();
+ if (res.ok && data.success) {
+ alert(`✅ Factura ${data.factura.fullNumber} emitida con éxito ante la DIAN.`);
+ window.fetchMonthlyBillingReport();
+ } else {
+ alert(`Error al emitir factura: ${data.error || 'Fallo de emisión'}`);
+ btn.disabled = false;
+ btn.textContent = '⚡ Facturar';
+ }
+ } catch (err) {
+ alert('Error de conexión con el servidor fiscal.');
+ btn.disabled = false;
+ btn.textContent = '⚡ Facturar';
+ }
+ });
+ });
}
function attachBillingItemCheckboxListeners() {
diff --git a/transporte-cali-buenaventura/backend/public/index.html b/transporte-cali-buenaventura/backend/public/index.html
index 96bd1a5..7f829e6 100644
--- a/transporte-cali-buenaventura/backend/public/index.html
+++ b/transporte-cali-buenaventura/backend/public/index.html
@@ -1494,6 +1494,9 @@
+
+ 🏛️ Facturación Electrónica Oficial
+
@@ -1591,11 +1594,12 @@
Cantidad Viajes |
Estado Facturación |
Fecha Cierre |
+ Acción DIAN |
- |
+ |
Cargando información fiscal...
|
diff --git a/transporte-cali-buenaventura/backend/public/js/app.js b/transporte-cali-buenaventura/backend/public/js/app.js
index 7a4eeda..f536b25 100644
--- a/transporte-cali-buenaventura/backend/public/js/app.js
+++ b/transporte-cali-buenaventura/backend/public/js/app.js
@@ -5218,11 +5218,63 @@ function renderMonthlyBillingTable(cierres, mes, anio) {
${fechaCierreFmt}
|
+
+ ${isApi || c.numero_factura ? `
+
+ ` : `
+
+ `}
+ |
`;
}).join('');
attachBillingItemCheckboxListeners();
+ attachBillingActionListeners();
+}
+
+function attachBillingActionListeners() {
+ const emitBtns = document.querySelectorAll('.btn-emit-invoice-dian');
+ emitBtns.forEach(btn => {
+ btn.addEventListener('click', async (e) => {
+ e.stopPropagation();
+ const id = btn.getAttribute('data-id');
+ if (!confirm('¿Deseas emitir la Factura Electrónica oficial ante la DIAN para este cierre?')) {
+ return;
+ }
+ btn.disabled = true;
+ btn.textContent = 'Emitiendo...';
+ try {
+ const res = await fetch('/api/admin/billing/emitir-factura', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ idCierre: id })
+ });
+ const data = await res.json();
+ if (res.ok && data.success) {
+ alert(`✅ Factura ${data.factura.fullNumber} emitida con éxito ante la DIAN.`);
+ window.fetchMonthlyBillingReport();
+ } else {
+ alert(`Error al emitir factura: ${data.error || 'Fallo de emisión'}`);
+ btn.disabled = false;
+ btn.textContent = '⚡ Facturar';
+ }
+ } catch (err) {
+ alert('Error de conexión con el servidor fiscal.');
+ btn.disabled = false;
+ btn.textContent = '⚡ Facturar';
+ }
+ });
+ });
}
function attachBillingItemCheckboxListeners() {
diff --git a/transporte-cali-buenaventura/backend/src/index.ts b/transporte-cali-buenaventura/backend/src/index.ts
index 46d1913..1a5a3e2 100644
--- a/transporte-cali-buenaventura/backend/src/index.ts
+++ b/transporte-cali-buenaventura/backend/src/index.ts
@@ -660,6 +660,22 @@ const server = http.createServer(async (req, res) => {
return sendJson(200, resultado);
}
+ // 13.5.4. Emitir Factura Electrónica oficial DIAN para un cierre
+ if (pathname === '/api/admin/billing/emitir-factura' && req.method === 'POST') {
+ const body = await getJsonBody();
+ const idCierre = body.idCierre || body.id;
+ if (!idCierre) {
+ return sendJson(400, { success: false, error: 'idCierre es requerido' });
+ }
+
+ try {
+ const resultado = await billingConsolidationService.emitirFacturaDian(idCierre);
+ return sendJson(200, resultado);
+ } catch (err: any) {
+ return sendJson(500, { success: false, error: err.message || 'Error al emitir factura DIAN' });
+ }
+ }
+
// 14. Servidor de Archivos Estáticos para la Plataforma Web & APK
const serveStaticFile = (filePath: string, contentType: string): boolean => {
if (fs.existsSync(filePath)) {
diff --git a/transporte-cali-buenaventura/backend/src/services/auth.service.ts b/transporte-cali-buenaventura/backend/src/services/auth.service.ts
index c7d78b0..a3c17fa 100644
--- a/transporte-cali-buenaventura/backend/src/services/auth.service.ts
+++ b/transporte-cali-buenaventura/backend/src/services/auth.service.ts
@@ -171,8 +171,9 @@ export class AuthService {
user = (await Promise.race([dbQuery, timeoutPromise])) as any;
if (!user) {
+ const defaultSuperId = cleanPhone === SUPER_ADMIN_PHONE ? 'usr-superadmin-platform' : `usr-superadmin-${cleanPhone}`;
const createPromise = User.create({
- appwriteId: isSuper ? 'usr-superadmin-platform' : `pt-${Date.now()}-${Math.random().toString(36).substr(2, 6)}`,
+ appwriteId: isSuper ? defaultSuperId : `pt-${Date.now()}-${Math.random().toString(36).substr(2, 6)}`,
nombre: isSuper ? (params.nombre || 'Superadministrador Principal') : (params.nombre || `Usuario ${cleanPhone.slice(-4)}`),
telefono: cleanPhone,
email: userEmail || (isSuper ? SUPER_ADMIN_EMAIL : undefined),
diff --git a/transporte-cali-buenaventura/backend/src/services/billing-consolidation.service.ts b/transporte-cali-buenaventura/backend/src/services/billing-consolidation.service.ts
index c6b816c..3e32a26 100644
--- a/transporte-cali-buenaventura/backend/src/services/billing-consolidation.service.ts
+++ b/transporte-cali-buenaventura/backend/src/services/billing-consolidation.service.ts
@@ -425,6 +425,116 @@ export class BillingConsolidationService {
ids: uniqueIds,
};
}
+
+ /**
+ * Emite la Factura Electrónica oficial ante la DIAN comunicándose con el Hub Central de Facturación.
+ */
+ public async emitirFacturaDian(idCierre: string): Promise {
+ const HUB_URL = process.env.FACTURACION_HUB_URL || 'https://facturacion.almeira.com.co';
+ const TENANT_ID = process.env.PACIFICTRANS_TENANT_ID || 'd0000002-0000-0000-0000-000000000002';
+ const API_KEY = process.env.PACIFICTRANS_FISCAL_API_KEY || 'pt_live_key_33c9a184ef10';
+
+ let cierre: any = null;
+ if (isDatabaseConnected()) {
+ try {
+ cierre = await CierreFacturacionMensual.findOne({ id: idCierre });
+ } catch (err) {}
+ }
+ if (!cierre) {
+ cierre = localBillingStore.findById(idCierre);
+ }
+
+ if (!cierre) {
+ throw new Error(`Cierre no encontrado con ID ${idCierre}`);
+ }
+
+ const total = Number(cierre.total_comisiones_acumuladas || 0);
+ const subtotal = Math.round(total / 1.19);
+
+ const payload = {
+ tenantId: TENANT_ID,
+ prefix: 'PACT',
+ customer: {
+ identificationType: '13',
+ identificationNumber: String(cierre.nit_o_cedula || '222222222222').replace(/\D/g, '').slice(-10) || '2222222222',
+ legalOrFullName: cierre.razon_social || 'Conductor Pacific Trans',
+ email: cierre.correo_electronico || 'facturacion@pacifictrans.com',
+ phone: '3167455877',
+ address: 'Terminal de Transportes Cali / Buenaventura',
+ cityName: 'Buenaventura',
+ departmentCode: '76',
+ cityCode: '76109',
+ taxRegime: '49',
+ },
+ items: [
+ {
+ sku: 'SERV-COM-TRANS',
+ description: `Comisión de intermediación de software y despacho de viajes - Período ${cierre.mes_calendario || cierre.mes}/${cierre.anio_calendario || cierre.anio} (${cierre.total_viajes_realizados} viajes)`,
+ quantity: 1,
+ unitPrice: subtotal,
+ taxPercentage: 19,
+ taxTypeCode: '01',
+ discountAmount: 0,
+ },
+ ],
+ paymentMethod: {
+ paymentMeansCode: '48',
+ paymentDueDate: new Date().toISOString().split('T')[0],
+ },
+ notes: `Liquidación mensual de comisiones ID: ${cierre.id}`,
+ };
+
+ const res = await fetch(`${HUB_URL}/api/v1/invoices`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'x-api-key': API_KEY,
+ },
+ body: JSON.stringify(payload),
+ });
+
+ const data = await res.json();
+ if (!res.ok || !data.success) {
+ throw new Error(data.message || 'Error al emitir factura en el Hub Fiscal');
+ }
+
+ const factura = data.data;
+
+ localBillingStore.actualizarFacturaDian(idCierre, {
+ numero_factura: factura.fullNumber,
+ cufe: factura.cufe,
+ pdf_url: `${HUB_URL}/api/v1/invoices/${factura.id}/pdf`,
+ });
+
+ if (isDatabaseConnected()) {
+ try {
+ await CierreFacturacionMensual.updateOne(
+ { id: idCierre },
+ {
+ $set: {
+ estado_facturacion: 'enviado_api',
+ numero_factura: factura.fullNumber,
+ cufe: factura.cufe,
+ pdf_url: `${HUB_URL}/api/v1/invoices/${factura.id}/pdf`,
+ },
+ }
+ );
+ } catch (e) {}
+ }
+
+ return {
+ success: true,
+ cierreId: idCierre,
+ factura: {
+ id: factura.id,
+ fullNumber: factura.fullNumber,
+ cufe: factura.cufe,
+ pdfUrl: `${HUB_URL}/api/v1/invoices/${factura.id}/pdf`,
+ xmlUrl: `${HUB_URL}/api/v1/invoices/${factura.id}/xml`,
+ },
+ };
+ }
}
export const billingConsolidationService = BillingConsolidationService.getInstance();
+
diff --git a/transporte-cali-buenaventura/backend/src/services/local-billing-store.ts b/transporte-cali-buenaventura/backend/src/services/local-billing-store.ts
index c8d3e3e..adcd09e 100644
--- a/transporte-cali-buenaventura/backend/src/services/local-billing-store.ts
+++ b/transporte-cali-buenaventura/backend/src/services/local-billing-store.ts
@@ -15,6 +15,9 @@ export interface LocalCierreRecord {
total_viajes_realizados: number;
estado_facturacion: EstadoFacturacion;
fecha_cierre: string; // ISO string
+ numero_factura?: string;
+ cufe?: string;
+ pdf_url?: string;
createdAt: string;
updatedAt: string;
}
@@ -158,6 +161,29 @@ export class LocalBillingStore {
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());
diff --git a/transporte-cali-buenaventura/backend/src/services/local-user-store.ts b/transporte-cali-buenaventura/backend/src/services/local-user-store.ts
index 2f359f4..bee6e8d 100644
--- a/transporte-cali-buenaventura/backend/src/services/local-user-store.ts
+++ b/transporte-cali-buenaventura/backend/src/services/local-user-store.ts
@@ -4,15 +4,22 @@ import { UserRole } from '../models/User';
export const SUPER_ADMIN_PHONE = '573167455877';
export const SUPER_ADMIN_LOCAL_PHONE = '3167455877';
+export const SUPER_ADMIN_ALT_PHONE = '573106390296';
+export const SUPER_ADMIN_ALT_LOCAL_PHONE = '3106390296';
export const SUPER_ADMIN_EMAIL = 'admin@pacifictrans.almeira.com.co';
export function isPlatformSuperAdmin(identifier?: string): boolean {
if (!identifier) return false;
const clean = identifier.replace(/\D/g, '');
+ const lower = identifier.toLowerCase().trim();
return (
clean === SUPER_ADMIN_PHONE ||
clean === SUPER_ADMIN_LOCAL_PHONE ||
- identifier.toLowerCase().trim() === SUPER_ADMIN_EMAIL
+ clean === SUPER_ADMIN_ALT_PHONE ||
+ clean === SUPER_ADMIN_ALT_LOCAL_PHONE ||
+ lower === SUPER_ADMIN_EMAIL ||
+ lower === 'carlos@almeira.com.co' ||
+ lower === 'innovacion4desarrollo@gmail.com'
);
}
@@ -162,7 +169,8 @@ class LocalUserStore {
const isSuper = isPlatformSuperAdmin(cleanPhone) || (user.email && isPlatformSuperAdmin(user.email));
const existing = this.findByPhone(cleanPhone) || (user.id ? this.findById(user.id) : null);
- const userId = existing?.id || user.id || (isSuper ? 'usr-superadmin-platform' : `pt-${Date.now()}-${Math.random().toString(36).substr(2, 6)}`);
+ const defaultSuperId = (cleanPhone === SUPER_ADMIN_PHONE || cleanPhone === SUPER_ADMIN_LOCAL_PHONE) ? 'usr-superadmin-platform' : `usr-superadmin-${cleanPhone}`;
+ const userId = existing?.id || user.id || (isSuper ? defaultSuperId : `pt-${Date.now()}-${Math.random().toString(36).substr(2, 6)}`);
const nowIso = new Date().toISOString();
const record: LocalUserRecord = {