@fullqueso/mcp-bc-gastos 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.env.example ADDED
@@ -0,0 +1,18 @@
1
+ # Business Central OAuth 2.0
2
+ BC_TENANT_ID=your-azure-tenant-id
3
+ BC_CLIENT_ID=your-azure-app-client-id
4
+ BC_CLIENT_SECRET=your-azure-app-client-secret
5
+ BC_TOKEN_URL=https://login.microsoftonline.com/YOUR_TENANT_ID/oauth2/v2.0/token
6
+ BC_SCOPE=https://api.businesscentral.dynamics.com/.default
7
+
8
+ # Business Central API
9
+ BC_API_BASE=https://api.businesscentral.dynamics.com/v2.0
10
+ BC_ENVIRONMENT=production
11
+
12
+ # Company GUIDs (from Business Central > Companies)
13
+ BC_COMPANY_FQ01=guid-for-store-fq01
14
+ BC_COMPANY_FQ28=guid-for-store-fq28
15
+ BC_COMPANY_FQ88=guid-for-store-fq88
16
+
17
+ # Optional
18
+ LOG_LEVEL=info
package/CHANGELOG.md ADDED
@@ -0,0 +1,49 @@
1
+ # CHANGELOG
2
+
3
+ ## [1.1.0] - 2026-02-19
4
+
5
+ ### Added
6
+ - Herramienta `get_expense_details` - Drill-down transaccional de gastos con filtros por categoría, cuenta, monto mínimo y límite configurable
7
+ - Herramienta `get_account_transactions` - Listado completo de transacciones por cuenta contable con running balance
8
+ - Vendor lookup automático en ambas herramientas nuevas: cada transacción incluye `vendor_name` y `vendor_number` vía cruce con Vendor Ledger Entries
9
+ - Métodos `getVendorLedgerEntries()`, `getVendors()` y `buildVendorMap()` en bc-client.js
10
+ - Método `getDetailedGLEntries()` con filtros flexibles (cuenta específica, rango, fechas, top)
11
+
12
+ ### Technical
13
+ - `buildVendorMap()` es resiliente: retorna `{}` si los endpoints de vendor no están disponibles (try/catch con logger.warn)
14
+ - GL entries y vendor map se fetchean en paralelo con `Promise.all` en ambas herramientas
15
+ - Tests 6 y 7 agregados a test-tools.js (total: 7 tools testeadas)
16
+
17
+ ---
18
+
19
+ ## [1.0.0] - 2026-02-18
20
+
21
+ ### Added
22
+ - Herramienta `get_expense_analysis` - Análisis detallado de gastos por categoría
23
+ - Herramienta `get_efficiency_ratios` - Cálculo de ratios financieros
24
+ - Herramienta `compare_stores` - Comparación entre tiendas
25
+ - Herramienta `detect_anomalies` - Detección automática de anomalías
26
+ - Herramienta `get_trends` - Análisis de tendencias históricas
27
+ - Integración con Business Central API vía OAuth 2.0
28
+ - Mapeo completo del Chart of Accounts (60000-99999, 50000-59999)
29
+ - Conversión automática VES/USD
30
+ - Benchmarking contra rangos esperados
31
+ - Insights accionables automáticos
32
+
33
+ ### Technical
34
+ - Cliente Business Central con OAuth 2.0
35
+ - ExpenseAnalyzer para análisis de gastos
36
+ - RatioCalculator para métricas financieras
37
+ - AnomalyDetector para alertas
38
+ - TrendAnalyzer para análisis histórico
39
+ - Formatter para outputs bien formateados
40
+
41
+ ---
42
+
43
+ ## [0.1.0] - 2026-02-18
44
+
45
+ ### Added
46
+ - Proyecto inicializado
47
+ - Documentación completa (CLAUDE.md, TODO.md, PLAN_SUMMARY.md)
48
+ - Estructura de proyecto definida
49
+ - Chart of Accounts mapeado
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Francisco Padilla / Full Queso
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,118 @@
1
+ # @fullqueso/mcp-bc-gastos
2
+
3
+ MCP server for analyzing operational expenses and financial ratios from Microsoft Business Central. Built for the Full Queso franchise (3 stores: FQ01 Chacao, FQ28 Marques, FQ88 Candelaria).
4
+
5
+ ## Features
6
+
7
+ - **Expense Analysis** - Detailed breakdown by 10 categories with account-level detail and benchmarks
8
+ - **Efficiency Ratios** - Expense-to-income, payroll, rent, utilities, marketing, operating margin
9
+ - **Store Comparison** - Rankings, variances, and savings opportunities across stores
10
+ - **Anomaly Detection** - Automatic alerts by severity with root-cause analysis
11
+ - **Trend Analysis** - 6-month historical trends with growth rates and seasonality
12
+ - **Expense Details** - Transaction-level drill-down with vendor information
13
+ - **Account Transactions** - Per-account ledger with running balance and vendor lookup
14
+
15
+ ## Installation
16
+
17
+ ### Claude Desktop
18
+
19
+ Add to your `claude_desktop_config.json`:
20
+
21
+ ```json
22
+ {
23
+ "mcpServers": {
24
+ "fullqueso-bc-gastos": {
25
+ "command": "npx",
26
+ "args": ["-y", "@fullqueso/mcp-bc-gastos"],
27
+ "env": {
28
+ "BC_TENANT_ID": "your-azure-tenant-id",
29
+ "BC_CLIENT_ID": "your-azure-app-client-id",
30
+ "BC_CLIENT_SECRET": "your-azure-app-client-secret",
31
+ "BC_TOKEN_URL": "https://login.microsoftonline.com/YOUR_TENANT/oauth2/v2.0/token",
32
+ "BC_SCOPE": "https://api.businesscentral.dynamics.com/.default",
33
+ "BC_API_BASE": "https://api.businesscentral.dynamics.com/v2.0",
34
+ "BC_ENVIRONMENT": "production",
35
+ "BC_COMPANY_FQ01": "company-guid-fq01",
36
+ "BC_COMPANY_FQ28": "company-guid-fq28",
37
+ "BC_COMPANY_FQ88": "company-guid-fq88"
38
+ }
39
+ }
40
+ }
41
+ }
42
+ ```
43
+
44
+ ### Local Development
45
+
46
+ ```bash
47
+ git clone https://github.com/Fullqueso/fullqueso-mcp-bc-gastos.git
48
+ cd fullqueso-mcp-bc-gastos
49
+ npm install
50
+ cp .env.example .env
51
+ # Edit .env with your credentials
52
+ npm start
53
+ ```
54
+
55
+ ## Tools
56
+
57
+ ### get_expense_analysis
58
+ Detailed expense analysis by category with benchmark comparisons.
59
+ - Parameters: `stores`, `period`, `month`, `start_date`, `end_date`
60
+
61
+ ### get_efficiency_ratios
62
+ Financial ratios: expense-to-income, payroll, rent, utilities, marketing, operating margin.
63
+ - Parameters: `stores`, `period`, `month`, `start_date`, `end_date`
64
+
65
+ ### compare_stores
66
+ Compare all stores with efficiency rankings and savings opportunities.
67
+ - Parameters: `period`, `month`, `start_date`, `end_date`
68
+
69
+ ### detect_anomalies
70
+ Detect expense anomalies with severity levels and recommended actions.
71
+ - Parameters: `stores`, `period`, `month`, `start_date`, `end_date`, `sensitivity`
72
+
73
+ ### get_trends
74
+ Historical trend analysis (up to 6 months) with growth rates and ASCII charts.
75
+ - Parameters: `store`, `months`
76
+
77
+ ### get_expense_details
78
+ Transaction-level drill-down with vendor lookup and filters.
79
+ - Parameters: `store`, `period`, `month`, `start_date`, `end_date`, `category`, `account_number`, `min_amount`, `limit`
80
+
81
+ ### get_account_transactions
82
+ Per-account ledger view with running balance and vendor information.
83
+ - Parameters: `account_number`, `store`, `start_date`, `end_date`
84
+
85
+ ## Environment Variables
86
+
87
+ | Variable | Required | Description |
88
+ |---|---|---|
89
+ | `BC_TENANT_ID` | Yes | Azure AD tenant ID |
90
+ | `BC_CLIENT_ID` | Yes | Azure AD app client ID |
91
+ | `BC_CLIENT_SECRET` | Yes | Azure AD app client secret |
92
+ | `BC_TOKEN_URL` | Yes | OAuth 2.0 token endpoint |
93
+ | `BC_SCOPE` | Yes | BC API scope |
94
+ | `BC_API_BASE` | Yes | BC API base URL |
95
+ | `BC_ENVIRONMENT` | Yes | BC environment (e.g., `production`) |
96
+ | `BC_COMPANY_FQ01` | Yes | Company GUID for store FQ01 |
97
+ | `BC_COMPANY_FQ28` | No | Company GUID for store FQ28 |
98
+ | `BC_COMPANY_FQ88` | No | Company GUID for store FQ88 |
99
+ | `LOG_LEVEL` | No | Log level: `debug`, `info`, `warn`, `error` (default: `info`) |
100
+
101
+ ## Chart of Accounts
102
+
103
+ 10 expense categories mapped to account ranges 60000-99999:
104
+
105
+ 1. **Planta Fisica** (60000-60999) - Rent, utilities
106
+ 2. **Alquiler Equipos** (61000-61999) - Equipment rental
107
+ 3. **Logistica** (62000-62999) - Vehicles, delivery
108
+ 4. **Marketing** (63000-63999) - Advertising, commissions
109
+ 5. **Administrativos** (64000-64999) - Office, software
110
+ 6. **Seguros** (65000-65999) - Insurance
111
+ 7. **Bancarios** (67000-67999) - Banking fees, interest
112
+ 8. **Servicios Contratados** (68000-68999) - Contracted services
113
+ 9. **Nomina** (70000-74999) - Payroll, benefits
114
+ 10. **Otros** (80000-99999) - Depreciation, other
115
+
116
+ ## License
117
+
118
+ MIT
@@ -0,0 +1,135 @@
1
+ // Industry Benchmarks for Quick-Service Restaurant / Deli
2
+ // Ranges and optimal percentages (as % of total income)
3
+
4
+ export const BENCHMARKS = {
5
+ planta_fisica: {
6
+ name: 'Planta Física (Alquiler + Servicios)',
7
+ min: 7,
8
+ max: 12,
9
+ optimal: 8,
10
+ alert_high: 15,
11
+ unit: '% de ingresos',
12
+ },
13
+ alquiler_equipos: {
14
+ name: 'Alquiler de Equipos',
15
+ min: 1,
16
+ max: 3,
17
+ optimal: 2,
18
+ alert_high: 5,
19
+ unit: '% de ingresos',
20
+ },
21
+ logistica: {
22
+ name: 'Logística',
23
+ min: 2,
24
+ max: 5,
25
+ optimal: 3,
26
+ alert_high: 7,
27
+ unit: '% de ingresos',
28
+ },
29
+ marketing: {
30
+ name: 'Marketing',
31
+ min: 2,
32
+ max: 5,
33
+ optimal: 3,
34
+ alert_high: 8,
35
+ unit: '% de ingresos',
36
+ },
37
+ administrativos: {
38
+ name: 'Administrativos',
39
+ min: 1,
40
+ max: 3,
41
+ optimal: 2,
42
+ alert_high: 5,
43
+ unit: '% de ingresos',
44
+ },
45
+ seguros: {
46
+ name: 'Seguros',
47
+ min: 0.5,
48
+ max: 2,
49
+ optimal: 1,
50
+ alert_high: 3,
51
+ unit: '% de ingresos',
52
+ },
53
+ bancarios: {
54
+ name: 'Bancarios',
55
+ min: 1,
56
+ max: 3,
57
+ optimal: 2,
58
+ alert_high: 5,
59
+ unit: '% de ingresos',
60
+ },
61
+ servicios_contratados: {
62
+ name: 'Servicios Contratados',
63
+ min: 1,
64
+ max: 3,
65
+ optimal: 2,
66
+ alert_high: 5,
67
+ unit: '% de ingresos',
68
+ },
69
+ nomina: {
70
+ name: 'Nómina',
71
+ min: 25,
72
+ max: 35,
73
+ optimal: 28,
74
+ alert_high: 40,
75
+ unit: '% de ingresos',
76
+ },
77
+ otros: {
78
+ name: 'Otros Gastos',
79
+ min: 1,
80
+ max: 5,
81
+ optimal: 2,
82
+ alert_high: 8,
83
+ unit: '% de ingresos',
84
+ },
85
+ };
86
+
87
+ // Operating Margin Benchmarks
88
+ export const MARGIN_BENCHMARKS = {
89
+ operating_margin: {
90
+ name: 'Margen Operativo',
91
+ min: 40,
92
+ max: 60,
93
+ optimal: 55,
94
+ alert_low: 35,
95
+ unit: '%',
96
+ },
97
+ expense_to_income: {
98
+ name: 'Gastos / Ingresos',
99
+ min: 40,
100
+ max: 60,
101
+ optimal: 45,
102
+ alert_high: 65,
103
+ unit: '%',
104
+ },
105
+ };
106
+
107
+ export function evaluateBenchmark(categoryKey, percentOfIncome) {
108
+ const benchmark = BENCHMARKS[categoryKey];
109
+ if (!benchmark) return { status: 'unknown', message: 'Sin benchmark disponible' };
110
+
111
+ if (percentOfIncome <= benchmark.optimal) {
112
+ return { status: 'optimal', message: `Óptimo (≤${benchmark.optimal}%)`, icon: '✅' };
113
+ }
114
+ if (percentOfIncome <= benchmark.max) {
115
+ return { status: 'normal', message: `Normal (${benchmark.min}-${benchmark.max}%)`, icon: '✓' };
116
+ }
117
+ if (percentOfIncome <= benchmark.alert_high) {
118
+ return { status: 'warning', message: `Elevado (>${benchmark.max}%)`, icon: '⚠️' };
119
+ }
120
+ return { status: 'critical', message: `Crítico (>${benchmark.alert_high}%)`, icon: '🚨' };
121
+ }
122
+
123
+ export function evaluateMargin(marginPct) {
124
+ const b = MARGIN_BENCHMARKS.operating_margin;
125
+ if (marginPct >= b.optimal) {
126
+ return { status: 'optimal', message: `Excelente (≥${b.optimal}%)`, icon: '✅' };
127
+ }
128
+ if (marginPct >= b.min) {
129
+ return { status: 'normal', message: `Saludable (${b.min}-${b.max}%)`, icon: '✓' };
130
+ }
131
+ if (marginPct >= b.alert_low) {
132
+ return { status: 'warning', message: `Bajo (<${b.min}%)`, icon: '⚠️' };
133
+ }
134
+ return { status: 'critical', message: `Crítico (<${b.alert_low}%)`, icon: '🚨' };
135
+ }
@@ -0,0 +1,39 @@
1
+ // Company Configuration - Full Queso Stores
2
+ // Uses lazy evaluation to ensure env vars are available after dotenv.config()
3
+
4
+ function getStores() {
5
+ return {
6
+ FQ01: {
7
+ companyId: process.env.BC_COMPANY_FQ01,
8
+ name: 'FQ01 Chacao',
9
+ shortName: 'Chacao',
10
+ location: 'Chacao, Caracas',
11
+ },
12
+ FQ28: {
13
+ companyId: process.env.BC_COMPANY_FQ28,
14
+ name: 'FQ28 Marqués',
15
+ shortName: 'Marqués',
16
+ location: 'El Marqués, Caracas',
17
+ },
18
+ FQ88: {
19
+ companyId: process.env.BC_COMPANY_FQ88,
20
+ name: 'FQ88 Candelaria',
21
+ shortName: 'Candelaria',
22
+ location: 'La Candelaria, Caracas',
23
+ },
24
+ };
25
+ }
26
+
27
+ export const ALL_STORE_CODES = ['FQ01', 'FQ28', 'FQ88'];
28
+
29
+ export function resolveStores(storeCodes) {
30
+ const stores = getStores();
31
+ if (!storeCodes || storeCodes.length === 0 || storeCodes.includes('all')) {
32
+ return Object.entries(stores).map(([code, info]) => ({ code, ...info }));
33
+ }
34
+ return storeCodes.map((code) => {
35
+ const info = stores[code];
36
+ if (!info) throw new Error(`Tienda desconocida: ${code}. Opciones: ${ALL_STORE_CODES.join(', ')}`);
37
+ return { code, ...info };
38
+ });
39
+ }
@@ -0,0 +1,199 @@
1
+ // Expense Accounts - Chart of Accounts (60000-99999)
2
+ // 10 Categorías de Gastos Operacionales
3
+
4
+ export const EXPENSE_CATEGORIES = {
5
+ planta_fisica: {
6
+ name: 'Planta Física',
7
+ nameEn: 'Physical Plant',
8
+ icon: '🏢',
9
+ accountRange: { min: 60000, max: 60999 },
10
+ accounts: {
11
+ 60100: { name: 'Alquiler Local', nameEn: 'Store Rent' },
12
+ 60200: { name: 'Electricidad', nameEn: 'Electricity' },
13
+ 60300: { name: 'Agua', nameEn: 'Water' },
14
+ 60400: { name: 'Gas', nameEn: 'Gas' },
15
+ 60500: { name: 'Internet/Telefonía', nameEn: 'Internet/Phone' },
16
+ 60600: { name: 'Mantenimiento Local', nameEn: 'Store Maintenance' },
17
+ 60700: { name: 'Limpieza', nameEn: 'Cleaning' },
18
+ 60800: { name: 'Seguridad Física', nameEn: 'Physical Security' },
19
+ 60900: { name: 'Otros Planta Física', nameEn: 'Other Plant Expenses' },
20
+ },
21
+ },
22
+ alquiler_equipos: {
23
+ name: 'Alquiler de Equipos',
24
+ nameEn: 'Equipment Rental',
25
+ icon: '🔧',
26
+ accountRange: { min: 61000, max: 61999 },
27
+ accounts: {
28
+ 61100: { name: 'Alquiler Equipos Refrigeración', nameEn: 'Refrigeration Equipment Rental' },
29
+ 61200: { name: 'Alquiler Equipos Cocina', nameEn: 'Kitchen Equipment Rental' },
30
+ 61300: { name: 'Alquiler POS/Tecnología', nameEn: 'POS/Tech Equipment Rental' },
31
+ 61400: { name: 'Leasing Vehicular', nameEn: 'Vehicle Leasing' },
32
+ 61900: { name: 'Otros Alquiler Equipos', nameEn: 'Other Equipment Rental' },
33
+ },
34
+ },
35
+ logistica: {
36
+ name: 'Logística',
37
+ nameEn: 'Logistics',
38
+ icon: '🚚',
39
+ accountRange: { min: 62000, max: 62999 },
40
+ accounts: {
41
+ 62100: { name: 'Combustible Vehículos', nameEn: 'Vehicle Fuel' },
42
+ 62200: { name: 'Mantenimiento Vehículos', nameEn: 'Vehicle Maintenance' },
43
+ 62300: { name: 'Peajes y Estacionamiento', nameEn: 'Tolls & Parking' },
44
+ 62400: { name: 'Servicio Delivery Terceros', nameEn: 'Third-party Delivery' },
45
+ 62500: { name: 'Embalaje y Empaque', nameEn: 'Packaging' },
46
+ 62600: { name: 'Flete y Transporte', nameEn: 'Freight & Transport' },
47
+ 62900: { name: 'Otros Logística', nameEn: 'Other Logistics' },
48
+ },
49
+ },
50
+ marketing: {
51
+ name: 'Marketing',
52
+ nameEn: 'Marketing',
53
+ icon: '📣',
54
+ accountRange: { min: 63000, max: 63999 },
55
+ accounts: {
56
+ 63100: { name: 'Publicidad Digital', nameEn: 'Digital Advertising' },
57
+ 63200: { name: 'Publicidad Impresa', nameEn: 'Print Advertising' },
58
+ 63300: { name: 'Redes Sociales', nameEn: 'Social Media' },
59
+ 63400: { name: 'Promociones y Descuentos', nameEn: 'Promotions & Discounts' },
60
+ 63500: { name: 'Eventos y Degustaciones', nameEn: 'Events & Tastings' },
61
+ 63600: { name: 'Comisiones Delivery Apps', nameEn: 'Delivery App Commissions' },
62
+ 63700: { name: 'Material POP', nameEn: 'POP Material' },
63
+ 63900: { name: 'Otros Marketing', nameEn: 'Other Marketing' },
64
+ },
65
+ },
66
+ administrativos: {
67
+ name: 'Administrativos',
68
+ nameEn: 'Administrative',
69
+ icon: '📋',
70
+ accountRange: { min: 64000, max: 64999 },
71
+ accounts: {
72
+ 64100: { name: 'Útiles de Oficina', nameEn: 'Office Supplies' },
73
+ 64200: { name: 'Software y Suscripciones', nameEn: 'Software & Subscriptions' },
74
+ 64300: { name: 'Honorarios Profesionales', nameEn: 'Professional Fees' },
75
+ 64400: { name: 'Viáticos y Representación', nameEn: 'Travel & Representation' },
76
+ 64500: { name: 'Capacitación Personal', nameEn: 'Staff Training' },
77
+ 64600: { name: 'Correo y Mensajería', nameEn: 'Mail & Courier' },
78
+ 64700: { name: 'Licencias y Permisos', nameEn: 'Licenses & Permits' },
79
+ 64900: { name: 'Otros Administrativos', nameEn: 'Other Administrative' },
80
+ },
81
+ },
82
+ seguros: {
83
+ name: 'Seguros',
84
+ nameEn: 'Insurance',
85
+ icon: '🛡️',
86
+ accountRange: { min: 65000, max: 65999 },
87
+ accounts: {
88
+ 65100: { name: 'Seguro Local', nameEn: 'Store Insurance' },
89
+ 65200: { name: 'Seguro Vehículos', nameEn: 'Vehicle Insurance' },
90
+ 65300: { name: 'Seguro Equipos', nameEn: 'Equipment Insurance' },
91
+ 65400: { name: 'Seguro Responsabilidad Civil', nameEn: 'Liability Insurance' },
92
+ 65900: { name: 'Otros Seguros', nameEn: 'Other Insurance' },
93
+ },
94
+ },
95
+ bancarios: {
96
+ name: 'Bancarios',
97
+ nameEn: 'Banking',
98
+ icon: '🏦',
99
+ accountRange: { min: 67000, max: 67999 },
100
+ accounts: {
101
+ 67100: { name: 'Comisiones Bancarias', nameEn: 'Bank Fees' },
102
+ 67200: { name: 'Comisiones Punto de Venta', nameEn: 'POS Fees' },
103
+ 67300: { name: 'Intereses Préstamos', nameEn: 'Loan Interest' },
104
+ 67400: { name: 'Comisiones Transferencias', nameEn: 'Transfer Fees' },
105
+ 67500: { name: 'Mantenimiento Cuentas', nameEn: 'Account Maintenance' },
106
+ 67900: { name: 'Otros Bancarios', nameEn: 'Other Banking' },
107
+ },
108
+ },
109
+ servicios_contratados: {
110
+ name: 'Servicios Contratados',
111
+ nameEn: 'Contracted Services',
112
+ icon: '🤝',
113
+ accountRange: { min: 68000, max: 68999 },
114
+ accounts: {
115
+ 68100: { name: 'Contabilidad Externa', nameEn: 'External Accounting' },
116
+ 68200: { name: 'Asesoría Legal', nameEn: 'Legal Advisory' },
117
+ 68300: { name: 'Consultoría', nameEn: 'Consulting' },
118
+ 68400: { name: 'Servicios IT', nameEn: 'IT Services' },
119
+ 68500: { name: 'Control de Plagas', nameEn: 'Pest Control' },
120
+ 68600: { name: 'Servicio Técnico Equipos', nameEn: 'Equipment Technical Service' },
121
+ 68900: { name: 'Otros Servicios Contratados', nameEn: 'Other Contracted Services' },
122
+ },
123
+ },
124
+ nomina: {
125
+ name: 'Nómina',
126
+ nameEn: 'Payroll',
127
+ icon: '💼',
128
+ accountRange: { min: 70000, max: 74999 },
129
+ accounts: {
130
+ 71100: { name: 'Sueldos y Salarios', nameEn: 'Salaries & Wages' },
131
+ 71110: { name: 'Sueldos Personal Tienda', nameEn: 'Store Staff Salaries' },
132
+ 71120: { name: 'Sueldos Personal Administrativo', nameEn: 'Admin Staff Salaries' },
133
+ 71200: { name: 'Horas Extra', nameEn: 'Overtime' },
134
+ 71300: { name: 'Bonificaciones', nameEn: 'Bonuses' },
135
+ 72100: { name: 'Seguro Social (IVSS)', nameEn: 'Social Security (IVSS)' },
136
+ 72200: { name: 'Política Habitacional (FAOV)', nameEn: 'Housing Policy (FAOV)' },
137
+ 72300: { name: 'INCES', nameEn: 'INCES' },
138
+ 73100: { name: 'Vacaciones', nameEn: 'Vacation Pay' },
139
+ 73200: { name: 'Utilidades', nameEn: 'Profit Sharing' },
140
+ 73300: { name: 'Prestaciones Sociales', nameEn: 'Social Benefits' },
141
+ 74100: { name: 'Cestaticket/Alimentación', nameEn: 'Meal Benefits' },
142
+ 74200: { name: 'Uniformes', nameEn: 'Uniforms' },
143
+ 74300: { name: 'Seguro HCM', nameEn: 'Health Insurance (HCM)' },
144
+ 74900: { name: 'Otros Nómina', nameEn: 'Other Payroll' },
145
+ },
146
+ },
147
+ otros: {
148
+ name: 'Otros Gastos',
149
+ nameEn: 'Other Expenses',
150
+ icon: '📦',
151
+ accountRange: { min: 80000, max: 99999 },
152
+ accounts: {
153
+ 80100: { name: 'Depreciación Equipos', nameEn: 'Equipment Depreciation' },
154
+ 80200: { name: 'Depreciación Mejoras Local', nameEn: 'Store Improvements Depreciation' },
155
+ 80300: { name: 'Amortización Intangibles', nameEn: 'Intangible Amortization' },
156
+ 85100: { name: 'Impuestos Municipales', nameEn: 'Municipal Taxes' },
157
+ 85200: { name: 'Impuesto Grandes Transacciones', nameEn: 'Large Transactions Tax' },
158
+ 90100: { name: 'Pérdidas por Merma', nameEn: 'Spoilage Losses' },
159
+ 90200: { name: 'Gastos Extraordinarios', nameEn: 'Extraordinary Expenses' },
160
+ 99900: { name: 'Otros Gastos No Clasificados', nameEn: 'Other Unclassified Expenses' },
161
+ },
162
+ },
163
+ };
164
+
165
+ // Range for all expense accounts
166
+ export const EXPENSE_RANGE = { min: 60000, max: 99999 };
167
+
168
+ export function isExpenseAccount(accountNumber) {
169
+ const num = parseInt(accountNumber, 10);
170
+ return num >= EXPENSE_RANGE.min && num <= EXPENSE_RANGE.max;
171
+ }
172
+
173
+ export function getExpenseCategory(accountNumber) {
174
+ const num = parseInt(accountNumber, 10);
175
+ for (const [key, cat] of Object.entries(EXPENSE_CATEGORIES)) {
176
+ if (num >= cat.accountRange.min && num <= cat.accountRange.max) {
177
+ return { key, ...cat };
178
+ }
179
+ }
180
+ return null;
181
+ }
182
+
183
+ export function getAccountName(accountNumber) {
184
+ const num = parseInt(accountNumber, 10);
185
+ for (const cat of Object.values(EXPENSE_CATEGORIES)) {
186
+ if (cat.accounts[num]) {
187
+ return cat.accounts[num].name;
188
+ }
189
+ }
190
+ return `Cuenta ${accountNumber}`;
191
+ }
192
+
193
+ export function getAllExpenseAccountNumbers() {
194
+ const numbers = [];
195
+ for (const cat of Object.values(EXPENSE_CATEGORIES)) {
196
+ numbers.push(...Object.keys(cat.accounts).map(Number));
197
+ }
198
+ return numbers;
199
+ }
@@ -0,0 +1,40 @@
1
+ // Revenue & Cost Accounts - Chart of Accounts
2
+ // Revenue: 40000-49999 (credit balance = income)
3
+ // COGS: 50000-59999 (debit balance = cost of goods sold)
4
+
5
+ export const REVENUE_ACCOUNTS = {
6
+ 40110: { name: 'Ventas Productos (Pedidos)', nameEn: 'Product Sales (Orders)' },
7
+ 40160: { name: 'Ventas Productos (Órdenes)', nameEn: 'Product Sales (Orders)' },
8
+ 40310: { name: 'Ventas Facturación Directa', nameEn: 'Direct Invoice Sales' },
9
+ 40450: { name: 'Ingresos Varios Pedidos', nameEn: 'Misc Order Revenue' },
10
+ 40460: { name: 'Ingresos Varios Órdenes', nameEn: 'Misc Order Revenue' },
11
+ 40520: { name: 'Ventas Diarias', nameEn: 'Daily Sales' },
12
+ 40580: { name: 'Cobros y Pagos', nameEn: 'Collections & Payments' },
13
+ 40920: { name: 'Ventas Efectivo', nameEn: 'Cash Sales' },
14
+ };
15
+
16
+ export const COGS_ACCOUNTS = {
17
+ 50110: { name: 'Costo Directo Intercompañía', nameEn: 'Direct Cost Intercompany' },
18
+ 50120: { name: 'Costo de Ventas', nameEn: 'Cost of Goods Sold' },
19
+ 50190: { name: 'Costo Directo Producción', nameEn: 'Direct Production Cost' },
20
+ 51401: { name: 'Compras Mercancía', nameEn: 'Merchandise Purchases' },
21
+ 52010: { name: 'Costo Intercompañía', nameEn: 'Intercompany Cost' },
22
+ 52040: { name: 'Otros Costos Compras', nameEn: 'Other Purchase Costs' },
23
+ 56020: { name: 'Costos Proveedores', nameEn: 'Supplier Costs' },
24
+ 57010: { name: 'Costos Varios', nameEn: 'Miscellaneous Costs' },
25
+ 57020: { name: 'Costos Diversos', nameEn: 'Diverse Costs' },
26
+ };
27
+
28
+ // Ranges
29
+ export const REVENUE_RANGE = { min: 40000, max: 49999 };
30
+ export const COGS_RANGE = { min: 50000, max: 59999 };
31
+
32
+ export function isRevenueAccount(accountNumber) {
33
+ const num = parseInt(accountNumber, 10);
34
+ return num >= REVENUE_RANGE.min && num <= REVENUE_RANGE.max;
35
+ }
36
+
37
+ export function isCOGSAccount(accountNumber) {
38
+ const num = parseInt(accountNumber, 10);
39
+ return num >= COGS_RANGE.min && num <= COGS_RANGE.max;
40
+ }