import React, { useState, useMemo, useEffect, useRef } from 'react'; import ReactDOM from 'react-dom/client'; import { LayoutDashboard, Receipt, TrendingUp, Users, Tags, FileBarChart, Settings, Menu, X, Plus, Edit2, Trash2, Download, Search, Calculator, FileText, Upload, Database, AlertTriangle, Check, ArrowDownLeft, Phone, Mail, MapPin, Calendar, LogOut, Hash, CheckSquare, ShieldCheck, PieChart as PieChartIcon } from 'lucide-react'; import { PieChart, Pie, Cell, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip as RechartsTooltip, ResponsiveContainer } from 'recharts'; const COLOR_PALETTE = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042', '#8B5CF6', '#EC4899', '#10B981', '#F43F5E', '#6366F1', '#14B8A6']; // ============================================================================ // Persistencia: Caja Menor guarda todo en Supabase (proyecto ecocosecha-finanzas, // mismo que usa el módulo Finanzas — ver window.SB en supabase-client.js), en // vez de estado solo-en-memoria. Las tablas son compartidas entre TODOS los // usuarios autenticados (no aisladas por user_id), porque es un libro contable // de la empresa, no datos personales de cada quién — ver migración // "caja_menor_schema". Estos mapeadores traducen entre el shape camelCase que // usa toda la UI (sin tocarla) y las columnas snake_case reales de Postgres. // ============================================================================ const catFromDb = (r) => ({ id: r.id, name: r.name, color: r.color, description: r.description || '' }); const catToDb = (c) => ({ name: c.name, color: c.color || '#0088FE', description: c.description || null }); const provFromDb = (r) => ({ id: r.id, name: r.name, document: r.document || '', address: r.address || '', phone: r.phone || '', email: r.email || '', categoryId: r.category_id || '' }); const provToDb = (p) => ({ name: p.name, document: p.document || null, address: p.address || null, phone: p.phone || null, email: p.email || null, category_id: p.categoryId || null }); const periodFromDb = (r) => ({ id: r.id, month: r.month, startDate: r.start_date, endDate: r.end_date, responsibleName: r.responsible_name, responsibleDocument: r.responsible_document, baseFund: Number(r.base_fund) || 0, initialBalance: Number(r.initial_balance) || 0, status: r.status || { elaboro: false, reviso: false, contabilizo: false, aprobo: false }, isClosed: !!r.is_closed, initialIncomeId: r.initial_income_id || '', physicalCount: (r.physical_count !== null && r.physical_count !== undefined) ? Number(r.physical_count) : null, physicalCountDate: r.physical_count_date || '' }); const periodToDb = (p) => ({ month: p.month, start_date: p.startDate, end_date: p.endDate, responsible_name: p.responsibleName || '', responsible_document: p.responsibleDocument || '', base_fund: Number(p.baseFund) || 0, initial_balance: Number(p.initialBalance) || 0, status: p.status, is_closed: !!p.isClosed, initial_income_id: p.initialIncomeId || null, physical_count: (p.physicalCount !== undefined && p.physicalCount !== null && p.physicalCount !== '') ? Number(p.physicalCount) : null, physical_count_date: p.physicalCountDate || null }); const txFromDb = (r) => ({ id: r.id, periodId: r.period_id, type: r.type, date: r.date, invoiceNo: r.invoice_no || '', providerId: r.provider_id || '', categoryId: r.category_id || '', description: r.description || '', deliveredBy: r.delivered_by || '', subtotal: Number(r.subtotal) || 0, discount: Number(r.discount) || 0, iva: Number(r.iva) || 0, isTaxable: !!r.is_taxable, reteFte: Number(r.rete_fte) || 0, reteIca: Number(r.rete_ica) || 0, amountPaid: Number(r.amount_paid) || 0, valeNo: r.vale_no || '' }); const txToDb = (t) => ({ period_id: t.periodId, type: t.type, date: t.date, invoice_no: t.invoiceNo || '', provider_id: t.providerId || null, category_id: t.categoryId || null, description: t.description || '', delivered_by: t.deliveredBy || null, subtotal: Number(t.subtotal) || 0, discount: Number(t.discount) || 0, iva: Number(t.iva) || 0, is_taxable: !!t.isTaxable, rete_fte: Number(t.reteFte) || 0, rete_ica: Number(t.reteIca) || 0, amount_paid: Number(t.amountPaid) || 0, vale_no: t.valeNo || null }); const formatCurrency = (value) => { return new Intl.NumberFormat('es-CO', { style: 'currency', currency: 'COP', minimumFractionDigits: 0 }).format(value || 0); }; // Carga perezosa de SheetJS (window.XLSX) — se comparte entre el reporte de Excel // y la carga/plantilla masiva de terceros, para no repetir el